Introduction

zcwilt/rest-api is written for Laravel and provides a query parser and api controllers for CRUD type actions.

The query parser allows for complex filtering and sorting, converting the URI query into eloquent queries.

The API controller supports resource creation, reading, updating and deletion.

Reading, updating and deletion can all access the query parser.

e.g.

delete ?whereIn[]=id:(1,2)

update ?whereBetween=age:30:60  @todo

although note that the filtering can be more complex than just a simple where

Some examples of filtering/Sorting etc

{api-uri}?where[]=id:eq:2

{api-uri}?whereIn[]=id:(1,2)

{api-uri}?sort[]=id,-name

Installation

Installation is via composer.

composer require zcwilt/rest-api

Query Parser

The query parser allows for complex filtering, sorting, the use of child relations and more.

Currently the filter parser supports

  • where
  • orWhere
  • whereIn
  • orWhereIn
  • whereNotIn
  • orWhereNotIn
  • whereBetween
  • whereNotBetween
  • orWhereBetween
  • orWhereNotBetween
  • withTrashed
  • onlyTrashed
  • scope

Sorting allows for multiple sort targets for ascending and descending sorts.

Includes allow for loading child models.

Joins are also supported

Query results by default return all columns for the query, however you can use the columns filter to restrict which columns are returned.

Query Parser Filtering

Simple Where Clauses

Simple where clauses take the format of

{api-uri}?where[]=fieldname:operator:value

for example

{api-uri}?where[]=id:eq:1

would equate to an eloquent query of

model::where('id', '=', 1)

The operators allowed are

  • eq equates to =
  • noteq equates to !=
  • lte equates to <=
  • gte equates to >=
  • gt equates to >
  • lt equates to <
  • lk equates to LIKE
  • nlk equates to NOT LIKE

Can also use orWhere

{api-uri}?orWhere[]=id:eq:1

Where In clauses

Where In clauses take the form of

{api-uri}?whereIn[]=fieldname:(comma separate list)

For example

{api-uri}?whereIn[]=id:(1,2,3)

Can also use

  • orWhereIn
  • whereNotIn
  • orWhereNotIn

Where Between Clauses

Where Between clauses take the form of

{api-uri}?whereBetween[]=fieldname:start:end

For example

{api-uri}?whereBetween[]=age:18:45

Can also use

  • orWhereBetween
  • whereNotBetween
  • orWhereNotBetween

Query Parser Soft Deletes

For models that support soft deletes, the query parser also provides 2 further filters.

With Trashed

To show all entries for the model, even when soft deleted use

{api-uri}?withTrashed

Only Trashed

To show only entries for the model that have been soft deleted use

{api-uri}?onlyTrashed

Note

Using withTrashed or onlyTrashed on models that do not support soft deletes will result in an exception, with the error message being returned in the Json response.

e.g.

{"error":{"message":"Model does not support soft deletes","status_code":400}}

Warning

There is currently no way to force delete soft deleted items. This is on the todo list.

Query Parser Sorting

example

{api-uri}?sort[]=id,-name

would sort ascending on id then sort descending on name

Query Parser Columns

example

{api-uri}?columns[]=id,name

will restrict the returned columns to just id and name.

Query Parser With

Warning

This feature is a bit experimental at the moment. In terms of testing i’ve only tried with a simple one to many relationship. e.g. user->posts.

example

{api-uri}?with[]=posts

The above assumes the query is being done on a model that has a relationship defined, and uses Laravel Querbuilder with method.

Query Parser Joins

Example

{api-uri}?join[]=joinType:tableName:leftKey:rightKey

The join clause takes 4 parameters

  • joinType - can be 1 of inner, left or cross
  • tableName - the table to join on
  • leftkey - the table field used on the left side of the Join on clause
  • rightKey - the table field used on the right side of the Join on clause

More examples

{api-uri}?join[]=inner:posts:posts.user_id:users.id
same as
$model->join('posts', 'posts.user_id', '=', 'users.id', 'inner');

Query Parser Scopes

Warning

This feature is a bit experimental at the moment.

example

{api-uri}?scope[]=myscope

Allows the use of Laravel scopes

the scope parameter should be the name of your scope less the preceding scope

e.g. If your scope is called scopeActive then you would just use active

URL Parameter Format

Most examples you will see in the documentation show URL parameters in a simple format

e.g {api-uri}?columns=id,name&where=id:eq:1

However this format will break if you need to include multiple copies of a parser

consider this

{api-uri}?columns=id,name&where=id:eq:1&orWhereBetween=age:(10,15)&orWhereBetween=age:(50,60)

