发布于: 2022-06-18 18:02:35

PHP最佳写法实践

翻译来源:https://github.com/alexeymezenin/laravel-best-practices

铁则:

*所有命名必须见名知意,易读!!!

*永远不要在路由文件里面写逻辑!!!

*永远不要在模板文件里面写原始 php 代码!!!

遵循 命名约定

遵循 PSR 2.

写法好的写法错误示范
控制器单数,首字母大写驼峰命名ArticleControllerArticlesController
路由复数articles/1article/1
命名路由带点符号的蛇形命名users.show_activeusers.show-active, show-active-users
模型单数,首字母大写驼峰命名UserUsers
hasOne 或 belongsTo单数,首字母小写的驼峰命名articleCommentarticleComments, article_comment
其他模型关系复数,首字母小写的驼峰命名articleCommentsarticleComment, article_comments
表名复数,蛇形命名article_commentsarticle_comment, articleComments
中间表单数,蛇形命名article_useruser_article, articles_users
字段蛇形,不要带模型名meta_titleMetaTitle; article_meta_title
模型属性蛇形命名$model->created_at$model->createdAt
外键单数小写模型名称加上 _idarticle_idArticleId, id_article, articles_id
命名空间首字母大写驼峰命名Models/ArticleCommentmodels/articleComment
类名首字母大写驼峰命名ArticleCommentArticlecomment
方法首字母小写驼峰命名getAllget_all
资源控制器方法名laravel自带storesaveArticle
测试类方法名首字母小写驼峰命名testGuestCannotSeeArticletest_guest_cannot_see_article
变量首字母小写驼峰命名$articlesWithAuthor$articles_with_author
集合描述性的, 复数的$activeUsers = User::active()->get()$active, $data
对象描述性的, 单数的$activeUser = User::active()->first()$users, $obj
配置和语言文件索引蛇形命名articles_enabledArticlesEnabled; articles-enabled
视图短横杠命名show-filtered.blade.phpshowFiltered.blade.php, show_filtered.blade.php
配置蛇形命名google_calendar.phpgoogleCalendar.php, google-calendar.php
Contract (接口)形容词或名词AuthenticatableAuthenticationInterface, IAuthentication
Trait形容词NotifiableNotificationTrait
Model对象首字母小写的驼峰命名userModel, articleModel$article , $user
Service对象首字母小写的驼峰命名userService, articleService$article , $user

未写完的功能(比如在下班时)在代码里要写 //todo 已经完成的功能: 未完成的功能:

数据库查询只能写在Model层和Repository

单一职责原则

说明:一个函数里面不要有太多功能的代码,不同功能的代码应该拆分成不同的函数

不要这样写:

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

数据库相关的逻辑代码写到模型(MODEL)或者 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',
        ];
    }
}

业务逻辑应当写在 service 类里面

不要这样做:

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

不要写重复代码

不要这样写:

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

尽量选择用模型而不是 Query Builder 和原生 SQL 查询,尽量用集合而不是数组

不要这样做:

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;
$article->category_id = $category->id;
$article->save();

应该这样写:

$category->article()->create($request->validated());

避免 N+1 问题,使用延迟加载

不要这样做:

@foreach (User::all() as $user)
    {{ $user->profile->name }}
@endforeach

这样做比较好:

$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())

模板里不要写 js,css 代码,前端任何代码不要写在 php 类文件里面

不要这样做:

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

这样做比较好:

<input id="article" type="hidden" value='@json($article)'>

或者

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

js 文件:

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

*写更简短、更易读的代码

不要这样做:

$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->idoptional($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 容器或者 Facade 替代 new 创建新类

不要这样做:

$user = new User;
$user->create($request->validated());

这样做比较好:

public function __construct(User $user)
{
    $this->user = $user;
}

// ....

$this->user->create($request->validated());

用 config() 取配置信息,而不是用 env()

不要这样做:

$apiKey = env('API_KEY');

这样做比较好:

// config/api.php
//'key' => env('API_KEY'),
// 使用如下:
$apiKey = config('api.key');

使用 accessors 和 mutators 修改日期格式

不要这样做:

{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->toDateString() }}
{{ Carbon::createFromFormat('Y-d-m H-i', $object->ordered_at)->format('m-d') }}

这样做比较好:

// 模型
protected $dates = ['ordered_at', 'created_at', 'updated_at'];
public function getSomeDateAttribute($date)
{
    return $date->format('m-d');
}

// 视图
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}



模型里加scope前缀的方法可以直接用到where条件上


所有模型的固定状态都应该声明为模型的常量,比如User的状态 文件审核状态


延伸阅读
    发表评论