开发者指南
编写文档
非常欢迎您对Winter文档做出贡献。如果您想做出贡献,请遵循 Winter CMS 文档指南:
- 每个至少有一个 H2 标题的页面都应该有一个目录列表。 TOC 列表应该是 H1 标题之后的第一个元素。 TOC 应该有指向页面上所有 H2 标题的链接。
- TOC 下方应该有介绍性文字,即使有介绍部分。如果不是真的需要,您可能想要去掉简介部分。不要单独留下 TOC。
- 尝试仅使用 H2 和 H3 标头。
- 每个 H2 和 H3 标题都应该有一个链接定义为
<a name="page-cycle-handlers"></a>
- 仅将 UL 标签用于 TOC 列表。
- 避免短,1 句话,段落。合并短段落并尝试更冗长一点。
- 避免在代码部分下方放置短的段落。将这些段落与代码块上方的文本合并。
- 使用内联
code
与代码相关的所有内容的标签——变量名、函数名、语法示例等。 - 对于比一行长的代码示例,使用代码块(代码块开头的三个反引号后跟块的语言,最后三个反引号)。
- 使用strong 标记其他所有内容。
- 强烈鼓励与其他文档文章的交叉链接。没有必要在同一段落中添加指向同一文章的链接。
- 见cms-pages.md 或者cms-themes.md 文件供您参考。
PSR 标准的例外情况
Winter 使用的 PSR 标准有一些例外。
控制器方法可以有一个下划线
PSR-2 声明方法必须在camelCase.但是,在后端控制器中,Winter 将在 AJAX 处理程序前加上动作名称以定义受控上下文。例如:
public function index()
{
// This is the index page (index action)
}
public function index_onDoSomething()
{
// AJAX handler only works on the index action
}
public function onDoSomethingElse()
{
// AJAX handler works globally for all actions
}
必须为这些情况授予例外。
与右花括号位于同一行的后续表达式
PSR-2 没有明确声明后续表达式应该与右大括号在同一行。
if ($expr1) {
// if body
}
elseif ($expr2) {
// elseif body
}
else {
// else body;
}
虽然 Winter CMS 代码库的某些部分可能会使用上述样式,但不建议这样做,遇到时应更改为以下样式。
if ($expr1) {
// if body
} elseif ($expr2) {
// elseif body
} else {
// else body;
}
为了可读性,在新行上放置多个条件是完全可以接受的,但是:
if (
$expr1 &&
$longExpression &&
$superLongExpressionTooMuchToHandleOhNoWontSomeoneThinkOfTheColumnCount
) {
// if body
} elseif (
$seriously ||
$thisIsSuperLongAndContrived ||
$butThisIsBetterThanSuperLongOneLiners
) {
// elseif body
} else {
// else body;
}
这是基于技术性的可接受的偏好,PSR-1 和 PSR-2 在这种情况下使用 SHOULD、MUST 等时并不明确。
此外,if
非常不鼓励由于只有一行代码而没有花括号的语句:
if ($condition)
doSomething();
您应该始终在表达式中使用完整的花括号:
if ($condition) {
doSomething();
}
使用尾随逗号
尽管没有以一种或另一种方式指定PSR-2, Winter CMS 强烈建议在多行数组中使用尾随逗号,尤其是对于本地化文件。尾随逗号可以更轻松地对多行数组项执行维护,而不会在差异或版本控制历史记录中造成不必要的视觉混乱。
Recommended:
$items = [
'apple',
'banana',
'orange',
];
NOT 受到推崇的:
$items = [
'apple',
'banana',
'orange'
];
开发者标准和模式
本节介绍了一些我们强烈建议所有人遵循的标准,尤其是当您要在 Marketplace 上发布产品时。
供应商命名
命名空间中的供应商或作者代码必须以大写字符开头,并且不应包含下划线或破折号。这些是有效名称的示例:
Acme.Blog
WinterStorm.User
Happygilmore.Golf
这些是名称的示例not 有效的:
acme.blog
winterStorm.user
Happy_gilmore.Golf
存储库命名
将作品发布到存储库(例如 Git)时,请使用以下命名约定。插件应该命名为-plugin
后缀和可选wn-
字首。
blog-plugin
wn-blog-plugin
主题应命名为-theme
后缀和可选wn-
字首。
happy-theme
wn-happy-theme
项目可以命名为wn-
表示项目类型的前缀和后缀(即-site
,-app
等),尽管这只是一个建议的约定,并没有在任何地方强制执行。
wn-wintercms.com-site
wn-api.wintercms.com-app
PHP变量命名
使用camelCase 无处不在,除了以下:
- 数据库属性和关系应该使用snake_case
- 回发参数和 HTML 元素应该使用snake_case
- 语言键应该使用snake_case
PHP类命名
使用PascalCase 对于所有班级。应避免使用下划线,因为在 PHP 正式支持命名空间之前,由于历史上在类名中使用下划线进行伪命名空间,因此已知它们会导致自动加载问题。必要时可以使用数字(通常表示类与服务的特定版本交互,即Conman
对比Conman4
) 但绝不能用在类名的开头。避免所有特殊字符。
如果您发现自己出于组织目的想要在类名中使用下划线,请不要这样做。请改用命名空间。
Don't:
-
jet
-
myJet
-
my_jet
-
API_Jet
Do:
-
Jet
-
MyJet
-
API\Jet
HTML 元素命名
表单元素名称应该使用 snake_case(下划线)
<input name="first_name">
如果名称是数组,则数组键可以是 StudlyCase 或 snake_case。
<input name="ForumMember[first_name]">
<input name="forum_member[first_name]">
元素 ID 应采用驼峰式或连字符式(破折号)
<div id="firstNameGroup">
<input id="firstName">
</div>
<div id="first-name-group">
<input id="first-name">
</div>
元素类名称应使用连字符(破折号)
<div class="form-group">
<input class="form-control">
</div>
查看文件命名
部分视图应以下划线字符开头。而控制器和布局视图不以下划线字符开头。由于视图通常位于单个文件夹中,因此可以使用下划线 (_) 和破折号 (-) 字符来组织文件。破折号用作空格字符的替代品。下划线用于替代斜杠字符(文件夹或名称空间)。
index_fancy-layout.htm <== Index\Fancy layout
form-with-sidebar.htm <== Form with sidebar
_field-container.htm <== Field container (partial)
_field_baloon-selector.htm <== Field\Baloon Selector (partial)
查看文件必须以.htm
文件扩展名。
类命名
类通常放在classes
目录。我们推荐使用许多类后缀和前缀。
- Manager
- Builder
- Writer
- Reader
- Handler
- Container
- Protocol
- Target
- Converter
- Controller
- View
- Factory
- Entity
- Engine
- Bag
不要命名瘫痪。是的,名称非常重要,但还不足以浪费大量时间。如果你不能在五分钟内想出一个好名字,那就继续吧。
事件命名
指定时事件名称.期限after 不在事件中使用,仅使用术语before 用来。例如:
- beforeSetAttribute - 这个事件来了before 任何默认逻辑。
- setAttribute - 这个事件来了after 任何默认逻辑。
NOTE: 绝大多数情况下都是如此,但是默认模型事件的事件如
boot
,create
,delete
,fetch
,restore
,save
,update
, &validate
都有前后变体来匹配模型方法事件。
在可能的情况下,事件应涵盖全球和本地版本。全局事件应以模块或插件名称为前缀。例如:
// For global events, it is prefixed with the module or plugin code
Event::fire('cms.page.end');
// For local events, the prefix is not required
$this->fireEvent('page.end');
避免使用诸如onSomething 在事件名称中,因为这个词bind/fire 代表这个动作词。
最好始终将调用对象作为第一个参数传递给全局事件,本地事件不需要这个。本地事件在停止时优先于全局事件,或者在处理时优先。
// Local event
if ($this->fireEvent('beforeAddContent', [$message, $view], true) === false) {
return;
}
// Global event
if (Event::fire('mailer.beforeAddContent', [$this, $message, $view], true) === false) {
return;
}
当期望多个结果时,很容易像这样组合数组:
// Combine local and global event results
$eventResults = array_merge(
$this->fireEvent('form.beforeRefresh', [$saveData]),
Event::fire('backend.form.beforeRefresh', [$this, $saveData])
);
数据库表命名
表名应以作者和插件名称为前缀。
acme_blog_xxx
布尔列名称应以前缀is_
is_activated
is_visible
这是因为模型属性可能会发生冲突,例如,public $visible;
在 Model 类中与具有相同名称的数据库列冲突。一些列名是例外,例如notify_user
.
如果您的插件扩展了属于其他插件的表,则添加的列名称应以作者和插件名称为前缀:
acme_blog_category_id
作者和插件名称的首字母缩略词也可以作为前缀:
ab_category_id
组件命名
组件类通常放在components
目录。组件的名称应代表其主要功能。
要显示记录列表,请使用List
后缀,例如:
ProductList
ProductReviewList
CategoryList
要显示单个记录,请使用Details
后缀,例如:
ProductDetails
CategoryDetails
使用后缀有助于避免与控制器和模型名称发生冲突。或者,您可以命名不带后缀的组件,用于名称具有描述性且不冲突的情况:
ProductReviews
CustomerCheckout
SeoDirectory
UserProfile
控制器命名
控制器通常放置在controllers
目录,用于后端控制器。控制器的名称应该是复数形式,例如:
People
Products
Categories
ProductCategories
模型命名
模型通常放置在models
目录。模型名称应为单数形式,例如:
Person
Product
Category
ProductCategory
当动态扩展其他插件的模型时,您应该至少在该字段前加上插件名称。如果更新该插件以添加可能与您的动态关系冲突的新关系,这有助于避免潜在的未来冲突。
User::extend(function($model) {
$model->hasOne['forum_member'] = ['Winter\Forum\Models\Member'];
});
完全限定的插件名称也是可以接受的,例如:
$user->winter_forum_member
模型范围
模型范围 应该总是返回作用域QueryBuilder
支持作用域链的实例。如果范围不返回QueryBuilder
instance 那么它不是一个范围,应该是一个常规/静态方法。
// Valid scope method
public function scopeWithValidUser(Builder $query, User $user)
{
return $query->where('user_id', $user->id);
}
// Invalid scope method
public function scopeWithValidUser(Builder $query, User $user)
{
return $query->where('user_id', $user->id)->get();
}
// Valid standalone method returning a collection
public function getValidUser(User $user)
{
return $this->withValidUser($user)->get();
}
范围命名可能很棘手,因为避免与模型 API 的其他方面(例如关系和属性)发生冲突很重要。一般来说,接收附加参数的作用域应该以apply
以表明它们正在应用于查询。定义为:
public function scopeApplyUser(Builder $query, User $user)
{
return $query->where('user_id', $user->id);
}
然后应用于模型:
$model->applyUser($user);
同时apply
是这些情况下的理想前缀名称,以下是我们为链式作用域推荐的其他一些前缀:
- is
- for
- with
- without
- filter
班级指导
应以轻松的方式考虑这些要点:
- 在类中,属性和方法应声明为
protected
有利于private
这样所有的类都可以作为基类。同样,static::someMethodOrConstantOrProperty
优先于self::someMethodOrConstantOrProperty
, 再次使扩展类更容易。 - 如果属性包含单个值(不是数组),则使该属性
public
而不是获取/设置方法。 - 如果一个属性包含一个集合(是一个数组),使该属性
protected
用得到getProperties
,getProperty
和setProperty
.