m_shige1979のときどきITブログ

プログラムの勉強をしながら学習したことや経験したことをぼそぼそと書いていきます

Github(変なおっさんの顔でるので気をつけてね)

https://github.com/mshige1979

laravel5でサンプルアプリ作成

サンプルアプリを作成する

簡単なブログチュートリアルみたいなもので記事の一覧と作成を行う

参考

Laravel5でシンプルなCRUDアプリを開発する : アシアルブログ
※一気にやってもうまくついていけないので少しだけ

DBのマイグレーション

create
$ php artisan make:migration create_articles_table
Created Migration: 2015_05_10_104527_create_articles_table
$ 

※関連したマイグレーションファイルを新規に作成する

ファイルの確認
$ ll database/migrations/
total 12
2014_10_12_000000_create_users_table.php
2014_10_12_100000_create_password_resets_table.php
2015_05_10_104527_create_articles_table.php
$ 

※3つあるけど作ったのは最後のものだけで他のはそのままあった感じ

編集
<?php

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateArticlesTable extends Migration {

	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
		// 新規作成
        Schema::create('articles', function(Blueprint $table)
        {
            $table->increments('id');
            $table->string('title');
            $table->string('body');
            $table->timestamps();
        });
	}

	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
		// 削除
        Schema::drop('articles');
	}

}
マイグレーション実行
php artisan migrate
Migration table created successfully.
Migrated: 2014_10_12_000000_create_users_table
Migrated: 2014_10_12_100000_create_password_resets_table
Migrated: 2015_05_10_104527_create_articles_table
$

f:id:m_shige1979:20150510105519p:plain

コントローラーやモデルの用意

雛形を作成する
$ php artisan make:model Article
Model created successfully.
Created Migration: 2015_05_10_105751_create_articles_table
$ php artisan make:controller ArticlesController
Controller created successfully.
$

マイグレーションファイルを作成したけどいらないので削除しておく

モデルを編集する

app/Article.php

<?php namespace App;

use Illuminate\Database\Eloquent\Model;

class Article extends Model {

	// table
    protected $table = "articles";

    // fields
    protected $fillable = ["title", "body"];

}
コントローラーを編集する

app/Http/Controllers/ArticlesController.php

<?php namespace App\Http\Controllers;

use App\Article;
use App\Http\Requests;
use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

class ArticlesController extends Controller {


    protected $article;

    public function __construct(Article $article){
        $this->article = $article;
    }

	/**
	 * Display a listing of the resource.
	 * 一覧
	 * @return Response
	 */
	public function index()
	{
		//
	}

	/**
	 * Show the form for creating a new resource.
	 * 新規作成
	 * @return Response
	 */
	public function create()
	{
		//
	}

	/**
	 * Store a newly created resource in storage.
	 * 新規登録
	 * @return Response
	 */
	public function store()
	{
		//
	}

	/**
	 * Display the specified resource.
	 * 詳細
	 * @param  int  $id
	 * @return Response
	 */
	public function show($id)
	{
		//
	}

	/**
	 * Show the form for editing the specified resource.
	 * 編集
	 * @param  int  $id
	 * @return Response
	 */
	public function edit($id)
	{
		//
	}

	/**
	 * Update the specified resource in storage.
	 * 更新
	 * @param  int  $id
	 * @return Response
	 */
	public function update($id)
	{
		//
	}

	/**
	 * Remove the specified resource from storage.
	 * 削除
	 * @param  int  $id
	 * @return Response
	 */
	public function destroy($id)
	{
		//
	}

}

メソッド名にgetとかいれなイカンみたいです

ルーティングを編集

app/Http/routes.php

<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/

Route::get('/', 'WelcomeController@index');

Route::get('home', 'HomeController@index');

Route::controllers([
	'auth' => 'Auth\AuthController',
	'password' => 'Auth\PasswordController',
]);

// 追加
Route::get('/', 'ArticlesController@index');
Route::controller('articles', 'ArticlesController');

f:id:m_shige1979:20150510112155p:plain
※まだ、なにも表示していないので空

各機能の作り込み

app/Http/routes.php
<?php

/*
|--------------------------------------------------------------------------
| Application Routes
|--------------------------------------------------------------------------
|
| Here is where you can register all of the routes for an application.
| It's a breeze. Simply tell Laravel the URIs it should respond to
| and give it the controller to call when that URI is requested.
|
*/

// get
Route::get('/', 'ArticlesController@index');
Route::get('articles/index', 'ArticlesController@index');
Route::get('articles/show/{id}', 'ArticlesController@show');
Route::get('articles/edit/{id}', 'ArticlesController@edit');
Route::get('articles/destroy/{id}', 'ArticlesController@destroy');

// post
Route::post('articles/store', 'ArticlesController@store');
Route::post('articles/update/{id}', 'ArticlesController@update');

// resource
Route::resource('articles', 'ArticlesController');
app/Http/Controllers/ArticlesController.php
<?php namespace App\Http\Controllers;

use App\Article;
use App\Http\Requests;
use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

class ArticlesController extends Controller {


    protected $article;

    public function __construct(Article $article)
    {
        $this->article = $article;
    }

	/**
	 * Display a listing of the resource.
	 * 一覧
	 * @return Response
	 */
	public function index()
	{
		//
        $articles = $this->article->all();
        return view('articles.index')->with(compact('articles'));
	}

	/**
	 * Show the form for creating a new resource.
	 * 新規作成
	 * @return Response
	 */
	public function create()
	{
		// 新規登録画面を表示
        return view('articles.create');
	}

	/**
	 * Store a newly created resource in storage.
	 * 新規登録
	 * @return Response
	 */
	public function store(Request $request)
	{
		// パラメータを取得して保存
        $data = $request->all();
        $this->article->fill($data);
        $this->article->save();

        // 一覧画面へ遷移
        return redirect()->to('/');
	}

	/**
	 * Display the specified resource.
	 * 詳細
	 * @param  int  $id
	 * @return Response
	 */
	public function show($id)
	{
		//
        $article = $this->article->find($id);

        return view('articles.show', compact('article'));
	}

	/**
	 * Show the form for editing the specified resource.
	 * 編集
	 * @param  int  $id
	 * @return Response
	 */
	public function edit($id)
	{
		//
        $article = $this->article->find($id);

        return view('articles.edit')->withArticle($article);
	}

	/**
	 * Update the specified resource in storage.
	 * 更新
	 * @param  int  $id
	 * @return Response
	 */
	public function update(Request $request, $id)
	{
		//
        $article = $this->article->find($id);
        $data = $request->all();
        $article->fill($data);
        $article->save();

        return redirect()->to('/');
	}

	/**
	 * Remove the specified resource from storage.
	 * 削除
	 * @param  int  $id
	 * @return Response
	 */
	public function destroy($id)
	{
		//
        $article = $this->article->find($id);
        $article->delete();

        return redirect()->to('/');
	}

}
resources/views/articles/index.blade.php
@extends('app')

@section('content')
    <h2 class="page-header">記事一覧</h2>
    <div>
        <a href="/articles/create" class="btn btn-primary">投稿</a>
    </div>
    <table class="table table-striped table-hover">
        <thead>
        <tr>
            <th>タイトル</th>
            <th>本文</th>
            <th>作成日時</th>
            <th>更新日時</th>
            <th></th>
        </tr>
        </thead>
        <tbody>
        @foreach($articles as $article)
            <tr>
                <td>{{{ $article->title }}}</td>
                <td>{{{ $article->body }}}</td>
                <td>{{{ $article->created_at }}}</td>
                <td>{{{ $article->updated_at }}}</td>
                <td>
                    <a href="/articles/show/{{{ $article->id }}}" class="btn btn-default btn-xs">詳細</a>
                    <a href="/articles/edit/{{{ $article->id }}}" class="btn btn-success btn-xs">編集</a>
                    <a href="/articles/destroy/{{{ $article->id }}}" class="btn btn-danger btn-xs">削除</a>
                </td>
            </tr>
        @endforeach
        </tbody>
    </table>
