📜 ⬆️ ⬇️

How to cross a hedgehog with a snake. We use GridView from Yii 2 in the project on Laravel

Recently there was an article about Yii, where Yii-specific components, in particular GridView and ActiveForm, and Laravel framework were discussed in comments. I thought why not.

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

What came out of it, read under the cut. It was necessary to write a couple of small straps and configure them in a certain way, but in general everything works, both the grid and the forms. Also there is a small review of existing analogs for Laravel.

What are the options


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

Primary requirements:
')
- layout Bootstrap
- automatic processing of sorting, pagination, form validation errors
- minimum of hand-written code
- customizability

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

Good grid does not depend on frameworks. For frameworks there are connectors. A rather cumbersome configuration. In addition, it seems that the displayed data is not screened.

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

A lot of boilerplate adds a global function, some kind of strange rendering method.

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

The form itself chooses routes for action, errors are taken from the session. But in general, close to what you need.

I do not like the approach of passing errors and input values ​​through the session. Through F5, the form is not re-sent; if updated by chance, all errors and values ​​are erased.

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

What is the point in the builder, if you open / close a group of tags, you must manually.

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

A good form-builder, almost a complete analogue of ActiveForm. You can define a repository for errors and entered values.

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

This option seems most appropriate. There are both grid and forms. Grid is quite good, but the problem with the forms.

- Actions view / create / edit hang on the same route, differ in get-parameter. Accordingly, the default URL for actions in the grid is the same.
- This is one form, just different display mode. This creates problems, if you need to create_at / updated_at show only for view. And your class for the field should be described for all 3 modes.
- Not very good code in the project

Integration


I will take steps, the final code can be found at the end of the article. Since the tables and full editing are often found in the administrative section, the example will be in the form of an admin for some site.

For reference, the laravel folder is 2.6 MB, the symfony folder is 4.6 MB, the yiisoft folder is 3.9 MB, the dependencies are Yii 5.6 MB.

Consider a simple application with orders and goods.

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; 


Create an Eloquent model and OrderController for the order section. Create a group of routes for the admin.

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'); }); 

Create a Bootstrap template with links to a CDN.

resources / views / layouts / 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> = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity = "sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va + PmSTsz / K68vbdEjh4u" crossorigin = "anonymous" <!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> 


We make middleware with initialization and connect to admin routes. Initialization looks like this.

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']); - it is better to disconnect, so as not to interfere, enough autoloaders Laravel. He searches for files through getAlias('@'...) and of course does not find it.
basePath is the root directory of the application, if improperly installed, there may be path errors. The runtime folder is created in the same directory.
assetManager.basePath, assetManager.baseUrl - the path and URL for publishing assets, the folder name is arbitrary.
assetManager.bundles - disable jQuery publishing, as it is connected separately in the main template.
request - we redefine the request component, in which we replace work with the CSRF token, the field name is the same as in the Laravel settings.
urlManager.enablePrettyUrl - must be enabled if you need additional modules such as Gii.
(new \yii\web\Application($config)) - Yii::$app = $this; is assigned in the constructor Yii::$app = $this;

The query component looks like this:

app/Yii/Web/Request.php

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

The token is controlled by Laravel, so the regeneration does not need to be processed.

Grid


Now you can try to run. Add the code for the order list.

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]); } 

resources / views / admin / order / 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 


You need to set the dataProvider.pagination.route and dataProvider.sort.route , otherwise it will call Yii::$app->controller->getRoute() , and the controller is null . Similar to ActionColumn , only there will be a check and an InvalidParamException . The URL is generated via \yii\web\UrlManager , but the result is the same as with Laravel routing. You can set the manager via dataProvider.pagination.urlManager , if needed.
The labels of the columns for now are auto-generated.
You also need to set some styles for sorting icons.

The grid is displayed, but since there are no front-end scripts, the Delete button does not work.

It is necessary to display the scripts that are in the \yii\web\View component. The methods renderHeadHtml(), renderBodyBeginHtml(), renderBodyEndHtml() are protected (it is not clear from whom, especially since all variables are public ). Oddly enough, there is a reason to use the antipattern “public morozov”. Or you can just copy them into the main template.

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(); } } 

