Separation of business logic and data access using Repository mode in Laravel 5

Source: Internet
Author: User
Tags autoload

1. Overview

The first thing to declare is that the design pattern and the framework used and the language is irrelevant, the key is to understand the principles behind the design pattern, so that no matter what technology you use, you can implement the corresponding design patterns in practice.

According to the original author's introduction, Repository is a link between the data mapping layer and the domain layer, which acts as a collection of domain objects in memory. The client object combines some entities of the query and submits them to Repository. objects can be removed or added from Repository, just as they do data operations on a Collection object, and the map layer's code will fetch the corresponding data from the database.

Conceptually, Repository is a collection of data from a data store that is encapsulated into objects and provides operations on those collections.

Repository mode of business logic and data access to the separation between the two through the Repository interface communication, popular point, you can think of Repository as a warehouse manager, we have to take things from the warehouse (business logic), Just need to find the administrator to be (Repository), do not need to find (data access), the specific process as shown:

This pattern of separating data access from business logic has many benefits:

    • Centralized data access logic makes code easy to maintain
    • Complete separation of business and data access logic
    • Reduce duplicate code
    • Reduce the chance of a program error

2, just interface.

To implement the Repository pattern, you first need to define the interfaces, which are like the contracts in Laravel, and require specific classes to implement. Now we assume that there are two data Objects Actor and Film. What can be done on these two data objects? In general, we will do these things:

    • Get All records
    • Get Paging Records
    • Create a new record
    • Get the specified record by primary key
    • Get the corresponding record by property
    • Update a record
    • Delete a record

Now you realize that if we do these things for each data object, how much duplicate code to write! Of course, this is not a big deal for small projects, but it is obviously a bad idea for large applications.

Now, if we want to define these operations, we need to create a Repository interface:

Interface Repositoryinterface {public    function all ($columns = Array (' * '));    Public Function paginate ($perPage = $columns = Array (' * '));    Public function Create (array $data);    Public function Update (array $data, $id);    Public Function Delete ($id);    Public function Find ($id, $columns = Array (' * '));    Public Function FindBy ($field, $value, $columns = Array (' * '));}

3. Directory structure

Before we continue to create concrete Repository implementation classes, let's first think about how we want to organize the code we want to write, usually, when I want to create classes, I like to organize the code in a component way, because I want the code to be reused in other projects easily. The directory structure I defined for the Repository component is as follows:

But this is not static, depending on the situation to decide. For example, if a component includes a configuration item, or a migration, the directory structure will be different.

In the SRC directory, I created three subdirectories: contracts, eloquent, and Exceptions. The reason for such an order is obvious, and at a glance you can see what kind of storage is in it. We will place the interface in the contracts directory, the eloquent directory is used to hold the abstract class and the concrete class that implements the Repository interface, and the Exceptions directory holds the exception handling class.

Since we are creating an extension package, we need to create a Composer.json file to define the namespace mapping directory, package dependencies, and other metadata. Here is the contents of my Composer.json file:

{    "name": "Bosnadev/repositories",    "description": "Laravel repositories",    "keywords": [        "Laravel",        "Repository",        "repositories",        "eloquent",        "database"    ],    "licence": "MIT",    " Authors ": [        {            " name ":" Mirza pasic ",            " email ":" Mirza.pasic@edu.fit.ba "        }    ],    " require ": {        " php ":" >=5.4.0 ",        " Illuminate/support ":" 5.* ",        " illuminate/database ":" 5.* "    },    " AutoLoad ": {"        psr-4 ": {            " bosnadev\\repositories\\ ":" src/"        }    },    " Autoload-dev ": {        " Psr-4 ": {            " bosnadev\\tests\\repositories\\ ":" tests/"        }    },    " Extra ": {        " Branch-alias ": {            "Dev-master": "0.x-dev"        }    ,    "minimum-stability": "Dev",    "prefer-stable": true}

As you can see, we mapped the bosnadev\repository to the SRC directory. Also, before we implement Repositoryinterface, because it is in the contracts directory, we need to set the correct namespace for it:

     

Here we are ready to formally begin to implement this contract.

4, Repository realization

Using Repository allows us to query data in the data source and return that data to business logic, while also persisting data modifications in the business logic to the data source:

Of course, each specific sub-Repository inherits from the abstract Repository parent class, and the Repository parent of this image implements the Repositoryinterface contract. Now we are officially starting to implement this contract.

The first method in the contract is all (), which is used to get all the records for the specific business logic, which only receives an array parameter $columns, which specifies the fields returned from the data source, returning all fields by default:

Public Function All ($columns = Array (' * ')) {    return bosnadev\models\actor::get ($columns);}

But that's not enough, and we want it to be a more general approach:

Public Function All ($columns = Array (' * ')) {    return $this->model->get ($columns);}

Where $this->model is an instance of Bosnadev\models\actor, we also need to define the method that sets the instance:

  App = $app;    $this->makemodel ();    }/** * Specify model class name * * @return Mixed */abstract function model (); /** * @return Model * @throws repositoryexception */Public Function Makemodel () {$model = $this-&G        T;app->make ($this->model ()); if (! $model instanceof Model) throw new Repositoryexception ("Class {$this->model ()} must is an instance of I            Lluminate\\database\\eloquent\\model ");   return $this->model = $model; }}

We define an abstract method model () in this abstract class, forcing the implementation of the method in the implementation class to get the model corresponding to the current implementation class:

      