@endsection
resources/views/articles/create.blade.php
@extends('app')

@section('content')
    <h2 class="page-header"><a href="/">記事投稿</a></h2>
    <form name="form1" action="/articles/store" method="post">
        <input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
        <div class="form-group">
            <label>タイトル</label>
            <input type="text" name="title" value="" required="required" class="form-control" />

        </div>
        <div class="form-group">
            <label>本文</label>
            <textarea name="body" required="required" class="form-control"></textarea>

        </div>
        <button type="submit" class="btn btn-default">投稿</button>
    </form>
@endsection
resources/views/articles/show.blade.php
@extends('app')

@section('content')
    <h2 class="page-header"><a href="/">記事詳細</a></h2>
    <ul class="list-inline">
        <li>
            <a href="/articles/edit/{{{ $article->id }}}" class="btn btn-primary pull-left">編集</a>
        </li>
        <li>
            <a href="/articles/destroy/{{{ $article->id }}}" class="btn btn-danger pull-left">削除</a>
        </li>
    </ul>
    <table class="table table-striped">
        <tbody>
        <tr>
            <th>タイトル</th>
            <td>{{{ $article->title }}}</td>
        </tr>
        <tr>
            <th>本文</th>
            <td>{{{ $article->body }}}</td>
        </tr>
        <tr>
            <th>作成日時</th>
            <td>{{{ $article->created_at }}}</td>
        </tr>
        <tr>
            <th>更新日時</th>
            <td>{{{ $article->updated_at }}}</td>
        </tr>
        </tbody>
    </table>
@endsection
resources/views/articles/edit.blade.php
@extends('app')

@section('content')
    <h2 class="page-header"><a href="/">記事編集</a></h2>
    <form name="form1" action="/articles/update/{{ $article->id }}" method="post">
        <input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">
        <div class="form-group">
            <label>タイトル</label>
            <input type="text" name="title" value="{{ $article->title }}" required="required" class="form-control" />

        </div>
        <div class="form-group">
            <label>本文</label>
            <textarea name="body" required="required" class="form-control">{{ $article->body }}</textarea>

        </div>
        <button type="submit" class="btn btn-default">編集</button>
    </form>
@endsection

完成イメージ

f:id:m_shige1979:20150510125847p:plain

所感

サンプルの通りにやろうとしたけどうまく動かなかったのでちょっと変えてやってみました。
なんとか動くようになったのでおk

laravel5の初期環境を構築

laravel5があるのでちょっと触ってみる

なんか以前やったけど保留中だったし最近メジャーな感じが感じるので…

ただ、今回はヘルパーとかモデルとかで便利なものがあっても使いません。
まだ、よく知らないので…

参考にさせて頂いたもの

Laravel5でシンプルなCRUDアプリを開発する : アシアルブログ
Laravel5初期設定&環境設定まとめ - 魔法使いの卵
※基本、参考にしたのはインストールとDB,デバッガーあたりです

ベース

OS

vagrantで作ったcentos6.x

php

5.5.x

DB

MySQL

やったこと

composerを入れてappを用意
curl -sS https://getcomposer.org/installer | php
php composer.phar create-project laravel/laravel sampleapp1 --prefer-dist

※もう、composerはデフォルトで別の場所に置いた方がいいかも…

データベースの作成
mysql> CREATE DATABASE `l5_sample1_db`;
mysql> GRANT ALL PRIVILEGES ON *.* TO 'l5_user'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;
.envを編集
DB_HOST=localhost
DB_DATABASE=l5_sample1_db
DB_USERNAME=l5_user
DB_PASSWORD=password

MySQLのために必要なものはこれくらい
※これは一部のIDEでは見えないようになっているおそれがある

config/app.phpを編集
<?php

return [
	'timezone' => 'Asia/Tokyo',
	'locale' => 'ja',
	'fallback_locale' => 'ja',
];

※基本的なものはこれだけ、他にもあるけど省略

デバッグ用のものを追加インストール
php ../composer.phar require barryvdh/laravel-debugbar
config/app.phpに追加
<?php
	'providers' => [
		// 追加
		'Barryvdh\Debugbar\ServiceProvider',
	],

	'aliases' => [
		// 追加
		'Debugbar' => 'Barryvdh\Debugbar\Facade',

	],
ビルドインサーバとして確認
php artisan serve --host 192.168.33.10

※hostを指定しないとlocalhostになる

f:id:m_shige1979:20150509161931p:plain

一応ここまで

まとめ

新しいことに取り組む場合はつい前回まで使っていたことが足を引っ張って「前のままでよくね?」思考に陥ってしまいます。
それでも問題無い場合もありますが、環境を大幅に変えるような状況に陥ったときに柔軟に対応できるようにいくつか経験しておくことで…

とかいうのはいままで散々書いてきたので次回以降やりそうなことを書いておく

他にもあるけどやらんかも知れないのでとりあえずこんだけを頑張る

Laravelエキスパート養成読本[モダンな開発を実現するPHPフレームワーク!] (Software Design plus)

Laravelエキスパート養成読本[モダンな開発を実現するPHPフレームワーク!] (Software Design plus)

PHPエンジニア養成読本 〔現場で役立つイマドキ開発ノウハウ満載! 〕 (Software Design plus)

PHPエンジニア養成読本 〔現場で役立つイマドキ開発ノウハウ満載! 〕 (Software Design plus)

cakephp3のチュートリアル(Bookmarker Tutorial)

cakephp3はもう使えるかもしれないので

チュートリアルからやってみる

環境

vagrantの中にphp5.5とかmysqlとか入れて見た

Bookmarker Tutorial Part 1

composer.pharを取得して初期化
curl -s https://getcomposer.org/installer | php
php composer.phar create-project --prefer-dist -s dev cakephp/app bookmarker
ディレクトリ確認
[vagrant@localhost app2]$ ll bookmarker/
total 48
drwxrwxrwx 1 vagrant vagrant   170 Feb  7 10:22 bin
-rwxrwxrwx 1 vagrant vagrant  1226 Feb  7 10:22 composer.json
-rwxrwxrwx 1 vagrant vagrant 31003 Feb  8 08:53 composer.lock
drwxrwxrwx 1 vagrant vagrant   306 Feb  8 08:53 config
-rwxrwxrwx 1 vagrant vagrant   648 Feb  7 10:22 index.php
drwxrwxrwx 1 vagrant vagrant   102 Feb  7 10:22 logs
-rwxrwxrwx 1 vagrant vagrant   819 Feb  7 10:22 phpunit.xml.dist
drwxrwxrwx 1 vagrant vagrant   102 Feb  7 10:22 plugins
-rwxrwxrwx 1 vagrant vagrant   944 Feb  7 10:22 README.md
drwxrwxrwx 1 vagrant vagrant   272 Feb  7 10:22 src
drwxrwxrwx 1 vagrant vagrant   170 Feb  7 10:22 tests
drwxrwxrwx 1 vagrant vagrant   170 Feb  7 10:22 tmp
drwxrwxrwx 1 vagrant vagrant   544 Feb  8 08:55 vendor
drwxrwxrwx 1 vagrant vagrant   272 Feb  7 10:22 webroot
[vagrant@localhost app2]$
データベースを作成
create database cake_bookmarks default charset utf8;
GRANT ALL PRIVILEGES ON *.* TO 'cake_bookmarks'@'localhost' IDENTIFIED BY 'password' WITH GRANT OPTION;
テーブルを作成
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)
);
作成したテーブルを確認

f:id:m_shige1979:20150208090337p:plain

データベースの設定を変更

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' => 'cake_bookmarks',
            'password' => 'password',
            'database' => 'cake_bookmarks',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
