Yii2のスタンドアロンアクションの例

サイトを開発するとき、不可欠な部分はデータ収集の取得です。 特定の条件に応じたサンプリング、ページネーション。 毎回、コントローラーで実装を書くのは非常に退屈です。 頻繁に使用される機能の拡張可能な実装をできるだけ早く作成する場合。



この記事では、Yii2フレームワークの機能であるスタンドアロンアクションを使用して、アプリケーションのすべての部分で使用できる統一されたアーキテクチャを美しく整理する方法の例を示します。



要するに、これは :一度アクション実装を作成し、それらを任意のコントローラーにバインドする機能です。 したがって、基本的なアプリケーションテンプレートに基づく基本的なSiteControllerアプリケーションは、エラー処理とキャプチャ検証のための2つのアクションを実装します。



SiteControllerにアクションを添付する
<?php namespace app\controllers; use Yii; use yii\web\Controller; class SiteController extends Controller { public function actions() { return [ 'error' => [ 'class' => 'yii\web\ErrorAction', ], 'captcha' => [ 'class' => 'yii\captcha\CaptchaAction', 'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null, ], ]; } }
      
      









何が必要ですか


  1. ListAction- スタンドアロンアクション 。クエリと検索モデル間の関係を実装します。
  2. DataProvider-リクエスト上のレイヤー。ページナビゲーション、並べ替えを実装します。
  3. 検索モデル -受信データを受け入れ、目的のクエリを使用してDataProviderを検証および作成する検索モデル。


最後の2つのことの実装は、標準のgiiで生成されたCRUDで確認できます。 ARモデル自体のメソッドを使用して実装できる場合(yii1の場合と同様)、データサンプルを別のクラスに入れることは冗長に思えるかもしれません。 しかし、責任を分離し、別のクラスの機能を削除すると、柔軟性が増すように思えます。



ListActionの実装


アクションを要求するときに呼び出されるrunメソッドを持つクラスです。 クラスはyii \ base \ Actionを継承します。 コントローラにバインドするときにアクションを設定して、プロパティを変更できます。 このアクションでは、基本抽象クラスから継承された検索モデルと、カスタムビュー、データの挿入方法、ページネーションの受信方法など、アクションの他のオプション設定を渡します。



コメント付きのクラス実装
 <?php namespace app\modules\shop\actions; use Yii; use yii\base; use yii\web\Response; use app\modules\shop\components\FilterModelBase; use yii\widgets\LinkPager; class ListAction extends base\Action { /** *   * @var FilterModelBase */ protected $_filterModel; /** * -        * @var callable */ protected $_validationFailedCallback; /** *     , *  true,        - eg $_GET/$_POST[SearchModel][attribute] * @var bool */ public $directPopulating = true; /** *   ,  true,     html , *   AJAX  * @var bool */ public $paginationAsHTML = false; /** *   * @var string */ public $requestType = 'get'; /** *    * @var string */ public $view = '@app/modules/shop/views/list/index'; public function run() { if (!$this->_filterModel) { throw new base\ErrorException('   '); } $request = Yii::$app->request; if ($request->isAjax) { Yii::$app->response->format = Response::FORMAT_JSON; } //   $data = (strtolower($this->requestType) === 'post' && $request->isPost) ? $_POST : $_GET; $this->_filterModel->load(($this->directPopulating) ? $data : [$this->_filterModel->formName() => $data]); //      $this->_filterModel->search(); //       if ($this->_filterModel->hasErrors()) { /** *       , *  ajax   ,     ,   */ if ($request->isAjax){ return (is_callable($this->_validationFailedCallback)) ? call_user_func($this->_validationFailedCallback, $this->_filterModel) : [ 'error' => current($this->_filterModel->getErrors()) ]; } if (empty($data)) { $this->_filterModel->clearErrors(); } } if (!($dataProvider = $this->_filterModel->getDataProvider())) { throw new base\ErrorException('  DataProvider'); } if ($request->isAjax) { //      return [ 'list' => $this->_filterModel->buildModels(), 'pagination' => ($this->paginationAsHTML) ? LinkPager::widget([ 'pagination' => $dataProvider->getPagination() ]) : $dataProvider->getPagination() ]; } return $this->controller->render($this->view ?: $this->id, [ 'filterModel' => $this->_filterModel, 'dataProvider' => $dataProvider, 'requestType' => $this->requestType, 'directPopulating' => $this->directPopulating ]); } public function setFilterModel(FilterModelBase $model) { $this->_filterModel = $model; } public function setValidationFailedCallback(callable $callback) { $this->_validationFailedCallback = $callback; } }
      
      







また、これがAjaxリクエストでない場合は、データを出力するためのデフォルトビューを作成する必要があります。



デフォルトビュー
 <?php use yii\widgets\ActiveForm; use yii\helpers\Html; /** * @var \yii\web\View $this * @var \yii\data\DataProviderInterface $dataProvider * @var \app\modules\shop\components\FilterModelBase $filterModel * @var ActiveForm: $form * @var string $requestType * @var bool $directPopulating */ //      safe  if (($safeAttributes = $filterModel->safeAttributes())) { echo Html::beginTag('div', ['class' => 'well']); $form = ActiveForm::begin([ 'method' => $requestType ]); foreach ($safeAttributes as $attribute) { echo $form->field($filterModel, $attribute)->textInput([ 'name' => (!$directPopulating) ? $attribute : null ]); } echo Html::submitInput('search', ['class' => 'btn btn-default']). Html::endTag('div'); ActiveForm::end(); } echo \yii\grid\GridView::widget([ 'dataProvider' => $dataProvider, 'filterModel' => $filterModel ]);
      
      









このビューでは、デフォルト 、検索モデルの安全な属性の検索フォームが実装され、GridViewウィジェットを使用して検索結果が表示されます。 属性は、スクリプトで指定されているか、検証規則が設定されている場合に安全です。



基本的な検索モデル


ListActionに渡された検索モデルを継承する抽象クラスを表します。 モデルとListActionの相互作用のベースを実装します。 サンプリングロジックは、継承されたモデルに実装されます。



抽象クラスの実装
 <?php namespace app\modules\shop\components; use yii\base\Model; use yii\data\DataProviderInterface; abstract class FilterModelBase extends Model { /** * @var DataProviderInterface */ protected $_dataProvider; /** * @return DataProviderInterface */ abstract public function search(); /** *    *      ,    -    .. * @return mixed */ public function buildModels() { return $this->_dataProvider->getModels(); } public function getDataProvider() { return $this->_dataProvider; } }
      
      







検索モデルを実装し、ListActionをアタッチして、任意のコントローラーでこのモデルを検索します。 検索モデルでは、データのサンプリングは必須です。 他のすべては、特定の検索モデルの要件に依存します-検証、データ構成ロジックなど。



データ構成ロジックは、buildModelsメソッドでオーバーライドされます。



以下にコメント付きで、製品検索モデルの簡単な例を示します。



検索モデル
 <?php namespace app\modules\shop\models\search; use app\modules\shop; use yii\data\ActiveDataProvider; use yii\data\Pagination; class ProductSearch extends shop\components\FilterModelBase { /** *     */ public $price; public $page_size = 20; /** *    * @return array */ public function rules() { return [ //   ['price', 'required'], //  ,       ['page_size', 'integer', 'integerOnly' => true, 'min' => 1] ]; } /** *    * @return ActiveDataProvider|\yii\data\DataProviderInterface */ public function search() { //        $query = shop\models\Product::find() ->with('categories'); /** *  DataProvider,   ,   */ $this->_dataProvider = new ActiveDataProvider([ 'query' => $query, 'pagination' => new Pagination([ 'pageSize' => $this->page_size ]) ]); //   ,    if ($this->validate()) { $query->where('price <= :price', [':price' => $this->price]); } return $this->_dataProvider; } /** *    , *     *   . * @return array|mixed */ public function buildModels() { $result = []; /** * @var shop\models\Product $product */ foreach ($this->_dataProvider->getModels() as $product) { $result[] = array_merge($product->getAttributes(), [ 'categories' => $product->categories ]); } return $result; } }
      
      







ListActionをコントローラーにアタッチし、製品検索モデルをコントローラーに渡すことは残ります。



製品を検索するためのコントローラーのセットアップ
 <?php namespace app\modules\shop\controllers; use yii\web\Controller; use app\modules\shop\actions\ListAction; use app\modules\shop\models\search\ProductSearch; class ProductController extends Controller { public function actions() { return [ 'index' => [ 'class' => ListAction::className(), 'filterModel' => new ProductSearch(), 'directPopulating' => false, ] ]; } }
      
      







Ajaxを介してアクションにアクセスすると、次のようなJSONが取得されます。



サンプリング結果
 { "list": [ { "id": "7", "price": "50", "title": "product title #7", "description": "product description #7", "create_time": "0", "update_time": "0", "categories": [ { "id": "1", "title": "category title #1", "description": "category description #1", "create_time": "0", "update_time": "0" } ] } ], "pagination": { "pageVar": "page", "forcePageVar": true, "route": null, "params": null, "urlManager": null, "validatePage": true, "pageSize": 20, "totalCount": 1 } }
      
      







検証に失敗した場合、配列にはエラーの説明が含まれます。 通常のリクエスト(Ajaxではない)を使用すると、次のようなものが表示されます。







たとえば、 基本的なアプリケーションテンプレートに基づいて小さなモジュールが作成されました。 Yii2アプリケーションの設定で接続し、テストデータで移行を開始する必要があります



 php yii migrate --migrationPath=modules/shop/migrations
      
      







上記のすべてを要約すると、この機能により、選択されたコレクションやその他の繰り返し機能の統一された実装を作成できます。



実際の例として、この機能をAPIで使用します。リクエスト、JSONでの応答出力、またはテスト用のWebインターフェイスに応じて、1つのアクションが実装されます。



All Articles