Now we implement the other contract methods in the abstract class:

   App = $app;    $this->makemodel ();    }/** * Specify model class name * * @return Mixed */abstract function model (); /** * @param array $columns * @return Mixed */Public function All ($columns = Array (' * ')) {return $    This->model->get ($columns); }/** * @param int $perPage * @param array $columns * @return Mixed */Public function paginate ($pe    RPage = $columns = Array (' * ')) {return $this->model->paginate ($perPage, $columns); }/** * @param array $data * @return Mixed */Public function Create (array $data) {return $this-    >model->create ($data); }/** * @param array $data * @param $id * @param string $attribute * @return Mixed * * Public fu Nction Update (array $data, $id, $attribute = "id") {return $this->model->where ($attribute, ' = ', $id)->updat    E ($data); }/** * @param $id * @return Mixed * *   Public Function Delete ($id) {return $this->model->destroy ($id); }/** * @param $id * @param array $columns * @return Mixed */Public function find ($id, $columns = a    Rray (' * ')) {return $this->model->find ($id, $columns); }/** * @param $attribute * @param $value * @param array $columns * @return Mixed */public func tion FindBy ($attribute, $value, $columns = Array (' * ')) {return $this->model->where ($attribute, ' = ', $value)-    >first ($columns); }/** * @return \illuminate\database\eloquent\builder * @throws repositoryexception * * Public Function M        Akemodel () {$model = $this->app->make ($this->model ()); if (! $model instanceof Model) throw new Repositoryexception ("Class {$this->model ()} must is an instance of I            Lluminate\\database\\eloquent\\model ");    return $this->model = $model->newquery (); }}

It's simple, isn't it? The only thing left to do now is to rely on injected actorrepository in Actorscontroller:

 
    actor = $actor;    }    Public Function Index () {        return \response::json ($this->actor->all ());}    }

5. Criteria Query Implementation

The above implementations are sufficient for simple queries, but for large projects, sometimes it is necessary to create some custom queries through the Criteria to get some more complex query result sets.

To implement this function, we first define the following abstract class:

       

This abstract class declares an abstract method of apply, which is required to implement the Criteria query in the implementation class that inherits the abstract class. Before defining a specific class that implements the abstract class, we first create a new contract for the Repository class:

        

Next we modify Repository's abstract class as follows:

     App = $app;        $this->criteria = $collection;        $this->resetscope ();    $this->makemodel ();    }/** * Specify model class name * * @return Mixed */public abstract function Model (); /** * @param array $columns * @return Mixed */Public function All ($columns = Array (' * ')) {$this-&G        T;applycriteria ();    return $this->model->get ($columns); }/** * @param int $perPage * @param array $columns * @return Mixed */Public function paginate ($per        Page = 1, $columns = Array (' * ')) {$this->applycriteria ();    return $this->model->paginate ($perPage, $columns); }/** * @param array $data * @return Mixed */Public function Create (array $data) {return $this-    >model->create ($data); }/** * @param array $data * @param $id * @param string $attribute * @return Mixed * * Public fun ction Update (array $data, $id, $attribute = "ID ") {return $this->model->where ($attribute, ' = ', $id)->update ($data); }/** * @param $id * @return Mixed * * Public function Delete ($id) {return $this->model->de    Stroy ($id); }/** * @param $id * @param array $columns * @return Mixed */Public function find ($id, $columns = a        Rray (' * ')) {$this->applycriteria ();    return $this->model->find ($id, $columns); }/** * @param $attribute * @param $value * @param array $columns * @return Mixed */public func        tion FindBy ($attribute, $value, $columns = Array (' * ')) {$this->applycriteria ();    return $this->model->where ($attribute, ' = ', $value)->first ($columns); }/** * @return \illuminate\database\eloquent\builder * @throws repositoryexception * * Public Function M        Akemodel () {$model = $this->app->make ($this->model ()); if (! $model instanceof model) tHrow New Repositoryexception ("Class {$this->model ()} must be-an instance of Illuminate\\database\\eloquent\\model");    return $this->model = $model->newquery ();        }/** * @return $this */Public Function Resetscope () {$this->skipcriteria (false);    return $this; }/** * @param bool $status * @return $this */Public Function Skipcriteria ($status = True) {$thi        S->skipcriteria = $status;    return $this;    }/** * @return mixed */Public Function Getcriteria () {return $this->criteria;         }/** * @param criteria $criteria * @return $this */Public function Getbycriteria (criteria $criteria) {        $this->model = $criteria->apply ($this->model, $this);    return $this;        }/** * @param criteria $criteria * @return $this */Public function Pushcriteria (criteria $criteria) {        $this->criteria->push ($criteria); return $this;    }/** * @return $this */Public Function Applycriteria () {if ($this->skipcriteria = = = True)        return $this; foreach ($this->getcriteria () as $criteria) {if ($criteria instanceof criteria) $this->mode        L = $criteria->apply ($this->model, $this);    } return $this; }}

Create a new Criteria

With the Criteria query, you can now organize the Repository code more simply:

You can define a Criteria class like this:

 
      Where (' length ', ' > ', +);        return $query;    }}

Using Criteria in a controller

Now that we've defined some simple Criteria, let's take a look at how to use them. There are two ways to use Criteria in Repository, the first of which is to use the Pushcriteria method:

 
      Film = $film;    }    Public Function Index () {        $this->film->pushcriteria (New Lengthovertwohours ());        Return \response::json ($this->film->all ());}    }

This method is useful when you need multiple Criteria. However, if you want to use only one Criteria, you can use the Getbycriteria () method:

 
      Film = $film;    }    Public Function Index () {        $criteria = new Lengthovertwohours ();        Return \response::json ($this->film->getbycriteria ($criteria)->all ());}    }

6. Install dependent packages

The Repository implementation mentioned in this tutorial has a corresponding expansion pack on GitHub: Https://github.com/Bosnadev/Repositories.

You can add this line of dependency by Composer.json in the project root directory:

"Bosnadev/repositories": "0.*"

Then run Composer update to install the Repository package.

  • Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.