Scaffoldで雛形を作成する
bin/cake bake all users
bin/cake bake all bookmarks
bin/cake bake all tags
usersのエンティティにパスワード設定処理を追加

src/Model/Entity/User.php

<?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($value);
    }
}
Specific Tag???

config/routes.php

<?php
/**
 * Routes configuration
 *
 * In this file, you set up routes to your controllers and their actions.
 * Routes are very important mechanism that allows you to freely connect
 * different URLs to chosen controllers and their actions (functions).
 *
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 */

use Cake\Core\Plugin;
use Cake\Routing\Router;

/**
 * The default class to use for all routes
 *
 * The following route classes are supplied with CakePHP and are appropriate
 * to set as the default:
 *
 * - Route
 * - InflectedRoute
 * - DashedRoute
 *
 * If no call is made to `Router::defaultRouteClass`, the class used is
 * `Route` (`Cake\Routing\Route\Route`)
 *
 * Note that `Route` does not do any inflections on URLs which will result in
 * inconsistently cased URLs when used with `:plugin`, `:controller` and
 * `:action` markers.
 *
 */
Router::defaultRouteClass('Route');

Router::scope('/', function ($routes) {
    /**
     * Here, we are connecting '/' (base path) to a controller called 'Pages',
     * its action called 'display', and we pass a param to select the view file
     * to use (in this case, src/Template/Pages/home.ctp)...
     */
    $routes->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);

    /**
     * ...and connect the rest of 'Pages' controller's URLs.
     */
    $routes->connect('/pages/*', ['controller' => 'Pages', 'action' => 'display']);

    /**
     * Connect catchall routes for all controllers.
     *
     * Using the argument `InflectedRoute`, the `fallbacks` method is a shortcut for
     *    `$routes->connect('/:controller', ['action' => 'index'], ['routeClass' => 'InflectedRoute']);`
     *    `$routes->connect('/:controller/:action/*', [], ['routeClass' => 'InflectedRoute']);`
     *
     * Any route class can be used with this method, such as:
     * - DashedRoute
     * - InflectedRoute
     * - Route
     * - Or your own route class
     *
     * You can remove these routes once you've connected the
     * routes you want in your application.
     */
    $routes->fallbacks('InflectedRoute');
});

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

/**
 * Load all plugin routes.  See the Plugin documentation on
 * how to customize the loading of plugin routes.
 */
Plugin::routes();
タグ検索用のアクションを追加

src/Controller/BookmarksController.php

<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * Bookmarks Controller
 *
 * @property \App\Model\Table\BookmarksTable $Bookmarks
 */
class BookmarksController extends AppController
{

    /**
     * Index method
     *
     * @return void
     */
    public function index()
    {
        $this->paginate = [
            'contain' => ['Users']
        ];
        $this->set('bookmarks', $this->paginate($this->Bookmarks));
        $this->set('_serialize', ['bookmarks']);
    }

    /**
     * View method
     *
     * @param string|null $id Bookmark id.
     * @return void
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function view($id = null)
    {
        $bookmark = $this->Bookmarks->get($id, [
            'contain' => ['Users', 'Tags', 'BookmarksTags']
        ]);
        $this->set('bookmark', $bookmark);
        $this->set('_serialize', ['bookmark']);
    }

    /**
     * Add method
     *
     * @return void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $bookmark = $this->Bookmarks->newEntity();
        if ($this->request->is('post')) {
            $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
            if ($this->Bookmarks->save($bookmark)) {
                $this->Flash->success('The bookmark has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The bookmark could not be saved. Please, try again.');
            }
        }
        $users = $this->Bookmarks->Users->find('list', ['limit' => 200]);
        $tags = $this->Bookmarks->Tags->find('list', ['limit' => 200]);
        $this->set(compact('bookmark', 'users', 'tags'));
        $this->set('_serialize', ['bookmark']);
    }

    /**
     * Edit method
     *
     * @param string|null $id Bookmark id.
     * @return void Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $bookmark = $this->Bookmarks->get($id, [
            'contain' => ['Tags']
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
            if ($this->Bookmarks->save($bookmark)) {
                $this->Flash->success('The bookmark has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The bookmark could not be saved. Please, try again.');
            }
        }
        $users = $this->Bookmarks->Users->find('list', ['limit' => 200]);
        $tags = $this->Bookmarks->Tags->find('list', ['limit' => 200]);
        $this->set(compact('bookmark', 'users', 'tags'));
        $this->set('_serialize', ['bookmark']);
    }

    /**
     * Delete method
     *
     * @param string|null $id Bookmark id.
     * @return void Redirects to index.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $bookmark = $this->Bookmarks->get($id);
        if ($this->Bookmarks->delete($bookmark)) {
            $this->Flash->success('The bookmark has been deleted.');
        } else {
            $this->Flash->error('The bookmark could not be deleted. Please, try again.');
        }
        return $this->redirect(['action' => 'index']);
    }

    // 追加
    public function tags()
    {
        $tags = $this->request->params['pass'];
        $bookmarks = $this->Bookmarks->find('tagged', [
            'tags' => $tags
        ]);
        $this->set(compact('bookmarks', 'tags'));
    }
}
タグの検索用メソッドを付与

src/Model/Table/BookmarksTable.php

<?php
namespace App\Model\Table;

use App\Model\Entity\Bookmark;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

/**
 * Bookmarks Model
 */
class BookmarksTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        $this->table('bookmarks');
        $this->displayField('title');
        $this->primaryKey('id');
        $this->addBehavior('Timestamp');
        $this->belongsTo('Users', [
            'foreignKey' => 'user_id'
        ]);
        $this->belongsToMany('Tags', [
            'foreignKey' => 'bookmark_id',
            'targetForeignKey' => 'tag_id',
            'joinTable' => 'bookmarks_tags'
        ]);
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->add('id', 'valid', ['rule' => 'numeric'])
            ->allowEmpty('id', 'create')
            ->add('user_id', 'valid', ['rule' => 'numeric'])
            ->requirePresence('user_id', 'create')
            ->notEmpty('user_id')
            ->allowEmpty('title')
            ->allowEmpty('description')
            ->allowEmpty('url');

        return $validator;
    }

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['user_id'], 'Users'));
        return $rules;
    }

    // 追加
    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']]);
            });
    }
}
タグ用のビューを作成

src/Template/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>

Bookmarker Tutorial Part 2

認証設定を追加

src/Controller/AppController.php

<?php
/**
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link      http://cakephp.org CakePHP(tm) Project
 * @since     0.2.9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT License
 */
namespace App\Controller;

use Cake\Controller\Controller;

/**
 * Application Controller
 *
 * Add your application-wide methods in the class below, your controllers
 * will inherit them.
 *
 * @link http://book.cakephp.org/3.0/en/controllers.html#the-app-controller
 */
class AppController extends Controller
{

    /**
     * Initialization hook method.
     *
     * Use this method to add common initialization code like loading components.
     *
     * @return void
     */
    public function initialize()
    {
        $this->loadComponent('Flash');
        $this->loadComponent('Auth', [
            'authenticate' => [
                'Form' => [
                    'fields' => [
                        'username' => 'email',
                        'password' => 'password'
                    ]
                ]
            ],
            'loginAction' => [
                'controller' => 'Users',
                'action' => 'login'
            ]
        ]);

        // Allow the display action so our pages controller
        // continues to work.
        $this->Auth->allow(['display']);
    }
}

※認証設定用の処理を記載

ログイン処理を追加

src/Controller/UsersController.php

<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * Users Controller
 *
 * @property \App\Model\Table\UsersTable $Users
 */
class UsersController extends AppController
{

    /**
     * Index method
     *
     * @return void
     */
    public function index()
    {
        $this->set('users', $this->paginate($this->Users));
        $this->set('_serialize', ['users']);
    }

