Edited at

CakePHP3 で開発環境準備、改訂版

More than 1 year has passed since last update.


インストール

まずは本体のインストール。

sudo composer create-project --prefer-dist cakephp/app myApp

無事終了したら、パーミッションを変えておく(OSXのみ)

sudo chown -R mymac:staff myApp

ここで一旦 .gitignore を編集して、 コミットしておく。

tmpとlogsをコメントアウトして、自分の環境でよくあるやつを入れておく。

cd myApp

vim .gitignore


.gitignore

/vendor/*

/config/app.php
#/tmp/*
#/logs/*

.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
Icon?
ehthumbs.db
Thumbs.db
*.swp
*.un~
*~
*BAK


あとはいつものgit処理で。

git init

git add .
git commit -m "Initialize repository"
git remote add origin https://github.com/xxxxxxx/myApp.git
git push -u origin master

pushしたらtmpとlogsのコメントアウトを外しておく。


.gitignore

/tmp/*

/logs/*

一旦サーバー起動してみて動作確認。

bin/cake server

http://localhost:8765/


Composerの設定

composer.jsonを編集する。

suggest に入っている phpunit と codesniffer を require-dev に移す。

ついでに heroku も設定しておく。

あと、PDF生成もするかもしれないので tcpdf も一応いれておく。


composer.json

    "require": {

"tecnick.com/tcpdf": "*"
},
"require-dev": {
"phpunit/phpunit": "*",
"cakephp/cakephp-codesniffer": "*",
"heroku/heroku-buildpack-php": "*"
},

composerのアップデートをかける

sudo composer update

無事入ったら、phpunitの動作確認。

vendor/bin/phpunit

PHPUnit 4.8.6 by Sebastian Bergmann and contributors.

Time: 297 ms, Memory: 7.25Mb

No tests executed!


データベース設定

この時点で(もっとそれより早くて構わないが)データベースを準備しておく。

テスト用のDBも作っておく。

CREATE DATABASE myapp_db DEFAULT CHARACTER SET utf8;

CREATE DATABASE test_myapp_db DEFAULT CHARACTER SET utf8;

config/app.php を編集してDB接続情報を入れておく。


config/app.php

    'Datasources' => [

'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'root',
'password' => 'password',
'database' => 'myapp_db',
'encoding' => 'utf8',
//'timezone' => 'UTC',
'cacheMetadata' => true,

],

/**
* The test connection is used during the test suite.
*/

'test' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => 'localhost',
'username' => 'root',
'password' => 'password',
'database' => 'test_myapp_db',
'encoding' => 'utf8',
//'timezone' => 'UTC',
'cacheMetadata' => true,
'quoteIdentifiers' => false,
],
],



migration

migration を使って、一つのテーブルを軽く作る。(ここでは例としてArticlesテーブルを作っている)

bin/cake bake migration CreateArticles name:string created modified

config/Migrations 以下に migrationファイルが生成されるので、編集して、その他のカラム設定を追加し、テーブル定義を完成させる。

vim config/Migrations/20150826024512_create_articles.php

以下のURLを参考に。

http://book.cakephp.org/3.0/en/migrations.html

<?php

use Phinx\Migration\AbstractMigration;

class CreateArticles extends AbstractMigration
{
/**
* Change Method.
*
* More information on this method is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-change-method
* @return void
*/

public function change()
{
$table = $this->table('articles');
$table->addColumn('name', 'string', [
'default' => null,
'limit' => 255,
'null' => false,
]);
$table->addColumn('age', 'integer', [
'default' => null,
'limit' => 11,
'null' => true,
]);
$table->addColumn('posted_date', 'date', [
'default' => null,
'limit' => null,
'null' => true,
]);
$table->addColumn('created', 'datetime', [
'default' => null,
'null' => false,
]);
$table->addColumn('modified', 'datetime', [
'default' => null,
'null' => false,
]);
$table->create();
}
}

ファイルを保存したら、以下のコマンドでDBにテーブルを生成。

bin/cake migrations migrate

注)このやり方だと例えばmysql のview tableなんかを使っているとうまくいかない。

結局はMySQLに接続して直接操作した方がよいのかもしれない。

参考:http://qiita.com/yutaono/items/34bdc9383ce5870e7b8b#3-3

terminalから直接mysql接続できる。

続いて、以下のコマンドで、articlesテーブルのモデルをbakeコマンドで自動生成。

(するとtestsディレクトリ以下にテストケースとフィクスチャーも自動生成される)

