Laravel5.1组件概览

Published on 2017 - 03 - 30

Creating Your First View

In the previous article we created the TODOParrot project and viewed the default landing page within the browser. The page was pretty sparse, consisting of the text “Laravel 5” and a random quotation. If you view the page’s source from within the browser, you’ll see a few CSS styles are defined, a Google font reference, and some simple HTML. You’ll find this view in the file welcome.blade.php, found in the directory resources/views. Open this file in your PHP editor, and update it to look like this:

<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Welcome to TODOParrot</title>
</head>
<body>
<h1>Welcome to TODOParrot</h1>
</body>
</html>

Reload the application’s home page within your browser (don’t forget to restart the PHP development server if you shut it down after completing the last chapter), and you should see the header “Welcome to TODOParrot”. Congratulations! You’ve just created your first Laravel view.

So why is this particular view returned when you navigate to the application home page? The welcome.blade.php view is served by a simple route definition found in the app/Http/routes.php file:

Route::get('/', function () {
    return view('welcome');
});

This route tells Laravel to serve the welcome.blade.php file when a GET request is made to the application’s homepage, represented by the forward slash (/). Although the majority of your views will be served in conjunction with a controller (more about this in a bit), if your application contains a bit of static content (such as an “About Us” page) then the above approach is a convenient solution for directly serving the view.

Because it’s understood that views use the .php extension, Laravel saves you the hassle of referencing the extension. Of course, you’re free to name the view whatever you please; try renaming welcome.blade.php as hola.blade.php, and then update the view method to look like this:

return view('hola');

Incidentally, for organizational purposes you can manage views in separate directories. To do so you can use a convenient dot-notation syntax for representing the directory hierarchy. For instance you could organize views according to controller by creating a series of aptly-named directories in resources/views. As an example, create a directory named home in resources/views, and move the welcome.blade.php view into the newly created directory. Then update the route to look like this:

Route::get('/', function () {
    return view('home.welcome');
});

You’re certainly not required to manage views in this fashion, however I find the approach indispensable given that a typical Laravel application can quickly grow to include dozens of views.

Creating Your First Controller

The lone default route serves the important purpose of giving you something to see when accessing the home page of a newly generated Laravel application, however in practice you’ll rarely serve views directly through the routes.php file. This is because the majority of your views will contain some degree of dynamic data, and this dynamic data is typically retrieved and passed into a view by way of a controller.

Although we’re not yet ready to begin passing dynamic data into a view (I’ll introduce this topic later in the chapter), it seems a fine time to learn how to create a controller capable of serving the welcome view. You can easily generate controllers using Artisan’s make:controller command:

$ php artisan make:controller --plain WelcomeController
Controller created successfully.

When generating controllers with make:controller, Laravel will by default stub out the various actions comprising a RESTful resource. You can override this behavior and instead create an empty controller by passing along the --plain option as demonstrated above. Doing so will create the following empty controller class, placing the contents inside app/Http/Controllers/WelcomeController.php:

 <?php

 namespace todoparrot\Http\Controllers;

 use Illuminate\Http\Request;

 use todoparrot\Http\Requests;
 use todoparrot\Http\Controllers\Controller;

class WelcomeController extends Controller
{
    //
}

With the controller generated, let’s create the index action and corresponding view. Open the newly created controller (app/Http/Controllers/WelcomeController.php) and add the following index method to the class:

function index()
{
  return view('home.welcome');
}

Controller class methods intended to respond to an application endpoint request are generally referred to as actions.

In this example I’m presuming you followed along with the earlier discussion pertaining to organizing views within subdirectories. If not, just replace home.welcome with welcome. In either case, after saving these changes open the routes.php file (app/Http/routes.php). Replace the lone defined route with the following route:

Route::get('/', 'WelcomeController@index');

This route tells Laravel to respond with GET requests to the application home page (/) by executing the WelcomeController’s index action. Save these changes, return to the browser, and reload the home page. You should see the same view as earlier, but this time it’s being served from a controller!

Managing Your Application Routes

As you learned earlier, the app/Http/routes.php file defines your application’s URL endpoints so Laravel knows how to respond to a particular request. To illustrate this capability we had a look at the default home page route, and subsequently update that route to instead serve the welcome.blade.php view using a controller. But this really only scratches the surface in terms of the many different ways in which you can define and manage routes. In this section I’ll touch upon several other key routing capabilities.

Defining Resource (RESTful) Controller Routes

These days it’s commonplace to dedicate each application controller to managing an associated resource. For instance a controller named ListsController might be responsible for retrieving all lists, retrieving a list detail view, inserting a new list, modifying an existing list, and deleting a list. These types of controllers are often created in such a way so as to conform to REST conventions. Laravel supports RESTful controllers (often referred to as resource controllers within the Laravel community). For instance, to define the seven routes associated with a RESTful controller, all you need to do is identify the controller in your routes file like so:

Route::resource('lists', 'ListsController');

Doing so will automatically make the following routes available to your application:

