2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

CakePHP3,4 コーディングメモ

Last updated at Posted at 2020-05-27

はじめに

自分用のメモのため、読んでもよくわからないことが多いと思いますが、参考がてらにどうぞ。随時更新します。

参照した画像がない場合、非同期で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.phpLogsTableの値を取り出したい時など

サンプル:

// 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パッケージをインストールできる。

ただし、以下のような注意が必要。

  1. ここでインストールできるパッケージはPackagistにあるもののみである。
  2. 最新バージョンで不具合がある場合があるのでできることならバージョンを指定した方がいい。
バージョンとは?どうやって指定するのか?

更新中。

オブジェクトのインスタンスが何かをIf文に含める

PHPには予めgetType($data)というメゾットがあり、変数のデータ型を取得できるが、Objectの場合、これだけでは何のインスタンスかわからない。
なので、
var_dump($data);でデータの型、インスタンスを調べた後、$this instanceof [Instance Name]をIf文に含める。

例:以下では、var_dump($data);で印字された内容を見るとErrorControllerのインスタンスであることがわかる。
image.png

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は以下のようになります。

ShippingsTable.php
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を指定してください。

ShippingsController.php
        $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>

ルーティングを何もいじっていなければ、当然以下のようにしてUsersControlleredit()メゾットからこのテンプレートが呼び出される。

    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行あける。
パラメータの前には必ずスペースを入れる。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?