Skip to content

Commit

Permalink
Adding json filter
Browse files Browse the repository at this point in the history
  • Loading branch information
mattamon committed May 23, 2024
1 parent affa815 commit 11e93a6
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 20 deletions.
3 changes: 3 additions & 0 deletions config/notes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ services:

Pimcore\Bundle\StudioBackendBundle\Note\Service\NoteServiceInterface:
class: Pimcore\Bundle\StudioBackendBundle\Note\Service\NoteService

Pimcore\Bundle\StudioBackendBundle\Note\Service\FilterServiceInterface:
class: Pimcore\Bundle\StudioBackendBundle\Note\Service\FilterService
31 changes: 31 additions & 0 deletions src/Exception/InvalidFilterException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\Exception;

/**
* @internal
*/
final class InvalidFilterException extends AbstractApiException
{
public function __construct(string $filter)
{
parent::__construct(400, sprintf(
'Invalid filter: %s',
$filter)
);
}
}
2 changes: 2 additions & 0 deletions src/Note/Controller/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteElement;
use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteParameters;
use Pimcore\Bundle\StudioBackendBundle\Note\Service\NoteServiceInterface;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attributes\Parameters\Query\FieldFilterParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attributes\Parameters\Query\FilterParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attributes\Parameters\Query\PageParameter;
use Pimcore\Bundle\StudioBackendBundle\OpenApi\Attributes\Parameters\Query\PageSizeParameter;
Expand Down Expand Up @@ -65,6 +66,7 @@ public function __construct(
#[PageParameter]
#[PageSizeParameter(50)]
#[FilterParameter('notes')]
#[FieldFilterParameter]
#[SuccessResponse(
description: 'Paginated assets with total count as header param',
content: new CollectionJson(new NoteCollection())
Expand Down
26 changes: 9 additions & 17 deletions src/Note/Repository/NoteRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteElement;
use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteParameters;
use Pimcore\Bundle\StudioBackendBundle\Note\Schema\CreateNote;
use Pimcore\Bundle\StudioBackendBundle\Note\Service\FilterServiceInterface;
use Pimcore\Model\Element\Note;
use Pimcore\Model\Element\Note\Listing as NoteListing;

Expand All @@ -31,7 +32,10 @@
*/
final readonly class NoteRepository implements NoteRepositoryInterface
{
public function __construct(private NoteResolverInterface $noteResolver)
public function __construct(
private NoteResolverInterface $noteResolver,
private FilterServiceInterface $filterService
)
{
}

Expand Down Expand Up @@ -78,23 +82,11 @@ public function listNotes(NoteElement $noteElement, NoteParameters $parameters):
$list->setOrder($parameters->getSortOrder());
}

if ($parameters->getFilter()) {
$condition = '('
. '`title` LIKE :filter'
. ' OR `description` LIKE :filter'
. ' OR `type` LIKE :filter'
. ' OR `user` IN (SELECT `id` FROM `users` WHERE `name` LIKE :filter)'
. " OR DATE_FORMAT(FROM_UNIXTIME(`date`), '%Y-%m-%d') LIKE :filter"
. ')';
$list->addConditionParam($condition, ['filter' => '%' . $parameters->getFilter() . '%']);
}
$this->filterService->applyFilter($list, $parameters);

if ($noteElement->getId() && $noteElement->getType()) {
$list->addConditionParam(
'(cid = :id AND ctype = :type)',
['id' => $noteElement->getId(), 'type' => $noteElement->getType()]
);
}
$this->filterService->applyFieldFilters($list, $parameters);

$this->filterService->applyElementFilter($list, $noteElement);

return $list;
}
Expand Down
20 changes: 18 additions & 2 deletions src/Note/Request/NoteParameters.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

namespace Pimcore\Bundle\StudioBackendBundle\Note\Request;

use JsonException;
use Pimcore\Bundle\StudioBackendBundle\Request\CollectionParameters;

/**
Expand All @@ -24,11 +25,12 @@
final readonly class NoteParameters extends CollectionParameters
{
public function __construct(
int $page = 1,
int $pageSize = 50,
private ?string $sortBy = null,
private ?string $sortOrder = null,
private ?string $filter = null,
int $page = 1,
int $pageSize = 50,
private ?string $fieldFilters = null,
) {
parent::__construct($page, $pageSize);
}
Expand All @@ -47,4 +49,18 @@ public function getFilter(): ?string
{
return $this->filter;
}

/**
* @throws JsonException
*/
public function getFieldFilters(): ?array
{
return $this->fieldFilters === null ? null :
json_decode(
$this->fieldFilters,
true,
512,
JSON_THROW_ON_ERROR
);
}
}
139 changes: 139 additions & 0 deletions src/Note/Service/FilterService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\Note\Service;

use Exception;
use Pimcore\Bundle\StaticResolverBundle\Db\DbResolverInterface;
use Pimcore\Bundle\StudioBackendBundle\Exception\InvalidFilterException;
use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteElement;
use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteParameters;
use Pimcore\Model\Element\Note\Listing as NoteListing;

