Last active
June 24, 2018 23:41
-
-
Save kevyworks/9ef3e50d01584ecf6e682266c51971f2 to your computer and use it in GitHub Desktop.
Laravel 5.6 - Repository
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Support\Repository\Contracts; | |
interface CriteriaInterface | |
{ | |
/** | |
* The criteria to be applied must go inside this method. | |
* | |
* @param mixed $queryBuilder Current query builder. | |
* | |
* @return mixed $queryBuilder Current instance of the query builder with the criteria appplied. | |
*/ | |
public function apply($queryBuilder); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Support\Repository; | |
use App\Support\Repository\Contracts\CriteriaInterface; | |
use App\Support\Repository\Contracts\RepositoryInterface; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Http\JsonResponse; | |
use Illuminate\Pagination\AbstractPaginator; | |
use Illuminate\Pagination\Paginator; | |
use Illuminate\Support\Collection; | |
use Illuminate\Support\Facades\Validator; | |
use Illuminate\Validation\ValidationException; | |
use ReflectionClass; | |
abstract class EloquentRepository implements RepositoryInterface | |
{ | |
/** | |
* @var \Illuminate\Database\Eloquent\Model|\Illuminate\Database\Eloquent\Builder | |
*/ | |
protected $model; | |
/** | |
* @var string|array | |
*/ | |
protected $with; | |
/** | |
* @var bool | |
*/ | |
protected $skipCriteria; | |
/** | |
* @var string | |
*/ | |
protected $resource; | |
/** | |
* @var array | |
*/ | |
protected $vRules; | |
/** | |
* @var array | |
*/ | |
protected $vMessages = []; | |
/** | |
* @var array | |
*/ | |
protected $vCustomAttributes = []; | |
/** | |
* @var array | |
*/ | |
protected $criteria; | |
/** | |
* @var string | |
*/ | |
private $modelClassName; | |
/** | |
* EloquentRepository constructor. | |
* | |
* @param \Illuminate\Database\Eloquent\Model $model | |
* | |
* @throws \ReflectionException | |
*/ | |
public function __construct(Model $model) | |
{ | |
$this->model = $model; | |
// A clean copy of the model is needed when the scope needs to be reset. | |
$reflex = new ReflectionClass($model); | |
$this->modelClassName = $reflex->getName(); | |
$this->skipCriteria = false; | |
$this->criteria = []; | |
} | |
/** | |
* Set the data transformer | |
* | |
* @param string $resource The resource class name | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function setResource($resource) | |
{ | |
if (is_object($resource)) { | |
$resource = get_class($resource); | |
} | |
$this->resource = $resource; | |
return $this; | |
} | |
/** | |
* Set validations | |
* | |
* @param array $rules | |
* @param array $messages | |
* @param array $customAttributes | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function setValidator(array $rules, array $messages = [], array $customAttributes = []) | |
{ | |
$this->vRules = $rules; | |
$this->vMessages = $messages; | |
$this->vCustomAttributes = $customAttributes; | |
return $this; | |
} | |
/** | |
* Finds one item by the provided field. | |
* | |
* @param mixed $value mixed Value used for the filter. If NULL passed then it will take ONLY the criteria. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* @param array $columns Columns to retrieve with the object. | |
* | |
* @return mixed|JsonResponse Model|NULL An Eloquent object when there is a result, | |
* NULL when there are no matches. | |
*/ | |
public function findOneBy($value = null, $field = 'id', array $columns = ['*']) | |
{ | |
$this->eagerLoadRelations(); | |
$this->applyCriteria(); | |
if (! is_null($value)) { | |
$this->model = $this->model->where($field, $value); | |
} | |
$result = $this->model->first($columns); | |
$this->resetScope(); | |
return $this->respondWithResource($result); | |
} | |
/** | |
* Eager Load Relations | |
*/ | |
protected function eagerLoadRelations() | |
{ | |
if (is_array($this->with)) { | |
$this->model = $this->model->with($this->with); | |
} | |
} | |
/** | |
* Apply Criteria | |
* | |
* @return $this | |
*/ | |
private function applyCriteria() | |
{ | |
if (! $this->skipCriteria) { | |
foreach ($this->criteria as $criteria) { | |
if ($criteria instanceof CriteriaInterface) { | |
$this->model = $criteria->apply($this->model); | |
} | |
} | |
} | |
return $this; | |
} | |
/** | |
* Resets the current scope of the repository. That is: clean the criteria, and all other properties that could have | |
* been modified, like current page, etc. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function resetScope() | |
{ | |
$this->vRules = null; | |
$this->vMessages = []; | |
$this->vCustomAttributes = []; | |
$this->criteria = []; | |
$this->skipCriteria(false); | |
$this->model = new $this->modelClassName(); | |
return $this; | |
} | |
/** | |
* Skips the current criteria (all of them). Useful when you don't want to reset the object but just not use the | |
* filters applied so far. | |
* | |
* @param bool|TRUE $status If you want to skip the criteria or not. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function skipCriteria($status = true) | |
{ | |
$this->skipCriteria = $status; | |
return $this; | |
} | |
/** | |
* Check if resource is set for data response. | |
* | |
* @param mixed $result | |
* | |
* @return mixed | |
*/ | |
public function respondWithResource($result) | |
{ | |
if ($this->resource && $result) { | |
if ($result instanceof AbstractPaginator || $result instanceof Collection) { | |
$result = call_user_func([$this->resource, 'collection'], $result); | |
} else { | |
$result = new $this->resource($result); | |
} | |
} | |
return $result; | |
} | |
/** | |
* Finds ALL items by the provided field. If NULL specified for the first 2 parameters, then it will take ONLY the | |
* criteria. | |
* | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return mixed|JsonResponse Collection Laravel Eloquent's Collection that may or may not be empty. | |
*/ | |
public function findAllBy($value = null, $field = null, array $columns = ['*']) | |
{ | |
$this->eagerLoadRelations(); | |
$this->applyCriteria(); | |
if (! is_null($value) && ! is_null($field)) { | |
$this->model = $this->model->where($field, $value); | |
} | |
$result = $this->model->get($columns); | |
$this->resetScope(); | |
return $this->respondWithResource($result); | |
} | |
/** | |
* Finds ALL the items in the repository where the given field is inside the given values. | |
* | |
* @param array $value mixed Array of values used for the filter. | |
* @param string $field Field on the database that you will filter by. | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return mixed|JsonResponse Collection Laravel Eloquent's Collection that may or may not be empty. | |
*/ | |
public function findAllWhereIn(array $value, $field, array $columns = ['*']) | |
{ | |
$this->eagerLoadRelations(); | |
$this->applyCriteria(); | |
$result = $this->model->whereIn($field, $value)->get($columns); | |
$this->resetScope(); | |
return $this->respondWithResource($result); | |
} | |
/** | |
* Finds ALL items the repository abstract without any kind of filter. | |
* | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return mixed|JsonResponse Collection Laravel Eloquent's Collection that may or may not be empty. | |
*/ | |
public function findAll(array $columns = ['*']) | |
{ | |
$this->eagerLoadRelations(); | |
$result = $this->model->all($columns); | |
$this->resetScope(); | |
return $this->respondWithResource($result); | |
} | |
/** | |
* Allows you to eager-load entity relationships when retrieving entities, either with or without criterias. | |
* | |
* @param array|string $relations Relations to eager-load along with the entities. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function with($relations) | |
{ | |
if (is_string($relations)) { | |
$relations = func_get_args(); | |
} | |
$this->with = $relations; | |
return $this; | |
} | |
/** | |
* Adds a criteria to the query. | |
* | |
* @param CriteriaInterface $criteria Object that declares and implements the criteria used. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function addCriteria(CriteriaInterface $criteria) | |
{ | |
$this->criteria[] = $criteria; | |
return $this; | |
} | |
/** | |
* Returns a Paginator that based on the criteria or filters given. | |
* | |
* @param int $perPage Number of results to return per page. | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return Paginator|JsonResponse object with the results and the paginator. | |
*/ | |
public function paginate($perPage, array $columns = ['*']) | |
{ | |
$this->eagerLoadRelations(); | |
$this->applyCriteria(); | |
$result = $this->model->paginate($perPage, $columns); | |
$result->appends(request()->query()); | |
$this->resetScope(); | |
return $this->respondWithResource($result); | |
} | |
/** | |
* Allows you to set the current page with using the paginator. Useful when you want to overwrite the $_GET['page'] | |
* parameter and retrieve a specific page directly without using HTTP. | |
* | |
* @param int $page The page you want to retrieve. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function setCurrentPage($page) | |
{ | |
Paginator::currentPageResolver(function () use ($page) { | |
return $page; | |
}); | |
return $this; | |
} | |
/** | |
* Creates a new entity of the entity type the repository handles, given certain data. | |
* | |
* @param array $data Data the entity will have. | |
* | |
* @return mixed Model|NULL An Eloquent object when the entity was created, NULL in case of error. | |
* @throws ValidationException | |
*/ | |
public function create(array $data) | |
{ | |
$cleanFields = $this->cleanUnfillableFields($data); | |
$this->validate($cleanFields); | |
$createdObject = $this->model->create($cleanFields); | |
$this->resetScope(); | |
return $createdObject; | |
} | |
/** | |
* Removes/Unset fields that are not fillable | |
* | |
* @param array $data | |
* | |
* @return array | |
*/ | |
private function cleanUnfillableFields(array $data) | |
{ | |
$fillableFields = $this->model->getFillable(); | |
foreach ($data as $key => $value) { | |
if (! in_array($key, $fillableFields)) { | |
unset($data[$key]); | |
} | |
} | |
return $data; | |
} | |
/** | |
* Validate | |
* | |
* @param array $data | |
* | |
* @throws ValidationException | |
*/ | |
private function validate(array $data) | |
{ | |
if (! empty($this->vRules)) { | |
$validator = Validator::make($data, $this->vRules, $this->vMessages, $this->vCustomAttributes); | |
if ($validator->fails()) { | |
throw new ValidationException($validator, new JsonResponse($validator->errors()->getMessages(), 422)); | |
} | |
} | |
} | |
/** | |
* Updates as many entities as the filter matches with the given $data. | |
* | |
* @param array $data Fields & new values to be updated on the entity/entities. | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* | |
* @return mixed Model|NULL|integer An Eloquent object representing the updated entity, a number of entities | |
* updated if mass updating, or NULL in case of error. | |
* @throws ValidationException | |
*/ | |
public function updateBy(array $data, $value = null, $field = 'id') | |
{ | |
$cleanFields = $this->cleanUnfillableFields($data); | |
$this->validate($cleanFields); | |
if (! is_null($value)) { | |
// Single update. | |
$this->model->where($field, $value)->update($cleanFields); | |
foreach ($cleanFields as $F => $V) { | |
$this->model->{$F} = $V; | |
} | |
$returnedVal = $this->model; | |
} else { | |
// Mass update. | |
$this->applyCriteria(); | |
$returnedVal = $this->model->update($cleanFields); | |
} | |
$this->resetScope(); | |
return $this->respondWithResource($returnedVal); | |
} | |
/** | |
* Removes as many entities as the filter matches. If softdelete is applied, then they will be soft-deleted. | |
* Criteria is applied as well, so please be careful with it. | |
* | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* | |
* @return boolean TRUE It will always return TRUE. | |
* @throws \Exception | |
*/ | |
public function delete($value = null, $field = 'id') | |
{ | |
$this->applyCriteria(); | |
if (! is_null($value)) { | |
$result = $this->model->where($field, $value)->delete(); | |
} else { | |
if (! empty($this->criteria)) { | |
$result = $this->model->delete(); | |
} else { | |
$result = false; | |
} | |
} | |
$this->resetScope(); | |
return (bool)$result; | |
} | |
/** | |
* @return int number of records matching the criteria (or total amount of records). | |
*/ | |
public function count() | |
{ | |
$this->applyCriteria(); | |
$result = $this->model->count(); | |
$this->resetScope(); | |
return $result; | |
} | |
/** | |
* Permanently removes a record (or set of records) from the database. | |
* Criteria is applied as well, so please be careful with it. | |
* | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. | |
* | |
* @return mixed | |
*/ | |
public function destroy($value = null, $field = 'id') | |
{ | |
$this->applyCriteria(); | |
if (! is_null($value)) { | |
$result = $this->model->where($field, $value)->forceDelete(); | |
} else { | |
if (! empty($this->criteria)) { | |
$result = $this->model->forceDelete(); | |
} else { | |
$result = false; | |
} | |
} | |
$this->resetScope(); | |
return (bool)$result; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Support\Repository\Contracts; | |
use Illuminate\Http\JsonResponse; | |
use Illuminate\Pagination\Paginator; | |
interface RepositoryInterface | |
{ | |
/** | |
* Finds one item by the provided field. | |
* | |
* @param mixed $value mixed Value used for the filter. If NULL passed then it will take ONLY the criteria. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* @param array $columns Columns to retrieve with the object. | |
* | |
* @return mixed|JsonResponse Model|NULL An Eloquent object when there is a result, | |
* NULL when there are no matches. | |
*/ | |
public function findOneBy($value = null, $field = 'id', array $columns = ['*']); | |
/** | |
* Finds ALL items the repository abstract without any kind of filter. | |
* | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return mixed|JsonResponse Collection Laravel Eloquent's Collection that may or may not be empty. | |
*/ | |
public function findAll(array $columns = ['*']); | |
/** | |
* Finds ALL items by the provided field. If NULL specified for the first 2 parameters, then it will take ONLY the | |
* criteria. | |
* | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return mixed|JsonResponse Collection Laravel Eloquent's Collection that may or may not be empty. | |
*/ | |
public function findAllBy($value = null, $field = null, array $columns = ['*']); | |
/** | |
* Finds ALL the items in the repository where the given field is inside the given values. | |
* | |
* @param array $value mixed Array of values used for the filter. | |
* @param string $field Field on the database that you will filter by. | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return mixed|JsonResponse Collection Laravel Eloquent's Collection that may or may not be empty. | |
*/ | |
public function findAllWhereIn(array $value, $field, array $columns = ['*']); | |
/** | |
* Allows you to eager-load entity relationships when retrieving entities, either with or without criterias. | |
* | |
* @param array|string $relations Relations to eager-load along with the entities. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function with($relations); | |
/** | |
* Adds a criteria to the query. | |
* | |
* @param CriteriaInterface $criteria Object that declares and implements the criteria used. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function addCriteria(CriteriaInterface $criteria); | |
/** | |
* Skips the current criteria (all of them). Useful when you don't want to reset the object but just not use the | |
* filters applied so far. | |
* | |
* @param bool|TRUE $status If you want to skip the criteria or not. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function skipCriteria($status = true); | |
/** | |
* Set the data transformer | |
* | |
* @param string $resource The resource class name | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function setResource($resource); | |
/** | |
* Set validations | |
* | |
* @param array $rules | |
* @param array $messages | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function setValidator(array $rules, array $messages = []); | |
/** | |
* Return a JsonResponse. | |
* | |
* @param mixed $result | |
* | |
* @return mixed | |
*/ | |
public function respondWithResource($result); | |
/** | |
* Returns a Paginator that based on the criteria or filters given. | |
* | |
* @param int $perPage Number of results to return per page. | |
* @param array $columns Columns to retrieve with the objects. | |
* | |
* @return Paginator|JsonResponse object with the results and the paginator. | |
*/ | |
public function paginate($perPage, array $columns = ['*']); | |
/** | |
* Allows you to set the current page with using the paginator. Useful when you want to overwrite the $_GET['page'] | |
* parameter and retrieve a specific page directly without using HTTP. | |
* | |
* @param int $page The page you want to retrieve. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function setCurrentPage($page); | |
/** | |
* Creates a new entity of the entity type the repository handles, given certain data. | |
* | |
* @param array $data Data the entity will have. | |
* | |
* @return mixed Model|NULL An Eloquent object when the entity was created, NULL in case of error. | |
*/ | |
public function create(array $data); | |
/** | |
* Updates as many entities as the filter matches with the given $data. | |
* | |
* @param array $data Fields & new values to be updated on the entity/entities. | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* | |
* @return mixed Model|NULL|integer An Eloquent object representing the updated entity, a number of entities | |
* updated if mass updating, or NULL in case of error. | |
*/ | |
public function updateBy(array $data, $value = null, $field = 'id'); | |
/** | |
* Removes as many entities as the filter matches. If softdelete is applied, then they will be soft-deleted. | |
* Criteria is applied as well, so please be careful with it. | |
* | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. Default: id. | |
* | |
* @return boolean TRUE It will always return TRUE. | |
* @throws \Exception | |
*/ | |
public function delete($value = null, $field = 'id'); | |
/** | |
* @return int number of records matching the criteria (or total amount of records). | |
*/ | |
public function count(); | |
/** | |
* Resets the current scope of the repository. That is: clean the criteria, and all other properties that could have | |
* been modified, like current page, etc. | |
* | |
* @return $this The current repository object instance. | |
*/ | |
public function resetScope(); | |
/** | |
* Permanently removes a record (or set of records) from the database. | |
* Criteria is applied as well, so please be careful with it. | |
* | |
* @param mixed $value mixed Value used for the filter. | |
* @param string $field Field on the database that you will filter by. | |
* | |
* @return mixed | |
*/ | |
public function destroy($value = null, $field = 'id'); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Controllers; | |
use App\Http\Resources\UserResource; | |
use App\Repositories\UserRepositoryInterface; | |
use Illuminate\Http\JsonResponse; | |
use Illuminate\Http\Request; | |
class UserController extends Controller | |
{ | |
/** | |
* @var \App\Repositories\UserRepositoryInterface | |
*/ | |
protected $usersRepository; | |
/** | |
* UserController constructor. | |
* | |
* @param \App\Repositories\UserRepositoryInterface $repository | |
*/ | |
public function __construct(UserRepositoryInterface $repository) | |
{ | |
$this->usersRepository = $repository | |
->setResource(UserResource::class); | |
} | |
/** | |
* @param \Illuminate\Http\Request $request | |
* | |
* @return \Illuminate\Http\JsonResponse | |
* @throws \Exception | |
*/ | |
public function index(Request $request) | |
{ | |
$result = $this | |
->usersRepository | |
->paginate((int)request('per_page', 15)); | |
return $result; | |
} | |
/** | |
* @param \Illuminate\Http\Request $request | |
* | |
* @return JsonResponse | |
* @throws \Exception | |
*/ | |
public function store(Request $request) | |
{ | |
$data = array_merge($request->all(), [ | |
'uid' => uuid(), | |
'password' => bcrypt($request->get('password')), | |
]); | |
$user = $this->usersRepository | |
->setValidator([ | |
'name' => 'required|unique:users,name', | |
'email' => 'required|email|unique:users,email', | |
'password' => 'required', | |
], [ | |
'required' => 'member `:attribute` is required.', | |
'unique' => "member `:attribute` is already taken.", | |
'email' => 'member `:attribute` is not a valid email address.', | |
]) | |
->create($data); | |
return $this->usersRepository->respondWithResource($user); | |
} | |
/** | |
* @param \Illuminate\Http\Request $request | |
* @param int $id | |
* | |
* @return \Illuminate\Http\JsonResponse | |
* @throws \Exception | |
*/ | |
public function show(Request $request, int $id) | |
{ | |
return $this | |
->usersRepository | |
->findOneBy($id); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Resources; | |
use Illuminate\Http\Resources\Json\Resource; | |
class UserResource extends Resource | |
{ | |
/** | |
* @inheritdoc | |
*/ | |
public function toArray($request) | |
{ | |
return [ | |
'type' => 'users', | |
'id' => (string)$this->id, | |
'attributes' => [ | |
'uid' => $this->uid, | |
'name' => $this->name, | |
'email' => $this->email, | |
], | |
'links' => [ | |
'self' => route('users.show', ['id' => $this->id]), | |
], | |
]; | |
} | |
/** | |
* @inheritdoc | |
*/ | |
public function with($request) | |
{ | |
return [ | |
'meta' => [ | |
'path' => route('users.index'), | |
], | |
]; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment