LoginSignup
0
0

More than 5 years have passed since last update.

CakePHP3 bake無しで動くControllerの作りかた

Last updated at Posted at 2018-12-13

なぜ思いついたのか?

マーチン・ファウラー著リファクタリングの第3章「コードの不吉な匂い」の一番最初に「重複したコード」という項目があり冒頭に以下の記述があります。

同じようなコードが2箇所以上で見られたら1箇所にまとめると良いプログラムとなります。

CakePHPはとても良いフレームワークですがbakeすると同じようなコードがたくさん作成されてしまうので何とかまとめられないかな〜と考えたのです。

最終的に作成したいController

基本的な機能は CrudController を継承すれば継承先は基本的に何も書かないで動作させたい。

GenresController.php
<?php
namespace App\Controller;
use App\Controller\CrudController;
class GenresController extends CrudController
{

}

各機能の抽象化

最初に genres テーブルのコントローラをbakeし変更を加えながらCrudControllerを作成してみます。

bake
./bin/cake bake controller genres

bake 後の index()

GenresController.php
<?php
namespace App\Controller;
use App\Controller\AppController;
class GenresController extends AppController
{
    public function index()
    {
        $this->paginate = [
            'contain' => ['Users']
        ];
        $genres = $this->paginate($this->Genres);
        $this->set(compact('genres'));
    }

他の機能もほぼ同じ様にモデル名、エンティティー名が独自のものになっています。その他には contain を制御する必要があります。

CrudController の 定義 およびindex()

CrudController.php
<?php
namespace App\Controller;

class CrudController extends Controller
{
     public function index(array $contains = [])
    {
        $this->paginate['contain'] = $contains;
        $entities = $this->paginate($this->{$this->modelClass});
        $this->set(compact('entities'));
    }

  • モデル名は {$this->modelClass}で置き換えます。
  • ビューに引き渡す変数名は entities (複数形)で固定します。(Template側の変更が必要になります。)
  • contain は引数で継承先から受け取る仕様にします。

CrudController の view()

CrudController.php
...
public function view($id = null, array $contains = [])
    {
        $options = [];
        if (count($contains) > 0) {
            $options = ['contain' => $contains];
        }
        $entity = $this->{$this->modelClass}->get($id, $options);
        $this->set(compact('entity'));
    }

index()とほぼ同じです。

  • モデル名は {$this->modelClass}で置き換えます。
  • ビューに引き渡す変数名は entity (単数)で固定します。(Template側の変更が必要になります。)
  • contain は引数で継承先から受け取る仕様にします。

CrudController の add()

CrudController.php
    public function add(array $redirect = [])
    {
        $entity = $this->{$this->modelClass}->newEntity();
        if ($this->request->is('post')) {
            $entity = $this->{$this->modelClass}->patchEntity($entity, $this->request->getData());
            if ($this->{$this->modelClass}->save($entity)) {
                $this->Flash->success(__('The ' . $this->entity_name . ' has been saved.'));
                if ($redirect) {
                    return $this->redirect($redirect);
                }
                return $this->redirect(['action' => 'view' . DS . $entity->id]);
            }
            $this->Flash->error(__('The ' . $this->entity_name . ' could not be saved. Please, try again.'));
        }
        $this->set(compact('entity'));
    }

  • モデル名は {$this->modelClass}で置き換えます。
  • ビューに引き渡す変数名は entity (単数)で固定します。(Template側の変更が必要になります。)
  • 追加後のリダイレクト先を指定可能にしました。
  • フラッシュメッセージがちょっと問題なのですがエンティティー名部分を$this->entity_nameに置き換えました。

エンティティー名を定義する。

CrudController.php
<?php
namespace App\Controller;
use Cake\Event\Event;
use Cake\Utility\Inflector;

class CrudController extends AppController
{
    protected $entity_name;

    function beforeFilter(Event $event)
    {
        $this->entity_name = Inflector::singularize(Inflector::humanize($this->modelClass));
        return parent::beforeFilter($event);
    }
...

beforeFilter()でInflectorを利用し$this->entity_nameを定義しました。
これでadd()のフラッシュメッセージ問題も解決しました。
エンティティー名に左右されない固定のメッセージにしても問題ないとも思います。

CrudController の edit()

CrudController.php
 public function edit($id = null, array $contains = [], array $redirect = [], $entity = null)
    {
        if (!$entity) {
            $options = [];
            if (count($contains) > 0) {
                $options = ['contain' => $contains];
            }
            $entity = $this->{$this->modelClass}->get($id, $options);
        }
        if ($this->request->is(['patch', 'post', 'put'])) {
            $entity = $this->{$this->modelClass}->patchEntity($entity, $this->request->getData());
            if ($this->{$this->modelClass}->save($entity)) {
                $this->Flash->success(__('The ' . $this->entity_name . ' has been saved.'));
                if ($redirect) {
                    return $this->redirect($redirect);
                }
                return $this->redirect(['action' => 'index']);
            }
            $this->Flash->error(__('The ' . $this->entity_name . ' could not be saved. Please, try again.'));
        }
        $this->set(compact('entity'));
    }
  • モデル名は {$this->modelClass}で置き換え。
  • ビューに引き渡す変数名は entity (単数)で固定します。(Template側の変更が必要になります。)
  • フラッシュメッセージのエンティティー名部分を$this->entity_nameに置き換え。
  • contain は引数で継承先から受け取る仕様。
  • 継承先で エンティティーを作成した場合に対応して最後の引数にエンティティーを渡すことも可能にしました。

CrudController の delete()

CrudController.php
public function delete($id = null, array $redirect = [], $entity = null)
    {
        $this->request->allowMethod(['post', 'delete']);
        if (!$entity) {
            $entity = $this->{$this->modelClass}->get($id);
        }
        if ($this->{$this->modelClass}->delete($entity)) {
            $this->Flash->success(__('The ' . $this->entity_name . ' has been deleted.'));
        } else {
            $this->Flash->error(__('The ' . $this->entity_name . ' could not be deleted. Please, try again.'));
        }
        if ($redirect) {
            return $this->redirect($redirect);
        }
        return $this->redirect(['action' => 'index']);
    }
  • モデル名は {$this->modelClass}で置き換え。
  • フラッシュメッセージのエンティティー名部分を$this->entity_nameに置き換え。
  • 継承先での エンティティー 設定可能
  • 削除後のリダイレクト先を指定可能にしました。

Templateについて

bakeされたTemplateは各々のエンティティー名が利用されていますので全て $entitiesや$entity に置き換える必要があります。

$this->entity_nameを利用すれば置き換え不要にすることも可能かも知れませんがTempalte にも extend() 機能がありますので重複コード取り除くことがある程度可能です。
そのために敢えてエンティティー名は抽象的な名前で作成しています。
Tempalteのリファクタリングは次の機会があれば発表したいと思います。

最後に

この発表では単純な置き換えだけをしましたが独自ルールを反映させる場合にメンテナンスが簡単になります。

例えば DB保存時に誰が操作したのか user_id を保存したい場合はsaveする前に
次の様な行を追加すれば全てのControllerで同じ処理をしてくれることになります。

CrudController.php
$entity->set('user_id', $this->Auth->user('id'));

最後までお読み頂きありがとうございました。

0
0
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
0
0