はじめに
『PHPフレームワーク CakePHP3入門』を読んだので、学んだことのメモなどです。
私の状態(こんな人におすすめ)
- 仕事でCakePHPのシステムを触り始めた
- CakePHP特有のファイル同士の紐付きを調べたが、知識が定着していない・断片的
- CakePHPやPHPの構文を書き慣れていない
この本をこれからやる人への伝言
- Mac (Appleシリコン) の場合、紹介されているXAMPPがうまく使えなかませんでした。(intl拡張を認識させられず、ずっとエラーになった。)
- 代わりにMAMPを使ったところ、その後の工程も問題なくできました。
https://www.mamp.info/en/mac/
- 代わりにMAMPを使ったところ、その後の工程も問題なくできました。
- エラーが出たら、次のページに解決策が書いてあることがあります
- 何度か一生懸命エラーを確認して、次のページに「エラーがでたら」の解決法が書いてあってしょんぼりしました...。まず次のページに解決法が書いてないか見た方が良いです。
- 環境の違いか、書いてあるままやってもうまくいかないことがありました。
- AIに聞きながら進めると、時間を浪費せずによかったです。
学んだことメモ
MVCアーキテクチャ
CakePHPは基本的に決められたディレクトリにあらかじめ指定された命名規則に従ってファイルを用意します。命名規則が重要です。
Controller(コントローラー)
基本概念
- 指定されたアドレスにアクセスされた際に実行する処理を制御
- アクセスされたアドレスにより自動的にControllerのメソッドが呼び出される(アクション)
- AppControllerを継承する
重要なメソッド
- indexメソッド: デフォルトで呼ばれるメソッド
- initialize: クラス全体に適用される設定。クラスのインスタンスが作成される際に自動的に呼び出される
データの受け渡し
// コントローラーからビューにデータを渡す
$this->set('footer', 'Hello/footer'.$n);
リクエストデータの取得
GETパラメータ(どちらでも可):
$str = $this->request->query['text1'];
$str = $this->request['url']['text1'];
POSTパラメータ(どちらでも可):
$str = $this->request->data('text1');
$str = $this->request->data['text1'];
テーブルクラスの使用
- 同じ名前のテーブルクラスはデフォルトで使える
- 例:BoardsControllerで
$this->Boards
- 例:BoardsControllerで
- 他のテーブルを使いたい場合:
$this->people = TableRegistry::get('People');
View(ビュー)
基本概念
-
Template:
.ctp
ファイル(PHPの処理も書ける)。ページごとに準備する。 - レイアウト: テンプレート、ヘッダーやフッターなどをまとめてページ全体をレイアウト。
- View: PHPのクラスとして処理を記述。基本的には個別で書かなくてよさそう。Helperもここに配置する。
レイアウト設定
// デフォルトレイアウトを使用しない場合
$this->viewBuilder()->autoLayout(false);
// カスタムレイアウトを指定
$this->viewBuilder()->layout('Hello'); // hello.ctp が呼び出される
エレメント
レイアウト内で使えるパーツ。第二引数にキー名・値で変数を渡せる:
<?=$this->element('header',['msg'=>$msg])?>
Model(モデル)
基本概念
-
Entity: テーブルの1行(レコード)を表現するクラス
- 値の整形や、1ユーザー単位のロジックを書く場所
- Entityクラスを継承する
- データベースから取り出したデータは全てエンティティクラスのインスタンスとして取り出される
- 定義しなくても、カラムと同名のプロパティが用意される
-
Table: テーブル全体を操作するクラス
- 検索、保存、リレーション、バリデーションを書く場所
- Tableクラスを継承する
データ取得
// findしたデータは、toArray()で取り出せる
$data->toArray();
// 集計関数も使える
$data->count();
$data->min('id');
// LIKE検索
'conditions'=>['name like'=>"%{$this->request->data['name']}%"]
// ORDER BY
$data->order(['name'=>'ASC','id'=>'DESC']);
データ操作
// 削除
$entity = $this->Boards->get($this->request->data['id']);
$this->Boards->delete($entity);
// 保存(登録も更新も)
// idがあれば更新、なければ追加
$this->Boards->save($entity);
// エンティティの更新
$this->Boards->patchEntity($entity, $this->request->data);
プライマリーキーの設定
// デフォルトは'id'カラム
// 変更したい場合はテーブルクラスのinitializeで設定
$this->primaryKey('name');
ライフサイクルメソッド
CRUDの前後で呼び出されるメソッド:
-
beforeFind
,beforeSave
,afterSave
など -
beforeSave
でfalseを返すと、saveをキャンセルする
他の場所からテーブルクラスを取得
※セットになっているControllerからは、これを書かなくても使える
$table = TableRegistry::get($tablename);
命名規則
基本ルール
- ファイル名もクラス名もキャメル記法が基本
- モデル関係は単数系、それ以外のDBデータを扱う名前は複数形にする
- クラスとして定義されるものはキャメル記法、クラスとは直接関係ないものはアンダースコア記法
データベース・モデル関係
- データベースのテーブル: 複数形
- モデル: 単数系のキャメル記法
- テーブルクラス: 複数名+Tableのキャメル記法
- エンティティクラス: 単数系のキャメル記法
- スクリプトファイル名: クラス名そのまま、単数系のキャメル記法
コントローラー
- Controller: 複数形+Controllerのキャメル記法
- スクリプトファイル名: コントローラクラス名をそのまま
- アクションメソッド名: 小文字で始まるキャメル記法
ビュー
- ビューのフォルダ名: コントローラクラスそのまま
- ビューテンプレートファイル名: アクションメソッドをアンダースコア記法にしたもの
URLとの関係
基本的なURLマッピング
-
Controller名の小文字: indexメソッド
http://localhost:8765/hello
-
Controller名/アクション名: そのメソッド(アクションメソッド)
http://localhost:8765/hello/other
ページ遷移
// フォワード(サーバー側で移動、ブラウザのアドレスバーは移動前のまま)
$this->setAction(アクション名);
// リダイレクト(ブラウザに対して別ページに移動するように要求)
$this->redirect(移動先アクセス);
ビューの自動読み込み
-
/hello
にアクセスすると -
Template/Hello
フォルダの中にある、アクション名のテンプレートファイルを読み込んで自動的にレンダリングする
フォーム処理
フォームの基本
- チェックボックスやラジオボタンがオフの場合は値は送られない
- 同じnameで、チェックボックスやラジオボタンよりも上にhidden項目を置いておくと、選択されなかったらそちらの値が送られる
<input type="hidden" name="check1" value="off" />
FormHelper
// フォーム作成
$this->Form->create();
// 送信ボタン
$this->Form->submit('送信');
// ラジオボタン
$this->Form->radio("HelloForm.radio1",[
['text'=>'えー', 'value'=>'a'],
['text'=>'びー', 'value'=>'b']
]);
// セレクトリスト
$this->Form->control('person_id', ['options' => $people]);
リクエストデータ
-
$this->request->data
は、フォームの各項目が連想配列にまとめられている - Form->createのURLを指定しないと、現在のURLにリクエストが再送される
HTMLヘルパー
基本概念
-
Form関係:
$this->Form
-
それ以外:
$this->Html
- 例:
$this->Html->link(タイトル, URL, 属性設定);
- 例:
データベース操作
bakeコマンド
テーブルからファイルの自動生成
php bin/cake.php bake 対象 名前
# 例:php bin/cake.php bake model boards
# allをすると、TestのFixtureなども生成される
便利なメソッド
// compact関数
$this->set(compact('books'));
// 以下と同じ
$this->set('books', $books->toArray());
QueryBuilder
$data = $this->Boards->find()
->where(['name'=>$input])
->order(['id'=>'DESC'])
->select(['name','title']);
ConnectionManager
- 直接SQLを実行することができる(findとか使わずに直でselect文を記載)
- beginやcommitもできる(beginやcommitしないとauto commitになる)
バリデーション
基本概念
- エンティティ(テーブルクラス)にバリデーションの情報を設置すると、save時に自動チェックされる
- validationエラーになったら、saveの戻り値はfalseになる
- validationDefaultメソッドを呼び出して、Validatorインスタンスを取得する
フォームとの連携
- Formヘルパーのinputメソッドを使うと、エラーメッセージの表示機能などを自動的に組み込む
- 表示位置などを調整したい場合は、入力項目でinputを使わずにtextなどを使って、
<?=$this->Form->error("項目名") ?>
よく使うバリデーション
-
notEmpty
,maxLength
,date(項目名,フォーマット)
,inList(項目名,配列)
- 第三引数にメッセージを指定する
-
notEmpty
はメッセージを変更できない(ブラウザのデフォルトエラーメッセージになる)
ValidationDefaultとbuildRules
- ValidationDefault: newEntityの時に呼び出される。入力値のチェック
- buildRules: saveの時に呼び出される。状態に関するルール(登録済みなど)
// buildRulesの例
$rules->add($rules->isUnique(['name'],'すでに登録済みです'));
独自ルールの追加
// カスタムルール
$validator->add('content','custom',[
'rule'=>['custom',"/\A\d+\z/"],
'message'=>'整数を入力してください'
]);
// メソッド追加
$validator->add('name','maxRecords',[
'rule'=>['maxRecords','name',5],
'message'=>__('最大値を超えています。'),
'provider'=>'table',
]);
requirePresence
そのフィールドがリクエストデータに存在することを必須にする
テーブルの結合(アソシエーション)
基本設定
テーブルクラスのinitializeで紐付けをする:
public function initialize(array $config)
{
$this->hasOne('Boards'); // 1対1
$this->hasMany('Boards'); // 1対多
$this->belongsTo('People'); // 多対1
}
結合の種類
- hasOne: 1対1の場合
-
hasMany: 1対多の場合 → 2回SQLが発行される
- 結合した要素は配列で表現される(1個でも)
- belongsTo: 多対1の場合 → left joinが実行される
使用場面
-
hasOne, hasMany: 自分が元になるテーブル側の時に使う
- peopleテーブルがメイン、Boardsテーブル側にperson_idを持つ
- peopleテーブルに
$this->hasOne('Boards');
- belongsTo: 逆の場合(people側にはboard_idを持っていない)にbelongsToが使える
動的な結合設定
呼び出す箇所によって結合条件を変えたい場合:
メソッド内で関係を定義する
public function index($id=null){
$this->People->hasMany('Boards');
$data = $this->People
->find('all')
->contain(['Boards']);
$this->set('data',$data);
}
結合条件
デフォルトの決まり方
-
関連名から外部キー名を作る
-
hasOne('Boards')
→ モデル名がPeople
→ 外部キーはperson_id
-
hasOne('Articles')
→ モデル名がUsers
→ 外部キーはuser_id
-
-
結合条件
-
Boards.person_id
がPeople.id
に紐付く
-
テスト
ユニットテスト
- bakeで作成したものはテストも自動生成される
- 生成時は
markTestIncomplete
が記載されていて、これが残ってると、実行時にincompleteと表示される - 実行:
vendor/bin/phpunit
Fixture
-
テストの際にダミーとして作成されるレコード情報のファイル
-
テスト用DBにinsertされる
-
テストごとに初期化される(デフォルト)
-
作成:
php bin/cake.php bake fixture People
-
$fields
: カラム定義を連想配列 -
$records
: サンプルとして利用するレコードの情報
TestCase
-
testInitialization: テスト実行時の初期化
- setUp: 開始準備
- tearDown: 終了後処理
- $fixtures: Fixturesのスクリプトをロード
-
メソッド呼び出し:
$this->get(アクセス先メソッド);
,$this->post(アクセス先メソッド,送信データ);
拡張機能
それぞれ、デフォルトのものも自分で作ることもできる
ヘルパー
ビュー(テンプレート)に追加されるプログラム
// AppView.phpで読み込み
$this->loadHelper('Text');
// 使用例
$this->Text->autoLink;
// 時刻処理
$this->Time->fromString(日時);
$this->Time->format($t,'yyyy年MM月dd日 HH時mm分ss秒');
// ヘルパーからヘルパーを呼ぶ
use Cake\View\Helper;
public $helpers = ['Html'];
エレメント
ビューに追加されるプログラム
- ビューテンプレートの一部を別ファイルに記述する感覚
- Template/Elementに配置
<?php echo $this->element('SampleBanner'); ?>
コンポーネント
コントローラーに追加されるプログラム
- ヘルパーとセットになることもある
- Controllerのinitializeで
$this->loadComponent('DataArray');
// Componentなどで呼び出し元のControllerを使いたい場合
$this->controller = $this->_registry->getController();
ビヘイビア
モデル関係(テーブルクラス)に追加されるプログラム
- テーブルのinitializeに追加
$this->addBehavior('ビヘイビア名');
Treeビヘイビア
- データをツリー構造にしてくれる
- 例えば、next_boardsテーブルにparent_idカラムがある場合、bakeはツリー構造をサポートするために
$this->addBehavior('Tree');
を追加します。
プラグイン
MVCアプリケーションとして機能するプログラムを外部から組み込んで利用できるようにする
// プラグインのコンポーネントを呼び出す
$this->loadComponent('プラグイン名.コンポーネント名');
// pluginsの中に配置
シェル
- Shellフォルダに入れておくと、コマンド実行できる
-
php bin/cake.php プラグイン名
で呼び出すと、main関数が実行される
読んでよかったこと
- 略されたり複数形になったり単数になったりのCakePHPのファイル間の法則が理解できた。
- 過去に調べたことはあったが、自分で何度か書くことによってだいぶ定着した気がする。
- ここが既存のコードを読むときに1番困ることだったので、よかった。
- PHPやCakePHPの構文を少し書き慣れた。
- 仕事ではずっと書いているわけではないので、なかなか書きなれなかった。今の時代、もっと効率の良い学習方法もあるのかもしれないが、やはりまとまった時間書くことは重要だとわかった。
その他
- 自分でチュートリアルをやりながら、マークダウン形式でメモをとり、最後にそれをCursorに整形してもらった。雑にメモをとっていたが、きれいに章立てや見出しを整えてもらい、読みやすくなった。