Laravel5.1模型-关系模型

Published on 2017 - 04 - 02

Introducing One-to-One Relations

One-to-one relationships link one row in a database table to one (and only one) row in another table. In my opinion there are generally few uses for a one-to-one relationship because the very nature of the relationship indicates the data could be consolidated within a single record. However, for the sake of demonstration let’s suppose your application offered user authentication and profile management, and you wanted to separate the user’s authentication (e-mail address, password) and profile (name, phone number, gender) data into two separate tables. This relationship is depicted in the below diagram.

To manage this relationship in Laravel you’ll associate the User model with the model responsible for managing the profiles, which we’ll call Profile. To create the model you can use the Artisan generator:

$ php artisan make:model Profile

You’ll find the newly generated model inside app/Profile.php:

<?php namespace todoparrot;

use Illuminate\Database\Eloquent\Model;

class Profile extends Model {

    //

}

Laravel will also generate a migration for the model’s corresponding database table (profiles). Open up the newly created migration (inside database/migrations) and modify the up method to look like this:

 public function up()
 {   
   Schema::create('profiles', function(Blueprint $table)
   {
     $table->increments('id');
     $table->integer('user_id')->unsigned()->nullable();
     $table->foreign('user_id')->references('id')->on('users');
     $table->string('name');
     $table->string('telephone');
    $table->timestamps();    
  });
}

The first line results in the addition of an integer-based column named user_id. The second line identifies this column as being a foreign key which references the users table’s id column.

After saving the changes run the following command to create the table:

$ php artisan migrate
Migrated: 2015_01_20_201647_create_profiles_table

With the tables in place it’s time to formally define the relations within the Laravel application.

Defining the One-to-One Relation

You’ll define a one-to-one relation by creating a public method typically having the same name as the related model. The method will return the value of the hasOne method, as demonstrated below:

class User extends Model {

  public function profile()
  {
    return $this->hasOne('todoparrot\Profile');
  }

}

Once defined, you can retrieve a user’s profile information by calling the user’s profile method. Because the relations can be chained, you could for instance retrieve a user’s telephone number like this:

$user = User::find(212)->profile->telephone;

To retrieve the telephone number, Laravel will look for a foreign key in the profiles table named user_id, matching the ID stored in that column with the user’s ID.

The above example demonstrates how to traverse a relation, but how is a relation created in the first place? I’ll show you how to do this next.

Creating a One-to-One Relation

You can easily create a One-to-One relation by creating the child object and then saving it through the parent object, as demonstrated in the below example:

$profile = new Profile;
$profile->telephone = '614-867-5309';

$user = User::find(212);
$user->profile()->save($profile);

Deleting a One-to-One Relation

Because a profile should not exist without a corresponding user, you’ll just delete the associated profile record in the case you want to end the relationship:

$user = User::find(212);
$user->profile()->delete();

However, if a user record were to be deleted from the database you wouldn’t want its corresponding profile record to be orphaned. One way to avoid this is by deleting the related profile record after deleting the user record (via Eloquent’s delete method), but chances are this two step process will eventually be neglected, leaving orphaned records strewn about the database. Instead, you’ll probably want to automate this process by taking advantage of the underlying database’s ability to delete child tables when the parent table is deleted. You can specify this requirement when defining the foreign key in your table migration. I’ve modified the relevant lines of the earlier migration used to create the profiles table, attaching the onDelete option to the foreign key:

$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')
      ->on('users')->onDelete('cascade');

With the cascading delete option in place, deleting a user from the database will automatically result in the deletion of the user’s corresponding profile.

Introducing the Belongs To Relation

Using the hasOne relation demonstrated in the User model as demonstrated above, it’s possible to retrieve a profile attribute via a user, such as a phone number, but not possible to retrieve a user via a given profile. This is because the hasOne relation is a one-way definition. You can make the relation bidirectional by defining a belongsTo relation in the Profile model, as demonstrated here:

class Profile extends Model {

    public function user()
    {
      return $this->belongsTo('todoparrot\User');
    }

}

Because the profiles table contains a foreign key representing the user (via the user_id column), each record found in profiles “belongs to” a record found in the users table. Once defined, you could retrieve a profile’s associated user e-mail address based on the profile’s telephone number like so:

$email = Profile::where('telephone', '614-867-5309')
  ->get()->first()->user->email;

The Belongs To association certainly isn’t limited to use in conjunction with One-to-One. Throughout the remainder of this article we’ll use it in conjunction with several other relations.