In Yii, assets are registered in the endBody() function, and the entire rendering is wrapped in a buffer, in which the CDATA magic constants are then replaced with real assets. Emulation of this behavior is found in the initAssets() function. We will not replace anything, we just need to fill the properties of $this->js, $this->css and others.

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



resources / views / admin / order / 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> 


The Html::csrfMetaTags() call is needed, since the yii.js script takes a csrf-token from the HTML page.

ArrayDataProvider works, but you need to make an ActiveDataProvider analog in order to get only what you need from the database.

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']], ]), 


Tags and filters


You need to make a base model inherited from \yii\base\Model , which will return labels for columns and field rules for filtering. For this there is a filterModel parameter. Let's make it configurable through the constructor.

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; } } 


You can inherit and define a specialized model, and put everything there.

 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, ... ]; ... } 


There is a small problem that if filterModel is specified, but there are no fields for the filter, the string with empty cells is still displayed. In this case, you can manually label. Although it would be better if there was such a check in the component itself.

View


Here we do the same, the settings of the columns can be copied from the grid. We will make the goods in the order by a separate grid on the viewing page. Tags also keep auto-generated for now.

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]); } 

resources / views / admin / order / 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 



Create / Update


First you need to make a form model, a wrapper for Eloquent models, inherited from \yii\base\Model , so that the ActiveForm component can call the necessary methods.

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(); } } 


Now you can make editing.

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]); } 

resources / views / admin / order / _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() ?> 


Validation rules are specified in Yii style. If necessary, you can override the validate() method and call the Laravel validator there. In this example, we will not do this.

Blade does not allow variable declarations. And ActiveForm::begin() and displays the tags and returns the value. You can explicitly write the <?php ?> Tag, you can make a new tag through Blade::extend() , as advised here , you can make a wrapper for ActiveForm . For now, leave <?php ?> .

As with the filter, you can inherit from the FormModel and put all the ads there.

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', ]; } } 



Tags on the watch page


You can now use OrderForm to set labels in the app/Http/Controllers/Admin/OrderController.php .

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


Deletion


It's simple.

app/Http/Controllers/Admin/OrderController.php

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

Additions


You can connect Gii. In its pure form, it is not needed, but you can take form validation rules and field labels from the model generator so as not to generate them manually. Or you can write your generator.

 composer require yiisoft/yii2-gii --dev 

routes / 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') - the path to the files is determined via Yii::getAlias('@'...) , therefore the path '@App/Models/Order.php' will be checked for the App\Models\Order class .

setBaseUrl('/admin') - you need the Yii routing to process only the part after the '/ admin'.

With Yii::setAlias('@App') and ['Yii', 'autoload'] there is such a problem. If you do not disable the autoloader, then if the class or namespace is incorrectly named in the existing file, an error occurs that is incorrectly processed. It happens like this. It connects the file, but then does not find the class and throws an UnknownClassException exception. The Laravel autoloader is called, which checks the facades and aliases and also does not find anything. Then the Composer autoloader is called, which reattaches the file, and another 'Cannot declare class' error occurs ..., because the name is already in use. The application crashes with error 500 without writing to the log.

Gii will work, despite the fact that we have disabled jQuery, since it has its own display template, and therefore it resets the assets of the application.

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


You can ActionColumn configuration to a separate class so as not to copy to different grids.

app / Yii / Widgets / 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); } } } 


You can make a wrapper for ActiveForm, where to place the call to the widget, and pass the model to the constructor. This will remove the direct <?php ?> Tags and transfer the model to each field. You can also add additional methods to initialize third-party widgets of fields of type Select2. Such a builder can also be used in projects on Yii.

app / Yii / Widgets / 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); } } 


resources/views/admin/order/_form.blade.php
 {!! $form->open() !!} {!! $form->field('user_id')->dropDownList( \App\User::pluck('name', 'id'), ['prompt' => '']) !!} {!! $form->submitButton('Submit'); !!} {!! $form->close() !!} 



Source


. . .

 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 

, , . , .

Source: https://habr.com/ru/post/334542/


All Articles