ヘビとハリネズミを横断する方法。 LaravelプロジェクトでYii 2のGridViewを使用する

最近、Yiiに関する記事があり、Yii固有のコンポーネント、特にGridViewとActiveForm、およびLaravelフレームワークがコメントで議論されました。 なぜだと思いました。



composer create-project laravel/laravel ... composer require yiisoft/yii2
      
      





それから来たもの、カットの下で読んでください。 いくつかの小さなバインディングを作成して特定の方法で構成する必要がありましたが、一般に、グリッドとフォームの両方ですべてが機能します。 Laravelの既存の類似物の概要も少しあります。



オプションは何ですか



https://github.com/view-components/grids

https://github.com/assurrussa/grid-view-table

https://github.com/dwightwatson/bootstrap-form

https://github.com/core-system/bootstrap-form

https://github.com/adamwathan/bootforms

https://github.com/zofe/rapyd-laravel



基本的な要件:



-ブートストラップレイアウト

-ソート、ページネーション、フォーム検証エラーの自動処理

-手書きの最小限のコード

-カスタマイズ性



https://github.com/view-components/grids



良いグリッド、フレームワークに依存しません。 フレームワーク用のコネクタがあります。 かなり面倒な設定。 さらに、出力はエスケープされていないようです。



https://github.com/assurrussa/grid-view-table



多くの定型文は、そのグローバルな機能、レンダリングの奇妙な方法を追加します。



https://github.com/dwightwatson/bootstrap-form



フォーム自体がアクションのルートを選択し、セッションからエラーが取得されます。 しかし、必要なものに全体的に近い。



セッションを通してエラーと入力値を渡すアプローチが好きではありません。 F5により、誤って更新された場合、フォームは再送信されず、すべてのエラーと値が削除されます。



https://github.com/core-system/bootstrap-form



タグのグループを手動で開いたり閉じたりした場合のビルダーのポイントは何ですか。



https://github.com/core-system/bootstrap-form



優れたフォームビルダー、ActiveFormのほぼ完全な類似物。 エラーのリポジトリと入力した値を設定できます。



https://github.com/zofe/rapyd-laravel



このオプションは最も適切なようです。 グリッドとフォームの両方があります。 グリッドは非常に優れていますが、フォームに問題があります。



-アクションの表示/作成/編集は同じルートでハングしますが、getパラメーターによって異なります。 したがって、デフォルトのグリッドでは、アクションのURLは同じです。

-これは1つの形式であり、表示モードが異なるだけです。 これは、表示専用にcreated_at / updated_atを表示する必要がある場合に問題を引き起こします。 そして、フィールドのクラスは、3つのモードすべてについて記述されなければなりません。

-プロジェクト内のコードがあまり良くない



統合



手順に従います。最終コードは記事の最後にあります。 テーブルと完全な編集は管理セクションにあることが多いため、例は特定のサイトの管理パネルの形式になります。



参考までに、laravelフォルダーは2.6 MB、symfonyフォルダーは4.6 MB、yiisoftフォルダーは3.9 MB、依存関係はYii 5.6 MBです。



注文と商品を使用した簡単なアプリケーションを検討してください。



