کد نویسی تمیز در لاراول یکی از مهم ترین بخش های توسعه یک پروژه است، که باید از یک سری اصول رعایت کنیم تا بتوانیم کد های تمیز تر و قابل نگهداری داشته باشیم. SOLID مجموعه ای از اصول طراحی شی گرا است که به ما کمک می کند کدی تمیز، قابل نگهداری و قابل توسعه بنویسیم.
SOLID مخفف:
S - اصل مسئولیت تک
O - اصل باز-بسته
L - اصل جایگزینی لیسکوف
I - اصل جداسازی رابط
د - اصل وارونگی وابستگی
در این مقاله، نحوه اعمال این 5 اصل را برای ساختن یک پروژه PHP/Laravel تمیز و قابل نگهداری نشان خواهیم داد. برای آشنایی دقیق تر با اصول سالید می توانید این مقاله را بررسی کنید.
اصل تک مسئولیت:
اصل تک مسئولیت (SRP) بیان میکند که یک کلاس باید یک مسئولیت داشته باشد که منجر به باگهای کمتر، درک آسانتر و بهبود قابلیت نگهداری کد میشود.
قبل از SRP:
در مثال زیر، متد store
در کنترلر، اصل تک مسئولیت (SRP) را نقض میکند، زیرا سه کار مجزا را انجام میدهد: اعتبارسنجی ورودی کاربر، ایجاد پست و بازگرداندن view.
<?php
namespace App\Http\Controllers;
use I1luminate\Http\Request;
use App\Models\Post;
class PostController extends Controller
{
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|string',
'content' => 'required|string',
]);
$post = new Post();
$post->title = $request->title;
$post->content = $request->content;
$post->save();
return redirect()->route('posts.index')->with('success', 'Post created successfully!');
}
}
بعد از SRP:
برای اعمال اصل تک مسئولیت (SRP)، وظایفی مانند ایجاد پست را به یک سرویس و اعتبار سنجی ورودی کاربر را به درخواست فرم واگذار می کنیم. این امر کنترلر را بر روی یک مسئولیت متمرکز نگه می دارد: مدیریت تعامل کاربر و سازماندهی فرآیند ایجاد. همچنین این کار باعث می شود به درستی از دیزاین پترن موجود استفاده کنید.
<?php
namespace App\Http\Controllers;
use I1luminate\Http\Request;
use App\Http\Requests\StorePostRequest;
use App\Services\PostService;
class PostController extends Controller
{
public function __construct(private PostService $postService)
{
}
public function store(StorePostRequest $request)
{
$post = $this->postService->createPost([
'title' => $request->title,
'content' => $request->content
]);
return redirect()->route('posts.index')->with('success', 'Post created successfully!');
}
}
آیا با لاراول 11 آشنا هستید؟ در این مقاله می توانید از جدیدترین ویژگی های لاراول با خبر شوید.
اصل باز-بسته:
اصل Open-closed بیان میکند که یک کلاس باید برای توسعه باز باشد، اما برای اصلاح بسته باشد تا باگها کاهش یابد و از تغییر منطق اصلی جلوگیری شود.
قبل از OCP:
با فرض اینکه ما یک کلاس "Work
" داریم، که انواع مختلفی از کارها را انجام می دهد، افزودن نوع کار جدید بعداً نیاز به تغییر کلاس "Work
" دارد که اصل OCP را نقض می کند.
<?php
class Work {
public function __construct(private string $work)
{
}
public function perform()
{
if ($this->work == "ground_isolation") {
//logic for ground isolation work
} else if ($this->work == "loft_insulation") {
//logic for loft insulation work
}
}
}
بعد از OCP:
برای اعمال اصل باز-بسته (OCP)، میتوانیم رابطی به نام «WorkInterface
» تعریف کنیم. این رابط متد های مورد نیاز برای هر نوع کاری را مشخص می کند. هر نوع جدید کار به عنوان یک کلاس جداگانه که از "WorkInterface
" به ارث می رسد، پیاده سازی می شود. این رویکرد به ما اجازه میدهد تا انواع کارهای جدید را بدون تغییر کد موجود که از "WorkInterface
" استفاده میکند، اضافه کنیم.
<?php
interface WorkInterface {
public function perform();
}
//
<?php
class GroundIsolationWork implements WorkInterface {
public function perform()
{
//logic for ground isolation work
}
}
//
<?php
class LoftInsulationWork implements WorkInterface {
public function perform()
{
//logic for loft insulation work
}
}
<?php
class WallInsulationWork implements WorkInterface {
public function perform()
{
//logic for wall insulation work
}
}
اصل جایگزینی لیسکوف:
اصل جایگزینی لیسکوف بیان میکند که اشیاء یک سوپرکلاس باید با اشیایی از انواع فرعی آن بدون تأثیر بر عملکرد برنامه قابل تعویض باشند.
قبل از LSP:
با فرض اینکه کلاسی به نام "Animal
"، کلاس والد "Dog
" و "Pigeon
" داشته باشیم، مشکل به این دلیل پیش می آید که در کلاس "Animal
" متد "fly
" داریم. از آنجایی که سگ ها نمی توانند پرواز کنند، ارث بردن از "Animal
" کلاس "Dog
" را مجبور می کند تا متد "fly
" را اجرا کند. این اصل جایگزینی Liskov (LSP) را نقض می کند.
class Animal {
public function fly() {
echo "Fly";
}
public function eat() {
echo "Eat";
}
}
class Dog extends Animal {
public function fly() {
throw new \Exception("I can't fly");
}
public function eat() {
// Specific implementation
echo "Dog eat";
}
}
class Pigeon extends Animal {
public function fly() {
echo "I can fly";
}
public function eat() {
// Specific implementation
echo "Pigeon eat";
}
}
بعد از LSP:
برای اعمال اصل جایگزینی لیسکوف (LSP)، ما متد "fly
" را از کلاس "Animal
" حذف کردیم، این به این دلیل است که همه حیوانات نمی توانند پرواز کنند، و یک کلاس جدید "FlyingBird
" ایجاد کردیم که از کلاس "Animal
" به ارث می رسد و متد "fly
" را اجرا می کند. اکنون، "Pigeon
" از "FlyingBird
" ارث می برد، زیرا می تواند پرواز کند، و Dog از "Animal
" (که دیگر متد "fly
" را ندارد) ارث می برد.
class Animal {
public function eat() {
echo "Eat";
}
}
class FlyingAnimal extends Animal {
public function fly() {
echo "Fly";
}
}
class Dog extends Animal {
public function eat() {
// Specific implementation
echo "Dog eat";
}
}
class Pigeon extends FlyingAnimal {
public function fly() {
echo "I can fly";
}
public function eat() {
// Specific implementation
echo "Pigeon eat";
}
}
برای آشنایی با 20 تا از بهترین روش ها و نکات در لاراول این مقاله را بررسی کنید.
اصل جداسازی رابط:
اصل تفکیک رابط بیان می کند که کلاس ها نباید مجبور به پیاده سازی رابط هایی شوند که از آنها استفاده نمی کنند.
قبل از ISP:
اگر دو کلاس "PublicDocument
" و "PrivateDocumen
t" داشته باشیم که یک رابط واحد به نام "DocumentInterface
" را پیاده سازی می کنند، کلاس "Pub licDocument
" مجبور می شود یک متد "editDocument
" را پیاده سازی کند، حتی اگر خود سند قابل ویرایش نباشد. این وضعیت ISP را نقض می کند.
interface DocumentInterface {
public function displayDocument{};
public function editDocument{};
public function printDocument{};
}
class PublicDocument implements DocumentInterface {
public function displayDocument() {
//implementation
}
public function editDocument() {
//implementation
}
public function printDocument() {
//implementation
}
}
class PrivateDocument implements DocumentInterface {
public function displayDocument() {
//implementation
}
public function editDocument() {
//implementation
}
public function printDocument() {
//implementation
}
}
بعد از ISP:
برای اعمال اصل جداسازی رابط (ISP)، دو رابط مجزا ("EditableDocumentInterface
" و "DisplayableDocumentInterface
") ایجاد می کنیم. کلاس "PublicDocument
" فقط "DisplayableDocumentInterface
" را اجرا می کند، که به ISP پایبند است و کد تمیزتر و قابل نگهداری تر را ترویج می کند.
interface EditableDocumentInterface {
public function editDocument{);
}
interface DisplayableDocumentInterface {
public function displayDocument{);
public function printDocument{);
}
class PublicDocument implements DisplayableDocumentInterface {
public function displayDocument() {
//implementation
}
public function printDocument() {
//implementation
}
}
class PrivateDocument implements DisplayableDocumenttinterface,EditableDocumentInterface {
public function displayDocument() {
//implementation
}
public function editDocument() {
//implementation
}
public function printDocument(){
//implementation
}
}
اصل وارونگی وابستگی:
اصل وارونگی وابستگی بیان میکند که ماژولهای سطح بالا (کلاسهای مسئول عملکردهای اصلی) باید به آبسترک ها (واسطها یا کلاسهای آبسترک) بستگی داشته باشند، نه به پیادهسازیهای مشخص (ماژولهای سطح پایین). این باعث اتصال شل، تست پذیری بهتر و توسعه پذیری می شود.
قبل از DIP:
در حال حاضر، این کلاس به جای تکیه بر یک رابط یا یک کلاس انتزاعی، کلاس "DocumentService
" را تزریق می کند. این رویکرد ممکن است برای توسعه پذیری آینده ایده آل نباشد. اگر نیاز به پردازش تصاویر، پوشهها یا انواع فایلهای دیگر در کنار اسناد داشته باشیم، اصلاح این کلاس برای پذیرش اجرای concrete متفاوت ضروری است. بهعلاوه، اگر سازنده «DocumentService
» وابستگیهایی داشته باشد، توانایی ما برای مدیریت مؤثر آن وابستگیها را بیشتر محدود میکند. برای رسیدگی به این مشکلات احتمالی، اصل وارونگی وابستگی (DIP) راه حل ایده آل است.
<?php
namespace App\Http\controllers;
use App\Http\Requests\ItemRequest ;
use App\Services\DocumentService;
class DocumentController extends Controller
{
public function __construct(private DocumentService $documentService)
{
}
public function upload(ItemRequest $request)
{
$validatedData = $request->validated();
$document = $this->documentService->upload($validatedData);
return response()->json(['message' => 'Document uploaded successfully!',
'document' => $document]);
}
}
پس از DIP:
برای اعمال اصل وارونگی وابستگی (DIP)، ما یک رابط ایجاد کردیم تا توسط DocumentService
پیاده سازی شود. ما همچنین یک ارائهدهنده سرویس ایجاد کردیم که کلاس «DocumentService
» را به رابط متصل میکند. این به ما این امکان را می دهد که به جای اجرای concrete، به رابط وابسته باشیم.
<?php
namespace App\Services;
interface ItemServiceInterface
{
public function upload(array $validatedData);
}
<?php
namespace App\Services;
use App\Models\Document;
use I1lluminate\Support\Facades\Storage;
class DocumentService implements ItemServiceInterface
{
public function upload(array $validatedData) {
$fileName = time() . '.' . $validatedData['extension'];
// Directly interacting with Laravel’s Storage facade (tight coupling)
Storage: :disk('documents')->put($fileName, $validatedData['file_content']);
$document = Document: :create([
'name' => $fileName,
'type' => $data['type'],
]);
return $document;
}
}
<?php
namespace App\Providers;
use App\Services\DocumentService;
use App\Services\ItemServiceInter face;
use I1Lluminate\Support\ServiceProvider;
class ItemServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(ItemServiceInterface::class, DocumentService::class);
}
public function boot()
{
// ... (Optional: Register any events or additional logic)
}
}
// contig/app. php
'providers' => [
//... other providers
App\Providers\ItemServiceProvider::class,
]
<?php
namespace App\Http\controllers;
use App\Http\Requests\DocumentRequest;
use App\Services\DocumentServiceInterface;
class DocumentController extends Controller
{
public function __construct(ItemServiceInterface $documentService)
{
}
public function upload(DocumentRequest $request)
{
$validatedData = $request->validated();
$document = $this->documentService->upload($validatedData);
return response()->json{['message' => 'Document uploaded successfully!',
'document' => $document]);
}
}
نتیجه
در پایان، با پیروی از اصول SOLID، ما اطمینان میدهیم که کدی که توسعه میدهیم قابل نگهداری، خواندن و درک آسان و مقیاسپذیر خواهد بود. همچنین با پیروی از دیزاین پترن ها نیز کد های قابل توسعه تری در لاراول خواهید داشت.