Skip to content

Commit

Permalink
Merge pull request #20 from Carghaez/master
Browse files Browse the repository at this point in the history
Add support to MySQL for keyword LIKE, add support to laravel 5.4.* for relation keys and other minor fixes
  • Loading branch information
esbenp authored Aug 3, 2017
2 parents 846537e + 65ea244 commit 02f2e9e
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 60 deletions.
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,14 @@ To get started with Bruno I highly recommend my article on

## Installation

For Laravel 5.3 and below
```bash
composer require optimus/bruno ~1.0
composer require optimus/bruno ~2.0
```

For Laravel 5.4 and above
```bash
composer require optimus/bruno ~3.0
```

## Usage
Expand Down Expand Up @@ -181,6 +187,8 @@ 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`

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
}
},
"require": {
"laravel/framework": "~5.1",
"laravel/framework": "~5.4",
"optimus/architect": "~1.0"
},
"require-dev": {
Expand Down
147 changes: 91 additions & 56 deletions src/EloquentBuilderTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,83 +13,99 @@ trait EloquentBuilderTrait
{
/**
* Apply resource options to a query builder
* @param Builder $query
* @param Builder $queryBuilder
* @param array $options
* @return Builder
*/
protected function applyResourceOptions(Builder $query, array $options = [])
protected function applyResourceOptions(Builder $queryBuilder, array $options = [])
{
if (!empty($options)) {
extract($options);

if (isset($includes)) {
if (!is_array($includes)) {
throw new InvalidArgumentException('Includes should be an array.');
}
if (empty($options)) {
return $queryBuilder;
}

$query->with($includes);
}
extract($options);

if (isset($filter_groups)) {
$filterJoins = $this->applyFilterGroups($query, $filter_groups);
if (isset($includes)) {
if (!is_array($includes)) {
throw new InvalidArgumentException('Includes should be an array.');
}

if (isset($sort)) {
if (!is_array($sort)) {
throw new InvalidArgumentException('Sort should be an array.');
}
$queryBuilder->with($includes);
}

if (!isset($filterJoins)) {
$filterJoins = [];
}
if (isset($filter_groups)) {
$filterJoins = $this->applyFilterGroups($queryBuilder, $filter_groups);
}

$sortingJoins = $this->applySorting($query, $sort, $filterJoins);
if (isset($sort)) {
if (!is_array($sort)) {
throw new InvalidArgumentException('Sort should be an array.');
}

if (isset($limit)) {
$query->limit($limit);
if (!isset($filterJoins)) {
$filterJoins = [];
}

if (isset($page)) {
$query->offset($page*$limit);
}
$sortingJoins = $this->applySorting($queryBuilder, $sort, $filterJoins);
}

if (isset($limit)) {
$queryBuilder->limit($limit);
}

if (isset($page)) {
$queryBuilder->offset($page*$limit);
}

return $query;
return $queryBuilder;
}

protected function applyFilterGroups(Builder $query, array $filterGroups = [], array $previouslyJoined = [])
/**
* @param Builder $queryBuilder
* @param array $filterGroups
* @param array $previouslyJoined
* @return array
*/
protected function applyFilterGroups(Builder $queryBuilder, array $filterGroups = [], array $previouslyJoined = [])
{
$joins = [];
foreach ($filterGroups as $group) {
$or = $group['or'];
$filters = $group['filters'];

$query->where(function (Builder $query) use ($filters, $or, &$joins) {
$queryBuilder->where(function (Builder $query) use ($filters, $or, &$joins) {
foreach ($filters as $filter) {
$this->applyFilter($query, $filter, $or, $joins);
}
});
}

foreach(array_diff($joins, $previouslyJoined) as $join) {
$this->joinRelatedModelIfExists($query, $join);
$this->joinRelatedModelIfExists($queryBuilder, $join);
}

return $joins;
}

protected function applyFilter(Builder $query, array $filter, $or = false, array &$joins)
/**
* @param Builder $queryBuilder
* @param array $filter
* @param bool|false $or
* @param array $joins
*/
protected function applyFilter(Builder $queryBuilder, array $filter, $or = false, array &$joins)
{
// $value, $not, $key, $operator
extract($filter);

$table = $query->getModel()->getTable();
$dbType = $queryBuilder->getConnection()->getDriverName();

$table = $queryBuilder->getModel()->getTable();

if ($value === 'null' || $value === '') {
$method = $not ? 'WhereNotNull' : 'WhereNull';

call_user_func([$query, $method], sprintf('%s.%s', $table, $key));
call_user_func([$queryBuilder, $method], sprintf('%s.%s', $table, $key));
} else {
$method = filter_var($or, FILTER_VALIDATE_BOOLEAN) ? 'orWhere' : 'where';
$clauseOperator = null;
Expand All @@ -105,8 +121,9 @@ protected function applyFilter(Builder $query, array $filter, $or = false, array
'sw' => $value.'%' // starts with
];

$databaseField = DB::raw(sprintf('CAST(%s.%s AS TEXT)', $table, $key));
$clauseOperator = $not ? 'NOT ILIKE' : 'ILIKE';
$castToText = (($dbType === 'postgres') ? 'TEXT' : 'CHAR');
$databaseField = DB::raw(sprintf('CAST(%s.%s AS ' . $castToText . ')', $table, $key));
$clauseOperator = ($not ? 'NOT':'') . (($dbType === 'postgres') ? 'ILIKE' : 'LIKE');
$value = $valueString[$operator];
break;
case 'eq':
Expand All @@ -116,8 +133,11 @@ protected function applyFilter(Builder $query, array $filter, $or = false, array
case 'gt':
$clauseOperator = $not ? '<' : '>';
break;
case 'bt':
$clauseOperator = 'between';
case 'gte':
$clauseOperator = $not ? '<' : '>=';
break;
case 'lte':
$clauseOperator = $not ? '>' : '<=';
break;
case 'lt':
$clauseOperator = $not ? '>' : '<';
Expand All @@ -141,7 +161,7 @@ protected function applyFilter(Builder $query, array $filter, $or = false, array
$customFilterMethod = $this->hasCustomMethod('filter', $key);
if ($customFilterMethod) {
call_user_func_array([$this, $customFilterMethod], [
$query,
$queryBuilder,
$method,
$clauseOperator,
$value,
Expand All @@ -154,19 +174,25 @@ protected function applyFilter(Builder $query, array $filter, $or = false, array
} else {
// In operations do not have an operator
if ($operator === 'in') {
call_user_func_array([$query, $method], [
call_user_func_array([$queryBuilder, $method], [
$databaseField, $value
]);
} else {
call_user_func_array([$query, $method], [
call_user_func_array([$queryBuilder, $method], [
$databaseField, $clauseOperator, $value
]);
}
}
}
}

protected function applySorting(Builder $query, array $sorting, array $previouslyJoined = [])
/**
* @param Builder $queryBuilder
* @param array $sorting
* @param array $previouslyJoined
* @return array
*/
protected function applySorting(Builder $queryBuilder, array $sorting, array $previouslyJoined = [])
{
$joins = [];
foreach($sorting as $sortRule) {
Expand All @@ -182,19 +208,24 @@ protected function applySorting(Builder $query, array $sorting, array $previousl
if ($customSortMethod) {
$joins[] = $key;

call_user_func([$this, $customSortMethod], $query, $direction);
call_user_func([$this, $customSortMethod], $queryBuilder, $direction);
} else {
$query->orderBy($key, $direction);
$queryBuilder->orderBy($key, $direction);
}
}

foreach(array_diff($joins, $previouslyJoined) as $join) {
$this->joinRelatedModelIfExists($query, $join);
$this->joinRelatedModelIfExists($queryBuilder, $join);
}

return $joins;
}

/**
* @param $type
* @param $key
* @return bool|string
*/
private function hasCustomMethod($type, $key)
{
$methodName = sprintf('%s%s', $type, Str::studly($key));
Expand All @@ -205,50 +236,54 @@ private function hasCustomMethod($type, $key)
return false;
}

private function joinRelatedModelIfExists(Builder $query, $key)
/**
* @param Builder $queryBuilder
* @param $key
*/
private function joinRelatedModelIfExists(Builder $queryBuilder, $key)
{
$model = $query->getModel();
$model = $queryBuilder->getModel();

// relationship exists, join to make special sort
if (method_exists($model, $key)) {
$relation = $model->$key();
$type = 'inner';

if ($relation instanceof BelongsTo) {
$query->join(
$queryBuilder->join(
$relation->getRelated()->getTable(),
$model->getTable().'.'.$relation->getForeignKey(),
$model->getTable().'.'.$relation->getQualifiedForeignKeyName(),
'=',
$relation->getRelated()->getTable().'.'.$relation->getOtherKey(),
$relation->getRelated()->getTable().'.'.$relation->getOwnerKey(),
$type
);
} elseif ($relation instanceof BelongsToMany) {
$query->join(
$queryBuilder->join(
$relation->getTable(),
$relation->getQualifiedParentKeyName(),
'=',
$relation->getForeignKey(),
$relation->getQualifiedForeignKeyName(),
$type
);
$query->join(
$queryBuilder->join(
$relation->getRelated()->getTable(),
$relation->getRelated()->getTable().'.'.$relation->getRelated()->getKeyName(),
'=',
$relation->getOtherKey(),
$relation->getQualifiedRelatedKeyName(),
$type
);
} else {
$query->join(
$queryBuilder->join(
$relation->getRelated()->getTable(),
$relation->getQualifiedParentKeyName(),
'=',
$relation->getForeignKey(),
$relation->getQualifiedForeignKeyName(),
$type
);
}

$table = $model->getTable();
$query->select(sprintf('%s.*', $table));
$queryBuilder->select(sprintf('%s.*', $table));
}
}
}
15 changes: 13 additions & 2 deletions src/LaravelController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@

namespace Optimus\Bruno;

use JsonSerializable;
use InvalidArgumentException;
use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Routing\Controller;
use Illuminate\Routing\Router;
use Illuminate\Http\JsonResponse;
use Optimus\Architect\Architect;

abstract class LaravelController extends Controller
{
/**
* Defaults
* @var array
*/
protected $defaults = [];

/**
Expand Down Expand Up @@ -37,11 +43,16 @@ protected function response($data, $statusCode = 200, array $headers = [])
*/
protected function parseData($data, array $options, $key = null)
{
$architect = new Architect;
$architect = new Architect();

return $architect->parseData($data, $options['modes'], $key);
}

/**
* Page sort
* @param array $sort
* @return array
*/
protected function parseSort(array $sort) {
return array_map(function($sort) {
if (!isset($sort['direction'])) {
Expand Down Expand Up @@ -82,7 +93,7 @@ protected function parseIncludes(array $includes)
* Parse filter group strings into filters
* Filters are formatted as key:operator(value)
* Example: name:eq(esben)
* @param array $filters
* @param array $filter_groups
* @return array
*/
protected function parseFilterGroups(array $filter_groups)
Expand Down

0 comments on commit 02f2e9e

Please sign in to comment.