SQL
 CREATE TABLE IF NOT EXISTS `users` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `email` varchar(100) NOT NULL, `password` varchar(255) NOT NULL, `remember_token` varchar(100) DEFAULT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `users_email_unique` (`email`) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS `products` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS `orders` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `user_id` int(10) unsigned NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `orders-users` (`user_id`), CONSTRAINT `orders-users` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ) ENGINE=InnoDB; CREATE TABLE IF NOT EXISTS `order_items` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `order_id` int(10) unsigned NOT NULL, `product_id` int(10) unsigned NOT NULL, `created_at` timestamp NULL DEFAULT NULL, `updated_at` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`), KEY `order_items-orders` (`order_id`), KEY `order_items-products` (`product_id`), CONSTRAINT `order_items-orders` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `order_items-products` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON UPDATE CASCADE ) ENGINE=InnoDB;
      
      







注文セクションのEloquentモデルとOrderControllerを作成します。 管理パネルのルートのグループを作成しましょう。



routes/web.php







 Route::group(['prefix' => 'admin', 'as' => 'admin.', 'namespace' => 'Admin'], function () { Route::get('/order', 'OrderController@index')->name('order.index'); Route::get('/order/view/{id}', 'OrderController@view')->name('order.view'); Route::get('/order/create', 'OrderController@create')->name('order.create'); Route::get('/order/update/{id}', 'OrderController@update')->name('order.update'); Route::post('/order/create', 'OrderController@create'); Route::post('/order/update/{id}', 'OrderController@update'); Route::post('/order/delete/{id}', 'OrderController@delete')->name('order.delete'); });
      
      





CDNへのリンクを含むブートストラップテンプレートを作成します。



リソース/ビュー/レイアウト/ main.blade.php
 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="/favicon.ico"> <title>@yield('title')</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <style>body { padding-top: 60px; }</style> </head> <body> @include('layouts.nav') <div class="container"> @yield('content') </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> </body> </html>
      
      



= "SHA384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin = "匿名"> </ SCRIPT> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="icon" href="/favicon.ico"> <title>@yield('title')</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries --> <!-- WARNING: Respond.js doesn't work if you view the page via file:// --> <!--[if lt IE 9]> <script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script> <script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script> <![endif]--> <style>body { padding-top: 60px; }</style> </head> <body> @include('layouts.nav') <div class="container"> @yield('content') </div> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> </body> </html>







ミドルウェアを初期化して、管理パネルをルートに接続します。 初期化は次のようになります。



routes/web.php







 $initYii2Middleware = function ($request, $next) { define('YII_DEBUG', env('APP_DEBUG')); include '../vendor/yiisoft/yii2/Yii.php'; spl_autoload_unregister(['Yii', 'autoload']); $config = [ 'id' => 'yii2-laravel', 'basePath' => '../', 'timezone' => 'UTC', 'components' => [ 'assetManager' => [ 'basePath' => '@webroot/yii-assets', 'baseUrl' => '@web/yii-assets', 'bundles' => [ 'yii\web\JqueryAsset' => [ 'sourcePath' => null, 'basePath' => null, 'baseUrl' => null, 'js' => [], ], ], ], 'request' => [ 'class' => \App\Yii\Web\Request::class, 'csrfParam' => '_token', ], 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, ], 'formatter' => [ 'dateFormat' => 'php:m/d/Y', 'datetimeFormat' => 'php:m/d/YH:i:s', 'timeFormat' => 'php:H:i:s', 'defaultTimeZone' => 'UTC', ], ], ]; (new \yii\web\Application($config)); // initialization is in constructor Yii::setAlias('@bower', Yii::getAlias('@vendor') . DIRECTORY_SEPARATOR . 'bower-asset'); return $next($request); }; Route::group(['prefix' => 'admin', 'as' => 'admin.', 'namespace' => 'Admin', 'middleware' => $initYii2Middleware], function () { ... });
      
      





spl_autoload_unregister(['Yii', 'autoload']);



-十分なLaravelオートローダーが干渉しないように、切断することをお勧めします。 getAlias('@'...)



を使用してファイルを検索しますが、もちろん見つかりません。

basePath



アプリケーションのルートディレクトリ。正しくインストールされていない場合、パスにエラーがある可能性があります。 runtime



フォルダーは同じディレクトリに作成されます。

assetManager.basePath, assetManager.baseUrl



アセットを公開するためのパスとURL。フォルダーの名前は任意です。

assetManager.bundles



メインテンプレートで個別に接続されているため、jQueryパブリケーションを無効にします。

request-作業をCSRFトークンに置き換えるrequestコンポーネントを再定義します。フィールド名はLaravel設定と同じです。

urlManager.enablePrettyUrl



- urlManager.enablePrettyUrl



タイプの追加モジュールが必要な場合に有効にする必要があります。

(new \yii\web\Application($config))



-コンストラクターでは、割り当てはYii::$app = $this;







要求コンポーネントは次のようになります。



app/Yii/Web/Request.php







 namespace App\Yii\Web; class Request extends \yii\web\Request { public function getCsrfToken($regenerate = false) { return \Session::token(); } }
      
      





トークンはLaravelによって管理されるため、再生成を処理する必要はありません。



グリッド



これで実行できます。 注文リストのコードを追加します。



app/Http/Controllers/Admin/OrderController.php







 public function index(Request $request) { $allModels = Order::query()->get()->all(); $gridViewConfig = [ 'dataProvider' => new \yii\data\ArrayDataProvider([ 'allModels' => $allModels, 'pagination' => ['route' => $request->route()->uri(), 'defaultPageSize' => 10], 'sort' => ['route' => $request->route()->uri(), 'attributes' => ['id']], ]), 'columns' => [ 'id', 'user.name', ['label' => 'Items', 'format' => 'raw', 'value' => function ($model) { $html = ''; foreach ($model->items as $item) { $html .= '<div>' . htmlspecialchars($item->product->name) . '</div>'; } return $html; }], 'created_at:datetime', 'updated_at:datetime', [ 'class' => \yii\grid\ActionColumn::class, 'urlCreator' => function ($action, $model, $key) use ($request) { $baseRoute = $request->route()->getName(); $baseRouteParts = explode('.', $baseRoute); $baseRouteParts[count($baseRouteParts) - 1] = $action; $route = implode('.', $baseRouteParts); $params = is_array($key) ? $key : ['id' => (string) $key]; return route($route, $params, false); } ], ], ]; return view('admin.order.index', ['gridViewConfig' => $gridViewConfig]); }
      
      





リソース/ビュー/管理者/注文/ index.blade.php
 @extends('layouts.main') @section('title', 'Index') @section('content') <h1>Orders</h1> <div class="text-right"> <a href="{{ route('admin.order.create') }}" class="btn btn-success">Create</a> </div> {!! \yii\grid\GridView::widget($gridViewConfig) !!} @endsection
      
      







dataProvider.pagination.route



dataProvider.sort.route



を設定する必要があります。設定しない場合は、 Yii::$app->controller->getRoute()



にアクセスし、コントローラーがnull



。 同様にActionColumn



では、チェックとInvalidParamException



のみがあります。 URLは\yii\web\UrlManager



を介して生成されますが、結果はLaravelルーティングと同じです。 必要に応じて、 dataProvider.pagination.urlManager



てマネージャーを設定できます。

列ラベルは今のところ自動生成されたままです。

また、アイコンをソートするためのいくつかのスタイルを設定する必要があります。



グリッドは表示されますが、フロントエンドスクリプトがないため、[削除]ボタンは機能しません。



\yii\web\View



コンポーネントにあるスクリプトを表示する必要があります。 メソッドrenderHeadHtml(), renderBodyBeginHtml(), renderBodyEndHtml()



は保護されています(特に、すべての変数がpublic



ことを考慮すると、誰からのものかは不明です)。 奇妙なことに、アンチパターン「public morozov」を適用する理由があります。 または、メインテンプレートに単純に貼り付けることができます。



app/Yii/Web/View.php







 namespace App\Yii\Web; class View extends \yii\web\View { public function getHeadHtml() { return parent::renderHeadHtml(); } public function getBodyBeginHtml() { return parent::renderBodyBeginHtml(); } public function getBodyEndHtml($ajaxMode = false) { return parent::renderBodyEndHtml($ajaxMode); } public function initAssets() { \yii\web\YiiAsset::register($this); ob_start(); $this->beginBody(); $this->endBody(); ob_get_clean(); } }
      
      





Yiiでは、アセットはendBody()



関数に登録され、すべてのレンダリングはバッファーにラップされます。バッファーでは、 CDATA



マジック定数が実際のアセットに置き換えられます。 この動作のエミュレーションは、 initAssets()



関数にあります。 何も置き換えません。プロパティ$this->js, $this->css



などを入力するだけです。



ルート/ web.php
 'components' => [ ... 'view' => [ 'class' => \App\Yii\Web\View::class, ], ],
      
      









リソース/ビュー/管理者/注文/ index.blade.php
 <!DOCTYPE html> <html lang="en"> <head> ... <?php $view = \Yii::$app->getView(); $view->initAssets(); ?> {!! \yii\helpers\Html::csrfMetaTags() !!} {!! $view->getHeadHtml() !!} </head> <body> {!! $view->getBodyBeginHtml() !!} ... {!! $view->getBodyEndHtml() !!} </body> </html>
      
      







yii.jsスクリプトはHTMLページからcsrfトークンを取得するため、 Html::csrfMetaTags()



の呼び出しHtml::csrfMetaTags()



必要です。



ArrayDataProvider



機能しますが、 ActiveDataProvider



類似物をActiveDataProvider



して、データベースから必要なものだけを取得する必要があります。



app/Yii/Data/EloquentDataProvider.php







 class EloquentDataProvider extends \yii\data\BaseDataProvider { public $query; public $key; protected function prepareModels() { $query = clone $this->query; if (($pagination = $this->getPagination()) !== false) { $pagination->totalCount = $this->getTotalCount(); if ($pagination->totalCount === 0) { return []; } $query->limit($pagination->getLimit())->offset($pagination->getOffset()); } if (($sort = $this->getSort()) !== false) { $this->addOrderBy($query, $sort->getOrders()); } return $query->get()->all(); } protected function prepareKeys($models) { $keys = []; if ($this->key !== null) { foreach ($models as $model) { $keys[] = $model[$this->key]; } return $keys; } else { $pks = $this->query->getModel()->getKeyName(); if (is_string($pks)) { $pk = $pks; foreach ($models as $model) { $keys[] = $model[$pk]; } } else { foreach ($models as $model) { $kk = []; foreach ($pks as $pk) { $kk[$pk] = $model[$pk]; } $keys[] = $kk; } } return $keys; } } protected function prepareTotalCount() { $query = clone $this->query; $query->orders = null; $query->offset = null; return (int) $query->limit(-1)->count('*'); } protected function addOrderBy($query, $orders) { foreach ($orders as $attribute => $order) { if ($order === SORT_ASC) { $query->orderBy($attribute, 'asc'); } else { $query->orderBy($attribute, 'desc'); } } } }
      
      





app / Http / Controllers / Admin / OrderController.php
  'dataProvider' => new \App\Yii\Data\EloquentDataProvider([ 'query' => Order::query(), 'pagination' => ['route' => $request->route()->uri(), 'defaultPageSize' => 10], 'sort' => ['route' => $request->route()->uri(), 'attributes' => ['id']], ]),
      
      







タグとフィルター



\yii\base\Model



から継承したベースモデルを作成する必要があります。これは、列のグリッドラベルとフィルタリングのフィールドルールを返します。 これにはfilterModel



パラメーターがあります。 コンストラクターで設定できるようにしましょう。



app / Yii / Data / FilterModel.php
 namespace App\Yii\Data; use App\Yii\Data\EloquentDataProvider; use Route; class FilterModel extends \yii\base\Model { protected $labels; protected $rules; protected $attributes; public function __construct($labels = [], $rules = []) { parent::__construct(); $this->labels = $labels; $this->rules = $rules; $safeAttributes = $this->safeAttributes(); $this->attributes = array_combine($safeAttributes, array_fill(0, count($safeAttributes), null)); } public function __get($name) { if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } else { return parent::__get($name); } } public function __set($name, $value) { if (array_key_exists($name, $this->attributes)) { $this->attributes[$name] = $value; } else { parent::__set($name, $value); } } public function rules() { return $this->rules; } public function attributeLabels() { return $this->labels; } public function initDataProvider($query, $sortAttirbutes = [], $route = null) { if ($route === null) { $route = Route::getCurrentRoute()->uri(); } $dataProvider = new EloquentDataProvider([ 'query' => $query, 'pagination' => ['route' => $route], 'sort' => ['route' => $route, 'attributes' => $sortAttirbutes], ]); return $dataProvider; } public function applyFilter($params) { $query = null; $dataProvider = $this->initDataProvider($query); return $dataProvider; } }
      
      







特殊なモデルを継承して定義し、そこにすべてを置くことができます。



 namespace App\Forms\Admin; use App\Yii\Data\FilterModel; class OrderFilter extends FilterModel { public function rules() { return [ ['id', 'safe'], ['user.name', 'safe'], ]; } public function attributeLabels() { return [ 'id' => 'ID', 'created_at' => 'Created At', 'updated_at' => 'Updated At', 'user.name' => 'User', ]; } public function applyFilter($params) { $this->load($params); $query = \App\Models\Order::query(); $query->join('users', 'users.id', '=', 'orders.user_id')->select('orders.*'); if ($this->id) $query->where('orders.id', '=', $this->id); if ($this->{'user.name'}) $query->where('users.name', 'like', '%'.$this->{'user.name'}.'%'); $sortAttributes = [ 'id', 'user.name' => ['asc' => ['users.name' => SORT_ASC], 'desc' => ['users.name' => SORT_DESC]], ]; $dataProvider = $this->initDataProvider($query, $sortAttributes); $dataProvider->pagination->defaultPageSize = 10; if (empty($dataProvider->sort->getAttributeOrders())) { $dataProvider->query->orderBy('orders.id', 'asc'); } return $dataProvider; } }
      
      





app / Http / Controllers / Admin / OrderController.php
 public function index(Request $request) { $filterModel = new \App\Forms\Admin\OrderFilter(); $dataProvider = $filterModel->applyFilter($request); $gridViewConfig = [ 'dataProvider' => $dataProvider, 'filterModel' => $filterModel, ... ]; ... }
      
      







filterModelが指定されているが、フィルター用のフィールドがない場合、空のセルを持つ行が引き続き表示されるという小さな問題があります。 この場合、手動でラベルを付けることができます。 ただし、コンポーネント自体にこのようなチェックがあれば良いでしょう。



表示する



ここでも同じことを行い、列の設定をグリッドからコピーできます。 注文ページで商品を個別のグリッドとして注文します。 ラベルも、今のところ、自動生成されたままです。



app/Http/Controllers/Admin/OrderController.php







 public function view($id) { $model = Order::findOrFail($id); $detailViewConfig = [ 'model' => $model, 'attributes' => [ 'id', 'user.name', 'created_at:datetime', 'updated_at:datetime', ], ]; $gridViewConfig = [ 'dataProvider' => new \App\Yii\Data\EloquentDataProvider([ 'query' => $model->items(), 'pagination' => false, 'sort' => false, ]), 'layout' => '{items}{summary}', 'columns' => [ 'id', 'product.name', 'created_at:datetime', 'updated_at:datetime', ], ]; return view('admin.order.view', ['model' => $model, 'detailViewConfig' => $detailViewConfig, 'gridViewConfig' => $gridViewConfig]); }
      
      





リソース/ビュー/管理者/注文/ view.blade.php
 @extends('layouts.main') @section('title', 'Index') @section('content') <h1>Order: {{ $model->id }}</h1> <p class="text-right"> <a href="{{ route('admin.order.update', ['id' => $model->id]) }}" class="btn btn-primary">Update</a> <a href="{{ route('admin.order.delete', ['id' => $model->id]) }}" class="btn btn-danger" data-confirm="Are you sure?" data-method="post">Delete</a> </p> {!! \yii\widgets\DetailView::widget($detailViewConfig) !!} <h2>Order Items</h2> {!! \yii\grid\GridView::widget($gridViewConfig) !!} @endsection
      
      









作成/更新



最初に、 \yii\base\Model



から継承されたEloquentモデルのラッパーであるフォームモデルを作成して、 ActiveForm



コンポーネントが必要なメソッドを呼び出せるようにする必要があります。



app / Yii / Data / FormModel.php
 namespace App\Yii\Data; use Illuminate\Database\Eloquent\Model as EloquentModel; class FormModel extends \yii\base\Model { protected $model; protected $labels; protected $rules; protected $attributes; public function __construct(EloquentModel $model, $labels = [], $rules = []) { parent::__construct(); $this->model = $model; $this->labels = $labels; $this->rules = $rules; $fillable = $model->getFillable(); $attributes = []; foreach ($fillable as $field) { $attributes[$field] = $model->$field; } $this->attributes = $attributes; } public function getModel() { return $model; } public function __get($name) { if (array_key_exists($name, $this->attributes)) { return $this->attributes[$name]; } else { return $this->model->{$name}; } } public function __set($name, $value) { if (array_key_exists($name, $this->attributes)) { $this->attributes[$name] = $value; } else { $this->model->{$name} = $value; } } public function rules() { return $this->rules; } public function attributeLabels() { return $this->labels; } public function save() { if (!$this->validate()) { return false; } $this->model->fill($this->attributes); return $this->model->save(); } }
      
      







これで、編集を行うことができます。



app/Http/Controllers/Admin/OrderController.php







  public function create(Request $request) { $model = new Order(); $formModel = new \App\Yii\Data\FormModel( $model, ['user_id' => 'User'], [['user_id', 'safe']] ); if ($request->isMethod('post')) { if ($formModel->load($request->input()) && $formModel->save()) { return redirect()->route('admin.order.view', ['id' => $model->id]); } } return view('admin.order.create', ['formModel' => $formModel]); } public function update($id, Request $request) { $model = Order::findOrFail($id); $formModel = new \App\Yii\Data\FormModel( $model, ['user_id' => 'User'], [['user_id', 'safe']] ); if ($request->isMethod('post')) { if ($formModel->load($request->input()) && $formModel->save()) { return redirect()->route('admin.order.view', ['id' => $model->id]); } } return view('admin.order.update', ['formModel' => $formModel]); }
      
      





リソース/ビュー/管理者/注文/ _form.blade.php
 <?php $form = \yii\widgets\ActiveForm::begin() ?> {!! $form->field($formModel, 'user_id')->dropDownList(\App\User::pluck('name', 'id'), ['prompt' => '']) !!} <button type="submit" class="btn btn-primary">Submit</button> <?php \yii\widgets\ActiveForm::end() ?>
      
      







検証ルールはYiiのスタイルで設定されます。 必要に応じて、 validate()



メソッドをオーバーライドし、そこでLaravelバリデーターを呼び出します。 この例では、これを行いません。



Bladeでは、変数宣言は許可されていません。 ActiveForm::begin()



もタグを表示し、値を返します。 <?php ?>



タグを明示的に記述できますActiveForm



Blade::extend()



を介して新しいタグを作成できます。 ここでお勧めするように、 ActiveForm



ラッパーをActiveForm



ます。 今のところ、 <?php ?>



ままにしておき<?php ?>







フィルターと同様に、 FormModel



から継承し、 FormModel



にすべての広告を配置できます。



app / Forms / Admin / OrderForm.php
 namespace App\Forms\Admin; class OrderForm extends FormModel { public function rules() { return [ ['user_id', 'safe'], ]; } public function attributeLabels() { return [ 'id' => 'ID', 'user_id' => 'User', 'created_at' => 'Created At', 'updated_at' => 'Updated At', 'user.name' => 'User', ]; } }
      
      









動画再生ページのタグ



OrderForm



を使用してapp/Http/Controllers/Admin/OrderController.php



ラベルを設定できるようになりました。



 $formModel = new \App\Forms\Admin\OrderForm($model); $detailViewConfig = [ 'model' => $formModel, ... ];
      
      







削除する



ここではすべてが簡単です。



app/Http/Controllers/Admin/OrderController.php







 public function delete($id) { $model = Order::findOrFail($id); $model->delete(); return redirect()->route('admin.order.index'); }
      
      





追加



Giiを接続できます。 純粋な形式では必要ありませんが、フォーム検証ルールとフィールドのラベルをモデルジェネレータから取得して、手動で生成しないようにすることができます。 または、独自のジェネレータを作成できます。



 composer require yiisoft/yii2-gii --dev
      
      





ルート/ web.php
  $config = [ 'components' => [ ... 'db' => [ 'class' => \yii\db\Connection::class, 'dsn' => 'mysql:host='.env('DB_HOST', 'localhost') .';port='.env('DB_PORT', '3306') .';dbname='.env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'charset' => 'utf8', ], ... ], ]; if (YII_DEBUG) { $config['modules']['gii'] = ['class' => \yii\gii\Module::class]; $config['bootstrap'][] = 'gii'; } (new \yii\web\Application($config)); // initialization is in constructor Yii::setAlias('@bower', Yii::getAlias('@vendor') . DIRECTORY_SEPARATOR . 'bower-asset'); Yii::setAlias('@App', Yii::getAlias('@app') . DIRECTORY_SEPARATOR . 'App'); ... Route::any('gii{params?}', function () { $request = \Yii::$app->getRequest(); $request->setBaseUrl('/admin'); \Yii::$app->run(); return null; })->where('params', '(.*)');
      
      







Yii::setAlias('@App')



-ファイルへのパスはYii::getAlias('@'...)



によって決定されるため、パス'@App/Models/Order.php'



はクラスApp\Models\Order



でチェックされます。



setBaseUrl('/admin')



-Yiiルーティングプロセスは、 '/ admin'の後の部分のみであることが必要です。



Yii::setAlias('@App')



および['Yii', 'autoload']



すると、このような問題が発生します。 オートローダーを無効にしない場合、クラスまたはネームスペースの名前が間違っていると、既存のファイルでエラーが発生し、正しく処理されません。 このようになります。 ファイルをマウントしますが、クラスを見つけられず、 UnknownClassException



をスローしUnknownClassException



。 Laravelオートローダーが呼び出され、ファサードとエイリアスをチェックし、何も検出されません。 次に、Composerオートローダーが呼び出され、ファイルが再び接続され、「クラスを宣言できません」...「名前が既に使用されているため、」という別のエラーが発生します。 ログなしでエラー500でアプリケーションがクラッシュします。



Giiには独自の表示テンプレートがあるため、アプリケーションアセットの設定がリセットされるため、GiiはjQueryを無効にしているにもかかわらず機能します。



ベンダー\ yiisoft \ yii2-gii \ Module.php
 protected function resetGlobalSettings() { if (Yii::$app instanceof \yii\web\Application) { Yii::$app->assetManager->bundles = []; } }
      
      







別のグリッドにコピーしないように、 ActionColumn



構成を別のクラスに配置できます。



app / Yii /ウィジェット/ ActionColumn.php
 namespace App\Yii\Widgets; use URL; use Route; class ActionColumn extends \yii\grid\ActionColumn { public $keyAttribute = 'id'; public $baseRoute = null; public $separator = '.'; /** * Overrides URL generation to use Laravel routing system * * @inheritdoc */ public function createUrl($action, $model, $key, $index) { if (is_callable($this->urlCreator)) { return call_user_func($this->urlCreator, $action, $model, $key, $index, $this); } else { if ($this->baseRoute === null) { $this->baseRoute = Route::getCurrentRoute()->getName(); } $baseRouteParts = explode($this->separator, $this->baseRoute); $baseRouteParts[count($baseRouteParts) - 1] = $action; $route = implode($this->separator, $baseRouteParts); $params = is_array($key) ? $key : [$this->keyAttribute => (string) $key]; return URL::route($route, $params, false); } } }
      
      







ActiveFormのラッパーを作成して、ウィジェットの呼び出しを配置し​​、モデルをコンストラクターに渡すことができます。 これにより、直接の<?php ?>



タグが削除され、モデルが各フィールドに渡されます。 Select2型のフィールドのサードパーティウィジェットを初期化するメソッドを追加することもできます。 このようなビルダーは、Yiiのプロジェクトでも使用できます。



app / Yii /ウィジェット/ FormBuilder.php
 namespace App\Yii\Widgets; use yii\widgets\ActiveForm; use yii\helpers\Html; class FormBuilder extends \yii\base\Component { protected $model; protected $form; public function __construct($model) { $this->model = $model; } public function getModel() { return $this->model; } public function setModel($model) { $this->model = $model; } public function getForm() { return $this->form; } public function open($params = ['successCssClass' => '']) { $this->form = ActiveForm::begin($params); } public function close() { ActiveForm::end(); } public function field($attribute, $options = []) { return $this->form->field($this->model, $attribute, $options); } public function submitButton($content, $options = ['class' => 'btn btn-primary']) { return Html::submitButton($content, $options); } }
      
      







リソース/ビュー/管理者/注文/ _form.blade.php
 {!! $form->open() !!} {!! $form->field('user_id')->dropDownList( \App\User::pluck('name', 'id'), ['prompt' => '']) !!} {!! $form->submitButton('Submit'); !!} {!! $form->close() !!}
      
      









ソースコード



ソースコードはこちらにあります 。 すべてのステップは別々のコミットによって行われます。 移行とテストデータがあります。



 php artisan migrate:refresh --seed
      
      





ラッパーはフォルダにありますapp/Yii







必須:



 App\Yii\Web\Request App\Yii\Data\EloquentDataProvider App\Yii\Data\FormModel
      
      





残りなくてもできますが、より便利です:



 App\Yii\Data\FilterModel App\Yii\Web\View App\Yii\Widgets\ActionColumn App\Yii\Widgets\FormBuilder
      
      





また、これは異なる実装を比較するための良い例だと思います。時間と希望があれば、コメントでこの管理パネルを別のテクノロジースタックに実装してください。



All Articles