foothing / laravel-repository
This package provides an implementation of the repository pattern for Laravel 5.
Installs: 2 292
Dependents: 6
Suggesters: 0
Security: 0
Stars: 4
Watchers: 4
Forks: 0
Open Issues: 1
Requires
- php: >=5.4.0
- illuminate/database: ~5.0
- illuminate/support: ~5.0
Requires (Dev)
- codeclimate/php-test-reporter: dev-master
- mockery/mockery: 0.9.*
- orchestra/testbench: ~3.0
- phpunit/phpunit: 4.*
This package is auto-updated.
Last update: 2024-10-23 00:06:45 UTC
README
This package provides an implementation of the repository pattern for Laravel 5.
Composer installation
composer require foothing/laravel-repository
Basic usage
This package ships with a repository interface definition and
an Eloquent
implementation.
The EloquentRepository
is ready to use.
// Make a repository instance manually $repository = new EloquentRepository(new Post());
You are now ready to go:
// Find first occurrence with matching field 'email' $repository->findOneBy('email', 'test@example.com'); // Find first occurrence where id > 1 $repository->findOneBy('id', '1', '>'); // Find where title starts with 'foo' $repository->findAllBy('title', 'foo%', 'like'); // Find all occurrences where id > 1 $repository->findAllBy('id', '1', '>'); // Returns all posts $repository->all(); // Returns all post with Laravel pagination format $repository->paginate(); // Return record count $repository->filter('name', 'Homer')->count(); // Create, update and delete instances. $model = new Post(Input::all()); $freshMoel = $repository->create($model); $updatedModel = $repository->update($model); $repository->delete($model);
If you want to extend the base implementation with
your custom methods all you need to do is extend the
base class and define a constructor with the Eloquent
model as the first argument.
class PostsRepository extends EloquentRepository { function __construct(Post $post) { // Call superclass constructor. parent::construct($post); } function customFancyMethod() { } }
Don't forget to call the superclass constructor in order to enable all the features that are delivered out of the box. You can also add additional dependencies while overriding the base constructor.
class PostsRepository extends AbstractEloquentRepository { function __construct(Post $post, AnotherDependency $dependency) { parent::construct($post); // Do whatever you like with your $dependency } }
and you are all set. Examples:
// Use with a dependency injector class PostsController { protected $posts; function __construct(PostsRepository $posts) { $this->posts = $posts; } function getIndex($id = null) { // Return one post by primary key return $this->posts->find($id); } }
Eager load relations
You can eager load relations using the with
method:
$posts = $repository->with(['author', 'comments'])->all(); Output: [ { "id": 1, "title": "Post title", "author": { "id": 1, "email": "test@example.com" }, "comments": [ {"id": 1, "text": "a comment"}, {"id": 2, "text": "another comment"}, ], } ]
You can chain the with
method to all available methods:
$posts = $repository->with(['author', 'comments'])->findOneBy('name', 'foo'); $posts = $repository->with(['author', 'comments'])->all(); // And so on.
You'll often want to eager load some relations by default. Let's assume you have an User model with a many-to-many relation with its Role models, and you want to fetch both the user info and the roles.
The Foothing\Resources\Resource
interface has three methods
which define the default Model behaviour:
- which relations to eager load by default for a findOne operation
- which relations to eager load by default for a findMany operation
- which fields to skip upon save
class User extends Model implements Foothing\Resources\Resource { /** * Array of relations we want to eager-load when * a single entity is being fetched. * * @return array */ function unitRelations() { return ['roles', 'posts']; } /** * Array of relations we want to eager-load when * a list of entities is being fetched. * * @return array */ function listRelations() { return ['roles']; } /** * When the resource is sent in a json-encoded * format it may happen to have relations fields * populated. Since they would be set as stdClass * objects we need to unset them before save. * * This method should return an array with all relations * we want to be unset when processing the updates. * * @return array */ function skipOnSave() { return ['roles', 'posts']; } }
In this way, each time you will use your UserRepository the following would be the default behaviour:
$users->find($id); // will eager load *roles* and *posts* $users->findOneBy($id); // will eager load *roles* and *posts* $users->create(); // will eager load *roles* and *posts* $users->update(); // will eager load *roles* and *posts* $users->all(); // will eager load *roles* $users->paginate(); // will eager load *roles*
This default behaviour will be ignored if you use the with
method explicitly.
The skipOnSave
method comes handy when dealing with JSON object, since
when you read a resource, then process it on the client side, then
send it back to the server, it will contain the relation properties.
This properties will be converted to stdClass PHP object breaking the
Eloquent save method.
Criteria
You can apply filters and order constraints using the CriteriaInterface
,
which also ships with an Eloquent
implementation.
Usage:
$criteria = new \Foothing\Repository\Eloquent\EloquentCriteria(); $criteria->filter('name', 'Homer'); $criteria->filter('lastName', 'Simpson'); // Chain methods $criteria->order('name')->sort('asc'); $repository->criteria($criteria)->paginate();
The repository provides shortcut methods you can use in your chains. The following example produces the same effect as the previous one.
$repository ->filter('name', 'Homer') ->filter('lastName', 'Simpson') ->order('name') ->sort('asc') ->paginate();
Available filters:
// Defaults to '=' $criteria->filter('name', 'Homer'); $criteria->filter('name', 'Home%', 'like'); $criteria->filter('id', 1, '>'); $criteria->filter('id', 1', '>='); $criteria->filter('id', 1, '<'); $criteria->filter('id', 1, '<='); $criteria->filter('id', 1, '!='); $criteria->filter('lastName', 'Simpson,Nahasapeemapetilon', 'in');
You can use the in operator with a comma separated list of values or with an array of values.
$criteria->filter('lastName', 'Simpson,Nahasapeemapetilon', 'in'); $criteria->filter('lastName', ['Simpson', 'Nahasapeemapetilon'], 'in');
Nested filters
It's also possible to query a Model relation. Assume your
Eloquent Model defines a children
relation like so:
class Foo extends Model { protected $table = 'foo'; public function children() { return $this->hasMany('Bar'); } }
In this case you'll be allowed to query children
values:
$criteria->filter('children.name', 'Bar');
Keep in mind that the methods subject to criteria restrictions are all those fetching more than one record.
Attach and detach many-to-many
Given the following Model
:
class Homer extends Model { public function roles() { return $this->hasMany('Role'); } }
you can use the attach()
and detach()
methods to attach and detach to / from relation.
$homer = Homer::find(1); $foodCritic = Role::find(1); $repository->attach($homer, 'roles', $foodCritic); $repository->detach($homer, 'roles', $foodCritic);
Scopes
You can plug-in your Model scopes using the scope()
method.
// Model class Person extends Model { public function scopeMale($query) { return $query->where('sex', 'male'); } } // Usage in Repository $malePeople = $repository->scope('male')->all(); $malePeople = $repository->scope('male')->filter('name', 'Bart')->all(); $malePeople = $repository->scope('male')->paginate(); $malePeople = $repository->scope('male')->findAllBy('name', 'John');
Scopes only apply on read methods.
Global Scopes
You can define a global scope for each repository implementation, which might come handy to restrict records access globally.
Just set
protected $globalScope = 'whatever';
in your repository. The global scope must match an Eloquent scope.
Note that if you use explicit scopes, i.e. $repository->scope('male')
,
your global scope will be ignored.
Repository API
<?php namespace Foothing\Repository; interface RepositoryInterface { // // // Crud. // // public function find($id); public function findOneBy($field, $arg1, $arg2 = null); public function findAllBy($field, $arg1, $arg2 = null); public function all(); public function paginate($limit = null, $offset = null); public function count(); public function create($entity); public function update($entity); public function delete($entity); // // // Eager loading. // // public function with(array $relations); // // // Relations. // // /** * Attach $relatedEntity and $entity in a many-to-many relation. * * @param Model $entity * @param string $relation * @param Model $relatedEntity * * @return Model the updated $entity */ public function attach($entity, $relation, $relatedEntity); /** * Detach $entity and $relatedEntity in a many-to-many relation. * * @param Model $entity * @param string $relation * @param Model $relatedEntity * * @return Model the updated $entity */ public function detach($entity, $relation, $relatedEntity); // // // Criteria shortcuts. // // public function criteria(CriteriaInterface $criteria); public function filter($field, $value, $operator = '='); public function order($field, $sort = null); public function sort($direction); // // // Scopes. // // public function scope($scope); // // // Helpers. // // /** * Forces the next read query to skip cached values. * @return self */ public function refresh(); /** * Reset the refresh flag. * @return self */ public function reset(); }
RemoteQuery
More info coming soon.