    /**
     * View method
     *
     * @param string|null $id User id.
     * @return void
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function view($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => ['Bookmarks']
        ]);
        $this->set('user', $user);
        $this->set('_serialize', ['user']);
    }

    /**
     * Add method
     *
     * @return void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $user = $this->Users->newEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $this->Flash->success('The user has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The user could not be saved. Please, try again.');
            }
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Edit method
     *
     * @param string|null $id User id.
     * @return void Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $this->Flash->success('The user has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The user could not be saved. Please, try again.');
            }
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Delete method
     *
     * @param string|null $id User id.
     * @return void Redirects to index.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $user = $this->Users->get($id);
        if ($this->Users->delete($user)) {
            $this->Flash->success('The user has been deleted.');
        } else {
            $this->Flash->error('The user could not be deleted. Please, try again.');
        }
        return $this->redirect(['action' => 'index']);
    }

    // ログイン処理を追加
    public function login()
    {
        if ($this->request->is('post')) {
            $user = $this->Auth->identify();
            if ($user) {
                $this->Auth->setUser($user);
                return $this->redirect($this->Auth->redirectUrl());
            }
            $this->Flash->error('Your username or password is incorrect.');
        }
    }

}
ログインページを追加

src/Template/Users/login.ctp

<h1>Login</h1>
<?= $this->Form->create() ?>
<?= $this->Form->input('email') ?>
<?= $this->Form->input('password') ?>
<?= $this->Form->button('Login') ?>
<?= $this->Form->end() ?>
ログアウト処理を追加

src/Controller/UsersController.php

<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * Users Controller
 *
 * @property \App\Model\Table\UsersTable $Users
 */
class UsersController extends AppController
{

    /**
     * Index method
     *
     * @return void
     */
    public function index()
    {
        $this->set('users', $this->paginate($this->Users));
        $this->set('_serialize', ['users']);
    }

    /**
     * View method
     *
     * @param string|null $id User id.
     * @return void
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function view($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => ['Bookmarks']
        ]);
        $this->set('user', $user);
        $this->set('_serialize', ['user']);
    }

    /**
     * Add method
     *
     * @return void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $user = $this->Users->newEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $this->Flash->success('The user has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The user could not be saved. Please, try again.');
            }
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Edit method
     *
     * @param string|null $id User id.
     * @return void Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $this->Flash->success('The user has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The user could not be saved. Please, try again.');
            }
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Delete method
     *
     * @param string|null $id User id.
     * @return void Redirects to index.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $user = $this->Users->get($id);
        if ($this->Users->delete($user)) {
            $this->Flash->success('The user has been deleted.');
        } else {
            $this->Flash->error('The user could not be deleted. Please, try again.');
        }
        return $this->redirect(['action' => 'index']);
    }

    
    public function login()
    {
        if ($this->request->is('post')) {
            $user = $this->Auth->identify();
            if ($user) {
                $this->Auth->setUser($user);
                return $this->redirect($this->Auth->redirectUrl());
            }
            $this->Flash->error('Your username or password is incorrect.');
        }
    }

    // ログアウト処理を追加
    public function logout()
    {
        $this->Flash->success('You are now logged out.');
        return $this->redirect($this->Auth->logout());
    }

}
ユーザーの新規追加処理のアクセス許可を設定

src/Controller/UsersController.php

<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * Users Controller
 *
 * @property \App\Model\Table\UsersTable $Users
 */
class UsersController extends AppController
{

    // addアクションは認証不要
    public function beforeFilter(\Cake\Event\Event $event)
    {
        $this->Auth->allow(['add']);
    }

    /**
     * Index method
     *
     * @return void
     */
    public function index()
    {
        $this->set('users', $this->paginate($this->Users));
        $this->set('_serialize', ['users']);
    }

    /**
     * View method
     *
     * @param string|null $id User id.
     * @return void
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function view($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => ['Bookmarks']
        ]);
        $this->set('user', $user);
        $this->set('_serialize', ['user']);
    }

    /**
     * Add method
     *
     * @return void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $user = $this->Users->newEntity();
        if ($this->request->is('post')) {
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $this->Flash->success('The user has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The user could not be saved. Please, try again.');
            }
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Edit method
     *
     * @param string|null $id User id.
     * @return void Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $user = $this->Users->get($id, [
            'contain' => []
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $user = $this->Users->patchEntity($user, $this->request->data);
            if ($this->Users->save($user)) {
                $this->Flash->success('The user has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The user could not be saved. Please, try again.');
            }
        }
        $this->set(compact('user'));
        $this->set('_serialize', ['user']);
    }

    /**
     * Delete method
     *
     * @param string|null $id User id.
     * @return void Redirects to index.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $user = $this->Users->get($id);
        if ($this->Users->delete($user)) {
            $this->Flash->success('The user has been deleted.');
        } else {
            $this->Flash->error('The user could not be deleted. Please, try again.');
        }
        return $this->redirect(['action' => 'index']);
    }

    public function login()
    {
        if ($this->request->is('post')) {
            $user = $this->Auth->identify();
            if ($user) {
                $this->Auth->setUser($user);
                return $this->redirect($this->Auth->redirectUrl());
            }
            $this->Flash->error('Your username or password is incorrect.');
        }
    }

    public function logout()
    {
        $this->Flash->success('You are now logged out.');
        return $this->redirect($this->Auth->logout());
    }

}
AppControllerに認証有無を設定する

src/Controller/AppController.php

<?php
/**
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link      http://cakephp.org CakePHP(tm) Project
 * @since     0.2.9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT License
 */
namespace App\Controller;

use Cake\Controller\Controller;

/**
 * Application Controller
 *
 * Add your application-wide methods in the class below, your controllers
 * will inherit them.
 *
 * @link http://book.cakephp.org/3.0/en/controllers.html#the-app-controller
 */
class AppController extends Controller
{

    /**
     * Initialization hook method.
     *
     * Use this method to add common initialization code like loading components.
     *
     * @return void
     */
    public function initialize()
    {
        $this->loadComponent('Flash');
        $this->loadComponent('Auth', [
            'authorize'=> 'Controller',// 追加
            'authenticate' => [
                'Form' => [
                    'fields' => [
                        'username' => 'email',
                        'password' => 'password'
                    ]
                ]
            ],
            'loginAction' => [
                'controller' => 'Users',
                'action' => 'login'
            ]
        ]);

        // Allow the display action so our pages controller
        // continues to work.
        $this->Auth->allow(['display']);
    }

    // メソッドを追加
    public function isAuthorized($user)
    {
        return false;
    }
}
BookmarksControllerの認証設定を確認

src/Controller/BookmarksController.php

<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * Bookmarks Controller
 *
 * @property \App\Model\Table\BookmarksTable $Bookmarks
 */
class BookmarksController extends AppController
{

    /**
     * Index method
     *
     * @return void
     */
    public function index()
    {
        $this->paginate = [
            'contain' => ['Users']
        ];
        $this->set('bookmarks', $this->paginate($this->Bookmarks));
        $this->set('_serialize', ['bookmarks']);
    }