Introducing One-to-Many Relations

The One-to-Many (also known as the Has Many) relationship is useful when you want to relate one table record to one or many other table records. The One-to-Many relation is used throughout TODOParrot, so in this section we’ll look at some actual code used to power the application. The One-to-Many relation is used when you want to relate a single table record to multiple table records. For instance a list can have multiple tasks, therefore one list is related to many tasks, meaning we’ll need to relate the Todolist and Task models using a One-to-Many relation.

Creating the Task Model

In the last article we created the Todolist model, meaning we’ll need to create the Task model in order to begin associating tasks with lists. Use Artisan to generate the Task model:

$ php artisan make:model Task

You’ll find the newly generated model inside app/Task.php:

<?php namespace todoparrot;

use Illuminate\Database\Eloquent\Model;

class Task extends Model {

    //

}

Open the newly created corresponding migration file (found in database/migrations) and modify it to look like this:

 public function up()
 {
   Schema::create('tasks', function(Blueprint $table)
   {
     $table->increments('id');
     $table->integer('todolist_id')->unsigned();
     $table->foreign('todolist_id')
       ->references('id')->on('todolists')
       ->onDelete('cascade');
     $table->string('name');
     $table->text('description'); 
     $table->boolean('done')->default(false);
     $table->timestamps();
   });
}

 public function down()
 {
   Schema::drop('tasks');
 }

Notice we’re including an integer-based column named todolist_id in the tasks table, followed by a specification that this column be defined as a foreign key. In doing so, Laravel will ensure that the column is indexed, and additionally you’ll optionally be able to determine what happens to these records should the parent be updated or deleted (more about this latter matter in a moment). After saving the changes, run the migration:

$ php artisan migrate
Migrated: 2014_10_30_164456_create_tasks_table

Defining the One-to-Many Relation

With the Task model and underlying table created, it’s time to create the relation. Open the Todolist model and create a public method named tasks, inside it referencing the hasMany method:

 class Todolist extends Model {

   ...

   public function tasks()
   {
     return $this->hasMany('todoparrot\Task');
   }

}

You’ll likely also want to define the opposite side of the relation within the Task model using the belongsTo method:

class Task extends Model {

   ...

   public function todolist()
   {
     return $this->belongsTo('todoparrot\Todolist');
   }

}

With the relation defined, let’s next review how to associate tasks with a list.

Associating Tasks with a TODO List

To assign a task to a list, you’ll first create a new Task object and then save it through the Todolist object, as demonstrated here:

 $list = Todolist::find(245);

 $task = new Task;
 $task->name = 'Walk the dog';
 $task->description = 'Walk Barky the Mutt';

 $list->tasks()->save($task);

 $task = new Task;
 $task->name = 'Make tacos for dinner';
 $task->description = 'Mexican sounds really yummy!';

 $list->tasks()->save($task);

With two tasks saved, you can now iterate over the list’s tasks within a view like you would any other collection. Let’s modify the Lists controller’s show action/view to additionally display list tasks. The Lists controller’s show action doesn’t actually change at all, but I’ll include it here anyway for easy reference:

public function show($id)  
{
  $list = Todolist::find($id);    
  return view('lists.show')->with('list', $list);
}

We’ll only need to update the view (resources/views/lists/show.blade.php) to iterate over the tasks. I’ll present the modified view here:

 @extends('layouts.master') 

 @section('content')

   <h1>{{ $list->name }}</h1> 

 <p>
 Created on: {{ $list->created_at }} 
 Last modified: {{ $list->updated_at }}<br />
 </p>

 <p>
 {{ $list->description }}
 </p>

 <h2>Tasks</h2> 
 @if ($list->tasks->count() > 0)
   <ul>
   @foreach ($list->tasks as $task)

     <li>{{ $task->name }}</li>

   @endforeach
   </ul>
 @else
  <p>
   You haven't created any tasks. 
   <a href="{{ URL::route('lists.tasks.create', [$list->id]) }}" 
      class='btn btn-primary'>Create a task</a>
 </p>  
 @endif

 @endsection

Filtering Related Records

You’ll often wish to retrieve a filtered collection of related records. For instance the user might desire to only see a list of incomplete list tasks. You can do so by filtering on the tasks table’s done column:

$completedTasks = Todolist::find(1)->tasks()->where('done', true)->get();

Introducing Many-to-Many Relations