HTTP Method Path Controller Description
GET /lists lists#index Display all TODO lists
GET /lists/new lists#create Display an HTML form for creating a new TODO list
POST /lists lists#store Create a new TODO list
GET /lists/:id lists#show Display a specific TODO list
GET /lists/:id/edit lists#edit Display an HTML form for editing an existing TODO list
PUT /lists/:id lists#update Update an existing TODO list
DELETE /lists/:id lists#destroy Delete an existing TODO list

Obviously you’ll need to additionally define the seven controller actions identified in this table (index, create, store, show, edit, update, and destroy)!.

Defining Implicit Routes

In addition to defining routes using methods such as Route::get, Route::post, and Route::Resource, you can alternatively identify the route request methods associated with each controller action directly within the action name itself. For instance, suppose a particular controller isn’t intended to be RESTful, and instead contains just a handful of GET- and POST-oriented actions. You can identify the controller in routes.php using the Route::controllers method:

Route::controllers([
  'lists' => 'ListsController'
]);

The ListsController.php file would then contain one or more actions, with the request method prefixed to the action name:

<?php 

 namespace todoparrot\Http\Controllers;

 use Illuminate\Http\Request;

 use App\Http\Requests;
 use App\Http\Controllers\Controller;

 class ListsController extends Controller {

   public function getIndex()
   {
     return view('lists.index');
    }

   public function getCreate()
   {
     return view('lists.create');
   }

   public function postStore()
   {
     return view('lists.store')
   }

 }

Notice how I’ve prefixed each action with get or post. These prefixes inform Laravel as to the HTTP method which should be used in conjunction with the URI. Therefore after creating a corresponding view for the getCreate action (storing it in create.blade.php) you should be able to navigate to /lists/create and see the view contents. If you were to create a form and post the form contents to /tasks/store you should see the contents of the resources/view/lists/store.blade.php view.

Defining Route Parameters

Laravel supports RESTful controllers , meaning for many standard applications you won’t necessarily have to explicitly define custom routes and manage route parameters. However, should you need to define a non-RESTful route, Laravel offers an easy way to define and parse parameters passed along via the URL. Suppose you wanted to build a custom blog detailing the latest TODOParrot features, and wanted to create pages highlighting posts on a per-category basis. For instance, the PHP category page might use a URL such as http://todoparrot.com/blog/category/php. You might first create a Blog controller (BlogController.php), and then point to a specific action in that controller intended to retrieve and display category-specific posts:

Route::get('blog/category/{category}', 'BlogController@category');

Once defined, when a user accesses a URI such as blog/category/php, Laravel would execute the Blog controller’s category action, making php available to the category action by expressly defining a method input argument as demonstrated here:

public function category($category)
{
  return view('blog.category')->with('category', $category);
 }

In this example the $category variable is subsequently being passed into the view.

If you need to pass along multiple parameters just specify them within the route definition as before:

Route::get('blog/category/{category}/{subcategory}', 'BlogController@category');

Then in the corresponding action be sure to define the input arguments in the same order as the parameters are specified in the route definition:

public function category($category, $subcategory)
{
  return view('blog.category')
    ->with('category', $category)
    ->with('subcategory', $subcategory);
}

Keep in mind these parameters are required. Neglecting to include them in the URL will result in an error. Sometimes however you might prefer to define these parameters as optional, and display a default view should the optional parameter(s) not be provided. To define an optional parameter you’ll append a question mark onto the parameter name, like this:

Route::get('blog/category/{category?}', 'BlogController@category');

You can then identify the parameter is optional in the associated action:

public function category($category = '')
{
  $category = $category == '' ? 'php' : $category;
  return view('blog.category')->with('category', $category);
}

Creating Route Aliases

Route aliases (also referred to as named routes) are useful because you can use the route name when creating links within your views, allowing you to later change the route path in the annotation without worrying about breaking the associated links. For instance the following definition associates a route with an alias named blog.category:

Route::get('blog/category/{category}', 
  ['as' => 'blog.category', 'uses' => 'BlogController@category']);

Once defined, you can reference routes by their alias using the URL::route method:

<a href="{{ URL::route('blog.category', ['category' => 'php']) }}">PHP</a>

Listing Routes

As your application grows it can be easy to forget details about the various routes. You can view a list of all available routes using Artisan’s route:list command:

$ php artisan route:list
+-----+--------------------...+------------------------------+------------+
| ... | URI                ...| Action                       | Middleware |
+-----+--------------------...+------------------------------+------------+
|     | GET|HEAD /         ...| App\..\HomeController@index  |            |
|     | POST auth/register ...| App\..\Auth\Auth...@register | guest      |
|     | POST auth/login    ...| App\..\Auth\Auth...@login    | guest      |
|     | ...                ...| ...                          | guest      |
+-----+--------------------...+------------------------------+------------+

Incidentally, this command will fail if you haven’t yet configured your database. Therefore if you see an Access denied message in conjunction with executing this command, don’t worry too much about it as it will resolve itself after you’ve completed the next chapter.

Caching Routes

Laravel 5 introduces route caching, an optimization that serializes your route definitions and places the results in a single file (bootstrap/cache/routes.php). Once serialized, Laravel no longer has to parse the route definitions with each request in order to initiate the associated response. To cache your routes you’ll use the route:cache command:

$ php artisan route:cache
Route cache cleared!
Routes cached successfully!

If you subsequently add, edit or delete a route definition you’ll need to clear and rebuild the cache. You can do so by running the route:cache command again. To clear (without rebuilding) the route cache, execute the route:clear command:

$ php artisan route:clear

This command will delete the cached routes file, causing Laravel to return to parsing the route definitions using app/Http/routes.php until you again decide to cache the routes.

Introducing Route Annotations

Early in Laravel 5’s development cycle a new feature known as route annotations was introduced. Route annotations offered developers the opportunity to define routes by annotating the action associated with the route within a comment located directly above the action. For instance you could use annotations to tell Laravel you’d like the Welcome controller’s index action to map to the application root URL like so:

/**
 * @Get("/")
 */
public function index()
{
  return view('welcome');
}

This feature generated quite a bit of controversy, and the Laravel developers eventually decided to remove the feature from the core distribution and instead make it available through a third-party package. If you’re familiar with route annotations from use within other frameworks and would like to use it in conjunction with Laravel 5, head on over to the Laravel Annotations package’s GitHub page and carefully review the installation and configuration instructions found in the README.

Defining URL Parameters Using Annotations

Defining URL parameter placeholders within route annotations is easy; just delimit the parameter with curly brackets:

  /**
   * @Get("/blog/category/{category}")
   */

You’ll access the category parameter via a method argument as explained in the earlier section, “Defining Custom Routes”.

Defining Route Aliases Using Annotations

You can define route aliases like so:

  /**
   * @Get("/blog/category/{category}", as="blog.category")
   */

You can then use the alias name within your views as described in the earlier section, “Creating Route Aliases”.

While route annotations are an interesting concept, I prefer to use the routes.php file as it offers a centralized location for easily examining all defined routes.

Introducing the Blade Template Engine

One of the primary goals of an MVC framework such as Laravel is separation of concerns. We don’t want to pollute views with database queries and other logic, and don’t want the controllers and models to make any presumptions regarding how data should be formatted. Because the views are intended to be largely devoid of any programming language syntax, they can be easily maintained by a designer who might lack programming experience. But certainly some logic must be found in the view, otherwise we would be pretty constrained in terms of what could be done with the data. Most frameworks attempt to achieve a happy medium by providing a simplified syntax for embedding logic into a view. Such facilities are known as template engines. Laravel’s template engine is called Blade. Blade offers all of the features one would expect of a template engine, including inheritance, output filtering, if conditionals, and looping.

In order for Laravel to recognize a Blade-augmented view, you’ll need to use the .blade.php extension. In this section we’ll work through a number of different examples involving Blade syntax and the welcome.blade.php view.

Displaying Variables

Your views will typically include dynamic data originating within the corresponding controller action. For instance, suppose you wanted to pass the name of a list retrieved from the database into a view. Because we haven’t yet discussed how to create new controllers and actions, let’s continue experimenting with the existing TODOParrot Welcome controlle (app/Http/Controllers/WelcomeController.php) and corresponding view (resources/views/welcome.blade.php). Modify the Welcome controller’s index action to look like this:

public function index()      
{ 
  return view('welcome')->with('name', 'San Juan Vacation');
}

Save these changes and then open welcome.blade.php (resources/views), and add the following line anywhere within the file:

{{-- Output the $name variable. --}}
<p>{{ $name }}</p>

Reload the home page and you should see “San Juan Vacation” embedded into the view! As an added bonus, I included an example of a Blade comment (Blade comments are enclosed within the {{-- and --}} tags).

You can also use a cool shortcut known as a magic method to identify the variable name:

public function index()      
{ 
    $name = 'San Juan Vacation';
    return view('welcome')->withName($name);
}

This variable is then made available to the view just as before:

<p>{{ $name }}</p>

Displaying Multiple Variables

You’ll certainly want to pass multiple variables into a view. You can do so exactly as demonstrated in the earlier example using the with method:

public function index()
{

    $data = array('name' => 'San Juan',
                  'date' => date('Y-m-d'));  

    return view('welcome')->with($data);

}

To view both the $name and $date variables within the view, update your view to include the following:

You last visited {{ $name }} on {{ $date }}.

You could also use multiple with methods, like so:

return view('welcome')->with('name', 'San Juan Vacation')
  ->with('date', date('Y-m-d'));

Logically this latter approach could get rather unwieldy if you needed to pass along more than two variables. Save some typing by using PHP’s compact() function:

$name = 'San Juan Vacation';
$date = date('Y-m-d');
return view('welcome', compact('name', 'date'));

The $name and $date variables defined in your action will then automatically be made available to the view. If this is confusing see the PHP manual’s compact() function documentation at http://php.net/compact.

Determining Variable Existence

There are plenty of occasions when a particular variable might not be set at all, and if not you want to output a default value. You can use the following shortcut to do so:

Welcome, {{ $name or 'California' }}

Escaping Dangerous Input