bin/cake bake model Articles

再びテストしてみる。(以下でテスト結果が出るはず)

vendor/bin/phpunit

PHPUnit 4.8.6 by Sebastian Bergmann and contributors.

II

Time: 425 ms, Memory: 9.75Mb

OK, but incomplete, skipped, or risky tests!
Tests: 2, Assertions: 0, Incomplete: 2.


コーディング規約

さらにコードスニッッファーの設定をする。

以下のコマンドを打つ。

sudo vendor/bin/phpcs --config-set installed_paths vendor/cakephp/cakephp-codesniffer

試してみる。

vendor/bin/phpcs --standard=CakePHP src/

エラーが出る(^^;

FILE: .../myApp/src/Model/Table/ArticlesTable.php

----------------------------------------------------------------------
FOUND 1 ERROR AFFECTING 1 LINE
----------------------------------------------------------------------
33 | ERROR | Expected 0 blank lines before closing function brace; 1
| | found
----------------------------------------------------------------------

Time: 398ms; Memory: 7.25Mb

直したきゃ直す。

vimの人は.vimrcを直しておくとよい。


.vimrc

set autoindent

set expandtab
set ts=4
set sw=4


npm install

npmを使ってjs関係の設定をする。

npm init

いろいろ質問されるのでエンターキーを押す。

引き続き、以下をだーっとインストールする。

npm install jquery jquery-ui --save-dev

npm install bootstrap --save-dev
npm install grunt --save-dev
npm install grunt-phpunit --save-dev
npm install grunt-phpcs --save-dev
npm install time-grunt --save-dev
npm install grunt-browserify --save-dev
npm install grunt-contrib-watch --save-dev
npm install grunt-contrib-uglify --save-dev
npm install grunt-notify --save-dev

直下にpackage.jsonが作られるので編集して

vim package.json 

以下を追加。


package.json

  "browser": {

"jquery": "./node_modules/jquery/dist/jquery.js",
"bootstrap": "./node_modules/bootstrap/dist/js/bootstrap.js",
"jquery-ui": "./node_modules/jquery-ui/jquery-ui.js"
}


gruntの設定

自分で直下にGruntfile.jsを作成する。

vim Gruntfile.js


Gruntfile.js

module.exports = function(grunt) {

require('time-grunt')(grunt);

grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
phpunit: {
classes: {
dir: 'tests/TestCase/'
},
options: {
bin: 'vendor/bin/phpunit',
bootstrap: 'tests/bootstrap.php',
colors: true
}
},
phpcs: {
application: {
src: ['src/**/*.php']
},
options: {
bin: 'vendor/bin/phpcs',
standard: 'CakePHP'
}
},
browserify : {
dist : {
src : 'src/Scripts/main.js',
dest : 'webroot/js/build.js'
}
},
uglify: {
my_target: {
options: {
sourceMap: true
},
files: {
'webroot/js/build.min.js': ['webroot/js/build.js']
}
}
},
watch: {
phpcs: {
files: ['src/**/*.php'],
tasks: ['phpcs'],
options: {
spawn: false,
},
},
browserify: {
files: ['src/Scripts/**/*.js'],
tasks: ['browserify', 'notify:browserify'],
options: {
spawn: false,
},
},
uglify: {
files: ['src/Scripts/**/*.js'],
tasks: ['uglify', 'notify:uglify'],
options: {
spawn: false,
},
},
},
notify: {
browserify: {
options: {
message: 'Browserify finished running',
}
},
uglify: {
options: {
message: 'Uglify finished running'
}
}
}
});

grunt.loadNpmTasks('grunt-notify');
grunt.loadNpmTasks('grunt-phpunit');
grunt.loadNpmTasks('grunt-phpcs');
grunt.loadNpmTasks('grunt-browserify');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-uglify');

grunt.registerTask('default', ['watch']);
grunt.registerTask('js', ['browserify', 'uglify']);
};


notify とかは完全に好みなので通知なんていらないって人は入れる必要ない。何をwatchするのかもその人の自由。

ちょっとここで動くかどうか確認。

grunt phpunit


jsの雛形

src/Scripts というフォルダーを作って、その中にメインのjsを作成する。

jqueryとbootstrapとdatepickerを読み込む。

mkdir src/Scripts

vim src/Scripts/main.js


src/Scripts/main.js

