$yuzu->log();

技術ネタなど。

【チュートリアル】最新フレームワークCakePHP3でブックマーカーを作ってみる その1

そろそろちゃんと最新フレームワークCakePHP3を触らないといけない気がしてきたので、チュートリアルとしてCakePHP3を使ってブックマーカーを作ってみます。

環境はMacOSXVirtualbox+VagrantCentOS乗っけてます。

インストール

PHPのインストール

PHPをインストールしていない場合はまずPHPから

PHP5.6系をインストールします。

リポジトリはremiで。

% rpm -Uvh http://rpms.famillecollet.com/enterprise/remi-release-6.rpm
% yum install --enablerepo=remi --enablerepo=remi-php56 php php-intl php-mbstring php-mysqlnd
$ php -v
PHP 5.6.9 (cli) (built: May 15 2015 09:40:22)
Copyright (c) 1997-2015 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2015 Zend Technologies
    with Xdebug v2.3.2, Copyright (c) 2002-2015, by Derick Rethans

Composerのインストール

よもやぺちぱーでComposerを使っていない人はいないと思いますが、一応インストール方法を。

グローバルにインストールしちゃいます。

$ curl -sS https://getcomposer.org/installer | php
% mv composer.phar /usr/local/bin/composer

Mysqlのインストール

とりあえず5.1以上をインストールしてください。

インストール方法は面倒なので飛ばします。

今回は5.6.24を使います。

CakePHP3のインストール

さていよいよCakePHP3のインストールです。

ワンライナーでインストールできてしまいます。良い時代ですね。

今回はプロジェクト名をbookmarkerで。

composer create-project --prefer-dist cakephp/app bookmarker
Installing cakephp/app (3.0.3)
  - Installing cakephp/app (3.0.3)
    Downloading: 100%

Created project in bookmarker
Loading composer repositories with package information
Installing dependencies (including require-dev)
省略
  - Installing cakephp/cakephp (3.0.6)
    Loading from cache
  - Installing cakephp/migrations (1.1.1)
    Downloading: 100%
  - Installing cakephp/debug_kit (3.1.5)
    Loading from cache
  - Installing cakephp/bake (1.0.9)
    Downloading: 100%
省略
Created `config/app.php` file
Set Folder Permissions ? (Default to Y) [Y,n]? Y
省略
Updated Security.salt value in config/app.php

MigrationsがCakeDCじゃなくなったのですね。

パーミッション云々聞かれるのでYとしましょう。

Saltも勝手にランダム値に設定してくれるみたいです。

2系でもインストールは楽だったのですが更に楽になりましたね。

ディレクトリ構成を見てみましょう。

$ tree -L 2
.
|-- README.md
|-- bin
|   |-- cake
|   |-- cake.bat
|   `-- cake.php
|-- composer.json
|-- composer.lock
|-- config
|   |-- app.default.php
|   |-- app.php
|   |-- bootstrap.php
|   |-- bootstrap_cli.php
|   |-- paths.php
|   |-- routes.php
|   `-- schema
|-- index.php
|-- logs
|   |-- cli-error.log
|   `-- empty
|-- phpunit.xml.dist
|-- plugins
|   `-- empty
|-- src
|   |-- Console
|   |-- Controller
|   |-- Model
|   |-- Shell
|   |-- Template
|   `-- View
|-- tests
|   |-- Fixture
|   |-- TestCase
|   `-- bootstrap.php
|-- tmp
|   |-- Bake-Controller-controller-ctp.php
|   |-- Bake-Element-Controller-add-ctp.php
|   |-- Bake-Element-Controller-delete-ctp.php
|   |-- Bake-Element-Controller-edit-ctp.php
|   |-- Bake-Element-Controller-index-ctp.php
|   |-- Bake-Element-Controller-view-ctp.php
|   |-- Bake-Element-form-ctp.php
|   |-- Bake-Layout-default-ctp.php
|   |-- Bake-Model-entity-ctp.php
|   |-- Bake-Model-table-ctp.php
|   |-- Bake-tests-fixture-ctp.php
|   |-- Bake-tests-test-case-ctp.php
|   |-- add-ctp.php
|   |-- cache
|   |-- debug_kit.sqlite
|   |-- edit-ctp.php
|   |-- index-ctp.php
|   |-- sessions
|   |-- tests
|   `-- view-ctp.php
|-- vendor
|   |-- aura
|   |-- autoload.php
|   |-- bin
|   |-- cakephp
|   |-- cakephp-plugins.php
|   |-- composer
|   |-- dnoegel
|   |-- empty
|   |-- ircmaxell
|   |-- jakub-onderka
|   |-- mobiledetect
|   |-- nesbot
|   |-- nikic
|   |-- psr
|   |-- psy
|   |-- robmorgan
|   `-- symfony
`-- webroot
    |-- css
    |-- favicon.ico
    |-- img
    |-- index.php
    `-- js

