プログラミングの話

cakePHP3にチャレンジ!

この投稿はCakePHP Advent Calendar 2014の6日目の記事です。

5日目:CakePHPでType Hinting!!!::CakePHP Advent(5日目)

前提

現在のcakePHPは 2.5.6が安定版、2.6.0がRC、3.0がbeatとしてリリースされています。

cakeの3って、2と何が違うの?という話は、Engine Yardさんの以下の記事がわかりやすいと思うので、そちらを参照してください。

第3回 週末ランサーズにてCakePHP3についての講演をしました

マドリードで見たCakePHP3の明るい未来

本日はまだ英語版しか無い、cakePHP3のBookmarkerチュートリアルほど取り組みます!

扱うこと

  • cakePHP3を始めるにあたって必要なもの
  • cakePHP3のインストール
  • プロジェクトの始め方
  • ちょっとだけカスタマイズ

cake以前の準備

InstallationQuick Start Guideを参考に進めていきます。

実行環境

必要なものに以下が指定されています。

  • webサーバー
  • PHP 5.4.16 以上
  • mbstring extension
  • mcrypt extension
  • intl extension
  • pdo
  • DB(後述)

DBは以下の内のどれか

  • MySQL 5.1.10以上
  • PostgreSQL
  • Microsoft SQL Server 2008以上
  • SQLite 3

まぁつまり、通常のLAMP構成みたいなものがあれば良いのでは無いかと思います。

自分はvagrantを利用して以下を用意しました。

  • centOS6.5
  • nginx
  • php5.6
  • mysql

caekPHPのインストールの準備

コマンドラインから適当なディレクトリで以下を実行します。

練習ということで、cakePHPを配置したいディレクトリで実行するのがいいと思います。

まず、cakeをインストールするためのcomposerをインストール

1
curl -s https://getcomposer.org/installer | php

チュートリアル Bookmarker

Bookmarkerを作るチュートリアルです。

チュートリアルの概要は、簡易版のソーシャルブックマークを作成しよう!というものです。

サイトに登録したユーザーは、URLを登録する事ができ、そのURLに対してタグを付与することが出来るようになります。

cakePHP3をcomposerでインストール

以下を実行するとcakeがインストールされます。

最後のbookmarkerってやつがインストールされるディレクトリ名になります。

1
php composer.phar create-project --prefer-dist -s dev cakephp/app bookmarker

時間が少々かかります。成功すると標準出力に以下の様なものが表示されます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
Installing cakephp/app (dev-master 28008873514274db441338eff5e2d07e75274f48)
  - Installing cakephp/app (dev-master master)
      Loading from cache

Created project in bookmarker
Loading composer repositories with package information
Installing dependencies (including require-dev)
  - Installing cakephp/plugin-installer (0.0.1)
      Downloading: 100%

  - Installing aura/installer-default (1.0.0)
      Downloading: 100%

  - Installing nesbot/carbon (1.13.0)
      Downloading: 100%

  - Installing psr/log (1.0.0)
      Downloading: 100%

  - Installing aura/intl (1.1.1)
      Downloading: 100%

  - Installing ircmaxell/password-compat (v1.0.4)
      Downloading: 100%

  - Installing cakephp/cakephp (3.0.x-dev 0e787e9)
      Downloading: 100%

  - Installing cakephp/debug_kit (3.0.x-dev 8a6f3da)
      Downloading: 100%

  - Installing mobiledetect/mobiledetectlib (2.8.11)
      Downloading: 100%

  - Installing d11wtq/boris (v1.0.8)
      Downloading: 100%

d11wtq/boris suggests installing ext-posix (*)
Writing lock file
Generating autoload files
Created `config/app.php` file
Permissions set on /vagrant/bookmarker/tmp/cache
Permissions set on /vagrant/bookmarker/tmp/cache/models
Permissions set on /vagrant/bookmarker/tmp/cache/persistent
Permissions set on /vagrant/bookmarker/tmp/cache/views
Permissions set on /vagrant/bookmarker/tmp/sessions
Permissions set on /vagrant/bookmarker/tmp/tests
Permissions set on /vagrant/bookmarker/tmp
Permissions set on /vagrant/bookmarker/logs
Updated Security.salt value in config/app.php

失敗してるとちゃんとproblemって出るので見落とさないように。