You’ll use the many-to-many relation when the need arises to relate a record in one table to one or several records in another table, and vice versa. Consider some future version of TODOParrot that allowed users to classify lists using one or more categories, such as “leisure”, “exercise”, “work”, “vacation”, and “cooking”. A list titled “San Juan Vacation” might be associated with several categories such as “leisure” and “vacation”, and the “leisure” category would likely be associated with more than one list, meaning a list can be associated with many categories, and a category can be associated with many lists. See the below diagram for an illustrative example of this relation.

In this section you’ll learn how to create the intermediary table used to manage the relation (known as a pivot table), define the relation within the respective models, and manage the relation data.

Creating the Pivot Table

Many-to-many relations require an intermediary table to manage the relation. The simplest implementation of the intermediary table, known as a pivot table, would consist of just two columns for storing the foreign keys pointing to each related pair of records. Laravel presumes the pivot table is named by concatenating the two related model names together with an underscore separating the names. The names should appear in alphabetical order. Therefore if we were creating a many-to-many relationship between the Todolist and Category models, the pivot table name would be category_todolist. Of course, the Category model and corresponding categories table also needs to exist, so let’s begin by generating the model:

$ php artisan make:model Category

You’ll find the newly generated model inside app/Category.php:

<?php namespace todoparrot;

use Illuminate\Database\Eloquent\Model;

class Category extends Model {

  protected $fillable = [];

}

Next, let’s create the categories table using Artisan’s make:migration command:

$ php artisan make:migration --create=categories create_categories_table
Created Migration: 2015_01_20_214155_create_categories_table

After creating the migration modify the newly created migration file’s up() method to look like this:

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

Finally, run Artisan’s migrate command to create the table:

$ php artisan migrate

With the Category model and corresponding categories table created, let’s next create the category_todolist table:

$ php artisan make:migration --create=category_todolist create_category_todolist_table

Next, open up the newly created migration (database/migrations/) and modify the up method to look like this:

public function up()
 {
   Schema::create('category_todolist', function(Blueprint $table)
   {
       $table->integer('category_id')->unsigned()->nullable();
       $table->foreign('category_id')->references('id')
             ->on('categories')->onDelete('cascade');

       $table->integer('todolist_id')->unsigned()->nullable();
       $table->foreign('todolist_id')->references('id')
             ->on('todolists')->onDelete('cascade');

       $table->timestamps();
   });
}

After saving the changes run Artisan’s migrate command to create the table:

$ php artisan migrate

Defining the Many-to-Many Relation

With the tables in place it’s time to define the many-to-many relation within the respective models. Open the Todolist model and add the following method to the class:

public function categories()
{
    return $this->belongsToMany('todoparrot\Category')->withTimestamps();
}

Notice I’ve chained the withTimestamps method to the return statement. This instructs Laravel to additionally update the category_todolist timestamps when saving a new record. If you choose to omit the created_at and updated_at timestamps from this pivot table (done by removing the call to $table->timestamps from the migration), you can omit the withTimestamps method).

Save the changes and then open the Category model, adding the following method to the class:

public function todolists()
{
    return $this->belongsToMany('todoparrot\Todolist')->withTimestamps();
}

After saving these changes you’re ready to begin using the relation!

Associating Records Using the Many-to-Many Relation

You can associate records using the many-to-many relation in the same way as was demonstrated for one-to-many relations; just traverse the relation and use the save method, as demonstrated here:

$tl = Todolist::find(1);

$category = new Category(array('name' => 'Vacation'));

$tl->categories()->save($category);

In order for this particular example to work you’ll need to make sure name has been added to the Category model’s fillable property.

After executing this code you’ll see the new category has been created and the association between this newly created category and the list has been made:

 mysql> select * from categories;
 +----+----------+---------------------+---------------------+
 | id | name     | created_at          | updated_at          |
 +----+----------+---------------------+---------------------+
 |  1 | Vacation | 2014-11-03 20:44:11 | 2014-11-03 20:44:11 |
 +----+----------+---------------------+---------------------+

 mysql> select * from category_todolist;
 +-------------+-------------+---------------------+---------------------+
 | category_id | todolist_id | created_at          | updated_at          |
 +-------------+-------------+---------------------+---------------------+
 |           1 |           1 | 2014-11-04 20:50:54 | 2014-11-04 20:50:54 |
 +-------------+-------------+---------------------+---------------------+

The above example involves the creation of a new category. You can easily associate an existing category with a list using similar syntax:

$list = Todolist::find(2);

$category = Category::find(1)

$list->categories()->save($category);

