1. 前言

不少初学者在写代码时,不注重代码的功能单一性和可扩展性,用一大段代码实现一个功能,导致后续很难阅读和维护。那么,如何优雅地编写好的代码呢?最好的办法就是借鉴和学习别人优秀的写法,时间长了自然而然就能写出令人赏心悦目的代码。下面我们举几个例子来说明。

2. 代码示例

2.1 示例一

假设现在要实现一个垃圾内容检测器,来过滤垃圾回复、标题等内容,怎么实现比较好呢?

在 Laravel 中,我们可以使用 表单验证 来检查提交字段是否满足要求。

首先,我们检查非法关键字:

app/Inspections/InvalidKeywords.php

namespace App\Inspections;

use Exception;

class InvalidKeywords
{
    protected $keywords = [
        'something forbidden'
    ];

    public function detect($body)
    {
        foreach ($this->keywords as $invalidKeyword) {
            if (stripos($body, $invalidKeyword) !== false) {
                throw new Exception('Your reply contains spam.');
            }
        }
    }
}

检查重复输入字符的:

app/Inspections/KeyHeldDown.php

namespace App\Inspections;

use Exception;

class KeyHeldDown
{
    public function detect($body)
    {
        if (preg_match('/(.)\\1{4,}/', $body)) {
            throw new Exception('Your reply contains spam.');
        }
    }
}

统一调用:

app/Inspections/Spam.php

namespace App\Inspections;

class Spam
{
    protected $inspections = [
        InvalidKeywords::class,
        KeyHeldDown::class,
    ];

    public function detect($body)
    {
        foreach ($this->inspections as $inspection) {
            app($inspection)->detect($body);
        }
        return false;
    }
}

新建自定义验证规则:

app/Rules/SpamFree.php

.
.
.
    public function passes($attribute, $value)
    {
        try {
            return !app(\App\Inspections\Spam::class)->detect($value);
        } catch (\Exception $e) {
            return false;
        }
    }
.
.
.

然后注册自定义规则:

app/Providers/AppServiceProvider.php

.
.
.
    public function boot()
    {
        .
        .
        .

        \Validator::extend('spamfree', \App\Rules\SpamFree::class . '@passes');
    }
.
.
.

这样每个检测规则都相对独立,如果我们要补充规则,只需要新建规则并将其添加到 $inspections 属性里面即可。

2.2 示例二

按条件筛选文章为例,根据条件返回对应的文章。如:https://example.com/articles?by=ilaoniu&unanswered=1

我们先创建一个过滤器基类:

app/Filters/Filters.php

namespace App\Filters;

use Illuminate\Http\Request;

class Filters
{
    protected $request, $builder;
    protected $filters = [];

    public function __construct(Request $request)
    {
        $this->request = $request;
    }

    public function apply($builder)
    {
        $this->builder = $builder;

        foreach ($this->getFilters() as $filter => $value) {
            if (method_exists($this, $filter)) {
                $this->$filter($value);
            }
        }

        return $this->builder;
    }

    protected function getFilters()
    {
        return $this->request->only($this->filters);
    }
}

然后新建文章对应的过滤器:

app/Filters/ArticlesFilters.php

namespace App\Filters;

use App\User;

class ArticlesFilters extends Filters
{
    protected $filters = ['by', 'popularity', 'unanswered'];

    protected function by($username)
    {
        $user = User::where('name', $username)->firstOrFail();

        return $this->builder->where('user_id', $user->id);
    }

    protected function popularity()
    {
        return $this->builder->orderBy('replies_count', 'desc');
    }

    protected function unanswered()
    {
        return $this->builder->where('replies_count', 0);
    }
}

下一步,在模型中为 定义本地作用域

app/Article.php

.
.
.
    public function scopeFilter($query, $filters)
    {
        return $filters->apply($query);
    }
.
.
.

最后就可以在控制器中很方便地调用了:

app/Http/Controllers/ArticleController.php

.
.
.
    public function index(\App\Article $article, \App\Filters\ArticlesFilters $filters)
    {
        $articles = \App\Article::latest()->filter($filters);
        .
        .
        .
    }
.
.
.