Skip to content

Instantly share code, notes, and snippets.

@kevyworks
Last active June 24, 2018 23:41
Show Gist options
  • Save kevyworks/9ef3e50d01584ecf6e682266c51971f2 to your computer and use it in GitHub Desktop.
Save kevyworks/9ef3e50d01584ecf6e682266c51971f2 to your computer and use it in GitHub Desktop.
Laravel 5.6 - Repository
<?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);
}
<?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;
}
}
<?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');
}
<?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);
}
}
<?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