إطار Laravel

محرك قوالب Blade

20 دقيقة الدرس 5 من 45

مقدمة في Blade

Blade هو محرك القوالب القوي في لارافيل الذي يجعل كتابة العروض ممتعة ومنتجة. على عكس محركات قوالب PHP الأخرى، لا يقيدك Blade من استخدام كود PHP عادي في عروضك.

لماذا Blade؟ يوفر Blade اختصارات مريحة لعمليات PHP الشائعة، ووراثة القوالب، والبنية القائمة على المكونات، ويُجمَّع إلى PHP عادي للحصول على أداء مثالي. يتم تخزين جميع عروض Blade مؤقتاً حتى يتم تعديلها!

موقع وتسمية ملفات Blade

تُخزن قوالب Blade في resources/views وتستخدم امتداد الملف .blade.php:

resources/views/ ├── layouts/ │ └── app.blade.php ├── posts/ │ ├── index.blade.php │ ├── show.blade.php │ ├── create.blade.php │ └── edit.blade.php ├── home.blade.php └── welcome.blade.php

عرض البيانات

عبارات Echo

عرض البيانات الممررة إلى عروضك باستخدام الأقواس المتعرجة المزدوجة:

<!-- صياغة Blade --> <h1>{{ $title }}</h1> <p>مرحباً، {{ $name }}!</p> <!-- يُجمَّع إلى: --> <h1><?php echo htmlspecialchars($title, ENT_QUOTES, 'UTF-8'); ?></h1> <p>مرحباً، <?php echo htmlspecialchars($name, ENT_QUOTES, 'UTF-8'); ?>!</p>
التهريب التلقائي: عبارات {{ }} في Blade تقوم تلقائياً بتهريب HTML لمنع هجمات XSS. يتم تمرير البيانات عبر دالة PHP htmlspecialchars.

عرض البيانات غير المهربة

عندما تحتاج إلى عرض HTML الخام (استخدمه بحذر!):

<!-- إخراج HTML الخام (غير مهرب) --> <div>{!! $htmlContent !!}</div> <!-- مثال: عرض نص غني من قاعدة البيانات --> <article> <h1>{{ $post->title }}</h1> <div>{!! $post->content !!}</div> </article>
تحذير أمني: استخدم فقط {!! !!} مع البيانات الموثوقة! لا تستخدمه أبداً مع محتوى مقدم من المستخدم دون تعقيم مناسب، حيث يمكن أن يؤدي إلى ثغرات XSS.

عرض JSON

<script> // يقوم Blade تلقائياً بالترميز إلى JSON var app = @json($array); // مع الخيارات var app = @json($array, JSON_PRETTY_PRINT); </script>

القيم الافتراضية

<!-- عرض $name أو 'ضيف' إذا كان $name غير معرف --> <p>مرحباً، {{ $name ?? 'ضيف' }}</p> <!-- استخدام عامل ternary --> <p>الحالة: {{ $isActive ? 'نشط' : 'غير نشط' }}</p>

توجيهات Blade

العبارات الشرطية

<!-- @if, @elseif, @else, @endif --> @if ($user->isAdmin()) <p>مرحباً، المدير!</p> @elseif ($user->isModerator()) <p>مرحباً، المشرف!</p> @else <p>مرحباً، المستخدم!</p> @endif <!-- @unless (عكس @if) --> @unless ($user->isPremium()) <div class="upgrade-banner"> قم بالترقية إلى Premium! </div> @endunless <!-- @isset و @empty --> @isset($records) <p>السجلات متاحة</p> @endisset @empty($records) <p>لم يتم العثور على سجلات</p> @endempty

توجيهات المصادقة

<!-- التحقق من تسجيل دخول المستخدم --> @auth <p>أنت مسجل الدخول!</p> @endauth <!-- التحقق من أن المستخدم ضيف --> @guest <a href="/login">يرجى تسجيل الدخول</a> @endguest <!-- مزيج --> @auth <a href="/dashboard">لوحة التحكم</a> @else <a href="/login">تسجيل الدخول</a> @endauth

عبارات Switch

