CakePHP

みなさんこんにちは!

最近画像アップロードプラグインを色々試しているのでメモしておきますー

今日は「Upload」というプラグインで画像をアップしてみようと思います! それではいってみましょー!

このプラグインとしての特徴としては

  • 画像をアップロードする機構を提供するがヘルパーは提供しない(自作ヘルパーの必要性)
  • サムネイルが作れる(Imagickが必要)
  • PDFのアップロードも可能

くらいでしょうか。アップロード処理に必要なものは大体揃ってるイメージです。

CakePHPとUploadのインストール

ここらへんはComposerでちょちょっと!

composer.json

{
    "name": "upload-example",
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        }
    ],
    "require": {
        "pear-cakephp/cakephp": "2.4.*",
        "josegonzalez/cakephp-upload": "1.0.0"
    }
}

インストール!

$ composer install

オールグリーンにする設定はこちらを参照!

bootstrap.phpにUploadプラグインを書くコードを書いて終了!

bootstrap.php

CakePlugin::load('Upload');

これでインストール終了です!

Uploadプラグインの設定

それではコード書いていきましょう。

まずは

Usersテーブルを作成し、アップロードした画像パスを保存する領域も作っておきましょう。

CREATE table users (
    `id` int(10) unsigned NOT NULL auto_increment,
    `username` varchar(20) NOT NULL,
    `photo` varchar(255) DEFAULT NULL,
    `photo_dir` varchar(255) DEFAULT NULL,
    PRIMARY KEY (`id`)
);

app/Model/User.php

 [
            'photo' => [
                'fields' => [
                    'dir' => 'photo_dir'
                ]
            ]
        ]
    ];
}

こうしておけばphoto_dirに勝手に画像へのパスが入ります。

Viewはこんな感じ

View/Posts/index.ctp

Form->create('User', array('type' => 'file')); ?>
    Form->input('User.username'); ?>
    Form->input('User.photo', array('type' => 'file')); ?>
    Form->input('User.photo_dir', array('type' => 'hidden')); ?>
Form->end('Submit'); ?>

PostsControllerも簡潔な感じ

request->is('post')){
            $this->User->save($this->request->data);
        }
    }

}

これで画像がwebroot/files以下にアップされます!

アップロードする場所を変えたりサムネイル作ったりしてみる

User.phpを少し変えます

 [
            'photo' => [
                'fields' => [
                    'dir' => 'photo_dir'
                ],
                'thumbnailSizes' => [
                    'thumb150' => '150x150',
                    'normal' => '200x200',
                    'big' => '500x500'
                ],
                'path' => '{ROOT}tmp{DS}files{DS}{model}{DS}{field}{DS}',
                'thumbnailMethod' => 'php'
            ]
        ]
    ];
}

画像をアップロードする場所をとりあえずtmpフォルダに変えました。なんとなく。 thumbnailSizesとして色々登録しておけばそのサイズに切り出してくれます。 thumbnailMethodでphpを指定していますが、imagickがない場合はこのオプションを指定しておくとimagickではなくphpでリサイズを行ってくれます。

これで先ほどのViewからアップロードしてみてください。 tmpフォルダにアップされたはずです。 あとアップロードされた画像の前にbigとかnormalとかついたはずです。こんな感じでリサイズされるってことですね!

最後に

いかがでしたでしょうか。 画像アップロードできましたか? アップロード処理は結構時間がかかる部分なのでこういう便利なプラグインを使ってサクッと終わらせたいですね!

定番レシピを勉強するならこれ!

Webアプリ開発を加速する CakePHP2定番レシピ119
長谷川 智希
秀和システム
売り上げランキング: 138,433
Read more

こんにちは!

そういえばComposerでCakePHPをインストールしたあと実際にアプリ開発を始めるまでの記事を書いていないことに気づいたので書きます!! Composer、最初いじったときどうしたらいいのかわからなかったのですが最近やっと慣れてきてもう手放せない子になりつつあるのでもしまだ使ってない!って方の手助けが出来ればいいなぁなんて思ってます!

それではやっていきましょう!!

Composerをダウンロード

それではいきます!

Composerとは(簡単に)

composerはライブラリの依存関係を解決してくれるツールです。 例えばあるプロジェクトではAというプラグイン、Bというプラグイン、Cというプラグインが入っていないと動かない!みたいなことがあったとします。 そのときドキュメントを作って1個ずつダウンロードしてインストールして・・・ってやってると、正直やってられないですよね。 あとプラグインも日々バージョンアップが行われている可能性があるので、どのバージョンのプラグインか、というのも考慮しておかなければいけません。 そういう面倒なことを吸収してくれるのがcomposerなのですね!便利!超カンタンに説明した気がする!

composerの公式ページはこちら

composerはダウンロードして動かします。 インストールすることも可能なのでやってみたい人はぐぐってやってみてください!

プロジェクトを作りたいフォルダ内で

$ curl -sS https://getcomposer.org/installer | php

これでcomposer.pharというファイルがダウンロード出来ました。これがcomposerの本体になります。

composer.jsonを書いてインストール!

composer.jsonに依存関係を記述していきます。

以下のように書くとcakephpをインストールすることが出来ます。pearレポジトリにcakephpが入っているのでそこを参照するようにします。

{
    "name": "cakephp_test",
    "repositories": [
    {
        "type": "pear",
        "url": "http://pear.cakephp.org"
    }
    ],
    "require": {
        "pear-cakephp/cakephp": "2.4.*",
        "cakephp/debug_kit": "2.2.*"
    }
}

