Laravel5.1模型-工具介绍

Published on 2017 - 04 - 01

Configuring Your Project Database

Laravel currently supports four databases, including MySQL, PostgreSQL, SQLite, and Microsoft SQL Server. The config/database.php file tells Laravel which database you’d like your project to use. In this file you’ll also define the database connection credentials along with a few other settings. I’ve pasted in the contents of config/database.php below (with comments removed for reasons of space). Following the snippet I’ll summarize the purpose of each setting:

<?php

 return [

   'fetch' => PDO::FETCH_CLASS,

   'default' => 'mysql',

   'connections' => [

    'sqlite' => [
      'driver'   => 'sqlite',
      'database' => storage_path().'/database.sqlite',
      'prefix'   => '',
    ],

    'mysql' => [
      'driver'    => 'mysql',
      'host'      => env('DB_HOST', 'localhost'),
      'database'  => env('DB_DATABASE', 'forge'),
      'username'  => env('DB_USERNAME', 'forge'),
      'password'  => env('DB_PASSWORD', ''),
      'charset'   => 'utf8',
      'collation' => 'utf8_unicode_ci',
      'prefix'    => '',
      'strict'    => false,
    ],

    'pgsql' => [
      'driver'   => 'pgsql',
      'host'     => env('DB_HOST', 'localhost'),
      'database' => env('DB_DATABASE', 'forge'),
      'username' => env('DB_USERNAME', 'forge'),
      'password' => env('DB_PASSWORD', ''),
      'charset'  => 'utf8',
      'prefix'   => '',
      'schema'   => 'public',
    ],

    'sqlsrv' => [
      'driver'   => 'sqlsrv',
      'host'     => env('DB_HOST', 'localhost'),
      'database' => env('DB_DATABASE', 'forge'),
      'username' => env('DB_USERNAME', 'forge'),
      'password' => env('DB_PASSWORD', ''),
      'prefix'   => '',
    ],

  ],

  'migrations' => 'migrations',
   'redis' => [

    'cluster' => false,

    'default' => [
      'host'     => '127.0.0.1',
      'port'     => 6379,
      'database' => 0,
    ],

  ],

];

Let’s review each setting:

  • fetch: The Eloquent ORM will by default return database results as instances of PHP’s stdClass object. You could optionally instead return results in array format by changing this setting to PDO:FETCH_ASSOC. If you’re not particularly familiar with object orientation then this alternative might seem attractive, however I suggest leaving the default in place unless special circumstances warrant the change.
  • default: The default setting identifies the type of database used by your project. You can set this to mysql (the default), pgsql (PostgreSQL), sqlite, or sqlsrv (Microsoft SQL Server). Keep in mind none of these databases come packaged with Laravel. You’ll need to separately install and configure the database, or obtain credentials to access the desired database.
  • connections: This array defines the database authentication credentials for each supported database. Of course, Laravel will only consider the setting associated with the database defined by the default setting, therefore you can leave the unused database options untouched (or entirely remove them). The env() function used to retrieve various connection parameters will look to the .env file and retrieve a configuration variable equal to the name of the function’s first parameter (for instance, DB_HOST). The env() function’s second parameter identifies a default value should the desired configuration variable not be found.
  • migrations: This setting defines the name of the table used for managing your project’s migration status. This is by default set to migrations, however if by the wildest of circumstances you needed to change the table name to something else, you can do so here.
  • redis: You can optionally use Redis to manage cache and session data (among other things). If you’d like to use Redis in your Laravel application, you’ll define the connection information using this setting.

After identifying the desired database and defining the authorization credentials, don’t forget to create the database because Laravel will not do it for you. Of course, if you’re working on multiple Laravel 5 projects then regardless you’ll need to create separate databases. With the database defined, it’s time to begin interacting with it!

Introducing the Eloquent ORM