You can alternatively use the attach and detach methods to associate and disassociate related records. For instance to both associate and immediately persist a new relationship between a list and category, you can either pass in the Category object or its primary key into attach. Both variations are demonstrated here:

$list = Todolist::find(2);

$category = Category::find(1)

// In this example we're passing in a Category object
$list->categories()->attach($category);

// The number 5 is the primary key of another category
$list->categories()->attach(5);

You can also pass an array of IDs into attach:

$list->categories()->attach([3,4]);

To disassociate a category from a list, you can use detach, passing along either the Category object, an object’s primary key, or an array of primary keys:

// Pass the Category object into the detach method
$list->categories()->detach(Category::find(3));

// Pass a category's ID
$list->categories()->detach(3);

// Pass along an array of category IDs
$list->categories()->detach([3,4]);

Determining if a Relation Already Exists

Laravel will not prevent you from duplicating an association, meaning the following code will result in a list being associated with the same category twice:

$list = Todolist::find(2);

$category = Category::find(1)

$list->categories()->save($category);
$list->categories()->save($category);

If you have a look at the database you’ll see that the Todolist record associated with the primary key 2 has been twice related to the Category record associated with the primary key 1, which is surely not the desired behavior:

mysql> select * from category_todolist;
+-------------+-------------+---------------------+---------------------+
| category_id | todolist_id | created_at          | updated_at          |
+-------------+-------------+---------------------+---------------------+
|           1 |           2 | 2014-11-04 20:50:54 | 2014-11-04 20:50:54 |
|           1 |           2 | 2014-11-04 20:50:55 | 2014-11-04 20:50:55 |
+-------------+-------------+---------------------+---------------------+

You can avoid this by first determining whether the relation already exists using the contains method:

 $list = Todolist::find(2);

 $category = Category::find(1)

 if ($list->categories->contains($category))
 {

   return Redirect::route('lists.show', array($list->id))
     ->with('message', 'Category could not be assigned. Duplicate entry!');

 } else {

   $list->categories()->save($category);

   return Redirect::route('lists.show', array($list->id))
     ->with('message', 'The category has been assigned!');

 }

Saving Multiple Relations Simultaneously

You can use the saveMany method to save multiple relations at the same time:

$list = Todolist::find(1);

$categories = [
  new Category(array('name' => 'Vacation')),
  new Category(array('name' => 'Tropical')),
  new Category(array('name' => 'Leisure')),
];

$list->categories()->saveMany($categories);

Traversing the Many-to-Many Relation

You’ll traverse a many-to-many relation in the same fashion as described for the one-to-many relation; just iterate over the collection:

 $list = Todolist::find(2);

 ...

 @if ($list->categories->count() > 0)
    <ul>

   @foreach($list->categories as $category)

     <li>{{ $category->name }}</li>

   @endforeach

   </ul>

 @endif

Because the relation is defined on each side, you’re not limited to traversing a list’s categories! You can also traverse a category’s lists:

$category = Category::find(2);

 ...

 @if ($category->lists->count() > 0)

   <ul>

   @foreach($category->lists as $list)

    <li>{{ $list->name }}</li>

  @endforeach

  </ul>

@endif

Synchronizing Many-to-Many Relations

Suppose you provide users with a multiple selection box that allows users to easily associate a list with one or more categories. Because the user can both select and deselect categories, you must take care to ensure that not only are the selected categories associated with the list, but also that any deselected categories are disassociated with the list. This task is a tad more daunting than it may at first seem. Fortunately, Laravel offers a method named sync which you can use to synchronize an array of primary keys with those already found in the database. For instance, suppose categories associated with the IDs 7, 12, 52, and 77 were passed into the action where you’d like to synchronize the list and categories. You can pass the IDs into sync as an array like this:

$categories = [7, 12, 52, 77];

$tl = Todolist::find(2);

$tl->categories()->sync($categories);

Once executed, the Todolist record identified by the primary key 2 will be associated only with the categories identified by the primary keys 7, 12, 52, and 77, even if prior to execution the Todolist record was additionally associated with other categories.

Managing Additional Many-to-Many Attributes

Thus far the many-to-many examples presented in this chapter have been concerned with a join table consisting of two foreign keys and optionally the created_at and updated_at timestamps. But what if you wanted to manage additional attributes within this table, such as why a list’s category was chosen? I realize this is perhaps a contrived example since the necessity of including such a reason seems to be a bit overkill, but cut me some slack since I’m making this up as I go along.

Believe it or not adding other attributes is as simple as including them in the table schema. For instance let’s create a migration that adds a column named description to the category_todolist table created earlier in this section:

$ php artisan make:migration add_description_to_category_todolist_table
Created Migration: 2015_01_20_221931_add_description_to_category_todolist_table

Next, open up the newly generated migration file and modify the up() and down() methods to look like this:

 public function up()
 {
   Schema::table('category_todolist', function($table)
   {
       $table->string('description');
   });
 }

 public function down()
 {
   Schema::table('category_todolist', function($table)
   {
       $table->dropColumn('description');
   });
 }

Save the changes and After generating the migration be sure to migrate the change into the database:

$ php artisan migrate
Created Migration: 2015_01_20_221931_add_description_to_category_todolist_table

With the additional column in place all you’ll need to do is adjust the syntax used to relate categories with the list. You’ll pass along the category’s ID along with the description key and desired value, as demonstrated here:

$list = Todolist::find(2);
$list->categories()->attach(
  [3 => ['description' => 'Because San Juan is a tropical island']]
);

If you later wished to update an attribute associated with an existing record, you can use the updateExistingPivot method, passing along the category’s foreign key along with an array containing the attribute you’d like to update along with its new value:

$list->categories()->updateExistingPivot(3, 
  ['description' => 'Sun, beaches and rum!']
);

Introducing Has Many Through Relations

Suppose TODOParrot’s CEO has just returned from the “Mo Big Data Mo Money” conference, flush with ideas regarding how user data can be exploited and sold to advertisers. To kick things off he’s asked you to create a new feature that summarizes the numbers of lists created according to country. You recently integrated a country of residence field into the user registration form (which means each user belongs to a country, and each country conceivably has many users), so you can tally up users according to country. To quickly recap this means the user/country relations would look like this:

 class User extends Model {

   public function country()
   {
     return $this->belongsTo('todoparrot\Country');
   }

 }

class Country extends Model {

   public function users()
   {
       return $this->hasMany('todoparrot\User');
   }

 }

Because the users table that contains the foreign key reference to the countries table’s ID, and not the user’s lists, how can you relate lists with countries? The SQL query used to mine this sort of data is pretty elementary:

SELECT count(todolists.id), countries.name FROM todolists 
  LEFT JOIN users on users.id = todolists.user_id 
  LEFT JOIN countries ON countries.id = users.country_id 
  GROUP BY countries.name;

But how might you implement such a feature within your Laravel application? Enter the Has Many Through relation. The Has Many Through relation allows you to create a shortcut for querying data available through distantly related tables. This is actually incredibly easy to implement; just add the following relation to the Country model:

public function lists()
{
    return $this->hasManyThrough('todoparrot\Todolist', 'todoparrot\User');
}

This relation gives the Country model the ability to access the Todolist model through the User model. After saving the model, you’ll be able to for instance iterate over all lists created by user’s residing in Italy:

$country = Country::where('name', 'Italy')->get()->first();

...

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

Introducing Polymorphic Relations

When considering an interface for commenting on different types of application data (products and blog posts, for example), one might presume it is necessary to manage each type of comment separately. This approach would however be repetitive because each comment model would presumably consist of the same data structure. You can eliminate this repetition using a polymorphic relation, resulting in all comments being managed via a single model.

Let’s work through an example that would use polymorphic relations to add commenting capabilities to the User and Todolist models. Begin by creating a new model named Comment:

$ php artisan make:model Comment

You’ll find the newly generated model inside app/Comment.php:

<?php namespace todoparrot;

use Illuminate\Database\Eloquent\Model;

 class Comment extends Model {

    //

}

Next, generate the associated table:

$ php artisan make:migration --create=comments create_comments_table
Created Migration: 2015_01_20_223902_create_comments_table

Next, open up the newly generated migration file and modify the up() method to look like this:

Schema::create('comments', function(Blueprint $table)
{
  $table->increments('id');
  $table->text('body');
  $table->integer('commentable_id');
  $table->string('commentable_type');
  $table->timestamps();
});

Finally, save the changes and run the migration:

$ php artisan migrate
Migrated: 2015_01_20_223902_create_comments_table

Because the Comment model serves as a central repository for comments associated with multiple different models, we require a means for knowing both which model and which record ID is associated with a particular comment. The commentable_type and commentable_id fields serve this purpose. For instance, if a comment is associated with a list, and the list record associated with the comment has a primary key of 453, then the comment’s commentable_type field will be set to Todolist and the commentable_id to 453.