/**
* @internal
*/
final class FilterService implements FilterServiceInterface
{
public function applyFilter(NoteListing $list, NoteParameters $parameters): void
{
if ($parameters->getFilter()) {
$list->addConditionParam(
$this->createFilterCondition($parameters->getFilter()),
['filter' => '%' . $parameters->getFilter() . '%']
);
}
}

public function applyFieldFilters(NoteListing $list, NoteParameters $parameters): void
{
try {
if(!$parameters->getFieldFilters() || empty($parameters->getFieldFilters())) {

Check failure on line 44 in src/Note/Service/FilterService.php

View workflow job for this annotation

GitHub Actions / static-analysis / Static Analysis with PHPStan (8.2, highest, false)

Expression in empty() is not falsy.

Check failure on line 44 in src/Note/Service/FilterService.php

View workflow job for this annotation

GitHub Actions / static-analysis / Static Analysis with PHPStan (8.2, highest, 11.x-dev as 11.99.9, true)

Expression in empty() is not falsy.
return;
}

$propertyKey = 'field';

foreach ($parameters->getFieldFilters() as $filter) {
$operator = $this->findOperator($filter['type'], $filter['operator']);
$value = $this->prepareValue($filter['type'], $filter['operator'], $filter['value']);

if ($operator === 'LIKE') {
$value = '%' . $value . '%';
}

if ($filter[$propertyKey] === 'user') {
$list->addConditionParam(
'`user` IN (SELECT `id` FROM `users` WHERE `name` ' . $operator . ' :user)',
['user' => $value]
);
}

if ($filter['type'] === 'date' && $filter['operator'] === 'eq') {
$maxTime = $value + (86400 - 1); //specifies the top point of the range used in the condition
$dateCondition = '`' . $filter[$propertyKey] . '` ' . ' BETWEEN :minTime AND :maxTime';
$list->addConditionParam($dateCondition, ['minTime' => $value, 'maxTime' => $maxTime]);
} else {
$list->addConditionParam(
'`' . $filter[$propertyKey] . '` ' . $operator . ' :' . $filter[$propertyKey],
[$filter[$propertyKey] => $value]
);
}
}
} catch (Exception) {
throw new InvalidFilterException('fieldFilters');
}
}

public function applyElementFilter(NoteListing $list, NoteElement $noteElement): void
{
if ($noteElement->getId() && $noteElement->getType()) {
$list->addConditionParam(
'(cid = :id AND ctype = :type)',
['id' => $noteElement->getId(), 'type' => $noteElement->getType()]
);
}
}

private function prepareValue(string $type, string $operator, string $value): mixed
{
return match($type) {
'date' => strtotime($value),
default => $this->matchValueOperator($operator, $value)
};
}


private function matchValueOperator(string $operator, mixed $value): mixed
{
return match($operator) {
'boolean' => (int)$value,
default => $value
};
}

private function createFilterCondition(string $filter): string
{
return '('
. '`title` LIKE :filter'
. ' OR `description` LIKE :filter'
. ' OR `type` LIKE :filter'
. ' OR `user` IN (SELECT `id` FROM `users` WHERE `name` LIKE :filter)'
. " OR DATE_FORMAT(FROM_UNIXTIME(`date`), '%Y-%m-%d') LIKE :filter"
. ')';
}


private function findOperator(string $type, string $operator): string
{
return match($type) {
'string' => 'LIKE',
'numeric', 'date' => $this->matchNumericOperator($operator),
default => '='
};
}

private function matchNumericOperator(string $operator): string
{
return match($operator) {
'lt' => '<',
'lte' => '<=',
'gt' => '>',
'gte' => '>=',
default => '='
};
}
}
33 changes: 33 additions & 0 deletions src/Note/Service/FilterServiceInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\Note\Service;

use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteElement;
use Pimcore\Bundle\StudioBackendBundle\Note\Request\NoteParameters;
use Pimcore\Model\Element\Note\Listing as NoteListing;

/**
* @internal
*/
interface FilterServiceInterface
{
public function applyFilter(NoteListing $list, NoteParameters $parameters): void;

public function applyFieldFilters(NoteListing $list, NoteParameters $parameters): void;

public function applyElementFilter(NoteListing $list, NoteElement $noteElement): void;
}
35 changes: 35 additions & 0 deletions src/OpenApi/Attributes/Parameters/Query/FieldFilterParameter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioBackendBundle\OpenApi\Attributes\Parameters\Query;

use Attribute;
use OpenApi\Attributes\QueryParameter as OpenApiQueryParameter;

#[Attribute(Attribute::TARGET_METHOD)]
final class FieldFilterParameter extends OpenApiQueryParameter
{
public function __construct()
{
parent::__construct(
name: 'fieldFilters',
description: 'Filter for specific fields, will be json decoded to an array.',
in: 'query',
required: false,
example: '[{"operator":"like","value":"John","field":"name","type":"string"}, {"operator":"eq","value":"10","property":"count","type":"numeric"} ]'
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public function __construct(string $filterFor = 'properties')
parent::__construct(
name: 'filter',
description: 'Filter for ' . $filterFor,
in: 'filter',
in: 'query',
required: false,
schema: new Schema(type: 'string', example: null),
);
Expand Down

0 comments on commit 11e93a6

Please sign in to comment.