@switch($role) @case('admin') <p>وصول المدير</p> @break @case('editor') <p>وصول المحرر</p> @break @case('viewer') <p>وصول المشاهدة فقط</p> @break @default <p>وصول الضيف</p> @endswitch

الحلقات

<!-- حلقة @for --> @for ($i = 0; $i < 10; $i++) <p>العنصر {{ $i }}</p> @endfor <!-- حلقة @foreach --> @foreach ($posts as $post) <article> <h2>{{ $post->title }}</h2> <p>{{ $post->excerpt }}</p> </article> @endforeach <!-- @forelse (مع حالة فارغة) --> @forelse ($posts as $post) <article> <h2>{{ $post->title }}</h2> </article> @empty <p>لا توجد منشورات متاحة.</p> @endforelse <!-- حلقة @while --> @while ($condition) <p>لا تزال الحلقة تعمل...</p> @endwhile

متغير الحلقة

داخل الحلقات، يوفر Blade متغير $loop مع معلومات مفيدة:

@foreach ($posts as $post) <div class="post {{ $loop->first ? 'first' : '' }} {{ $loop->last ? 'last' : '' }} {{ $loop->even ? 'even' : 'odd' }}"> <h3>{{ $post->title }}</h3> <!-- معلومات الحلقة --> <small> المنشور {{ $loop->iteration }} من {{ $loop->count }} (الفهرس: {{ $loop->index }}، المتبقي: {{ $loop->remaining }}) </small> </div> @endforeach

خصائص $loop المتاحة:

  • $loop->index - فهرس التكرار الحالي (يبدأ من 0)
  • $loop->iteration - رقم التكرار الحالي (يبدأ من 1)
  • $loop->remaining - التكرارات المتبقية
  • $loop->count - إجمالي العناصر في المصفوفة
  • $loop->first - منطقي: التكرار الأول
  • $loop->last - منطقي: التكرار الأخير
  • $loop->even - منطقي: تكرار زوجي
  • $loop->odd - منطقي: تكرار فردي
  • $loop->depth - مستوى تداخل الحلقة
  • $loop->parent - متغير الحلقة الأم في الحلقات المتداخلة

وراثة القوالب

تعريف تخطيط

أنشئ تخطيطاً رئيسياً يمكن للعروض الأخرى توسيعه:

<!-- resources/views/layouts/app.blade.php --> <!DOCTYPE html> <html lang="ar" dir="rtl"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>@yield('title', 'تطبيقي')</title> <!-- الأنماط --> <link rel="stylesheet" href="/css/app.css"> @stack('styles') </head> <body> <header> @include('partials.navigation') </header> <main> @yield('content') </main> <footer> @include('partials.footer') </footer> <!-- السكريبتات --> <script src="/js/app.js"></script> @stack('scripts') </body> </html>

توسيع تخطيط

<!-- resources/views/posts/index.blade.php --> @extends('layouts.app') @section('title', 'جميع المنشورات') @section('content') <h1>منشورات المدونة</h1> @foreach ($posts as $post) <article> <h2>{{ $post->title }}</h2> <p>{{ $post->excerpt }}</p> <a href="{{ route('posts.show', $post) }}">اقرأ المزيد</a> </article> @endforeach @endsection @push('scripts') <script src="/js/posts.js"></script> @endpush

@yield مقابل @section

<!-- @yield: استبدال محتوى بسيط --> @yield('title') @yield('content') <!-- @section مع @show: تعريف محتوى افتراضي يمكن تجاوزه --> @section('sidebar') <!-- محتوى الشريط الجانبي الافتراضي --> <p>الشريط الجانبي الافتراضي</p> @show <!-- يمكن للطفل توسيع قسم الأب --> @section('sidebar') @parent <!-- تضمين محتوى الأب --> <p>محتوى شريط جانبي إضافي</p> @endsection

تضمين العروض الفرعية

التضمين الأساسي