    /**
     * View method
     *
     * @param string|null $id Bookmark id.
     * @return void
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function view($id = null)
    {
        $bookmark = $this->Bookmarks->get($id, [
            'contain' => ['Users', 'Tags', 'BookmarksTags']
        ]);
        $this->set('bookmark', $bookmark);
        $this->set('_serialize', ['bookmark']);
    }

    /**
     * Add method
     *
     * @return void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $bookmark = $this->Bookmarks->newEntity();
        if ($this->request->is('post')) {
            $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
            if ($this->Bookmarks->save($bookmark)) {
                $this->Flash->success('The bookmark has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The bookmark could not be saved. Please, try again.');
            }
        }
        $users = $this->Bookmarks->Users->find('list', ['limit' => 200]);
        $tags = $this->Bookmarks->Tags->find('list', ['limit' => 200]);
        $this->set(compact('bookmark', 'users', 'tags'));
        $this->set('_serialize', ['bookmark']);
    }

    /**
     * Edit method
     *
     * @param string|null $id Bookmark id.
     * @return void Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $bookmark = $this->Bookmarks->get($id, [
            'contain' => ['Tags']
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
            if ($this->Bookmarks->save($bookmark)) {
                $this->Flash->success('The bookmark has been saved.');
                return $this->redirect(['action' => 'index']);
            } else {
                $this->Flash->error('The bookmark could not be saved. Please, try again.');
            }
        }
        $users = $this->Bookmarks->Users->find('list', ['limit' => 200]);
        $tags = $this->Bookmarks->Tags->find('list', ['limit' => 200]);
        $this->set(compact('bookmark', 'users', 'tags'));
        $this->set('_serialize', ['bookmark']);
    }

    /**
     * Delete method
     *
     * @param string|null $id Bookmark id.
     * @return void Redirects to index.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $bookmark = $this->Bookmarks->get($id);
        if ($this->Bookmarks->delete($bookmark)) {
            $this->Flash->success('The bookmark has been deleted.');
        } else {
            $this->Flash->error('The bookmark could not be deleted. Please, try again.');
        }
        return $this->redirect(['action' => 'index']);
    }

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

    // 認証有無確認メソッド追加
    public function isAuthorized($user)
    {
        $action = $this->request->params['action'];

        // The add and index actions are always allowed.
        if (in_array($action, ['index', 'add', 'tags'])) {
            return true;
        }
        // All other actions require an id.
        if (empty($this->request->params['pass'][0])) {
            return false;
        }

        // Check that the bookmark belongs to the current user.
        $id = $this->request->params['pass'][0];
        $bookmark = $this->Bookmarks->get($id);
        if ($bookmark->user_id == $user['id']) {
            return true;
        }
        return parent::isAuthorized($user);
    }
}
認証メッセージを追加

src/Template/Layout/default.ctp

<?php
/**
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 *
 * Licensed under The MIT License
 * For full copyright and license information, please see the LICENSE.txt
 * Redistributions of files must retain the above copyright notice.
 *
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
 * @link          http://cakephp.org CakePHP(tm) Project
 * @since         0.10.0
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
 */

$cakeDescription = 'CakePHP: the rapid development php framework';
?>
<!DOCTYPE html>
<html>
<head>
    <?= $this->Html->charset() ?>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>
        <?= $cakeDescription ?>:
        <?= $this->fetch('title') ?>
    </title>
    <?= $this->Html->meta('icon') ?>

    <?= $this->Html->css('base.css') ?>
    <?= $this->Html->css('cake.css') ?>

    <?= $this->fetch('meta') ?>
    <?= $this->fetch('css') ?>
    <?= $this->fetch('script') ?>
</head>
<body>
    <header>
        <div class="header-title">
            <span><?= $this->fetch('title') ?></span>
        </div>
        <div class="header-help">
            <span><a target="_blank" href="http://book.cakephp.org/3.0/">Documentation</a></span>
            <span><a target="_blank" href="http://api.cakephp.org/3.0/">API</a></span>
        </div>
    </header>
    <div id="container">

        <div id="content">
            <?= $this->Flash->render() ?>
            <?= $this->Flash->render('auth') ?>
            <div class="row">
                <?= $this->fetch('content') ?>
            </div>
        </div>
        <footer>
        </footer>
    </div>
</body>
</html>
BookmarksControllerを編集して追加、編集、リスト参照
<?php
namespace App\Controller;

use App\Controller\AppController;

/**
 * Bookmarks Controller
 *
 * @property \App\Model\Table\BookmarksTable $Bookmarks
 */
class BookmarksController extends AppController
{

    /**
     * Index method
     *
     * @return void
     */
    public function index()
    {
        $this->paginate = [
            'conditions' => [
                'Bookmarks.user_id' => $this->Auth->user('id'),
            ]
        ];
        $this->set('bookmarks', $this->paginate($this->Bookmarks));
    }

    /**
     * View method
     *
     * @param string|null $id Bookmark id.
     * @return void
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function view($id = null)
    {
        $bookmark = $this->Bookmarks->get($id, [
            'contain' => ['Users', 'Tags', 'BookmarksTags']
        ]);
        $this->set('bookmark', $bookmark);
        $this->set('_serialize', ['bookmark']);
    }

    /**
     * Add method
     *
     * @return void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
        $bookmark = $this->Bookmarks->newEntity($this->request->data);
        $bookmark->user_id = $this->Auth->user('id');
        if ($this->request->is('post')) {
            if ($this->Bookmarks->save($bookmark)) {
                $this->Flash->success('The bookmark has been saved.');
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error('The bookmark could not be saved. Please, try again.');
        }
        $tags = $this->Bookmarks->Tags->find('list');
        $this->set(compact('bookmark', 'tags'));
    }

    /**
     * Edit method
     *
     * @param string|null $id Bookmark id.
     * @return void Redirects on successful edit, renders view otherwise.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function edit($id = null)
    {
        $bookmark = $this->Bookmarks->get($id, [
            'contain' => ['Tags']
        ]);
        if ($this->request->is(['patch', 'post', 'put'])) {
            $bookmark = $this->Bookmarks->patchEntity($bookmark, $this->request->data);
            $bookmark->user_id = $this->Auth->user('id');
            if ($this->Bookmarks->save($bookmark)) {
                $this->Flash->success('The bookmark has been saved.');
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error('The bookmark could not be saved. Please, try again.');
        }
        $tags = $this->Bookmarks->Tags->find('list');
        $this->set(compact('bookmark', 'tags'));
    }

    /**
     * Delete method
     *
     * @param string|null $id Bookmark id.
     * @return void Redirects to index.
     * @throws \Cake\Network\Exception\NotFoundException When record not found.
     */
    public function delete($id = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        $bookmark = $this->Bookmarks->get($id);
        if ($this->Bookmarks->delete($bookmark)) {
            $this->Flash->success('The bookmark has been deleted.');
        } else {
            $this->Flash->error('The bookmark could not be deleted. Please, try again.');
        }
        return $this->redirect(['action' => 'index']);
    }

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

    // 認証有無確認メソッド追加
    public function isAuthorized($user)
    {
        $action = $this->request->params['action'];

        // The add and index actions are always allowed.
        if (in_array($action, ['index', 'add', 'tags'])) {
            return true;
        }
        // All other actions require an id.
        if (empty($this->request->params['pass'][0])) {
            return false;
        }

        // Check that the bookmark belongs to the current user.
        $id = $this->request->params['pass'][0];
        $bookmark = $this->Bookmarks->get($id);
        if ($bookmark->user_id == $user['id']) {
            return true;
        }
        return parent::isAuthorized($user);
    }
}
Bookmarkのエンティティを編集

src/Model/Entity/Bookmark.php

<?php
namespace App\Model\Entity;

use Cake\ORM\Entity;
use Cake\Collection\Collection;
/**
 * Bookmark Entity.
 */
class Bookmark extends Entity
{

    /**
     * Fields that can be mass assigned using newEntity() or patchEntity().
     *
     * @var array
     */
    protected $_accessible = [
        'user_id' => true,
        'title' => true,
        'description' => true,
        'url' => true,
        'user' => true,
        'tags' => true,
    ];

