Laravel 项目开发规范

一. 说明

以下内容大部分引用Laravel China社区的文章 - 分享下团队的开发规范 ——《Laravel 项目开发规范》
相对而言,上面引用的文章的规范更加严格,但考虑到目前的情况,会适当地对一些规范进行更改和增删。

二. 目的

暂无

三. 优点

规范有一下优点:

  • 高效编码 - 避免了过多的选择造成的『决策时间』浪费;
  • 风格统一 - 最大程度统一了开发团队成员代码书写风格和思路,代码阅读起来如出一辙;
  • 减少错误 - 减小初级工程师的犯错几率。

四. 开发哲学

  • DRY –「Don't Repeat Yourself」不写重复的逻辑代码;
  • 约定俗成 - 「Convention Over Configuration」,优先选择框架提倡的做法,不过度配置;
  • KISS - 「Keep it Simple, Stupid」提倡简单易读的代码,不写高深、晦涩难懂的代码,不过度设计
  • 主厨精选 - 让有经验的人来为你选择方案,不独创方案;
  • 官方提倡 - 优先选择官方推崇的方案。

五. 设计理念

以下是一些优秀的『程序设计理念』:

  • MVC - Model, View, Controller ,以 MVC 为核心,严格控制 Controller 的可读性和代码行数;
  • Restful - 利用『资源化概念』和标准的 HTTP 动词来组织你的程序;

在此规范中,我们会将使用这两套理念作为程序设计基础。这些设计理念为我们设计程序提供了依据,遵循这些理念,能让程序变得清晰易读。

六. 能愿动词

为了避免歧义,文档大量使用了「能愿动词」,对应的解释如下:

  • 必须(Must) - 只能这样子做,请无条件遵循,没有别的选项;
  • 绝不(Must Not)- 严令禁止,在任何情况下都不能这样做;
  • 应该(Should) - 强烈建议这样做,但是不强求;
  • 不应该(Should Not) - 强烈建议不这样做,但是不强求;
  • 可以(May) - 选择性高一点,在这个文档内,此词语使用较少;

七. 关于Laravel版本的选择

选择Laravel版本时,应该 优先选择LTS版本,除非有特殊原因,如生产服务器的PHP版本不是PHP7以上,而是PHP5.*,且为了稳定不升级到PHP7,那么 可以 考虑使用上一个版本的发行版。
比如Laravel 5.5是最新的LTS但是只支持PHP7以上,那么 可以 考虑使用Laravel 5.4。当然比较可以使用Laravel 5.1 LTS版本,但是该版本比较旧,没有新版本的一些新特性。

请使用以下命令来创建指定版本的 Laravel 项目:

composer create-project laravel/laravel project-name --prefer-dist "5.5.*" 

绝不 也禁止使用复制粘贴项目文件的方式来创建项目。

八. 环境说明

一般情况下,一个项目 应该 有以下三个基本的项目环境:

  • Local - 开发环境
  • Staging - 线上测试环境,对应git的test分支
  • Production - 线上生产环境,对应git的master分支

九. git分支

在创建git仓库后,建议最好分开三个分支

  • 主分支 - master,对应开发环境
  • 测试分支 - test,对应线上测试环境
  • 开发分支 - develop,对应线上生产环境

所有功能都是从develop分支新建分支,按功能模块命名

  • 新的功能模块,使用 features/功能名称 来命名

  • 修复bug,使用 fix/bug名称 来命名

  • 功能开发后,合并到develop

  • 开发测试通过后,将develop合并到test分支

  • 测试环境功能测试通过后,将test合并到master

十. 配置信息与环境变量

在 Laravel 中有以下几种方法:

  1. 硬代码,直接写死。- ❌ 可维护性低
  2. 写死在 config/app.php 文件中。 - ❌ 无法区分环境进行配置
  3. 存储于 .env 文件中,使用 env() 方法直接读取。 - ❌ 虽然解决了环境变量问题但是不推荐
  4. 存储在 .envconfig/app.php 文件中,然后使用 config() 函数来读取。- ✅ 最佳实践