Because web applications commonly display user-contributed data (product reviews, blog comments, etc.), you must take great care to ensure malicious data isn’t inserted into the database. You’ll typically do this by employing a multi-layered filter, starting by properly validating data and additionally escaping potentially dangerous data (such as JavaScript code) prior to embedding it into a view. In earlier versions of Laravel this was automatically done using the double brace syntax presented in the previous example, meaning if a malicious user attempted to inject JavaScript into the view, the HTML tags would be escaped. Here’s an example:

{{ 'My list <script>alert("spam spam spam!")</script>' }}

Rather than actually executing the JavaScript alert function when the string was rendered to the browser, Laravel would instead rendered the string as text:

My list &lt;script&gt;alert("spam spam spam!")&lt;/script&gt;

In Laravel 4 if you wanted to output raw data and therefore allow in this case the JavaScript code to execute, you would use triple brace syntax:

{{{ 'My list <script>alert("spam spam spam!")</script>' }}}

Perhaps because at a glance it was too easy to confuse {{{...}}} and {{...}}, this syntax was changed in Laravel 5. In Laravel 5 you’ll use the {!! and !!} delimiters to output raw data:

{!! 'My list <script>alert("spam spam spam!")</script>' !!}

Of course, you should only output raw data when you’re absolutely certain it does not originate from a potentially dangerous source.

Looping Over an Array

TODOParrot users spend a lot of time working with lists, such as the list of tasks comprising a list, or a list of their respective TODO lists. These various list items are stored as records in the database, retrieved within a controller action, and then subsequently iterated over within the view. Blade supports several constructs for looping over arrays, including @foreach which I’ll demonstrate in this section (be sure to consult the Laravel documentation for a complete breakdown of Blade’s looping capabilities). Let’s demonstrate each by iterating over an array into the index view. Begin by modifying the WelcomeController.php index method to look like this:

public function index()
{
  $lists = array('Vacation Planning', 'Grocery Shopping', 'Camping Trip'); 
   return view('welcome')->with('lists', $lists); 
}

Next, update the index view to include the following code:

<ul>
  @foreach ($lists as $list)
    <li>{{ $list }}</li>
  @endforeach
</ul>

When rendered to the browser, you should see a bulleted list consisting of the three items defined in the $lists array.

Because the array could be empty, consider using the @forelse construct instead:

<ul>
  @forelse ($lists as $list)
    <li>{{ $list }}</li>
  @empty
    <li>You don't have any lists saved.</li>
  @endforelse
</ul>

This variation will iterate over the $lists array just as before, however if the array happens to be empty the block of code defined in the @empty directive will instead be executed.

If Conditional

In the previous example I introduced the @forelse directive. While useful, for readability reasons I’m not personally a fan of this syntax and instead use the @if directive to determine whether an array contains data:

<ul>
  @if (count($lists) > 0)
    @foreach ($lists as $list)
      <li>{{ $list }}</li>
    @endforeach
  @else
    <li>You don't have any lists saved.</li>
  @endif
</ul>

Blade also supports the if-elseif-else construct:

 @if (count($lists) > 1)
   <ul>
     @foreach ($lists as $list)
       <li>{{ $list }}</li>
     @endforeach
   </ul>
 @elseif (count($lists) == 1)
   <p>
     You have one list: {{ $lists[0] }}.
   </p>
@else
    <p>You don't have any lists saved.</p>
@endif
</ul>

Managing Your Application Layout

The typical web application consists of a design elements such as a header and footer, and these elements are generally found on every page. Because eliminating redundancy is one of Laravel’s central tenets, clearly you won’t want to repeatedly embed elements such as the site logo and navigation bar within every view. Instead, you’ll use Blade syntax to create a master layout that can then be inherited by the various page-specific views. To create a layout, first create a directory within resources/views called layouts, and inside it create a file named master.blade.php. Add the following contents to this newly created file:

 <!doctype html> 
 <html lang="en"> 
 <head>
   <meta charset="UTF-8"> 
   <title>Welcome to TODOParrot</title>
 </head>
 <body>

   @yield('content')

</body>
</html>

The @yield directive identifies the name of the section that should be embedded into the template. This is best illustrated with an example. After saving the changes to master.blade.php, open welcome.blade.php and modify its contents to look like this:

@extends('layouts.master')

@section('content')

<h1>Welcome to TODOParrot</h1>

<p>
  TODOParrot is the ultimate productivity application for 
  tropically-minded users.
</p>

@endsection

The @extends directive tells Laravel which layout should be used. Note how dot notation is used to represent the path, so for instance layouts.master translates to layouts/master. You specify the layout because it’s possible your application will employ multiple layouts, for instance one sporting a sidebar and another without.

After saving the changes reload the home page and you’ll see that the index.blade.php view is wrapped in the HTML defined in master.blade.php, with the HTML found in the @section directive being inserted into master.blade.php where the @yield('content') directive is defined.

Defining Multiple Layout Sections

