A Laravel base controller class and a trait that will enable to add filtering, sorting, eager loading and pagination to your resource URLs.
Dedicated to Giordano Bruno
This package is named after my hero Giordano Bruno. A true visionary who dared to dream beyond what was thought possible. For his ideas and his refusal to renounce them he was burned to the stake in 1600. I highly recommend this short cartoon on his life narrated by Neil deGrasse Tyson.
- Parse GET parameters for dynamic eager loading of related resources, sorting and pagination
- Advanced filtering of resources using filter groups
- Use Optimus\Architect for sideloading, id loading or embedded loading of related resources
- ... Ideas for new functionality is welcome here
To get started with Bruno I highly recommend my article on resource controls in Laravel APIs
For Laravel 6.0 and above
composer require moova/optimus-bruno dev-master
The examples will be of a hypothetical resource endpoint /books
which will return a collection of Book
,
each belonging to a Author
.
Book n ----- 1 Author
Key | Type | Description |
---|---|---|
Includes | array | Array of related resources to load, e.g. ['author', 'publisher', 'publisher.books'] |
Sort | array | Property to sort by, e.g. 'title' |
Limit | integer | Limit of resources to return |
Page | integer | For use with limit |
Filter_groups | array | Array of filter groups. See below for syntax. |
<?php
namespace App\Http\Controllers;
use Optimus\Api\Controller\EloquentBuilderTrait;
use Optimus\Api\Controller\LaravelController;
use App\Models\Book;
class BookController extends LaravelController
{
use EloquentBuilderTrait;
public function getBooks()
{
// Parse the resource options given by GET parameters
$resourceOptions = $this->parseResourceOptions();
// Start a new query for books using Eloquent query builder
// (This would normally live somewhere else, e.g. in a Repository)
$query = Book::query();
$this->applyResourceOptions($query, $resourceOptions);
$books = $query->get();
// Parse the data using Optimus\Architect
$parsedData = $this->parseData($books, $resourceOptions, 'books');
// Create JSON response of parsed data
return $this->response($parsedData);
}
}
Simple eager load
/books?includes[]=author
Will return a collection of 5 Book
s eager loaded with Author
.
IDs mode
/books?includes[]=author:ids
Will return a collection of Book
s eager loaded with the ID of their Author
Sideload mode
/books?includes[]=author:sideload
Will return a collection of Book
s and a eager loaded collection of their
Author
s in the root scope.
See mere about eager loading types in Optimus\Architect's README
Two parameters are available: limit
and page
. limit
will determine the number of
records per page and page
will determine the current page.
/books?limit=10&page=3
Will return books number 30-40.
Should be defined as an array of sorting rules. They will be applied in the order of which they are defined.
Sorting rules
Property | Value type | Description |
---|---|---|
key | string | The property of the model to sort by |
direction | ASC or DESC | Which direction to sort the property by |
Example
[
{
"key": "title",
"direction": "ASC"
}, {
"key": "year",
"direction": "DESC"
}
]
Will result in the books being sorted by title in ascending order and then year in descending order.
Should be defined as an array of filter groups.
Filter groups
Property | Value type | Description |
---|---|---|
or | boolean | Should the filters in this group be grouped by logical OR or AND operator |
filters | array | Array of filters (see syntax below) |
Filters
Property | Value type | Description |
---|---|---|
key | string | The property of the model to filter by (can also be custom filter) |
value | mixed | The value to search for |
operator | string | The filter operator to use (see different types below) |
not | boolean | Negate the filter |
Operators
Type | Description | Example |
---|---|---|
ct | String contains | ior matches Giordano Bruno and Giovanni |
sw | Starts with | Gior matches Giordano Bruno but not Giovanni |
ew | Ends with | uno matches Giordano Bruno but not Giovanni |
eq | Equals | Giordano Bruno matches Giordano Bruno but not Bruno |
gt | Greater than | 1548 matches 1600 but not 1400 |
gte | Greater than or equalTo | 1548 matches 1548 and above (ony for Laravel 5.4 and above) |
lte | Lesser than or equalTo | 1600 matches 1600 and below (ony for Laravel 5.4 and above) |
lt | Lesser than | 1600 matches 1548 but not 1700 |
in | In array | ['Giordano', 'Bruno'] matches Giordano and Bruno but not Giovanni |
bt | Between | [1, 10] matches 5 and 7 but not 11 |
Special values
Value | Description |
---|---|
null (string) | The property will be checked for NULL value |
(empty string) | The property will be checked for NULL value |
Remember our relationship Books n ----- 1 Author
. Imagine your want to
filter books by Author
name.
[
{
"filters": [
{
"key": "author",
"value": "Optimus",
"operator": "sw"
}
]
}
]
Now that is all good, however there is no author
property on our
model since it is a relationship. This would cause an error since
Eloquent would try to use a where clause on the non-existant author
property. We can fix this by implementing a custom filter. Where
ever you are using the EloquentBuilderTrait
implement a function named
filterAuthor
public function filterAuthor(Builder $query, $method, $clauseOperator, $value)
{
// if clauseOperator is idential to false,
// we are using a specific SQL method in its place (e.g. `in`, `between`)
if ($clauseOperator === false) {
call_user_func([$query, $method], 'authors.name', $value);
} else {
call_user_func([$query, $method], 'authors.name', $clauseOperator, $value);
}
}
Note: It is important to note that a custom filter will look for a relationship with
the same name of the property. E.g. if trying to use a custom filter for a property
named author
then Bruno will try to eagerload the author
relationship from the
Book
model.
Custom filter function
Argument | Description |
---|---|
$query | The Eloquent query builder |
$method | The where method to use (where , orWhere , whereIn , orWhereIn etc.) |
$clauseOperator | Can operator to use for non-in wheres (!= , = , > etc.) |
$value | The filter value |
$in | Boolean indicating whether or not this is an in-array filter |
[
{
"or": true,
"filters": [
{
"key": "author",
"value": "Optimus",
"operator": "sw"
},
{
"key": "author",
"value": "Prime",
"operator": "ew"
}
]
}
]
Books with authors whoose name start with Optimus
or ends with Prime
.
[
{
"filters": [
{
"key": "author",
"value": "Brian",
"operator": "sw"
}
]
},
{
"filters": [
{
"key": "year",
"value": 1990,
"operator": "gt"
},
{
"key": "year",
"value": 2000,
"operator": "lt"
}
]
}
]
Books with authors whoose name start with Brian and which were published between years 1990 and 2000.
Filters may be optionally expressed in Shorthand, which takes the a given filter array[key, operator, value, not(optional)] and builds a verbose filter array upon evaluation.
For example, this group of filters (Verbose)
[
{
"or": false,
"filters": [
{
"key": "author",
"value": "Optimus",
"operator": "sw"
},
{
"key": "author",
"value": "Prime",
"operator": "ew"
}
{
"key": "deleted_at",
"value": null,
"operator": "eq",
"not": true
}
]
}
]
May be expressed in this manner (Shorthand)
[
{
"or": false,
"filters": [
["author", "sw", "Optimus"],
["author", "ew", "Prime"],
["deleted_at", "eq", null, true]
]
}
]
This package is compliant with PSR-1, PSR-2 and PSR-4. If you notice compliance oversights, please send a patch via pull request.
$ phpunit
Please see CONTRIBUTING for details.
The MIT License (MIT). Please see License File for more information.