专栏名称: 唤之
目录
相关文章推荐
OSC开源社区  ·  2024年AI编程工具的进化 ·  23 小时前  
OSC开源社区  ·  敢自称Java版PyTorch,EasyAi ... ·  23 小时前  
OSC开源社区  ·  如何公正评价百度开源的贡献? ·  2 天前  
程序员的那些事  ·  if微信+DeepSeek=王炸,百度+De ... ·  昨天  
码农翻身  ·  DeepSeek彻底爆了! ·  昨天  
51好读  ›  专栏  ›  唤之

[译]Laravel 的十八个最佳实践

唤之  · 掘金  · 程序员  · 2018-06-05 07:32

正文

这篇文章并不是什么由 Laravel 改编的 SOLID 原则、模式等。

只是为了让你注意你在现实生活的 Laravel 项目中最常忽略的内容。

单一责任原则

一个类和一个方法应该只有一个职责。错误的做法:

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 isVerfiedClient()
{
    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 模型或存储库类中。

坏:

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();
    }
}

验证

将验证从控制器移动到请求类。

很常见但不推荐的做法:

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');
        }
    }
}

不要重复你自己(DRY)

尽可能重用代码。 SRP(单一职责原则)正在帮助你避免重复。当然,这也包括了 Blade 模板、Eloquent 的范围等。

坏:

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 而不是 Query Builder 和原生的 SQL 查询。要优先于数组的集合

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->all());

不要在 Blade 模板中执行查询并使用关联加载(N + 1 问题)

不好的地方在于,这对于100 个用户来说,等于执行 101 个 DB 查询:

[@foreach](https://laravel-china.org/users/5651) (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

下面的做法,对于 100 个用户来说,仅仅只执行 2 个 DB 查询:

$users = User::with('profile')->get();

...

[[@foreach](https://laravel-china.org/users/5651)](https://laravel-china.org/users/5651) ($users as $user)
    {{ $user->profile->name }}
@endforeach

与其花尽心思给你的代码写注释,还不如对方法或变量写一个描述性的名称

坏:

if (count((array) $builder->getQuery()->joins) > 0)

好:

// 确定是否有任何连接。
if (count((array) $builder->getQuery()->joins) > 0)

最好:

if ($this->hasJoins())

不要把 JS 和 CSS 放在 Blade 模板中,也不要将任何 HTML 放在 PHP 类中

坏:

let article = `{{ json_encode($article) }}`;

好:

<input id="article" type="hidden" value="{{ json_encode($article) }}">

Or

<button class="js-fav-article" data-article="{{ json_encode($article) }}">{{ $article->name }}<button>

最好的方法是使用在 Javascript 中这样来传输数据:

let article = $('#article').val();

在代码中使用配置和语言文件、常量,而不是写死它

坏:

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 功能和社区软件包,而不是其他第三方软件包和工具。因为将来与你的应用程序一起工作的开发人员都需要学习新的工具。另外,使用第三方软件包或工具的话,如果遇到困难,从 Laravel 社区获得帮助的机会会大大降低。不要让你的客户为此付出代价!

| 任务 | 标准工具 | 第三方工具 |
| ----------------------- | -------------------------------------- | ------------------------------------------------------- |
| 授权 | Policies | Entrust, Sentinel and other packages |
| 前端编译 | Laravel Mix | Grunt, Gulp, 3rd party packages |
| 开发环境 | Homestead | Docker |
| 部署 | Laravel Forge | Deployer and other solutions |
| 单元测试 | PHPUnit, Mockery | Phpspec |
| 浏览器测试 | Laravel Dusk | Codeception |
| 数据库操作 | Eloquent | SQL, Doctrine |
| 模板 | Blade | Twig |
| 数据操作 | Laravel collections | Arrays |
| 表单验证 | Request classes | 3rd party packages, validation in controller |
| 认证 | Built-in | 3rd party packages, your own solution |
| API 认证 | Laravel Passport | 3rd party JWT and OAuth packages |
| 创建 API | Built-in | Dingo API and similar packages |
| 数据库结构操作 | Migrations | Working with DB structure directly |
| 局部化 | Built-in | 3rd party packages |
| 实时用户接口 | Laravel Echo, Pusher | 3rd party packages and working with WebSockets directly |
| Generating testing data | Seeder classes, Model Factories, Faker | Creating testing data manually |
| 生成测试数据 | Laravel Task Scheduler | Scripts and 3rd party packages |
| 数据库 | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |

遵循Laravel命名约定

遵循 PSR 标准 。 另外,请遵循 Laravel 社区接受的命名约定:

| 类型 | 规则 | 正确示例 | 错误示例 |
| -------------------------------- | ------------------------------------------------------------ | --------------------------------------- | --------------------------------------------------- |
| Controller | 单数 | ArticleController | ~~ArticlesController~~ |
| Route | 复数 | articles/1 | ~~article/1~~ |
| Named route | 带点符号的蛇形命名 | users.show_active | ~~users.show-active, show-active-users~~ |
| Model | 单数 | User | ~~Users~~ |
| hasOne or belongsTo relationship | 单数 | articleComment | ~~articleComments, article_comment~~ |
| All other relationships | 复数 | articleComments | ~~articleComment, article_comments~~ |
| Table | 复数 | article_comments | ~~article_comment, articleComments~~ |
| Pivot table | 按字母顺序排列的单数模型名称 | article_user | ~~user_article, articles_users~~ |
| Table column | 带着模型名称的蛇形命名 | meta_title | ~~MetaTitle; article_meta_title~~ |
| Foreign key | 带_id后缀的单数型号名称 | article_id | ~~ArticleId, id_article, articles_id~~ |
| Primary key | - | id | ~~custom_id~~ |
| Migration | - | 2017_01_01_000000_create_articles_table | ~~2017_01_01_000000_articles~~ |
| Method | 小驼峰命名 | getAll | ~~get_all~~ |
| Method in resource controller | 具体看表格 | store | ~~saveArticle~~ |
| Method in test class | 小驼峰命名 | testGuestCannotSeeArticle | ~~test_guest_cannot_see_article~~ |
| Variable | 小驼峰命名 | $articlesWithAuthor | ~~$articles_with_author~~ |
| Collection | 具描述性的复数形式 | $activeUsers = User::active()->get() | ~~$active, $data~~ |
| Object | 具描述性的单数形式 | $activeUser = User::active()->first() | ~~$users, $obj~~ |
| Config and language files index | 蛇形命名 | articles_enabled | ~~ArticlesEnabled; articles-enabled~~ |
| View | 蛇形命名 | show_filtered.blade.php | ~~showFiltered.blade.php, show-filtered.blade.php~~ |
| Config | 蛇形命名 | google_calendar.php | ~~googleCalendar.php, google-calendar.php~~ |







请到「今天看啥」查看全文


推荐文章
OSC开源社区  ·  2024年AI编程工具的进化
23 小时前
OSC开源社区  ·  敢自称Java版PyTorch,EasyAi到底有几斤几两?
23 小时前
OSC开源社区  ·  如何公正评价百度开源的贡献?
2 天前
程序员的那些事  ·  if微信+DeepSeek=王炸,百度+DeepSeek=???
昨天
码农翻身  ·  DeepSeek彻底爆了!
昨天
一条  ·  一个海难幸存者的故事
7 年前