自分の環境(ここのCentOS 6.5 x86_64)では php-intl がインストール出来なくてエラーが出てました。最後にちょろっと補足を書いておきます。

アクセスするとこんな感じ

2系列の時より綺麗!!!

(だけどまっさらで、必要な情報はもうちょい上に挙げられなかったのかなーなんてね)

cakephp3 top

ディレクトリ構成

appがsrcに変わった感じ?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bookmarker
├── README.md
├── bin
├── composer.json
├── composer.lock
├── config
├── index.php
├── logs
├── phpunit.xml.dist
├── plugins
├── src
├── tests
├── tmp
├── vendor
└── webroot

src以下はずいぶんスッキリ

1
2
3
4
5
6
7
src
├── Console
├── Controller
├── Model(以下にTable,Entity,Behaviorがあります)
├── Shell
├── Template (2系列までのView相当)
└── View (3系列でViewがオブジェクトになった影響のディレクトリ)

設定

まずtmpとかlogsとかの権限をphpが関与できる権限に変更しましょう。

つぎにDBの作成とテーブルの設定。チュートリアルにある、以下をまんま流し込む

今回は面倒かつ練習用なので

ユーザー名 root パスワード root 作成したDBの名前 bookmarker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    created DATETIME,
    updated DATETIME
);

CREATE TABLE bookmarks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(50),
    description TEXT,
    url TEXT,
    created DATETIME,
    updated DATETIME,
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    created DATETIME,
    updated DATETIME,
    UNIQUE KEY (title)
);

CREATE TABLE bookmarks_tags (
    bookmark_id INT NOT NULL,
    tag_id INT NOT NULL,
    PRIMARY KEY (bookmark_id, tag_id),
    INDEX tag_idx (tag_id, bookmark_id),
    FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
    FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id)
);

でもって、作成したDBの設定をconfig/app.phpに反映

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#config/app.phpの200行目付近

        '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' => 'root',
              'database' => 'bookmarker',
                'encoding' => 'utf8',
                'timezone' => 'UTC',
                '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'],
            ],

スキャッフォルドで色々と生成

おなじみのスキャッフォルド

bookmarkerとしてcakeをインストールしたディレクトリで以下を叩きます

1
2
3
php bin/cake.php bake all users
php bin/cake.php bake all bookmarks
php bin/cake.php bake all tags

成功してると最後に以下が表示される

1
Bake All complete.

成功したなら以下にアクセス

1
http://example.com/bookmarks

3系ではcakeのデフォはフラットですね。

cake3 bookmarker

これだけで、結構な色々な機能が使えるのですが、見れない所がちょいちょい有る。

が、それは後で治すと思うのでとりあえず放置

とりあえず、この後色々試すのにユーザーとタグとブクマのデータを入れて欲しい。 順番もきちんとユーザー、タグ、ブクマで入れると勘違いが起きない。

パスワードを暗号化させよう

現状のままだと、ユーザーのパスワードが暗号化されていないので実用性が低いと思われるので。