あとでCakePHPの初期画面をオールグリーンにするため、プラグインの入れ方を書くためにDebugKitも入れておきます。 composerへの書き方はそれぞれのrepositoryのREADMEなどに書いてあることが多いので調べてみましょう。

ここまで書けたらインストールしましょう。 以下のコマンドでインストール出来ます!

$ php composer.phar install

うまくいけばカレントディレクトリにvendorフォルダとPluginフォルダ、あとそれぞれのライブラリのバージョン情報が格納されているcomposer.lockが出来たはずです。 vendorフォルダ内にはCakePHP本体が、PluginフォルダにはDebugKitが入っているはずです。 これでインストール完了です!!

CakePHP2でプロジェクト作成して開発の準備をする

ここまで出来たら開発が出来るまでもう少しです!

次はCakePHPのbakeコマンドを使ってプロジェクトを作成します。

$ vendor/bin/cake bake project $PWD

Welcome to CakePHP v2.4.4 Console
---------------------------------------------------------------
App : composer
Path: ちょめちょめ
---------------------------------------------------------------
Skel Directory: ちょめちょめ
Will be copied to: ちょめちょめ
---------------------------------------------------------------
Look okay? (y/n/q)
[y] > y

これでプロジェクトが作成されました。見慣れたCakePHPのディレクトリ構造が出来ていると思います。

ターミナルに表示されているように include_pathを直さなければならないのでなおしていきましょう。 またcakeコマンドもどんな環境でも動くように設定しておきます。

webroot/index.php

define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . APP_DIR . DS . 'vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

webroot/test.php

define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . APP_DIR . DS . 'vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

Console/cake.php

$ds = DIRECTORY_SEPARATOR;
$dispatcher = 'Cake' . $ds . 'Console' . $ds . 'ShellDispatcher.php';

if (function_exists('ini_set')) {
    $root = dirname(dirname(__FILE__));

    // the following line differs from its sibling
    // /app/Console/cake.php
    ini_set('include_path', $root . $ds . 'vendor' . $ds . 'pear-pear.cakephp.org' . $ds . 'CakePHP' . PATH_SEPARATOR . ini_get('include_path'));
}

if (!include $dispatcher) {
    trigger_error('Could not locate CakePHP core files.', E_USER_ERROR);
}
unset($paths, $path, $dispatcher, $root, $ds);

return ShellDispatcher::run($argv);

最後にデータベースの情報をbakeして終了!

$ Console/cake bake db_config

ここまでで以下のような画面になっているはずです。

DebugKitを読み込む

それでは最後にDebugKitを読み込んでみましょう。

といってももう既にインストールされてるしPluginフォルダ内にあるのでプラグインを読み込むにはbootstrap.phpに書き込むだけで終わりです。

bootstrap.php に追記

CakePlugin::load('DebugKit');

これでオールグリーン!!

最後に

これでComposerを使ったCakePHP開発出来るはずです。 僕も最初は手探りで、本当に便利か?と思っていたのですが慣れてしまえばもうこれ無しでは考えられないです。 webroot/index.phpとかをいじるのが面倒なのですが、CakePHP3からこの作業もなくなるのでより便利になります。 誰かの役に立てればいいなぁ。

手元にあると便利です

CakePHP2 実践入門 (WEB+DB PRESS plus)
安藤 祐介 岸田 健一郎 新原 雅司 市川 快 渡辺 一宏 鈴木 則夫
技術評論社
売り上げランキング: 43,078
Read more

みなさんこんにちは!

ついにCakePHP3のdev preview 1が出ましたね! CakePHP 3.0.0 dev preview 1 released

テンション上がったので少しですが試してみようと思います。 CakePHP自体の開発スピードが最近上がってきたと感じるのでわくわくしてます。 それでは一緒にいじっていきましょう!

CakePHP3をインストールする

CakePHP3からComposerが必須になったのでComposerからインストールします。

$ curl -sS https://getcomposer.org/installer | php
$ php composer.phar create-project -s dev cakephp/app .

これでインストール完了です!簡単!ひゃっほーい!

CakePHP2からはdatabase.phpがなくなり、app.phpに統合されたのでapp.phpからデータベースの設定を行いましょう。 これでとりあえずインストール完了です。 おそらくCakePHP3の安定版ではデータベース設定もbake出来るとは思うのですが今のところbakeが動かないようなので手動で設定しました。

これでいつもの緑画面が出てきたと思います。(DebugKitは気分的に入れてみました。オールグリーン、気持ちいいので)

データベースには以下のテーブルとデータを挿入しました。

