crud插件
So far, we covered different aspects of OctoberCMS. This is a follow up article to discover how to use OctoberCMS for CRUD applications and take a detailed view at how to work with models, relations and controllers. Let’s get started.
到目前为止 ,我们介绍了OctoberCMS的不同方面。 这是一篇后续文章,旨在发现如何将OctoberCMS用于CRUD应用程序,并详细介绍如何使用模型,关系和控制器。 让我们开始吧。
要求 (Requirements)
I will assume you already know how to set up a working installation of OctoberCMS. If not, you can check out the introduction article, or read the installation section in the documentation.
我假设您已经知道如何设置OctoberCMS的有效安装。 如果没有,您可以查看介绍文章 ,或阅读文档中的安装部分。
我们正在建设 (What We’re Building)
We are going to build a project management plugin where you can add different users to teams and assign them to projects. You can check the final result on GitHub, and feel free to suggest edits or additions to the plugins.
我们将构建一个项目管理插件,您可以在其中将不同的用户添加到团队中并将他们分配给项目。 您可以在GitHub上查看最终结果,并随时建议对插件进行编辑或添加。
设置插件 (Setting up the Plugin)
Let’s start by using the create:plugin
scaffolding command to create the initial plugin structure, and define the Plugin::pluginDetails
method with our plugin details.
让我们从使用create:plugin
scaffolding命令创建初始插件结构开始,并使用我们的插件详细信息定义Plugin::pluginDetails
方法。
php artisan create:plugin rafie.sitepointDemo
// Plugin.php
public function pluginDetails()
{
return [
'name' => 'Project management',
'description' => 'Manage your teams and projects.',
'author' => 'RAFIE Younes',
'icon' => 'icon-leaf'
];
}
创建数据库表 (Creating Database Tables)
Every team has a name, a list of users and projects.
每个团队都有一个名称,用户和项目列表。
php artisan create:model rafie.sitepointdemo Team
// models/team.php
class Team extends Model
{
// ...
public $table = 'rafie_sitepointDemo_teams';
public $hasMany = [
'projects' => '\Rafie\SitepointDemo\Projects',
'users' => '\Backend\Models\User'
];
// ...
}
// updates/create_teams_table.php
class CreateTeamsTable extends Migration
{
public function up()
{
Schema::create('rafie_sitepointDemo_teams', function($table)
{
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name', 100);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('rafie_sitepointDemo_teams');
}
}
Every project belongs to a team and has a name, a description and an end date.
每个项目都属于一个团队,并具有名称,描述和结束日期。
php artisan create:model rafie.sitepointdemo Project
// models/project.php
class Project extends Model
{
// ...
public $table = 'rafie_sitepointDemo_projects';
public $belongsTo = [
'team' => '\Rafie\SitepointDemo\Models\Team'
];
// ...
}
// updates/create_projects_table.php
class CreateProjectsTable extends Migration
{
public function up()
{
Schema::create('rafie_sitepointDemo_projects', function($table)
{
$table->engine = 'InnoDB';
$table->increments('id');
$table->string('name', 100);
$table->text('description');
$table->datetime('ends_at');
$table->integer('team_id')->unsigned();
$table->timestamps();
});
}
// ...
}
Because OctoberCMS already has a users table for the backend, we should add the team_id
column. We will create a migration to add our team index.
因为OctoberCMS已经有一个用于后端的用户表,所以我们应该添加team_id
列。 我们将创建一个迁移以添加我们的团队索引。
// updates/add_team_to_users.php
class AddTeamToUsers extends Migration
{
public function up()
{
if(!Schema::hasColumn('backend_users', 'team_id'))
{
Schema::table('backend_users', function($table)
{
$table->integer('team_id')->unsigned()->index()->nullable();
});
}
}
public function down()
{
if(Schema::hasColumn('backend_users', 'team_id'))
{
Schema::table('backend_users', function($table)
{
$table->dropColumn('team_id');
});
}
}
}
Then, we need to link to it with a new relationship definition.
然后,我们需要使用新的关系定义链接到它。
// Plugin.php
class Plugin extends PluginBase
{
// ...
public function boot()
{
User::extend(function($model){
$model->belongsTo['team'] = ['Rafie\SitepointDemo\Models\Team'];
});
}
}
Our plugin version file looks like this:
我们的插件版本文件如下所示:
// updates/version.yaml
1.0.1:
- First version of Sitepoint demo
- add_team_to_users.php
- create_teams_table.php
- create_projects_table.php
管理团队 (Managing Teams)
Inside your models folder, you can see that every model class has a configuration folder which contains two files:
在您的models文件夹中,您可以看到每个模型类都有一个配置文件夹,其中包含两个文件:
columns.yaml
: Holds the table columns that you want to use when listing table records.columns.yaml
:保存列出表记录时要使用的表列。fields.yaml
: The same thing as columns but it’s used to configure forms for creating and updating records.fields.yaml
:与列相同,但用于配置用于创建和更新记录的表单。
To manage teams we need to create a controller to take action on certain events. We use the following scaffolding command.
为了管理团队,我们需要创建一个控制器来对某些事件采取行动。 我们使用以下脚手架命令。
php artisan create:controller rafie.sitepointDemo Teams
If you follow the naming conventions, the controller will automatically map to the model. If you check in your browser at the backend/rafie/sitepointDemo/teams/create
URL, you’ll see the new record form.
如果遵循命名约定,则控制器将自动映射到模型。 如果您在backend/rafie/sitepointDemo/teams/create
URL backend/rafie/sitepointDemo/teams/create
浏览器,则会看到新的记录表单。
Inside config_form.yaml
, you’ll see that the form
and modelClass
properties are mapped to our team model. The new team form only shows a disabled input for the ID. We can add other inputs using the fields.yaml
file inside our model.
在config_form.yaml
内部,您将看到form
和modelClass
属性映射到我们的团队模型。 新团队表单仅显示ID的禁用输入。 我们可以使用模型中的fields.yaml
文件添加其他输入。
// models/team/fields.yaml
fields:
name:
label: Name
type: text
required: true
users:
label: Users
type: checkboxlist
Every team has a name and a list of users. The name is a simple text value, while the users are listed using the checkboxlist
component. You can check the list of field types in the documentation.
每个团队都有一个名称和用户列表。 名称是一个简单的文本值,而用户是使用checkboxlist
组件列出的。 您可以在文档中检查字段类型的列表。
You have also the ability to use form widgets in the field types. Widgets are rich components like a WYSWYG editor, Color picker, Media Finder, etc. The only part left here is to create a Team::getUsersOptions
method to fill the users checkbox list.
您还可以在字段类型中使用表单窗口小部件 。 小部件是丰富的组件,例如WYSWYG编辑器,颜色选择器,媒体查找器等。这里剩下的唯一部分是创建Team::getUsersOptions
方法来填充用户复选框列表。
// models/team.php
class Team extends Model
{
// ...
public function getUsersOptions()
{
return \Backend\Models\User::lists('login', 'id');
}
}
You can see from the screenshot above that the fields marked as required
have an asterisk after the input name. However, this does not mean that the validation is handled for you. You still need to add validation inside your models.
您可以从上面的屏幕截图中看到,标记为required
字段的输入名称后面带有星号。 但是,这并不意味着将为您处理验证。 您仍然需要在模型中添加验证。
// models/Team.php
class Team extends Model
{
use \October\Rain\Database\Traits\Validation;
public $rules = [
'name' => 'required'
];
// ...
}
The fields in the configuration file are automatically mapped to the model if found. If not, we need to use the create_onSave
and update_onSave
methods to alter the saving strategy.
如果找到配置文件中的字段,则会自动将其映射到模型。 如果没有,我们需要使用create_onSave
和update_onSave
方法来更改保存策略。
// controllers/teams.php
class Teams extends Controller
{
// ...
public function create_onSave()
{
$inputs = post('Team');
// save team
$teamModel = new \Rafie\SitepointDemo\Models\Team;
$teamModel->name = $inputs['name'];
$teamModel->save();
// update users team_id
\Backend\Models\User::whereIn('id', $inputs['users'])
->update(['team_id' => $teamModel->id]);
\Flash::success("Team saved successfully");
return $this->makeRedirect('update', $teamModel);
}
}
The post
method is a helper function to avoid resolving the request object from the container. After saving the team model and updating the user relation we show a success message and create a redirect response using the FormController::makeRedirect
method.
post
方法是一个辅助函数,可避免从容器中解析请求对象。 保存团队模型并更新用户关系后,我们将显示成功消息并使用FormController::makeRedirect
方法创建重定向响应。
// controllers/teams.php
class Teams extends Controller
{
// ...
public function update_onSave($recordId)
{
$inputs = post('Team');
// update team
$teamModel = \Rafie\SitepointDemo\Models\Team::findOrFail($recordId);
$teamModel->name = $inputs['name'];
$teamModel->save();
\Backend\Models\User::where('team_id', $teamModel->id)
->update(['team_id' => 0]);
// update users team_id
\Backend\Models\User::whereIn('id', $inputs['users'])
->update(['team_id' => $teamModel->id]);
\Flash::success("Team updated successfully");
}
}
The update_onSave
method has one parameter containing the updated record ID. We update the team and the attached users accordingly. Another way to accomplish this is to make use of the FormController::update_onSave
method. It takes care of mapping form fields to the model and saving it.
update_onSave
方法具有一个包含更新的记录ID的参数。 我们会相应地更新团队和附加的用户。 完成此操作的另一种方法是使用FormController::update_onSave
方法。 它负责将表单字段映射到模型并将其保存。
// controllers/teams.php
class Teams extends Controller
{
// ...
public function update_onSave($recordId)
{
$inputs = post('Team');
\Backend\Models\User::where('team_id', $recordId)
->update(['team_id' => 0]);
// update users team_id
\Backend\Models\User::whereIn('id', $inputs['users'])
->update(['team_id' => $recordId]);
$this->asExtension('FormController')->update_onSave($recordId, $context);
}
}
The only part left is deleting the records. You may use the update_onDelete
method to reset the team_id
on the users table and then delete the team.
剩下的唯一部分是删除记录。 您可以使用update_onDelete
方法重置用户表上的team_id
,然后删除该团队。
// controllers/teams.php
class Teams extends Controller
{
// ...
public function update_onDelete($recordId)
{
$teamModel = \Rafie\SitepointDemo\Models\Team::findOrFail($recordId);
\Backend\Models\User::where('team_id', $teamModel->id)
->update(['team_id' => 0]);
$teamModel->delete();
\Flash::success("Team deleted successfully");
return $this->makeRedirect('delete', $teamModel);
}
}
Or you could just use formAfterDelete
to reset the team_id
.
或者,您可以只使用formAfterDelete
重置team_id
。
// controllers/teams.php
class Teams extends Controller
{
// ...
public function formAfterDelete($model)
{
\Backend\Models\User::where('team_id', $model->id)
->update(['team_id' => 0]);
}
}
If you noticed, the update form doesn’t automatically select the users attached to the team. We may have to do it manually using the formExtendFields
method inside the Teams
controller. The getContext
method returns whether the user is creating or updating the model.
如果您注意到了,更新表单不会自动选择团队中附属的用户。 我们可能必须使用Teams
控制器内的formExtendFields
方法手动进行操作。 getContext
方法返回用户是创建还是更新模型。
// controllers/teams.php
class Teams extends Controller
{
// ...
public function formExtendFields($form)
{
if( $form->getContext() === 'update')
{
$team = $form->model;
$userField = $form->getField('users');
$userField->value = $team->users->lists('id');
}
}
}
管理项目 (Managing Projects)
We follow the same steps for managing projects: we start by defining the form fields.
我们执行与管理项目相同的步骤:首先定义表单字段。
models/project/fields.yaml
fields:
name:
label: Name
type: text
required: true
description:
label: Description
type: textarea
required: true
ends_at:
label: Ends At
type: datepicker
required: true
team_id:
label: Team
type: dropdown
We define the list of teams to be displayed on the team dropdown.
我们定义要在团队下拉列表中显示的团队列表。
// models/project.php
class Project extends Model
{
// ...
public function getTeamIdOptions()
{
$teams = \Rafie\SitepointDemo\Models\Team::all(['id', 'name']);
$teamsOptions = [];
$teams->each(function($team) use (&$teamsOptions) {
$teamsOptions[$team->id] = $team->name;
});
return $teamsOptions;
}
}
Because all form fields are mapped to the model, we won’t have to hook into the saving process to update some relations. The create
, update
and delete
actions are handled automatically in this case.
由于所有表单字段均已映射到模型,因此我们无需参与保存过程即可更新某些关系。 在这种情况下,将自动处理create
, update
和delete
动作。
清单 (Listing)
OctoberCMS makes listing records very simple and extendable. You can show and hide columns, search, sort, filter, format column values, etc. Check the documentation for the full list of options.
OctoberCMS使清单记录非常简单且可扩展。 您可以显示和隐藏列,搜索,排序,过滤,格式化列值等。请查阅文档以获取选项的完整列表。
上市团队 (Listing Teams)
The controllers/teams/config_list.yaml
file contains our listing options. Every property has a comment describing its usage.
controllers/teams/config_list.yaml
文件包含我们的列表选项。 每个属性都有描述其用法的注释。
// controllers/teams/config_list.yaml
# Model List Column configuration
list: $/rafie/sitepointdemo/models/team/columns.yaml
# Model Class name
modelClass: Rafie\SitepointDemo\Models\Team
# List Title
title: Manage Teams
# Link URL for each record
recordUrl: rafie/sitepointdemo/teams/update/:id
# Message to display if the list is empty
noRecordsMessage: backend::lang.list.no_records
# Records to display per page
recordsPerPage: 20
# Displays the list column set up button
showSetup: true
# Displays the sorting link on each column
showSorting: true
//...
You can see that the list
property points to the columns.yaml
file, which defines the columns that should be displayed. The showSetup
option lets the user select what columns to show in the list.
您可以看到list
属性指向columns.yaml
文件,该文件定义了应显示的列。 使用showSetup
选项,用户可以选择要在列表中显示的列。
// models/team/columns.yaml
columns:
id:
label: ID
searchable: true
name:
label: Name
users:
label: Users
relation: users
select: login
searchable: false
The id
and name
properties are self-explanatory. The searchable
property is set to true
by default, that’s why we didn’t specify it on the name
property.
id
和name
属性是不言自明的。 默认将searchable
属性设置为true
,这就是为什么我们没有在name
属性上指定它的原因。
OctoberCMS has a relation
property which can be used to show values from related models. In this case, we select the login
attribute from the users
relation model. You can check the documentation for the full list of available column options.
OctoberCMS具有一个relation
属性,可用于显示相关模型的值。 在这种情况下,我们从users
关系模型中选择login
属性。 您可以查看文档以获取可用列选项的完整列表。
上市项目 (Listing Projects)
We follow the same steps for listing projects: we have a name, a description, an end date and a team.
我们采用相同的步骤列出项目:我们有一个名称,描述,结束日期和团队。
// models/project/columns.yaml
columns:
id:
label: ID
searchable: true
name:
label: Name
description:
label: Description
type: text
ends_at:
label: End At
type: datetime
team:
label: Team
relation: team
select: name
To help format the end date properly inside our list, we specify the column type as datetime
. This may throw an exception for you because you need to add the ends_at
attribute in your model inside the $dates
array. Check the documentation for the full list of available column types.
为了帮助在列表中正确设置结束日期的格式,我们将列类型指定为datetime
。 这可能会为您引发异常,因为您需要在$dates
数组内的模型中添加ends_at
属性。 查看文档以获取可用列类型的完整列表。
// models/project.php
class Project extends Model
{
// ...
protected $dates = ['ends_at'];
// ...
}
As for the team column, we specify the relation type and select the name attribute from the model. The final result looks like this.
对于团队列,我们指定关系类型并从模型中选择名称属性。 最终结果如下所示。
扩展清单 (Extending Lists)
If you want to alter the list behavior, you may override the index
method inside your controller and the index.htm
view. What we want to do now is truncate the description column. You can check the documentation for more details about extending the list behavior.
如果要更改列表行为,则可以在控制器和index.htm
视图中覆盖index
方法。 我们现在要做的是截断描述列。 您可以查看文档以获取有关扩展列表行为的更多详细信息。
// controllers/projects.php
class Projects extends Controller
{
// ...
public function listOverrideColumnValue($record, $columnName)
{
if( $columnName == "description" && strlen($record->description) > 20 )
{
$description = substr($record->description, 0, 20);
return "<span title='{$record->description}'>{$description}...</span>";
}
}
}
You may be thinking about extending the users listing to show their current team. We use the boot
method inside our plugin definition file to extend other plugins.
您可能正在考虑扩展用户列表以显示其当前团队。 我们在插件定义文件中使用boot
方法来扩展其他插件。
// Plugin.php
class Plugin extends PluginBase
{
// ...
public function boot()
{
// ...
\Backend\Controllers\Users::extendListColumns(function ($list) {
$list->addColumns([
'team' => [
'label' => 'Team',
'relation' => 'team',
'select' => 'name'
]
]);
});
}
}
筛选器 (Filters)
Filtering lists in OctoberCMS is easy. First, you reference your filter configuration file inside your config_list.yaml
file.
在OctoberCMS中过滤列表很容易。 首先,您在config_list.yaml
文件中引用过滤器配置文件。
// controllers/projects/config_list.yaml
// ...
filter: config_filter.yaml
// ...
Inside your config_filter.yaml
file, you define a list of scopes that you want to use.
在config_filter.yaml
文件中,您定义要使用的作用域列表。
// controllers/projects/config_filter.yaml
scopes:
team:
label: Team
modelClass: \Rafie\SitepointDemo\Models\Team
nameFrom: name
conditions: team_id = :filtered
Our scope is named team
and will list our available teams using the specified modelClass
and nameFrom
properties. The conditions will filter projects where the team_id
is equal to the selected teams; you may think of it as a raw SQL where statement. The screenshot below shows the list of projects taken by the Backend
team.
我们的范围命名为team
,并将使用指定的modelClass
和nameFrom
属性列出可用的团队。 条件将过滤team_id
等于所选团队的项目; 您可能会将其视为原始SQL where语句。 下面的屏幕快照显示了Backend
团队采取的项目列表。
OctoberCMS has two scope types. The first one is the group type, and it’s the one we used previously. The second is the checkbox type, which is used for boolean situations. We’ll use the latter to hide the past due projects from the list.
OctoberCMS具有两种范围类型。 第一个是组类型,这是我们之前使用的类型。 第二种是复选框类型,用于布尔情况。 我们将使用后者从列表中隐藏过期的项目。
// controllers/projects/config_filter.yaml
scopes:
// ...
hide_past_due:
label: Hide past due
type: checkbox
conditions: ends_at > now()
The only past due project in our list is the one with an id of 2
.
我们列表中唯一过期的项目是ID为2
。
权限 (Permissions)
We can’t talk about CRUD operations without covering permissions and how to guard your controllers. Combined with the simplicity of Laravel, OctoberCMS lets you define a list of permissions for your plugin and group them under a specific tab, then use them to guard your controllers.
如果不涉及权限以及如何保护您的控制器,我们不能谈论CRUD操作。 结合Laravel的简单性,OctumCMS使您可以定义插件的权限列表,并将其分组在特定选项卡下,然后使用它们来保护您的控制器。
// Plugin.php
class Plugin extends PluginBase
{
// ...
public function registerPermissions()
{
return [
'rafie.sitepointDemo.manage_teams' => [
'label' => 'Manage Teams',
'tab' => 'SitepointDemo'
],
'rafie.sitepointDemo.manage_projects' => [
'label' => 'Manage Projects',
'tab' => 'SitepointDemo'
]
];
}
}
If you visit the update or create user page and select the permissions tab, you’ll see the SitepointDemo
tab which holds the plugin permissions.
如果您访问更新或创建用户页面并选择权限选项卡,则将看到包含插件权限的SitepointDemo
选项卡。
Now, inside the projects and teams controllers you add the $requiredPermission
attribute.
现在,在项目和团队控制器内部,添加$requiredPermission
属性。
// controllers/teams.php
class Teams extends Controller
{
// ...
public $requiredPermissions = ['rafie.sitepointDemo.manage_teams'];
// ...
}
// controllers/projects.php
class Projects extends Controller
{
// ...
public $requiredPermissions = ['rafie.sitepointDemo.manage_projects'];
// ...
}
If you want to restrict access to a certain action inside a controller, you may use the User::hasAccess
and User::hasPermissions
methods. Check the documentation for more details about permissions.
如果要限制对控制器内某个动作的访问,则可以使用User::hasAccess
和User::hasPermissions
方法。 查看文档以获取有关权限的更多详细信息。
// controllers/teams.php
class Teams extends Controller
{
// ...
public function update()
{
if( !$this->user->hasPermissions(['rafie.sitepointDemo.update_teams']) )
{
// redirect Unauthorized 401
}
}
}
结论 (Conclusion)
Every CMS tries to make CRUD operations easier and more straightforward for newcomers, and I think that OctoberCMS achieved that goal successfully by making every aspect of it clear and extendable.
每个CMS都会尝试使CRUD操作对于新来者来说更加容易和直接,我认为OctoberCMS通过使它的各个方面都清晰可扩展而成功实现了该目标。
You can check the final result on GitHub and if you have any questions or opinions let me know in the comments!
您可以在GitHub上查看最终结果,如果有任何疑问或意见,请在评论中让我知道!
翻译自: https://www.sitepoint.com/octobercms-crud-building-a-teamproject-management-plugin/
crud插件