开发者指南

编写文档

非常欢迎您对Winter文档做出贡献。如果您想做出贡献,请遵循 Winter CMS 文档指南:

  1. 每个至少有一个 H2 标题的页面都应该有一个目录列表。 TOC 列表应该是 H1 标题之后的第一个元素。 TOC 应该有指向页面上所有 H2 标题的链接。
  2. TOC 下方应该有介绍性文字,即使有介绍部分。如果不是真的需要,您可能想要去掉简介部分。不要单独留下 TOC。
  3. 尝试仅使用 H2 和 H3 标头。
  4. 每个 H2 和 H3 标题都应该有一个链接定义为<a name="page-cycle-handlers"></a>
  5. 仅将 UL 标签用于 TOC 列表。
  6. 避免短,1 句话,段落。合并短段落并尝试更冗长一点。
  7. 避免在代码部分下方放置短的段落。将这些段落与代码块上方的文本合并。
  8. 使用内联code 与代码相关的所有内容的标签——变量名、函数名、语法示例等。
  9. 对于比一行长的代码示例,使用代码块(代码块开头的三个反引号后跟块的语言,最后三个反引号)。
  10. 使用strong 标记其他所有内容。
  11. 强烈鼓励与其他文档文章的交叉链接。没有必要在同一段落中添加指向同一文章的链接。
  12. 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 无处不在,除了以下:

  1. 数据库属性和关系应该使用snake_case
  2. 回发参数和 HTML 元素应该使用snake_case
  3. 语言键应该使用snake_case

PHP类命名

使用PascalCase 对于所有班级。应避免使用下划线,因为在 PHP 正式支持命名空间之前,由于历史上在类名中使用下划线进行伪命名空间,因此已知它们会导致自动加载问题。必要时可以使用数字(通常表示类与服务的特定版本交互,即Conman 对比Conman4) 但绝不能用在类名的开头。避免所有特殊字符。

如果您发现自己出于组织目的想要在类名中使用下划线,请不要这样做。请改用命名空间。

Don't:

Do:

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 目录。我们推荐使用许多类后缀和前缀。

  1. Manager
  2. Builder
  3. Writer
  4. Reader
  5. Handler
  6. Container
  7. Protocol
  8. Target
  9. Converter
  10. Controller
  11. View
  12. Factory
  13. Entity
  14. Engine
  15. Bag

不要命名瘫痪。是的,名称非常重要,但还不足以浪费大量时间。如果你不能在五分钟内想出一个好名字,那就继续吧。

事件命名

指定时事件名称.期限after 不在事件中使用,仅使用术语before 用来。例如:

  1. beforeSetAttribute - 这个事件来了before 任何默认逻辑。
  2. 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

班级指导

应以轻松的方式考虑这些要点:

  1. 在类中,属性和方法应声明为protected 有利于private 这样所有的类都可以作为基类。同样,static::someMethodOrConstantOrProperty 优先于self::someMethodOrConstantOrProperty, 再次使扩展类更容易。
  2. 如果属性包含单个值(不是数组),则使该属性public 而不是获取/设置方法。
  3. 如果一个属性包含一个集合(是一个数组),使该属性protected 用得到getProperties,getPropertysetProperty.
豫ICP备18041297号-2