(function(){

'use strict';

window.jQuery = window.$ = require('jquery');
require('bootstrap');
require('jquery-ui/datepicker');

var message = 'Hello App';
console.log(message);

$(".datepicker").datepicker({
dateFormat: "yy-mm-dd"
});

$("#myModal").on('shown.bs.modal', function (e) {
$(".datepicker").datepicker({
dateFormat: "yy-mm-dd"
});
})

$("#myModal").on('hidden.bs.modal', function (e) {
$(this).removeData();
});

})();


grunt が動くかどうか確認する。

grunt js

webroot/js/build.min.js が出来ているはずなので、レイアウトから読み込むようにしておく。

とりあえず /body の直上。


src/Template/Layout/default.ctp

    <?= $this->Html->script('build.min') ?>

</body>
</html>

ついでにjquery-uiのcssを webroot/css 以下にコピーしておく。

cssは node_modules/jquery-ui/themes 以下にテーマ別においてあるので好きな物を選んでコピー。

レイアウトに以下追加。


src/Template/Layout/default.ctp

    <?= $this->Html->css('jquery-ui.min.css') ?>



コミット、他のメンバーの設定

.gitignoreに追加する。


.gitignore

/node_modules/*

/webroot/files/*

add して commit して pushする。

git add .

git commit -m "Improve project file"
git push origin master

他の開発メンバーは、

git clone https://github.com/xxxxxxxxx/myApp.git

cd myApp/
sudo composer install
sudo
vendor/bin/phpcs --config-set installed_paths vendor/cakephp/cakephp-codesniffer
npm install

データベースの設定も忘れずに。

vim config/app.php


bakeで画面作り

見る画面がないのも寂しいのでbakeで作っておく。

bin/cake bake controller Articles

ビューも

bin/cake bake template Articles

一応確認。

bin/cake server

http://localhost:8765/articles


heroku設定

で、herokuの準備。

直下にProcfileを作る。


Procfile

web: vendor/bin/heroku-php-apache2 webroot/


bootstrap.phpを編集。76行目あたりに以下を追加。


config/bootstrap.php

if (isset($_ENV['CAKE_ENV'])) {

Configure::load('app_' . $_ENV['CAKE_ENV'], 'default');
}

さらに、DebugKitのところを編集


config/bootstrap.php

if (Configure::read('debug') && !isset($_ENV['CAKE_ENV'])) {

Plugin::load('DebugKit', ['bootstrap' => true]);
}

app_heroku.phpを作成する。


config/app_heroku.php

<?php

$db = parse_url(env('CLEARDB_DATABASE_URL'));
return [
'debug' => false,
'Security' => [
'salt' => env('SALT'),
],
'Datasources' => [
'default' => [
'className' => 'Cake\Database\Connection',
'driver' => 'Cake\Database\Driver\Mysql',
'persistent' => false,
'host' => $db['host'],
'username' => $db['user'],
'password' => $db['pass'],
'database' => substr($db['path'], 1),
'encoding' => 'utf8',
//'timezone' => 'UTC',
'cacheMetadata' => true,
'quoteIdentifiers' => false,
],
],
'EmailTransport' => [
'default' => [
'className' => 'Smtp',
// The following keys are used in SMTP transports
'host' => 'ssl://' . $smtp['host'],
'port' => $smtp['port'],
'timeout' => 30,
'username' => $smtp['user'],
'password' => $smtp['pass'],
'client' => null,
'tls' => null,
],
],
'Log' => [
'debug' => [
'className' => 'Cake\Log\Engine\ConsoleLog',
'stream' => 'php://stdout',
'levels' => ['notice', 'info', 'debug'],
],
'error' => [
'className' => 'Cake\Log\Engine\ConsoleLog',
'stream' => 'php://stderr',
'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
],
],
];


heroku操作

で、heroku で作業。

heroku login

メールアドレスとパスワードを入れると、

Authentication successful.

になるはず。

引き続き、

heroku create

で、新しくアプリが作られる。

さらに、CakePHP用の環境変数設定。

heroku config:add CAKE_ENV="heroku"

SALT設定

heroku config:add SALT="123456789123456"

SMTPサーバーの設定

heroku config:add SMTP_SERVER="smtp://username:password@smtp.mail.hoge.jp:465"

ビルドパックをphpに設定。(これをやらないとnodejsになってしまう)

heroku buildpacks:set https://github.com/heroku/heroku-buildpack-php

herokuにプッシュ。

git push heroku master

データベースを使う。

heroku addons:create cleardb

シェルを起動して、

heroku run bash

マイグレ

bin/cake migrations migrate

マイグレを使わない場合は、

heroku config

と打つと、環境変数と共に、以下のような、

CLEARDB_DATABASE_URL:    mysql://b1234567890abc:12345678@aa-bbbb-cccc-dddd-01.cleardb.net/heroku_b123456789abcde?reconnect=true

接続情報が出るので、これを分解してmysqlで接続する。いや、別にそのまんまです。

mysql -u b1234567890abc -p -h aa-bbbb-cccc-dddd-01.cleardb.net heroku_b123456789abcde

接続できたら、普通に、create database して、create table して、とやる。

DBは以上。

ブラウザ起動!

heroku open

これで動くはず。

http://xxxxxxxxxx.herokuapp.com/articles/


BASIC認証

とりあえずBASIC認証かけといてって必ず言われるので、以下をAppController.phpに仕込む。


src/Controller/AppController.php

use Cake\Event\Event;

class AppController extends Controller
{
public function beforeFilter(Event $event)
{
parent::beforeFilter($event);
$this->autoRender = false;
$loginId = 'admin';
$password = 'pass';

if (isset($_SERVER['PHP_AUTH_USER'])) {
if (! ($_SERVER['PHP_AUTH_USER'] === $loginId && $_SERVER['PHP_AUTH_PW'] === $password)) {
$this->_basicUnauthorized();
}
} else {
$this->_basicUnauthorized();
}
$this->autoRender = true;
}

protected function _basicUnauthorized()
{
header('WWW-Authenticate: Basic realm="Please enter your ID and password"');
header('HTTP/1.0 401 Unauthorized');
die("Authorization Required");
}
}



bootstrap設定

とりあえずプラグインでテーマ設定する。

bin/cake bake plugin bootstrap

(ここでbootstrapというのは自分で決めたプラグインの名前である)

フォルダー plugins/Bootstrap/src が作成されるので、そこにさらに自分でフォルダーを作る。

mkdir -p plugins/Bootstrap/src/Template/Bake/Template/

mkdir -p plugins/Bootstrap/src/Template/Bake/Element/

オリジナルのbake用のテンプレをコピーする。

cp vendor/cakephp/bake/src/Template/Bake/Template/* plugins/Bootstrap/src/Template/Bake/Template/

cp vendor/cakephp/bake/src/Template/Bake/Element/form.ctp plugins/Bootstrap/src/Template/Bake/Element/form.ctp

メニュー部分の高さ用のcssを作ってwebroot/css以下に保存する。


webroot/css/menu.css

body {

padding-top: 50px;
padding-bottom: 20px;
}

bootstrap.min.css もコピーしておく。

cp node_modules/bootstrap/dist/css/bootstrap.min.css webroot/css/bootstrap.min.css

レイアウトに追加


default.ctp

    <?= $this->Html->css('bootstrap.min.css') ?>

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


formhelper の生成するタグを変える。

src/View/AppView.php の initialize のところに以下を追加する。


src/View/AppView.php

    public function initialize()

{
$this->loadHelper('Form', [
'templates' => 'app_form',
]);
}

で、config 直下に自分で app_form.php というファイルを作る。


/config/app_form.php

<?php

return [
'inputContainer' => '<div class="form-control">{{content}}</div>',
'label' => '<label{{attrs}} class="control-label">{{text}}</label>',
'input' => '<input type="{{type}}" name="{{name}}"{{attrs}} class="form-control">'
];

自分がいじりたい部分だけ、配列に書いて、修正を入れる。

オリジナルは

vendor/cakephp/cakephp/src/View/Helper/FormHelper.php

にだーっと書いてある。

あとはbake する時に、

bin/cake bake template Users -t Bootstrap

とする。


最新版にする

sudo composer update

npm update


テストを書く

とりあえずコントローラの方のbakeで自動生成されているテストに何も考えずに書く。


tests/TestCase/Controller/ArticlesControllerTest.php

use Cake\ORM\TableRegistry;



tests/TestCase/Controller/ArticlesControllerTest.php

    public function testIndex()

{
$this->get('/articles?page=1');

$this->assertResponseOk();
}



tests/TestCase/Controller/ArticlesControllerTest.php

    public function testView()

{
$this->get('/articles/view/1');

$this->assertResponseOk();
}



tests/TestCase/Controller/ArticlesControllerTest.php

    public function testAdd()

{
$data = [
'name' => '記事',
'age' => 19,
'posted_date' => '2015-08-12'
];
$this->post('/articles/add', $data);

$this->assertResponseSuccess();

$articles = TableRegistry::get('Articles');
$query = $articles->find()->where(['name' => $data['name']]);
$this->assertEquals(1, $query->count());
}



tests/TestCase/Controller/ArticlesControllerTest.php

    public function testEdit()

{
$this->get('/articles/edit/1');

$this->assertResponseOk();
}


削除はステータス302で移動するのでassertResponseSuccessにしている。


tests/TestCase/Controller/ArticlesControllerTest.php

    public function testDelete()

{
$this->post('/articles/delete/1');

$this->assertResponseSuccess();
}


ちなみにBASIC認証をかけてしまった場合は、以下を追加


tests/TestCase/Controller/ArticlesControllerTest.php

    public function setUp()

{
parent::setUp();

$_SERVER['PHP_AUTH_USER'] = 'admin';
$_SERVER['PHP_AUTH_PW'] = 'pass';
}



file upload

jquery-file-upload を使うことにする。composer にあるので、composer.json に追記。

{

"require": {
"blueimp/jquery-file-upload": "*"
},
"autoload": {
"psr-4": {
"UploadHandler\\": "./vendor/blueimp/jquery-file-upload/server/php"
},
"classmap": ["./vendor/blueimp/jquery-file-upload/server/php/UploadHandler.php"]
},
}

composer をアップデートかける。

sudo composer update

新規にUploadsController.php を作る。


src/Controller/UploadsController.php

<?php

namespace App\Controller;

use App\Controller\AppController;
use UploadHandler;

/**
* Uploads Controller
*
*/

