404 Not Found

404 Not Found


nginx

قوالب Laravel ومحرك قوالب Blade

Blade هو "محرك التخطيط" في Laravel — هنا تتحول البيانات إلى الصفحات التي يراها المستخدمون، وتقضي التخطيطات والمكونات والوراثة على الكود المكرر.

1. ما ستتعلمه


2. قصة حقيقية لمطور واجهة أمامية

(1) المشكلة: الاضطرار لكتابة شريط التنقل بشكل متكرر في كل صفحة

كتب Charlie 10 صفحات لـ ShopMetrics، ينسخ يدويًا 60 سطرًا من HTML لشريط التنقل في كل صفحة. عندما طلب Bob نقل رابط "التسعير" من شريط التنقل إلى قائمة منسدلة، كان على Charlie تعديل جميع الملفات العشرة واحدًا تلو الآخر — لكن عندما وصل إلى الملف السابع، فاته صنف، مما تسبب في عدم تناسق أنماط شريط التنقل على الموقع المباشر، مما أدى إلى ثلاث شكاوى من Alice.

(2) حلول تخطيط Blade

يستخدم Blade @extends لوراثة التخطيط و@section لملء المحتوى؛ شريط التنقل يُعرَّف مرة واحدة فقط ويتزامن تلقائيًا عبر الموقع بأكمله.

PHP
// resources/views/layouts/app.blade.php — تعريف التخطيط مرة واحدة
<body>
    <nav>...</nav>  <!-- التنقل يظهر في كل صفحة -->
    @yield('content')  <!-- كل صفحة تملأ هذا الموضع -->
</body>

// resources/views/shops/index.blade.php — وراثة وملء
@extends('layouts.app')
@section('content')
    <h1>جميع المتاجر</h1>
@endsection

(3) العائد

بعد أن نفّذ Charlie تخطيط Blade، أصبح شريط التنقل يحتاج فقط إلى صيانة ملف واحد. تغيّر طلب Bob من "تعديل 10 ملفات" إلى "تعديل ملف واحد"، بدون أي حذف.


3. وراثة تخطيط Blade

(1) تعريف التخطيط

HTML
<!-- resources/views/layouts/app.blade.php -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>@yield('title', 'ShopMetrics')</title>
    @vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body>
    @include('partials.navbar')
    <main class="container mx-auto">
        @yield('content')
    </main>
    @include('partials.footer')
</body>
</html>

(2) وراثة التخطيطات

HTML
<!-- resources/views/shops/index.blade.php -->
@extends('layouts.app')

@section('title', 'جميع المتاجر')

@section('content')
    <div class="grid grid-cols-3 gap-4">
        @foreach($shops as $shop)
            <div class="card">{{ $shop->name }}</div>
        @endforeach
    </div>
@endsection
الأمر الوظيفة التشبيه
@extends تحديد التخطيط المراد وراثته اختيار قالب
@section تعريف كتل المحتوى ملء الفراغات في القالب
@yield إخراج كتلة المحتوى عنصر نائب في القالب
@include استيراد عرض فرعي إدراج مقطع مشترك

(1) ▶ مثال: تخطيط عمودي لـ ShopMetrics

HTML
<!-- resources/views/layouts/dashboard.blade.php -->
@extends('layouts.app')

@section('content')
    <div class="flex">
        @include('partials.sidebar')
        <div class="flex-1 p-6">
            @yield('dashboard-content')
        </div>
    </div>
@endsection

<!-- resources/views/dashboard/analytics.blade.php -->
@extends('layouts.dashboard')

@section('title', 'لوحة التحليلات')

@section('dashboard-content')
    <h1>نظرة عامة على التحليلات</h1>
    <div id="chart"></div>
@endsection

الناتج:

TEXT
// تم التنفيذ بنجاح

4. هياكل التحكم

(1) العبارات الشرطية

PHP
@if($shop->is_active)
    <span class="badge-green">نشط</span>
@elseif($shop->is_suspended)
    <span class="badge-yellow">معلق</span>
@else
    <span class="badge-gray">غير نشط</span>
@endif

@auth
    <a href="{{ route('dashboard') }}">لوحة التحكم</a>
@endauth

@guest
    <a href="{{ route('login') }}">تسجيل الدخول</a>
@endguest

(2) عبارات التكرار

