Last active
October 15, 2018 20:04
-
-
Save fenos/c1634f39ca61da26e6c0 to your computer and use it in GitHub Desktop.
Custom Implementation of an Abstract RESTfull Repositories with Laravel.
This file contains hidden or 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 | |
interface FilterableInterface { | |
} |
This file contains hidden or 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
/** | |
* | |
* Implementation of RestApi Abstract Class | |
* on this Example repository | |
*/ | |
<?php | |
use User; | |
/** | |
* Class ImplementationRest | |
*/ | |
class ImplementationRest extends RestApi { | |
/** | |
* @var | |
*/ | |
protected $user; | |
function __construct(User $user) | |
{ | |
$this->user = $user; | |
} | |
/** | |
* @return mixed | |
*/ | |
protected function model() | |
{ | |
return $this->user; | |
} | |
/** | |
* Profile Filter | |
* | |
* @param $query | |
* @param RestData $data | |
* @return mixed | |
*/ | |
public function profileFilter($query, RestData $data) | |
{ | |
return $query->with('profile')->where('users.id',$data->user_id); | |
} | |
/** | |
* Only users who has got cars | |
* | |
* @param $query | |
* @param RestData $data | |
* @return mixed | |
*/ | |
public function carFilter($query,RestData $data) | |
{ | |
return $query->has('cars'); | |
} | |
} |
This file contains hidden or 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
/** | |
* Scenario -> | |
* Given the following url: http://localhost.dev/user/1?filters=profile,cars | |
* You'll be able to request the user with ID 1 information applying the 2 following filters | |
* profile and cars | |
*--------------------------------------------------------------------------------------------- | |
* | |
* Abstract class that will extends your Repository | |
*/ | |
<?php | |
/** | |
* Class RestApi | |
* Extend this class to a repository | |
*/ | |
abstract class RestApi implements FilterableInterface { | |
/** | |
* @var array | |
*/ | |
protected $filters; | |
/** | |
* @var array | |
*/ | |
protected $currentSort = array('created_at', 'desc'); | |
/** | |
* @var int | |
*/ | |
protected $perPage = 20; | |
/** | |
* @var int | |
*/ | |
protected $limit; | |
/** | |
* @var array | |
*/ | |
protected $eagerLoading = []; | |
/** | |
* @return mixed | |
*/ | |
abstract protected function model(); | |
/** | |
* Return a new query | |
* | |
* @return mixed | |
*/ | |
protected function query() | |
{ | |
return $this->model()->newQuery(); | |
} | |
/** | |
* Return a item of a query | |
* generated via REST | |
* | |
* @param array $data | |
* @return | |
*/ | |
public function item($data) | |
{ | |
return $this->applyFilter($this->query(),$data)->first(); | |
} | |
/** | |
* Return a collection of a query | |
* generated via REST | |
* | |
* @param array $data | |
*/ | |
public function collection($data) | |
{ | |
return $this->applyFilter($this->query(),$data)->get(); | |
} | |
/** | |
* Create a pagination | |
* | |
* @param array $data | |
*/ | |
public function paginate($data) | |
{ | |
return $this->applyFilter($this->query(),$data)->paginate($this->perPage); | |
} | |
/** | |
* Create a new Record | |
* | |
* @param array $data | |
* @return mixed | |
*/ | |
public function create(array $data) | |
{ | |
return $this->model()->create($data); | |
} | |
/** | |
* Delete a record applying | |
* filters | |
* | |
* @param $data | |
* @return mixed | |
*/ | |
public function delete($data) | |
{ | |
return $this->applyFilter($this->query(),$data)->delete(); | |
} | |
/** | |
* Update record filtering | |
* | |
* @param $data | |
* @param $update | |
* @return mixed | |
*/ | |
public function update($data,$update) | |
{ | |
return $this->applyFilter($this->query(),$data)->update($update); | |
} | |
/** | |
* Sort By Field and direction | |
* | |
* @param $field | |
* @param string $direction | |
* @return $this | |
*/ | |
public function sortBy($field,$direction = 'DESC') | |
{ | |
$direction = $direction ?: 'DESC'; | |
$direction = (strtoupper($direction) == 'ASC') ? 'ASC' : 'DESC'; | |
$field = $field ?: 'created_at'; | |
$this->currentSort = array($field, $direction); | |
return $this; | |
} | |
public function limit($limit) { | |
$this->limit = $limit; | |
return $this; | |
} | |
/** | |
* Apply Filter Accordently | |
* | |
* @param $model | |
* @param $data | |
* @return mixed | |
*/ | |
public function applyFilter($model,$data) | |
{ | |
$model = $this->mapFilters($model,$data); | |
// Order By | |
list($sortField, $sortDir) = $this->currentSort; | |
$model->orderBy($sortField,$sortDir); | |
if (! is_null($this->limit)) | |
{ | |
$model->limit($this->limit); | |
} | |
// Check if some eager loading haas been required | |
if (count($this->eagerLoading) > 0) | |
{ | |
$model->with($this->eagerLoading); | |
} | |
return $model; | |
} | |
/** | |
* Set eager Loading | |
* | |
* @return mixed | |
*/ | |
public function with() | |
{ | |
$parameters = func_get_args(); | |
$parsed = ( is_array($parameters[0])) ? $parameters[0] : $parameters; | |
$this->eagerLoading = array_merge($this->eagerLoading,$parsed); | |
return $this; | |
} | |
/** | |
* @param $model | |
* @param $data | |
* @return mixed | |
*/ | |
protected function mapFilters($model, $data) | |
{ | |
// prepare data to pass | |
$restData = $this->getRestData(); | |
$restData->pushData($data); | |
return $this->callFilterMethod( | |
$model, | |
$restData->requestedFilters($this), | |
$restData | |
); | |
return $model; | |
} | |
/** | |
* @param $model | |
* @param $neededFilters | |
* @param $restData | |
* @return mixed | |
*/ | |
protected function callFilterMethod($model, $neededFilters, $restData) | |
{ | |
// Apply all the filters to the query | |
foreach ( $neededFilters as $key => $filter ) | |
{ | |
$method = $key; | |
$model = $this->{$method}($model, $restData); | |
} | |
return $model; | |
} | |
/** | |
* @return RestData | |
*/ | |
protected function getRestData() | |
{ | |
$restData = app('rest.data'); | |
return $restData; | |
} | |
} |
This file contains hidden or 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
// | |
// Container of your filters, and values | |
// | |
<?php | |
use RestMapperException; | |
/** | |
* Class RestData | |
*/ | |
class RestData { | |
/** | |
* @var array | |
*/ | |
protected $filters = []; | |
/** | |
* @var array | |
*/ | |
protected $data = []; | |
/** | |
* @param $filters | |
*/ | |
function __construct($filters) | |
{ | |
$this->filters = $filters; | |
} | |
/** | |
* Parse the requested filters | |
* | |
* @param FilterableInterface $filterableInterface | |
* @return array | |
*/ | |
public function requestedFilters(FilterableInterface $filterableInterface) | |
{ | |
// parse filters followed by (,) | |
// example: filters=profile,tasks,cars | |
$this->filters = (count($this->filters) > 0) | |
? preg_split('/\s?,\s/', $this->filters) | |
: $this->filters; | |
$neededFilters = []; | |
// Set callables filters | |
foreach ( $this->filters as $filter ) | |
{ | |
$method = $this->getFilterMethod($filter); | |
if (method_exists($filterableInterface,$method)) | |
{ | |
$neededFilters[$method] = $filter; | |
} | |
} | |
return $neededFilters; | |
} | |
/** | |
* Get Value | |
* | |
* @param $name | |
* @return mixed | |
* @throws RestMapperException | |
*/ | |
public function get($name) | |
{ | |
if (array_key_exists($name,$this->data)) | |
{ | |
return $this->data[$name]; | |
} | |
$error = "The query has [{$name}] value required"; | |
throw new RestMapperException($error); | |
} | |
/** | |
* Add data to be available on the filters | |
* | |
* @param array $data | |
*/ | |
public function pushData(array $data) | |
{ | |
$this->data = array_merge($this->data,$data); | |
} | |
/** | |
* You can access to the data | |
* throught properties | |
* | |
* @param $name | |
* @return mixed | |
*/ | |
function __get($name) | |
{ | |
return $this->get($name); | |
} | |
/** | |
* Get a filter method name | |
* | |
* @param $name | |
* @return string | |
*/ | |
protected function getFilterMethod($name) | |
{ | |
return $name . "Filter"; | |
} | |
} |
This file contains hidden or 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 | |
use Illuminate\Support\ServiceProvider; | |
use RestData; | |
class RestApiServiceProvider extends ServiceProvider { | |
/** | |
* Register the application services. | |
* | |
* @return void | |
*/ | |
public function register() | |
{ | |
$this->app['rest.data'] = $this->app->share(function ($app) | |
{ | |
$data = $app['request']->input('filters'); | |
if ( count($data) == 0 ) | |
{ | |
$data = []; | |
} | |
return new RestData($data); | |
}); | |
} | |
} |
This file contains hidden or 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
/** | |
* Controller Layer where you'll call | |
* your repository and pass the data to the repository | |
* L5 Controller | |
*\ | |
<?php | |
use ImplementationRepository; | |
/** | |
* Class UserController | |
*/ | |
class UserController extends BaseControllerApi { | |
/** | |
* @var UserTarget | |
*/ | |
protected $userTarget; | |
/** | |
* @param UserTarget $userTarget | |
*/ | |
function __construct(UserTarget $userTarget) | |
{ | |
$this->userTarget = $userTarget; | |
} | |
public function getUser(ImplementationRest $rest,$user_id) | |
{ | |
return $rest->item($compact('user_id')); // it will apply the filters provided and build the query for you | |
// Other methods: SortBy() and perPage() are not mandatory | |
$rest->sortBy($field,$direction)->collection($data); | |
$rest->perPage(20)->paginate($data); | |
$rest->create($data); | |
$rest->update($data,$fieldsToUpdate); | |
$rest->delete($data); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is great, thank you for creating this! A really great (even if a bit terse) overview of modular abstraction in Laravel :)