A layout can identify multiple sections. For instance many web applications employ a main content area and a sidebar. In addition to the usual header and footer the layout might include some globally available sidebar elements, but you probably want the flexibility of appending view-specific sidebar content. This can be done using multiple @section directives in conjunction with @show and @parent. For reasons of space I’ll just include the example layout’s :

 <body>

   <div class="container">

     <div class="col-md-9">
       @yield('content')
     </div>

     <div class="col-md-3">
      @section('advertisement')
      <p>
       Jamz and Sun Lotion Special $29!
     </p>
     @show
   </div>
 </div>
</body>

You can think of the @show directive as a shortcut for closing the section and then immediately yielding it:

@endsection
@yield('advertisement')

The view can then also reference @section('advertisement'), additionally referencing the @parent directive which will cause anything found in the view’s sidebar section to be appended to anything found in the layout’s sidebar section:

 @extends('layouts.master')

 @section('content')
   <h1>Welcome to TODOParrot!</h1>
 @endsection

 @section('advertisement')
   @parent
     <p>
     Buy the TODOParrot Productivity guide for $10!
    </p>
@endsection

Once this view is rendered, the advertisement section would look like this:

<p>
  Jamz and Sun Lotion Special $29!
</p>
<p>
  Buy the TODOParrot Productivity guide for $10!
</p>

If you would rather replace (rather than append to) the parent section, just eliminate the @parent directive reference.

Taking Advantage of View Partials

Suppose you wanted to include a recurring widget within several different areas of the application. This bit of markup is fairly complicated, such as a detailed table row, and you assume it will be subject to considerable evolution in the coming weeks. Rather than redundantly embed this widget within multiple locations, you can manage this widget within a separate file (known as a view partial) and then include it within the views as desired. For instance, if you wanted to manage a complex table row as a view partial, create a file named for instance row.blade.php, placing this file within your resources/views directory (I prefer to manage mine within a directory named partials found in resources/views). Add the table row markup to the file:

<tr style="padding-bottom: 5px;">
  <td>
  {{ $link->name }}
  </td>
</tr>

Notice how I’m using a view variable ($link) in the partial. When importing the partial into your view you an optionally pass a variable into the view like so:

<table class="table borderless">
@foreach ($links as $link)

  @include('partials.row', array('link' => $link))

@endforeach
</table>

Integrating Images, CSS and JavaScript

Your project images, CSS and JavaScript should be placed in the project’s public directory. While you could throw everything into public, for organizational reasons I prefer to create images, css, and javascript directories. Regardless of where in the public directory you place these files, you’re free to reference them using standard HTML or can optionally take advantage of a few helpers available via the Laravel HTML package. For instance, the following two statements are identical:

<img src="/images/logo.png" alt="TODOParrot logo" />

{!! HTML::image('images/logo.png', 'TODOParrot logo') !!}

Similar HTML component helpers are available for CSS and JavaScript. Again, you’re free to use standard HTML tags or can use the facade. The following two sets of statements are identical:

<link rel="stylesheet" href="/css/app.min.css">
<script src="/javascript/jquery-1.10.1.min.js"></script>
<script src="/javascript/bootstrap.min.js"></script>

{!! HTML::style('css/app.min.css') !!}
{!! HTML::script('javascript/jquery-1.10.1.min.js') !!}
{!! HTML::script('javascript/bootstrap.min.js') !!}

If you want to take advantage of the HTML helpers, you’ll need to install the LaravelCollective/HTML package. This was previously part of the native Laravel distribution, but has been moved into a separate package as of version 5. Fortunately, installing the package is easy. First, add the LaravelCollective/HTML package to your composer.json file:

"require": {
  ...
  "laravelcollective/html": "~5.0"
},

Save the changes and run composer install to install the package. Next, add the following line to the providers array found in your config/app.php file:

'Collective\Html\HtmlServiceProvider',

Next, add the following line to the config/app.php aliases array:

'HTML' => 'Collective\Html\HtmlFacade'

With these changes in place, you can begin using the LaravelCollective/HTML package. Be sure to check out the GitHub README of the Laravel documentation for a list of available helpers.

Integrating the Bootstrap Framework

Bootstrap is a blessing to design-challenged web developers like yours truly, offering an impressively comprehensive and eye-appealing set of widgets and functionality. Being a particularly design-challenged developer I use Bootstrap as the starting point for all of my personal projects, often customizing it with a Bootswatch theme. TODOParrot is no exception, taking advantage of both Bootstrap and the Bootswatch Flatly theme.

The Bootstrap CSS source files (in Less format) were initially included with every new Laravel 5 project, later removed in a subsequent release, and returned in an even later release. I’ll presume you’re using the very latest Laravel release and therefore already have the Bootstrap files available in your project. If not, just head over to http://getbootstrap.com/ and download the source files, placing the unzipped contents into resources/assets/less/bootstrap. After doing so, place the following line at the top of the app.less file (found in resources/assets/less):

@import "bootstrap/bootstrap";

If your project already includes the files (located inside resources/assets/less/bootstrap), then the above line will already exist in your app.less file.

This import statement will automatically compile all of the Bootstrap files along with any Less statements found in app.less, meaning if you include the compiled app.css in your master.blade.php header, all of Bootstrap’s files will additionally be made available.

