Blade 允许你继承、修改和包含其他的视图

@yield@section .. @show 设置布局

  • 定义一个布局视图,也就是模板
  1. <!-- 父模板 resources/views/layouts/master.blade.php -->
  2. <!doctype html>
  3. <html lang="zh-CN">
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8. <title>@yield('title','主页')</title>
  9. </head>
  10. <body>
  11. <div class="container">
  12. @yield('content')
  13. </div>
  14. @section('footerScripts')
  15. <script src="app.js"></script>
  16. @show
  17. </body>
  18. </html>

@yield@section 基本完全一样,都用在父模板中,子视图会覆盖其中的内容,都可以设置默认值; 唯一区别在于,@section 可以在子模版中到用父模板的内容, 通过 @parent

  • 定义一个子视图,继承刚才的模板
<!-- 继承父模版  resources/views/dashboard.blade.php -->
@extends('layouts.master')

@section('title','dashboard')

@section('content')
    Welcome to your application dashboard!
@endsection

@section('footerScripts')
    @parent
    <script src="dashboard.js"></script>
@stop

tip: @show VS @endsection VS @stop

`@show` 用于父模板中; 
`@endsection` 用于子模版中
`@stop` 是 `@endsection` 的 别名

@extends

表示一个视图继承另一个视图,即: 这个视图不是单独存在的,它的作用更像是定义了各个部分的内容,而不是一个完整的 html 页面。

@extends('layouts.master') 表示它继承自 resources/views/layouts/master.blade.php 这个文件

每个视图只能继承一个文件,且继承应该放在第一行

@section and @endsection

简单内容支持 @section('title','dashboard') 这种写法,@stop@endsection 的别名, 两者完全一样

@parent

父模板中,使用 @section('footerScripts') 含默认值的形式定义了 footerScript 部分,意味着,在子模版中,我们有两种选择,1.完全重写父模板的内容,2.在父模板的基础上追加内容。

@parent 即表示获取父模板的内容,故例子中意味着追加

包含视图

@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>

传递数据,你可以通过第二个参数,显式的传递(如 $text),也可以引用包含文件中的可以用的变量(如 $pageName)。

more ↓