class UploadsController extends AppController
{
/**
* initialize method
*
* @return void
*/

public function initialize()
{
parent::initialize();
$this->loadComponent('RequestHandler');
}

/**
* index method
*
* @return void
*/

public function index()
{
$this->autoRender = false;
$uploadHandler = new UploadHandler();
}
}


あとはビューにファイル参照ボタンを追加して、


src/Template/Articles/add.ctp

    <style type="text/css">

.bar {
height: 18px;
background: green;
}
</style>

<?= $this->Form->create($article, ['type' => 'file']) ?>
<input id="fileupload" type="file" name="files[]" data-url="/uploads/index" multiple>
<div id="progress">
<div class="bar" style="width: 0%;"></div>
</div>
<div id="files"></div>


JavaScriptも適当に追加


main.js

    $('#fileupload').fileupload({

dataType: 'json',
done: function (e, data) {
$.each(data.result.files, function (index, file) {
$('<p/>').text(file.name).appendTo('#files');
});
},
progressall: function (e, data) {
var progress = parseInt(data.loaded / data.total * 100, 10);
$('#progress .bar').css(
'width',
progress + '%'
);
}
}).prop('disabled', !$.support.fileInput)
.parent().addClass($.support.fileInput ? undefined : 'disabled');


webrootの下にアップロードするファイル用のフォルダーを作っておく。

