ترجمه ها:
한국어 (by cherrypick)
Tiếng Việt (by Chung Nguyễn)
این مستندات درباره سازگاری لاراول با اصول SOLID یا Design Pattern ها و ... نیست. اینجا شما روش های اصولی توسعه پروژه های مبتنی بر لاراول رو پیدا میکنید که معمولا داخل پروژه ها در نظر گرفته نمیشوند.
-
به جای نوشتن query ها در blade از eager loading استفاده کنید. (مسئله N+1)
-
کامنت گذاری بکنید، ولی اسامی متدها یا متغیرها را توصیفی و معنادار در نظر بگیرید.
-
به جای استفاده مستقیم از متن ها در کد، از فایل های config و languages استفاده کنید!
-
از ابزارهای استاندارد لاراول که مورد تایید جامعه کاربری آن میباشد، استفاده کنید.
-
تا حد ممکن در کدتان، از Syntax های معنادار و کوتاه استفاده کنید.
-
به جای ایجاد یک object با new، از IoC container و facades استفاده کنید.
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;
}
اگر از Query Builder یا raw SQL queries استفاده میکنید، تمام منطق پایگاه داده را در model ها یا Repository classes قرار بدهید.
❌ روش اشتباه:
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 classes انجام دهید نه در controllers.
❌ روش اشتباه:
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 classes بنویسید.
❌ روش اشتباه:
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');
}
}
}
تا حد ممکن از کد ها بازاستفاده کنید. تک وظیفه ای شدن به شما کمک میکند تا کار تکراری نکنید. همچنین در Blade template حتما این اصل را رعایت کنید و در model ها از Eloquent scopes استفاده کنید و ... .
❌ روش اشتباه:
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 و raw SQL queries از Eloquent ORM استفاده کنید. همچنین به جای استفاده از Arrays از Collections استفاده کنید.
Eloquent به شما این قابلیت را میدهد که کدهای خوانا و قابل توسعه بنویسید. همچنین دارای ویژگی های داخلی کاربردی مثل soft deletes یا events یا scopes و .. میباشد.
❌ روش اشتباه:
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();
[منظور نویسنده از این بخش این میباشد که برای راحتی کار و کوتاه تر شدن کد، در html طوری مقادیر name هر input یا ... را نامگذاری کنید که با column های جدول مربوطه یکسان باشد تا laravel آن ها رو با یک کامند خیلی سریع مپ و ذخیره کند.]
❌ روش اشتباه:
$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());
❌ روش اشتباه (برای ۱۰۰ کاربر، ما ۱۰۱ کوئری را اجرا میکنیم!):
@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)
❗️ قابل قبول:
// Determine if there are any joins.
if (count((array) $builder->getQuery()->joins) > 0)
✔️ روش قابل قبول:
if ($this->hasJoins())
در تمپلیت های Blade از js و css استفاده نکنید و هیچگونه کد HTML ای را در class های PHP استفاده نکنید.
❌ روش اشتباه:
let article = `{{ json_encode($article) }}`;
❗️ قابل قبول:
<input id="article" type="hidden" value="@json($article)">
Or
<button class="js-fav-article" data-article="@json($article)">{{ $article->name }}<button>
در فایل JavaScript:
let article = $('#article').val();
بهترین راه استفاده از پکیج مخصوص انتقال داده از php به js میباشد.
❌ روش اشتباه:
public function isNormal()
{
return $article->type === 'normal';
}
return back()->with('message', 'مقاله شما ایجاد گردید!');
✔️ روش قابل قبول:
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
به جای استفاده از ابزارها و پکیج های غیررسمی لاراول از ابزارها و قابلیت های داخلی و رسمی لاراول استفاده کنید. هر توسعه دهنده [لاراولی] ای که در آینده بخواهد با کدهای شما کار کند، باید ابزارهای جدید یاد بگیرد. همچنین اگر شما از ابزارهای غیررسمی استفاده کنید، شانس دریافت کمک از جامعه کاربری لاراول به طرز قابل توجهی کمتر میشود. این هزینه را به گردن مشتریان خود نیندازید! [منظور نویسنده این هست که تو کارهای بزرگ و مهمتون از ابزارهای جدید و پراکنده استفاده نکنید. همیشه سعی کنید از ابزارهای داخلی و یا پکیج های رسمی لاراول استفاده کنید مگر این که چاره دیگری نباشد! شما با پراکنده کردن ابزارها هزینه فنی/مالی توسعه محصول را برای آینده زیادتر میکنید!]
نیاز | ابزارهای رسمی لاراول | ابزارهای غیررسمی لاراول |
---|---|---|
Authorization | Policies | Entrust, Sentinel and other packages |
Compiling assets | Laravel Mix | Grunt, Gulp, 3rd party packages |
Development Environment | Homestead | Docker |
Deployment | Laravel Forge | Deployer and other solutions |
Unit testing | PHPUnit, Mockery | Phpspec |
Browser testing | Laravel Dusk | Codeception |
DB | Eloquent | SQL, Doctrine |
Templates | Blade | Twig |
Working with data | Laravel collections | Arrays |
Form validation | Request classes | 3rd party packages, validation in controller |
Authentication | Built-in | 3rd party packages, your own solution |
API authentication | Laravel Passport | 3rd party JWT and OAuth packages |
Creating API | Built-in | Dingo API and similar packages |
Working with DB structure | Migrations | Working with DB structure directly |
Localization | Built-in | 3rd party packages |
Realtime user interfaces | Laravel Echo, Pusher | 3rd party packages and working with WebSockets directly |
Generating testing data | Seeder classes, Model Factories, Faker | Creating testing data manually |
Task scheduling | Laravel Task Scheduler | Scripts and 3rd party packages |
DB | MySQL, PostgreSQL, SQLite, SQL Server | MongoDB |
از استاندارد های php که به PSR معروف است استفاده کنید.
همچنین روش های نامگذاری مورد قبول جامعه کاربری لاراول:
بخش مربوطه | قاعده اسم گذاری | ✔️ روش قابل قبول | ❌ روش اشتباه |
---|---|---|---|
Controller | اسامی مفرد | ArticleController | |
Route | اسامی جمع | articles/1 | |
Named route | روش snake_case همراه با نقاط اتصال | users.show_active | |
Model | اسامی مفرد | User | |
hasOne or belongsTo relationship | اسامی مفرد | articleComment | |
All other relationships | اسامی جمع | articleComments | |
Table | اسامی جمع | article_comments | |
Pivot table | نام مدل ها با اسامی مفرد و ترتیب الفبایی | article_user | |
Table column | روش snake_case بدون اسم مدل | meta_title | |
Model property | روش snake_case | $model->created_at | |
Foreign key | اسامی مفرد model name with _id suffix | article_id | |
Primary key | - | id | |
Migration | - | 2017_01_01_000000_create_articles_table | |
Method | روش camelCase | getAll | |
Method in resource controller | table | store | |
Method in test class | روش camelCase | testGuestCannotSeeArticle | |
Variable | روش camelCase | $articlesWithAuthor | |
Collection | توصیفی و اسامی جمع | $activeUsers = User::active()->get() | |
Object | توصیفی و اسامی مفرد | $activeUser = User::active()->first() | |
Config and language files index | snake_case | articles_enabled | |
View | snake_case | show_filtered.blade.php | |
Config | snake_case | google_calendar.php | |
Contract (interface) | صفت یا اسم | Authenticatable | |
Trait | صفت | Notifiable |
$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->id |
optional($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') |
ایجاد یک object جدید با کلمه new یک اتصال کامل بین class ها و تست های پیچیده ایجاد میکند! بهتر است از IoC container یا facades استفاده کنید. [این بخش از مطلب تا جایی که من متوجه شدم مربوط به مباحث توسعه تست محور میباشد که من آشنایی زیادی ندارم ولی بعد از مطالعه و تکمیل اطلاعاتم این بخش را با توضیح تکمیلی، کامل خواهم کرد.]
❌ روش اشتباه:
$user = new User;
$user->create($request->all());
✔️ روش قابل قبول:
public function __construct(User $user)
{
$this->user = $user;
}
....
$this->user->create($request->all());
اطلاعات موجود در .env را در صورت نیاز به استفاده، در فایل های config لود کنید و سپس با استفاده از helper function فایل های کانفیگ یعنی config() با آن در نرم افزار خود کار کنید.
❌ روش اشتباه:
$apiKey = env('API_KEY');
✔️ روش قابل قبول:
// config/api.php
'key' => env('API_KEY'),
// Use the data
$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') }}
✔️ روش قابل قبول:
// Model
protected $dates = ['ordered_at', 'created_at', 'updated_at'];
public function getSomeDateAttribute($date)
{
return $date->format('m-d');
}
// View
{{ $object->ordered_at->toDateString() }}
{{ $object->ordered_at->some_date }}
-
در فایل های route خود هیچوقت منطق برنامه را قرار ندهید.
-
تا حد ممکن از vanilla PHP در فایل های blade استفاده نکنید.