You could also alternatively (and likely preferably) use Bootstrap’s recommended CDNs (Content Delivery Network) to add Bootstrap and jQuery (jQuery is required to take advantage of the Bootstrap JavaScript plugins:

 <head>
   ...
   <link rel="stylesheet" 
     href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
   <script 
     src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
   <script 
     src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js">
   </script>

Regardless of the approach you choose, once added you’re free to begin taking advantage of Bootstrap’s various CSS widgets and JavaScript plugins. For instance try adding a stylized hyperlink to the welcome.blade.php view just to confirm everything is working as expected:

<a href="http://www.wjgilmore.com" 
  class="btn btn-success">W.J. Gilmore, LLC</a>

Integrating the Bootstrapper Package

Using Bootstrap as described above is perfectly fine, and in fact I do exactly that in TODOParrot. However, for those of you who prefer to use view helpers whenever possible, check out Bootstrapper, a great package created by Patrick Tallmadge. Once installed you can use a variety of helpers to integrate Bootstrap widgets into your views. For instance the following helper will create a hyperlinked button:

 {!! Button::success('Success') !!}

To install Bootstrapper, open your project’s composer.json file and add the following line to the require section:

"require": {
  ...
  "patricktalmadge/bootstrapper": "~5"
},

Save the changes and then open up config/app.php and locate the providers array, adding the following line to the end of the array:

'providers' => array(
  ...
  'Bootstrapper\BootstrapperL5ServiceProvider'
),

By registering the Bootstrapper service provider, Laravel will known to initialize Bootstrapper alongside any other registered service providers, making its functionality available to the application. Next, search for the aliases array also located in app/config/app.php. Paste the following rather lengthy bit of text into the bottom of the array:

 'Accordion' => 'Bootstrapper\Facades\Accordion',
 'Alert' => 'Bootstrapper\Facades\Alert',
 'Badge' => 'Bootstrapper\Facades\Badge',
 'Breadcrumb' => 'Bootstrapper\Facades\Breadcrumb',
 'Button' => 'Bootstrapper\Facades\Button',
 'ButtonGroup' => 'Bootstrapper\Facades\ButtonGroup',
 'Carousel' => 'Bootstrapper\Facades\Carousel',
 'ControlGroup' => 'Bootstrapper\Facades\ControlGroup',
 'DropdownButton' => 'Bootstrapper\Facades\DropdownButton',
 'Form' => 'Bootstrapper\Facades\Form',
 'Helpers' => 'Bootstrapper\Facades\Helpers',
 'Icon' => 'Bootstrapper\Facades\Icon',
 'InputGroup' => 'Bootstrapper\Facades\InputGroup',
 'Image' => 'Bootstrapper\Facades\Image',
 'Label' => 'Bootstrapper\Facades\Label',
 'MediaObject' => 'Bootstrapper\Facades\MediaObject',
 'Modal' => 'Bootstrapper\Facades\Modal',
 'Navbar' => 'Bootstrapper\Facades\Navbar',
 'Navigation' => 'Bootstrapper\Facades\Navigation',
 'Panel' => 'Bootstrapper\Facades\Panel',
 'ProgressBar' => 'Bootstrapper\Facades\ProgressBar',
 'Tabbable' => 'Bootstrapper\Facades\Tabbable',
 'Table' => 'Bootstrapper\Facades\Table',
 'Thumbnail' => 'Bootstrapper\Facades\Thumbnail',

Adding these aliases will save you the hassle of having to include the entire namespace when referencing one of the Bootstrap components. Save these changes and then run composer update from your project’s root directory to install Bootstrapper. With Bootstrapper installed, all that remains to begin using Bootstrap is to add Bootstrap and jQuery to your project layout. Open the master.blade.php file we created in the earlier section and add the following lines to the layout :

{!! HTML::style('path/to/bootstrap.css') !!}
{!! HTML::script('http://code.jquery.com/jquery-1.11.3.min.js') !!}
{!! HTML::script('path/to/bootstrap.js') !!}

Notice I’m using the jQuery CDN in the above example. jQuery is required to take advantage of Bootstrap’s JavaScript components. You’re not required to use them, and in fact if you don’t plan on using them I suggest removing the last two lines in the above example. Also, I’m using the HTML package’s script helper to reference the CSS and JS files. You can alternatively use the HTML link and script elements to accomplish the same goal. In either case, after saving the changes you’re ready to begin taking advantage of Bootstrapper’s CSS styling and (optionally) jQuery plugins!

Introducing Elixir

Writing code is but one of many tasks the modern developer has to juggle when working on even a simple web application. You’ll want to compress images, minify CSS and JavaScript files, hide debugging statements from the production environment, run unit tests, and perform countless other mundane duties. Keeping track of these responsibilities let alone ensuring you remember to carry them all out is a pretty tall order, particularly because you’re presumably devoting the majority of your attention to creating and maintaining great application features.

The Laravel developers hope to reduce some of the time and hassle associated with these sort of tasks by providing a new API called Laravel Elixir. The Elixir API integrates with Gulp, providing an easy solution for compiling your Laravel project’s Less, Sass and CoffeeScript, and perform any other such administrative task. In this section I’ll show you how to create and execute several Elixir tasks in conjunction with your Laravel application. But first because many readers are likely not familiar with Gulp I’d like to offer a quick introduction, including instructions for installing Gulp and it’s dependencies.

Introducing Gulp

Gulp is a powerful open source build system you can use to automate all of the aforementioned tasks and many more. You’ll automate away these headaches by writing Gulp tasks, and can save a great deal of time when doing so by integrating one or more of the hundreds of available Gulp plugins. In this section I’ll show you how to install and configure Gulp for subsequent use within Elixir.

Installing Gulp

Gulp is built atop Node.js, meaning you’ll need to install Node.js. No matter your operating system this is easily done by downloading one of the installers via the Node.js website. If you’d prefer to build Node from source you can download the source code via this link. Mac users can install Node via Homebrew. Linux users additionally likely have access to Node via their distribution’s package manager.

Once installed you can confirm Node is accessible via the command-line by retrieving the Node version number:

$ node -v
v0.12.2

Node users have access to a great number of third-party libraries known as Node Packaged Modules (NPM). You can install these modules via the aptly-named npm utility. We’ll use npm to install Gulp:

$ npm install -g gulp

Once installed you should be able to execute Gulp from the command-line:

$ gulp -v
[14:12:51] CLI version 3.9.0

With Gulp installed it’s time to install Elixir!

Installing Elixir

Laravel 5 applications automatically include a file named package.json which resides in the project’s root directory. This file looks like this:

{
  "devDependencies": {
    "gulp": "^3.8.8",
    "laravel-elixir": "*"
  }
}

Node’s npm package manager uses package.json to learn about and install a project’s Node module dependencies. As you can see, a default Laravel project requires two Node packages: gulp and laravel-elixir. You can install these packages locally using the package manager like so:

$ npm install

Once complete, you’ll find a new directory named node_modules has been created within your project’s root directory, and within in it you’ll find the gulp and laravel-elixir packages. This directory is used by Node.js to house the various packages installed per the package.json specifications, so you definitely do not want to delete nor modify it.

Creating Your First Elixir Task

Your Laravel project includes a default gulpfile.js which defines your Elixir-flavored Gulp tasks. Inside this file you’ll find an example Gulp task:

elixir(function(mix) {
    mix.less('app.less');
});

The mix.less task compiles a Less file named app.less which resides in resources/assets/less. In Laravel 5.0.X this file imported Bootstrap and used Less syntax to override a few Bootstrap styles:

@import "bootstrap/bootstrap";

@btn-font-weight: 300;
@font-family-sans-serif: "Roboto", Helvetica, Arial, sans-serif;

body, label, .checkbox label {
  font-weight: 300;
}

As of Laravel 5.1 the app.less file has been wiped clean, so I suggest adding the following content back into the file in order to follow along. Keep in mind you’ll need to download the Bootstrap Less source files per the earlier instructions in order for the @import statement and variable overrides to work. After adding a few styles , you can execute the Elixir task by running gulp within your project root directory:

$ gulp
[14:44:43] Using gulpfile ~/Software/dev.todoparrot.com/gulpfile.js
[14:44:43] Starting 'default'...
[14:44:43] Starting 'less'...
[14:44:43] Running Less: resources/assets/less/app.less
[14:44:44] Finished 'default' after 753 ms
[14:44:44] gulp-notify: [Laravel Elixir] Less Compiled!
[14:44:44] Finished 'less' after 875 ms

Once executed, you’ll find a compiled CSS file named app.css inside your project’s public/css directory. Of course, in order to actually use the styles defined in the app.css file you’ll need to reference it within your layout:

<link rel="stylesheet" href="/css/app.css">

Additionally, the compiled app.css file will additionally contain all of the compiled Bootstrap CSS definitions. In some cases you’ll only want to use a subset of these definitions, so be sure to do some research regarding how you can selectively determine which files are compiled.

Compiling Your JavaScript Assets

You’ll likely also want to manage your JavaScript assets. For instance if you use CoffeeScript, you’ll place your CoffeeScript files in resources/assets/coffee (you’ll need to create this directory). Here’s a simple CoffeeScript statement which will display one of those annoying alert boxes in the browser:

alert "Hi I am annoying"

Save this statement to resources/assets/coffee/test.coffee. Next, modify your gulpfile.js file to look like this:

elixir(function(mix) {
    mix.less('app.less');
    mix.coffee();
});

Incidentally, you could also chain the commands together like so:

elixir(function(mix) {
    mix.less('app.less').coffee();
});

Save the changes and run gulp again:

 $ gulp
 [14:47:06] Using gulpfile ~/Software/dev.todoparrot.com/gulpfile.js
 [14:47:06] Starting 'default'...
 [14:47:06] Starting 'less'...
 [14:47:06] Running Less: resources/assets/less/app.less
 [14:47:07] Finished 'default' after 743 ms
 [14:47:07] gulp-notify: [Laravel Elixir] Less Compiled!
 [14:47:07] Finished 'less' after 857 ms
 [14:47:07] Starting 'coffee'...
 [14:47:07] Running CoffeeScript: resources/assets/coffee//**/*.coffee
 [14:47:07] gulp-notify: [Laravel Elixir] CoffeeScript Compiled!
 [14:47:07] Finished 'coffee' after 190 ms

You’ll see that a directory named js has been created inside public. Inside this directory you’ll find the file app.js which contains the following JavaScript code:

(function() {
  alert("Hi I am annoying");

}).call(this);

Like the compiled CSS, you’ll need to properly reference the app.js file within your project layout or appropriate view.

Watching for Changes

Because you’ll presumably be making regular tweaks to your CSS and JavaScript, consider using Elixir’s watch command to automatically execute gulpfile.js anytime your assets change:

$ gulp watch
[14:49:29] Starting 'watch'...
[14:49:29] Starting 'less'...
[14:49:29] Running Less: resources/assets/less/app.less
[14:49:30] Finished 'watch' after 718 ms
[14:49:30] gulp-notify: [Laravel Elixir] Less Compiled!
[14:49:30] Finished 'less' after 851 ms
[14:49:30] Starting 'coffee'...
[14:49:30] Running CoffeeScript: resources/assets/coffee//**/*.coffee
[14:49:30] gulp-notify: [Laravel Elixir] CoffeeScript Compiled!
[14:49:30] Finished 'coffee' after 178 ms
[14:49:30] Starting 'watch-assets'...

This process will continue to run, so you’ll want to execute it in a dedicated tab or in the background. Once running, each time the target files associated with the Elixir tasks are changed, Gulp will automatically run the tasks and update the compiled files accordingly.

Testing Your Views

Earlier versions of Laravel automatically included the BrowserKit and DomCrawler packages, both of which are very useful for functionally testing various facets of your application in conjunction with PHPUnit. Among their many capabilities you can write and execute tests that interact with your Laravel application in the very same way a user would. For instance you might wish to confirm a page is rendering and displaying a particular bit of content, or ensure that a particular link is taking users to a specific destination.

Fortunately, you can easily add these capabilities to your application via Composer via the powerful Goutte package. Goutte is a web crawling library that can mimic a user’s behavior in many important ways, and we an use it in conjunction with PHPUnit to functionally test the application. Open your project’s composer.json file and add the following line:

"require-dev": {
  ...
  "fabpot/goutte": "2.*"
},

Save the changes and run composer update to install Goutte.

For organizational purposes it makes sense to create a new test file for each controller/view pair you plan on testing (and breaking it down even further if necessary), so let’s create a new file named WelcomeTest.php, placing it in the tests directory:

<?php

use Goutte\Client;

class WelcomeTest extends TestCase {

}

Inside this class create the following test, which will access the home page and confirm the user sees the message Welcome to TODOParrot, which is placed inside an h1 tag:

 public function testUserSeesWelcomeMessage()
 {

   $client = new Client();
   $crawler = $client->request('GET', 'http://localhost:8000/');

   $this->assertEquals(200, $client->getResponse()->getStatus());

   $this->assertCount(1, 
    $crawler->filter('h1:contains("Welcome to TODOParrot")'));

}

In this test we’re actually executing two assertions: the first (assertEquals()) confirms that the response returned a 200 status code (successful request). The second uses the assertCount method to confirm there is only one instance of an h1 tag that contains the welcome message. It’s not that we expect there to be multiple instances, but we’re particularly concerned about there being one instance and so the assertCount is a useful method for such purposes. Of course, when writing these sorts of test you’ll need to keep in mind that the user interface is often in a state of constant change, therefore you’ll want to reserve the use of such tests to confirming particularly important bits of content.

You can also create tests that interact with the page. For instance, suppose you wanted to confirm a link to the Contact Us page is included in a given page. The link looks like this:

<a href="/about/contact" class="btn btn-primary">Contact Us</a>

When the user clicks on this link we clearly want him to be taken to the contact form. Let’s confirm that when this link is clicked the user is taken to the About controller’s contact action, and the corresponding view contains the h1 header Contact Us:

public function testUserClicksContactLinkAndIsTakenToContactPage()
{ 

  $client = new Client();
  $crawler = $client->request('GET', 'http://localhost:8000/');

  $link = $crawler->selectLink('Contact Us')->link();

  $this->assertEquals('http://localhost:8000/about/contact', 
    $link->getUri());

  $crawler = $client->click($link);

  $this->assertCount(1, 
    $crawler->filter('h1:contains("Contact Us")'));

}

Incidentally, as of Laravel 5.1 you can use the new HTTP request API and other interactive testing capabilities to implement a similar test to the above using more user-friendly syntax:

public function testUserClicksContactLinkAndIsTakenToContactPage()
{ 

    $this->visit('/')
         ->click('Contact Us')
         ->seePageIs('/about/contact')
         ->see('<h1>Contact Us</h1>');

}

Reference

  • Easy Laravel 5 Chapter 2 Managing Your Project Controllers, Layout, Views, and Other Assets