Принцип единственной ответственности (Single Responsibility Principle)
Каждый класс и метод должны выполнять лишь одну функцию.
- Плохо:
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(): bool { return $this->isVerifiedClient() ? $this->getFullNameLong() : $this->getFullNameShort(); } public function isVerifiedClient(): bool { return auth()->user() && auth()->user()->hasRole('client') && auth()->user()->isVerified(); } public function getFullNameLong(): string { return 'Mr. ' . $this->first_name . ' ' . $this->middle_name . ' ' . $this->last_name; } public function getFullNameShort(): string { return $this->first_name[0] . '. ' . $this->last_name; }
Тонкие контроллеры, толстые модели
Выносите работу с данными в модели.
- Плохо:
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(): Collection { return $this->verified() ->with(['orders' => function ($q) { $q->where('created_at', '>', Carbon::today()->subWeek()); }]) ->get(); } }
Валидация
Следуя принципам тонкого контроллера и SRP, выносите валидацию из контроллера в 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(): array { 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): void { if (!is_null($image)) { $image->move(public_path('images') . 'temp'); } } }
Не повторяйся (DRY)
Этот принцип призывает вас переиспользовать код везде, где это возможно. Если вы следуете принципу SRP, вы уже избегаете повторений, но Laravel позволяет вам также переиспользовать представления, части 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', true)->whereNotNull('deleted_at'); } public function getActive(): Collection { return $this->active()->get(); } public function getArticles(): Collection { return $this->whereHas('user', function ($q) { $q->active(); })->get(); }
Предпочитайте Eloquent конструктору запросов (Query builder) и сырым запросам в БД. Предпочитайте работу с коллекциями работе с массивами
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();
Используйте массовое заполнение (Mass assignment)
- Плохо:
$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)
- Плохо (будет выполнен 101 запрос в БД для 100 пользователей):
@foreach (User::all() as $user) {{ $user->profile->name }} @endforeach
- Хорошо (будет выполнено 2 запроса в БД для 100 пользователей):
$users = User::with('profile')->get(); ... @foreach ($users as $user) {{ $user->profile->name }} @endforeach
Используйте метод chunk при работе с большим количеством данных
- Плохо:
$users = $this->get(); foreach ($users as $user) { ... }
- Хорошо:
$this->chunk(500, function ($users) { foreach ($users as $user) { ... } });
Предпочитайте читаемые имена переменных и методов комментариям
- Плохо:
// Determine if there are any joins 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($article)'> Или <button class="js-fav-article" data-article='@json($article)'>{{ $article->name }}<button>
В JS файле:
let article = $('#article').val();
Еще лучше использовать специализированный пакет для передачи данных из бэкенда во фронтенд.
Конфиги, языковые файлы и константы вместо текста в коде
Непосредственно в коде не должно быть никакого текста.
- Плохо:
public function isNormal() { return $article->type === 'normal'; } return back()->with('message', 'Ваша статья была успешно добавлена');
- Хорошо:
public function isNormal(): bool { return $article->type === Article::TYPE_NORMAL; } return back()->with('message', __('app.article_added'));
Соблюдайте соглашения сообщества об именовании
Следуйте стандартам PSR при написании кода.
Также, соблюдайте другие cоглашения об именовании:
Короткий и читаемый синтаксис там, где это возможно
- Плохо:
$request->session()->get('cart'); $request->input('name');
- Хорошо:
session('cart'); $request->name;
Еще примеры:
Используйте IoC или фасады вместо new Class
Внедрение классов через синтаксис new Class создает сильное сопряжение между частями приложения и усложняет тестирование. Используйте контейнер или фасады.
- Плохо:
$user = new User; $user->create($request->validated());
- Хорошо:
public function __construct(User $user) { $this->user = $user; } .... $this->user->create($request->validated());
Не работайте с данными из файла .env напрямую
Передайте данные из .env файла в кофигурационный файл и используйте config() в приложении, чтобы использовать эти данные.
- Плохо:
$apiKey = env('API_KEY');
- Хорошо:
// config/api.php 'key' => env('API_KEY'), // Используйте данные в приложении $apiKey = config('api.key');
Не размещайте логику в маршрутах.
Старайтесь не использовать "сырой" PHP в шаблонах Blade.
Используйте базу данных, размещенную в памяти (in-memory DB) при тестировании.
Не меняйте стандартные инструменты фреймворка, иначе у вас могут возникнуть проблемы при обновлении фреймворка и другие сложности.
Используйте современный синтаксис PHP, но при этом не забывайте, что читаемость важнее.
Используйте такие инструменты, как View Composers, с большой осторожностью. В большинстве случаев, есть возможность найти другое решение проблемы.