目的
開発の途中でテーブル定義が変更されることがよくある。(カラム追加とか)
その際に、CakePHPのBakeコマンドを再実行して、Model(Table,Entity)を最新化すると独自に記述したロジックが上書きされてしまうので、独自ロジックはBakeしたModelを継承したクラスに記述して、BakeしたModelを更新しても独自ロジックに影響しない状態にする。
※実運用に達していないので、参考程度と考えていただきたい。
ソースコード
bake用のModelを生成するコマンドとテンプレート
通常のbake model
を利用するとsrc/Model/Entity(またはTable)
にModelがBakeされる。
ここにはBakeしたModelを継承したModelを配置したいので、ModelCommand
を継承したCommandを作り、public $pathFragment = 'Model/Baked/';
とすることで、`src/Model/Baked/Entity(またはTable)にModelをBakeするようにする。
<?php
declare(strict_types=1);
namespace App\Command;
use Bake\Command\ModelCommand;
use Bake\Utility\TemplateRenderer;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Core\Configure;
use Cake\ORM\Table;
class BakedModelCommand extends ModelCommand
{
public $pathFragment = 'Model/Baked/';
/**
* {@inheritDoc}
*/
public function bake(string $name, Arguments $args, ConsoleIo $io): void
{
parent::bake($name, $args, $io);
}
}
上だけだと、Bakeのデフォルトのテンプレートを使われてしまい、BakeされたModelのnamespace
がデフォルトになってしまうので、vendor/cakephp/bake/templates/bake/Model
配下のentity.twig
、table.twig
をコピーして、templates/plugin/Bake/Model
配下に配置します。2ファイルのnamespace
を修正します。
bakeしたModelを継承するクラスを生成するコマンドとテンプレート
同じくModelCommand
を継承しつつ、ModelCommand
のbakeTable
とbakeTable
を元に出力先ディレクトリや利用するテンプレートを変更したbakeExtendedTable
とbakeExtendedEntity
を作成した。
もっといい方法はないものか・・?
(ModelCommand
が出力先ディレクトリや利用するテンプレートを差し替えれるようにしておいてくれると継承しやすいのに。。)
<?php
declare(strict_types=1);
namespace App\Command;
use Bake\Command\ModelCommand;
use Bake\Utility\TemplateRenderer;
use Cake\Console\Arguments;
use Cake\Console\ConsoleIo;
use Cake\Core\Configure;
use Cake\ORM\Table;
class ExtendedModelCommand extends ModelCommand
{
/**
* {@inheritDoc}
*/
public function bake(string $name, Arguments $args, ConsoleIo $io): void
{
$table = $this->getTable($name, $args);
$tableObject = $this->getTableObject($name, $table);
$data = $this->getTableContext($tableObject, $table, $name, $args, $io);
$this->bakeExtendedTable($tableObject, $data, $args, $io);
$this->bakeExtendedEntity($tableObject, $data, $args, $io);
}
public function bakeExtendedTable(Table $model, array $data, Arguments $args, ConsoleIo $io): void
{
if ($args->getOption('no-table')) {
return;
}
$namespace = Configure::read('App.namespace');
$pluginPath = '';
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
}
$name = $model->getAlias();
$entity = $this->_entityName($model->getAlias());
$data += [
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'namespace' => $namespace,
'name' => $name,
'entity' => $entity,
'associations' => [],
'primaryKey' => 'id',
'displayField' => null,
'table' => null,
'validation' => [],
'rulesChecker' => [],
'behaviors' => [],
'connection' => $this->connection,
];
$renderer = new TemplateRenderer($this->theme);
$renderer->set($data);
$out = $renderer->generate('Bake.Model/extended_table');
$path = $this->getPath($args);
$filename = $path . 'Table' . DS . $name . 'Table.php';
$io->out("\n" . sprintf('Baking table class for %s...', $name), 1, ConsoleIo::QUIET);
$io->createFile($filename, $out, false);
// Work around composer caching that classes/files do not exist.
// Check for the file as it might not exist in tests.
if (file_exists($filename)) {
require_once $filename;
}
$this->getTableLocator()->clear();
$emptyFile = $path . 'Table' . DS . '.gitkeep';
$this->deleteEmptyFile($emptyFile, $io);
}
public function bakeExtendedEntity(Table $model, array $data, Arguments $args, ConsoleIo $io): void
{
if ($args->getOption('no-entity')) {
return;
}
$name = $this->_entityName($model->getAlias());
$namespace = Configure::read('App.namespace');
$pluginPath = '';
if ($this->plugin) {
$namespace = $this->_pluginNamespace($this->plugin);
$pluginPath = $this->plugin . '.';
}
$data += [
'name' => $name,
'namespace' => $namespace,
'plugin' => $this->plugin,
'pluginPath' => $pluginPath,
'primaryKey' => [],
];
$renderer = new TemplateRenderer($this->theme);
$renderer->set($data);
$out = $renderer->generate('Bake.Model/extended_entity');
$path = $this->getPath($args);
$filename = $path . 'Entity' . DS . $name . '.php';
$io->out("\n" . sprintf('Baking entity class for %s...', $name), 1, ConsoleIo::QUIET);
$io->createFile($filename, $out, false);
$emptyFile = $path . 'Entity' . DS . '.gitkeep';
$this->deleteEmptyFile($emptyFile, $io);
}
}
上記コマンドから呼び出すテンプレートが以下2つ。
{#
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
#}
{% set annotations = DocBlock.buildTableAnnotations(associations, associationInfo, behaviors, entity, namespace) %}
<?php
declare(strict_types=1);
namespace {{ namespace }}\Model\Table;
use {{ namespace }}\Model\Baked\Table\{{ name }}Table as BakedTable;
{% set uses = ['use Cake\\ORM\\Query;', 'use Cake\\ORM\\RulesChecker;', 'use Cake\\ORM\\Table;', 'use Cake\\Validation\\Validator;'] %}
{{ uses|join('\n')|raw }}
{{ DocBlock.classDescription(name, 'Model', annotations)|raw }}
class {{ name }}Table extends BakedTable
{
/**
* Initialize method
*
* @param array $config The configuration for the Table.
* @return void
*/
public function initialize(array $config): void
{
parent::initialize($config);
}
/**
* Default validation rules.
*
* @param \Cake\Validation\Validator $validator Validator instance.
* @return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator): Validator
{
parent::validationDefault($validator);
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* @param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* @return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules): RulesChecker
{
parent::buildRules($rules);
return $rules;
}
/**
* Returns the database connection name to use by default.
*
* @return string
*/
public static function defaultConnectionName(): string
{
return '{{ connection }}';
}
}
{#
/**
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
*
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
* @link http://cakephp.org CakePHP(tm) Project
* @since 2.0.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
#}
<?php
declare(strict_types=1);
namespace {{ namespace }}\Model\Entity;
use {{ namespace }}\Model\Baked\Entity\{{ name }} as BakedEntity;
class {{ name }} extends BakedEntity
{
}
利用方法
bin/cake bake baked_model Xxxs
bin/cake bake extended_model Xxxs
感想
一旦はこれで開発してみて問題があれば修正する。
マニュアルに記載されているSimpleBakeCommand
継承版も作ってみたが、引数のXxxs
からファイル名やクラス名を生成するのが手間なので、ModelCommand
継承にすることにした。
ただこの実装だとModelCommand
に変更が入った時にコピペし直しが必要なので、もっといい方法がありそう。
参考
この考え方自体は以下を見て知った。
https://wp.tech-style.info/archives/1519
https://qiita.com/ymm1x/items/12ced37212636b09de19