38 directories, 40 files

srcディレクトリにMVCが入るようになったのですね。

起動してみる

ビルドインサーバがあるので試してみます。

ローカルホスト上で動いている場合は

$ bin/cake server
Welcome to CakePHP v3.0.6 Console
---------------------------------------------------------------
App : src
Path: /vagrant/blog/bookmarker/src/
DocumentRoot: /vagrant/blog/bookmarker/webroot
---------------------------------------------------------------
built-in server is running in http://localhost:8765/
You can exit with `CTRL-C`

http://localhost:8765/ でアクセスできます。

仮想マシン上で動いている場合はホストを指定しなければなりません。

bin/cake server -H 0.0.0.0

Welcome to CakePHP v3.0.6 Console
---------------------------------------------------------------
App : src
Path: /vagrant/blog/bookmarker/src/
DocumentRoot: /vagrant/blog/bookmarker/webroot
---------------------------------------------------------------
built-in server is running in http://0.0.0.0:8765/
You can exit with `CTRL-C`

vagrantでIPを192.168.33.50と指定しているので

http://192.168.33.50:8765/ でアクセスできます。

f:id:yuzurus:20150609113143p:plain

起動できました。

データベース(MySQL)の設定

CakePHPからMySQLへアクセスできるように設定をします。

Config/app.phpを自分の環境に合わせて設定してください。

    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            /**
             * CakePHP will use the default DB port based on the driver selected
             * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
             * the following line and set the port accordingly
             */
            //'port' => 'nonstandard_port_number',
            'username' => 'root',
            'password' => '',
            'database' => 'cake_bookmarks',
            'encoding' => 'utf8',
            'timezone' => 'Asia/Tokyo',
            'cacheMetadata' => true,

            /**
             * Set identifier quoting to true if you are using reserved words or
             * special characters in your table or column names. Enabling this
             * setting will result in queries built using the Query Builder having
             * identifiers quoted when creating SQL. It should be noted that this
             * decreases performance because each query needs to be traversed and
             * manipulated before being executed.
             */
            'quoteIdentifiers' => false,

            /**
             * During development, if using MySQL < 5.6, uncommenting the
             * following line could boost the speed at which schema metadata is
             * fetched from the database. It can also be set directly with the
             * mysql configuration directive 'innodb_stats_on_metadata = 0'
             * which is the recommended value in production environments
             */
            //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
        ],

timezoneをAsia/Tokyoにした場合以下のようなエラーが出るかもしれません。

Exception: SQLSTATE[HY000]: General error: 1298 Unknown or incorrect time zone: 'Asia/Tokyo' in [/vagrant/bookmarker/vendor/cakephp/cakephp/src/Database/Driver/Mysql.php, line 94]
php-intl

MySQLタイムゾーンの設定に不備があるので下記のコマンドを実行してください

$ mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql -p

bakeを使ってコードを生成する

CakePHPチュートリアルでお馴染みのbakeでコードを生成していきます。

$ bin/cake bake all users
$ bin/cake bake all bookmarks
$ bin/cake bake all tags

コントローラ、モデル、ビュー、テストケースが生成されます。

テスト大事ですね!

では http://192.168.33.50:8765/bookmarks にアクセスしてみましょう。 f:id:yuzurus:20150609165505p:plain

ユーザを登録したりブックマークを登録したりタグを登録してみましょう。

パスワードのハッシュ化

f:id:yuzurus:20150609223405p:plain

ユーザ登録するとパスワードがプレーンテキストで保存されています。

