环境说明
一般情况下,一个项目 应该 有以下三个基本的项目环境:
Local - 开发环境
Staging - 线上测试环境, develop 分支
Production - 线上生产环境, master 分支
开发专用扩展包
安装
安装开发专用扩展包时 必须 使用 --dev 参数,如:
composer require laracasts/generators --dev
加载
开发专用的 provider 绝不在 config/app.php 里面注册,必须在
app/Providers/AppServiceProvider.php 文件中使用如以下方式:
public function register()
{
if ($this->app->environment() == 'local') {
$this->app->register('Laracasts\Generators\GeneratorsServiceProvider');
}
}
配置信息与环境变量
在此统一规定:所有程序配置信息 必须 通过 config() 来读取,所有的 .env 配置信息 必须 通过 config() 来读取,绝不 在配置文件以外的范围使用 env()。
辅助函数
必须 把所有的『自定义辅助函数』存放于 bootstrap 文件夹中。
并在 bootstrap/app.php 文件的最顶部进行加载:
require __DIR__ . '/helpers.php';
...
代码风格
代码风格 必须 严格遵循 PSR-2 规范。所以使用代码格式化插件要配置为PSR-2
路由器
路由闭包
绝不 在路由配置文件里书写『闭包路由』或者其他业务逻辑代码,因为一旦使用将无法使用 路由缓存 。
路由器要保持干净整洁,绝不 放置除路由配置以外的其他程序逻辑。
Restful 路由
必须 优先使用 Restful 路由,配合资源控制器使用,见 文档。
Alt text
使用 resource 方法时,如果仅使用到部分路由,必须 使用 only 列出所有可用路由:
Route::resource('photos', 'PhotosController', ['only' => ['index', 'show']]);
使用 except,对于新增方法没有保护作用,而 only 相当于白名单,相对于 except 更加直观。路由使用白名单有利于养成『安全习惯』。
路由模型绑定
在允许使用路由 模型绑定 的地方 必须 使用。
模型绑定代码 必须 放置于 app/Providers/RouteServiceProvider.php 文件的 boot 方法中:
public function boot()
{
Route::bind('user_name', function ($value) {
return User::where('name', $value)->first();
});
Route::bind('photo', function ($value) {
return Photo::find($value);
});
parent::boot();
}
全局路由器参数
出于安全考虑,应该 使用全局路由器参数限制,详见 文档。
必须 在 RouteServiceProvider 文件的 boot 方法里定义模式:
/**
* 定义你的路由模型绑定,模式过滤器等。
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
public function boot(Router $router)
{
$router->pattern('id', '[0-9]+');
parent::boot($router);
}
模式一旦被定义,便会自动应用到所有使用该参数名称的路由上:
Route::get('users/{id}', 'UsersController@show');
Route::get('photos/{id}', 'PhotosController@show');
只有在 id 为数字时,才会路由到控制器方法中,否则 404 错误。
路由命名
除了 resource 资源路由以外,其他所有路由都 必须 使用 name 方法进行命名。
Route::post('users/{id}/follow', 'UsersController@follow')->name('users.follow');
数据模型
放置位置
所有的数据模型文件,都 必须 存放在:app/Models/ 文件夹中。
命名空间:
namespace App\Models;
忽略此块
User.php 初始化模型处理
Laravel 5.5 默认安装会把 User 模型存放在 app/User.php,必须 移动到 app/Models 文件夹中,并修改命名空间声明为 App/Models。
为了不破坏原有的逻辑点,必须 全局搜索 App/User 并替换为 App/Models/User。
使用基类
所有的 Eloquent 数据模型 都 必须 继承统一的基类 App/Models/Model,此基类存放位置为 /app/Models/Model.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 基类:
namespace App\Models;
class Photo extends Model
{
protected $fillable = ['id', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
命名规范
基本命名规范:
变量命名 必须 使用「Snake Case」写法,如:$user_id, $post_id
方法命名 必须 使用「驼峰」写法,并且首字母小写,如:getUserInfo
类命名 必须 使用「驼峰」写法,并且首字母大写,如:UserInfo
数据模型相关的命名规范:
数据模型类名 必须 为「单数」, 如:App\Models\Photo
数据库表名字 必须 为「单数」,多个单词情况下使用「Snake Case」 如:photo, my_photo
数据库字段名 必须 为「Snake Case」,如:view_count, is_vip
数据库表主键 必须 为「id」
数据模型变量 必须 为「resource_id」,如:$user_id, $post_id
利用 Trait 来扩展数据模型
有时候数据模型里的代码会变得很臃肿,应该 利用 Trait 来精简逻辑代码量,提高可读性
存放于文件夹:app/Models/Traits 文件夹中。
Repository
绝不 使用 Repository,因为我们不是在写 JAVA 代码,太多封装就成了「过度设计(Over Designed)」,极大降低了编码愉悦感,使用 MVC 够傻够简单。
PS: 那么MVC M 和 C 我们怎么定义呢?M* 写什么 C 写什么,对于一些公共的方法我们该写在哪里(Traits?)*
全局作用域
Laravel 的 Model 全局作用域 允许我们为给定模型的所有查询添加默认的条件约束。
所有的全局作用域都 必须 统一使用 闭包定义全局作用域,如下:
/**
* 数据模型的启动方法
*
* @return void
*/
protected static function boot()
{
parent::boot();
static::addGlobalScope('age', function(Builder $builder) {
$builder->where('age', '>', 200);
});
}
控制器
资源控制器
必须 使用资源的复数形式,如:
类名:PhotosController
文件名:PhotosController.php
错误的例子:
类名:PhotoController
文件名:PhotoController.php
保持短小精炼
必须 保持控制器文件代码行数最小化,还有可读性。
不应该 为「方法」书写注释,这要求方法取名要足够合理,不需要过多注释;
应该 为一些复杂的逻辑代码块书写注释,主要介绍产品逻辑 - 为什么要这么做;
不应该 在控制器中书写「私有方法」,控制器里 应该 只存放「路由动作方法」;
绝不 遗留「死方法」,就是没有用到的方法,控制器里的所有方法,都应该被使用到,否则应该删除;
绝不 在控制器里批量注释掉代码,无用的逻辑代码就必须清除掉。
表单验证
表单请求验证类
必须 使用 表单请求 - FormRequest 类 来处理控制器里的表单验证。
使用基类
创建 app/Http/Requests/Request.php 基类,所有表验证类 必须 继承此基类。
基类文件如下:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class Request extends FormRequest
{
public function authorize()
{
// Using policy for Authorization
return true;
}
}
表验证类文件参考:
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
];
}
}
授权策略
必须 使用 授权策略 类来做用户授权。
使用基类
所有授权策略类 必须 继承 app/Policies/Policy.php 基类。
基类文件如下:
namespace App\Policies;
use Illuminate\Auth\Access\HandlesAuthorization;
class Policy
{
use HandlesAuthorization;
public function __construct()
{
//
}
public function before($user, $ability)
{
if ($user->isAdmin()) {
return true;
}
}
}
授权策略类文件参考:
namespace App\Policies;
use App\Models\User;
use App\Models\Photo;
class PhotoPolicy extends Policy
{
public function update(User $user, Photo $photo)
{
return $user->isAuthorOf($photo);
}
public function destroy(User $user, Photo $photo)
{
return $user->isAuthorOf($photo);
}
}
Artisan 自定义命令行
必须 有项目的命名空间。
如:
php artisan phphub:clear-token
php artisan phphub:send-status-email
...
错误的例子为:
php artisan clear-token
php artisan send-status-email
...
Laravel 安全实践
SQL 注入
Laravel 的 查询构造器 和 Eloquent 是基于 PHP 的 PDO,PDO 使用 prepared 来准备查询语句,保障了安全性。
在使用 raw() 来编写复杂查询语句时,必须 使用数据绑定。
错误的做法:
$name = "admin"; // 假设用户提交
$password = "xx' OR 1='1"; // // 假设用户提交
$result = DB::select(DB::raw("SELECT * FROM users WHERE name ='$name'
and password = '$password'"));
dd($result);
以下是正确的做法,利用 select 方法 的第二个参数做数据绑定:
$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 提供白名单和黑名单过滤($fillable 和 $guarded):
举例,users 表里的 is_admin 字段是用来标识用户『是否是管理员』,某不怀好意的用户,更改了『修改个人资料』的表单,增加了一个字段
这个时候如果你更新代码如下:
Auth::user()->update(Request::all());
此用户将获取到管理员权限。可以有很多种方法来避免这种情况出现,最简单的方法是通过设置 User 模型里的 $guarded 字段来避免:
protected $guarded = ['id', 'is_admin'];
PS: 使用 Request::all() 方法时必须定义好黑名单($guarded)。
推荐阅读篇