[CakePHP3] ORMでSELECT時、任意のcolumnを[key => value]で返す方法

2020/04/12

author

masyus

CakePHP2を経験された方がCakePHP3で躓きやすいポイントの1つとして、

find('list')とselect(['id', 'name'])で組み合わせれば [key => value] で返せる

ことを期待する点かと思います。

実際は、PRIMARY KEYのidはkeyとして返してくれますがvalueはselect()の指定では返してくれませんでした。どのように実装をすれば良いか、備忘録を兼ねて方法を2つ紹介いたします。検証したCakePHPのバージョンは3.6.13、PHPは7.3です。

実装方法1: find('list')時にkeyFieldとvalueFieldを指定する

find('list')の際にkeyFieldvalueFieldを指定することで実現可能です。keyおよびvalueは好きなcolumnを指定できます。サンプルコードは下記になります。

<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\View\View;

class ArticlesController extends AppController
{
    public function initialize()
    {
        parent::initialize();
        $this->loadModel('Articles');
        $this->loadModel('MasterArticleTypes');
    }

    /**
     * 一覧画面
     */
    public function index()
    {
        $this->paginate = [
            'contain' => ['Authors'],
        ];
        $articles = $this->paginate($this->Articles);

        // 記事タイプのkey・valueを取得
        $articleTypeList = $this->MasterArticleTypes
            ->find('list', [
                'keyField' => 'id',
                'valueField' => 'name',
            ])
            ->toArray();

        $this->set(compact('articles', 'articleTypeList'));
    }
}

おそらくこの実装方法を一番よく使うことになると思われますが、keyとvalueの後ろに都度Fieldをつける必要があるのが手間ですし冗長に感じます。その意味ではCakePHP2のほうが直感的に書けましたが、こればかりは仕方ないかもしれません。

実装方法2: 対象Tableのinitialize()にてsetDisplayField()を設定する

Tableクラスのinitialize()でsetDisplayField()関数を活用することで、予めfind('list')でデータ取得時のデフォルトvalueを決め打つ方法です。[key => value]で返すcolumnを固定したい場合には効果的かと思われます。サンプルコードは下記になります。

<?php
namespace App\Model\Table;

use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;

/**
 * MasterArticleTypes Model
 *
 * @method \App\Model\Entity\MasterArticleType get($primaryKey, $options = [])
 * @method \App\Model\Entity\MasterArticleType newEntity($data = null, array $options = [])
 * @method \App\Model\Entity\MasterArticleType[] newEntities(array $data, array $options = [])
 * @method \App\Model\Entity\MasterArticleType|bool save(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\MasterArticleType|bool saveOrFail(\Cake\Datasource\EntityInterface $entity, $options = [])
 * @method \App\Model\Entity\MasterArticleType patchEntity(\Cake\Datasource\EntityInterface $entity, array $data, array $options = [])
 * @method \App\Model\Entity\MasterArticleType[] patchEntities($entities, array $data, array $options = [])
 * @method \App\Model\Entity\MasterArticleType findOrCreate($search, callable $callback = null, $options = [])
 *
 * @mixin \Cake\ORM\Behavior\TimestampBehavior
 */
class MasterArticleTypesTable extends Table
{
    /**
     * Initialize method
     *
     * @param array $config The configuration for the Table.
     * @return void
     */
    public function initialize(array $config)
    {
        parent::initialize($config);

        $this->setTable('article_types');
        $this->setPrimaryKey('id');
        $this->setDisplayField('name'); // bakeすると、デフォルトはidになることが多いので注意
    }
}

setDisplayField()を指定しておきますと、Controller側では下記の記述で実現できるようになります。

<?php
namespace App\Controller;

use App\Controller\AppController;
use Cake\View\View;

class ArticlesController extends AppController
{
    public function initialize()
    {
        parent::initialize();
        $this->loadModel('Articles');
        $this->loadModel('MasterArticleTypes');
    }

    /**
     * 一覧画面
     */
    public function index()
    {
        $this->paginate = [
            'contain' => ['Authors'],
        ];
        $articles = $this->paginate($this->Articles);

        // 記事タイプのkey・valueを取得
        $articleTypeList = $this->MasterArticleTypes
            ->find('list')
            ->toArray();

        $this->set(compact('articles', 'articleTypeList'));
    }
}

1点気になることがあるとすれば、初見の方はTableクラスを確認しに行かないと「何故[key => value]を[id => name]で返せるのか?」が分からないため、やや暗黙知的な印象はあります。

参考