Logically you’ll want to attach other fields to the comments table if you plan on for instance assigning ownership to comments via the User model, or would like to include a title for each comment.

Next, open the Comment model and add the following method:

class Comment extends Model {

    public function commentable()
    {
      return $this->morphTo();
    }

}

The morphTo method defines a polymorphic relationship. Personally I find the name to be a poor choice; when you read it just think “belongs To” but for polymorphic relationships, since the record will belong to whatever model is defined in the commentable_type field. This defines just one side of the relationship; you’ll also want to define the inverse relation within any model that will be commentable, creating a method that determines which model is used to maintain the comments, and referencing the name of the method used in the polymorphic model:

class Todolist extends Model {

  public function comments()
  {
    return $this->morphMany('\todoparrot\Comment', 'commentable');
  }

}

With these two methods in place, it’s time to begin using the polymorphic relation! The syntax for adding, removing and retrieving comments is straightforward; in the following example we’ll attach a new comment to a list:

$list = Todolist::find(1);

$c = new Comment();

$c->body = 'Great work!';

$list->comments()->save($c);

After saving the comment, review the database and you’ll see a record that looks like the following:

 mysql> select * from comments;
 +----+-------------+----------------+---------------------+------------+------------+
 | id | body        | commentable_id | commentable_type    | created_at | updated_at |
 +----+-------------+----------------+---------------------+------------+------------+
 |  1 | Great work! |              1 | todoparrot\Todolist | 2015-...   | 2015-.. .   |
 +----+-------------+----------------+---------------------+------------+------------+

The list’s comments are just a collection, so you can easily iterate over it. You’ll retrieve the list within the controller per usual:

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

In the corresponding view you’ll iterate over the comments collection:

  @foreach ($list->comments as $comment)
    <p>
      {{ $comment->body }}
    </p>
  @endforeach

To delete a comment you can of course just delete the comment using its primary key.

Eager Loading

There’s a matter known as the “N + 1 Queries” problem that has long confused web developers to the detriment of their application’s performance. To understand the nature of the issue, consider the following seemingly innocent query:

$users = User::take(5)->get();

In the corresponding application view you then iterate over the retrieved locations like so:

<ul>
  @foreach($users as $user)
    <li>{{ $user->first_name }}: {{ $user->state->name }}</li>
  @endforeach
</ul>

Pretty innocent bit of code, right? It certainly seems so until you realize these two snippets result in the execution of 6 distinct queries! Thus the name “N + 1”, because we’re executing one query to retrieve the ten locations, and then 5 queries to retrieve the name of each user’s state name! In situations where you know you’re going to need to access a relation’s attribute you can use the with method to inform Laravel of your intent to subsequently access this relation and therefore preload the data:

$users = User::with('state')->take(5)->get();

When you subsequently access a User object’s state name, the data will be immediately available because each user’s state-related data was preloaded along with the original query!

Introducing Scopes

Applying conditions to queries gives you to power to retrieve and present filtered data in every imaginable manner. Some of these conditions will be used more than others, and Laravel provides a way for you to cleanly package these conditions into easily readable and reusable statements. Consider a filter that only retrieves completed list tasks. You could use the following where condition to retrieve those tasks:

$completedTasks = Task::where('done', true)->get();

You might however wish to use a query such as this at multiple locations throughout an application. If so, you can DRY the code up a bit by instead using a scope. A scope is just a convenience method you can add to your model which encapsulates the syntax used to execute a query such as the above. Scopes are defined by prefixing the name of a method with scope, as demonstrated here:

class Task extends Model
{

    public function scopeDone($query)
    {
        return $query->where('done', 1);
    }

}

With the scope defined, you can execute it like so:

$completedTasks = Task::done()->get();

Creating Dynamic Scopes

If you wanted to create a scope capable of returning both completed and incomplete tasks based on a supplied argument, just define an input parameter like you would any model method:

class Task extends Model {

    public function scopeDone($query, $flag)
    {
        return $query->where('done', $flag);
    }

}

With the input parameter defined, you can use the scope like this:

// Get completed tasks
$completedTasks = Task::done(true)->get();

// Get incomplete tasks
$incompleteTasks = Task::done(false)->get();

Using Scopes with Relations

You’ll often want to use scopes in conjunction with relations. For instance, you can retrieve a list of tasks associated with a specific list:

$list = Todolist::find(34);
$completedTasks = $list->tasks()->done(true)->get();

Reference

  • Easy Laravel 5 Chapter 4 Model Relations, Scopes, and Other Advanced Features