The above query will not work as the PHP/Laravel request will only be able to choose one of the orWhereBetween clauses.

In these cases you will need to use the standard bracket notation for GET URLS

e.g.

{api-uri}?columns=id,name&where=id:eq:1&orWhereBetween[]=age:(10,15)&orWhereBetween[]=age:(50,60)

Note

You only need to use bracket notation e.g. &orWhereBetween[]=age:(10,15) if you have multiple parsers of a certain type. However for simplicity and consistency, we would suggest using bracket notation all the time.

API Controllers

Routes

zcwilt/rest-api uses resource controllers. To define your routes for each controller you want, you will need to add the following to your api routes file.

Route::resource('modelName', 'controller');
Route::delete('modelName', 'controller@destroyByQuery');
Route::put('modelName', 'controller@updateByQuery');

modelName is the model name you want to have api access.

Controller is the name of your controller class.

As an example. To use the dummy simple controller supplied by the project, your routing entries would be.

Route::resource('dummySimple', 'Api\DummySimpleController');
Route::delete('dummySimple', 'Api\DummySimpleController@destroyByQuery');
Route::put('dummySimple', 'Api\DummySimpleController@updateByQuery');

or to use the default User model that comes with Laravel

Route::resource('user', 'Api\UserController');
Route::delete('user', 'Api\UserController@destroyByQuery');
Route::put('user', 'Api\UserController@updateByQuery');

For each Laravel Model that you want to use in the API you will need to create a Controller

Controller Definition

As mentioned above, for each Laravel model that you want to provide API access to you will need to create a Controller.

This should be placed in the standard laravel location e.g. App/Http/Controllers or a sub directory. Our suggestion is to use App/Http/Controllers/Api

The controller definition is fairly simple

<?php
namespace App\Http\Controllers\Api;

use Zcwilt\Api\Controllers\ApiController;

class DummySimpleController extends ApiController
{
    protected $modelName = '\\Zcwilt\Api\\Models\\DummySimple';
}

Note

The protected $modelName defines the Eloquent Model that will be used by the controller. The factory class used will try and resolve the model from either your projects App folder or from the App/Models folder, If the Model is in one of these folders there is no need to namespace the model name. e.g. you could just do

protected $modelName = 'ModelName';

Validation

The base ApiController class that your controller extends contains calls to Laravels Validation system.

To use validation on your api request you must create a public rules method on the model your controller accesses.

e.g.

public function rules($id = 0)
{
    return [
        'email' => 'required|unique:zcwilt_users'.($id ? ",email,$id" : ''),
        'name' => 'required'
    ];
}

Note

For update methods, the primary key value is passed as the $id parameter.

Warning

The controller uses $request->all() to pass request fields to the model update/create methods. This could allow malicious users to update database fields that you did not want. You must therefore be very explicit in your models as to which fields can be used in this way. e.g. using the $fillable property.

Api Endpoints

The api endpoints provided by the resource controller and extra controller methods provide the following route/actions

GET api/modelname -> controller@index : allows for query filtering
GET api/modelname/{id} -> controller@show
POST api/modelname -> controller@store
The request body should be an array of field/values
e.g ['name' => 'foo', 'email' => 'bar@test.com']
PUT api/modelname/{id} -> controller@update
The request body should be an array of field/values
e.g ['name' => 'foo', 'email' => 'bar@test.com']
PUT api/modelname -> controller@updateByQuery : allows for query filtering
The request body should be an array of parser clauses however  field name/values
should be set in a fields array
e.g ['where' => 'status:eq:1', 'fields' => [name' => 'foo', 'email' => 'bar@test.com']]
DELETE api/modelname/{id} - controller@destroy
DELETE api/modelname - controller@destroyByQuery : allows for query filtering
The request body should be an array of parser clauses
e.g ['where' => ''status:eq:1]

Exception Handling

@todo

Authorization

This project/code is agnostic as to how you provide authorization for your API.

It’s expected that you may need to extend the Api Controller to provide authorization and/or role/scope based access.

There will be some more documentation here regarding Authorizartion etc

Testing

Tests can be run within the package

vendor/bin/phpunit

There is also a phpunit xml file for running tests with code coverage

vendor/bin/phpunit -c phpunit-cc.xml

Note

To run the code coverage tests, you will need to install xdebug

Contributing

See the - Contributing File

Acknowledgements

This project would not exist without the work of others.

Thanks go to

From our composer.json

The following projects also provided inspiration.

Thanks also to the guys from Reddit who provided feedback

License

This code is released under the MIT license.

See the License File

Todo

  • Allow for force deleting entries
  • Document Exception Handling
  • Update demo site with auth examples