قابلیتهای پیشرفته Laravel؛ چرا بیشتر تیمها فقط از ۲۰٪ آن استفاده میکنند؟
قابلیتهای پیشرفته Laravel از Service Container و Events تا Octane و Horizon؛ چرا نادیده گرفتن این ابزارها، پروژه را در مقیاس زمینگیر میکند؟
قابلیتهای پیشرفته Laravel، بخش بزرگی از قدرت این فریمورک است که در بسیاری از پروژهها نادیده گرفته میشود. بیشتر تیمها در سطح Controller، Model و Migration متوقف میشوند و هیچگاه به لایههایی مثل Service Container، Events، Broadcasting، Octane و Horizon نزدیک نمیشوند. نتیجه، پروژههایی است که در ابتدا سریع پیش میروند اما در مواجهه با مقیاس، پیچیدگی و نگهداشت، به یک بدهی فنی سنگین تبدیل میشوند. آنچه این نوشتار بررسی میکند، دقیقاً همان شکاف بین «استفاده سطحی» و «بهرهبرداری کامل» است.
در بازبینی دهها پروژه Laravel در دورههای طولانی، یک الگوی آشنا دیدهام: تیمها در سه ماه اول با سرعت بالا پیش میروند، اما از ماه ششم، هر قابلیت جدید چند برابر زمان میبرد. دلیل این تغییر، معمولاً در خود فریمورک نیست؛ در نادیده گرفتن ابزارهای پیشرفتهای است که Laravel از ابتدا ارائه داده. تسلط بر این ابزارها، تفاوت بین پروژهای که در مقیاس پایدار میماند و پروژهای که زیر وزن خودش فرو میریزد را مشخص میکند.
چرا تسلط بر قابلیتهای پیشرفته یک ضرورت است؟
Laravel بر اساس Laravel در ویکیپدیا یکی از محبوبترین فریمورکهای PHP است که برای ساخت اپلیکیشنهای وب مدرن طراحی شده است. اما محبوبیت آن، دلیل کافی برای تسلط بر لایههای پیشرفته نیست. دلیل واقعی، در سه پیامد عملی نهفته است:
پیامد اول، شکست در مقیاس. پروژههایی که از Service Container و Events استفاده نمیکنند، در مواجهه با رشد تیم و پیچیدگی منطق، به یک آشفتگی تبدیل میشوند. اگر با مبانی معماری نرمافزار آشنا نیستید، تفاوت فریمورکهای بکاند چارچوبی از این لایهها ارائه میدهد.
پیامد دوم، هزینه نگهداشت. هر تغییری در منطق کسبوکار، به یک عملیات پرمخاطره تبدیل میشود چون منطق در چند لایه پراکنده است.
پیامد سوم، تجربه توسعهدهنده. توسعهدهندهای که با ابزارهای پیشرفته کار میکند، سریعتر و با اطمینان بیشتری تغییرات را اعمال میکند.
| لایه | سطح پایه | سطح پیشرفته |
|---|---|---|
| منطق کسبوکار | Controller | Service و Action |
| منطق جانبی | داخل Controller | Event و Listener |
| عملیات سنگین | درخواست همگام | Queue و Job |
| ارتباط لحظهای | Polling | Broadcasting |
| عملکرد | PHP-FPM | Octane |
Laravel ابزارهای پیشرفته را در اختیار همه قرار میدهد، اما تسلط بر آنها یک انتخاب است؛ و همین انتخاب، مرز بین پروژه موفق و پروژه شکستخورده را میسازد.
Service Container و تزریق وابستگی
Service Container قلب معماری Laravel است و درک عمیق آن، پیشنیاز استفاده از سایر قابلیتهای پیشرفته است. این لایه، مسئول مدیریت نمونههای کلاسها و تزریق خودکار وابستگیهاست.
در سطح پایه، Service Container بهصورت خودکار وابستگیها را در سازنده کلاس تزریق میکند:
class OrderController extends Controller
{
public function __construct(
private OrderService $orderService,
private PaymentService $paymentService
) {}
}
اما در سطح پیشرفته، Service Container امکان تعریف Bindingهای سفارشی را فراهم میکند:
// در ServiceProvider
$this->app->bind(
PaymentGateway::class,
StripeGateway::class
);
// Binding با پارامتر
$this->app->bind(PaymentGateway::class, function ($app) {
return new StripeGateway(
apiKey: config('services.stripe.key'),
timeout: config('services.stripe.timeout', 30)
);
});
مفهوم Singleton در سطح پیشرفته. برای کلاسهایی که باید فقط یک نمونه داشته باشند:
$this->app->singleton(MetricsCollector::class);
Contextual Binding. یکی از قدرتمندترین قابلیتهای Service Container که امکان تعریف Binding متفاوت برای کلاسهای مختلف را فراهم میکند:
$this->app->when(AdminReport::class)
->needs(Storage::class)
->give(fn () => new S3Storage('admin-bucket'));
$this->app->when(UserReport::class)
->needs(Storage::class)
->give(fn () => new LocalStorage('user-reports'));
این الگو، در پروژههایی که رفتار کلاسها بر اساس زمینه تغییر میکند، بسیار مفید است. اگر با مبانی ORM و لایهبندی داده آشنا نیستید، راهنمای ORM و سادهسازی کار با دیتابیس این لایه را باز میکند.
Tagging. امکان گروهبندی Bindingها و بازیابی گروهی آنها:
$this->app->tag([StripeGateway::class, PayPalGateway::class], 'payment-gateways');
$gateways = $this->app->tagged('payment-gateways');
در سطح پیشرفته، درک تفاوت بین bind، singleton و scoped اهمیت زیادی دارد. در Laravel Octane که در ادامه بررسی میشود، scoped نقش کلیدی در جلوگیری از نشت state بین درخواستها دارد.
Service Providers در عمق
Service Providers، نقطه شروع هر بخش از اپلیکیشن Laravel هستند. این کلاسها، مسئول register کردن سرویسها و اجرای تنظیمات اولیه هستند. تسلط بر این لایه، امکان ساخت ماژولهای مستقل و قابل استفاده مجدد را فراهم میکند.
ساختار پایه یک Service Provider:
class PaymentServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(PaymentGateway::class, function ($app) {
return new StripeGateway(config('services.stripe.key'));
});
}
public function boot(): void
{
RateLimiter::for('payment', function (Request $request) {
return Limit::perMinute(10)->by($request->user()->id);
});
}
public function provides(): array
{
return [PaymentGateway::class];
}
}
تفاوت register و boot. این تفاوت، یکی از نقاط مبهم Service Providers است. متد register فقط برای binding سرویسهاست و نباید به سرویسهای دیگر وابسته باشد. متد boot برای تنظیمات اولیه و event listenerهاست و بعد از register تمام Providerها اجرا میشود.
Deferred Providers. یکی از قابلیتهای پیشرفته که به بهبود عملکرد کمک میکند. اگر یک Provider فقط سرویسهای خاصی را register میکند و در همه درخواستها استفاده نمیشود، میتوان آن را deferred کرد:
class PaymentServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function provides(): array
{
return [PaymentGateway::class];
}
}
Providerهای deferred، فقط زمانی بارگذاری میشوند که سرویس آنها واقعاً درخواست شود. این کار، زمان bootstrap اپلیکیشن را بهطور محسوسی کاهش میدهد.
Provider برای ماژولهای مستقل. در پروژههای بزرگ، هر ماژول میتواند Provider مستقل خود را داشته باشد:
app/Modules/
├── User/
│ └── Providers/UserServiceProvider.php
├── Order/
│ └── Providers/OrderServiceProvider.php
└── Payment/
└── Providers/PaymentServiceProvider.php
این ساختار، امکان جداسازی کامل ماژولها و تست مستقل هر کدام را فراهم میکند. برای درک عمیقتر ساختاردهی پروژه، ساختاربندی پروژه توسعه چارچوبی از این لایهها ارائه میدهد.
Facades از نگاه معماری
Facades یکی از جذابترین و در عین حال مورد مناقشهترین بخشهای Laravel است. این لایه، امکان دسترسی ساده به سرویسهای Service Container را فراهم میکند، اما در سطح معماری، تصمیمهای مهمی را تحمیل میکند.
use IlluminateSupportFacadesCache;
Cache::put('key', 'value', 3600);
$value = Cache::get('key');
در پشت این کد ساده، یک مکانیزم پیچیده نهفته است. Facade یک کلاس استاتیک است که از متد __callStatic برای هدایت فراخوانیها به نمونه واقعی سرویس استفاده میکند.
مزایای Facade. سه مزیت اصلی:
مزیت اول، خوانایی. Cache::put بسیار خواناتر از $container->make('cache')->put است.
مزیت دوم، تستپذیری نسبی. Laravel ابزارهایی برای Mock کردن Facadeها فراهم میکند:
Cache::shouldReceive('get')
->with('key')
->once()
->andReturn('value');
مزیت سوم، انعطافپذیری در زمان اجرا. میتوان پیادهسازی زیربنایی را بدون تغییر کد عوض کرد.
معایب Facade. مهمترین عیب، پنهان کردن وابستگیها است. وقتی از Facade در سازنده یک کلاس استفاده میشود، وابستگی صریح نیست و در سطح معماری، تصمیمگیری درباره تستپذیری و جداسازی سختتر میشود.
الگوی حرفهای استفاده. در پروژههای بزرگ، رویکرد توصیهشده ترکیبی است: در لایههای انتزاعی مثل Service و Action، از تزریق وابستگی صریح استفاده شود و از Facade فقط در لایههای بیرونی مثل Controller و Middleware بهره گرفته شود.
// الگوی توصیهشده در Service
class OrderService
{
public function __construct(
private CacheRepository $cache,
private LoggerInterface $logger
) {}
}
// در Controller استفاده از Facade مجاز است
class OrderController extends Controller
{
public function index()
{
$orders = Cache::remember('orders', 600, fn () => Order::all());
return view('orders.index', compact('orders'));
}
}
این الگو، تعادل بین خوانایی و تستپذیری را حفظ میکند. اگر با مبانی الگوهای معماری آشنا نیستید، آموزش الگوی MVC با مثال عملی چارچوبی از این لایهها ارائه میدهد.
Events و Listeners؛ جداکردن منطق جانبی
Events و Listeners، یکی از قدرتمندترین ابزارهای معماری در Laravel هستند که امکان جداکردن منطق جانبی از منطق اصلی را فراهم میکنند. این لایه، تفاوت بین یک پروژه با معماری تمیز و یک پروژه با کد درهمتنیده را میسازد.
ساختار پایه یک Event:
class OrderPlaced
{
use Dispatchable, SerializesModels;
public function __construct(
public Order $order
) {}
}
ساختار پایه یک Listener:
class SendOrderConfirmation
{
public function handle(OrderPlaced $event): void
{
Mail::to($event->order->user)->send(
new OrderConfirmedMail($event->order)
);
}
}
ثبت Event و Listener در EventServiceProvider:
protected $listen = [
OrderPlaced::class => [
SendOrderConfirmation::class,
UpdateInventory::class,
NotifyAdmin::class,
TrackAnalytics::class,
],
];
مزایای این الگو. در نگاه اول، Events و Listeners ممکن است پیچیدگی اضافه کنند. اما در سطح پروژه، سه مزیت کلیدی ارائه میدهند:
مزیت اول، جداسازی مسئولیت. منطق اصلی (ثبت سفارش) از منطق جانبی (ارسال ایمیل، بهروزرسانی موجودی، اطلاع به ادمین) جدا میشود. اگر فردا یکی از این عملیات حذف شود، فقط یک Listener حذف میشود.
مزیت دوم، قابلیت گسترش. افزودن عملیات جدید، فقط نیازمند یک Listener جدید است، بدون تغییر در منطق اصلی.
مزیت سوم، Queue-native. Listenerها میتوانند بهطور مستقیم ShouldQueue پیاده کنند و در پسزمینه اجرا شوند:
class SendOrderConfirmation implements ShouldQueue
{
use InteractsWithQueue;
public function handle(OrderPlaced $event): void
{
// این Listener در پسزمینه اجرا میشود
}
}
Event Subscribers. یکی از قابلیتهای پیشرفته که امکان گروهبندی چند Listener در یک کلاس را فراهم میکند:
class OrderEventSubscriber
{
public function handleOrderPlaced(OrderPlaced $event): void { /* ... */ }
public function handleOrderCancelled(OrderCancelled $event): void { /* ... */ }
public function subscribe(Dispatcher $events): void
{
$events->listen(OrderPlaced::class, [self::class, 'handleOrderPlaced']);
$events->listen(OrderCancelled::class, [self::class, 'handleOrderCancelled']);
}
}
این الگو، در پروژههایی که چند Event مرتبط دارند، ساختار را سادهتر میکند.
Events و Listeners، معماری را از «زنجیرهای از فراخوانیها» به «شبکهای از واکنشها» تبدیل میکنند؛ همین تغییر نگاه، پروژه را در مقیاس پایدار نگه میدارد.
Queueهای پیشرفته؛ Batch، Chain و Rate Limiting
Queue در Laravel، فراتر از ارسال ساده Job به پسزمینه است. لایههای پیشرفتهای وجود دارند که هر کدام برای سناریوی خاصی طراحی شدهاند و تسلط بر آنها، تفاوت بین یک Queue کاربردی و یک سیستم پردازش توزیعشده را میسازد.
Job Chaining. اجرای چند Job بهترتیب، بهطوری که اگر یکی شکست بخورد، بقیه اجرا نشوند:
Bus::chain([
new ProcessPayment($order),
new GenerateInvoice($order),
new SendInvoiceEmail($order),
new UpdateOrderStatus($order),
])->dispatch();
این الگو، برای فرآیندهایی که ترتیب در آنها اهمیت دارد، ضروری است. اگر یکی از Jobها شکست بخورد، تمام زنجیره متوقف میشود و میتوان بهطور هدفمند Retry کرد.
Job Batching. اجرای مجموعهای از Jobها بهصورت موازی، با امکان پیگیری پیشرفت:
$batch = Bus::batch([
new ProcessImage($image1),
new ProcessImage($image2),
new ProcessImage($image3),
])->then(function (Batch $batch) {
// پس از موفقیت همه Jobها
})->catch(function (Batch $batch, Throwable $e) {
// در صورت شکست
})->finally(function (Batch $batch) {
// در هر حالت
})->dispatch();
// پیگیری پیشرفت
$batch->progress(); // درصد پیشرفت
Batchها، برای پردازش حجم زیادی از داده (مثل آپلود انبوه تصاویر یا ایمیل مارکتینگ) ضروری هستند.
Rate Limiting در Queue. برای Jobهایی که باید با محدودیت نرخ اجرا شوند:
class CallExternalApi implements ShouldQueue
{
public function middleware(): array
{
return [new RateLimited('external-api')];
}
}
// تعریف در ServiceProvider
RateLimiter::for('external-api', function () {
return Limit::perMinute(60);
});
Job Middleware. یکی از قابلیتهای پیشرفته که امکان اجرای منطق قبل یا بعد از Job را فراهم میکند:
class LogJobStart
{
public function handle($job, $next): void
{
Log::info('Job started', ['job' => get_class($job)]);
$next($job);
Log::info('Job completed', ['job' => get_class($job)]);
}
}
Job Prioritization. تعریف صفهای مختلف با اولویتهای متفاوت:
php artisan queue:work --queue=high,default,low
این الگو، امکان اجرای Jobهای حساس در اولویت بالاتر و Jobهای سنگین در اولویت پایینتر را فراهم میکند. برای درک عمیقتر طراحی Jobها، بهینهسازی عملکرد بکاند چارچوبی از این لایهها ارائه میدهد.
Broadcasting و ارتباط لحظهای
Broadcasting یکی از قابلیتهای پیشرفته Laravel است که امکان ارسال رویدادها به کلاینتها در زمان واقعی را فراهم میکند. این لایه، زیربنای اپلیکیشنهای تعاملی مثل چت، داشبوردهای زنده و اعلانهای آنی است.
ساختار پایه یک Event قابل Broadcasting:
class MessageSent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(
public Message $message
) {}
public function broadcastOn(): array
{
return [
new PrivateChannel('chat.' . $this->message->room_id),
];
}
public function broadcastAs(): string
{
return 'message.sent';
}
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'content' => $this->message->content,
'user' => $this->message->user->only(['id', 'name']),
'sent_at' => $this->message->created_at->toIso8601String(),
];
}
}
کانالهای عمومی و خصوصی. Laravel سه نوع کانال را پشتیبانی میکند:
کانال عمومی. برای رویدادهایی که همه میتوانند دریافت کنند:
Broadcast::channel('announcements', function () {
return true;
});
کانال خصوصی. برای رویدادهایی که فقط کاربران مجاز دریافت میکنند:
Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
return $user->rooms()->where('id', $roomId)->exists();
});
کانال Presence. برای رویدادهایی که نیاز به اطلاع از حضور کاربران دارند:
Broadcast::channel('chat.{roomId}', function (User $user, int $roomId) {
if ($user->rooms()->where('id', $roomId)->exists()) {
return ['id' => $user->id, 'name' => $user->name];
}
});
Driverهای Broadcasting. Laravel از چند Driver پشتیبانی میکند:
Pusher. سرویس ابری که سریعترین راهاندازی را دارد اما هزینه ماهانه دارد.
Reverb. راهحل رسمی Laravel که بهصورت خودمیزبان اجرا میشود و هزینهای ندارد.
Redis + Socket.io. راهحل سنتیتر که کنترل بیشتری فراهم میکند.
Ably. جایگزین Pusher با قیمتگذاری متفاوت.
// تنظیم در .env
BROADCAST_DRIVER=reverb
// در کد
broadcast(new MessageSent($message));
در سطح عملکرد، یکی از نکات کلیدی این است که Broadcasting با Queue ترکیب شود تا ارسال رویدادها تجربه کاربر را کند نکند. برای مرور یک نمونه عملی از این ترکیب، ساخت اپلیکیشن چت با جاوااسکریپت چارچوبی از این لایه ارائه میدهد.
Task Scheduling حرفهای
Task Scheduling در Laravel، بسیار فراتر از یک Cron ساده است. این لایه، امکان تعریف زمانبندی پیچیده، اجرای شرطی و مدیریت همزمانی را فراهم میکند.
ساختار پایه در Kernel:
protected function schedule(Schedule $schedule): void
{
$schedule->command('reports:daily')->dailyAt('02:00');
$schedule->job(new CleanupTempFiles)->hourly();
$schedule->call(fn () => Cache::flush())->weekly();
}
زمانبندیهای پیشرفته. Laravel مجموعهای از زمانبندیهای آماده را ارائه میدهد:
$schedule->command('backup')->daily()->at('03:00');
$schedule->command('sync')->everyFiveMinutes();
$schedule->command('report')->weeklyOn(1, '08:00'); // دوشنبهها
$schedule->command('cleanup')->monthlyOn(1, '00:00');
$schedule->command('task')->cron('0 */6 * * *');
شرطهای اجرا. امکان اجرای شرطی بر اساس شرایط مختلف:
$schedule->command('report')
->daily()
->when(fn () => app()->isProduction())
->environments(['production']);
$schedule->command('sync')
->everyMinute()
->withoutOverlapping(); // جلوگیری از اجرای همزمان
$schedule->command('heavy-task')
->hourly()
->runInBackground(); // اجرا در پسزمینه
$schedule->command('quick-task')
->everyMinute()
->evenInMaintenanceMode(); // حتی در حالت تعمیر
گروهبندی زمانبندیها. امکان اعمال تنظیمات مشترک روی چند زمانبندی:
$schedule->command('report:daily')->daily();
$schedule->command('report:weekly')->weekly();
$schedule->command('report:monthly')->monthly();
// گروهبندی
Schedule::onOneServer()->group(function () use ($schedule) {
$schedule->command('report:daily')->daily();
$schedule->command('report:weekly')->weekly();
});
متد onOneServer، در محیطهای چندسروری، از اجرای تکراری زمانبندیها جلوگیری میکند. این متد نیازمند یک Cache Driver مشترک بین سرورهاست.
مانیتورینگ زمانبندیها. Laravel امکان ثبت لاگ و ارسال ایمیل در صورت خطا را فراهم میکند:
$schedule->command('critical-task')
->hourly()
->onFailure(fn () => Log::error('Critical task failed'))
->emailOutputOnFailure('admin@example.com');
در سطح پروژههای بزرگ، یکی از تصمیمهای مهم، استفاده از Supervisor برای مدیریت Workerها و Cron برای اجرای Scheduler است.
Gate و Policy در سطح پیشرفته
Gate و Policy، دو ابزار Laravel برای مدیریت مجوزدهی هستند. تفاوت بنیادین آنها در این است که Gate برای مجوزهای ساده و Policy برای مجوزهای مرتبط با یک مدل خاص طراحی شدهاند.
Gate در سطح پیشرفته. Gate برای مجوزهای سراسری که به مدل خاصی وابسته نیستند:
Gate::define('access-admin-panel', function (User $user) {
return $user->hasRole('admin') || $user->hasPermission('panel.access');
});
Gate::define('view-reports', function (User $user, ?Team $team = null) {
if ($user->isSuperAdmin()) return true;
if ($team === null) return false;
return $user->teams()->where('id', $team->id)->exists();
});
// استفاده
if (Gate::allows('access-admin-panel')) { /* ... */ }
Gate::authorize('view-reports', $team);
Policy در سطح پیشرفته. Policy برای مجوزهای مرتبط با یک مدل، ساختار تمیزتری ارائه میدهد:
class PostPolicy
{
public function viewAny(User $user): bool
{
return true;
}
public function view(User $user, Post $post): bool
{
if ($post->isPublished()) return true;
return $user->id === $post->user_id;
}
public function create(User $user): bool
{
return $user->hasPermission('posts.create');
}
public function update(User $user, Post $post): bool
{
if ($user->isAdmin()) return true;
return $user->id === $post->user_id && !$post->isLocked();
}
public function delete(User $user, Post $post): bool
{
if ($post->isLocked()) return false;
return $user->isAdmin() || $user->id === $post->user_id;
}
public function restore(User $user, Post $post): bool
{
return $user->isAdmin();
}
}
Policy Filters. یکی از قابلیتهای پیشرفته که امکان اعمال منطق سراسری قبل از تمام متدهای Policy را فراهم میکند:
class PostPolicy
{
public function before(User $user, string $ability): ?bool
{
if ($user->isSuperAdmin()) {
return true; // Super Admin همیشه مجاز
}
return null; // در غیر این صورت، تصمیم به متد اصلی واگذار میشود
}
}
این الگو، از تکرار منطق در هر متد جلوگیری میکند.
استفاده در Blade. Laravel امکان استفاده از Gate و Policy در قالبها را فراهم میکند:
@can('update', $post)
<a href="{{ route('posts.edit', $post) }}">ویرایش</a>
@endcan
@canany(['update', 'delete'], $post)
<div class="actions">...</div>
@endcanany
در سطح امنیت، یکی از اصول مهم این است که Gate و Policy نباید جایگزین اعتبارسنجی در سطح دیتابیس شوند. هر کوئری که بر اساس مجوز فیلتر میشود، باید در سطح خود کوئری نیز محدود شود. اگر با این حوزه آشنا نیستید، امنیت بکاند چارچوبی از این لایهها ارائه میدهد.
قواعد اعتبارسنجی سفارشی
Laravel مجموعه بزرگی از قواعد اعتبارسنجی را ارائه میدهد، اما در پروژههای واقعی، نیاز به قواعد سفارشی مکرراً پیش میآید. تسلط بر این لایه، دقت اعتبارسنجی را بهطور محسوسی افزایش میدهد.
Rule Objects. روش توصیهشده برای قواعد پیچیده:
class IranianNationalCode implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (!preg_match('/^\d{10}$/', $value)) {
$fail('کد ملی باید ۱۰ رقم باشد.');
return;
}
if (preg_match('/^(\d)\1{9}$/', $value)) {
$fail('کد ملی معتبر نیست.');
return;
}
$check = (int) $value[9];
$sum = 0;
for ($i = 0; $i < 9; $i++) {
$sum += (int) $value[$i] * (10 - $i);
}
$remainder = $sum % 11;
$expected = $remainder < 2 ? $remainder : 11 - $remainder;
if ($check !== $expected) {
$fail('کد ملی معتبر نیست.');
}
}
}
استفاده در Request:
class StoreUserRequest extends FormRequest
{
public function rules(): array
{
return [
'national_code' => ['required', new IranianNationalCode()],
'email' => ['required', 'email', Rule::unique('users')],
];
}
}
قواعد شرطی. Laravel امکان تعریف قواعد بر اساس شرایط را فراهم میکند:
public function rules(): array
{
return [
'type' => ['required', 'in:personal,business'],
'national_code' => [
Rule::requiredIf($this->input('type') === 'personal'),
new IranianNationalCode(),
],
'company_registration' => [
Rule::requiredIf($this->input('type') === 'business'),
'string',
],
];
}
پیامهای سفارشی. امکان تعریف پیامهای فارسی و دقیق:
public function messages(): array
{
return [
'national_code.required' => 'وارد کردن کد ملی الزامی است.',
'email.unique' => 'این ایمیل قبلاً ثبت شده است.',
];
}
public function attributes(): array
{
return [
'national_code' => 'کد ملی',
'email' => 'پست الکترونیک',
];
}
Custom Validation Rule با پارامتر. امکان تعریف قاعدهای که پارامتر دریافت میکند:
class MinWords implements ValidationRule
{
public function __construct(
private int $min
) {}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$wordCount = str_word_count(strip_tags($value));
if ($wordCount < $this->min) {
$fail(":attribute باید حداقل {$this->min} کلمه داشته باشد.");
}
}
}
// استفاده
'content' => ['required', new MinWords(300)],
Horizon و Telescope؛ چشمان پروژه
Horizon و Telescope دو ابزار رسمی Laravel هستند که در سطح پیشرفته، نقش چشم و گوش پروژه را بازی میکنند. بدون این دو ابزار، دیباگ مسائل پیچیده در محیط تولید تقریباً غیرممکن است.
Laravel Horizon. یک داشبورد زیبا و قدرتمند برای مدیریت Queueهای مبتنی بر Redis:
composer require laravel/horizon
php artisan horizon:install
php artisan horizon
قابلیتهای کلیدی Horizon:
قابلیت اول، مانیتورینگ زنده. نمایش تعداد Jobهای در انتظار، در حال اجرا و شکستخورده بهصورت زنده.
قابلیت دوم، متریکهای تاریخی. ثبت throughput و runtime Jobها در طول زمان.
قابلیت سوم، Auto-scaling. تنظیم خودکار تعداد Workerها بر اساس بار:
// config/horizon.php
'environments' => [
'production' => [
'supervisor-default' => [
'connection' => 'redis',
'queue' => ['default'],
'balance' => 'auto',
'minProcesses' => 1,
'maxProcesses' => 10,
'balanceMaxShift' => 1,
'balanceCooldown' => 3,
],
],
],
Laravel Telescope. ابزار دیباگ که تمام درخواستها، کوئریها، Jobها، Exceptionها، لاگها و Eventها را ثبت میکند:
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate
قابلیتهای کلیدی Telescope:
قابلیت اول، ثبت تمام درخواستها. شامل هدرها، پارامترها، پاسخ و زمان اجرا.
قابلیت دوم، ثبت تمام کوئریها. با نمایش SQL، پارامترها و زمان اجرا.
قابلیت سوم، ثبت Exceptionها. با نمایش Stack Trace و دادههای مرتبط.
قابلیت چهارم، ثبت Jobها. با نمایش وضعیت (موفق، شکستخورده، در انتظار).
// محدودسازی Telescope به محیط توسعه
// app/Providers/TelescopeServiceProvider.php
public function register(): void
{
Telescope::night();
$this->hideSensitiveParameters();
Telescope::filter(function (IncomingEntry $entry) {
if ($this->app->environment('local')) {
return true;
}
return $entry->isReportableException() ||
$entry->isFailedJob() ||
$entry->isScheduledTask();
});
}
این الگو، از ثبت دادههای حساس در محیط تولید جلوگیری میکند. برای آشنایی با اصول امنیت در محیط تولید، امنیت بکاند چارچوب کاملی ارائه میدهد.
Horizon و Telescope، سرمایهگذاری کوچکی هستند که بهرهوری تیم را در بلندمدت چند برابر میکنند؛ نبود آنها در پروژههای بزرگ، یک انتخاب پرهزینه است.
Laravel Octane و عملکرد فوقسریع
Laravel Octane یکی از پیشرفتهترین قابلیتهای Laravel است که با استفاده از Swoole یا RoadRunner، اپلیکیشن را در حافظه نگه میدارد و از راهاندازی مجدد در هر درخواست جلوگیری میکند. این تغییر معماری، عملکرد اپلیکیشن را چند برابر افزایش میدهد.
composer require laravel/octane
php artisan octane:install
php artisan octane:start --workers=4 --task-workers=6
تفاوت بنیادین با PHP-FPM:
| معیار | PHP-FPM | Octane |
|---|---|---|
| چرخه عمر اپلیکیشن | هر درخواست | در حافظه |
| سرعت Bootstrap | کند | سریع |
| مصرف حافظه | پایین | بالا |
| State بین درخواستها | ندارد | دارد (نیاز به مدیریت) |
مفهوم State در Octane. در Octane، اپلیکیشن بین درخواستها زنده میماند. این مزیت بزرگ، به یک چالش نیز تبدیل میشود: state میتواند بین درخواستها نشت کند. برای مدیریت این چالش، Laravel مفهوم scoped را معرفی کرده:
// در ServiceProvider
$this->app->scoped(UserContext::class);
// UserContext در هر درخواست، نمونه جدیدی خواهد داشت
هر سرویسی که state مختص یک درخواست را نگه میدارد، باید با scoped ثبت شود، نه singleton.
Concurrent Tasks. یکی از قابلیتهای منحصربهفرد Octane که امکان اجرای موازی عملیات را فراهم میکند:
use Laravel\Octane\Facades\Octane;
[$users, $orders, $products] = Octane::concurrently([
fn () => User::all(),
fn () => Order::all(),
fn () => Product::all(),
]);
این الگو، در سناریوهایی که چند عملیات مستقل باید انجام شوند، زمان پاسخ را بهطور محسوسی کاهش میدهد.
Tick و Interval. امکان اجرای دورهای عملیات در حافظه:
Octane::tick('metrics', function () {
Cache::put('server-load', sys_getloadavg(), 60);
})->seconds(10);
ملاحظات استفاده از Octane. در استفاده از این ابزار، سه نکته کلیدی باید در نظر گرفته شود:
نکته اول، عدم استفاده از state سراسری. هر متغیر استاتیک یا Singleton که state درخواست را نگه میدارد، باید بازبینی شود.
نکته دوم، بازنشانی سرویسها. برخی سرویسها مثل دیتابیس و کش، باید در هر درخواست بازنشانی شوند.
نکته سوم، تست دقیق. رفتار اپلیکیشن تحت Octane میتواند با PHP-FPM متفاوت باشد. تست در محیط نزدیک به تولید ضروری است.
در سطح پروژههای بزرگ، یکی از تصمیمهای مهم، انتخاب بین Swoole و RoadRunner است. Swoole عملکرد بالاتری دارد اما نیازمند نصب Extension است. RoadRunner سادهتر و مبتنی بر Go است.
تست پیشرفته و Mocking
تست در Laravel، فراتر از Feature Test ساده است. لایههای پیشرفتهای وجود دارند که هر کدام برای سناریوی خاصی طراحی شدهاند.
Mock کردن Service Container. امکان جایگزینی سرویسها در تست:
public function test_order_uses_correct_gateway(): void
{
$gateway = Mockery::mock(PaymentGateway::class);
$gateway->shouldReceive('charge')
->once()
->with(1000)
->andReturn(new PaymentResult(success: true));
$this->app->instance(PaymentGateway::class, $gateway);
$response = $this->post('/orders', [
'amount' => 1000,
]);
$response->assertRedirect();
}
Mock کردن Facade. Laravel ابزار اختصاصی برای این کار دارد:
public function test_cache_is_used(): void
{
Cache::shouldReceive('remember')
->once()
->with('orders', 600, Mockery::any())
->andReturn(collect([new Order()]));
$response = $this->get('/orders');
$response->assertOk();
}
Time Freezing. یکی از قابلیتهای مهم برای تست عملیات زمان-محور:
public function test_subscription_expires_after_30_days(): void
{
$this->freezeTime(function (Carbon $time) {
$subscription = Subscription::create([
'user_id' => 1,
'started_at' => $time,
]);
$time->addDays(31);
$this->assertTrue($subscription->fresh()->isExpired());
});
}
Parallel Testing. یکی از قابلیتهای پیشرفته که زمان اجرای تستها را چند برابر کاهش میدهد:
php artisan test --parallel --processes=4
در سطح پروژههای بزرگ، هدف Coverage حرفهای بین ۷۰ تا ۸۰ درصد است. تلاش برای ۱۰۰ درصد، معمولاً به تستهای بیارزش منجر میشود که نگهداری آنها پرهزینهتر از فایدهشان است.
مفهوم Test Doubles. سه نوع مختلف:
نوع اول، Stub. پاسخ ثابت بازمیگرداند.
نوع دوم، Mock. علاوه بر پاسخ، انتظارات را نیز بررسی میکند.
نوع سوم، Spy. فراخوانیها را ثبت میکند و در پایان تست، بررسی میشوند.
// Spy
$spy = Mockery::spy(NotificationService::class);
// اجرای کد
$spy->shouldHaveReceived('send')->once();
برای درک عمیقتر استراتژی تست در پروژههای PHP، بهینهسازی عملکرد بکاند چارچوبی از این لایهها ارائه میدهد.
دامهایی که قابلیتهای پیشرفته را بیاثر میکنند
در بازبینی پروژههای متعدد، مجموعهای از دامهای مشخص را دیدهام که استفاده از قابلیتهای پیشرفته را بیاثر میکند.
دام اول، استفاده از Service Container بدون درک دامنه. وقتی Service Container برای هر چیزی استفاده شود، وابستگیها مبهم میشوند. Service Container باید فقط برای سرویسهای واقعی بهکار رود.
دام دوم، Event برای هر عملیات. وقتی هر عملیات به یک Event تبدیل شود، دیباگ کردن جریان اجرا غیرممکن میشود. Events باید فقط برای منطق جانبی و اختیاری استفاده شوند.
دام سوم، Queue برای همه چیز. وقتی هر عملیاتی در Queue اجرا شود، پیچیدگی سیستم چند برابر میشود و دیباگ مسائل پیچیده میشود.
دام چهارم، نبود مانیتورینگ. استفاده از Queue و Broadcasting بدون Horizon یا ابزارهای مشابه، به مسائل پنهان منجر میشود.
دام پنجم، نادیده گرفتن state در Octane. انتقال به Octane بدون بازبینی state، به نشت داده بین کاربران و مسائل امنیتی جدی منجر میشود.
دام ششم، Policyهای پیچیده بدون تست. منطق مجوزدهی، حساسترین بخش اپلیکیشن است و باید بهطور کامل تست شود.
دام هفتم، Broadcast بدون احراز کانال. نبود احراز در کانالهای خصوصی، به نشت داده به کاربران غیرمجاز منجر میشود.
دام هشتم، Scheduling بدون نظارت. زمانبندیهایی که اجرا نمیشوند و کسی متوجه نمیشود، به بحرانهای پنهان منجر میشوند.
دام نهم، Facade در همه جا. استفاده بیرویه از Facade، وابستگیها را پنهان میکند و تستپذیری را کاهش میدهد.
دام دهم، نبود مستندسازی معماری. استفاده از قابلیتهای پیشرفته بدون مستندسازی، پروژه را به یک جعبه سیاه تبدیل میکند.
دام یازدهم، تست نکردن Mockها. Mockهایی که بهدرستی تنظیم نشدهاند، به تستهای کاذب موفق منجر میشوند.
دام دوازدهم، نادیده گرفتن سرعت راهاندازی. Providerهای سنگین که در هر درخواست بارگذاری میشوند، زمان Bootstrap را افزایش میدهند. استفاده از Deferred Providerها این مشکل را حل میکند.
برای مرور اشتباهات رایج در سطح معماری، اشتباهات رایج در توسعه بکاند چارچوب کاملی ارائه میدهد.
پرسشهای پرتکرار درباره قابلیتهای پیشرفته Laravel
Service Container در Laravel چیست؟ لایهای است که مسئول مدیریت نمونههای کلاسها و تزریق خودکار وابستگیهاست. این لایه، پایه معماری Laravel است.
تفاوت bind و singleton در Service Container چیست؟ در bind، هر بار نمونه جدیدی ساخته میشود. در singleton، فقط یک نمونه در طول درخواست ساخته میشود.
Deferred Service Provider چیست؟ Providerی که فقط زمانی بارگذاری میشود که سرویس آن واقعاً درخواست شود. این کار، زمان Bootstrap اپلیکیشن را کاهش میدهد.
Events و Listeners چه مزیتی دارند؟ جداسازی منطق جانبی از منطق اصلی، قابلیت گسترش بدون تغییر کد اصلی، و امکان اجرا در Queue.
Job Chaining چه تفاوتی با Job Batching دارد؟ Chaining اجرای ترتیبی Jobهاست که اگر یکی شکست بخورد، بقیه متوقف میشوند. Batching اجرای موازی مجموعهای از Jobها با امکان پیگیری پیشرفت است.
Broadcasting چطور کار میکند؟ با ارسال رویدادها به یک Driver (مثل Pusher یا Reverb) که آنها را به کلاینتهای متصل ارسال میکند.
تفاوت Gate و Policy چیست؟ Gate برای مجوزهای سراسری بدون مدل خاص، Policy برای مجوزهای مرتبط با یک مدل.
Policy Filter چیست؟ متد before در Policy که قبل از تمام متدهای دیگر اجرا میشود و امکان اعمال منطق سراسری (مثل Super Admin) را فراهم میکند.
Laravel Horizon چیست؟ داشبورد و سیستم مدیریت Queueهای مبتنی بر Redis با قابلیت مانیتورینگ، متریک و Auto-scaling.
Laravel Telescope چیست؟ ابزار دیباگ که تمام درخواستها، کوئریها، Jobها، Exceptionها و Eventها را ثبت میکند. برای محیط توسعه طراحی شده است.
Laravel Octane چه مزیتی دارد؟ با نگهداشتن اپلیکیشن در حافظه، سرعت Bootstrap را حذف میکند و عملکرد را چند برابر افزایش میدهد.
State در Octane چطور مدیریت میشود؟ با استفاده از scoped بهجای singleton برای سرویسهایی که state مختص درخواست دارند.
Concurrent Tasks در Octane چیست؟ قابلیتی که امکان اجرای موازی چند عملیات مستقل را فراهم میکند و زمان پاسخ را کاهش میدهد.
چطور قواعد اعتبارسنجی سفارشی بسازم؟ با پیادهسازی کلاس Rule که اینترفیس ValidationRule را پیاده میکند.
Parallel Testing در Laravel چطور کار میکند؟ با تقسیم تستها بین چند Process، زمان اجرا را چند برابر کاهش میدهد. با php artisan test --parallel فعال میشود.
چطور Facade را Mock کنم؟ با استفاده از Facade::shouldReceive() که امکان تعریف انتظارات را فراهم میکند.
Job Middleware چیست؟ لایهای که امکان اجرای منطق قبل یا بعد از Job را فراهم میکند (مثل Rate Limiting یا Logging).
چطور Queue را برای محیط چندسروری پیکربندی کنم؟ با استفاده از Redis یا یک Driver مشترک، و ترکیب onOneServer در Scheduler برای جلوگیری از اجرای تکراری.
پرسشی که پیش از استفاده از این قابلیتها باید پاسخ دهید
پیش از آنکه قابلیتهای پیشرفته Laravel را وارد پروژه کنید، یک پرسش بنیادین را از خود بپرسید: «این قابلیت، کدام مسئله واقعی را حل میکند و اگر امروز آن را اضافه نکنم، چه اتفاقی میافتد؟» اگر پاسخ روشن است، قابلیت انتخاب درستی است. اگر پاسخ مبهم است، احتمالاً اضافه کردن آن، فقط پیچیدگی را افزایش میدهد بدون آنکه ارزش مشخصی ایجاد کند.
قابلیتهای پیشرفته Laravel، ابزارهایی هستند که در دستان آشنا، به موتور رشد پروژه تبدیل میشوند و در دستان ناآشنا، به بدهی فنی. تفاوت این دو، در سطح دانش فنی نیست؛ در سطح درک عملی از دامنه مسئله است. اگر تجربهای از بهکارگیری یکی از این قابلیتها در پروژه واقعی دارید — چه موفق و چه ناموفق — برای بازخورد ارزشمند است بدانید. مخصوصاً اگر در سناریوی خاصی از ترکیب غیرمعمولی از این ابزارها استفاده کردهاید که توانسته مشکل مشخصی را حل کند. تجربهی خودتان را در دیدگاهها بنویسید؛ همان راهحلها میتوانند به خواننده بعدی کمک کنند.