CREATE TABLE IF NOT EXISTS `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) NOT NULL,
  `content` text NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ;

INSERT INTO `posts` (`id`, `title`, `content`, `created`, `modified`) VALUES
(1, 'test1', 'test1の内容です', NOW(), NOW()),
(2, 'test2', 'test2の内容です', NOW(), NOW()),
(3, 'test3', 'test3の内容です', NOW(), NOW());

次で実際に触ってみます。

コードを書いてみる

今回やることはさきほど入れたデータを表示したりCakePHP3から実装されたEntityやRepositoryを使って遅延評価を行ったりしてみたいなぁと思います。

それでは早速書いていきましょう。

まずはPostsController.phpから書いていきます。

PostsController.php

set('posts', TableRegistry::get('Posts')->find('all'));
    }
}

namespaceとかuse使ってる部分がモダンw 見ればわかるとは思いますが、Postsテーブルのインスタンスを作り、全件取得しています。 それをViewにpostsという変数名でオブジェクトを流し込んでいます。 namespaceにはそのファイルの場所名を書きます。今回のControllerであればApp/Controller以下にあるので上記のようになっています。

それではModelを書いていきましょう。 僕もまだEntityやRepositoryは厳密にどのようなものか理解出来ていないのですが、Entityにはモデルのロジックを、Repositoryにはデータベースのリレーションやバリデーションを書いていくようです。(あんまわかってない。誰か教えて!)

まずRepositoryのほうから作ります。

App/Model/Repository/PostsTable.php

<?php namespace App\Model\Repository;

use Cake\ORM\Table;

class PostsTable extends Table {

}

今回はリレーションやバリデーションはかけないので何も書きません。 次にEntity

App/Model/Entity/Post.php

<?php namespace App\Model\Entity;

use Cake\ORM\Entity;

class Post extends Entity {

}

Entityのほうには後から関数を定義してみようと思います。

そして最後にView これでとりあえず動くはずです。

App/View/Posts/index.ctp


    id;?>

これで以下の画像のように数字が出てくるはずです。

foreach$postsをイテレートしてidを表示してみました。もちろんidの部分をtitlecontentに変えればそれを表示することが出来ます。

Viewに渡されたオブジェクトからEntityモデルを叩く

今回はViewに$postsという変数名でオブジェクトを渡しましたが、ここからPostEntityを叩くことが出来ます。

App/Model/Entity/Post.phpにメソッドを追加します。

App/Model/Entity/Post.php

public function getFullContent(){
    return $this->_properties['title'] . ":" . $this->_properties['content'];
}

そしてViewも変えます。

App/View/Posts/index.ctp


    full_content . "
";?>

これで以下のような表示が出来ます。

Entityのほうでgetから始めた関数はViewのオブジェクト側でgetを除いたアンダースコア記法で呼び出せるのです! これ超ベンリ!感動しちゃいました。Railsの感覚に近い。。

最後に

僕も対してわかっていないのでとりあえず動かしてみてこういうことが出来る、こういうことが出来なくなった、というのを1つずつ確認している段階です。 また開発バージョンなのでこれから変更がかかる可能性があります。 開発が終わるのがいつになるかわかりませんが、少しずつでも追っかけていると移行が早く出来て便利かもしれませんね!

CakePHPをこれから始めたい人は持っておくといいかも

Webアプリ開発を加速する CakePHP2定番レシピ119
長谷川 智希
秀和システム
売り上げランキング: 65,741
Read more

みなさんこんにちは!!

以前CakePHPとWordPressでアプリを組んだのでリレーションとか色々書いていこうと思います。 WordPressではデータを入力、保存するのみで、主なデータ表示はCakePHPで行いました。

WordPressのデータをCakePHPで取得するために設定する

CakePHPとWordPressはインストール済み、また記事は何個か入っている、タグも何個か設定してある前提でやっていきます。

今回やるのは「記事からその記事に結びついているタグを表示する」ことと「タグからそのタグが結びついている記事を表示する」ことです。 自分でもちょっとおかしいと思うところは結構ありますw

WordPressのデータベースの構造は以下を見ればわかります。 http://wpdocs.sourceforge.jp/データベース構造

CakePHPではきつそうな部分が混ざってますがそこはスルーしましょう。 ここでやりたいことは記事に対するタグを引っ張ってくることです。

それではやっていきましょう。 いじっていくテーブルは、wp_postswp_term_relationshipswp_term_taxonomywp_termsの4つです。 なのでそれに対応するModelを作っていきます。

wp_postswp_term_relationshipsを持っているので以下のように書きます。

Posts.php

App::uses('AppModel', 'Model');

class Post extends AppModel {

    public $primaryKey = 'ID';

    public $useTable = 'wp_posts';

    public $name = 'Post';

    public $tablePrefix = 'wp_';

    public $hasMany = array(
        'TermRelationship' => [
            'className' => 'TermRelationship',
            'foreignKey' => 'object_id'
        ]
    );
}

wp_term_relationshipsはこんな感じです。ちょっとつっこみたいのはよくわかる。。。

TermRelationship.php

App::uses('AppModel', 'Model');

class TermRelationship extends AppModel {

    public $useTable = 'wp_term_relationships';

    public $name = 'TermRelationship';

    public $tablePrefix = 'wp_';

    public $belongsTo = [
        'TermTaxonomy' => [
            'className' => 'WpTermTaxonomy',
            'foreignKey' => 'term_taxonomy_id',
        ]
    ];
}   

wp_term_taxonomy

TermTaxonomy.php

App::uses('AppModel', 'Model');
class WpTermTaxonomy extends AppModel {

    public $name = 'TermTaxonomy';

    public $useTable = 'wp_term_taxonomy';

    public $primaryKey = 'term_taxonomy_id';

    public $tablePrefix = 'wp_';

    public $belongsTo = [
        'WpTerm' => [
            'className' => 'WpTerm',
            'foreignKey' => 'term_id'
        ]
    ];

}

そして最後。wp_terms

Term.php

App::uses('AppModel', 'Model');

class Term extends AppModel {

    public $name = 'Term';

    public $primaryKey = 'term_id';

    public $tablePrefix = 'wp_';

    public $displayField = 'name';

    public $hasOne = [
        'TermTaxonomy' => [
            'className' => 'TermTaxonomy',
            'foreignKey' => 'term_taxonomy_id'
        ]
    ];

}

これで設定完了です!お疲れ様です!

動作確認・実際に表示してみる

それでは最後に動作確認をしてみましょう。 たくさんのRelationを使うときはContainableBehaviorが便利なのでAppModelで読み込んでおきましょう。

AppModel.php

App::uses('Model', 'Model');

class AppModel extends Model {

    public $actsAs = ['Containable'];

}

動作確認です。まずは記事からタグを引っ張りだす!

PostsController.php

App::uses('AppController', 'Controller');

class PostsController extends AppController {

    public function index(){
        $articles = ClassRegistry::init('Post')->find('all', 
            ['contain' => [
                'TermRelationship' => [
                    'TermTaxonomy' => [
                        'Term'
                    ]
                ]
            ]
        );
        debug($articles);
    }
}

階層の深い部分にありますがタグが一覧表示されていると思います!

ここまでわかれば簡単!タグから記事を表示しましょう。 PostsController.php

App::uses('AppController', 'Controller');

class PostsController extends AppController {

    public function index(){
        $tags = ClassRegistry::init('Term')->find('all', 
            ['contain' => [
                'TermTaxonomy' => [
                    'TermRelationship' => [
                        'Post'
                    ]
                ]
        );
        debug($tags);
    }
}

最後に

いかがでしたでしょうか。 少し無理やりでしたが、目標は達成出来たかと思います。 CakePHPのリレーションは「_id」がついているものを勝手につなげますが、それを利用したものと言えます。 もっと綺麗に実装出来そうですが、実際に実装していく場合は、findの条件の部分を覆い隠せば綺麗になると思います。

覆い隠す場合は以下の記事が参考になるかもしれません。

findせずにconditionsを返す、DRYなCakePHPのModel実装のススメ

CakePHPerなら持っておかねば!

Webアプリ開発を加速する CakePHP2定番レシピ119
長谷川 智希
秀和システム
売り上げランキング: 34,461
Read more

まだまだあけましておめでとうございます。 年始をいかがお過ごしでしょうか。

私はいつものように、日常と変わらない生活をしています。

さてさて、ユニットテストの件ですが、以前更新して随分時間が経ってしまいましたが、書いていこうかなぁと思います。

今回はバリデーションをテストするための方法を書こうと思います。 前回の routes.php に対するテストの記事は以下になります。 CakePHP2でユニットテスト 〜route.php編〜

バリデーションを適応していく

今回はUserモデルに対してnamenotEmptyを、telに対して独自のバリデーションであるcheckNotEmptynumericOnlyを適応していると仮定します。

User.php

App::uses('AppModel', 'Model');

class User extends AppModel {
    public $validate = [
        'name' => [
            'rule' => ['notEmpty'],
            'message' => '名前を入力して下さい。'
        ],
        'tel1' => [
            'notEmpty' => [
                'rule' => ['checkNotEmpty'],
                'message' => '電話番号を入力して下さい。'
            ],
            'numericOnly' => [
                'rule' => ['numericOnly'],
                'message' => '数字のみ入力して下さい。'
            ]
        ]
    ];

    public function checkNotEmpty(){
        if(!empty($this->data[$this->alias]['tel1']) && 
           !empty($this->data[$this->alias]['tel2']) && 
           !empty($this->data[$this->alias]['tel3'])) {
            return true;
        } else {
            return false;
        }
    }

    public function numericOnly(){
        // 数字のみ許可
        $pattern = '/^[0-9]+$/';
        $tel1 = preg_match($pattern, $this->data[$this->alias]['tel1']);
        $tel2 = preg_match($pattern, $this->data[$this->alias]['tel2']);
        $tel3 = preg_match($pattern, $this->data[$this->alias]['tel3']);
        if(empty($tel1) && empty($tel2) && empty($tel3)){
           return true;
        } else {
           return false;
        }
    }
}

こういうモデルに対してテストを書いていこうと思います。

バリデーションに対するモデルを書く

テストを書いていきます。

Test/Case/Model/UserTest.php

App::uses('User', 'Model');

class UserTest extends CakeTestCase {

    public function setUp() {
        parent::setUp();
        $this->User = ClassRegistry::init('User');
    }

    public function tearDown() {
        unset($this->User);
        parent::tearDown();
    }

    public function testUserValidations(){
        $baseCase = [
            'name' => '佐藤'
            'tel1' => '090'
            'tel2' => '0088'
            'tel3' => '8800'
        ];

        $testCases = [
            true => [
                ['name' => '谷'],
                ['tel1' => '03', 'tel2' => '1111', 'tel3' => '2222']
            ],
            false => [
                ['name' => ''],
                ['tel1' => ''],
                ['tel2' => -1]
            ]
        ];

        foreach($testCases as $expected => $cases){
            foreach($cases as $case){
                $testCase = array_merge($baseCase, $case);
                $this->User->set($testCase);
                $result = $this->Entry->validates();
                if((boolean)$expected){
                    $this->assertTrue($result);
                } else {
                    $this->assertFalse($result);
                }
            }
        }
    }

}

こんな感じでテストを書いていきます。 このやり方であればテストケースを変更するのも簡単だし、trueとfalseで固まっているのでわかりやすいかと思います。

最後に

いかがでしたでしょうか。

今回もroutes.phpのテストと同じく、直感的でわかりやすかったかと思います。 ユニットテストは自分が安心するのもありますし、機能毎の詳細な仕様書にもなります。 なのでしっかりテストを書いていきましょう。(面倒くさいけど)

CakePHPで新しい本が出てました。読まねば。

Webアプリ開発を加速する CakePHP2定番レシピ119
長谷川 智希
秀和システム
売り上げランキング: 22,052
Read more

みなさんあけましておめでとうございます! 2ヶ月くらい更新してなかったですが、ブログは書き続けてたほうがいいなぁと思っていたので新年からまた書き始めます!

それでは早速書いていきましょう。

最近Composerを使ってCakePHPでFacebookログインを実装したので、そこまでの流れをまとめておこうと思います。

Composerで必要なものをインストールする

composer.jsonから作っていきましょう。

CakePHPFacebook PHP SDKをインストールするので以下のように書けばインストール出来ます。

{
    "name": "facebook-login",
    "repositories": [
        {
            "type": "pear",
            "url": "http://pear.cakephp.org"
        }
    ],
    "require": {
        "pear-cakephp/cakephp": "2.4.4",
        "facebook/php-sdk": "dev-master"
    }
}

composer.pharをダウンロードしてパッケージをインストールしていきます。

$ curl -s http://getcomposer.org/installer | php
$ php composer.phar install

Bakeでプロジェクトを作成します。

$ vendor/bin/cake bake project $PWD --empty

これでブラウザからアクセスすると、いつもの緑画面が出てくると思います。

ここまでで大方終了しましたが忘れちゃいけないので CAKE_CORE_INCLUDE_PATH を書き換えること。

3箇所書き換えていきましょう。

webroot/index.php

define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . APP_DIR . DS . 'vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

webroot/test.php

define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . APP_DIR . DS . 'vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

Console/cake.php

$ds = DIRECTORY_SEPARATOR;
$dispatcher = 'Cake' . $ds . 'Console' . $ds . 'ShellDispatcher.php';

if (function_exists('ini_set')) {
    $root = dirname(dirname(__FILE__));

    // the following line differs from its sibling
    // /app/Console/cake.php
    ini_set('include_path', $root . $ds . 'vendor' . $ds . 'pear-pear.cakephp.org' . $ds . 'CakePHP' . PATH_SEPARATOR . ini_get('include_path'));
}

if (!include $dispatcher) {
    trigger_error('Could not locate CakePHP core files.', E_USER_ERROR);
}
unset($paths, $path, $dispatcher, $root, $ds);

return ShellDispatcher::run($argv);

最後にデータベースをbakeして終了っと!

$ Console/cake bake db_config

実装前の準備をする(Facebookアプリ作成、SDKの読み込み、データベース等)

次にFacebookアプリやFacebookSDKの読み込み、データベースの作成等を行っていきます。

以下のURLに飛び、Facebookアプリを「新規作成」し、アプリIDとシークレットをゲットしておいてください!

次にcomposerでいれたFacebook PHP SDKを読み込んでいきます。 PagesController.php で読み込んでいきましょう。

PagesController.php

<?php App::uses('AppController', 'Controller');
App::import('vendor', 'facebook/php-sdk/src/facebook');

最後にデータベースです。 今回は Admin テーブルを作成しその中にログインに必要な情報を事前に入れておこうと思います。 実際に試す場合は自分のFacebookIDを入れておいてください。

CREATE TABLE IF NOT EXISTS `admin` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `facebook_id` varchar(128) NOT NULL,
  `created` datetime NOT NULL,
  `modified` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

INSERT INTO `admin_user` (`id`, `facebook_id`, `created`, `modified`) VALUES
(1, '自分のFacebookID', NOW(), NOW());

最後の最後に! routes.php 内でPagesControllerのルーティングが定められているのでそれをコメントアウトしておきます。

    // Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));

これで終了です。やっと本題にいきましょう!

Facebookログインを実装していく

さて、やっと本題です。 ログインの流れとしては、

  • ログインボタンクリック
  • Facebook側で認証をする
  • 認証後リダイレクト先でFacebookIDが取れ、データベース内にそのIDがあればログインを完了させマイページに飛ばす

こんな感じでやっていこうと思います。

一気にいきます。PagesController.phpにがばっと書いていきます。

PagesController.php

 [
            'authenticate' => [
                'Form' => [
                    'userModel' => 'Admin'
                ]
            ],
            // ログインページ
            'loginAction' => ['controller' => 'pages', 'action' => 'index'],
            // ログイン後リダイレクトページ
            'loginRedirect' => ['controller' => 'pages', 'action' => 'my'],
            // ログアウト後リダイレクト先
            'logoutRedirect' => ['controller' => 'pages', 'action' => 'index'],
            // エラーメッセージ
            'authError' => 'Admin Only'
        ]
    ];

    public function beforeFilter(){
        $this->facebook = new Facebook([
            'appId' => '取得したAppId',
            'secret' => '取得したAppSecret',
            'cookie' => true
        ]);
        $this->Auth->allow('index', 'login');
    }

    protected function _authFacebook(){
        $login_url = $this->facebook->getLoginUrl(['redirect_uri' => Router::fullBaseUrl() . Router::url(['controller'=>'pages', 'action'=>'login'])]);
        return $this->redirect($login_url);
    }

    public function login(){
        $this->autoRender = false;
        $fb_id = $this->facebook->getUser();
        $login_info = ClassRegistry::init('Admin')->find('first', ['conditions' => ['Admin.facebook_id' => $fb_id]]);
        if(isset($login_info)){
            if($this->Auth->login($login_info['Admin'])){
                return $this->redirect(['action' => 'my']);
            } else {
                return $this->redirect(['action' => 'index']);
            }
        } else {
            return $this->redirect(['action' => 'index']);
        }
    }

    public function logout(){
        $this->Auth->logout();
        return $this->redirect(['action' => 'index']);
    }

    public function index(){
        if($this->Auth->loggedIn()){
            return $this->redirect(['action' => 'my']);
        }
        if($this->request->is('post')){
            $this->_authFacebook();
        }
    }

    public function my(){

    }

}

わからなければ質問なり公式ドキュメントを参照してください!!!

Modelのフォルダ内に Admin.php を下記のような感じで作成します。

<?php App::uses('AppModel', 'Model');

class Admin extends AppModel {

    public $useTable = 'admin';

}

Viewは index.ctpmy.ctp を作ります。

index.ctp

Form->create('Admin', ['method' => 'post']);?>

Form->button('Facebookログイン');?>

my.ctp

マイページ

Html->link('ログアウト', ['controller'=>'pages', 'action'=>'logout']);?>

これでFacebookログインが実装できました!!

最後に

いかがでしたでしょうか。 なんとなくの流れは理解出来たかなぁと思います。 何かわからない点がありましたらコメントやTwitterで聞いていただければと思います!

CakePHP本が続々出ていますね。

Webアプリ開発を加速する CakePHP2定番レシピ119
長谷川 智希
秀和システム
売り上げランキング: 15,164
Read more

こんにちは! 最近流行り?のComposerを使ってCakePHPを導入していましたが、色々使いにくいなぁと思っていたのですが、それは僕が使い慣れてないだけだったので、これからCakePHPをComposerで導入しようと思っている人向けにやるべき初期設定を書こうと思います。

それでは始めましょう

composer.jsonの設定

composer.jsonの設定は以下のような感じです。 本当に最低限のインストールしかしない設定になっています。

{
    "name": "test-app",
    "repositories": [
        "type": "pear",
        "url": "http://pear.cakephp.org"
    ],
    "require": {
        "pear-cakephp/cakephp": "2.3.*"
    }
    "config": {
        "vendor-dir": "Vendor/"
    }
}

このあとにcomposer.pharを用いてインストールを行います。 ここらへんは公式ドキュメントが詳しいのでそちらを参照下さい。

編集すべき部分と編集内容

アプリを作成することが出来たら、.htaccessなど、変更する部分はあると思いますが、composerでインストールしたときの編集すべき特有な部分は3つあります。

  • app/webroot/index.php
  • app/webroot/test.php
  • app/Console/cake.php

それぞれのファイルの変更点は以下に書きます。

app/webroot/index.php CAKE_CORE_INCLUDE_PATH部分

define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'Vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

app/webroot/test.php CAKE_CORE_INCLUDE_PATH部分

define('CAKE_CORE_INCLUDE_PATH', ROOT . DS . 'Vendor' . DS . 'pear-pear.cakephp.org' . DS . 'CakePHP');

app/Console/cake.php

if (function_exists('ini_set')) {
    $root = dirname(dirname(dirname(__FILE__))).$ds.'Vendor'.$ds.'pear-pear.cakephp.org'.$ds.'CakePHP';

    // the following line differs from its sibling
    // /app/Console/cake.php
    ini_set('include_path', $root . PATH_SEPARATOR .  $ds . 〜〜));
}

これを書いてあげることで、どの環境でも設定を変更することなく、動作するようになります。 僕は、ROOT定数を使うという発想がなかったので、各環境においてそれぞれの絶対パスを全て打ち込んでいました。プログラマーとしてまだまだですね。。。

最後に

公式ドキュメントにも同じようなことが書いてありますが、明示的に3つ編集すべきだよ!っていう記事があればさらに便利だなーと思ったのでこんな記事を書いてみました。 僕はまだまだcomposerを使いこなすことが出来ていませんが、もし3つ編集するんじゃなくて、別の方法があるよ!っていうことがあれば教えてくださると助かります!

CakePHPをやるのであればこの本は持っていて損はないです!よく訳に立ってます!

CakePHP2 実践入門 (WEB+DB PRESS plus)
安藤 祐介 岸田 健一郎 新原 雅司 市川 快 渡辺 一宏 鈴木 則夫
技術評論社
売り上げランキング: 17,808
Read more

みなさんこんにちは。 今回はCakePHP2でユニットテストをする方法を書いていきます。 僕は最近やっとCakePHPを使い始めて1年経過しましたが、多少大きな開発になるとやはりテストを書いたほうが早く開発が出来ますね。

また、やはりなんといってもテストがあるととにかく安心する!←これ大事

開発を進めていくにあたって、安心というものは最高に重要ですね。 僕は普段忙しくても、routeのテスト、モデルのテストは必ず書くようにしています。 最低限ここらへんのテストが正常であれば動くので。

viewとcontrollerは変更が激しいのでなかなか書かないんですけどね。。。

さて、前置きはこれくらいにして、これから何回かにわけて

  • route.phpのテスト
  • Modelのテスト
  • Controllerのテスト
  • Viewのテスト

を書いていきたいと思います。

それでは今回はroute.phpのテストを書いていきます。

適当にroute.phpに記述しよう

まずはCakePHPをインストールしてください。

インストール直後のroute.phpには以下の記述があるだけだと思います。

route.php

Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));

これだけでも十分テストを書いていけるのですが、もう1つくらいパターンの違うものを書いておきましょう。 route.phpに以下のコードを追加してください。

Router::connect('/circle', array(
        'controller' => 'pages',
        'action' => 'circle',
        'tag' => 'circle'
    ));

これで準備は出来ました。次はこのroute.phpに対するテストを書いていきましょう。

記述したroute.phpに合わせてテストを書こう

現在route.phpには以下の3つのRouterが設定されていると思います。

route.php

Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
Router::connect('/circle', array(
        'controller' => 'pages',
        'action' => 'circle',
        'tag' => 'circle'
    ));

これらのroute.phpに対してテストを書いていきます。 app/Test/Case以下に ‘AppRoutesTest.php’ファイルを作成しファイルに以下の内容を書き込んでください。

AppRoutesTest.php

App::uses('Router', 'Routing');

class AppRoutesTest extends CakeTestCase {

    public function setUp(){
        parent::setUp();
        require APP . 'Config' . DS . 'routes.php';
    }

    public function tearDown(){
        Router::reload();
        parent:tearDown();
    }

    // ここからテスト!!
    public function testTopPage(){
        $expects = array(
            'controller' => 'pages',
            'action' => 'display'
        );
        $url = Router::parse('/');
        $this->assertEqual($expects['controller'], $url['controller']);
        $this->assertEqual($expects['action'], $url['action']);
    }

    public function testPagesAll(){
        $expects = array(
            'controller' => 'pages',
            'action' => 'display',
            'pass' => array(1)
        );
        $url = Router::parse('/pages/1');
        $this->assertEqual($expects['controller'], $url['controller']);
        $this->assertEqual($expects['action'], $url['action']);
        $this->assertEqual($expects['pass'], $url['pass']);
    }

    public function testCirclePage(){
        $expects = array(
            'controller' => 'pages',
            'action' => 'circle',
            'tag' => 'circle'
        );
        $url = Router::parse('/circle');
        $this->assertEqual($expects['controller'], $url['controller']);
        $this->assertEqual($expects['action'], $url['action']);
        $this->assertEqual($expects['tag'], $url['tag']);
    }
}

これでテストを書くことが出来ました! コードの中身の説明ですが・・・

setUp()はテストが行われる前に実行される関数、tearDown()はテストが完了したあとに実行される関数です。 またそれぞれのテストケースですが、まず最初に$expects変数の中に、正しくルーティングが設定してあれば返ってくるであろう配列を書いておきます。 そしてRouter::parse()でURLをパースします。パースすることによって、route.phpに書いてあるルーティングに従って、CakePHPがcontrollerやactionを割り当てます。 その結果が$urlに入っているので、あとはPHPUnitのassertEqual()で正しいかどうかを調べています。

3番目のテストケース「testCirclePage」のときのように、少し特殊なルーティングを行っている場合、以下のようにすればどのような値が$urlにわたっているか調べることが出来ます。

$url = Router::parse('/circle');
debug($url);

こうやって調べてテストを書くことによって、どんなルーティングにも対応出来ているテストケースを書くことが出来ます!

テストを動かそう

それでは最後にテストを動かしましょう。 インストールしたディレクトリの最後に「test.php」と打てばGUIでテストを実行出来るページが表示されます。 こんな感じで↓

AppRoutesをクリックし、全てがグリーンであればテストは全て成功しています! こんな感じで↓

また、先ほどのRouter::parse()の中身を見る方法は、この画面の赤い枠で囲まれた部分をクリックするとdebug結果を見ることが出来ます 赤枠をクリック!

debug結果が表示される

これでroute.phpのテストはなんでも書けるはず!!

最後に

いかがでしたでしょうか。 route.phpに対するテストは、CakePHPのテストの中でも1番簡単なので、これをまずは理解しましょう。 もし何かわからないことがあれば、@DAI199までリプライください!

テストに関しては以下の本が1番詳しく書いてあると思いました。

CakePHP2 実践入門 (WEB+DB PRESS plus)
安藤 祐介 岸田 健一郎 新原 雅司 市川 快 渡辺 一宏 鈴木 則夫
技術評論社
売り上げランキング: 17,808
Read more

ついに今日から25日まで続くAdvent Calenderが始まりました。 CakePHP Advent Calender2012の1日目を担当させていただく、たがみ(@DAI199)です。

Cakeを使い始めて半年程度の新米ですが、僕の知識をみなさんに共有するために今回の記事を書きたいと思います。

CakePHPアドベントカレンダー2012本家 http://www.adventar.org/calendars/40 Twitterハッシュタグ #cakeadvent2012

普段はFacebookと連携したアプリを量産しているため、今回の記事では、CakePHPとFacebookを連携し、ちょっとしたアプリを作ってみたいと思います。 デモアプリはこちら

>>> 女の子の画像一覧を見てみる

Facebookログインの実装

それでは早速Facebookログインを実装していきましょう。 以下のURLからFacebookSDKをダウンロードします。 >>> https://github.com/facebook/facebook-php-sdk

それをインストールしたCakePHPのapp/Vendorフォルダに入れます。ここではフォルダ名は「facebook」へ変更しました。

次に「AppController.php」に以下のコードを書きます。ここでやっていることは、Facebookに接続することと、Facebookでログイン出来たことを確認するためにSessionに書き込むことです。

&lt;?php
App::uses('Controller', 'Controller');
class AppController extends Controller {
    public $facebook;
    public $helper = array('Html', 'Form', 'Session');

    function beforeFilter(){
        App::import('Vendor', 'facebook/src/facebook');
        $this-&gt;facebook = new Facebook( array(
                        'appId' =&gt; 'APPID',
                        'secret' =&gt; 'APPSECRET',
                        'cookie' =&gt; true 
                        ));

    }

    // Facebookで接続するときにOAuthを通す
    public function authFacebook() {
        $login_url = $this-&gt;facebook-&gt;getLoginUrl(array('scope' =&gt; 'email,publish_stream,user_birthday,user_education_history,user_likes'));
        $this-&gt;redirect($login_url);
    }

    public function connectFb(){
        $fb = $this-&gt;facebook-&gt;getUser();
        if(empty($fb)){
            $this-&gt;authFacebook();
        }
        $this-&gt;Session-&gt;write('Facebook.id', $fb);
    }
}

上記のコードのAPPID,APPSECRETの部分はFacebookアプリを作成していれてください。 Facebookアプリを作成するためには以下のURLから作成します。 >>> https://developers.facebook.com/

これでFacebookログインが実装出来ました。

Graph APIを叩く

初期画面でFacebookログインボタンを表示して、クリックするとdisplayのほうへリダイレクトされます。 以下コードになります。

【Controller】 FacebookController.php

&lt;?php
class FacebookController extends AppController {
    public function index(){
        if($this-&gt;request-&gt;is('post')){
            $this-&gt;redirect(array('action'=&gt;'display'));
        }
    }

    public function display(){
        // Facebookへ接続
        $this-&gt;connectFb();
        $fb = $this-&gt;facebook-&gt;getUser();

        // Friend List を取得
        try {
            $me = $this-&gt;facebook-&gt;api('/me');
            $friend_list = $this-&gt;facebook-&gt;api(&quot;{$fb}/friends?fields=gender&quot;);
        } catch (FacebookApiException $e){
            error_log($e);
        }
        $this-&gt;set(compact('fb'));
        $this-&gt;set(compact('me'));
        $this-&gt;set(compact('friend_list'));
    }
}

Viewのほうはこんな感じに書いてみました。

【View】 index.ctp

&lt;form action=&quot;&lt;?php echo $this-&gt;Html-&gt;url(array('action'=&gt;'index'));?&gt;&quot; method=&quot;post&quot;&gt;
    &lt;button&gt;Facebookログイン&lt;/button&gt;
&lt;/form&gt;

display.ctp

&lt;h1&gt;自分の顔&lt;/h1&gt;
&lt;img src=&quot;https://graph.facebook.com/&lt;?php echo $fb;?&gt;/picture?width=150&quot; alt=&quot;&quot; /&gt;

&lt;h2&gt;異性の顔((´^ω^))&lt;/h2&gt;
&lt;?php for($i=0; $i &lt; count($friend_list['data']); $i++):?&gt;
    &lt;?php if($me['gender'] != $friend_list['data'][$i]['gender']):?&gt;
        &lt;a href=&quot;https://www.facebook.com/&lt;?php echo $friend_list['data'][$i]['id'];?&gt;&quot;&gt;
            &lt;img src=&quot;https://graph.facebook.com/&lt;?php echo $friend_list['data'][$i]['id'];?&gt;/picture?width=100&amp;height=100&quot; alt=&quot;&quot; /&gt;
        &lt;/a&gt;
    &lt;?php endif;?&gt;
&lt;?php endfor;?&gt;

以上のコードによりGraphAPIを叩くことが出来ました。 GraphAPIについては、Facebookの公式ドキュメントを読むとよいです。

女の子一覧を見てうはうは

さて、それではログインしてみましょう。 自分の友達の女の子でもうはうはです。 あ、もちろん女性の方は男性が表示されます。 女性の方が女性を見たい場合は、Viewの部分をちょちょっといじると出来ますよ。

デモはこちら >>> 女の子一覧を見てうはうは

最後に

すごい簡単なアプリですが、Facebookを使ったアプリを作ったことがない人にとっては新鮮なのではないでしょうか。 これをもう少し改良すれば異性を解析するアプリを作ったり本格的なアプリも出来そうですね。

初めてのAdventCalenderで緊張していますが、わかりにくいこと、間違っているとこ、こうしたほうがいいよ!ってとこがあればぜひコメントを残してくださるとありがたいです。

CakePHP AdventCalender2012 2日目の担当は、@fukayatsuさんです。よろしくお願いします!!

Read more

今までCakePHPを使用してきて、ページ送りを実装したことがなかったのですが、今回実装する機会がありましたのでそれをまとめようと思います。 ページ送りというのは以下のようなやつです。

検索結果とかによく実装されているやつですね。

ただこれを自分で実装しようとすると、自分で全て計算し、例えば10件表示させようと思ってるけど、少し足りない場合はどうするか、とか色々面倒なことになってしまいます。 そこでCakePHPに準備されている、PaginatorヘルパーとPaginatorコンポーネントでさくっとページ送りを実装していこうと思います。

今回作成するページ送りは主に検索結果を表示することを想定して作っています。

まずは検索ボックスとなるビューを作成しましょう。

View/Posts/search.ctp

&lt;?php
echo $this-&gt;Form-&gt;create('Post', array(
    'url'=&gt;array('controller'=&gt;'posts','action'=&gt;'search','type'=&gt;'get')); 
echo $this-&gt;Form-&gt;input('title');
echo $this-&gt;Form-&gt;end('Search');

次にGETで送信された値を検索してみましょう。 例えば以下のようにControllerを書きます。

Controller/PostsController.php

&lt;?php
class PostsController extends AppController {
    public $helpers = array('Html', 'Form', 'Paginator');
    public $components = array('Paginator');

    public function search() {
        // 前回検索した値を常に保存しておく
        $this-&gt;request-&gt;data['Post'] = $this-&gt;request-&gt;query['Post'];

        // Viewに値を渡す
        $this-&gt;set('results', $this-&gt;paginate('Post', array(
                'post_name LIKE' =&gt; &quot;%{$this-&gt;request-&gt;data['Post']}%&quot;)));

あとはビューで結果を表示するだけです。 以下のようにしましょう。

View/Posts/search.ctp

&lt;?php
echo $this-&gt;Paginator-&gt;prev('前へ');
echo $this-&gt;Paginator-&gt;numbers();
echo $this-&gt;Paginator-&gt;next('次へ');

foreach($results as $result) {
    // 処理を書く
}

これで以下のようなページ送りを実装することが出来ました。

この状態では次のようなURLになっていると思います。

/posts/search/page:3?title=hogehoge

それを以下のようなURLにしましょう。

/posts/search?title=hogehoge&page=3

次のコードをControllerに書きましょう。

$this->Paginator->settings['paramType'] = 'querystring';

これでURLがGET風になりました!

以下が参考にした記事です。

参考

Pagination – CakePHP公式ドキュメント(英語)

Read more