mkdir webroot/files

とりあえず動いたってところまで。


JSON出力

routes.php に以下を追記。


config/routes.php

Router::extensions(['json', 'xml']);


これで、単に今までのコントローラのURLに .json を付けるだけで結果がJSONになる。

やってみてびびったのだが、http://book.cakephp.org/3.0/en/views/json-and-xml-views.html#creating-json-views にあるように、

    public function initialize()

{
parent::initialize();
$this->loadComponent('RequestHandler');
}

を追加する必要はない。

bakeで作った素のコントローラ、


src/Controller/ArticlesController.php

    public function view($id = null)

{
$article = $this->Articles->get($id, [
'contain' => []
]);
$this->set('article', $article);
$this->set('_serialize', ['article']);
}

これそのまんまでいいのだ。呼ぶ時に .json をつければJSONになり、つけなければビューが呼ばれる。

まあ、なんかちょっと気味が悪いが、これでよしとしてテストを追加する。


tests/TestCase/Controller/ArticlesControllerTest.php

    public function testJson()

{
$this->configRequest([
'headers' => ['Accept' => 'application/json']
]);
$result = $this->get('/articles/view/1.json');

$this->assertResponseOk();

$expected = [
'article' =>
['id' => 1, 'name' => '山田太郎', 'age' => '18', 'posted_date' => '2015-10-15', ・・・],
];
$expected = json_encode($expected, JSON_PRETTY_PRINT);
$this->assertEquals($expected, $this->_response->body());
}


expectedにはfixtureの値を入れておく。