Object-relational mapping (ORM) is without question the feature that led me to embrace web frameworks several years ago. Even if you’ve never heard of an ORM, anybody who has created a database-backed web site has undoubtedly encountered the problem this programming technique so gracefully resolves: impedence mismatch. Borrowed from the electrical engineering field, impedence mismatch is the term used to describe the challenges associated with using an object-oriented language such as PHP or Ruby in conjunction with a relational database, because the programmer is faced with the task of somehow mapping the application objects to database tables. An ORM solves this problem by providing a convenient interface for converting application objects to database table records, and vice versa. Additionally, most ORMs offer a vast array of convenient features useful for querying, inserting, updating, and deleting records, managing table relationships, and dealing with other aspects of the data life cycle.

Creating Your First Model

Creating a model using Artisan is easy. Let’s kick things off by creating the Todolist model, which the TODOParrot application uses to manage the user’s various lists. Beginning with Laravel 5 you can now generate models using Artisan:

$ php artisan make:model Todolist
Model created successfully.
Created Migration: 2015_01_14_215705_create_todolists_table

You’ll find the new model in app/Todolist.php. It looks like this:

<?php namespace todoparrot;
    use Illuminate\Database\Eloquent\Model;

    class Todolist extends Model {

      //

    }

As you can see, a Laravel model is just a PHP class that extends Laravel’s Model class, thereby endowing the class with the features necessary to act as the bridge between your Laravel application and the underlying database table. Currently the class is empty however we’ll begin expanding its contents soon enough.

A model is only useful when it’s associated with an underlying database table. You might have noticed from the above output that when you created the model Laravel also created something called a migration. This file contains the blueprint for creating the model’s associated table. Let’s talk about the power of migrations next.

Introducing Migrations

With the model created, you’ll typically create the corresponding database table, done through a fantastic Laravel feature known as migrations. Migrations offer a file-based approach to changing the structure of your database, allowing you to create and drop tables, add, update and delete columns, and add indexes, among other tasks. Further, you can easily revert, or roll back, any changes if a mistake has been made or you otherwise reconsider the decision. Finally, because each migration is stored in a text file, you can manage them within your project repository.

To demonstrate the power of migrations, let’s have a look at the migration file that was created along with the Todolist model. This migration file is named 2015_01_14_215705_create_todolists_table.php, and it was placed in the database/migrations directory. Open up this file and you’ll see the following contents:

 <?php

 use Illuminate\Database\Schema\Blueprint;
 use Illuminate\Database\Migrations\Migration;

 class CreateTodolistsTable extends Migration {

   /**
    * Run the migrations.
   *
   * @return void
   */
   public function up()
   {
     Schema::create('todolists', function(Blueprint $table)
     {
       $table->increments('id');
       $table->timestamps();
     });
   }

   /**
    * Reverse the migrations.
    *
    * @return void
    */
   public function down()
   {
     Schema::drop('todolists');
   }
}

Like a model, a Laravel migration is just a standard PHP class, except in this case the class extends the Migration class. Take note of the two class methods, up() and down(). These have special significance in regards to migrations, with the up() method defining what occurs when the migration is executed, and down() defining what occurs when the migration is reverted. Therefore the down() method should define what happens when you’d like to undo the changes occurring as a result of executing the up() method. Let’s first discuss this example’s up() method:

public function up()         
{ 
  Schema::create('todolists', function(Blueprint $table)
  {
    $table->increments('id');
    $table->timestamps();
  });
}

You’ll regularly see the Schema class appear in migration files, because it is Laravel’s solution for manipulating database tables in all manner of fashions. This example uses the Schema::create method to create a table named todolists. An anonymous function (closure) passed along as the Schema::create method’s second parameter defines the table columns:

  • $table->increments('id'): The increments method indicates we want to create an automatically incrementing integer column that will additionally serve as the table’s primary key.
  • $table->timestamps(): The timestamps method informs Laravel to include created_at and updated_at timestamp columns, which will be automatically updated to reflect the current timestamp when the record is created and updated, respectively.

There are plenty of other methods useful for creating different column data types, setting data type attributes, and more. Be sure to check out the Schema documentation for a complete list, otherwise stay tuned as we’ll cover various other methods in the examples to come.

We want each todolists record to manage more than just a primary key and timestamps, of course. Notably each list should be assigned a name and description, so let’s modify the Schema::create body to additionally add these fields:

  Schema::create('todolists', function(Blueprint $table)
  {
    $table->increments('id');
    $table->string('name');  
    $table->text('description');
    $table->timestamps();
  });

