You might also want to check out the real-world Laravel example application
অনুবাদঃ
Nederlands (by Protoqol)
한국어 (by cherrypick)
ภาษาไทย (by kongvut sangkla)
فارسی (by amirhossein baghaie)
Українська (by Tenevyk)
Tiếng Việt (by Chung Nguyễn)
Español (by César Escudero)
Français (by Mikayil S.)
Polski (by Karol Pietruszka)
Deutsch (by Sujal Patel)
Italiana (by Sujal Patel)
বাংলা (by Anowar Hossain)
العربية (by ahmedsaoud31)
اردو (by RizwanAshraf1)
সিঙ্গেল রেস্পন্সিবিলিটি প্রিন্সিপল বা একক দায়িত্ব নীতি
ফ্যাট মডেলস, স্কীনি কন্ট্রোলারস
বিসনেস লজিক সমূহ সার্ভিস ক্লাসে থাকা প্রয়োজন
একি জিনিস বার বার করবেন না। (DRY)
ব্লেড-টেমপ্লেটে কোয়েরী লিখবেন-না এবং একবারে লোড করুন। (N + 1 সমস্যা)
ব্লেড টেমপ্লেটের মধ্যে JS এবং CSS সরাসরি ইঞ্জেক্ট করবেন না এবং PHP Class এ HTML লিখবেন না
সরাসরি লেখা থেকে কনফিগারেশন, ল্যাঙ্গুয়েজ এবং কনস্টান্ট ফাইল ব্যাবহার করুন
কমিউনিটিতে প্রচলিত মানসম্মত লারাভেলের টুলস গুলো ব্যাবহার করুন
লারাভেল নেমিং কনভেনশন অনুসরণ করুন
যত সম্ভব সংক্ষিপ্ত ও সহজে পড়া যায় এমন সিনট্যাক্স লিখবেন
নতুন ক্লাসের পরিবর্তে IoC কন্টেইনার বা facades ব্যবহার করুন
.env
ফাইলের ডাটা সরাসরি নিবেন না
একটা ক্লাস এবং একটা মেথডের একটা করে কাজ/দায়িত্ব হওয়া উচিৎ।
খারাপঃ
public function getFullNameAttribute(): string
{
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(): string
{
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;
}
সবগুলো ডাটাবেস লজিক Eloquent মডেলে অথবা Repository ক্লাসে থাকা উচিৎ, আপনি যদি Query Builder অথবা raw SQL queries ব্যাবহার করেন।
খারাপঃ
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();
}
}
ভ্যালিডেশন কোড গুলো Controller থেকে Request class এ সরিয়ে ফেলুন।
খারাপঃ
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',
];
}
}
একটা কন্ট্রোলারের একটাই কাজ হওয়া উচিৎ। তাই বিজনেস লজিক গুলো কন্ট্রোলার থেকে সার্ভিস ক্লাসে সরিয়ে ফেলুন।
খারাপঃ
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');
}
}
}
কোডের পুনঃব্যবহার নিশ্চিত করুন। বার বার লেখার থেকে SRP আপনাকে সাহায্য করবে। সাথে Blade টেম্পলেট, 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', 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 Query লেখার পরিবর্তে Eloquent কে গুরুত্ব দিন। Array থেকে Collection ব্যবহার কে গুরুত্ব দিন
Eloquent আপনাকে পাঠযোগ্য এবং রক্ষণাবেক্ষণযোগ্য কোড করতে সাহায্য করবে। এছাড়াও, 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();
খারাপঃ
$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->validated());
খারাপ (১০০ জন ইউজারের জন্য, ১০১ টা DB queries এক্সিকিউট হবে):
@foreach (User::all() as $user)
{{ $user->profile->name }}
@endforeach
ভালো (১০০ জন ইউজারের জন্য, ২ টা DB queries এক্সিকিউট হবে)-ঃ
$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())
খারাপঃ
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', 'Your article has been added!');
ভালঃ
public function isNormal()
{
return $article->type === Article::TYPE_NORMAL;
}
return back()->with('message', __('app.article_added'));
অন্যকোন থার্ডপার্টি packages বা tools ব্যাবহার করা থেকে লারাভেলের নিজস্ব ফাংশনালিটি এবং কমিউনিটি প্যাকেজ ব্যাবহার করা ভাল। ভবিষ্যতে কোন ডেভেলপার আপনার প্রজেক্টে কাজ করতে গেলে তাকে টুলস গুলো শিখে নেয়া লাগবে। এছাড়াও, থার্ডপার্টি packages বা tools ব্যাবহার করলে লারাভেল কমিউনিটি থেকে সাপোর্ট পাওয়ার সম্ভাবনা কম। আপনার ক্লায়েন্ট কে সেটার জন্য অর্থ খরচ করাবেন না।
কাজ | স্ট্যান্ডার্ড টুলস | থার্ডপার্টি টুলস |
---|---|---|
Authorization | Policies | Entrust, Sentinel and other packages |
Compiling assets | Laravel Mix, Vite | Grunt, Gulp, 3rd party packages |
Development Environment | Laravel Sail, Homestead | Docker |
Deployment | Laravel Forge | Deployer and other solutions |
Unit testing | PHPUnit, Mockery | Phpspec, Pest |
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, Laravel Sanctum | 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 |
PSR standards অনুসরণ করুন।
এছাড়াও, লারাভেল কমিউনিটি কর্তৃক স্বীকৃত নামকরণের রীতি(নেমিং কনভেনশন) অনুসরণ করা যায়ঃ
কি | কিভাবে | ভাল | খারাপ |
---|---|---|---|
Controller | singular | ArticleController | |
Route | plural | articles/1 | |
Route name | snake_case with dot notation | users.show_active | |
Model | singular | User | |
hasOne or belongsTo relationship | singular | articleComment | |
All other relationships | plural | articleComments | |
Table | plural | article_comments | |
Pivot table | singular model names in alphabetical order | article_user | |
Table column | snake_case without model name | meta_title | |
Model property | snake_case | $model->created_at | |
Foreign key | singular 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 | descriptive, plural | $activeUsers = User::active()->get() | |
Object | descriptive, singular | $activeUser = User::active()->first() | |
Config and language files index | snake_case | articles_enabled | |
View | kebab-case | show-filtered.blade.php | |
Config | snake_case | google_calendar.php | |
Contract (interface) | adjective or noun | AuthenticationInterface | |
Trait | adjective | Notifiable | |
Trait (PSR) | adjective | NotifiableTrait | |
Enum | singular | UserType | |
FormRequest | singular | UpdateUserRequest | |
Seeder | singular | UserSeeder |
খারাপঃ
$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') |
নতুন ক্লাসের সিনট্যাক্স ক্লাস গুলোকে টাইট কাপলিং করে এবং টেস্টিং জটিল করে। এর থেকে ভালো IoC container অথবা facades ব্যাবহার করা।
খারাপঃ
$user = new User;
$user->create($request->validated());
ভালঃ
public function __construct(User $user)
{
$this->user = $user;
}
...
$this->user->create($request->validated());
বরং ডাটা গুলোকে কনফিগ ফাইলের মধ্যে রাখুন এবং 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 }}
routes ফাইলের মধ্যে কখনো লজিক রাখবেন না।
Blade টেম্পলেটের মধ্যে ভ্যানিলা PHP এর ব্যাবহার কমায় ফেলেন।