例えばこの状態でサービスリリースをしてSQLインジェクションなどでユーザ情報が流出すると、このIDとパスワードを使って他のサービスのログインを試み、2次被害してしまいます。そういうことを防ぐためにパスワードは必ず暗号化して保存しなければなりません。

CakePHP2ではModelはModelクラスひとつでしたが、CakePHP3ではTableとEntityに分かれています。 TableはDBのテーブルにアクセスするクラスで、Entityは単一のレコードを扱うクラスです。

パスワードのハッシュ化ロジックはUserのEntityクラスのpasswordセッターに追加します。

_set{カラム名}とすると、カラムの保存前にメソッドが呼び出されるようです。

<?php
namespace App\Model\Entity;
use Cake\Auth\DefaultPasswordHasher;
use Cake\ORM\Entity;

/**
 * User Entity.
 */
class User extends Entity
{

    /**
     * Fields that can be mass assigned using newEntity() or patchEntity().
     *
     * @var array
     */
    protected $_accessible = [
        'email' => true,
        'password' => true,
        'bookmarks' => true,
    ];


    // Code from bake.

    protected function _setPassword($value)
    {
        $hasher = new DefaultPasswordHasher();
        return $hasher->hash($value);
    }

}

DefaultPasswordHasherはBCrypt(Blowfish暗号)になります。

これで、パスワードはハッシュ化されて保存されるようになります。

CakePHP2系ではSecurityコンポーネントで指定できるソルトは$2a$のみでしたが、DefaultPasswordHasherではデフォルトで$2y$なのですね。

特定タグのブックマークを取得する

http://192.168.33.50:8765/bookmarks/tagged/funny/cat/gifs でアクセスした時にfunny, cat, gifsのタグが付いたブックマークをを表示する、という機能をつけてみます。

routeの実装

config/routes.phpに以下を追加

Router::scope(
    '/bookmarks',
    ['controller' => 'Bookmarks'],
    function ($routes) {
        $routes->connect('/tagged/*', ['action' => 'tags']);
    }
);

CakePHP2を経験してる人であれば意味はわかりますね。

では対応するメソッドを実装します。

tagsメソッドの実装

public function tags()
{
    $tags = $this->request->params['pass'];
    $bookmarks = $this->Bookmarks->find('tagged', [
        'tags' => $tags
    ]);
    $this->set(compact('bookmarks', 'tags'));
}

次に

$this->Bookmarks->find('tagged', [
        'tags' => $tags
]);

に当たる部分を実装します。

finder メソッドの実装

BookmarksTable.phpに以下を実装します。

public function findTagged(Query $query, array $options)
{
    $fields = [
        'Bookmarks.id',
        'Bookmarks.title',
        'Bookmarks.url',
    ];
    return $this->find()
        ->distinct($fields)
        ->matching('Tags', function ($q) use ($options) {
            return $q->where(['Tags.title IN' => $options['tags']]);
        });
}

これがカスタムファインダーメソッドです。これはCakePHP3の目玉機能の一つでクエリを再利用可能にします。

Viewの実装

ロジックができたので次はそれを表示するViewを実装します。

Bookmarks/tags.ctpに実装します。

<h1>
    Bookmarks tagged with
    <?= $this->Text->toList($tags) ?>
</h1>

<section>
<?php foreach ($bookmarks as $bookmark): ?>
    <article>
        <h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
        <small><?= h($bookmark->url) ?></small>
        <?= $this->Text->autoParagraph($bookmark->description) ?>
    </article>
<?php endforeach; ?>
</section>

これで表示できるようになります。 「探偵物」というタグに「金田一少年の事件簿」というブックマークを紐付けたので以下のような表示になります。

http://192.168.33.50:8765/bookmarks/tagged/探偵物 f:id:yuzurus:20150609222758p:plain

とりあえず今日はここまで。 次回はログイン、ログアウト、アクセス制限などの機能を追加していきます。

【チュートリアル】最新フレームワークCakePHP3でブックマーカーを作ってみる その2

参考 http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html

CakePHPで学ぶ継続的インテグレーション (impress top gear)

CakePHPで学ぶ継続的インテグレーション (impress top gear)