If you’re not familiar with these column types I’ll describe them next:

  • $table->string('name'): The string method indicates we want to create a variable character column (commonly referred to as a VARCHAR by databases such as MySQL and PostgreSQL) named name. Remember, the Schema class is database-agnostic, and therefore leaves it to whatever supported Laravel database you happen to be using to determine the maximum string length, unless you use other means to constrain the limit.
  • $table->text('description'): The text method indicates we want to create a text column (commonly referred to as TEXT by databases such as MySQL and PostgreSQL).

The example’s down() method is much easier to introduce because it consists of a single instruction: Schema::drop('todolists'). When run it will remove, or drop the todolists table.

Now that you understand what comprises this migration file, let’s execute it and create the todolists table:

$ php artisan migrate
Migration table created successfully.
Migrated: 2015_01_14_215705_create_todolists_table

Typically only the second output line will be displayed, however because this is the very first migration, Laravel also creates a table called migrations. This table is used by Laravel to keep track of the current migration version. This version number correlates with the name of the migration file. For instance after running the 2015_01_14_215705_create_todolists_table migration the migrations table looks like this:

mysql> select * from migrations;
+------------------------------------------+-------+
| migration                                | batch |
+------------------------------------------+-------+
| 2015_01_14_215705_create_todolists_table |     1 |
+------------------------------------------+-------+

Every time a migration is run, a record will be added to the migrations table identifying the migration file name, and the group, or batch in which the migration belongs. In other words, if you create for instance three migrations and then run php artisan migrate, those three migrations will be placed in the same batch. If you later wanted to undo any of the changes found in any of those migrations, those three migrations would be treated as a group and rolled back together.

Once complete, open your development database using your favorite database editor (I prefer to use the MySQL CLI), and confirm the todolists table has been created:

mysql> show tables;
+------------------------------+
| Tables_in_dev_todoparrot_com |
+------------------------------+
| todolists                    |
| migrations                   |
+------------------------------+
2 rows in set (0.03 sec)

Indeed it has! Let’s next check out the todolists table schema:

 mysql> describe todolists;
 +-------------+------------------+---+----------------+
 | Field       | Type             |...| Extra          |
 +-------------+------------------+---+----------------+
| id          | int(10) unsigned |...| auto_increment |
| name        | varchar(255)     |...|                |
| description | text             |...|                |
| created_at  | timestamp        |...|                |
| updated_at  | timestamp        |...|                |
+-------------+------------------+---+----------------+
5 rows in set (0.00 sec)

Sure enough, an automatically incrementing integer column has been created, in addition to the name, description, created_at and updated_at columns.

Suppose you realize a mistake was made in the migration (perhaps you forgot to add a column or used an incorrect datatype). You can easily roll back the changes using the following command:

$ php artisan migrate:rollback
Rolled back: 2015_01_14_215705_create_todolists_table

After rolling back the changes check your database and you’ll see both the todolists table has been removed and the relevant record in the migrations table. Of course, we’ll actually want to use the todolists table, so run php artisan migrate anew before moving on.

Adding and Removing Columns

To add or remove a table column you’ll generate a migration just as you did when creating a table in the previous section, the only difference being you’ll use the --table option to identify the table you’d like to modify:

$ php artisan make:migration add_note_to_tasks_table --table=tasks
Created Migration: 2015_02_18_152547_add_note_to_tasks_table

Next open up the newly created migration file, creating the desired column in the up method, and making sure you drop the column in the down method:

 public function up()
 {
   Schema::table('tasks', function(Blueprint $table)
   {
       $table->string('note');
   });
 }

 /**
  * Reverse the migrations.
  *
  * @return void
  */
 public function down()
 {
   Schema::table('tasks', function(Blueprint $table)
   {
       $table->dropColumn('note');    
   });
 }

Controlling Column Ordering

One of the beautiful aspects of an ORM such as Eloquent and migrations is the ability to essentially forget about matters such as column ordering. However as I tend to obsess over irrelevant minutiae, I often desire to manage the order of columns, typically placing the primary and foreign keys at the beginning of the table. If you later add a column and would like to insert it into a specific location within the table structure, use the after method:

