はじめに
自分用のメモのため、読んでもよくわからないことが多いと思いますが、参考がてらにどうぞ。随時更新します。
参照した画像がない場合、非同期でno-image.jpg
を表示する方法
自分の作成したHelperクラスで画像を取得する際、以下のようにHtmlHelperを利用して<img />
タグを生成できる。
public function getProductImage()
{
$options = [
'class' => 'css-class-name',
'alt' => '',
];
$imagePath = "product-image.jpg";
return $this->Html->image($imagePath, $options);
}
この時、もちろん$imagePath
に画像がなければエラーになり何の画像も表示されない。そこで、以下のようにfile_exists
を使用してファイルの存在チェックができる。
public function getProductImage()
{
$options = [
'class' => 'css-class-name',
'alt' => '',
];
$imagePath = "product-image.jpg";
#ファイルの存在チェック
if (file_exists($this->Url->image($imagePath))) {
return $this->Html->image($imagePath, $options);
}
return $this->Html->image('no-image.jpg', $options);
}
これでも動くが、わざわざサーバーサイドでこの処理を行う必要はない。<img />
タグで使用できる、onerror
オプションを使えば非同期にクライエントサイドで同じ処理ができる。よって最適なコードは以下。
public function getProductImage()
{
$options = [
'class' => 'css-class-name',
'alt' => '',
#onerrorオプションを導入
'onerror' => 'this.onerror=null;this.src="' . $this->Url->image('no-image.jpg') . '"'
];
$imagePath = "product-image.jpg";
return $this->Html->image($imagePath, $options);
}
DBから引っ張ってきた値をFormのSelect Optionに設定
find('list')
を使用する。ディフォルトでは[id]=>[name]のペアが返されるが、keyField
,valueField
を定義することで、name
以外の項目をDBから取り出すことができる。
Goodコード:
$results = TableRegistry::getTableLocator()->get('Users')->find('list', [
//key-valueを定義
'keyField' => 'id',
'valueField' => 'email'
])->where(['role =' => 'admin'])->toArray();
Badコード:
$results = TableRegistry::getTableLocator()->get('Users')->find('list', [
//fieldsはCakePHP3では使われない
'fields' => ['Users.id', 'Users.email']
])->where(['role =' => 'admin'])->toArray();
単一レコードの取得
Cake\ORM\Table::find()を使用することで、SQLと同じようなロジックでDBからデータを取得できる。
# find()を使った単一レコードの取得
$inquiry = $this->Users->find('all')
->where(['Users.id' => $id]) |
->firstOrFail();
が、単一レコードを取得する際はCake\ORM\Table::get()
を使用するべき。
# get()を使った単一レコードの取得
$product = $this->Products->get($id)
CustomFinderの使用
Finderは裏でSQLを生成しているわけだが、共通して使用されるクエリ部分はCustomFinderを作ることで使いまわせる。
公式にある通り、Tableレイヤーで定義する。
App.phpの設定値を取得
loadModel()とTableRegistry::getTableLocator()->get()の使い分け
あるMVCのモジュールで、他のモデルから値を引っ張りたい場合、loadModel()
かTableRegistry::getTableLocator()->get()
を使用できます。
この時、以下のようにして使い分けるといいです(動きとしては同じですが、そう推奨されているみたい)。
- Controller内であれば
loadModel()
使用タイミング:
UserController
の中でBooksTable
から値を取得したいときなど。
サンプル:
// Load Logs model
$logsTable = $this->loadModel('Logs');
- Model内であれば
TableRegistry::getTableLocator()->get()
使用タイミング:
例えばModel/Entity/User.php
でLogsTable
の値を取り出したい時など
サンプル:
// Load Logs model
$logsTable = TableRegistry::getTableLocator()->get('Logs');
参考:https://blog.sohelrana.me/loading-model-inside-another-model-and-controller-in-cakephp-3/
Debug
以下のいずれかを使用。個人的にはdd()
かprint_r()
を使う。色々ルールや使い方の違いがあると思うが、データをみる目的であればなんでもいいのでは、と思う。
dd('Testing');
dump('Testing');
print_r('Testing');
debug('Testing');
Pluginがインストールされてるかのチェック
# show all loaded plugins
bin/cake plugin loaded
# check for specific plugin (nix)
bin/cake plugin loaded | grep MyPlugin
PluginとPackagistについて
composer require [package name]
により、最新バージョンのPluginパッケージをインストールできる。
ただし、以下のような注意が必要。
- ここでインストールできるパッケージはPackagistにあるもののみである。
- 最新バージョンで不具合がある場合があるのでできることならバージョンを指定した方がいい。
バージョンとは?どうやって指定するのか?
更新中。
オブジェクトのインスタンスが何かをIf文に含める
PHPには予めgetType($data)
というメゾットがあり、変数のデータ型を取得できるが、Objectの場合、これだけでは何のインスタンスかわからない。
なので、
var_dump($data);
でデータの型、インスタンスを調べた後、$this instanceof [Instance Name]
をIf文に含める。
例:以下では、var_dump($data);
で印字された内容を見るとErrorControllerのインスタンスであることがわかる。
if (!$this instanceof ErrorController) {
//ErrorControllerのインスタンスでない場合、実行
}
Cell, Element
src/Template/Element/{element}.php
で定義されたHTML情報は、Templateで<?= $this->element('sidemenu'); ?>
と記述すれば持ってくることができる。
Cellは、ElementにController機能を追加し、よりダイナミックかつ複雑な動きをできるようにしたもの。src/View/Cell/{cellNameCell}.php
で定義できる。Templateで<?= $this->cell('cellName::methodName', ['parameters']) ?>
でCellに定義したメゾットとそれに連携するTemplateを呼び出すことができる。
使用例:
新着メッセージ数を表示→Cellを使用し、メッセージ数をカウント
共通のサイドメニューを表示→Elementを使用
同一テーブルを複数箇所Associateする
例えば、以下のような出荷テーブル(shippings)、県テーブル(prefectures)というテーブルがあったとします。
shippings
Column | Associated table.column |
---|---|
id | - |
name | - |
sender_pref_id | prefectures.id |
sender_address | - |
receiver_pref_id | prefectures.id |
receiver_address | - |
quantity | - |
shipping date | - |
prefectures
Column | Associated table.column |
---|---|
id | shippings.sender_pref_id, shippings.receiver_pref_id |
name | - |
ここで、shippings
はsender_pref_id、receiver_pref_idを介してprefectures
とAssociateの関係にあるのに注目してください。この時、ShippingsTableは以下のようになります。
class ShippingsTable extends AppTable
{
/**
* Initialize method
*
* @param array $config
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
$this->setTable('shippings');
$this->setDisplayField('name');
$this->setPrimaryKey('id');
//ここで、Prefecturesに属していることを示します。
$this->belongsTo('Prefectures', [
'foreignKey' => 'sender_pref_id',
'joinType' => 'INNER',
]);
$this->belongsTo('ReceiverPrefs' //こうすることで$this->delivery_note_pref->nameのようにしてEntitityから県名を取得できます。, [
'className' => 'Prefectures' //Associationの名前が実際のテーブル名と一致していないのでClassNameを明示します。
'foreignKey' => 'receiver_pref_id',
'joinType' => 'INNER',
]);
ここで注意してほしいのは、Controller等でAssociationの情報を取得する際、contain
で使用するAssociationを指定してください。
$shipping = $this->Shippings->get($id, [
'contain' => ['Prefectures', 'ReceiverPrefs']]);
クエスチョンマーク
??
PHP7から追加されたTernary Operatorでは表現できなかった便利な演算子。
// Fetches the value of $_GET['user'] and returns 'nobody'
// if it does not exist.
$username = $_GET['user'] ?? 'nobody';
// This is equivalent to:
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';
Reference: https://www.php.net/manual/en/migration70.new-features.php#migration70.new-features.null-coalesce-op
Nullable types
パラメータや返却値の型宣言の前に?をつけることで、Nullでも許容できるようになります。
function testReturn(): ?string #これで返却値はNullかString型のみ許容されるようになる
{
return null;
}
var_dump(testReturn());
Reference: https://www.php.net/manual/en/migration71.new-features.php
Marshallingとは、beforeMarshalの使い方
CakePHPの公式サイトでは、beforeMarshalの機能について以下のように説明しています。
If you need to modify request data before it is converted into entities, you can use the Model.beforeMarshal event. This event lets you manipulate the request data just before entities are created:
でも一体Marshalって何でしょう?
StackOverflow
に良い説明がありました。
Marshalling is the process of transforming the memory representation of an object to a data format that could be stored or transmitted. It's also called serialization (although it could be different in certain contexts).
この説明から、beforeMarshalはデータを挿入する手前でEntityへ変換される際、データフォーマットを変えたい場合などに使えそうですね。
例えば、フロント側のフォームでYYYY/MM/DD HH:MM
を入力する項目がある場合でテーブル挿入前にこのbeforeMarshalを使ってss
部分を補ったりできます。
Reference: https://stackoverflow.com/questions/154185/what-is-object-marshalling
Arrayから値をHashで取得する
Arrayから値を取得する際、CakePHPではHashを使用するように推奨されています。
Arrayの使い勝手をよくするこのHashはCakePHPの強みであり、統一して使われるべき。
// Define $users
$users = [
['id' => 1, 'name' => 'mark'],
['id' => 2, 'name' => 'jane'],
['id' => 3, 'name' => 'sally'],
['id' => 4, 'name' => 'jose'],
];
//HashでSallyを取得する
$results = Hash::get($users, 2.name)
//Hashを使わなずにSallyを取得する
$results = $users[2][name]
Reference: https://blog.howtelevision.co.jp/entry/2014/07/04/160309
ドロップダウンのオプション項目をコントローラーからセットする
以下のようなtemplates/User/edit.php
があるとする。
<div class="sample-class">
<?= $this->Form->control('user', [
'type' => 'select',
'class' => 'option-class',
//optionsを設定
'options' => $users,
'label' => false,
'empty' => __('指定なし'),
'default' => __('指定なし')
]) ?>
</div>
ルーティングを何もいじっていなければ、当然以下のようにしてUsersController
のedit()
メゾットからこのテンプレートが呼び出される。
public function edit($id = null)
{
$users = $this->User->find('list');
$this->set(compact('users'));
//テンプレートを明示的に指定する。なくてもよい。
$this->render('edit');
}
このような時、テンプレートで定義される項目名の複数形が既にセットされている場合オプションの設定を省略してもCakeが自動でオプション項目をセットしてくれる。上の場合、user
というテンプレートの項目に対してusers
が既にedit()
で定義されているため、'options' => $users,
の明示的な設定は不要。
Coding Standard
メゾット間は1行あける。
パラメータの前には必ずスペースを入れる。