第一种方法是最古老的方法,代码可维护性极低,一旦域名变更就只能全局替换。
第二种方法无法区分环境,例如本地使用开发环境域名测试,线上才是正式的域名。
第三种方法虽然解决了环境变量的问题,并且也具备一定的灵活性,但是不够灵活,假如你的网站流量巨大,需要配置几个域名,使其在加载静态资源时随机支配域名,这种做法就无法满足需求了。
第四种方法既支持环境变量,又具备极高的灵活性,假如遇到同样的多域名随机问题,你只需要写一个辅助方法,然后在 config/app.php 中调用即可,不需要动到任何一行业务逻辑代码。

代码示例

.env 文件中设置:

DOMAIN=018eighteen.test

config/app.php 文件中设置:

'domain' => env('DOMAIN', '018eighteen.com'),

程序中两种获取 相同配置 的方法:

  1. env('DOMAIN')
  2. config('app.domain')

在此统一规定:所有程序配置信息 必须 通过 config() 来读取,所有的 .env 配置信息 必须 通过 config() 来读取,绝不 在配置文件以外的范围使用 env()

这样做主要有以下几个优势:

  1. 定义分明,config() 是配置信息,env() 只是用来区分不同环境;
  2. 统一放置于 config 中还可以利用框架的 配置信息缓存功能 来提高运行效率;
  3. 代码健壮性, config()env() 之上多出来一个抽象层,会使代码更加健壮,更加灵活。

十一. 路由器

1. 路由闭包

绝不 在路由配置文件里书写『闭包路由』或者其他业务逻辑代码,因为一旦使用将无法使用 路由缓存
路由器要保持干净整洁,绝不 放置除路由配置以外的其他程序逻辑。

2. Restful 路由

必须 优先使用 Restful 路由,配合资源控制器使用,见 文档

超出 Restful 路由的,应该 模仿上图的方式来定义路由。

3. resource 方法正确使用

一般资源路由定义:

Route::resource('photos', 'PhotosController');

等于以下路由定义:

Route::get('/photos', 'PhotosController@index')->name('photos.index');
Route::get('/photos/create', 'PhotosController@create')->name('photos.create');
Route::post('/photos', 'PhotosController@store')->name('photos.store');
Route::get('/photos/{photo}', 'PhotosController@show')->name('photos.show');
Route::get('/photos/{photo}/edit', 'PhotosController@edit')->name('photos.edit');
Route::put('/photos/{photo}', 'PhotosController@update')->name('photos.update');
Route::delete('/photos/{photo}', 'PhotosController@destroy')->name('photos.destroy');

使用 resource 方法时,如果仅使用到部分路由,必须 使用 only 列出所有可用路由:

Route::resource('photos', 'PhotosController', ['only' => ['index', 'show']]);

绝不 使用 except,因为 only 相当于白名单,相对于 except 更加直观。路由使用白名单有利于养成『安全习惯』。

4. 单数 or 复数?

资源路由路由 URI 必须 使用复数形式,如:

  • /photos/create
  • /photos/{photo}

错误的例子如:

  • /photo/create
  • /photo/{photo}

5. 路由命名

除了 resource 资源路由以外,其他所有路由都 必须 使用 name 方法进行命名。
必须 使用『资源前缀』作为命名规范,如下的 users.follow,资源前缀的值是 users.

Route::post('users/{id}/follow', 'UsersController@follow')->name('users.follow');

6. 获取 URL

获取 URL 必须 遵循以下优先级:

  1. $model->link()
  2. route 方法
  3. url 方法

在 Model 中创建 link() 方法:

public function link($params = [])
{
    $params = array_merge([$this->id], $params);
    return route('models.show', $params);
}

所有单个模型数据链接使用:

$model->link();

// 或者添加参数
$model->link($params = ['source' => 'list'])

『单个模型 URI』经常会发生变化,这样做将会让程序更加灵活。

除了『单个模型 URI』,其他路由 必须 使用 route 来获取 URL(这也是目前使用次数最多的方法):

$url = route('profile', ['id' => 1]);

无法使用 route 的情况下,可以 使用 url 方法来获取 URL:

url('profile', [1]);

十二. 数据模型

1. 放置位置

所有的数据模型文件,都 必须 存放在:app/Models/ 文件夹中。

命名空间:

namespace App\Models;

2. 使用基类

所有的 Eloquent 数据模型必须 继承统一的基类 App/Models/Model,此基类存放位置为 /app/Models/Model.php,内容参考以下:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model as EloquentModel;

