Blade المتقدم ومنشئو العروض
بينما تكون قوالب Blade الأساسية واضحة ومباشرة، يوفر Laravel ميزات متقدمة قوية لبناء طبقات عرض معقدة وقابلة للصيانة. في هذا الدرس، سنستكشف منشئو العروض، ومنشئو العروض، وأنماط البيانات المشتركة، وتوجيهات Blade المخصصة، وتقنيات المكونات المتقدمة، والمكونات المجهولة لبناء واجهات مستخدم متطورة.
منشئو العروض
تسمح لك منشئو العروض بربط البيانات بالعروض كلما تم عرضها، مما يحافظ على نظافة المتحكمات الخاصة بك ويتجنب تكرار الكود.
// App\Providers\ViewServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\View;
use App\Models\Category;
use App\Models\Setting;
class ViewServiceProvider extends ServiceProvider
{
public function boot()
{
// منشئ لعرض واحد
View::composer('products.index', function ($view) {
$view->with('categories', Category::all());
});
// منشئ لعروض متعددة
View::composer(['products.*', 'shop.*'], function ($view) {
$view->with([
'categories' => Category::active()->get(),
'brands' => Brand::popular()->get(),
]);
});
// منشئ باستخدام حرف بدل
View::composer('partials.*', function ($view) {
$view->with('settings', Setting::cached());
});
// منشئ لجميع العروض
View::composer('*', function ($view) {
$view->with('siteName', config('app.name'));
});
// استخدام فئة مخصصة
View::composer('layouts.sidebar', 'App\Http\View\Composers\SidebarComposer');
// منشئون متعددون لعرض واحد
View::composer('dashboard', [
'App\Http\View\Composers\StatsComposer',
'App\Http\View\Composers\NotificationsComposer',
]);
}
}
// فئة منشئ مخصصة
namespace App\Http\View\Composers;
use Illuminate\View\View;
use App\Models\Category;
use App\Models\Tag;
class SidebarComposer
{
public function compose(View $view)
{
$view->with([
'recentPosts' => Post::latest()->take(5)->get(),
'popularTags' => Tag::withCount('posts')
->orderBy('posts_count', 'desc')
->take(10)
->get(),
'categories' => Category::withCount('posts')
->get(),
]);
}
}
ملاحظة: يتم حل منشئو العروض من حاوية الخدمة، لذا يمكنك تلميح نوع التبعيات في منشئيهم للحقن التلقائي للتبعية.
منشئو العروض
منشئو العروض مشابهون للمنشئين ولكنهم ينفذون فوراً عند إنشاء العرض، بدلاً من قبل العرض فقط.
// في ViewServiceProvider
public function boot()
{
// ينفذ المنشئ فوراً عند إنشاء العرض
View::creator('layouts.app', function ($view) {
$view->with('appVersion', config('app.version'));
});
// حالة استخدام: تعيين البيانات الافتراضية قبل أي معالجة
View::creator('admin.*', function ($view) {
$view->with([
'user' => auth()->user(),
'permissions' => auth()->user()->permissions ?? [],
'adminMenu' => $this->buildAdminMenu(),
]);
});
}
// مثال عملي: افتراضيات النموذج
namespace App\Http\View\Creators;
class FormDefaultsCreator
{
public function create($view)
{
// تعيين تكوين النموذج الافتراضي
$view->with([
'formMethod' => 'POST',
'formEnctype' => 'multipart/form-data',
'submitText' => 'Submit',
'cancelUrl' => url()->previous(),
]);
}
}
View::creator('forms.*', FormDefaultsCreator::class);
مشاركة البيانات عبر جميع العروض
شارك البيانات الشائعة عبر جميع العروض بكفاءة باستخدام طرق مختلفة.
// الطريقة 1: View::share() في مزود الخدمة
public function boot()
{
View::share('siteName', config('app.name'));
View::share('currentYear', date('Y'));
// بيانات مشتركة ديناميكية
View::share('cartCount', function () {
return auth()->check() ? auth()->user()->cart->count() : 0;
});
}
// الطريقة 2: وسيط لبيانات المستخدم المصادق عليه
namespace App\Http\Middleware;
class ShareAuthenticatedUserData
{
public function handle($request, Closure $next)
{
if (auth()->check()) {
View::share('currentUser', auth()->user());
View::share('notifications', auth()->user()->unreadNotifications);
}
return $next($request);
}
}
// الطريقة 3: منشئ العرض للبيانات المشتركة المعقدة
View::composer('*', function ($view) {
$view->with([
'navigation' => Cache::remember('main-navigation', 3600, function () {
return Menu::where('location', 'main')->with('items')->first();
}),
'footerLinks' => Cache::remember('footer-links', 3600, function () {
return Menu::where('location', 'footer')->with('items')->first();
}),
]);
});
// الطريقة 4: استخدام التكوين لبيانات العرض العامة
// config/view.php
return [
'shared' => [
'site_name' => env('APP_NAME', 'Laravel'),
'contact_email' => env('CONTACT_EMAIL', 'info@example.com'),
'social_links' => [
'twitter' => 'https://twitter.com/example',
'facebook' => 'https://facebook.com/example',
],
],
];
// في مزود الخدمة
View::share(config('view.shared'));
نصيحة: للعمليات المكلفة مثل استعلامات قاعدة البيانات في البيانات المشتركة، استخدم دائماً التخزين المؤقت لتجنب مشاكل الأداء. استخدم Cache::remember() مع قيم TTL مناسبة.
توجيهات Blade المخصصة
أنشئ توجيهات Blade قابلة لإعادة الاستخدام للأنماط الشائعة والمنطق المعقد.
// في AppServiceProvider أو BladeServiceProvider المخصص
use Illuminate\Support\Facades\Blade;
public function boot()
{
// توجيه بسيط
Blade::directive('datetime', function ($expression) {
return "<?php echo ($expression)->format('m/d/Y H:i'); ?>";
});
// توجيه مع قيمة افتراضية
Blade::directive('money', function ($expression) {
return "<?php echo '$' . number_format($expression, 2); ?>";
});
// توجيه شرطي
Blade::directive('admin', function () {
return "<?php if(auth()->check() && auth()->user()->isAdmin()): ?>";
});
Blade::directive('endadmin', function () {
return "<?php endif; ?>";
});
// توجيه قائم على الدور
Blade::directive('role', function ($role) {
return "<?php if(auth()->check() && auth()->user()->hasRole({$role})): ?>";
});
Blade::directive('endrole', function () {
return "<?php endif; ?>";
});
// توجيه معقد مع معلمات
Blade::directive('icon', function ($expression) {
return "<?php echo '<i class=\"fas fa-' . {$expression} . '\"></i>'; ?>";
});
// توجيه لـ SVG المضمّن
Blade::directive('svg', function ($expression) {
return "<?php echo file_get_contents(public_path('icons/' . {$expression} . '.svg')); ?>";
});
// توجيه التخزين المؤقت
Blade::directive('cache', function ($expression) {
return "<?php if(app('cache')->has({$expression})): echo app('cache')->get({$expression}); else: ob_start(); ?>";
});
Blade::directive('endcache', function ($expression) {
return "<?php \$content = ob_get_clean(); app('cache')->put({$expression}, \$content, 3600); echo \$content; endif; ?>";
});
// توجيه فئة HTML
Blade::directive('class', function ($expression) {
return "<?php echo 'class=\"' . implode(' ', array_filter({$expression})) . '\"'; ?>";
});
// توجيه المسار النشط
Blade::directive('routeactive', function ($expression) {
return "<?php echo request()->routeIs({$expression}) ? 'active' : ''; ?>";
});
}
// الاستخدام في قوالب Blade
@@datetime($post->created_at)
@@money(1234.56) // الإخراج: $1,234.56
@@admin
<a href="/admin">لوحة الإدارة</a>
@@endadmin
@@role('editor')
<button>تحرير المنشور</button>
@@endrole
@@icon('home')
@@svg('logo')
@@cache('sidebar-content')
<!-- محتوى مكلف هنا -->
@@endcache
<div @@class(['btn', 'btn-primary', $active ? 'active' : ''])>
<a class="@@routeactive('home')">الرئيسية</a>
تحذير: كن حذراً مع التوجيهات المخصصة التي تنفذ منطقاً معقداً. يمكن أن تجعل القوالب أصعب في التصحيح. استخدمها للأنماط المتكررة، وليس لمنطق الأعمال.
مكونات Blade المتقدمة
ابنِ مكونات متطورة وقابلة لإعادة الاستخدام مع فتحات وسمات وسلوك ديناميكي.
// resources/views/components/alert.blade.php
@@props([
'type' => 'info',
'dismissible' => false,
'icon' => null,
])
@@php
$classes = [
'alert',
'alert-' . $type,
$dismissible ? 'alert-dismissible' : '',
];
$icons = [
'success' => 'check-circle',
'error' => 'times-circle',
'warning' => 'exclamation-triangle',
'info' => 'info-circle',
];
$iconName = $icon ?? $icons[$type] ?? 'info-circle';
@@endphp
<div {{ $attributes->merge(['class' => implode(' ', $classes)]) }}>
@@if($dismissible)
<button type="button" class="close" data-dismiss="alert">
<span>×</span>
</button>
@@endif
<i class="fas fa-{{ $iconName }}"></i>
<div class="alert-content">
@@if(isset($title))
<h4 class="alert-title">{{ $title }}</h4>
@@endif
{{ $slot }}
@@if(isset($actions))
<div class="alert-actions">
{{ $actions }}
</div>
@@endif
</div>
</div>
// الاستخدام
<x-alert type="success" dismissible>
<x-slot:title>نجاح!</x-slot>
تم حفظ تغييراتك.
<x-slot:actions>
<a href="/dashboard">اذهب إلى لوحة التحكم</a>
</x-slot>
</x-alert>
// مكون مع منطق قائم على الفئة
// app/View/Components/DataTable.php
namespace App\View\Components;
use Illuminate\View\Component;
class DataTable extends Component
{
public $columns;
public $rows;
public $sortable;
public $searchable;
public function __construct($columns, $rows, $sortable = true, $searchable = true)
{
$this->columns = $columns;
$this->rows = $rows;
$this->sortable = $sortable;
$this->searchable = $searchable;
}
public function shouldShowPagination()
{
return method_exists($this->rows, 'links');
}
public function render()
{
return view('components.data-table');
}
}
// resources/views/components/data-table.blade.php
<div class="data-table-wrapper">
@@if($searchable)
<input type="text" class="table-search" placeholder="بحث...">
@@endif
<table class="table">
<thead>
<tr>
@@foreach($columns as $column)
<th>
{{ $column['label'] }}
@@if($sortable && ($column['sortable'] ?? true))
<i class="sort-icon"></i>
@@endif
</th>
@@endforeach
</tr>
</thead>
<tbody>
@@forelse($rows as $row)
<tr>
@@foreach($columns as $column)
<td>{{ data_get($row, $column['field']) }}</td>
@@endforeach
</tr>
@@empty
<tr>
<td colspan="{{ count($columns) }}">لا توجد بيانات متاحة</td>
</tr>
@@endforelse
</tbody>
</table>
@@if($this->shouldShowPagination())
{{ $rows->links() }}
@@endif
</div>
// الاستخدام
<x-data-table
:columns="[
['label' => 'الاسم', 'field' => 'name'],
['label' => 'البريد الإلكتروني', 'field' => 'email'],
['label' => 'الحالة', 'field' => 'status', 'sortable' => false],
]"
:rows="$users"
/>
المكونات المجهولة
أنشئ مكونات بسيطة بدون فئات مخصصة لعناصر واجهة مستخدم قابلة لإعادة الاستخدام بسرعة.
// resources/views/components/card.blade.php
@@props([
'title' => null,
'footer' => null,
'padding' => true,
])
<div {{ $attributes->merge(['class' => 'card']) }}>
@@if($title)
<div class="card-header">
<h3>{{ $title }}</h3>
</div>
@@endif
<div class="card-body {{ $padding ? 'p-4' : 'p-0' }}">
{{ $slot }}
</div>
@@if($footer)
<div class="card-footer">
{{ $footer }}
</div>
@@endif
</div>
// resources/views/components/button.blade.php
@@props([
'variant' => 'primary',
'size' => 'md',
'type' => 'button',
'icon' => null,
])
<button
type="{{ $type }}"
{{ $attributes->merge(['class' => "btn btn-{$variant} btn-{$size}"]) }}
>
@@if($icon)
<i class="fas fa-{{ $icon }}"></i>
@@endif
{{ $slot }}
</button>
// resources/views/components/modal.blade.php
@@props([
'id',
'title',
'size' => 'md', // sm, md, lg, xl
])
<div class="modal fade" id="{{ $id }}" tabindex="-1">
<div class="modal-dialog modal-{{ $size }}">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ $title }}</h5>
<button type="button" class="close" data-dismiss="modal">
<span>×</span>
</button>
</div>
<div class="modal-body">
{{ $slot }}
</div>
@@if(isset($footer))
<div class="modal-footer">
{{ $footer }}
</div>
@@endif
</div>
</div>
</div>
// أمثلة الاستخدام
<x-card title="ملف المستخدم">
<p>تذهب معلومات المستخدم هنا</p>
</x-card>
<x-button variant="success" icon="check">
حفظ التغييرات
</x-button>
<x-modal id="confirmModal" title="تأكيد الإجراء" size="sm">
هل أنت متأكد أنك تريد المتابعة؟
<x-slot:footer>
<x-button variant="secondary" data-dismiss="modal">إلغاء</x-button>
<x-button variant="danger">تأكيد</x-button>
</x-slot>
</x-modal>
تمرين 1: أنشئ نظام منشئ عرض لموقع تجارة إلكترونية. نفذ منشئين لـ: (1) بيانات عربة التسوق على جميع الصفحات، (2) فئات المنتجات في الشريط الجانبي، (3) المنتجات المشاهدة مؤخراً. استخدم التخزين المؤقت لتحسين الأداء وتأكد من تشغيل المنشئين فقط عند الضرورة.
تمرين 2: ابنِ مكتبة توجيهات Blade مخصصة مع: (1) @@currency لتنسيق الأموال مع دعم اللغة، (2) @@permission/@@endpermission للمحتوى القائم على الدور، (3) @@truncate لاقتطاع النص مع روابط "قراءة المزيد"، (4) @@markdown لعرض محتوى markdown. اختبر كل توجيه بشكل شامل.
تمرين 3: طور مكتبة مكونات واجهة مستخدم شاملة باستخدام مكونات Blade. أنشئ: (1) DataTable مع الفرز والتصفية والترقيم، (2) منشئ النموذج مع عرض التحقق، (3) TabPanel مع علامات تبويب ديناميكية، (4) نظام الإخطارات مع أنواع مختلفة (نجاح، خطأ، تحذير). اجعل جميع المكونات قابلة للتخصيص بدرجة عالية مع props وslots.