Yii 2: スタンドアローンなアクションについて

## はじめに

各コントローラのアクションに同じようなコードを書いているのであれば、それを再利用可能な形にするということは、保守性の面からいっても大事かなと思います。そこで Yii 1.x でもあった機能ですが、Yii 2 のスタンドアローンなアクションについて少しだけですが紹介したいと思います！

## 例

例えば Gii の CRUD Generator でコードを自動生成するとコントローラの actioinCreate() は以下のようなものになっています。

```HogeController.php
class HogeController extends Controller
{
    public function actionCreate()
    {
        $model = new Hoge();

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        } else {
            return $this->render('create', [
                'model' => $model,
            ]);
        }
    }
}
```

フォームから値を受け取って、正しい値ならデータを保存して /view/12345 にリダイレクトする、みたいなごく一般的なデータの追加アクションです。このようなコードが他のコントローラにもたくさんあったとしたら、以下のように再利用可能な形に書き換えることができます。

```CreateAction.php
<?php

namespace app\actions;

use Yii;
use yii\base\Action;

class CreateAction extends Action
{
    public $modelClass;
    public $modelNamespace = 'app\models';
    public $view = 'create';

    protected function beforeRun()
    {
        $modelClass = $this->modelClass;

        if ($modelClass === null) {
            $modelClass = ucfirst($this->controller->id);
        }
        $this->modelClass = $this->modelNamespace . '\\' . $modelClass;

        return true;
    }

    public function run()
    {
        $model = new $this->modelClass;

        if ($model->load(Yii::$app->request->post()) && $model->save()) {
            return $this->controller->redirect(['view', 'id' => $model->id]);
        }
        return $this->controller->render($this->view, [
            'model' => $model,
        ]);
    }
}
```

多少長くなりますが、一度書いておけば他のコントローラでも、他のプロジェクトでも使いまわせるのでとても便利です。yii\base\Action クラスを継承したクラスを追加し run() というメソッド内に再利用可能なコードを書いていきます。コントローラには $this->controller でアクセスできます。run() が呼ばれる前に何か処理を追加したい場合は beforeRun() メソッド内に記述します。run() が呼ばれたあとに何か処理をしたい場合のために afterRun() というメソッドも用意されています。

コントローラ側では actions() メソッドを追加して、どの再利用アクションを使うのか指定します。以下の場合 actionCreate() は app\actions\CreateAction クラスを使う、という意味になります。

```HogeController.php
class HogeController extends Controller
{
    public function actions()
    {
        return [
            'create' => 'app\actions\CreateAction',
        ];
    }
```

コントローラがすっきりしました！

上記の CreateAction() の場合、モデルクラスはコントローラの ID 名を使って処理しているので、モデルクラスとコントローラ  ID 名が異なる場合はパラメータを追加して調整してやります。

```HogeController.php
public function actions()
{
    return [
        'create' => [
            'class' => 'app\actions\CreateAction',
            'modelClass' => 'Fuga', // 調整
        ],
    ];
}
```

`$modelNamespace` や `$view` も同様ですが、yii\base\Action クラスを継承したクラスの public なプロパティはコントローラの actions() のパラメータとして変更可能なものになります。

## 気をつけておきたいこと

自分は一般的な CRUD のために再利用可能な CURD アクション (ファイル構成: BaseAction, IndexAction, CreateAction, ViewAction, UpdateAction, DeleteAction) を作ってみたりしたんですが、コントローラをすっきりさせるために、あれやこれや処理を追加していってどうしたもんかなぁという状況に陥りました。あらゆるものに対処するために処理を詰め込めばそれ相応にコントローラはすっきりしますが、逆に再利用可能なアクションクラスたちは複雑さを増してきてメンテナンスも大変になります。

ですので、再利用可能なアクションクラスも (キャプチャアクションのような特定の機能に特化したアクションなどは別ですが ) できるだけシンプルで最低限の機能のみ実装しておいて、あとはコントローラに普通にそのまま記述するか、それでもコントローラが太っていく場合は、アプリケーションコンポーネントなどを利用する形を取るのが良いのかなと思います。要はバランスと使い分けです。

## 参考にできるクラス

以下は Yii のコアで書かれている yii\base\Action を継承したクラスたちです。あと yii\rest\Action を継承した CRUD クラスもあります。ここらへんのコード読むといろいろヒントもありそうですね！

- [yii\web\ViewAction](http://www.yiiframework.com/doc-2.0/yii-web-viewaction.html)
- [yii\web\ErrorAction](http://www.yiiframework.com/doc-2.0/yii-web-erroraction.html)
- [yii\captcha\CaptchaAction](http://www.yiiframework.com/doc-2.0/yii-captcha-captchaaction.html)
- [yii\rest\Action](http://www.yiiframework.com/doc-2.0/yii-rest-action.html)
- [yii\rest\ActiveController](http://www.yiiframework.com/doc-2.0/yii-rest-activecontroller.html)

## おわりに

簡単ではありますが、スタンドアローンなアクションについて紹介しました。詳しくは公式ガイドにも書かれていますので、以下の関連リンクなどを参考にしてみて下さい。

現時点ではまだ公式サイトに Yii 2 の日本語ガイドのページがないみたいなので、yiijan/yii2-i18n-ja という日本の Yii ユーザさんが集まってガイドなどを翻訳しているリポジトリのリンクを貼っておきます。

## 関連リンク

- [Yii 2 ガイド: コントローラ](https://github.com/yiijan/yii2-i18n-ja/blob/master/docs/guide-ja/structure-controllers.md)
- [yii\base\Action](http://www.yiiframework.com/doc-2.0/yii-base-action.html)