    protected function _getTagString()
    {
        if (isset($this->_properties['tag_string'])) {
            return $this->_properties['tag_string'];
        }
        if (empty($this->tags)) {
            return '';
        }
        $tags = new Collection($this->tags);
        $str = $tags->reduce(function ($string, $tag) {
            return $string . $tag->title . ', ';
        }, '');
        return trim($str, ', ');
    }
}
$_accessibleを追加
    protected $_accessible = [
        'user_id' => true,
        'title' => true,
        'description' => true,
        'url' => true,
        'user' => true,
        'tags' => true,
        'tag_string' => true, // 追加
    ];
Viewを変更

src/Template/Bookmarks/add.ctp

<div class="actions columns large-2 medium-3">
    <h3><?= __('Actions') ?></h3>
    <ul class="side-nav">
        <li><?= $this->Html->link(__('List Bookmarks'), ['action' => 'index']) ?></li>
        <li><?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?> </li>
        <li><?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?> </li>
        <li><?= $this->Html->link(__('List Bookmarks Tags'), ['controller' => 'BookmarksTags', 'action' => 'index']) ?> </li>
        <li><?= $this->Html->link(__('New Bookmarks Tag'), ['controller' => 'BookmarksTags', 'action' => 'add']) ?> </li>
        <li><?= $this->Html->link(__('List Tags'), ['controller' => 'Tags', 'action' => 'index']) ?> </li>
        <li><?= $this->Html->link(__('New Tag'), ['controller' => 'Tags', 'action' => 'add']) ?> </li>
    </ul>
</div>
<div class="bookmarks form large-10 medium-9 columns">
    <?= $this->Form->create($bookmark); ?>
    <fieldset>
        <legend><?= __('Add Bookmark') ?></legend>
        <?php
            echo $this->Form->input('user_id', ['options' => $users]);
            echo $this->Form->input('title');
            echo $this->Form->input('description');
            echo $this->Form->input('url');
            echo $this->Form->input('tag_string', ['type' => 'text']);
        ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
    <?= $this->Form->end() ?>
</div>

src/Template/Bookmarks/edit.ctp

<div class="actions columns large-2 medium-3">
    <h3><?= __('Actions') ?></h3>
    <ul class="side-nav">
        <li><?= $this->Form->postLink(
                __('Delete'),
                ['action' => 'delete', $bookmark->id],
                ['confirm' => __('Are you sure you want to delete # {0}?', $bookmark->id)]
            )
        ?></li>
        <li><?= $this->Html->link(__('List Bookmarks'), ['action' => 'index']) ?></li>
        <li><?= $this->Html->link(__('List Users'), ['controller' => 'Users', 'action' => 'index']) ?> </li>
        <li><?= $this->Html->link(__('New User'), ['controller' => 'Users', 'action' => 'add']) ?> </li>
        <li><?= $this->Html->link(__('List Bookmarks Tags'), ['controller' => 'BookmarksTags', 'action' => 'index']) ?> </li>
        <li><?= $this->Html->link(__('New Bookmarks Tag'), ['controller' => 'BookmarksTags', 'action' => 'add']) ?> </li>
        <li><?= $this->Html->link(__('List Tags'), ['controller' => 'Tags', 'action' => 'index']) ?> </li>
        <li><?= $this->Html->link(__('New Tag'), ['controller' => 'Tags', 'action' => 'add']) ?> </li>
    </ul>
</div>
<div class="bookmarks form large-10 medium-9 columns">
    <?= $this->Form->create($bookmark); ?>
    <fieldset>
        <legend><?= __('Edit Bookmark') ?></legend>
        <?php
            echo $this->Form->input('user_id', ['options' => $users]);
            echo $this->Form->input('title');
            echo $this->Form->input('description');
            echo $this->Form->input('url');
            echo $this->Form->input('tag_string', ['type' => 'text']);
        ?>
    </fieldset>
    <?= $this->Form->button(__('Submit')) ?>
    <?= $this->Form->end() ?>
</div>
Tag String設定

src/Model/Table/BookmarksTable.php

<?php
namespace App\Model\Table;

use App\Model\Entity\Bookmark;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

/**
 * Bookmarks Model
 */
class BookmarksTable extends Table
{

    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        $this->table('bookmarks');
        $this->displayField('title');
        $this->primaryKey('id');
        $this->addBehavior('Timestamp');
        $this->belongsTo('Users', [
            'foreignKey' => 'user_id'
        ]);
        $this->belongsToMany('Tags', [
            'foreignKey' => 'bookmark_id',
            'targetForeignKey' => 'tag_id',
            'joinTable' => 'bookmarks_tags'
        ]);
    }

    /**
     * Default validation rules.
     *
     * @param \Cake\Validation\Validator $validator Validator instance.
     * @return \Cake\Validation\Validator
     */
    public function validationDefault(Validator $validator)
    {
        $validator
            ->add('id', 'valid', ['rule' => 'numeric'])
            ->allowEmpty('id', 'create')
            ->add('user_id', 'valid', ['rule' => 'numeric'])
            ->requirePresence('user_id', 'create')
            ->notEmpty('user_id')
            ->allowEmpty('title')
            ->allowEmpty('description')
            ->allowEmpty('url');

        return $validator;
    }

    /**
     * Returns a rules checker object that will be used for validating
     * application integrity.
     *
     * @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
     * @return \Cake\ORM\RulesChecker
     */
    public function buildRules(RulesChecker $rules)
    {
        $rules->add($rules->existsIn(['user_id'], 'Users'));
        return $rules;
    }

    // 追加
    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']]);
            });
    }

    public function beforeSave($event, $entity, $options)
    {
        if ($entity->tag_string) {
            $entity->tags = $this->_buildTags($entity->tag_string);
        }
    }

    protected function _buildTags($tagString)
    {
        $new = array_unique(array_map('trim', explode(',', $tagString)));
        $out = [];
        $query = $this->Tags->find()
            ->where(['Tags.title IN' => $new]);

        // Remove existing tags from the list of new tags.
        foreach ($query->extract('title') as $existing) {
            $index = array_search($existing, $new);
            if ($index !== false) {
                unset($new[$index]);
            }
        }
        // Add existing tags.
        foreach ($query as $tag) {
            $out[] = $tag;
        }
        // Add new tags.
        foreach ($new as $tag) {
            $out[] = $this->Tags->newEntity(['title' => $tag]);
        }
        return $out;
    }
}

結果

f:id:m_shige1979:20150208110902p:plain
※やっつけだからなんかうまく動かないかも…

github

https://github.com/mshige1979/cakephp3_bookmark1
※一部は動かせそうだけど、認証とかいろいろ加工がまだまだ必要

所感

英語難しい、ソースを見てもうーん???っていう場合がある。
ある程度なんとかなる感じがしてきたけど
うまくとりかかる要因がまだないのでなんか作るアイデアを捻出しよう

cakephp3【beta3】クエリービルダーでjoin

1つのテーブルより別のテーブルと紐付ける場合

joinを使用することが可能らしい

containsとかあったけどなんか関連付のイメージがしっくりきていないのでjoinをやってみる

準備