PHP
@foreach($shops as $shop)
    <tr>
        <td>{{ $shop->name }}</td>
        <td>{{ $shop->revenue }}</td>
    </tr>
    @if($loop->last)
        </tbody></table>
    @endif
@endforeach

@forelse($orders as $order)
    <li>{{ $order->total }}</li>
@empty
    <li>لا توجد طلبات بعد.</li>
@endforelse
متغير التكرار الوصف
$loop->index الفهرس الحالي (يبدأ من 0)
$loop->iteration الدورة الحالية (تبدأ من 1)
$loop->first هل هذا الأول؟
$loop->last هل هذا الأخير؟
$loop->count الإجمالي

(1) ▶ مثال: العرض الشرطي في لوحة تحكم ShopMetrics

HTML
<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')

@section('dashboard-content')
    @auth
        <h1>مرحبًا، {{ auth()->user()->name }}!</h1>
    @endauth

    @if($shops->count() > 0)
        <div class="grid grid-cols-3 gap-4">
            @foreach($shops as $shop)
                <div class="card {{ $loop->first ? 'border-blue-500' : '' }}">
                    <h3>{{ $shop->name }}</h3>
                    <p>الإيرادات: ${{ number_format($shop->revenue, 2) }}</p>
                </div>
            @endforeach
        </div>
    @else
        <p>لا توجد متاجر بعد. <a href="{{ route('shops.create') }}>أنشئ واحدًا!</a></p>
    @endif
@endsection

الناتج:

TEXT
// تم التنفيذ بنجاح

5. مكونات Blade

(1) المكونات المجهولة

HTML
<!-- resources/views/components/shop-card.blade.php -->
@props(['shop', 'highlight' => false])

<div class="card {{ $highlight ? 'border-gold' : '' }}">
    <h3>{{ $shop->name }}</h3>
    <p>{{ $shop->description }}</p>
    {{ $slot }}
</div>

<!-- الاستخدام -->
<x-shop-card :shop="$shop" :highlight="true">
    <p>مميز هذا الأسبوع!</p>
</x-shop-card>

(2) المكونات الصنفية

BASH
php artisan make:component Alert
# ينشئ: app/View/Components/Alert.php
# و:     resources/views/components/alert.blade.php
PHP
// app/View/Components/Alert.php
class Alert extends Component
{
    public function __construct(
        public string $type = 'info',
        public ?string $message = null,
    ) {}

    public function render(): View
    {
        return view('components.alert');
    }
}
HTML
<!-- resources/views/components/alert.blade.php -->
<div class="alert alert-{{ $type }}">
    {{ $message ?? $slot }}
</div>

<!-- الاستخدام -->
<x-alert type="success" message="تم إنشاء المتجر!" />
<x-alert type="warning">تحقق من إعداداتك.</x-alert>
البُعد مجهول صنفي
الملف قالب Blade فقط صنف PHP + قالب Blade
المنطق بلا/ضئيل قد يحتوي على منطق أعمال
Props إعلان @props() معاملات المنشئ
مناسب لـ مكونات عرض بسيطة مكونات تتطلب منطقًا

(1) ▶ مثال: مكون بطاقة إحصائيات ShopMetrics

HTML
<!-- resources/views/components/stat-card.blade.php -->
@props(['label', 'value', 'icon', 'trend' => null])

<div class="bg-white rounded-lg shadow p-6">
    <div class="flex items-center justify-between">
        <div>
            <p class="text-gray-500 text-sm">{{ $label }}</p>
            <p class="text-2xl font-bold">{{ $value }}</p>
            @if($trend)
                <p class="text-sm {{ $trend > 0 ? 'text-green-500' : 'text-red-500' }}">
                    {{ $trend > 0 ? '+' : '' }}{{ $trend }}%
                </p>
            @endif
        </div>
        <span class="text-3xl">{{ $icon }}</span>
    </div>
</div>

<!-- الاستخدام في لوحة التحكم -->
<x-stat-card label="إجمالي الإيرادات" value="$12,450" icon="$" :trend="15.3" />
<x-stat-card label="المتاجر النشطة" value="23" icon="#" :trend="-2.1" />

الناتج:

TEXT
// تم التنفيذ بنجاح

6. View Composer

يضخ View Composers البيانات المشتركة تلقائيًا في العروض، مما يلغي الحاجة لتمريرها بشكل متكرر في كل دالة متحكم.