$table->string('city', 100)->after('street');

Checking Migration Status

Sometimes you’ll create several migrations and lose track of which ones were moved into the database. Of course, you could visually confirm the changes in the database, however Laravel offers a more convenient command-line solution. The following example uses the migrate:status command to review the status of the TODOParrot project at some point during development:

$ php artisan migrate:status
+------+--------------------------------------------+
| Ran? | Migration                                  |
 +------+--------------------------------------------+
|| 2015_01_14_215705_create_todolists_table   |
|| 2014_01_15_155245_create_users_table       |
|| 2014_01_16_030306_add_user_id_to_todolists |
|| 2014_01_16_175008_create_tasks_table       |
+------+--------------------------------------------+

With our first model and corresponding table created, let’s spend some time creating and manipulating a few lists.

Defining Accessors, Mutators, and Methods

It’s important to remember Laravel models are just POPOs (Plain Old PHP Objects) that by way of extending the Model class have been endowed with some special additional capabilities. This means you’re free to take advantage of PHP’s object-oriented features to further enhance the model, including adding getters, setters, and methods.

Defining Accessors

You’ll use an accessor (also known as a getter) when you’d like to encapsulate the retrieval of a model attribute. You’ll define an accessor using the convention getAttributeName. Suppose some future version of TODOParrot allowed users to create accounts. It was determined users’ usernames would only ever be referenced using lowercase characters, regardless of how the username was originally entered into the system. You could use an accessor to modify how the username is retrieved using a method named getUsername:

class User extends Model {

  public function getUsername()
  {
    return strtolower($this->username); 
  }

}

Frankly I’m not a fan of accessors, as even if the lowercase username requirement were a strict business requirement it is still a presentational matter and therefore I’d argue the task should be left to your application’s presentational logic. Much more useful in my opinion is the creation of virtual accessors, used to combine multiple attributes together. For instance, suppose the hypothetical User model separates the user’s name into first_name and last_name attributes. However you’d like the option of easily retrieving the user’s full name, which logically always consists of the first name followed by the last name. You can define an accessor to easily retrieve this virtual attribute:

class User extends Model {

  public function getFullnameAttribute()
  {
    return $this->first_name . " " . $this->last_name;
  }

}

Once saved, you can access the virtual fullname attribute as you would any other:

use todoparrot\User;

...

$list = User::find(12); 
echo $list->fullname;

Incidentally, if you’re using the PsySH shell (via Tinker) to experiment with the models and Eloquent you’ll need to first declare the namespace:

>>> namespace todoparrot;
=> null
>>> $list = User::find(12);
>>> echo $list->fullname;

Defining Mutators

You’ll use a mutator (also known as a setter) when you’d like to modify the value of an attribute. Staying with the theoretical User model, users would logically sign in to TODOParrot using a username or e-mail address and password. For security reasons the password should be stored in the database using a hashed format, meaning it’s theoretically impossible to recreate the original value even when the hashed value is known. You would want to be absolutely certain the password is only saved to the database using the chosen hashing algorithm, and therefore might consider creating a mutator for the password attribute. Laravel recognizes mutators when the method is defined using the setAttributeNameAttribute convention, meaning you’ll want to create a method named setPassword:

class User extends Model {

  public function setPasswordAttribute($password)
  {
      $this->attributes['password'] = \Hash::make($password);
  }

}

This example uses Laravel’s Hash class to generate a hash (learn more about this class here), accepting the plaintext password passed into the method, generating the hash, and assigning the hash to the class instance’s password attribute. Here’s an example:

$user = new User;
$user->password = 'blah';
echo $user->password;
$2y$10$e3ufaNvBFWM/SeFc4ZyAhe8u5UR/K0ZUc5IjCPUvOYv6IVuk7Be7q

Defining Methods