テーブル
CREATE TABLE `members` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `comments` (
  `member_id` int(11) NOT NULL,
  `id` int(11) NOT NULL,
  `data` varchar(255) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`member_id`, `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
雛形作成
sh app/bin/cake bake shell sample1
sh app/bin/cake bake model members
sh app/bin/cake bake model comments

サンプル1

join
<?php
namespace App\Shell;

use Cake\Console\Shell;
use Cake\ORM\TableRegistry;

/**
 * Sample1 shell command.
 */
class Sample1Shell extends Shell {

/**
 * main() method.
 *
 * @return bool|int Success or error code.
 */
	public function main() {
		// table
		$members = TableRegistry::get('Members');

		// find
		$query = $members->find()
			->hydrate(false)
			->join([
				'table' => 'comments',
				'alias' => 'c',
				'type' => 'LEFT',
				'conditions' => 'c.member_id = Members.id',
			])->select([
				"id" => "Members.id"
				, "name" => "Members.name"
				, "comments_data" => "c.data"
			]);

		// SQ
		var_dump($query->sql());

		// 出力
		foreach($query as $item){
			var_dump($item);
		}
		
	}

}
出力されたSQL(整形済み)
SELECT 
    Members.id AS `id`, 
    Members.name AS `name`, 
    c.data AS `comments_data` 

FROM 
    members Members 
    LEFT JOIN comments c ON 
        c.member_id = Members.id
抽出できるデータ
array(3) {
  'id' =>
  int(1)
  'name' =>
  string(3) "aaa"
  'comments_data' =>
  string(7) "aaa-111"
}
array(3) {
  'id' =>
  int(1)
  'name' =>
  string(3) "aaa"
  'comments_data' =>
  string(7) "aaa-222"
}
array(3) {
  'id' =>
  int(1)
  'name' =>
  string(3) "aaa"
  'comments_data' =>
  string(7) "aaa-333"
}
array(3) {
  'id' =>
  int(2)
  'name' =>
  string(3) "bbb"
  'comments_data' =>
  string(7) "bbb-111"
}
array(3) {
  'id' =>
  int(3)
  'name' =>
  string(3) "ccc"
  'comments_data' =>
  string(7) "ccc-111"
}
array(3) {
  'id' =>
  int(3)
  'name' =>
  string(3) "ccc"
  'comments_data' =>
  string(7) "ccc-222"
}

まとめ

joinでleftやinnerを使用できる。
その場合は一緒に抽出したいテーブルのカラムはセットしておかないと取得できない

「hydrate」を「false」で指定した場合は配列で項目を取得、「true」の場合はプロパティみたいな感じになる

所感

cakephp3では日本語化が微妙な感じですすんでいる感じ。ただ、まだ英語の方が詳しく書かれているのでそちらしか参考にできない部分があると思います。
なんかだんだんとやっぱりSQLがいいなと思い始めて聞か感じがする。無駄に学習コストがかかると便利には感じないかも…

cakephp2でプラグインを作成

作ったことがない

ので作成する

参考資料

プラグイン — CakePHP Cookbook 2.x ドキュメント
※公式だけどなんかいまいちしっくりきていない

環境構築

mkdir app1
cd app1/
git clone https://github.com/cakephp/cakephp.git -b 2.5.3 .
git submodule add https://github.com/cakephp/debug_kit.git app/Plugin/DebugKit

※あとはいろいろとデータベースとかcore.phpとかごにょごにょする

サンプルプラグイン

プラグイン

ContactManager

コントローラーなど

contacts

モデル用のtable

CREATE TABLE `contacts` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `data` varchar(256) DEFAULT NULL,
  `created` datetime DEFAULT NULL,
  `modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

サンプル

作成
sh app/Console/cake bake plugin ContactManager
sh app/Console/cake bake controller Contacts --plugin ContactManager
sh app/Console/cake bake model Contacts --plugin ContactManager
sh app/Console/cake bake view Contacts --plugin ContactManager

※最初にテーブルを作成しておくこと

app/Config/bootstrap.php
CakePlugin::load('ContactManager', array('bootstrap' => false, 'routes' => false));

※末尾に追加されている

プラグイン構成
.
├── Config
│   └── Schema
│       └── empty
├── Console
│   └── Command
│       └── Task
│           └── empty
├── Controller
│   ├── Component
│   │   └── empty
│   ├── ContactManagerAppController.php
│   └── ContactsController.php
├── Lib
│   └── empty
├── Model
│   ├── Behavior
│   │   └── empty
│   ├── ContactManagerAppModel.php
│   ├── Contact.php
│   └── Datasource
│       └── empty
├── Test
│   ├── Case
│   │   ├── Controller
│   │   │   ├── Component
│   │   │   │   └── empty
│   │   │   └── ContactsControllerTest.php
│   │   ├── Model
│   │   │   ├── Behavior
│   │   │   │   └── empty
│   │   │   └── ContactTest.php
│   │   └── View
│   │       └── Helper
│   │           └── empty
│   └── Fixture
│       ├── ContactFixture.php
│       └── empty
├── Vendor
│   └── empty
├── View
│   ├── Contacts
│   │   ├── add.ctp
│   │   ├── edit.ctp
│   │   ├── index.ctp
│   │   └── view.ctp
│   └── Helper
│       └── empty
└── webroot
    └── empty

25 directories, 24 files
$ 

アクセス

所感

プラグインを作成することでアプリケーションとは分離した機能を付与できるかも、
ビューだけやヘルパー、モデルだけのプラグインも作成できると思われる
毎回コントローラーなどを用意したものをプラグインとして用意することも考えておく必要がありそう。

気になっていること

debugkitってどうやって出力しているんだろう?
よくわからない…

cakephp3のmigrations3

テーブル追加などのいくつかの機能

なんか必要そうな記法を調査

テーブル

テーブル作成
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $sample1 = $this->table("sample1");
        $sample1
            ->create()
            ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

※テーブルに何も指定しない場合はidが主キーで設定される

idを主キーにしない
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $this->dropTable("sample1");
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("shohin_cd", "string", array("limit" => 10))
            ->addColumn("shohin_name", "string", array("limit" => 256))
            ->create()
            ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `shohin_cd` varchar(10) NOT NULL,
  `shohin_name` varchar(256) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

※idを無効にしたら主キーは任意で設定しないといけない

プライマリキーを設定
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $this->dropTable("sample1");
        $sample1 = $this->table("sample1"
            , array(
                "id" => false,
                "primary_key" => array("shohin_cd")
            )
        );
        $sample1
            ->addColumn("shohin_cd", "string", array("limit" => 10))
            ->addColumn("shohin_name", "string", array("limit" => 256))
            ->create()
            ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `shohin_cd` varchar(10) NOT NULL,
  `shohin_name` varchar(256) NOT NULL,
  PRIMARY KEY (`shohin_cd`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

※テーブル設定時にプライマリキーを指定する場合

テーブル名変更
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $this->table("sample1")
            ->rename("hoge11111");
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

※tableで取得したあとにrenameメソッドで変更

テーブル削除
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        $this->droptable("hoge11111");
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

※dropTableメソッドで削除

項目のデータ型

string
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        if($this->hasTable("sample1")){
            $this->dropTable("sample1");
        }
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("data1", "string")
            ->addColumn("data2", "string", array("limit" => 100))
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `data1` varchar(255) NOT NULL,
  `data2` varchar(100) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

※varcharで未指定の場合は255桁数となる

integer、biginteger
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        if($this->hasTable("sample1")){
            $this->dropTable("sample1");
        }
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("data1", "integer")
            ->addColumn("data2", "integer", array("limit" => 10))
            ->addColumn("data3", "biginteger")
            ->addColumn("data4", "biginteger", array("limit" => 10))
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `data1` int(11) NOT NULL,
  `data2` int(10) NOT NULL,
  `data3` bigint(20) NOT NULL,
  `data4` bigint(10) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

※integerやbigintegerの場合

decimal
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        if($this->hasTable("sample1")){
            $this->dropTable("sample1");
        }
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("data1", "decimal")
            ->addColumn("data2", "decimal", array("limit" => "5,2"))
            // ->addColumn("data2", "decimal", array("precision" => 5, "scale" => 2))
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `data1` decimal(10,0) NOT NULL,
  `data2` decimal(5,2) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

※データの桁数が小数部も指定する場合は”n,n”のように指定すればできるよう

text
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        if($this->hasTable("sample1")){
            $this->dropTable("sample1");
        }
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("data1", "text")
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `data1` text NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
date、time、datetime
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        if($this->hasTable("sample1")){
            $this->dropTable("sample1");
        }
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("data1", "date")
            ->addColumn("data2", "time")
            ->addColumn("data3", "datetime")
            ->addColumn("data4", "timestamp")
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `data1` date NOT NULL,
  `data2` time NOT NULL,
  `data3` datetime NOT NULL,
  `data4` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
その他
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        if($this->hasTable("sample1")){
            $this->dropTable("sample1");
        }
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("data1", "binary")
            ->addColumn("data2", "boolean")
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

CREATE TABLE `sample1` (
  `data1` blob NOT NULL,
  `data2` tinyint(1) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

追加位置

予めテーブルを作成
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Migrate Up.
     */
    public function up()
    {
        if($this->hasTable("sample1")){
            $this->dropTable("sample1");
        }
        $sample1 = $this->table("sample1"
            , array(
                "id" => false
            )
        );
        $sample1
            ->addColumn("data1", "string")
            ->addColumn("data2", "string")
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}
data1の後に追加
<?php

use Phinx\Migration\AbstractMigration;

class Sample1 extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     *
     * Uncomment this method if you would like to use it.
     *
    public function change()
    {
    }
    */
    
    /**
     * Migrate Up.
     */
    public function up()
    {
        $sample1 = $this->table("sample1");
        $sample1
            ->addColumn("hoge1", "integer", array("after" => "data1"))
            ->addColumn("hoge2", "char", array("after" => "hoge1"))
            ->save();
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

※afterに設定

CREATE TABLE `sample1` (
  `data1` varchar(255) NOT NULL,
  `hoge1` int(11) NOT NULL,
  `hoge2` char(255) NOT NULL,
  `data2` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

所感

全部のデータ型を保証しているわけではない感じ。
一部は対応できるけど、できない部分はSQLで対応するしかないかも…

cakephp3のmigrations2

ちょっと前回はあれなんで


cakephp3のmigrations - m_shige1979のささやかな抵抗と欲望の日々

1個マイグレーションファイルを作成しても

で?

と思ったのでもう少しやってみる

やったこと

  1. 初回作成
  2. テーブルに項目追加
  3. テーブルに項目削除、新規テーブル作成
  4. ロールバック

初回作成

マイグレーションファイルを作成
$ sh app/bin/cake migrations create Initial

Welcome to CakePHP v3.0.0-beta2 Console
---------------------------------------------------------------
App : src
Path: /vagrant/projects/beta2/app/src/
---------------------------------------------------------------
using migration path /vagrant/projects/beta2/app/config/Migrations
created ./app/config/Migrations/20141016121505_initial.php
$

マイグレーション名はキャメルケースなんで小文字はだめらしい

20141016121505_initial.php
<?php

use Phinx\Migration\AbstractMigration;

class Initial extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     *
     * Uncomment this method if you would like to use it.
     *
    public function change()
    {
    }
    */
    
    /**
     * Migrate Up.
     */
    public function up()
    {
        $sample1 = $this->table("sample1");
        $sample1
            ->addColumn("name", "string", array("limit" => 128))
            ->addColumn("deleted", "integer")
            ->addColumn("created", "datetime")
            ->addColumn("modified", "datetime")
            ->create()
            ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}

※簡単にテーブルを作成する

実行
$ sh app/bin/cake migrations migrate

Welcome to CakePHP v3.0.0-beta2 Console
---------------------------------------------------------------
App : src
Path: /vagrant/projects/beta2/app/src/
---------------------------------------------------------------
using migration path /vagrant/projects/beta2/app/config/Migrations
using environment default
using adapter mysql
using database my_app

 == 20141016121505 Initial: migrating
 == 20141016121505 Initial: migrated 0.0321s

All Done. Took 0.0578s
$

※「20141016121505」が実行タイムスタンプ

DB内容

f:id:m_shige1979:20141016213523p:plain
f:id:m_shige1979:20141016213647p:plain

2回目

マイグレーションを作成
$ sh app/bin/cake migrations create Sample1

Welcome to CakePHP v3.0.0-beta2 Console
---------------------------------------------------------------
App : src
Path: /vagrant/projects/beta2/app/src/
---------------------------------------------------------------
using migration path /vagrant/projects/beta2/app/config/Migrations
created ./app/config/Migrations/20141016124037_sample1.php
$
20141016124037_sample1.php
<?php

use Phinx\Migration\AbstractMigration;

class Sample1 extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     *
     * Uncomment this method if you would like to use it.
     *
    public function change()
    {
    }
    */
    
    /**
     * Migrate Up.
     */
    public function up()
    {
        $sample1 = $this->table("sample1");
        $sample1
            ->addColumn("age", "integer")
            ->addColumn("sex", "integer")
            ->save()
            ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}
マイグレーション
$ sh app/bin/cake migrations migrate

Welcome to CakePHP v3.0.0-beta2 Console
---------------------------------------------------------------
App : src
Path: /vagrant/projects/beta2/app/src/
---------------------------------------------------------------
using migration path /vagrant/projects/beta2/app/config/Migrations
using environment default
using adapter mysql
using database my_app

 == 20141016124037 Sample1: migrating
 == 20141016124037 Sample1: migrated 0.0791s

All Done. Took 0.0988s
$
結果

f:id:m_shige1979:20141016214647p:plain
f:id:m_shige1979:20141016214721p:plain

3回目

マイグレーションファイルを作成
$ sh app/bin/cake migrations create Sample2

Welcome to CakePHP v3.0.0-beta2 Console
---------------------------------------------------------------
App : src
Path: /vagrant/projects/beta2/app/src/
---------------------------------------------------------------
using migration path /vagrant/projects/beta2/app/config/Migrations
created ./app/config/Migrations/20141016125733_sample2.php
$
20141016125733_sample2.php
<?php

use Phinx\Migration\AbstractMigration;

class Sample2 extends AbstractMigration
{
    /**
     * Change Method.
     *
     * More information on this method is available here:
     * http://docs.phinx.org/en/latest/migrations.html#the-change-method
     *
     * Uncomment this method if you would like to use it.
     *
    public function change()
    {
    }
    */
    
    /**
     * Migrate Up.
     */
    public function up()
    {

        // 変更
        $sample1 = $this->table("sample1");
        $sample1
            ->removeColumn("age")
            ->renameColumn("sex", "seibetsu")
            ->addColumn("memo", "string")
            ->save()
        ;

        // テーブル作成
        $sample2 = $this->table("sample2");
        $sample2
            ->addColumn("name", "string", array("limit" => 256))
            ->addColumn("memo", "string")
            ->addColumn("memo2", "string")
            ->addColumn("deleted", "integer")
            ->addColumn("created", "datetime")
            ->addColumn("modified", "datetime")
            ->create()
        ;
    }

    /**
     * Migrate Down.
     */
    public function down()
    {

    }
}
実行
$ sh app/bin/cake migrations migrate

Welcome to CakePHP v3.0.0-beta2 Console
---------------------------------------------------------------
App : src
Path: /vagrant/projects/beta2/app/src/
---------------------------------------------------------------
using migration path /vagrant/projects/beta2/app/config/Migrations
using environment default
using adapter mysql
using database my_app

 == 20141016125733 Sample2: migrating
 == 20141016125733 Sample2: migrated 0.0860s

All Done. Took 0.1146s
$ 
結果

f:id:m_shige1979:20141016221154p:plain
f:id:m_shige1979:20141016221230p:plain
f:id:m_shige1979:20141016221310p:plain
※新しいテーブルも追加されている

ロールバック

したらどうなる

実行
$ sh app/bin/cake migrations rollback

Welcome to CakePHP v3.0.0-beta2 Console
---------------------------------------------------------------
App : src
Path: /vagrant/projects/beta2/app/src/
---------------------------------------------------------------
using migration path /vagrant/projects/beta2/app/config/Migrations
using environment default
using adapter mysql
using database my_app

 == 20141016125733 Sample2: reverting
 == 20141016125733 Sample2: reverted 0.0020s

All Done. Took 0.0259s
$
結果

f:id:m_shige1979:20141016221717p:plain
※前回実行したテーブルの定義は変わらないけど前回実行した履歴がなくなるのでやり直すことができる

まとめ

所感

データベースのクライアントツールsqlで定義を修正し直すのが嫌な人は楽になりそう。
また、デプロイ時にテーブルにテーブルの定義を多少簡単にできる、
追加位置や設定パラメータがどこまで対応できるかもう少し調査してみる。