{{-- 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

渲染文章和文章评论

{{ 原始状态 }}
@if (count($article->comments)>0)
    @foreach( $article->comments as $comment )
        @include('comments.item',['comment'=>$comment])
    @endforeach
@else
    @include('comments.no-item')
@endif

{{ 优化1 }}
@forelse($article->comments as $comment)
    @include('comments.item') 
@empty
    @include('comments.no-item')
@endforelse

{{ 优化2 }}
@each('comments.item', $article->comments, 'comment', 'comments.no-item')

参数说明

  1. 循环要渲染的视图
  2. 需要循环的数据,通常是 array 或 collection
  3. 赋予当前渲染视图的变量名
  4. 当循环数据(参数2)为空时,渲染的视图 【可选】

使用 Stacks

当多次继承时,如果每个页面都要向父模板添加内容,基本的 include 就不好处理了。

特别是某些页面或 sections 有一些特定的 css 或 js 需要加载时,include 是不合适的,因为依赖顺序的问题。 Stacks 被用来解决这些问题。

在父模板定义一个 stack ,它仅仅是一个占位符,在子模版,你可以 @push/@endpush 推一些内容到 堆栈(stack) 的尾部,或者,@prepend/@endprepend 插入一些内容到 堆栈(stack) 的首部 。

<!-- 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>

使用 Components 和 Slots

当你包含的模板要引入 大块 内容做变量时,考虑使用 compontensslot

<!-- resources/views/partials/modal.blade.php -->
<div class="modal">
    <div>{{ $content }}</div>
    <div class="close button etc">...</div>
</div>

<!-- in another template -->
@include('partials.modal', [
    'body' => '<p>The password you have provided is not valid. Here are the rules for valid passwords: [...]</p><p><a href="#">...</a></p>'
])
{{ 优化 }}
<!-- resources/views/partials/modal.blade.php -->
<div class="modal">
    <div>{{ $slot }}</div>
    <div class="close button etc">...</div>
</div>

<!-- in another template -->
@component('partials.modal')
    <p>The password you have provided is not valid.
    Here are the rules for valid passwords: [...]</p>
    <p><a href="#">...</a></p>
@endcomponent

多个 slot

<!-- resources/views/partials/modal.blade.php -->
<div class="modal">
    <div class="modal-header">{{ $title }}</div>
    <div>{{ $slot }}</div>
    <div class="close button etc">...</div>
</div>


@component('partials.modal')
    @slot('title')
        Password validation failure
    @endslot

    <p>The password you have provided is not valid.Here are the rules for valid passwords: [...]</p>
    <p><a href="#">...</a></p>
@endcomponent

为你的组件起一个别名

// AppServiceProvider@boot
Blade::component('partials.modal', 'modal');

<!-- in a template -->
@modal
    Modal content here
@endmodal

视图合成器 View Composers 和 服务注入

你可以在路由中,直接传递参数给视图, 第三章 👇

Route::get('passing-data-to-views', function () {
    return view('dashboard')
        ->with('key', 'value');
});

但是,有时,你发现你需要一遍遍的传递相同的数据给视图,或者一些 header 之类的,你愿意每次都使用路由传递吗?

使用 View Composers 绑定数据

谢天谢地,View Composers 是一种简单的方式,它允许你指定一些视图加载时,传递给他们某些数据。

假想: 我们有一个侧边栏目 partials.sidebar (resources/views/partials/sidebar.blade.php) , 显示最新的文章,多个页面都要用到这个侧边栏。 我们应该怎么传递数据呢?

  1. 路由
Route::get('home', function () {
    return view('home')
        ->with('posts', Post::recent());
});

Route::get('about', function () {
    return view('about')
        ->with('posts', Post::recent());
});

// 不断重复,烦人的方式
  1. 使用 share
// Some service provider
public function boot()
{
    // ...
    view()->share('recentPosts', Post::recent());
}

// view()->share 分享变量到每一个视图,过火了,哥们
  1. 基于 View Composers 使用闭包,实现视图范围控制
// Some service provider@boot
view()->composer('partials.sidebar', function ($view) {
    $view->with('recentPosts', Post::recent());
});

多个视图时

view()->composer(['partials.header', 'partials.footer'], function($view){
    $view->with('key','data');
})

//or 使用通配符
View::composer('partials.*', function () {
    $view->with('recentPosts', Post::recent());
});
  1. 基于 View Composers 使用类,实现视图范围控制
    最灵活 但 也最复杂
    1. 创建 RecentPostsComposer.php 官方文档建议放在 App\Http\ViewComposers
<?php
namespace App\Http\ViewComposers;

use App\Post;
use Illuminate\Contracts\View\View;

class RecentPostsComposer
{
    public function compose(View $view)
    {
        $view->with('recentPosts', Post::recent());
    }
}

这个 Composer 被调用时 compose() 就会执行 ,这里绑定了数据。

2. 将这个类放到某个地方, 一般创建 `ViewComposerServiceProvider`, 暂时先在 `App\Providers\AppServiceProvider` 中注册
// App\Providers\AppServiceProvider 
public function boot()
{
    view()->composer('partials.sidebar', \App\Http\ViewComposers\RecentPostsComposer::class);
}

Blade service injection

有三种类似的数据我们需要传到视图:

  • 需要迭代的数据集合
  • 需要在视图显示的对象
  • 产生数据或视图的服务

注入服务最简单的方式如下:
通过路由注入服务

// 参数中指定类型 为服务
Route::get('backend/sales', function (AnalyticsService $analytics) {
    return view('backend.sales-graphs')
        ->with('analytics', $analytics);
});

// 视图中使用服务
<div class="finances-display">
    {{ $analytics->getBalance() }} / {{ $analytics->getBudget() }}
</div>

Blade service injection 可以让事情变得更简单

直接注入服务到视图

@inject('analytics', 'App\Services\Analytics')

<div class="finances-display">
    {{ $analytics->getBalance() }} / {{ $analytics->getBudget() }}
</div>

自定义 Blade 指令

你可以自定义你的 blade 指令,你可能会想,这是一种简化代码的好方式,例如: @button('buttonName') 替换一堆按钮的 html ,但是,这是糟糕的,更好的方式可能是包含一个视图模板。

自定义模板指令,倾向于简化最有用的重复逻辑。例如,@if(auth()->guest()) (检验用户是否登录),我们可以定义一个 @ifGuest 的指令替代它。

一般创建一个服务注册自定义指令, 暂时先在 App\Providers\AppServiceProvider 中注册

public function boot()
{
    Blade::directive('ifGuest', function () {
        return "<?php if (auth()->guest()): ?>";
    });
}

我们已经注册了这个指令,当我们写 @ifGuest 时,它将会被替换成 <?php if (auth()->guest()): ?>

tip: 你可能会写出如下代码。

Blade::directive('ifGuest', function () {
    // 这是反模式的 ! 不要复制.
    $ifGuest = auth()->guest();
    return "<?php if ({$ifGuest}): ?>";
});

以上代码,假设自定义从指令每次页面加载时都重新生成,但实际情况是,blade 缓存会缓存指令,所以以上代码将达不到你的预期。

带参数的自定义指令

// Binding
Blade::directive('newlinesToBr', function ($expression) {
    return "<?php echo nl2br({$expression}); ?>";
});
// In use
<p>@newlinesToBr($message->body)</p>

例子: 多用户站点使用自定义指令

假设我们有个多用户应用,有 www.myapp.com, client1.myapp.com, client2.myapp.com 等网站。

我们已经封装了一个类 (Context), 用来捕获当前访问者相关的一些信息和逻辑,例如,当前的认证用户是谁,他正在访问公共站点还是子站点。

我们可能经常做出如下操作:

@if (app('context')->isPublic())
    &copy; Copyright MyApp LLC
@else
    &copy; Copyright {{ app('context')->client->name }}
@endif

我们可以简化 @if (app('context')->isPublic())@ifPublic,之后代码将如下

// Binding
Blade::directive('ifPublic', function () {
    return "<?php if (app('context')->isPublic()): ?>";
});

// In use
@ifPublic
    &copy; Copyright MyApp LLC
@else
    &copy; Copyright {{ app('context')->client->name }}
@endif

进一步简化,使用 if

// Binding
Blade::if('ifPublic', function () {
    return (app('context'))->isPublic();
});