Skip to content

Commit

Permalink
Add filter for relation (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
LIQRGV committed Jul 9, 2020
1 parent 6ad46f6 commit 5a1d48c
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 4 deletions.
43 changes: 39 additions & 4 deletions src/Struct/FilterStruct.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,28 @@

namespace LIQRGV\QueryFilter\Struct;

use Illuminate\Database\Eloquent\Builder;

class FilterStruct {
private static $OPERATOR_MAPPING = [
"is" => "=",
"!is" => "!=",
];

private static $LOGICAL_OR = "|";
private static $LOGICAL_OR_FLAG = "|";
private static $NOT_FLAG = "!";

private static $WHERE_QUERY_MAPPING = [
"in" => "whereIn",
"!in" => "whereNotIn",
"between" => "whereBetween",
];

private static $RELATION_FLAG_MAPPING = [
true => "whereHas",
false => "whereDoesntHave",
];

public $fieldName;
public $operator;
public $value;
Expand All @@ -27,8 +35,8 @@ public function __construct($fieldName, $operator, $value) {
}

public function apply($object) {
if (strpos($this->fieldName, self::$LOGICAL_OR)) {
$fieldNames = explode(self::$LOGICAL_OR, $this->fieldName);
if (strpos($this->fieldName, self::$LOGICAL_OR_FLAG)) {
$fieldNames = explode(self::$LOGICAL_OR_FLAG, $this->fieldName);
$fieldNameCount = count($fieldNames);
$subquery = function ($query) use ($fieldNames, $fieldNameCount) {
for ($fieldIndex = 0; $fieldIndex < $fieldNameCount; $fieldIndex++) {
Expand All @@ -42,7 +50,10 @@ public function apply($object) {
return $this->_apply($object, $this->fieldName, 'and');
}

private function _apply($object, $fieldName, $boolean) {
private function _apply(Builder $object, $fieldName, $boolean, $isChild = false) {
if ($this->isRelation($fieldName)) {
return $this->_applyRelation($object, $fieldName, $boolean, $isChild);
}
if (array_key_exists($this->operator, self::$WHERE_QUERY_MAPPING)) {
$whereQuery = self::$WHERE_QUERY_MAPPING[$this->operator];
$value = explode(",", $this->value);
Expand All @@ -60,4 +71,28 @@ private function transformOperator($rawOperator) {

return $rawOperator;
}

private function _applyRelation(Builder $object, $fieldName, $boolean, $isChild)
{
[$relation, $other] = explode('.', $fieldName, 2);

$relationQuery = "whereHas";
if (!$isChild) {
$operatorType = $this->operator[0] !== self::$NOT_FLAG;
$relationQuery = self::$RELATION_FLAG_MAPPING[$operatorType];
}

if ($boolean === 'or') {
$relationQuery = "or" . ucfirst($relationQuery);
}

return $object->$relationQuery($relation, function (Builder $relation) use ($other) {
return $this->_apply($relation, $other, 'and');
});
}

private function isRelation($fieldName)
{
return strpos($fieldName, '.') !== false;
}
}
14 changes: 14 additions & 0 deletions tests/Mocks/RelationMocks/MockModelWithRelationMany.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace LIQRGV\QueryFilter\Mocks\RelationMocks;

use Illuminate\Database\Eloquent\Model;
use LIQRGV\QueryFilter\Mocks\MockModel;

class MockModelWithRelationMany extends Model
{
function mockModels()
{
return $this->hasMany(MockModel::class);
}
}
14 changes: 14 additions & 0 deletions tests/Mocks/RelationMocks/MockModelWithRelationOne.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace LIQRGV\QueryFilter\Mocks\RelationMocks;

use Illuminate\Database\Eloquent\Model;
use LIQRGV\QueryFilter\Mocks\MockModel;

class MockModelWithRelationOne extends Model
{
function mockModel()
{
return $this->hasOne(MockModel::class);
}
}
121 changes: 121 additions & 0 deletions tests/RequestParserRelationTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

namespace Tests\LIQRGV\QueryFilter;

use Illuminate\Database\Query\Builder;
use Illuminate\Http\Request;
use Illuminate\Routing\Route;
use Illuminate\Support\Facades\Config;
use LIQRGV\QueryFilter\Exception\ModelNotFoundException;
use LIQRGV\QueryFilter\Exception\NotModelException;
use LIQRGV\QueryFilter\Mocks\MockModelController;
use LIQRGV\QueryFilter\Mocks\RelationMocks\MockModelWithRelationOne;
use LIQRGV\QueryFilter\RequestParser;
use Symfony\Component\HttpFoundation\ParameterBag;

class RequestParserRelationTest extends TestCase
{
function testFilterRelationOne()
{
$uri = 'some_model';
$controllerClass = MockModelController::class;
$query = new ParameterBag([
"filter" => [
"mockModel.id" => [
"is" => 2
]
]
]);
$requestParserOptions = [
];

$request = $this->createControllerRequest($uri, $controllerClass, $query, $requestParserOptions);

$requestParser = new RequestParser($request);
$requestParser->setModel(MockModelWithRelationOne::class);
$builder = $requestParser->getBuilder();

$query = $builder->getQuery();
$this->assertEquals("mock_model_with_relation_ones", $query->from);

// assert relation first
$this->assertEquals("Column", $builder->getQuery()->wheres[0]["query"]->wheres[0]["type"]);
$this->assertEquals("mock_model_with_relation_ones.id", $builder->getQuery()->wheres[0]["query"]->wheres[0]["first"]);
$this->assertEquals("=", $builder->getQuery()->wheres[0]["query"]->wheres[0]["operator"]);
$this->assertEquals("mock_models.mock_model_with_relation_one_id", $builder->getQuery()->wheres[0]["query"]->wheres[0]["second"]);
$this->assertEquals("and", $builder->getQuery()->wheres[0]["query"]->wheres[0]["boolean"]);

// assert relation query
$this->assertEquals("Basic", $builder->getQuery()->wheres[0]["query"]->wheres[1]["type"]);
$this->assertEquals("id", $builder->getQuery()->wheres[0]["query"]->wheres[1]["column"]);
$this->assertEquals("=", $builder->getQuery()->wheres[0]["query"]->wheres[1]["operator"]);
$this->assertEquals("2", $builder->getQuery()->wheres[0]["query"]->wheres[1]["value"]);
$this->assertEquals("and", $builder->getQuery()->wheres[0]["query"]->wheres[1]["boolean"]);
}

function testFilterOrRelationOne()
{
$uri = 'some_model';
$controllerClass = MockModelController::class;
$query = new ParameterBag([
"filter" => [
"mockModel.id|mockModel.other_attr" => [
"is" => 2
]
]
]);
$requestParserOptions = [];

$request = $this->createControllerRequest($uri, $controllerClass, $query, $requestParserOptions);

$requestParser = new RequestParser($request);
$requestParser->setModel(MockModelWithRelationOne::class);
$builder = $requestParser->getBuilder();

$query = $builder->getQuery();
$this->assertEquals("mock_model_with_relation_ones", $query->from);

// assert nested query
$this->assertEquals("Nested", $builder->getQuery()->wheres[0]["type"]);
$this->assertEquals("and", $builder->getQuery()->wheres[0]["boolean"]);

/** @var Builder $nestedQuery */
$nestedQuery = $builder->getQuery()->wheres[0]["query"];

$this->assertEquals("Exists", $nestedQuery->wheres[0]["type"]);
$this->assertEquals("Exists", $nestedQuery->wheres[1]["type"]);
$this->assertEquals("or", $nestedQuery->wheres[0]["boolean"]);
$this->assertEquals("or", $nestedQuery->wheres[1]["boolean"]);

$firstInnerQuery = $nestedQuery->wheres[0]["query"];
$secondInnerQuery = $nestedQuery->wheres[1]["query"];

// assert first relation
$this->assertEquals("Column", $firstInnerQuery->wheres[0]["type"]);
$this->assertEquals("mock_model_with_relation_ones.id", $firstInnerQuery->wheres[0]["first"]);
$this->assertEquals("=", $firstInnerQuery->wheres[0]["operator"]);
$this->assertEquals("mock_models.mock_model_with_relation_one_id", $firstInnerQuery->wheres[0]["second"]);
$this->assertEquals("and", $firstInnerQuery->wheres[0]["boolean"]);

// assert first relation query field
$this->assertEquals("Basic", $firstInnerQuery->wheres[1]["type"]);
$this->assertEquals("id", $firstInnerQuery->wheres[1]["column"]);
$this->assertEquals("=", $firstInnerQuery->wheres[1]["operator"]);
$this->assertEquals("2", $firstInnerQuery->wheres[1]["value"]);
$this->assertEquals("and", $firstInnerQuery->wheres[1]["boolean"]);

// assert second relation
$this->assertEquals("Column", $secondInnerQuery->wheres[0]["type"]);
$this->assertEquals("mock_model_with_relation_ones.id", $secondInnerQuery->wheres[0]["first"]);
$this->assertEquals("=", $secondInnerQuery->wheres[0]["operator"]);
$this->assertEquals("mock_models.mock_model_with_relation_one_id", $secondInnerQuery->wheres[0]["second"]);
$this->assertEquals("and", $secondInnerQuery->wheres[0]["boolean"]);

// assert second relation query field
$this->assertEquals("Basic", $secondInnerQuery->wheres[1]["type"]);
$this->assertEquals("other_attr", $secondInnerQuery->wheres[1]["column"]);
$this->assertEquals("=", $secondInnerQuery->wheres[1]["operator"]);
$this->assertEquals("2", $secondInnerQuery->wheres[1]["value"]);
$this->assertEquals("and", $secondInnerQuery->wheres[1]["boolean"]);
}
}

0 comments on commit 5a1d48c

Please sign in to comment.