目录结构
- interface RepositoryInterface {
- public function all($columns = array('*'));
- public function paginate($perPage = 15, $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('*'));
- }
- {
- "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
- }
- <?php namespace Bosnadev\Repositories\Contracts;
- interface RepositoryInterface {
- ...
- }
现在我们开始实现接口
实现接口
使用仓库可以让我们查询数据, 建立业务实体和数据源之间的持续映射关系
当然, 每一个子仓库都应该继承这个抽象的仓库(实现了接口).
现在从接口第一个方法all()开始. 这个方法负责取出实体中的所有记录, 参数$columns是一个数组, 如果发送这个参数的话, 我们会取出$columns中指定的字段, 不传的话默认取出所有字段.
- public function all($columns = array('*')) {
- return $this->model->get($columns);
- }
然后我们设置$this->model属性为我们想要操作的数据对象, 来个抽象仓库类的完整案例:
- <?php namespace Bosnadev\Repositories\Eloquent;
- use Bosnadev\Repositories\Contracts\RepositoryInterface;
- use Bosnadev\Repositories\Exceptions\RepositoryException;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Container\Container as App;
- /**
- * Class Repository
- * @package Bosnadev\Repositories\Eloquent
- */
- abstract class Repository implements RepositoryInterface {
- /**
- * @var App
- */
- private $app;
- /**
- * @var
- */
- protected $model;
- /**
- * @param App $app
- * @throws \Bosnadev\Repositories\Exceptions\RepositoryException
- */
- public function __construct(App $app) {
- $this->app = $app;
- $this->makeModel();
- }
- /**
- * Specify Model class name
- *
- * @return mixed
- */
- abstract function model();
- /**
- * @return Model
- * @throws RepositoryException
- */
- public function makeModel() {
- $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;
- }
- }
我们定一个这个抽象类之后, 现在我们实现一个具体的仓库类, 只需要实现 model()方法就行了
- <?php namespace App\Repositories;
- use Bosnadev\Repositories\Contracts\RepositoryInterface;
- use Bosnadev\Repositories\Eloquent\Repository;
- class ActorRepository extends Repository {
- /**
- * Specify Model class name
- *
- * @return mixed
- */
- function model()
- {
- return 'Bosnadev\Models\Actor';
- }
- }
现在实现接口中的其他方法:
- <?php namespace Bosnadev\Repositories\Eloquent;
- use Bosnadev\Repositories\Contracts\RepositoryInterface;
- use Bosnadev\Repositories\Exceptions\RepositoryException;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Container\Container as App;
- /**
- * Class Repository
- * @package Bosnadev\Repositories\Eloquent
- */
- abstract class Repository implements RepositoryInterface {
- /**
- * @var App
- */
- private $app;
- /**
- * @var
- */
- protected $model;
- /**
- * @param App $app
- * @throws \Bosnadev\Repositories\Exceptions\RepositoryException
- */
- public function __construct(App $app) {
- $this->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($perPage = 15, $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 function 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->destroy($id);
- }
- /**
- * @param $id
- * @param array $columns
- * @return mixed
- */
- public function find($id, $columns = array('*')) {
- return $this->model->find($id, $columns);
- }
- /**
- * @param $attribute
- * @param $value
- * @param array $columns
- * @return mixed
- */
- public function findBy($attribute, $value, $columns = array('*')) {
- return $this->model->where($attribute, '=', $value)->first($columns);
- }
- /**
- * @return \Illuminate\Database\Eloquent\Builder
- * @throws RepositoryException
- */
- public function makeModel() {
- $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();
- }
- }
现在我们实现控制器试试
- <?php namespace App\Http\Controllers;
- use App\Repositories\ActorRepository as Actor;
- class ActorsController extends Controller {
- /**
- * @var Actor
- */
- private $actor;
- public function __construct(Actor $actor) {
- $this->actor = $actor;
- }
- public function index() {
- return \Response::json($this->actor->all());
- }
- }
标准查询方式
刚刚只是一些简单的操作, 在大型项目中, 我们有可能会定义一些其他的复杂操作.
为了实现这个, 我们要先顶一个抽象类, 用来扩展 repository 的查询能力.
- <?php namespace Bosnadev\Repositories\Criteria;
- use Bosnadev\Repositories\Contracts\RepositoryInterface as Repository;
- use Bosnadev\Repositories\Contracts\RepositoryInterface;
- abstract class Criteria {
- /**
- * @param $model
- * @param RepositoryInterface $repository
- * @return mixed
- */
- public abstract function apply($model, Repository $repository);
- }
这个方法是用来处理查询请求的, 现在我们要扩展 repository, 所以我们再定义一个接口文件:
- <?php namespace Bosnadev\Repositories\Contracts;
- use Bosnadev\Repositories\Criteria\Criteria;
- /**
- * Interface CriteriaInterface
- * @package Bosnadev\Repositories\Contracts
- */
- interface CriteriaInterface {
- /**
- * @param bool $status
- * @return $this
- */
- public function skipCriteria($status = true);
- /**
- * @return mixed
- */
- public function getCriteria();
- /**
- * @param Criteria $criteria
- * @return $this
- */
- public function getByCriteria(Criteria $criteria);
- /**
- * @param Criteria $criteria
- * @return $this
- */
- public function pushCriteria(Criteria $criteria);
- /**
- * @return $this
- */
- public function applyCriteria();
- }
现在让 repository 类实现这个接口:
- <?php namespace Bosnadev\Repositories\Eloquent;
- use Bosnadev\Repositories\Contracts\CriteriaInterface;
- use Bosnadev\Repositories\Criteria\Criteria;
- use Bosnadev\Repositories\Contracts\RepositoryInterface;
- use Bosnadev\Repositories\Exceptions\RepositoryException;
- use Illuminate\Database\Eloquent\Model;
- use Illuminate\Support\Collection;
- use Illuminate\Container\Container as App;
- /**
- * Class Repository
- * @package Bosnadev\Repositories\Eloquent
- */
- abstract class Repository implements RepositoryInterface, CriteriaInterface {
- /**
- * @var App
- */
- private $app;
- /**
- * @var
- */
- protected $model;
- /**
- * @var Collection
- */
- protected $criteria;
- /**
- * @var bool
- */
- protected $skipCriteria = false;
- /**
- * @param App $app
- * @param Collection $collection
- * @throws \Bosnadev\Repositories\Exceptions\RepositoryException
- */
- public function __construct(App $app, Collection $collection) {
- $this->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->applyCriteria();
- return $this->model->get($columns);
- }
- /**
- * @param int $perPage
- * @param array $columns
- * @return mixed
- */
- public function paginate($perPage = 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 function 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->destroy($id);
- }
- /**
- * @param $id
- * @param array $columns
- * @return mixed
- */
- public function find($id, $columns = array('*')) {
- $this->applyCriteria();
- return $this->model->find($id, $columns);
- }
- /**
- * @param $attribute
- * @param $value
- * @param array $columns
- * @return mixed
- */
- public function findBy($attribute, $value, $columns = array('*')) {
- $this->applyCriteria();
- return $this->model->where($attribute, '=', $value)->first($columns);
- }
- /**
- * @return \Illuminate\Database\Eloquent\Builder
- * @throws RepositoryException
- */
- public function makeModel() {
- $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){
- $this->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->model = $criteria->apply($this->model, $this);
- }
- return $this;
- }
- }
添加一些查询规则
通过添加的标准查询, 我们能更容易的组织我们的代码, repository 也不至于会有一堆臃肿的代码
添加的查询规则文件
- <?php namespace App\Repositories\Criteria\Films;
- use Bosnadev\Repositories\Contracts\CriteriaInterface;
- use Bosnadev\Repositories\Contracts\RepositoryInterface as Repository;
- use Bosnadev\Repositories\Contracts\RepositoryInterface;
- class LengthOverTwoHours implements CriteriaInterface {
- /**
- * @param $model
- * @param RepositoryInterface $repository
- * @return mixed
- */
- public function apply($model, Repository $repository)
- {
- $query = $model->where('length', '>', 120);
- return $query;
- }
- }
在控制器中使用这些标准查询
有两种方式使用, 第一种:
- <?php namespace App\Http\Controllers;
- use App\Repositories\Criteria\Films\LengthOverTwoHours;
- use App\Repositories\FilmRepository as Film;
- class FilmsController extends Controller {
- /**
- * @var Film
- */
- private $film;
- public function __construct(Film $film) {
- $this->film = $film;
- }
- public function index() {
- $this->film->pushCriteria(new LengthOverTwoHours());
- return \Response::json($this->film->all());
- }
- }
使用这种方法你能添加任意数量的标准查询规则, 但是如果你只想添加一个, 那么你可以使用这个方法getByCriteria():
- <?php namespace App\Http\Controllers;
- use App\Repositories\Criteria\Films\LengthOverTwoHours;
- use App\Repositories\FilmRepository as Film;
- class FilmsController extends Controller {
- /**
- * @var Film
- */
- private $film;
- public function __construct(Film $film) {
- $this->film = $film;
- }
- public function index() {
- $criteria = new LengthOverTwoHours();
- return \Response::json($this->film->getByCriteria($criteria)->all());
- }
- }
安装这个包
在你的composer.json 文件中添加"bosnadev/repositories": "0.*", 然后运行composer update.
总结
在应用中使用 repository 设计模式有很多好处, 最基本的就是能减少你的代码量, 是你的代码更容易维护, 测试和扩展.
从程序的设计架构角度来说, 你的控制器不需要知道你在哪儿存储的数据, 这些数据怎么来的, 这样是非常漂亮的结构.
有一个 laravel 实现的包推荐一下, 可能大多数人都知道吧
https://github.com/andersao/l5-repository