Custom methods can greatly reduce the amount of logic cluttering your controllers, not to mention help you to stay DRY. For instance, suppose a future version of the List model includes an due date attribute which the user can use to define a date in which the list should be completed. You can define a convenience method to determine whether the list’s due date has arrived:

 use Carbon\Carbon;

 ...

 class Todolist extends Model {

     public function isDueToday()
     {
       $now = \Carbon::now();
      if ($this->created_at->diff($now)->days == 0) {
        return true;
      } else {
     return false;
      }
    }

}

This example uses the fantastic Carbon library, a PHP 5.3+ DateTime extension. With the isDueToday method in place, you can easily determine whether a list’s due date has arrived:

$list = Todolist::find(12);

if ($list->isDueToday()) {
  echo "This list is due today!";
}

Validating Your Models

Readers familiar with frameworks such as Ruby on Rails are used to defining validation rules in the model and then using native methods such as valid? to determine whether a model object’s attributes are set to expectations. Laravel supports a similar approach, although it does require developers to do a bit of additional work in order to achieve a desirable validation workflow. In this section I’ll run you through a simple example in which we’ll add validators to the List model, and then use the validator to ensure the supplied data conforms to the defined rules.

Open up the Todolist model (app/Todolist.php) and add the following property:

class Todolist extends Model {

  private $rules = [
    'name' => 'required',
    'description' => 'required'
  ];

}

The $rules array just serves as a convenient structure for defining the validations rules associated with each model attribute. This example depicts a pretty simple set of rules, stating only that the name and description attributes are required without imposing additional constraints such as for instance a minimum string length. If you do want to attach multiple validators to an attribute you’ll separate each with the pipe character, like so:

  private $rules = [
    'email' => 'required|email|unique:users'
  ]; 

Incidentally, Laravel offers a wide variety of native rules useful for validating e-mail addresses, URLs, integers, string length, dates, and more. You can view a complete list of available validators here.

Next, you’ll want to use those rules to ensure the supplied data is conformant before saving it to the database. You’ll use Laravel’s Validator class in conjunction with these rules to perform the validation and generate the error messages should validation fail. We can encapsulate the validation logic in a method:

 class Todolist extends Model {

   ...

   public function validate() 
   {

     $v = \Validator::make($this->attributes, $this->rules);
     if ($v->passes()) return true;
     $this->errors = $v->messages();
     return false;

   }

}

As an example, suppose you wanted to validate a user-supplied list before saving it to the database. The logic flow might look something like this:

$data = [
  'name' => 'San Juan Vacation',
  'description' => 'Things to do before leaving for vacation' 
];

$list = new Todolist($data);

 if ($list->validate()) {
   $errors = $list->errors();
} else {
   $list->save();
}