PHP
// app/Providers/AppServiceProvider.php
public function boot(): void
{
    // مشاركة معلومات المستأجر مع جميع عروض لوحة التحكم
    View::composer('dashboard.*', function ($view) {
        $view->with('tenant', Tenant::current());
    });

    // مشاركة بيانات التنقل مع عروض محددة
    View::composer(['layouts.app', 'shops.*'], NavigationComposer::class);
}
النوع وقت التنفيذ الغرض
View::composer() في كل مرة يُعرض فيها العرض حساب البيانات ديناميكيًا
View::creator() عند إنشاء نسخة من العرض ربط البيانات مبكرًا
View::share() جميع العروض ثوابت عامة

(1) ▶ مثال: View Composer لتنقل ShopMetrics

PHP
// app/View/Composers/NavigationComposer.php
class NavigationComposer
{
    public function compose(View $view): void
    {
        $view->with([
            'navShops' => auth()->check()
                ? auth()->user()->shops()->take(5)->get()
                : collect(),
            'navNotifications' => auth()->check()
                ? auth()->user()->unreadNotifications()->count()
                : 0,
        ]);
    }
}

// التسجيل في AppServiceProvider
View::composer('layouts.app', NavigationComposer::class);

الناتج:

TEXT
// تم التنفيذ بنجاح

7. أصول الواجهة الأمامية: Vite

يستخدم Laravel 11 Vite افتراضيًا لتجميع أصول الواجهة الأمامية.

(1) تثبيت Tailwind CSS وAlpine.js

BASH
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
npm install alpinejs

(2) تهيئة Vite

JAVASCRIPT
// vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
    ],
});

(3) الإدخال في Blade

HTML
<!-- في رأس التخطيط -->
@vite(['resources/css/app.css', 'resources/js/app.js'])
الأمر الوظيفة
npm run dev بدء خادم تطوير Vite (إعادة تحميل ساخنة)
npm run build تجميع أصول الإنتاج (مضغوطة)

(1) ▶ مثال: تنقل ShopMetrics مع تفاعلات Alpine.js

HTML
<!-- resources/views/partials/navbar.blade.php -->
<nav class="bg-white shadow" x-data="{ open: false }">
    <div class="container mx-auto px-4">
        <div class="flex justify-between h-16">
            <a href="{{ route('home') }}" class="font-bold text-xl">ShopMetrics</a>

            <div class="flex items-center">
                @auth
                    <div class="relative" @click="open = !open">
                        <button class="flex items-center">
                            {{ auth()->user()->name }}
                            @if($navNotifications > 0)
                                <span class="badge-red">{{ $navNotifications }}</span>
                            @endif
                        </button>
                        <div x-show="open" x-transition class="dropdown">
                            <a href="{{ route('dashboard') }}">لوحة التحكم</a>
                            <a href="{{ route('shops.index') }}">المتاجر</a>
                            <form action="{{ route('logout') }}" method="POST">
                                @csrf
                                <button type="submit">تسجيل الخروج</button>
                            </form>
                        </div>
                    </div>
                @else
                    <a href="{{ route('login') }}">تسجيل الدخول</a>
                @endauth
            </div>
        </div>
    </div>
</nav>

الناتج:

TEXT
// تم التنفيذ بنجاح

8. مثال شامل: صفحة لوحة تحكم ShopMetrics

HTML
<!-- ============================================
     شامل: صفحة لوحة تحكم ShopMetrics
     يغطي: وراثة التخطيط، المكونات، التوجيهات، Alpine.js
     ============================================ -->

<!-- resources/views/dashboard/index.blade.php -->
@extends('layouts.dashboard')

@section('title', 'لوحة التحكم — ShopMetrics')