<!-- تضمين عرض جزئي --> @include('partials.alert') <!-- التضمين مع البيانات --> @include('partials.alert', ['type' => 'success', 'message' => 'تم الحفظ!']) <!-- التضمين فقط إذا كان العرض موجوداً --> @includeIf('partials.sidebar') <!-- التضمين عندما يكون الشرط صحيحاً --> @includeWhen($user->isAdmin(), 'partials.admin-panel') <!-- التضمين ما لم يكن الشرط صحيحاً --> @includeUnless($user->isGuest(), 'partials.user-menu') <!-- تضمين أول عرض موجود --> @includeFirst(['custom.header', 'partials.header'])

إنشاء جزئي

<!-- resources/views/partials/alert.blade.php --> @if (session('success')) <div class="alert alert-success"> {{ session('success') }} </div> @endif @if (session('error')) <div class="alert alert-error"> {{ session('error') }} </div> @endif

مكونات Blade

إنشاء مكونات مجهولة

<!-- resources/views/components/alert.blade.php --> @props(['type' => 'info', 'dismissible' => false]) <div {{ $attributes->merge(['class' => "alert alert-$type"]) }}> @if ($dismissible) <button type="button" class="close">×</button> @endif {{ $slot }} </div>

استخدام المكونات

<!-- استخدام بسيط --> <x-alert> هذا تنبيه معلومات! </x-alert> <!-- مع السمات --> <x-alert type="success" dismissible="true"> تم حفظ السجل بنجاح! </x-alert> <!-- تمرير فئات إضافية --> <x-alert type="error" class="mt-4"> حدث خطأ! </x-alert>

الفتحات المسماة

<!-- تعريف المكون --> <!-- resources/views/components/card.blade.php --> <div class="card"> <div class="card-header"> {{ $header }} </div> <div class="card-body"> {{ $slot }} </div> @isset($footer) <div class="card-footer"> {{ $footer }} </div> @endisset </div> <!-- استخدام المكون --> <x-card> <x-slot:header> عنوان البطاقة </x-slot> <!-- الفتحة الافتراضية (جسم البطاقة) --> هذا هو محتوى البطاقة. <x-slot:footer> <button>حفظ</button> </x-slot> </x-card>

المكونات القائمة على الفئات

# توليد مكون قائم على الفئة php artisan make:component Button # ينشئ: # - app/View/Components/Button.php # - resources/views/components/button.blade.php
<?php // app/View/Components/Button.php namespace App\View\Components; use Illuminate\View\Component; class Button extends Component { public $type; public $size; public function __construct($type = 'primary', $size = 'medium') { $this->type = $type; $this->size = $size; } public function render() { return view('components.button'); } }
<!-- resources/views/components/button.blade.php --> <button {{ $attributes->merge(['class' => "btn btn-$type btn-$size"]) }}> {{ $slot }} </button> <!-- الاستخدام --> <x-button type="success" size="large" id="save-btn"> حفظ التغييرات </x-button>

توجيهات Blade المفيدة

حماية CSRF

<form method="POST" action="/posts"> @csrf <!-- يُنشئ حقل رمز CSRF مخفي --> <input type="text" name="title"> <button type="submit">إرسال</button> </form>

انتحال الطريقة

<!-- المتصفحات تدعم فقط GET و POST --> <!-- استخدم @method لمحاكاة PUT, PATCH, DELETE --> <form method="POST" action="/posts/1"> @csrf @method('PUT') <!-- يُنشئ حقل _method مخفي بقيمة PUT --> <input type="text" name="title"> <button type="submit">تحديث</button> </form> <form method="POST" action="/posts/1"> @csrf @method('DELETE') <button type="submit">حذف</button> </form>

بيانات الجلسة

<!-- عرض رسالة الجلسة --> @if (session('status')) <div class="alert"> {{ session('status') }} </div> @endif <!-- التحقق من وجود مفتاح في الجلسة --> @session('status') <div class="alert"> {{ $value }} </div> @endsession

الإدخال القديم

<!-- إعادة ملء النموذج بالإدخال القديم بعد أخطاء التحقق --> <input type="text" name="title" value="{{ old('title') }}"> <input type="email" name="email" value="{{ old('email', $user->email) }}">

أخطاء التحقق

