Laravel offers a custom templating engine called Blade, which is inspired by .NET'S Razor engine. It boasts a concise syntax, a shallow learning curve, a powerful and intuitive inheritance model, and easy extensibility.
For a quick look at what writing Blade looks like, check out Example 4-1.
Example 4-1. Blade samples
{{ $group->title }}
{!! $group->heroImageHtml() !!}
@forelse ($user as $user)
{{ $user->first_name }} {{$user -> last_name }}
@empty
No user in this group
@endforelse
As you cna see, Blade introduces a convention in which its custom tags, called "directives," are prefixed with an @. You'll use directives for all of your control structures and also for inheritance and any custom functionality you want to add.
Blade's syntax is clean and concise, so at its core it's just more pleasant and tidy to work with than the alternatives. But the moment you need anything of any complexity in your templates-nested inheritance, complex conditionals, or recursion-Blade starts to really shine. Just like the best Laravel components, it takes complex application requirements and makes them easy and accessible.
Additionally, since all Blade syntax is compiled into normal PHP code and then cached, it's fast and it allows you to use native PHP in your Blade files if you want. However, I'd recommmend avoiding usage of PHP if at all possible-usually if you need to do anything that you can't do with Blade or a custom Blade directive, it doesn't belong in the template.
Using Twig with Laravel
Unlike many other Symfony-based frameworks, Laravel doesn't use Twig by default. But if you're just in love with Twig, there's a TwigBridge package that makes if easy to use Twig instead of Blade.
Echoing Data
As you can see in Example 4-1, {{and}} are used to wrap sections of PHP that you'd like to echo. {{ $variable }} is similar to in plain PHP.
It's different in one way, however, and you might've guessed this already: Blade escapes all echoes by default useing PHP's htmlentities() to protect your users from malicious script insertion. That means {{ $variable }} is functionally equivalent to . If yhou want to echo without the escaping, use {!! and !!} instead.
{{ and }} When Using a Frontend Templating Framework
You might've noticed that the echo syntax for Blade ({{ }}) is similar to the echo syntax for many frontend frameworks. So, how does Laravel know when you're wirting Blade versus Handlebars?
Blade will ignore any {{ that's prefaced with an @. So, it will parse the first of the following examples, but the second will be echoed out directly:
// Parsed as Blade; the value of $bladeVariable is echoed to the view
{{ $bladeVariable }}
// @ is removed, and "{{ handlebarsVariable }}" echoed to the view directly
@{{ handlebarsVariable }}
Control Structures
Most of the control structures in Blade will be very familiar. Many directly echo the name and structure of the same tag in PHP.
There are a few convenience helpers, but in general, the control structures just look cleaner than they would in PHP.
Conditionals
First, let's take a look at the control strucrures that allow for logic.
@if
Blade's @if ($condition) compiles to . @else, @elseif, and @endif also compile to the exact same syntax in PHP. Take a look at Example 4-2 for some examples.
Example 4-2. @if, @else, @elseif, and @endif
@if ( count($talks) === 1)
There is one talk at this time period.
@elseif (cout($talks) === 0)
There are no talks at this time period.
@else
There are {{ count($talks) }} talks at this time period.
@endif
Just liek with the native PHP conditionals, you can mix and match these how you want. They don't have any special logic; there's literally a parser looking for someting with the shape of @if $condition) and replacing it with the appropriate PHP code.
@unless and @endunless
@unless, on the other hand, is a new syntax that doesn't have a direct equivalent in PHP. It's the direct inverse of @if. @unless condition) is the same as
Example 4-3. @unless and @endunless
@unless $user -> hasPaid())
You can complete your payment by switching to the payment tab.
@endunless
Loops
Next, let's take a look at the loops.
@for, @foreach, and @while
@for, @foreach, and @while work the same in Blade as they do in PHP; see Examples 4-4, 4-5, and 4-6.
Example 4-4. @for and @endfor
@for ($i = 0; $i < $talk ->slotsCount(); $i++)
The number is {{ $i }}
@endfor
Example 4-5. @foreach and @endforeach
@foreach ($talks as $talk)
{{ $talk -> title }} ({{ $talk->length }} minutes)
@endforeach
Example 4-6. @while and @endwhile
@while $item = array_pop($items))
{{ $item->orSomething() }}
@endwhile
@forelse
@forelse is a @foreach that also allows you to program in a fallback if the object you're iterating over is empty. We saw it in action at the start of this chapter; Example 4-7 shows another example.
Exmaple 4-7. @forelse
@forelse $talks as $talk)
{{ $talk -> title }} ( {{ $talk ->length }} minutes )
@empty
No talks this day
@endforelse
$loop Within @foreach and @forelse
The @foreach and @forelse directives in Laravel 5.3 add one feature that's not available in PHP foreach loops: the $loop variable. Used within a @foreach or @forelse loop, this variable will return a stdClass object with the following properties:
index
The 0-based index of the current item in the loop; 0 would mean "first item"
iteration
The 1-based index of the current item in the loop; 1 would mean "first item"
remaining
How many items remain in the loopo; if the current item is the first of three, this will be 2
count
The count of items in the loop
last
A boolean indicating whether this is the last item in the loop
depth
How many "levels" deep this loop is: 1 for a loop, 2 for a loop within a loop, etc.
parent
A reference to the $loop variable for the parent loop item; if this loop is within
another @foreach loop otherwise, null
Here's an example fo how to use it:
@foreach pages as $page)
@if $page->hasChildren())
@foreach $page->children() as $child)
{{ $loop->iteration }}:
{{ $child->title }}
@endforeach
@endif
@endforeach
or
If you're ever unsure whether a variable is set, you're probably used to checking isset() on it before echoing it, and echoing something else if it's not set. Blade has a convenience helper,or , that does this for you and lets you set a default fallback: {{ $title ro "Default" }} will echo the value of $title if it's set, or "Default" if not.
Template Inheritance
Blade provides a structure for template inheritance that allows views to extends, modify, and include other views.
Here's how inheritance is structured with Blade.
Defining Sections with @section/@show and @yield
Let's start with a top-level Blade layout, like in Example 4-8. This is the definition of the generic page wrapper that we'll later place page-specific content into.
Example 48. Blade layout
@yield('content')
@sectionfooterScripts')
@show
This looks a bit like a normal HTML page, but you can see we've yielded in two places(title and content), and we've defined a section in a third(footerScripts).
We have threee Blade directive here that each look a little different: @yield('title', 'Home Page') alone, @yield('content')with a deined default, and @section ...
@show with actual content in it.
All three function essentially the same. All three are defining that there's a section with a given name (which is the first parameter). All three are defining that the section can be extended later. And all three are defining what to do if the section isn't extedned, either by providing a string fallback ('Home Page'), no fallback (which will just not show anything if it's not extednded), or an entrie block fallback (in this case, ).
What's different? Well, clearly, @yieldcontent') has no default content. But addtionanlly, the default content in @yield('title') only will be shown if it's never extended. If it is extended, its child sections will not have programmatic access to the default value. @section ... @show, on the other hand, is both defining a default and doing so in a way that its default contents will be available to its children, through @parent.
Once you have a parent layout like this, you can extend it like in Example 4-9.
Example 4-9. Extending a Blad layout
@extendslayouts.master;)
@section('title', 'Dashboard')
@section('content')
Welcom to your application dashboard!
@endsection
@section('footerScripts')
@parent
@endsection
@show versus @endsection
You may have noticed that Example 4-8 uses @section ... @show, but Example 4-9 uses @section ... @endsection. What's the difference?
Use @show when you're defining the place for a section, in the parent template. Use @endsection when you're defining the content for a template in a child template.
This child view will actually allow us to cover a few new concepts in Blade inheritance.
@extends
First, with @extends('layouts.master'), we define that this view should not be rendered on its own, but that is instead extends antoer view. That means its role is to define the content of various sections, but not to stand alone. It's almost more like a series of buckets of content, rrather than an HTML page. This line also defines that the view it's extending lives at resources/views/layouts/master.blade.php.
Each file should only extend one other file, and the @extends call should be the first line of the file.
@section and @endsection
Second, with @section('title', 'Dashboard'), we provide our content for the first section, title. Since the content is so shortk, instead of using @section and @endsection we're just using a shortcut. This allows us to pass the content in as the second parameter of @section and then move on. If it's a bit disconcerting to see @section without @endsection, you could just use the normal syntax.
Third, with @section('content') and on, we use the normal syntax to define the contents of the contentn section. We'll just throw a little greeting in for now. Note however, that when yhou;re using @section in a child view, you end it with @endsection r its alias @stop), instead of @show, which is reserved for defining sections in parent views.
@prent
Fourch, with @section('footerScripts') and on, we use the normal syntax to define the contents of the footerScripts section.
But remember, we actually defined that content (or, at least, its "default") already in the master layout. So this time, we have two options:we can either overwrite the content from the parent view, or we can add to it.
You can see that we have the option to include the content from the parent by using the @parent directive within the section. If we didn't, the content of this section would entirely overwrite anything defined in the parent for this section.
@include
Now that we've established the basics of inheritance, there are a few more tricks we can perform.
What if we're in a view and want to pull in another view? Maybe we have a cll-to-action "sign up" button that we want to re-use around the site. And maybe we want to customize its button text every time we use it. Take a look at Example 4-10.
Example 4-10. Includeng view partials with @include
Here's why you should sign up for our app: It's Gread.
@include('sign-up-button', ['text' => 'See just how great it is' ])
{{ $text }}
@include pulls in the partial and, optionally, passes data into it. Note that not only can you explicitly pass data to an include via the second parameter of @include, but you can also reference any variables within the included file that are available to the including view ($pageName, in this example). Once again, you can do whatever you want, but I would recommend you consider always explicitly passing every variable that you intend to use, just for clarity.
@each
You can probably imagin some circumstances in which you'd need to loop over an array or collection and @include a partial for each item. There's a directive for that:@each.
Let's say we have a sidebar compposed of modules,and we want to include multiple modules, each with a different tile. Take a look at Example 4-11.
Example 4-11. Using view partials in a loop with @each
@eachpartials.module', $modules, 'module', 'partials.empty-module')
{{ $module->title }}
No modules :(
View Composers and Service Injection
As we covered in Chapter 3, it's simple to pass data to our views from the route definition (see Example 4-12).
Example 4-12. Reminder on how to pass data to views
Route::get('passing-data-to-views', function(){
return view('dashboard')
->with('key', 'value');
});
There are times, however, when you will find yourself passing the same data over and over to multiple views. Or, you might find yourself using a header partial or something similar that requires some data; will you now have to pass that data in from every route definition that might ever load that header partial?
Binding Data to Views Using View Composers
Thankfully, there's a simpler way. The solution is called a view composer, and it allows you to define that any t ime a particular view loads, it should have certain data passed to it-without the route definition having to pass that data in explicitly.
Let's say you have a sidebar on every page, which is defined in a partial named partials.sidebar (resources/views/partials/sidebar.blade.php) and then included on every page, This sidebar shows a list of the last seven posts that were published on your site. If it's on every page, every route definition would normally have to grab that list and pass it in, like in Example 4-13.
Example 4-13. Passing sidebar data in from every route
Route::get('home', function(){
return view('home')
->with('posts', Post::recent());
});
Route::get('about', function(){
return view('about')
->with('posts', Post::recent());
});
That could get annoying quickly.Instead, we're going to use view composers to "share" that variable with a prescribed set of views. We can do this a few ways,, so let's start simple and move up.
Sharing a variable globally
First, the ismplest option: just globally "share" a variable with every view in your application like in Example 4-14.
Example 4-14. Sharing a variable globally
// Some service provider
public funciton boot()
{
....
view()->share('posts', Post::recent());
}
If you want to use view()->share(),the besst place would be the boot() method of a service provider so that the binding runs on every page load. You can create a custom ViewComposerService Provider (see Chapter 11 for more about service providers), but for now just put it in App\Providers\AppServiceProvider in the boot() method.
Using view()->share() makes the variable accessible to every view in the entire application, however, so might be overkill.
Closure-based view composers
The next option is to use a closure-based view composer to share variables with a single view, like in Example 4-15.
Example 4-15. Creating a closure-based view composer
view()->composer('partials.sidebar', function ($view){
$view->with('posts', Post::recent());
});
As you can see, we've defined the name of the view we want it shared with in the first parameter(partials.sidebar)and then passed a closure to the second parameter; in the closure, we've used $view->with() to share a variable, but now only with a specific view.
Anywhere a view composer is binding to a particular view (like in Example 4-15, which binds to partials.sidebar), you can pass an array of view names instead to bind to multiple views.
You can also use an asterisk in the view path, as in partials.*, tasks.*, or just *:
view()->composer(
['partials.header', 'partials.footer'],
function(){
$view->with('posts', Post::recent());
}
);
view()->composer('partials.*', function(){
$view->with('posts', Post::recent());
});
Class-based view composers
Finally, the most flexible but also most complex option is to create a dedicated class for your view composer.
First, let's create the view composer class. There's no formally defined palce for view composers to live, but the docs recommend App\Http\ViewComposers. So, let's create App\Http\ViewComposers\RecentPostsComposer like in Example 4-16.
Example 4-16. A view composer
namespace App\Http\Viewcomposers;
user App\Post;
user Illuminate\Contracts\View\View;
class RecentPostsComposer
{
private $posts;
public function __construct(Post $posts)
{
$this->posts = $posts;
}
public function compose(View $view)
{
$view->with('posts', $this->posts->recent());
}
}
As you can see, we're injecting the Post model (typehinted constructor parameters of view composers will be automatically injected; see Chapter 11 for more on the container and dependency injection). Note that we could skip the private $posts and the constructor injection and just usse Post::recent() in the compose() method if we wanted. Then, when this composer is clalled, it runs the compose() method, in which we bind the posts variable to the result of running the recent() method.
Like the other methods of sharing variables, this view composer needs to have a binding somewhere. Again, you'd likely create a custom ViewComposerServiceProvider, but for now, as seen in Exmaple 4-17, we'll just put it in the boot() method of App \Providers\AppServiceProvider.
Example 4-17. Registering a view composer in AppServiceProvider
//AppServiceProvider
public function boot()
{
...
view()->composer(
'partials.sidebar',
\App\Http\Viewcomposers\RecentPostsComposer::class
);
}
Note that this binding is the same as closure-based view composer, but instead of passing a closure, we're passing the class name of our view composer. Now, every time Blade renders the partials.sidebar view, it'll automatically run our provider and pass the view a posts variable set to the results of the recent() method on our Post model.
Blade Service Injection
There are three primary types of data we're most likely to inject into a view: collections of data to iterate over, single objects that we're displayingn on the page, and services that generate data or views.
With a service, the pattern will most likely look like Example 4-18, where we inject an instance of our analytics service into the route definition by typehinting it in the route's method signature, and then pass it into the view.
Example 4-18. Injecting service into a view via the route definition constructor
Route::get('backend/sales', function(AnalyticsService $analytics){
return view('bakcend.sales-graphs')
-> with('analytics', $analytics);
});
Just as with view composers, Blade's service injection offers a convenient shortcut to reduce duplicationin your route definitions. Normally, the content of a view using our analytics service might look like Exmaple 4-19.
Example 4-19. Using an injected navigation service in a view
{{ $analytics->getBalance() }} / PP $analytics->getBudget() }}
Blade serviceinjection makes it easy to inject an instance of a class outside of the container directly from the view, like in Example 4-20.
Example 4-20. Injecting a service directly into a view
@injectanalytics', 'App\Services\Anaalytics')
{{ $analytics->getBalance() }} / {{ $analytics->getBudget() }}
As you can see, this @inject directive has acually made an $analytics variable available, whichwe're using later in our view.
The first parameter of @inject is the name of the variable you're injecting, and the second parameter is the class of interface that you want to inject an instance of.
This is resolved just like when you type hint a dependency in a constructor elsewhere in Laravel;if you're unfamiliar with how that works, go to Chapter 11 to learn more.
Just like view composers, Blade service injection makes it easy to make certain data or functionality available to every instance of a view, without having toinject it via the route definition every time.
Custom Blade Directives
All of the built-in syntax of Blade tahtwe've covered so far-@if, @unless, and so on - are called directives, Each Blade directive is a mapping between a pattern (e.g., @if ($condition)) and a PHP output( e.g., ).
Directives aren't just for the core; you can actually create your own. You might think directives are good for making little shortcuts to bigger pieces of code-for example, using @button('buttonName') and having it expand to a larger set of button HTML.
This isn't a terrible idea, but for simple code expansion like this you might be better off including a view partial.
I've found custom directives to be the most useful when they simplify some form of repeated logic. Say we're tired of having to wrap our code with @if (auth()->guest() (to check if a user is logged in or not)and we want a custom @ifGuest directive. As with view composers, it might be worth having a custom service provider to register these, but for no let's just put it in the boot() method of App\Profiders\AppServiceProvider. Take a look at Example 4-21 to see what this binding will look like.
Example 4-21. Binding a custom Blade directive
// AppServiceProvider
public function boot()
{
Blade::directive('ifGuest', function() {
return "guest()): ?>"
});
}
We've now registered a custom directive, @ifGuest, which will be replaced with the PHP code guest()): ?>.
This might feell strange. You're writing a string that will be returned and then executed as PHP, But what this means is that you can now take the complex, or ugly, or unclear, or repetitive aspects of your PHP templating code and hide them behind clear, simple, and expressive syntax.
Custom directive result caching
You might be tempted to do some logic to make your custom directive faster by performing an operation in the binding and then embedding the result within the returned string:
Blade::directive('fiGuest', function(){
// Antipattern! Do not cop.
ifGuest = auth()->guest();
return "";
});
The problem with this idea is that it assumes this directive will be re-created on every page load. However, Blade caches aggressively, so you're going to find yourself in a bad spot if you try this.
Parameters in Custom Blade Directives
What if you want to check a condition in your custom logic? Check ou Example 4-22.
Example 4-22. Creating a Blade directive with parameters
//Binding
Blade::directive('newlinesToBr', function($expression){
return "";
});
//In use
@newlinesToBr($message->body)
The $expression parameter received by the closure represents whatever's within the parentheses. As you can see, we then generate a valid PHP cod snippet and return it.
$expression parameter scoping before Laravel 5.3
Before Laravel 5.3, the $expression parameter also included the parentheses themselves. So, in Exmaple 4-22, $expression (which is $message->body in Laravel 5.3 and later) would have instead been ($message->body), and we would've had to write .
If you find yourself constantly writing the same conditional logic over and over, you should consider a Blade directive.
Example: Using Custom Blade Directives for a Multienant App
So, let's imagine we're building an application that supports multitenancy, which means users might be visiting the site from www.myapp.com, client1.myapp.com, client2.myapp.com, or elsewhre.
Suppose we have written a class to encapsulate some of our multitenancy logic and named it Context. this class will capture information and logic about the context of the current visit, such as who the authenticated user is and whether the user is visiting the public website or a client subdomain.
We'll probably frequently resolve that Context class in our views and perform conditionals on ti, like in Example 4-23. The app('context') is aa shortcut to get an instance of a class from the container, which we'll learn more about in Chapter 11.
Example 4-23. Conditional on context without a custom Blade directive
@if (app('context')-> isPublic())
© Copyright MyApp LLC
@else
© Copyright {{ app('context') -> client ->name }}
@endif
What if we could simplify @if (app('context') ->isPublic()) to just @ifPublic?
Le's do it, Check out Example 4-24/
Example 4-24.Conditionals on context with a custom Blade directive
// Binding
Blade::directive('ifPublilc', function(){
return "isPublic()): ?>";
});
// In user
@ifPublic
© Copyright MyApp LLC
@else
© Copyright {{ app('context')->client->name }}
@eldif
Since this resolves to a simple if statement, we can still rely on the native @else and @endif conditionals. But if we wanted, we could also create a custom @elseIfClient directive, or a separate @ifClient directive, or really whatever else we want.
Testing
The most common method of testing views is through application testing, meaning that you're actually calling the route that displays the views and ensuring the views have certain content (see Example 4-25). You can also click buttons or submit forms and ensure that you are redirected to a certain page, or that you see a certain error.
(You'll learn more about testing in Chapter 12.)
Example 4-25. Testing that a view display certain content
// EventsText.php
public function test_list_page_show_all_events()
{
$event1 = factory(Event::class) -> create();
$event2 = factory(Event::class) -> create();
$this->visit('events')
->see($event1->title)
->see($event2->ttile)
}
You can also test that a certain view has been passed a particular set of data, which, if it accomplishes your testing goals, is less fragile than checking for certain text on the page. Example 4-26 demonstrates this approach.
Example 4-26. Testing that a view was passed certain content
// EventsTest.php
public function test_liat_pageshows_all_events()
{
$event1 = factory(Event::class)->create();
$event2 = factory(Event::class)->create();
$this->visit('events');
$this->assertViewHas('events', Event::all());
$this->assertViewHasAll([
'events' => Event::all(),
'title' => 'Events Page'
]);
$this->assertViewMissing('dogs');
}
In 5.3, we gained the ability to pass a closure to $assertViewHas(), meaning we can customize how we want to check more complex data structures. Example 4-27 illustrates how we might use this.
Example 4-27. Passing a closure to assertViewHas()
//EventsTest.php
public function test_list_page_shows_all_events()
{
$event1 = factory(Event::class)->create();
$this->visit('events/' . $event1->id);
$this->assertViewHas('event', function($event) use ($event1) {
return $event->id === $event1->id;
});
}
TL;DR
Blade is Laravel's templating engine. Its primary focus is a clear, concise, and expressive syntax with powerful inheritance and extensibility. Its "safe echo" brackets are {{ and }}, it unprotected echo brackets are {!! and !!}, and it has a series of custom tags called directives that all begin with @@if and @unless, for example).
You can define a paret template and leave "holes" in it for content useing @yield and @section/@show. You can then teach its child views to extedd it using @extends('parent.view.name'), and define their sections using @section/@endsection. You use @parent to reference the content of the block's parent.
View composers make it easy to define that, every time a particular view or subview loads, it should have certain information available to it. And service injection allows the view itself to request data straight from the application container.
댓글 없음:
댓글 쓰기