class Model extends EloquentModel
{
    public function scopeRecent($query)
    {
        return $query->orderBy('created_at', 'desc');
    }
}

以 Photo 数据模型作为例子继承 Model 基类:

<?php

namespace App\Models;

class Photo extends Model
{
    protected $fillable = ['id', 'user_id'];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}

3. 命名规范[#]

数据模型相关的命名规范:

  • 数据模型类名 必须 为「单数」, 如:App\Models\Photo
  • 类文件名 必须 为「单数」,如:app/Models/Photo.php
  • 数据库表名字 必须 为「复数」,多个单词情况下使用「Snake Case」 如:photos, my_photos (注:目前由于和其他团队合作开发,所以这一条规范暂时不硬性要求)
  • 数据库表迁移名字 必须 为「复数」,如:2014_08_08_234417_create_photos_table.php
  • 数据填充文件名 必须 为「复数」,如:PhotosTableSeeder.php
  • 数据库字段名 必须 为「Snake Case」,如:view_count, is_vip
  • 数据库表主键 必须 为「id」(注:这条规范一定要严格执行,避免像018server的prouct表一样出现product_id这样的主键)
  • 数据库表外键 必须 为「resource_id」,如:user_id, post_id
  • 数据模型变量 必须 为「resource_id」,如:$user_id, $post_id

4. 利用 Trait 来扩展数据模型

有时候数据模型里的代码会变得很臃肿,应该 利用 Trait 来精简逻辑代码量,提高可读性,类似于 Ruby China 源码

借鉴于 Rails 的设计理念:「Fat Models, Skinny Controllers」。

存放于文件夹:app/Models/Traits 文件夹中。

5. Repository

分享下团队的开发规范 ——《Laravel 项目开发规范》 提出不适用 Repository 模式进行开发,但是考虑到随着功能越来越多,不适用 Repository 会使得控制器越来越臃肿,有些代码也会不停地重复写,另外以后有可能需要编写单元测试,所以最后还是决定启用 Repository

具体参照为什么你应该使用 Repository

6. 关于 SQL 文件

  • 绝不 使用命令行或者 PHPMyAdmin 直接创建索引或表。必须 使用 数据库迁移 去创建表结构,并提交版本控制器中;
  • 绝不 为了共享对数据库更改就直接导出 SQL,所有修改都 必须 使用 数据库迁移 ,并提交版本控制器中;
  • 绝不 直接向数据库手动写入伪造的测试数据。必须 使用 数据填充 来插入假数据,并提交版本控制器中。

考虑到可能会和其他团队合作开发,所以具体还是根据团队的协定而定。但是如果是自己团队开发的话,必须严格按照以上标准。

十三. 控制器

1. 资源控制器

必须 优先使用 Restful 资源控制器

2. 单数 or 复数?

必须 使用资源的复数形式,如:

  • 类名:PhotosController
  • 文件名:PhotosController.php

错误的例子:

  • 类名:PhotoController
  • 文件名:PhotoController.php

3. 保持短小精炼

必须 保持控制器文件代码行数最小化,还有可读性。

  • 不应该 为「方法」书写注释,这要求方法取名要足够合理,不需要过多注释;
  • 应该 为一些复杂的逻辑代码块书写注释,主要介绍产品逻辑 - 为什么要这么做。
  • 不应该 在控制器中书写「私有方法」,控制器里 应该 只存放「路由动作方法」;
  • 绝不 遗留「死方法」,就是没有用到的方法,控制器里的所有方法,都应该被使用到,否则应该删除;
  • 绝不 在控制器里批量注释掉代码,无用的逻辑代码就必须清除掉。
  • 应该 创建Service层建立对应的Service类,以实现控制器对应的逻辑,见下放关于Service

十四. Service

在上面提过决定启用 RepositoryRepository主要是实现对model的增删改查。
Service 则是介于 ControllerRepository 之间,是对 Controller 业务逻辑的实现,实现过程中,通过 Repository 来对数据进行操作。

1. 创建 Service 文件夹

首先我们需要在 app 文件夹创建自己 Service 文件夹 services,然后文件夹的每一个文件都要设置相应的命名空间。

2. 创建对应的 Service

PostsController.php

<?php 
namespace App\Controllers;

use App\Http\Controllers\Controller;
use App\Services\PostsService;

class PostsController extend Controller{

    private $postsService;

    public function __construct (PostsService $posts) {
        $this->postsService = $posts;
    }
    
        public function addArticle (Request $request) {
            return $this->postsService->addArticle ($request->all());
        }
    
}

PostsService.php

<?php 
namespace App\Services;

class PostsService{
    
        public function addArticle ($data) {
            //To add a article...
        }
    
}

十五. 视图

在不进行前后端分离的情况下,请使用视图

1. 优先使用 Blade

视图文件 必须 优先考虑使用 .blade.php 后缀来指定使用 Blade 模板引擎。

2. 保持目录清晰

  • layouts - 页面布局文件 必须 放置于此目录下;
  • common - 存放页面通用元素;
  • pages - 简单的页面存放文件夹,如:about、contact 等;
  • resources - 对应 Restful 路由的资源路径名称,以 URI photos/create 为例,对应 create.blade.php 文件,存放在文件夹 photos 下。

必须 避免在 resources/views 目录下直接放置视图文件。

3. 局部视图

局部视图文件 必须 使用 _ 前缀来命名,如:photos/_upload_form.blade.php

4. 视图命名要释义

为了和 Restful 路由器和资源控制器保持一致,视图命名也 必须 使用资源视图的命名方式。以 photos 为例:

  • photos/index.blade.php
    • 内容列表视图
    • 对应路由器 /photos,命名 photos.index
    • 控制器方法 PhotosController@index
  • photos/show.blade.php
    • 单个内容视图
    • 对应路由器 /photos/{id},命名 photos.show
    • 控制器方法 PhotosController@show
  • photos/create.blade.php
    • 内容创建视图
    • 对应路由器 /photos/create,命名 photos.create
    • 控制器方法 PhotosController@create
  • photos/edit.blade.php
    • 内容编辑的视图
    • 对应路由器 /photos/edit,命名 photos.edit
    • 控制器方法 PhotosController@edit

5. create_and_edit 视图

很多情况下,创建和编辑视图里的页面结构接近相似,在这种情况下,应该 使用 create_and_edit 视图。以 photos 为例:

  • PhotosController@create - 对应视图:/photos/create_and_edit.blade.php
  • PhotosController@edit - 对应 视图:/photos/create_and_edit.blade.php

这样一来,通常情况下,一个完整的 photos 资源对应的视图文件为以下:

├── photos
│   ├── create_and_edit.blade.php
│   ├── index.blade.php
│   └── show.blade.php

十六. 表单验证

1. 表单请求验证类

必须 使用 表单请求 - FormRequest 类 来处理控制器里的表单验证。

2. 使用基类

所有 FormRequest 表验证类 必须 继承 app/Http/Requests/Request.php 基类。基类文件如下:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class Request extends FormRequest
{
    public function authorize()
    {
        // Using policy for Authorization
        return true;
    }
}

3. 验证类命名

FormRequest 表验证类 必须 遵循 资源路由 方式进行命名,photos 对应 app/Http/Requests/PhotoRequest.php

4. 类文件参考

FormRequest 表验证类文件请参考以下:

<?php

namespace App\Http\Requests;

class PhotoRequest extends Request
{
    public function rules()
    {
        switch($this->method())
        {
            // CREATE
            case 'POST':
            {
                return [
                    // CREATE ROLES
                ];
            }
            // UPDATE
            case 'PUT':
            case 'PATCH':
            {
                return [
                    // UPDATE ROLES
                ];
            }
            case 'GET':
            case 'DELETE':
            default:
            {
                return [];
            };
        }
    }

    public function messages()
    {
        return [
            // Validation messages
        ];
    }
}

十七. 数据填充

1. factory 辅助函数

必须 使用 factory 方法来做数据填充,因为是框架提倡的,并且可以同时为测试代码服务。

2. 运行效率

开发数据填充时,必须 特别注意 php artisan db:seed 的运行效率,否则随着项目的代码量越来越大,db:seed 的运行时间会变得越来越长,有些项目多达几分钟甚至几十分钟。

原则是:

Keep it lighting speed.

只有当 db:seed 运行起来很快的时候,才能完全利用数据填充工具带来的便利,而不是累赘。

4. 批量入库

所有假数据入库操作,都 必须 是批量操作,配合 factory 使用以下方法:

$users = factory(User::class)->times(1000)->make();
User::insert($users->toArray());

以上只执行一条数据库语句,推荐阅读 大批量假数据填充的正确方法

十八. Artisan 命令行

所有的自定义命令,都 必须 有项目的命名空间。

如:

php artisan phphub:clear-token
php artisan phphub:send-status-email
...

错误的例子为:

php artisan clear-token
php artisan send-status-email
...

十九. 日期和时间

必须 使用 Carbon 来处理日期和时间相关的操作。

Laravel 5.1 中文的 diffForHumans 可以使用 jenssegers/date

Laravel 5.3 及以上版本的 diffForHumans,只需要在 config/app.php 文件中配置 locale 选项即可 :

'locale' => 'zh-CN',

二十. 前端开发

根据 分享下团队的开发规范 ——《Laravel 项目开发规范》,规范里这么写的:

  • 必须 使用 Laravel 官方前端工具做前端开发自动化;
  • 必须 保证页面只加载一个 .css 文件;
  • 必须 保证页面只加载一个 .js 文件;
  • 必须.css.js 增加 版本控制
  • 必须 使用 SASS 来书写 CSS 代码;

但是考虑到目前团队的情况,在不前后端分离的情况下可以不执行以上规范。
如果实行前后端分离,则前端必须使用vue脚手架,并生成静态文件,增加版本控制。

二十一. Laravel 安全实践

1. 说明

没有绝对安全,只有相对安全。Laravel 相较于其他框架在安全方面已经做得很优秀,不过作为开发者,我们要在日常开发中对『安全』需怀着敬畏之心,积极培养自己的安全意识。以下是一些 Laravel 安全相关的规范。

2. 关闭 DEBUG

Laravel Debug 开启时,会暴露很多能被黑客利用的服务器信息,所以,生产环境下请 必须 确保:

APP_DEBUG=false

3. XSS

跨站脚本攻击(cross-site scripting,简称 XSS),具体危害体现在黑客能控制你网站页面,包括使用 JS 盗取 Cookie 等,关于 XSS 的介绍请前往 IBM 文档库:跨站点脚本攻击深入解析

默认情况下,在无法保证用户提交内容是 100% 安全的情况下,必须 使用 Blade 模板引擎的 {{ $content }} 语法会对用户内容进行转义。

Blade 的 {!! $content !!} 语法会直接对内容进行 非转义 输出,使用此语法时,必须 使用 HTMLPurifier for Laravel 5 来为用户输入内容进行过滤。使用方法参见: 使用 HTMLPurifier 来解决 Laravel 5 中的 XSS 跨站脚本攻击安全问题

4. SQL 注入

Laravel 的 查询构造器Eloquent 是基于 PHP 的 PDO,PDO 使用 prepared 来准备查询语句,保障了安全性。

在使用 raw() 来编写复杂查询语句时,必须 使用数据绑定。

错误的做法:

Route::get('sql-injection', function() {
    $name = "admin"; // 假设用户提交
    $password = "xx' OR 1='1"; // // 假设用户提交
    $result = DB::select(DB::raw("SELECT * FROM users WHERE name ='$name' and password = '$password'"));
    dd($result);
});

以下是正确的做法,利用 select 方法 的第二个参数做数据绑定:

Route::get('sql-injection', function() {
    $name = "admin"; // 假设用户提交
    $password = "xx' OR 1='1"; // // 假设用户提交
    $result = DB::select(
        DB::raw("SELECT * FROM users WHERE name =:name and password = :password"),
        [
            'name' => $name,
            'password' => $password,
        ]
    );
    dd($result);
});

DB 类里的大部分执行 SQL 的函数都可传参第二个参数 $bindings ,详见:API 文档

(注:建议最好直接使用Laravel的Eloquent ORM来对数据库进行操作)

5. 批量赋值

Laravel 提供白名单和黑名单过滤($fillable$guarded),开发者 应该 清楚认识批量赋值安全威胁的情况下合理灵活地运用。

批量赋值安全威胁,指的是用户可更新本来不应有权限更新的字段。举例,users 表里的 is_admin 字段是用来标识用户『是否是管理员』,某不怀好意的用户,更改了『修改个人资料』的表单,增加了一个字段:

<input name="is_admin" value="1" />

这个时候如果你更新代码如下:

Auth::user()->update(Request::all());

此用户将获取到管理员权限。可以有很多种方法来避免这种情况出现,最简单的方法是通过设置 User 模型里的 $guarded 字段来避免:

protected $guarded = ['id', 'is_admin'];

6. CSRF

CSRF 跨站请求伪造是 Web 应用中最常见的安全威胁之一,具体请见 Wiki - 跨站请求伪造 或者 Web 应用程序常见漏洞 CSRF 的入侵检测与防范

Laravel 默认对所有『非幂等的请求』强制使用 VerifyCsrfToken 中间件防护,需要开发者做的,是区分清楚什么时候该使用『非幂等的请求』。

幂等请求指的是:'HEAD', 'GET', 'OPTIONS',既无论你执行多少次重复的操作都不会给资源造成变更。

  • 所有删除的动作,必须 使用 DELETE 作为请求方法;
  • 所有对数据更新的动作,必须 使用 POST、PUT 或者 PATCH 请求方法。

Laravel 自动为每一个被应用管理的有效用户会话生成一个 CSRF “令牌”,该令牌用于验证授权用户和发起请求者是否是同一个人。想要生成包含 CSRF 令牌的隐藏输入字段,可以使用帮助函数 csrf_field 来实现:

<?php echo csrf_field(); ?>

辅助函数 csrf_field 会生成如下 HTML:

<input type="hidden" name="_token" value="<?php echo csrf_token(); ?>">

当然还可以使用 Blade 模板引擎提供的方式:

{!! csrf_field() !!}

你不需要自己编写代码去验证 POST、PUT 或者 DELETE 请求的 CSRF 令牌,因为 Laravel 自带的 HTTP 中间件 VerifyCsrfToken 会为我们做这项工作:将请求中输入的 token 值和 Session 中的存储的 token 作对比来进行验证。

X-CSRF-Token
除了将 CSRF 令牌作为 POST 参数进行验证外,还可以通过设置 X-CSRF-Token 请求头来实现验证,VerifyCsrfToken 中间件会检查 X-CSRF-TOKEN 请求头,首先创建一个 meta 标签并将令牌保存到该 meta 标签:

<meta name="csrf-token" content="{{ csrf_token() }}">

然后在 js 库(如 jQuery)中添加该令牌到所有请求头,这为基于 AJAX 的应用提供了简单、方便的方式来避免 CSRF 攻击:

$.ajaxSetup({
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
});

二十三. Laravel 程序优化

1. 配置信息缓存

生产环境中的 应该 使用『配置信息缓存』来加速 Laravel 配置信息的读取。

使用以下 Artisan 自带命令,把 config 文件夹里所有配置信息合并到一个文件里,减少运行时文件的载入数量:

php artisan config:cache

缓存文件存放在 bootstrap/cache/ 文件夹中。

可以使用以下命令来取消配置信息缓存:

php artisan config:clear

注意:配置信息缓存不会随着更新而自动重载,所以,开发时候建议关闭配置信息缓存,一般在生产环境中使用。可以配合 Envoy 任务运行器 使用,在每次上线代码时执行 config:clear 命令。

2. 路由缓存

生产环境中的 应该 使用『路由缓存』来加速 Laravel 的路由注册。

路由缓存可以有效的提高路由器的注册效率,在大型应用程序中效果越加明显,可以使用以下命令:

php artisan route:cache

缓存文件存放在 bootstrap/cache/ 文件夹中。另外,路由缓存不支持路由匿名函数编写逻辑,详见:文档 - 路由缓存

可以使用下面命令清除路由缓存:

php artisan route:clear

注意:路由缓存不会随着更新而自动重载,所以,开发时候建议关闭路由缓存,一般在生产环境中使用。可以配合 Envoy 任务运行器 使用,在每次上线代码时执行 route:clear 命令。

3. 类映射加载优化

optimize 命令把常用加载的类合并到一个文件里,通过减少文件的加载,来提高运行效率。生产环境中的 应该使用 optimize 命令来优化类的加载速度:

php artisan optimize --force

以上命令会在 bootstrap/cache/ 文件夹中生成缓存文件。你可以可以通过修改 config/compile.php 文件来添加要合并的类。在 production 环境中,参数 --force 不需要指定,文件就会自动生成。

要清除类映射加载优化,请运行以下命令:

php artisan clear-compiled

此命令会删除上面 optimize 生成的两个文件。

注意:此命令要运行在 php artisan config:cache 后,因为 optimize 命令是根据配置信息(如:config/app.php 文件的 providers 数组)来生成文件的。

4. 自动加载优化

此命令不止针对于 Laravel 程序,适用于所有使用 composer 来构建的程序。此命令会把 PSR-0PSR-4转换为一个类映射表,来提高类的加载速度。

composer dumpautoload -o

注意:php artisan optimize --force 命令里已经做了这个操作。

5. 使用 Memcached 来存储会话

每一个 Laravel 的请求,都会产生会话,修改会话的存储方式能有效提高程序效率。会话的配置文件是 config/session.php。生产环境中的 必须 使用 Memcached 或者 Redis 等专业的缓存软件来存储会话,应该 优先选择 Memcached(注:为了服务器方便管理,也可以用redis):

'driver' => 'memcached',

6. 使用专业缓存驱动器

「缓存」是提高应用程序运行效率的法宝之一,Laravel 默认缓存驱动是 file 文件缓存,生产环境中的 必须 使用专业的缓存系统,如 Redis 或者 Memcached。应该 优先考虑 Redis。应该 避免使用数据库缓存。

'default' => 'redis',

7. 数据库请求优化

关联模型数据读取时 必须 使用 延迟预加载预加载

临近上线时 必须 使用 Laravel Debugbar 或者 Clockwork 留意每一个页面的总 SQL 请求条数,进行数据库请求调优。

8. 为数据集书写缓存逻辑

应该 合理的使用 Laravel 提供的缓存层操作,把从数据库里面拿出来的数据集合进行缓存,减少数据库的压力,运行在内存上的专业缓存软件对数据的读取也远远快于数据库。

$hot_posts = Cache::remember('posts.hot_posts', $minutes = 30, function()
{
    return Post::getHotPosts();
});

remember 甚至连数据关联模型也都一并缓存了。

9. 使用即时编译器

可以 使用 OpCache 进行优化。OpCache 都能轻轻松松的让你的应用程序在不用做任何修改的情况下,直接提高 50% 或者更高的性能,PHPhub 之前做个一个实验,具体请见:使用 OpCache 提升 PHP 5.5+ 程序性能

二十四. 项目文档编写规范

1. 说明

每一个项目都 必须 包含一个 readme.md 文件,readme 里书写这个项目的简单信息。作用主要有两个,一个是团队新成员可从此文件中快速获悉项目大致情况,另一个是部署项目时可以作为参考。

2. 排版规范

文档页面排版 必须 遵循 中文文案排版指北 ,在此基础上:

  • 中文文档请使用全角标点符号;
  • 必须 遵循 Markdown 语法,勿让代码显示错乱;
  • 原文中的双引号(" ")请代换成中文的引号(『』符号怎么打出来见 这里)。
  • 所有的 「加亮」、「加粗」和「链接」都需要在左右保持一个空格。

3. 行文规范

readme.md 文档 应该 包含以下内容:

  • 「项目概述」- 介绍说明项目的一些情况,类似于简单的产品说明,简单的功能描述,项目相关链接等,500 字以内;
  • 「运行环境」- 运行环境说明,系统要求等信息;
  • 「开发环境部署/安装」- 一步一步引导说明,保证项目新成员能最快速的,没有歧义的部署好开发环境;
  • 「服务器架构说明」- 最好能有服务器架构图,从用户浏览器请求开始,包括后端缓存服务使用等都描述清楚(主要体现为软件的使用),配合「运行环境」区块内容,可作为线上环境部署的依据;
  • 「代码上线」- 介绍代码上线流程,需要执行哪些步骤;
  • 「扩展包说明」- 表格列出所有使用的扩展包,还有在哪些业务逻辑或者用例中使用了此扩展包;
  • 「自定义 Artisan 命令列表」- 以表格形式罗列出所有自定义的命令,说明用途,指出调用场景;
  • 「队列列表」- 以表格形式罗列出项目所有队列接口,说明用途,指出调用场景。

范例见 附录:readme-example.md

一些额外补充

1. 一个方法做一件事情

一个方法做一件事情,尽量为每一块逻辑用一个方法写起来,不要把全部逻辑放在同一个方法,不然很难维护,别人阅读起来也很吃力。

  • 错误的例子:
    PostsController.php
<?php 
namespace App\Controllers;

use App\Http\Controllers\Controller;

class PostsController extend Controller {

    public function doSomething (Request $request) {
        //检查数据
        //...

        //查询文章是否存在
        $article = Article::find($request->input('id'));
        if (!$article) {
            //...
        }

        //添加文章记录
        //...

        //添加用户文章发布日志记录
        //...
    }    

}
  • 正确的例子:
    PostsController.php
<?php 
namespace App\Controllers;

use App\Http\Controllers\Controller;
use App\Services\PostsService;

class PostsController extend Controller {

    private $postsService;

    public function __construct (PostsService $posts) {
        $this->postsService = $posts;
    }

    /**
     * 添加文章
     */
    public function doSomething (Request $request) {
        try {
            $this->postsService->addArticle($request->input('user_id'), $request->input('data'));

            return response()->json([...]);
        } catch (\Exception $e) {
            //捕捉抛出的异常处理
            //...
        }
    }

}

PostsService.php

<?php
namespace App\Services;

class PostsService {

    /**
     * 添加文章
     */
    public function addArticle ($userId, $data) {
        //检查数据
        $check = $this->checkForAddArticle($userId, $data);

        //添加文章记录
        $article = $this->doAddArticle($userId, $data);

        //添加用户文章发布日志记录
        $log = $this->addUserPublishLog($userId, $article);

        //...
    }

    /**
     * 检查数据
     */
    private function checkForAddArticle ($userId, $data) {
        //检查失败,抛出异常,由控制器catch
        //...
    }

    /**
     * 添加文章记录
     */
    private function doAddArticle($userId, $data) {
        //...
    }

    /**
     * 添加用户文章发布日志记录
     */
    private function addUserPublishLog($userId, $article) {
        //...
    }

}

2. 尽量要多做注释说明

尽量多做注释,这样别人也能看得懂,自己需要维护后者修复bug的时候,也比较好找

3. 将常用数值写入新建的配置文件中

一般数据库会有一些状态位,如 orders 表有 order_status 字段用来记录订单状态

`order_status` tinyint(2) NOT NULL DEFAULT '0' COMMENT '订单状态: 0未付款 1已支付  2待配送 3派送中 4座位使用中 5已完成 6已取消 7超时未付款 8待退款 9已退款'

那么可以将这些值写入一个新建的配置文件,如 config/params.php

<?php

return [
    'order_status' => [
        1 => 'unpaid',
        2 => 'paid',
        3 => 'wait_for_delivery',
        ...
        9 => 'refunded'
    ],

    //other config
];

然后通过用 config() 函数读取

4. 一个请求一个方法

不要用一个方法执行两种类型的请求,get和post分别用不同的方法,不要通过如下去写

if (!empty($_POST)) {}

5. 数据操作

不要直接执行原生sql,使用laravel的creat、insert、update、save等方法,防止sql注入并且可以过滤掉非法数据,最好使用Laravel的Eloquent ORM。不要使用以下写法:

\DB::select("SELECT * FROM users WHERE id = 1");
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容

  • 原文链接 必备品 文档:Documentation API:API Reference 视频:Laracasts ...
    layjoy阅读 8,605评论 0 121
  • 必备品文档:DocumentationAPI: API Reference视频:Laracasts速查表:Lara...
    ethanzhang阅读 5,739评论 0 68
  • 2015年的夏天 我从半年前就开始倒数这个夏天我在等 你和我说好的 再见面的约定我在等 我等了那么久的一句回答 以...
    DREAMER追梦人阅读 203评论 0 1
  • 北宋诗人苏轼 的《念奴娇·赤壁怀古》写道: 大江东去,浪淘尽,千古风流人物。 故垒西边,人道是,三国周郎赤壁。乱石...
    如歌的行板紫雪阅读 876评论 1 3
  • 这些年你很辛苦,但是你很坚强,你是我的母亲,也是我的骄傲。 这些年你受的辛苦和难处,我也都记得。 我也很爱你。 未...
    以诺爸爸阅读 146评论 0 0