一个类和一个方法应该只有一个责任。
例如:
public function getFullNameAttribute()
{
    if (auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified()) {
        return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
    } else {
        return $this->first_name[0] . '. ' . $this->last_name;
    }
}
更优的写法:
public function getFullNameAttribute()
{
    return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort();
}
public function isVerifiedClient()
{
    return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified();
}
public function getFullNameLong()
{
    return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name;
}
public function getFullNameShort()
{
    return $this->first_name[0] . '. ' . $this->last_name;
}
如果您使用的是查询生成器或原始SQL查询,请将所有与数据库相关的逻辑放入Eloquent模型或Repository类中。
例如:
public function index()
{
    $clients = Client::verified()
        ->with(['orders' => function ($q) {
            $q->where('created_at', '>', Carbon::today()->subWeek());
        }])
        ->get();
    return view('index', ['clients' => $clients]);
}
更优的写法:
public function index()
{
    return view('index', ['clients' => $this->client->getWithNewOrders()]);
}
class Client extends Model
{
    public function getWithNewOrders()
    {
        return $this->verified()
            ->with(['orders' => function ($q) {
                $q->where('created_at', '>', Carbon::today()->subWeek());
            }])
            ->get();
    }
}
把验证规则放到 Request 类中.
例子:
public function store(Request $request)
{
    $request->validate([
        'title' => 'required|unique:posts|max:255',
        'body' => 'required',
        'publish_at' => 'nullable|date',
    ]);
    ....
}
更优的写法:
public function store(PostRequest $request)
{    
    ....
}
class PostRequest extends Request
{
    public function rules()
    {
        return [
            'title' => 'required|unique:posts|max:255',
            'body' => 'required',
            'publish_at' => 'nullable|date',
        ];
    }
}
控制器必须遵循单一职责原则,因此最好将业务代码从控制器移动到服务层中。
例子:
public function store(Request $request)
{
    if ($request->hasFile('image')) {
        $request->file('image')->move(public_path('images') . 'temp');
    }
    
    ....
}
更优的写法:
public function store(Request $request)
{
    $this->articleService->handleUploadedImage($request->file('image'));
    ....
}
class ArticleService
{
    public function handleUploadedImage($image)
    {
        if (!is_null($image)) {
            $image->move(public_path('images') . 'temp');
        }
    }
}
尽可能重用代码,SRP可以帮助您避免重复造轮子。 此外尽量重复使用Blade模板,使用Eloquent的 scopes 方法来实现代码。
例子:
public function getActive()
{
    return $this->where('verified', 1)->whereNotNull('deleted_at')->get();
}
public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->where('verified', 1)->whereNotNull('deleted_at');
        })->get();
}
更优的写法:
public function scopeActive($q)
{
    return $q->where('verified', 1)->whereNotNull('deleted_at');
}
public function getActive()
{
    return $this->active()->get();
}
public function getArticles()
{
    return $this->whereHas('user', function ($q) {
            $q->active();
        })->get();
}
使用Eloquent可以帮您编写可读和可维护的代码。 此外Eloquent还有非常优雅的内置工具,如软删除,事件,范围等。
例子:
SELECT *
FROM `articles`
WHERE EXISTS (SELECT *
              FROM `users`
              WHERE `articles`.`user_id` = `users`.`id`
              AND EXISTS (SELECT *
                          FROM `profiles`
                          WHERE `profiles`.`user_id` = `users`.`id`) 
              AND `users`.`deleted_at` IS NULL)
AND `verified` = '1'
AND `active` = '1'
ORDER BY `created_at` DESC
更优的写法:
Article::has('user.profile')->verified()->latest()->get();
例子:
$article = new Article;
$article->title = $request->title;
$article->content = $request->content;
$article->verified = $request->verified;
// Add category to article
$article->category_id = $category->id;
$article->save();
更优的写法:
$category->article()->create($request->validated());
例子 (对于100个用户,将执行101次DB查询):
@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach
更优的写法 (对于100个用户,使用以下写法只需执行2次DB查询):
$users = User::with('profile')->get();
...
@foreach ($users as $user)
    {{ $user->profile->name }}