<!-- عرض جميع الأخطاء --> @if ($errors->any()) <div class="alert alert-error"> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif <!-- عرض خطأ لحقل محدد --> <input type="text" name="email"> @error('email') <span class="error">{{ $message }}</span> @enderror

PHP الخام

<!-- تنفيذ كود PHP خام --> @php $counter = 0; $isActive = true; @endphp <!-- استخدام المتغيرات --> <p>العداد: {{ $counter }}</p>

التعليقات

<!-- تعليق HTML (مرئي في المصدر) --> <!-- هذا تعليق HTML --> {{-- تعليق Blade (لا يُعرض في HTML) --}} {{-- هذا التعليق لن يظهر في العرض المُجمَّع --}}

التوجيهات المخصصة

يمكنك إنشاء توجيهات Blade مخصصة في مزود الخدمة:

// app/Providers/AppServiceProvider.php use Illuminate\Support\Facades\Blade; public function boot() { // إنشاء توجيه @datetime Blade::directive('datetime', function ($expression) { return "<?php echo ($expression)->format('m/d/Y H:i'); ?>"; }); // إنشاء توجيه @money Blade::directive('money', function ($expression) { return "<?php echo '$' . number_format($expression, 2); ?>"; }); } // الاستخدام في العروض: // @datetime($post->created_at) // @money($product->price)

تمرين عملي 1: التخطيط الرئيسي

أنشئ بنية تخطيط كاملة:

  1. أنشئ resources/views/layouts/app.blade.php مع رأس الصفحة والتنقل ومنطقة المحتوى الرئيسية والشريط الجانبي والتذييل
  2. أنشئ عروض جزئية للتنقل والشريط الجانبي والتذييل في resources/views/partials/
  3. استخدم @yield لأقسام العنوان والمحتوى
  4. أضف @stack للأنماط والسكريبتات
  5. أنشئ عرضاً فرعياً يوسع هذا التخطيط

تمرين عملي 2: قائمة منشورات المدونة

أنشئ صفحة قائمة منشورات المدونة:

  1. مرر مصفوفة من المنشورات من المتحكم إلى العرض
  2. استخدم @forelse للتكرار عبر المنشورات
  3. اعرض رسالة "لم يتم العثور على منشورات" عندما تكون فارغة
  4. أضف عبارات @if لإظهار شارة "جديد!" للمنشورات المنشأة في آخر 7 أيام
  5. استخدم متغير $loop لإضافة فئة "featured" للمنشور الأول
  6. اعرض ألوان خلفية متناوبة باستخدام $loop->even

تمرين عملي 3: مكون التنبيه

أنشئ مكون تنبيه قابل لإعادة الاستخدام:

  1. أنشئ resources/views/components/alert.blade.php
  2. قبول props: النوع (success/error/warning/info)، dismissible (منطقي)، العنوان (اختياري)
  3. استخدم @props و$attributes->merge() للفئات الديناميكية
  4. أضف فتحات مسماة للعنوان والإجراءات (الأزرار)
  5. استخدم المكون في عروض مختلفة مع متغيرات مختلفة

الملخص

في هذا الدرس، أتقنت:

  • عرض البيانات باستخدام عبارات echo في Blade والتهريب
  • استخدام توجيهات Blade للشروط والحلقات والمصادقة
  • وراثة القوالب مع @extends و@section و@yield
  • تضمين العروض الجزئية باستخدام @include ومتغيراته
  • إنشاء واستخدام مكونات Blade (المجهولة والقائمة على الفئات)
  • العمل مع الفتحات المسماة وسمات المكونات
  • التوجيهات الأساسية: @csrf و@method و@error و@old
  • إنشاء توجيهات Blade مخصصة

Blade هو محرك قوالب قوي وأنيق يجعل بناء العروض الديناميكية متعة. لديك الآن الأساس لإنشاء قوالب عرض جميلة وقابلة للصيانة في لارافيل!

في الدروس التالية، سنستكشف قواعد البيانات باستخدام Eloquent ORM والترحيلات والعلاقات لبناء تطبيقات كاملة الميزات.