فصل چهارم-قالببندی با Blade
قالب بندی با Blade
در مقایسه با اکثر زبانهای بکاند، PHP در واقع بهعنوان یک زبان قالببندی عملکرد نسبتاً خوبی دارد. اما کاستیهایی دارد و همچنین استفاده از <?php بهصورت درونخطی در سراسر کد ناخوشایند است، بنابراین انتظار میرود که اکثر فریمورکهای مدرن یک زبان قالببندی ارائه دهند.
Laravel یک موتور قالببندی سفارشی به نام Blade ارائه میدهد که از موتور Razor در .NET الهام گرفته است. این موتور دارای سینتکسی مختصر، یک منحنی یادگیری سطحی، یک مدل ارثبری قدرتمند و شهودی، و قابلیت توسعه آسان است.
برای نگاهی سریع به نحوه نگارش قالبهای Blade، به مثال ۴-۱ توجه کنید.
مثال ۴-۱. نمونههایی از Blade
<h1>{{ $group->title }}</h1>
{!! $group->heroImageHtml() !!}
@forelse ($users as $user)
• {{ $user->first_name }} {{ $user->last_name }}<br>
@empty
No users in this group.
@endforelse
همانطور که مشاهده میکنید، Blade از آکولاد ({{ }}) برای "echo" استفاده میکند و یک قاعده کلی را معرفی میکند که در آن تگهای سفارشی آن، که "دستورالعمل" (directives) نامیده میشوند، با @ پیشوند میگیرند. شما از دستورالعملها برای تمامی ساختارهای کنترلی، ارثبری، و همچنین هر ویژگی سفارشی که میخواهید اضافه کنید، استفاده خواهید کرد.
سینتکس Blade تمیز و مختصر است، بنابراین در هسته خود خوشایندتر و منظمتر از سایر گزینهها است. اما درست زمانی که در قالبهای خود به ارثبریهای تودرتو، شرطهای پیچیده یا بازگشت (recursion) نیاز داشته باشید، Blade قدرت واقعی خود را نشان میدهد. درست مانند بهترین مؤلفههای Laravel، این موتور الزامات پیچیده اپلیکیشن را ساده و در دسترس میکند.
علاوه بر این، از آنجایی که تمام سینتکسهای Blade به کد معمولی PHP کامپایل شده و سپس کش (cache) میشوند، عملکرد سریعی دارد و در صورت نیاز میتوانید از کد PHP بومی در فایلهای Blade خود استفاده کنید. با این حال، پیشنهاد میکنم تا حد امکان از استفاده از PHP در قالبهای Blade خودداری کنید—معمولاً اگر نیاز به کاری دارید که نمیتوان آن را با Blade یا یک دستورالعمل سفارشی Blade انجام داد، احتمالاً آن کار درون قالب جایگاهی ندارد.
استفاده از Twig با Laravelبرخلاف بسیاری از فریمورکهای مبتنی بر Symfony، Laravel بهصورت پیشفرض از Twig استفاده نمیکند. اما اگر به Twig علاقهمند هستید، یک پکیج به نام Twig Bridge وجود دارد که استفاده از Twig را بهجای Blade در Laravel آسان میکند. |
چاپ کردن داده ها
ساختارهای کنترلی
بیشتر ساختارهای کنترلی در Blade بسیار آشنا خواهند بود. بسیاری از آنها دقیقاً نام و ساختار مشابهی با تگهای PHP دارند.
چند helper برای راحتی بیشتر وجود دارد، اما در کل، ساختارهای کنترلی در Blade خواناتر و تمیزتر از معادلهایشان در PHP هستند.
شرط ها (Conditionals)
حلقه ها (Loops)
در ادامه، نگاهی به حلقهها میاندازیم.
@for, @foreach, و @while
دستورات @for, @foreach و @while در Blade دقیقاً مانند PHP کار میکنند. به مثالهای 4-4، 4-5 و 4-6 توجه کنید.
مثال 4-4: استفاده از @for و @endfor
@for ($i = 0; $i < $talk->slotsCount(); $i++)
The number is {{ $i }}<br>
@endfor
مثال 4-5: استفاده از @foreach و @endforeach
@foreach ($talks as $talk)
• {{ $talk->title }} ({{ $talk->length }} minutes)<br>
@endforeach
مثال 4-6: استفاده از @while و @endwhile
@while ($item = array_pop($items))
{{ $item->orSomething() }}<br>
@endwhile
@forelse و @endforelse
@forelse مشابه @foreach است، اما این امکان را میدهد که یک مقدار پیشفرض (fallback) را در صورتی که مقدار مورد تکرار خالی باشد، تعریف کنید.
این ساختار را در ابتدای این فصل دیدیم. در مثال 4-7 یک نمونه دیگر آورده شده است:
مثال 4-7: استفاده از @forelse
@forelse ($talks as $talk)
• {{ $talk->title }} ({{ $talk->length }} minutes)<br>
@empty
No talks this day.
@endforelse
متغیر $loop در @foreach و @forelseدستورات @foreach و @forelse یک قابلیت اضافی دارند که در حلقههای foreach در PHP وجود ندارد: متغیر $loop. مقدار 0-based اندیس آیتم فعلی در حلقه (۰ یعنی اولین آیتم) iteration مقدار 1-based اندیس آیتم فعلی در حلقه (۱ یعنی اولین آیتم) remaining تعداد آیتمهای باقیمانده در حلقه count تعداد کل آیتمهای حلقه first و last متغیرهای boolean که مشخص میکنند این اولین یا آخرین آیتم در حلقه است even و odd متغیرهای boolean که مشخص میکنند این تکرار زوج یا فرد است depth عمق حلقه: ۱ برای یک حلقه معمولی، ۲ برای حلقه تو در تو، و ... parent ارجاعی به متغیر $loop حلقه والد اگر این حلقه درون یک @foreach دیگر باشد، در غیر این صورت مقدار null خواهد بود.
نمونهای از استفاده از $loop
|
Template Inheritance (ارث بری قالب ها)
Blade یک ساختار برای ارثبری قالبها فراهم میکند که به ویوها اجازه میدهد تا قالبهای دیگر را گسترش داده، تغییر دهند و شامل کنند.
بیایید نگاهی به نحوه ساختار ارثبری در Blade بیندازیم.
تعریف بخشها با @section/@show و @yield
وارد کردن تکه های ویو
حالا که اصول ارثبری را مشخص کردیم، چند ترفند دیگر وجود دارد که میتوانیم انجام دهیم.
@include
اگر در یک ویو باشیم و بخواهیم ویو دیگری را وارد کنیم، چه؟ شاید یک دکمه فراخوان “ثبتنام” داریم که میخواهیم در سرتاسر سایت از آن استفاده کنیم. و شاید بخواهیم متن دکمه را هر بار که از آن استفاده میکنیم، سفارشی کنیم. به مثال ۴-۱۰ نگاه کنید.
Example 4-10. Including view partials with @include
<!-- resources/views/home.blade.php -->
<div class="content" data-page-name="{{ $pageName }}">
<p>Here's why you should sign up for our app: <strong>It's Great.</strong></p>
@include('sign-up-button', ['text' => 'See just how great it is'])
</div>
<!-- resources/views/sign-up-button.blade.php -->
<a class="button button--callout" data-page-name="{{ $pageName }}">
<i class="exclamation-icon"></i> {{ $text }}
</a>
@include قسمت جزئی را وارد میکند و به صورت اختیاری دادههایی را به آن منتقل میکند. توجه داشته باشید که نه تنها میتوانید به طور صریح دادهها را از طریق پارامتر دوم @include به یک جزئیات منتقل کنید، بلکه میتوانید به هر متغیری که در فایل وارد شده و برای ویو اصلی در دسترس است نیز اشاره کنید (در این مثال، $pageName). دوباره، شما میتوانید هر کاری که بخواهید انجام دهید، اما پیشنهاد میکنم همیشه هر متغیری که قصد دارید استفاده کنید را به طور صریح منتقل کنید تا شفافیت بیشتری داشته باشد.
همچنین میتوانید از دستورات @includeIf، @includeWhen و @includeFirst استفاده کنید، همانطور که در مثال ۴-۱۱ نشان داده شده است.
مثال 4-11. وارد کردن ویوها بهصورت شرطی
{{-- Include a view if it exists --}}
@includeIf('sidebars.admin', ['some' => 'data'])
{{-- Include a view if a passed variable is truth-y --}}
@includeWhen($user->isAdmin(), 'sidebars.admin', ['some' => 'data'])
{{-- Include the first view that exists from a given array of views --}}
@includeFirst(['customs.header', 'header'], ['some' => 'data'])
@each
شما احتمالاً میتوانید برخی شرایطی را تصور کنید که در آن باید از یک آرایه یا مجموعه عبور کنید و برای هر آیتم یک جزئیات را با استفاده از @include وارد کنید. برای این کار یک دستور وجود دارد: @each.
فرض کنید یک نوار کناری داریم که از ماژولها تشکیل شده است و میخواهیم چندین ماژول را وارد کنیم، هر کدام با عنوان متفاوت. به مثال ۴-۱۲ نگاه کنید.
مثال 4-12. استفاده از پارتشیالهای ویو در یک حلقه با دستور @each
<!-- resources/views/sidebar.blade.php -->
<div class="sidebar">
@each('partials.module', $modules, 'module', 'partials.empty-module')
</div>
<!-- resources/views/partials/module.blade.php -->
<div class="sidebar-module">
<h1>{{ $module->title }}</h1>
</div>
<!-- resources/views/partials/empty-module.blade.php -->
<div class="sidebar-module">
No modules :(
</div>
به سینتکس @each توجه کنید. پارامتر اول نام جزئیات نمایی است. پارامتر دوم آرایه یا مجموعهای است که باید از آن عبور کرد. پارامتر سوم نام متغیری است که هر آیتم (در اینجا، هر عنصر در آرایه $modules) به نمای مربوطه ارسال خواهد شد. و پارامتر اختیاری چهارم نمایی است که در صورتی که آرایه یا مجموعه خالی باشد نمایش داده میشود (یا میتوانید یک رشته را به عنوان قالب ارسال کنید).
استفاده از کامپوننت ها
استفاده از استک ها
یک الگوی رایج که مدیریت آن با استفاده از گنجاندنهای ساده Blade ممکن است دشوار باشد، زمانی است که هر نمای موجود در یک سلسلهمراتب Blade نیاز به اضافه کردن چیزی به یک بخش خاص دارد—تقریباً مانند اضافه کردن یک ورودی به یک آرایه.
رایجترین موقعیت برای این کار زمانی است که برخی صفحات (و گاهی اوقات، بهطور کلیتر، بخشهای خاصی از یک وبسایت) نیاز به بارگذاری فایلهای CSS و JavaScript خاص و منحصر به فرد دارند. فرض کنید که شما یک فایل CSS جهانی برای سایت، یک فایل CSS برای بخش «شغلها»، و یک فایل CSS برای صفحه «درخواست شغل» دارید.
استکهای Blade دقیقاً برای همین موقعیت ساخته شدهاند. در الگوی والد خود، یک استک تعریف کنید که فقط یک مکاننگهدار است. سپس، در هر الگوی فرزند میتوانید ورودیهایی را با استفاده از @push/@endpush به آن استک «اضافه» کنید که آنها را به انتهای استک در رندر نهایی اضافه میکند. همچنین میتوانید از @prepend/@endprepend برای اضافه کردن آنها به بالای استک استفاده کنید. مثال 4-19 این موضوع را نشان میدهد.
مثال 4-19. استفاده از استکهای Blade
<!-- resources/views/layouts/app.blade.php -->
<html>
<head><!-- the head --></head>
<body>
<!-- the rest of the page -->
<script src="/css/global.css"></script>
<!-- the placeholder where stack content will be placed -->
@stack('scripts')
</body>
</html>
<!-- resources/views/jobs.blade.php -->
@extends('layouts.app')
@push('scripts')
<!-- push something to the bottom of the stack -->
<script src="/css/jobs.css"></script>
@endpush
<!-- resources/views/jobs/apply.blade.php -->
@extends('jobs')
@prepend('scripts')
<!-- push something to the top of the stack -->
<script src="/css/jobs--apply.css"></script>
@endprepend
اینها نتیجه زیر را تولید میکنند:
<html>
<head><!-- the head --></head>
<body>
<!-- the rest of the page -->
<script src="/css/global.css"></script>
<!-- the placeholder where stack content will be placed -->
<script src="/css/jobs--apply.css"></script>
<script src="/css/jobs.css"></script>
</body>
</html>
کامپوزرهای ویو و تزریق سرویس
بایند کردن دادهها به ویوها با استفاده از کامپوزرهای ویو
خوشبختانه راه سادهتری وجود دارد. راهحل به نام کامپوزر ویو است و این امکان را به شما میدهد که هر زمان یک ویو خاص بارگذاری شد، دادههای معینی به آن منتقل شود—بدون اینکه تعریف روت مجبور باشد آن دادهها را بهطور صریح ارسال کند.
فرض کنید شما یک سایدبار در هر صفحه دارید که در یک پارشیال به نام partials.sidebar (resources/views/partials/sidebar.blade.php) تعریف شده است و سپس در هر صفحه گنجانده میشود. این سایدبار لیستی از آخرین هفت پستی که در سایت شما منتشر شده را نشان میدهد. اگر این سایدبار در هر صفحه باشد، هر تعریف روت معمولاً باید آن لیست را بگیرد و آن را به ویو ارسال کند، مانند مثال ۴-۲۱.
مثال 4-21. ارسال دادههای سایدبار از هر مسیر (route)
Route::get('home', function () {
return view('home')
->with('posts', Post::recent());
});
Route::get('about', function () {
return view('about')
->with('posts', Post::recent());
});
این ممکن است بهسرعت آزاردهنده شود. بهجای این کار، ما از کامپوزرهای ویو استفاده خواهیم کرد تا آن متغیر را با مجموعهای از ویوها به اشتراک بگذاریم. ما میتوانیم این کار را به چندین روش انجام دهیم، پس بیایید از سادهترین روش شروع کنیم و به تدریج پیش برویم.
به اشتراکگذاری یک متغیر بهطور سراسری
اولین گزینه، سادهترین روش: صرفاً یک متغیر را بهطور سراسری با هر ویو در اپلیکیشن خود به اشتراک بگذارید، مانند مثال ۴-۲۲.
مثال 4-22. اشتراکگذاری یک متغیر بهصورت سراسری
// Some service provider
public function boot()
{
...
view()->share('recentPosts', Post::recent());
}
اگر میخواهید از view()->share() استفاده کنید، بهترین مکان برای این کار متد boot() یک سرویسپروایدر است تا این اتصال در هر بار بارگذاری صفحه انجام شود. شما میتوانید یک سرویسپروایدر سفارشی به نام ViewComposerServiceProvider ایجاد کنید (برای اطلاعات بیشتر در مورد سرویسپروایدرها به فصل ۱۱ مراجعه کنید)، اما در حال حاضر فقط آن را در App\Providers\AppServiceProvider در متد boot() قرار دهید.
با استفاده از view()->share() متغیر برای هر ویو در سراسر اپلیکیشن در دسترس خواهد بود، بنابراین ممکن است بیش از حد باشد.
کامپوزرهای ویو با دامنه ویو با استفاده از closures
گزینه بعدی استفاده از یک کامپوزر ویو مبتنی بر closure است تا متغیرها را با یک ویو خاص به اشتراک بگذارید، مانند مثال ۴-۲۳.
مثال 4-23. ایجاد یک View Composer مبتنی بر Closure
view()->composer('partials.sidebar', function ($view) {
$view->with('recentPosts', Post::recent());
});
همانطور که میبینید، ما نام ویوی مورد نظر برای به اشتراک گذاشتن را در پارامتر اول (مثلاً partials.sidebar) تعریف کردهایم و سپس یک closure به پارامتر دوم پاس دادهایم؛ در این closure از متد $view->with() استفاده کردهایم تا یک متغیر را فقط با یک ویو خاص به اشتراک بگذاریم.
View Composerها برای چندین ویو (چندین نما) هرجا که یک view composer به یک view خاص متصل میشود (مثل Example 4-23 که به partials.sidebar متصل است)، میتوانید به جای آن یک آرایه از نامهای ویو را پاس بدهید تا به چندین ویو متصل شوید.
|
View Composerهای محدود به یک ویو با استفاده از کلاسها
همانطور که میبینید، زمانی که این composer فراخوانی میشود، متد compose() اجرا میشود، که در آن متغیر recentPosts به نتیجه اجرای متد recent() مدل Post متصل میشود.
مانند سایر روشهای اشتراکگذاری متغیرها، این view composer نیاز به یک binding در جایی دارد. دوباره، احتمالاً شما یک custom ViewComposerServiceProvider ایجاد خواهید کرد، اما برای اکنون، همانطور که در Example 4-25 مشاهده میکنید، فقط آن را در متد boot() از App\Providers\AppServiceProvider قرار میدهیم.
مثال 4-25. ثبت یک View Composer در AppServiceProvider
public function boot(): void
{
view()->composer(
'partials.sidebar',
\App\Http\ViewComposers\RecentPostsComposer::class
);
}
توجه داشته باشید که این binding مشابه یک view composer مبتنی بر closure است، اما به جای پاس دادن یک closure، نام کلاس view composer خود را پاس میدهیم. حالا، هر بار که Blade ویو partials.sidebar را رندر میکند، به طور خودکار provider ما اجرا خواهد شد و متغیر recentPosts را که به نتایج متد recent() از مدل Post تنظیم شده است، به ویو منتقل میکند.
تزریق سرویس در Blade (Blade Service Injection)
دستورات Blade سفارشی
تمام سینتکسهای داخلی Blade که تاکنون بررسی کردیم—@if، @unless و غیره—به عنوان دستورها شناخته میشوند. هر دستور Blade یک نگاشت بین یک الگو (مثلاً @if ($condition)) و یک خروجی PHP (مثلاً ) است.
دستورات فقط برای هسته نیستند؛ شما میتوانید دستورهای خودتان را نیز ایجاد کنید. شاید فکر کنید دستورات برای ایجاد میانبرهایی به کدهای بزرگتر مناسب هستند—مثلاً استفاده از @button('buttonName') و تبدیل آن به مجموعهای بزرگتر از کد HTML مربوط به دکمه. این ایده بدی نیست، اما برای گسترش کد ساده مانند این، شاید بهتر باشد که یک partial view را شامل کنید.
دستورات سفارشی بیشتر زمانی مفید هستند که برخی از اشکال منطق تکراری را ساده کنند. فرض کنید از این که مجبوریم کد خود را با @if (auth()->guest()) (برای بررسی این که آیا کاربر وارد شده است یا نه) بپیچیم خسته شدهایم و میخواهیم دستور سفارشی @ifGuest ایجاد کنیم. همانند view composers، ممکن است ارزش داشته باشد که یک سرویس پرووایدر سفارشی برای ثبت اینها داشته باشیم، اما برای حالا، اجازه دهید که آن را فقط در متد boot() در App\Providers\AppServiceProvider قرار دهیم. به مثال 4-29 نگاه کنید تا ببینید این نگاشت چگونه خواهد بود.
مثال 4-29. متصل کردن یک دستور سفارشی Blade در یک Service Provider
public function boot(): void
{
Blade::directive('ifGuest', function () {
return "<?php if (auth()->guest()): ?>";
});
}
ما اکنون یک دستور سفارشی به نام @ifGuest ثبت کردهایم که با کد PHP جایگزین خواهد شد.
<?php if (auth()->guest()): ?>.
این ممکن است کمی عجیب به نظر برسد. شما یک رشته مینویسید که سپس به عنوان PHP اجرا خواهد شد. اما این به این معنی است که اکنون میتوانید جنبههای پیچیده، زشت، یا غیرقابل فهم، یا تکراری کد قالببندی PHP خود را پنهان کرده و آنها را پشت سینتکسهای واضح، ساده و گویا قرار دهید.
کش کردن نتیجه دستور سفارشی ممکن است وسوسه شوید که با انجام برخی عملیات در داخل دستور، عملکرد آن را سریعتر کنید و سپس نتیجه را در داخل رشته برگشتی قرار دهید:
|
پارامترها در دستورهای Blade سفارشی
اگر بخواهید در منطق سفارشی خود پارامتر بپذیرید، به مثال ۴-۳۰ نگاهی بیندازید.
مثال 4-30. ایجاد یک دستور Blade با پارامترها
// متصل کردن
Blade::directive('newlinesToBr', function ($expression) {
return "<?php echo nl2br({$expression}); ?>";
});
// در عمل
<p>@newlinesToBr($message->body)</p>
پارامتر $expression که به closure ارسال میشود، نمایانگر هر چیزی است که در داخل پرانتزها قرار دارد. همانطور که مشاهده میکنید، سپس یک قطعه کد PHP معتبر تولید کرده و آن را باز میگردانیم.
اگر خود را در حال نوشتن مکرر منطق شرطی مشابهی دیدید، بهتر است یک دستور Blade سفارشی در نظر بگیرید.
مثال: استفاده از دستورهای Blade سفارشی برای یک اپلیکیشن چندمستأجره
فرض کنید داریم یک اپلیکیشن میسازیم که از چند مستأجر پشتیبانی میکند، به این معنی که کاربران ممکن است از آدرسهایی مانند www.myapp.com، client1.myapp.com، client2.myapp.com یا مکانهای دیگر به سایت مراجعه کنند.
فرض کنید یک کلاس به نام Context نوشتهایم تا برخی از منطقهای چندمستأجره خود را در آن محصور کنیم. این کلاس اطلاعات و منطق مربوط به زمینه بازدید فعلی را ضبط میکند، مانند اینکه کاربر احراز هویتشده کیست و آیا کاربر به وبسایت عمومی مراجعه میکند یا به یک زیر دامنه مشتری.
احتمالاً ما بهطور مکرر آن کلاس Context را در نماهایمان فراخوانی کرده و بر اساس آن شرطها را اجرا خواهیم کرد، مانند مثال ۴-۳۱. app('context') یک میانبر برای گرفتن یک نمونه از یک کلاس از کانتینر است که در فصل ۱۱ بیشتر در مورد آن خواهیم آموخت.
@if (app('context')->isPublic())
© Copyright MyApp LLC
@else
© Copyright {{ app('context')->client->name }}
@endif
فرض کنید بتوانیم @if (app('context')->isPublic()) را به سادگی به @ifPublic تبدیل کنیم. بیایید این کار را انجام دهیم. به مثال ۴-۳۲ نگاه کنید.
مثال 4-32. شرطگذاری بر اساس کانتکست با استفاده از یک دستور سفارشی Blade
// متصل کردن
Blade::directive('ifPublic', function () {
return "<?php if (app('context')->isPublic()): ?>";
});
// در عمل
@ifPublic
© Copyright MyApp LLC
@else
© Copyright {{ app('context')->client->name }}
@endif
از آنجایی که این به یک دستور if ساده تبدیل میشود، هنوز میتوانیم از دستورات @else و @endif بومی استفاده کنیم. اما اگر بخواهیم، میتوانیم یک دستور @elseIfClient سفارشی، یا یک دستور @ifClient جداگانه، یا هر چیز دیگری که بخواهیم ایجاد کنیم.
دستورات سفارشی آسان تر برای دستورات "if"
تست
خلاصه
Blade موتور قالبسازی لاراول است. تمرکز اصلی آن بر روی نوشتار واضح، مختصر و بیانی با قابلیت ارثبری و گسترش قدرتمند است. براکتهای "نمایش ایمن" آن {{ و }} هستند، براکتهای نمایش غیر محافظت شده آن {!! و !!} هستند و مجموعهای از تگهای سفارشی به نام دستورات وجود دارند که همه با @ شروع میشوند (برای مثال، @if و @unless).
شما میتوانید یک قالب والد تعریف کنید و "حفرههایی" در آن برای محتوا ایجاد کنید با استفاده از @yield و @section/@show. سپس میتوانید به نماهای فرزند خود آموزش دهید تا از آن ارثبری کنند با استفاده از @extends('parent.view') و بخشهای خود را با استفاده از @section/@endsection تعریف کنند. برای ارجاع به محتوای بلوک والد از @parent استفاده میکنید.
کمپوزرهای نما باعث میشوند که هر بار که یک نمای خاص یا زیرنمای آن بارگذاری میشود، اطلاعات خاصی برای آن در دسترس باشد. همچنین، تزریق سرویس به نما اجازه میدهد تا خود نما دادهها را مستقیماً از کانتینر برنامه درخواست کند.