src/Model/Entity/User.phpに追記していきます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\Auth\DefaultPasswordHasher; #追記
/**
 * 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,
        ];

    protected function _setPassword($value) {  #追記
            $hasher = new DefaultPasswordHasher(); #追記
            return $hasher->hash($valu             #追記

    }
}

(どうでもいいけど、cakeのスキャッフォルド、tab文字で生成されるの凄く気になりませんか?)

これで、Userを編集したり、新しく保存するとユーザーのパスワードが暗号化されるはず。

2系列で使っていたModelは、Model/Entityに移動した形ですかね? 3系列ではModelがオブジェクトを返すようになったらしいので、それに伴い、大きく変更されてるっぽいですね。

面倒だけど、ドキュメンを読む

Modelから抜粋

In CakePHP your application’s domain model gets split into 2 primary object types. The first are repositories or table objects. These objects provide access to collections of data. They allow you to save new records, modify/delete existing ones, define relations, and perform bulk operations. The second type of objects are entities. Entities represent individual records and allow you to define row/record level behavior & functionality.

ざっくり訳すと、modelは2つに別れて、

オブジェクト 役割
table 新しいデータの保存とか、変更、削除と、リレーションの定義
entities 独立したレコードの表示や、行の振る舞いや関数の定義

つまり、tableはデータを弄る系とリレーション、entitiesはデータの利用ってことでいいのかな?

ついでにViewがどう変わったのかも確認する

3.0 Migration Guideがわかりやすそう

The folders containing view files now go under src/Template instead of src/View. This was done to separate the view files from files containing php classes (eg. Helpers, View classes).

いままでのViewがTemplateに変わったとの事。

cake3ではViewもオブジェクトになったからだと思われる。

でもって、Viewのオブジェクトで色々と設定が出来るようになった?って感じかな。

タグ付けされてるブックマークを探したいよね。

たとえば

1
http://example.com/bookmarks/tagged/funny/cat/gifs

以上だと、funnyとかタグ付けされてるやつブクマが分かることを期待する。

この機能を実現するために、以下の手順を通して行う。

  1. config/routes.phpを編集
  2. src/Controller/BookmarksController.phpの編集
  3. src/Model/Table/BookmarksTable.phpの編集
  4. Template/Bookmarks/tags.ctpの追加

まず、config/routes.phpを編集

1
2
3
4
5
6
7
8
#URLのbookmarks以下でtaggedにマッチしたら、BookmarksControllersのtagsメソッドにアクセス
Router::scope(
    '/bookmarks',
        ['controller' => 'Bookmarks'],
        function ($routes) {
            $routes->connect('/tagged/*', ['action' => 'tags']);
        }
);

以上を、

1
Router::scope("/",#以下略

が出てくる前に記述する。

記述しないと、/ が追記したものより先にマッチして思うようにアクセス出来ない。

コントローラーの編集

routesに追記したのはBookmarksControllersのtagsにアクセスと書いたが、まだ無いので追記。 位置は基本的に気にしなくていい。

1
2
3
4
5
6
7
8
public function tags() {
    $tags = $this->request->params['pass'];
        #findの第一引数のtaggedが、ModelのfindTaggedに関連づいている
    $bookmarks = $this->Bookmarks->find('tagged', [
                'tags' => $tags
                ]);
    $this->set(compact('bookmarks', 'tags'));
}

モデルの編集

チュートリアルには、コントローラーを薄く保つために、モデルにロジックを記述するんだ!とか書いてある。

結果として、より規約を意識する形になったと思う。

コードのコメントにも書いたが、Controller内のModel呼び出しで、find('tagged'〜のところが、findTaggedと結びついている。

Model/Table/BookmarksTable.phpに以下を追記

1
2
3
4
5
6
7
8
9
10
11
12
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']]);
                });
}

Templateの追加

Controllerにメソッドを追加したら、Templateも追加しましょう。

/Template/Bookmarks/tags.ctpに以下を記述して保存

1
2
3
4
5
6
7
8
9
10
11
<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>

全て終えると

1
http://example.com/bookmarks/tagged/hogehoge

にアクセスするとhogehogeとタグ付けされた本がリストアップされる。

スクショは、予めブクマしたものにプログラミングとタグ付けをしてURLにアクセスしたもの。

1
http://example.com/bookmarks/tagged/プログラミング

cake3 tagged

寂しいけどこんな感じ。 これでbookmarkerチュートリアルは終了。

cake3の感想

結構色々変わった!というのがチュートリアルを終えての印象。

cake2の頃も結構規約を意識しなければならなかったが、更に規約を意識してプログラムを書かなければと思う。

cake慣れていれば問題ないけど、初心者とかは結構詰まるのではないかな?

ModelとViewが更に大きく変わっていると思うけれど、今回のチュートリアルでは構成と役割が分割されたくらいしかわからないので、次回に持越し。

とりあえず、正式にリリースされたら、新規で作るものに関しては使っていきたいと思う。 これだけの変更があると、マイグレーションツールとかがどの位通用するのかとかも気になる所。


想像以上に長くなったので、とりあえず本日の記事は以上にします。 近いうちにBookmarkarチュートリアル2に取り組む記事もあげる予定です。

cakePHP3のチュートリアルの足掛かりになればと思います。

補足

php-intlがインストールできて無かった話

libicudata.so.50()(64bit)が無いと言われたので

1
2
3
wget ftp://195.220.108.108/linux/remi/enterprise/6/remi/x86_64/libicu-last-50.1.2-11.el6.remi.x86_64.rpm
rpm -iV libicu-last-50.1.2-11.el6.remi.x86_64.rpm
yum -y install --enablerepo=remi-php56 php-intl

って感じでインストールした。