While the create method is convenient, Laravel requires you to take some additional safeguards to ensure that a malicious user doesn’t inject an undesired attribute into an array that might for instance be passed from a form into the create method. You can use a protected property named $fillable to determine which model attributes can be set using mass-assignment (in the fashion demonstrated via the above example. Because the current Todolist model doesn’t identify any “fillable” attributes, the above example will actually fail. In order for it to succeed, you need to set the $fillable property like so:

class Todolist extends Model {

  protected $fillable = ['name', 'description'];

}

Alternatively, suppose your table is fairly large and you’re fine with allowing mass assignment for all but a few select attributes. Rather than maintain an unwieldy list in $fillable, you can instead identify only those you do not want to be mass-assigned using the $guarded property:

class Todolist extends Model {

  protected $guarded = ['some_important_attr'];

}

While this approach isn’t terrible, it’s a bit tedious to integrate validation logic into every new model. Additionally, I’m just not interested in treating the validation and persistence process as two separate tasks, instead preferring to save the data and if it fails due to validation, just receive the validation errors in return. If you prefer a more succinct approach, several third-party packages can add convenient validation capabilities to your models.

Creating a RESTful Controller

You’ll interact with the Todolist model like you would any other PHP class, keeping in mind that a number of special methods and properties are additionally made available to the class because it extends Laravel’s Model class. We’ll logically want to interact with the model within the TODOParrot application in a variety of fashions. Notably we’ll want to retrieve lists, learn more about a specific list, create a new list, update a list, and delete a list. These tasks are so central to web applications that most popular web frameworks, Laravel included, implement representational state transfer (REST), an approach to designing networked applications that codify the way in which these tasks (create, retrieve, update, and delete) are implemented. RESTful applications use the HTTP protocol and a series of well-defined URL endpoints to implement the seven actions defined in the following table:

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

The :id included in several of the paths is a placeholder for a record’s primary key. For instance, if you wanted to view the list identified by the primary key 427, then the URL path would look like /lists/427. At first glance, it might not be so obvious how some of the other paths behave; for instance REST newcomers are often confused by POST /lists or PUT /lists/:id. Not to worry! We’ll sort all of this out in the sections to come.

As mentioned, Laravel natively supports RESTful routing. You can use Laravel’s make:controller command to create a REST-enabled controller:

$ php artisan make:controller ListsController
Controller created successfully.

Regardless of which generator you use, you’ll find the generated controller in app/Http/Controllers/ListsController.php. Open the newly created app/Http/Controllers/ListsController.php file and you’ll find the following code (comments removed for reasons of space):

 <?php 

 namespace todoparrot\Http\Controllers;

 use Illuminate\Http\Request;

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

 class ListsController extends Controller {

     public function index()
     {
     }

     public function create()
     {
     }

     public function store()
     {
     }

     public function show($id)
     {
     }

     public function edit($id)
     {
     }

     public function update($id)
     {
     }

     public function destroy($id)
     {
     }

 }

A controller is just a typical PHP class that extends Laravel’s BaseController class. Because Laravel generates RESTful controllers by default, seven methods (referred to as actions in framework parlance) have been created, with each intended to correspond with an endpoint defined in the earlier table. However, Laravel doesn’t know you intend to use the newly generated controller in a RESTful fashion until the route definitions are defined.

With the controller in place, you’ll next need to update the routes.php file. Open app/Http/routes.php and add the following line:

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

If you try to access /lists you’ll be greeted with a blank page. This is because the ListsController’s index action does not identify a view to be rendered, nor does a view even yet exist for that matter! Let’s create a simple view for use in conjunction with the index action.

Begin by creating a new directory named lists inside resources/views. This is where all of the views associated with ListController will be housed. Next, create a file named index.blade.php, placing it inside this newly created directory. Add the following contents to it:

@extends('layouts.master')

@section('content')

<h1>Lists</h1>

@endsection

Next, open app/Http/Controllers/ListsController and modify the index action to look like this:

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

Save the file and navigate to /lists. You should see the application layout and the h1 header found in index.blade.php. Congratulations, you’ve just implemented your first RESTful Laravel controller! Next, we’ll integrate the Todolist model into the ListsController controller.

Interacting with the Todolist Model

With the RESTful controller defined, we’ll begin integrating model-related logic and work towards creating, retrieving, updating and deleting lists, in addition to carrying out other useful tasks. However before doing so let’s focus solely on the syntax used to interact with the model. Fortunately, we can easily do so using the Tinker console first and save a new list to the database. Begin by opening a new Tinker session:

$ php artisan tinker
Psy Shell v0.4.4 (PHP 5.5.21 — cli) by Justin Hileman
>>>

Create a new Todolist object. To save some typing, you can declare the namespace as demonstrated here:

>>> namespace todoparrot;
>>> $list = new Todolist;

Next, assign a list name and description:

>>> $list->name = 'San Juan Vacation';
=> 'San Juan Vacation'
>>> $list->description = 'Pre-vacation planning';
=> 'Pre-vacation planning'

You can retrieve the $list object’s class name using PHP’s get_class() function:

>>> echo get_class($list);
=> todoparrot\Todolist

Finally, we’ll use the Eloquent ORM’s save() method to save the $list object to the database:

>>> $list->save();
=> true

Once the record is saved, the object will be assigned an id value (presuming you’re using auto-incrementing keys). You can see that value by referencing the id attribute:

>>> echo $list->id;
=> 1

I’m jumping ahead a bit here, but you can also easily see how many records are in the database using Eloquent’s count method:

>>> echo Todolist::all()->count();
=> 1

The save, all, and count methods are just a few of the many features made available to your models thanks to the Eloquent ORM. We’ll learn about many more in the sections to follow.

Open the database and you should see the newly added record. TODOParrot uses MySQL, and so I’ll use the mysql command line client for the purposes of demonstration:

 mysql> select * from todolists;
 +----+----------+-----------------------+---------------------+-----------------\
 ----+
 | id | name     | description           | created_at          | updated_at      \
     |
 +----+----------+-----------------------+---------------------+-----------------\
 ----+
 |  1 | San Juan | Pre-vacation planning | 2015-06-25 23:38:34 | 2015-06-26 16:05\
 :24 |
 +----+----------+-----------------------+---------------------+-----------------\
----+
1 row in set (0.00 sec)

Feel free to spend some more time experimenting with the Todolist model inside Tinker. In particular, be sure to add a few more records as we’ll use them in the next section. Once you’re done, exit the console like this:

>>> exit;

Integrating a Model Into Your Controller

Now that you have a bit of experience interacting with a Laravel model, let’s integrate a Todolist model into the Lists controller. We’ll return to the Lists controller’s index action, which currently looks like this:

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

If you recall from the earlier introduction a RESTful controller’s index action is typically used to display a list of records. So let’s use the Todolist model in conjunction with this action and corresponding view to display a list of lists. Begin by importing the todoparrot\Todolist namespace into the controller. Strictly speaking you aren’t obligated to do this but it will save some typing. The import should be placed at the very top of the controller alongside the other use statements:

<?php 

namespace todoparrot\Http\Controllers;

use Illuminate\Http\Request;

use Illuminate\Routing\Controller;
use todoparrot\Todolist;

class ListsController extends Controller {

...

}

Next, modify the index action to look like this:

public function index()
{
 $lists = Todolist::all();
  return view('lists.index')->with('lists', $lists);
}

Pretty simple, right? We’re using the all method introduced in the last section to retrieve all of the Todolist records found in the todolists table. This returns an object of type Illuminate\Database\Eloquent\Collection which is among other things iterable! We want to iterate over that collection of records in the view, and so $lists is passed into the view.

Next, open the corresponding view (resources/views/lists/index.blade.php), and modify the content section to look like this:

 <h1>Lists</h1>

 @if ($lists->count() > 0)
   <ul>
     @foreach ($lists as $list) 
       <li>{{ $list->name }}</li>
     @endforeach
   </ul>
 @else
  <p>
    No lists found!
  </p>  
@endif

The updated index view uses Eloquent’s count() function to determine whether $lists contains at least one element. If so, the Blade templating engine’s @foreach directive is used to iterate over $lists, with each retrieved element being an object of type todoparrot\Todolist. Each object’s properties are exposed using PHP’s standard object notation, meaning you could for instance access an object’s name property using $list->name.

Save the changes, navigate to /lists and you should see a bulleted list of any TODO lists found in the todolists table! While an exciting development, I promise you we’re just getting started!

Testing Your Models

Testing your models to ensure they are performing as desired is a crucial part of the application development process. Mind you, the goal isn’t to test Eloquent’s features; one can presume Eloquent continues to be tested by the Laravel development team and community at large. Instead, you want to focus on confirming proper functioning of features you incorporate into the application models, such as whether your model accessors and mutators are properly configured, whether your custom methods are behaving as expected, whether your validators are properly constraining input, whether features such as relations and scopes are correctly defined. With this in mind, let’s take some time to investigate a few testing scenarios.

Configuring the Test Database

Because you’ll want to test your application in conjunction with some realistic data you’ll need to configure a test-specific database. If you’re using PHPUnit the easiest way to do so in Laravel 5 is by overriding the .env configuration variables at the time the tests are executed. You can easily do this by adding the database configuration environment variables to the phpunit.xml file:

<php>
    ...
    <env name="DB_DATABASE" value="testing_todoparrot_com"/>
</php>

This will result in config/database.php using the same DB_HOST, DB_USERNAME, and DB_PASSWORD environment variables as those defined in your .env file but use the overridden DB_DATABASE configuration variable defined in phpunit.xml. Of course if you feel the need to configure a different username and password for the test database, you can easily override those variables as well within phpunit.xml.

Automatically Running Database Migrations

It is crucial for you to ensure that your database structure and test data are in a known state prior to the execution of each and every test, otherwise you’re likely to introduce all sorts of uncertainty into the very situations you’re trying to verify. One foolproof way to do this is by completely tearing down and rebuilding your test database structure prior to and following each test, respectively. I showed you a fairly involved solution for doing so, however as of Laravel 5.1 it is a trivial task! All you need to do is add the following use DatabaseMigrations statement to your test class:

<?php

 use Illuminate\Foundation\Testing\WithoutMiddleware;
 use Illuminate\Foundation\Testing\DatabaseMigrations;
 use Illuminate\Foundation\Testing\DatabaseTransactions;

 use App\Todolist;

 class TodolistTest extends TestCase
 {

     use DatabaseMigrations;
     ...

 }

Once declared, Laravel will automatically rollback and execute your migrations for each and every test!

Creating a Model Factory

In addition to test-oriented database migrations, Laravel 5.1 introduces a great new feature known as a factory. Factories are useful for generating sample data which can then be used in your tests. Laravel 5.1+ applications include an example User model factory, found in database/factories/ModelFactory.php:

$factory->define(App\User::class, function ($faker) {
    return [
        'name' => $faker->name,
        'email' => $faker->email,
        'password' => str_random(10),
        'remember_token' => str_random(10),
    ];
}); 

This factory uses the previously introduced Faker package to generate a placeholder name and e-mail address, and additionally uses PHP’s str_random() function to generate a random password and password recovery token. Keep in mind you’re free to set any column using a static value, although tools such as Faker will save you quite a bit of hassle when you’d like to create a large number of sample records.

Add the following Todolist factory below the factory defined above:

$factory->define(App\Todolist::class, function ($faker) {
    return [
        'name' => 'blah',
        'description' => $faker->name
    ];
});

With the factory created you can then reference it within your tests like so:

$listFactory = factory('App\Todolist')->create();

Executing the factory in conjunction with create() will cause the record to be saved to the database. You could optionally just create an object of type Todolist containing the information found in the factory by instead using make():

$listFactory = factory('App\Todolist')->make();

Perhaps not surprisingly the latter approach will be faster, and so is recommended when there is no need to subsequently retrieve factory-generated data from the database during the course of the test.

Creating Your First Model Test

To begin, create a directory named models inside your project’s test directory. Keep in mind this is purely for organizational purposes, and you’re free to create any directory you please. Next, create a file named TodolistTest.php, placing it in the models directory. Add the following contents to this file:

 use Illuminate\Foundation\Testing\WithoutMiddleware;
 use Illuminate\Foundation\Testing\DatabaseMigrations;
 use Illuminate\Foundation\Testing\DatabaseTransactions;

 use todoparrot\todolist;       

 class TodolistTest extends TestCase
 {     

   use DatabaseMigrations;

   public function testCanInstantiateTodolist()
   {   

     $list = new Todolist;      
     $this->assertEquals(get_class($list), 'todoparrot\Todolist');

   }

}

This is a pretty trivial test, intended to confirm we can instantiate the Todolist class, that it is part of the todoparrot namespace, and that it is of type Todolist. Execute the test like so:

vendor/bin/phpunit tests/models/TodolistTest.php 
PHPUnit 4.7.2 by Sebastian Bergmann and contributors.

.

Time: 147 ms, Memory: 14.50Mb

OK (1 test, 1 assertion)

Great! Let’s try something a tad more involved. Earlier in the article we configured a presence validator for the Todolist model’s name attribute. Let’s confirm the validator is indeed working as expected. Add the following method to the TodolistTest class:

public function testNotValidWhenNameMissing()
{

  $t = new Todolist;

  $this->assertFalse($t->validate());

}

How about a test involving the previously created Todolist factory? Add the following method to the TodolistTest class:

   public function testTodolistRecordCount()
   {

       $listFactory = factory('App\Todolist')->create();

       $lists = Todolist::all();

       $this->assertEquals($lists->count(), 1);

   }

What other tests would be useful? Try adding a method to your model and confirming it is producing the intended outcome!

Reference