@endforeach
例子:
if (count((array) $builder->getQuery()->joins) > 0)
加上注释:
// 确定是否有任何连接
if (count((array) $builder->getQuery()->joins) > 0)
更优的写法:
if ($this->hasJoins())
例子:
let article = `{{ json_encode($article) }}`;
更好的写法:
<input id="article" type="hidden" value='@json($article)'>
Or
<button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
在Javascript文件中加上:
let article = $('#article').val();
当然最好的办法还是使用专业的PHP的JS包传输数据。
例子:
public function isNormal()
{
    return $article->type === 'normal';
}
return back()->with('message', 'Your article has been added!');
更优的写法:
public function isNormal()
{
    return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
强力推荐使用内置的Laravel功能和扩展包,而不是使用第三方的扩展包和工具。 如果你的项目被其他开发人员接手了,他们将不得不重新学习这些第三方工具的使用教程。 此外,当您使用第三方扩展包或工具时,你很难从Laravel社区获得什么帮助。 不要让你的客户为额外的问题付钱。
| 想要实现的功能 | 标准工具 | 第三方工具 | 
|---|---|---|
| 权限 | Policies | Entrust, Sentinel 或者其他扩展包 | 
| 资源编译工具 | Laravel Mix | Grunt, Gulp, 或者其他第三方包 | 
| 开发环境 | Homestead | Docker | 
| 部署 | Laravel Forge | Deployer 或者其他解决方案 | 
| 自动化测试 | PHPUnit, Mockery | Phpspec | 
| 页面预览测试 | Laravel Dusk | Codeception | 
| DB操纵 | Eloquent | SQL, Doctrine | 
| 模板 | Blade | Twig | 
| 数据操纵 | Laravel集合 | 数组 | 
| 表单验证 | Request classes | 他第三方包,甚至在控制器中做验证 | 
| 权限 | Built-in | 他第三方包或者你自己解决 | 
| API身份验证 | Laravel Passport, Laravel Sanctum | 第三方的JWT或者 OAuth 扩展包 | 
| 创建 API | Built-in | Dingo API 或者类似的扩展包 | 
| 创建数据库结构 | Migrations | 直接用 DB 语句创建 | 
| 本土化 | Built-in | 第三方包 | 
| 实时消息队列 | Laravel Echo, Pusher | 使用第三方包或者直接使用WebSockets | 
| 创建测试数据 | Seeder classes, Model Factories, Faker | 手动创建测试数据 | 
| 任务调度 | Laravel Task Scheduler | 脚本和第三方包 | 
| 数据库 | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB | 
来源 PSR standards.
另外,遵循Laravel社区认可的命名约定:
| 对象 | 规则 | 更优的写法 | 应避免的写法 | 
|---|---|---|---|
| 控制器 | 单数 | ArticleController | ~~ArticlesController~~ | 
| 路由 | 复数 | articles/1 | ~~article/1~~ | 
| 路由命名 | 带点符号的蛇形命名 | users.show_active | ~~users.show-active, show-active-users~~ | 
| 模型 | 单数 | User | ~~Users~~ | 
| hasOne或belongsTo关系 | 单数 | articleComment | ~~articleComments, article_comment~~ | 
| 所有其他关系 | 复数 | articleComments | ~~articleComment, article_comments~~ | 
| 表单 | 复数 | article_comments | ~~article_comment, articleComments~~ | 
| 透视表 | 按字母顺序排列模型 | article_user | ~~user_article, articles_users~~ | 
| 数据表字段 | 使用蛇形并且不要带表名 | meta_title | ~~MetaTitle; article_meta_title~~ | 
| 模型参数 | 蛇形命名 | $model->created_at | ~~$model->createdAt~~ | 
| 外键 | 带有_id后缀的单数模型名称 | article_id | ~~ArticleId, id_article, articles_id~~ | 
| 主键 | - | id | ~~custom_id~~ | 
| 迁移 | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ | 
| 方法 | 驼峰命名 | getAll | ~~get_all~~ | 
| 资源控制器 | table | store | ~~saveArticle~~ | 
| 测试类 | 驼峰命名 | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ | 
| 变量 | 驼峰命名 | $articlesWithAuthor | ~~$articles_with_author~~ | 
| 集合 | 描述性的, 复数的 | $activeUsers = User::active()->get() | ~~$active, $data~~ | 
| 对象 | 描述性的, 单数的 | $activeUser = User::active()->first() | ~~$users, $obj~~ | 
| 配置和语言文件索引 | 蛇形命名 | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ | 
| 视图 | 短横线命名 | show-filtered.blade.php | ~~showFiltered.blade.php, show_filtered.blade.php~~ | 
| 配置 | 蛇形命名 | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ | 
| 内容 (interface) | 形容词或名词 | AuthenticationInterface | ~~Authenticatable, IAuthentication~~ | 
| Trait | 使用形容词 | Notifiable | ~~NotificationTrait~~ | 
例子:
$request->session()->get('cart');
$request->input('name');
更优的写法:
session('cart');
$request->name;
更多示例:
| 常规写法 | 更优雅的写法 | 
|---|---|
| Session::get('cart') | session('cart') | 
| $request->session()->get('cart') | session('cart') | 
| Session::put('cart', $data) | session(['cart' => $data]) | 
| $request->input('name'), Request::get('name') | $request->name, request('name') | 
| return Redirect::back() | return back() | 
| is_null($object->relation) ? null : $object->relation->id | optional($object->relation)->id | 
| return view('index')->with('title', $title)->with('client', $client) | return view('index', compact('title', 'client')) | 
| $request->has('value') ? $request->value : 'default'; | $request->get('value', 'default') | 
| Carbon::now(), Carbon::today() | now(), today() | 
| App::make('Class') | app('Class') | 
| ->where('column', '=', 1) | ->where('column', 1) | 
| ->orderBy('created_at', 'desc') | ->latest() | 
| ->orderBy('age', 'desc') | ->latest('age') | 
| ->orderBy('created_at', 'asc') | ->oldest() | 
| ->select('id', 'name')->get() | ->get(['id', 'name']) | 
| ->first()->name | ->value('name') | 
创建新的类会让类之间的更加耦合,使得测试越发复杂。请改用IoC容器或注入来实现。
例子:
$user = new User;
$user->create($request->validated());
更优的写法:
public function __construct(User $user)
{
    $this->user = $user;
}
....
$this->user->create($request->validated());
.env 文件里获取数据将数据传递给配置文件,然后使用config()帮助函数来调用数据
例子:
$apiKey = env('API_KEY');
更优的写法:
// config/api.php
'key' => env('API_KEY'),
// Use the data
$apiKey = config('api.key');
例子:
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}
更优的写法:
// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at'];
public function getSomeDateAttribute($date)
{
    return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
永远不要在路由文件中放任何的逻辑代码。
尽量不要在Blade模板中写原始 PHP 代码。