@section('dashboard-content')
    <div class="space-y-6" x-data="{ period: '7d' }">
        <!-- محدد الفترة -->
        <div class="flex gap-2">
            <button @click="period = '7d'"
                    :class="period === '7d' ? 'btn-primary' : 'btn-secondary'">7 أيام</button>
            <button @click="period = '30d'"
                    :class="period === '30d' ? 'btn-primary' : 'btn-secondary'">30 يومًا</button>
            <button @click="period = '90d'"
                    :class="period === '90d' ? 'btn-primary' : 'btn-secondary'">90 يومًا</button>
        </div>

        <!-- بطاقات الإحصائيات -->
        <div class="grid grid-cols-4 gap-4">
            <x-stat-card label="إجمالي الإيرادات" value="${{ number_format($totalRevenue, 0) }}" icon="$" :trend="$revenueTrend" />
            <x-stat-card label="الطلبات" value="{{ number_format($orderCount) }}" icon="#" :trend="$orderTrend" />
            <x-stat-card label="المتاجر النشطة" value="{{ $activeShops }}" icon="#" :trend="$shopTrend" />
            <x-stat-card label="معدل التحويل" value="{{ number_format($conversionRate, 1) }}%" icon="%" :trend="$conversionTrend" />
        </div>

        <!-- الطلبات الأخيرة -->
        <div class="bg-white rounded-lg shadow">
            <h2 class="p-4 border-b font-semibold">الطلبات الأخيرة</h2>
            @forelse($recentOrders as $order)
                <div class="p-4 border-b flex justify-between">
                    <span>{{ $order->product_name }}</span>
                    <span>${{ number_format($order->total, 2) }}</span>
                </div>
            @empty
                <p class="p-4 text-gray-500">لا توجد طلبات في هذه الفترة.</p>
            @endforelse
        </div>
    </div>
@endsection

❓ أسئلة شائعة

س ما الفرق بين {{ }} و{!! !!} في Blade؟
ج {{ }} يهرب من HTML تلقائيًا (لمنع XSS)، بينما {!! !!} يُخرج HTML خام دون هروب. استخدم {!! !!} فقط عندما يكون المحتوى موثوقًا، مثل إخراج محرر نصوص غني.
س ما الفرق بين @include و@component؟
ج @include يُستخدم لإدراج مقاطع عرض بسيطة بدون عزل prop؛ @component لديه إعلانات prop صريحة وآلية slot، مما يجعله أكثر ملاءمة لمكونات UI القابلة لإعادة الاستخدام. استخدم @include للمقاطع البسيطة و@component للمكونات المستقلة.
س ما الفرق بين مكونات Blade ومكونات Vue؟
ج مكونات Blade تُعرض على الخادم وتولد HTML ثابت؛ مكونات Vue تُعرض على العميل وتدعم ربط البيانات التفاعلية. في مشاريع Laravel، يُستخدم Blade عادةً لهيكل الصفحة، بينما يُستخدم Alpine.js أو Vue للتفاعل.
س ما الفرق بين Vite وMix؟
ج Vite هو أداة البناء الافتراضية للواجهة الأمامية في Laravel 9 وما بعده؛ يستخدم خادم تطوير ES Module، يبدأ بسرعة ويدعم الإعادة الساخنة. Mix (Laravel Mix) مبني على Webpack؛ سهل التهيئة لكن أبطأ. استخدم Vite للمشاريع الجديدة.
س كيف أقيّم المتغيرات في Blade؟
ج استخدم {{ dd($variable) }} لتفريغ المتغير وإيقاف الصفحة مباشرة؛ أو ثبّت ملحق Laravel Debugbar لعرض جميع المتغيرات واستعلامات SQL واستخدام الذاكرة.
س ما الفرق بين طريقة View Composers والمتحكمات في تمرير البيانات؟
ج المتحكمات تمرر البيانات صراحةً باستخدام view('xxx', ['key' => $value])، وهو مناسب للبيانات الخاصة بذلك العرض؛ View Composers يضخ البيانات المشتركة تلقائيًا، وهو مناسب للبيانات التي تحتاجها عروض متعددة (مثل شريط التنقل أو عدد الإشعارات).

📖 ملخص


📝 تمارين

  1. تمرين أساسي (⭐): أنشئ ملف تخطيط layouts/app.blade.php لـ ShopMetrics يتضمن شريط تنقل وتذييل، ثم أنشئ صفحة رئيسية home/index.blade.php ترث من هذا التخطيط واملأها بالمحتوى.

  2. تمرين متقدم (⭐⭐): أنشئ مكون Blade <x-alert> يدعم type (success/warning/error) ومحتوى slot، لاستخدامه في سيناريوهات ShopMetrics عند نجاح الإنشاء أو فشل التحقق.

  3. تحدٍ (⭐⭐⭐): استخدم Alpine.js لتنفيذ مكون شريط جانبي قابل للطي، ودمجه في تخطيط لوحة التحكم، مع دعم الطي التلقائي على الأجهزة المحمولة، وحفظ الحالة في localStorage.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%