Laravel позволяет решить одну задачу несколькими способами.
В этом разделе перечислены наши соглашения написания кода на Laravel.
Linting
На всех проектах используется .
В PHPStorm можно настроить автоматическое форматирование PHP кода при сохранении Pint - ом:
Отключите встроенное форматирование кода для PHP
Создайте новый File Watcher для Laravel Pint
Используйте следующие настройки
Program: $ProjectFileDir$/vendor/bin/pint
Arguments: $FileRelativePath$
Output paths to refresh: $FileRelativePath$
Working directory: $ProjectFileDir$
Контроллеры
Название ресурсных контроллеров должны быть в единственном числе
// Хорошо
final class ArticleController
// Плохо
final class ArticlesController
Названия методов
Старайтесь не выходить за дефолтные CRUD названия методов (index, create, store, show, edit, update и destroy)
Создавайте новый контроллер, если вам нужны другие методы.
Используйте method injection для Request класса и остальных зависимостей
// Хорошо
public function update(Request $request, User $user)
{
$this->validate($request, ['email' => ['email']);
$name = $request->input('name');
}
// Плохо
public function update(User $user)
{
$this->validate(request(), ['email' => ['email']);
$name = request('name');
}
Сначала зависимости из маршрутов, а затем остальные
// Routes/web.php
Route::post(‘/users/{user}’, [UserController, ‘update’])
// Хорошо
public function update(User $user, Request $request, UserSevice $service) {}
// Плохо
public function update(Request $request, User $user, UserSevice $service) {}
Маршруты
Используйте новый синтаксис
// Хорошо
Route::get('about', [AboutPageController::class, 'index']);
// Плохо
Route::get('about', 'AboutPageController@index');
Адрес маршрута не должен начинаться с /, если только адрес не будет пустой строкой
// Хорошо
Route::get('/', 'HomeController@index');
Route::get('open-source', 'OpenSourceController@index');
// Плохо
Route::get('', 'HomeController@index');
Route::get('/open-source', 'OpenSourceController@index');
// Хорошо
Route::get('/', [HomeController::class, 'index'])->name('home.index');
// Плохо
Route::name('home.index')->get('/', [HomeController::class, 'index']);
Используйте синтаксис массива для Route::middleware()
// Хорошо
Route::get('about',[AboutPageController::class,'index'])->middleware(['cache:1day']);
Route::get('about', [AboutPageController::class, 'index'])->middleware(['cache:1day', 'CORS']);
// Плохо
Route::get('about', [AboutPageController::class, 'index'])->middleware('cache:1day', 'CORS');
Route::get('about', [AboutPageController::class, 'index'])->middleware('cache:1day');
Request
Используйте методы класса Request вместо магических
// Хорошо
public function store(Request $request)
{
$email = $request->get('email');
if ($request->hasFile('avatar') {
//
}
if ($request->filled('contacts') {
//
}
}
// Плохо
public function store(Request $request)
{
$email = $request->email;
if ($request->avatar) {
//
}
if ($request->contacts) {
//
}
}
Валидация
Старайтесь не использовать | как разделитель для правил валидации.
// Хорошо
public function rules(): array
{
return [
'email' => ['required', 'email'],
];
}
// Плохо
public function rules(): array
{
return [
'email' => 'required|email',
];
}
// Хорошо
// User.php
protected function firstName(): Attribute
{
return Attribute::make(
get: fn ($value) => ucfirst($value),
);
}
// Плохо
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
Artisan команды
Имена команд должны быть в kebab-case стиле
# Хорошо
php artisan delete-old-records
# Плохо
php artisan deleteOldRecords
# Плохо
php artisan delete_old_records
Сервисы
Старайтесь выносить логику, которая может быть переиспользована в разных местах приложения в классы сервисы.
// Хорошо
// App\Http\Services\StorageImage
public function store(file $image, string, $path = null, $folder = 'images'): string
{
...
Storage::put($path, $file->__toString());
...
}
// App\Http\Controllers\UserController
public function update(User $user, Request $request, Image $imageService)
{
$pathToFile = $imageService->store(
image: $request->file('image'),
path: $user->image
);
}
public function store(Request $request, Image $service)
{
$pathToFile = $imageService->store($request->file('image'));
}
// Плохо
// App\Http\Controllers\UserController
public function update(User $user, Request $request, Image $imageService)
{
...
$path = 'user/avatars/user.jpg';
Storage::put($path, $requset->file('image')->__toString());
}
public function store(Request $request, Image $service)
{
...
$path = 'user/avatars/user.jpg';
Storage::put($path, $requset->file('image')->__toString());
}
Необходимо зависеть от абстракции, а не от реализации
Старайтесь ответить на вопрос: "Может ли текущая реализация быть заменена в будущем?" заранее.
Если - да, то необходимо реализовать интерфейс.
// Хорошо
// App\Http\Services\Dns;
// Интерфейс должен содержать методы, которые используются вне сервиса
// (В контроллерах, джобах и т.д)
interface Dns
{
public function getDomain(): string;
public function deleteDomainById(mixed $id): void;
}
// App\Http\Services\CloudflareDns;
class CloudflareDns implements Dns
{
// Вспомогательные методы сервиса лучше делать приватными или защищенными
protected function sendRequestToCloudflare()
{
// Реализация вспомогательного метода
}
public function getDomain(): string
{
// Реализация
}
public function deleteDomainById(mixed $id): void
{
// Реализация
}
}
// App\Http\Controllers\ModalController;
// В типе сервиса указывается интерфейс
public fuction store(Request $request, Dns $dnsService)
{
...
$domain = $dnsService->getDomain();
...
}
Какая от этого польза?
В контексте примера выше. Если в дальнейшем будет необходимо мигрировать с сервиса cloudflare на любой аналог. Нужно будет всего лишь написать новую реализацию интерфейса DNS для аналога, без необходимости изменять код контроллера.
Binding интерфейса к реализации.
Laravel позволяет внедрять зависимости без использования конструктора.
// Хорошо
// App\Providers\DnsServiceProvider
class DnsServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->app->bind(Dns::class, CloudflareDns::class);
$this->app->when(ModalController::class)
->needs(Dns::class)
->give(CloudflareDns::class);
}
}
Какая от этого польза?
Например, необходимо при локальной разработке не отправлять запрос в Cloudlfare. Тогда можно реализовать класс заглушку и подменять их в зависимости от среды разработки.
// Хорошо
// App\Providers\DnsServiceProvider
class DnsServiceProvider extends ServiceProvider
{
$this->app->when(ModalController::class)
->needs(Dns::class)
->give(
App::environment('local')
? TestingDns::class,
: CloudflareDns::class
);
}
Коллекции
// Из массива пользователей нужно получить
// массив не пустых почт
// Без коллекций
function getUserEmails($users)
{
$emails = [];
for ($i = 0; $i < count($users); $i++) {
$user = $users[$i];
if ($user->email !== null) {
$emails[] = $user->email;
}
}
return $emails;
}
// С использованием коллекций
function getUserEmails($users)
{
return Collection::make($users)
->filter(fn ($user) => $user->email !== null)
->map(fn ($user) => $user->email)
->toArray();
}
Оба решения хороши и мы никак не ограничиваем вас в использовании.
Но с помощью коллекций иногда можно получить более элегантное решение, не так ли?
Не злоупотребляйте коллекциями если это вредит читабельности кода!
Конфиги
Не забывайте дублировать новые переменные в файле .env в файл .env.example, скрывая чувствительные данные