Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,004 changes: 346 additions & 658 deletions features/doctrine/date_filter.feature

Large diffs are not rendered by default.

345 changes: 257 additions & 88 deletions features/hydra/collection.feature

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions src/Bridge/Doctrine/Common/Filter/DateFilterTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
namespace ApiPlatform\Core\Bridge\Doctrine\Common\Filter;

use ApiPlatform\Core\Bridge\Doctrine\Common\PropertyHelperTrait;
use ApiPlatform\Core\Exception\InvalidArgumentException;
use Psr\Log\LoggerInterface;

/**
* Trait for filtering the collection by date intervals.
Expand Down Expand Up @@ -54,6 +56,8 @@ public function getDescription(string $resourceClass): array

abstract protected function getProperties(): ?array;

abstract protected function getLogger(): LoggerInterface;

/**
* Determines whether the given property refers to a date field.
*/
Expand All @@ -75,4 +79,17 @@ protected function getFilterDescription(string $property, string $period): array
],
];
}

private function normalizeValue(string $value, string $dateTimeClass, string $property, string $parameterType): ?\DateTimeInterface
{
try {
return new $dateTimeClass($value);
} catch (\Exception $e) {
$this->getLogger()->notice('Invalid filter ignored', [
'exception' => new InvalidArgumentException(sprintf('Invalid value for "%s[%s]". Use date/time string supported by %s::__construct', $property, $parameterType, $dateTimeClass)),
]);

return null;
}
}
}
25 changes: 11 additions & 14 deletions src/Bridge/Doctrine/MongoDbOdm/Filter/DateFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ protected function filterProperty(string $property, $values, Builder $aggregatio
$matchField,
self::PARAMETER_BEFORE,
$values[self::PARAMETER_BEFORE],
$nullManagement
$nullManagement,
$property
);
}

Expand All @@ -79,7 +80,8 @@ protected function filterProperty(string $property, $values, Builder $aggregatio
$matchField,
self::PARAMETER_STRICTLY_BEFORE,
$values[self::PARAMETER_STRICTLY_BEFORE],
$nullManagement
$nullManagement,
$property
);
}

Expand All @@ -89,7 +91,8 @@ protected function filterProperty(string $property, $values, Builder $aggregatio
$matchField,
self::PARAMETER_AFTER,
$values[self::PARAMETER_AFTER],
$nullManagement
$nullManagement,
$property
);
}

Expand All @@ -99,24 +102,18 @@ protected function filterProperty(string $property, $values, Builder $aggregatio
$matchField,
self::PARAMETER_STRICTLY_AFTER,
$values[self::PARAMETER_STRICTLY_AFTER],
$nullManagement
$nullManagement,
$property
);
}
}

/**
* Adds the match stage according to the chosen null management.
*/
private function addMatch(Builder $aggregationBuilder, string $field, string $operator, string $value, string $nullManagement = null): void
private function addMatch(Builder $aggregationBuilder, string $field, string $operator, string $value, ?string $nullManagement, string $property): void
{
try {
$value = new \DateTime($value);
} catch (\Exception $e) {
// Silently ignore this filter if it can not be transformed to a \DateTime
$this->logger->notice('Invalid filter ignored', [
'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)),
]);

if (null === $value = $this->normalizeValue($value, 'DateTime', $property, $operator)) {
return;
}

Expand All @@ -129,7 +126,7 @@ private function addMatch(Builder $aggregationBuilder, string $field, string $op

if ((self::INCLUDE_NULL_BEFORE === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true)) ||
(self::INCLUDE_NULL_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true)) ||
(self::INCLUDE_NULL_BEFORE_AND_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER, self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true))
(self::INCLUDE_NULL_BEFORE_AND_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE, self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true))
) {
$aggregationBuilder->match()->addOr(
$aggregationBuilder->matchExpr()->field($field)->operator($operatorValue[$operator], $value),
Expand Down
45 changes: 32 additions & 13 deletions src/Bridge/Doctrine/Orm/Filter/DateFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
self::PARAMETER_BEFORE,
$values[self::PARAMETER_BEFORE],
$nullManagement,
$type
$type,
$property
);
}

Expand All @@ -92,7 +93,8 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
self::PARAMETER_STRICTLY_BEFORE,
$values[self::PARAMETER_STRICTLY_BEFORE],
$nullManagement,
$type
$type,
$property
);
}

Expand All @@ -105,7 +107,8 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
self::PARAMETER_AFTER,
$values[self::PARAMETER_AFTER],
$nullManagement,
$type
$type,
$property
);
}

Expand All @@ -118,7 +121,8 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
self::PARAMETER_STRICTLY_AFTER,
$values[self::PARAMETER_STRICTLY_AFTER],
$nullManagement,
$type
$type,
$property
);
}
}
Expand All @@ -128,20 +132,35 @@ protected function filterProperty(string $property, $values, QueryBuilder $query
*
* @param string|DBALType $type
*/
protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value, string $nullManagement = null, $type = null)
protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterface $queryNameGenerator, string $alias, string $field, string $operator, string $value, string $nullManagement = null, $type = null/*, string $property*/)
{
if (\func_num_args() > 8) {
$property = func_get_arg(8);
} else {
if (__CLASS__ !== \get_class($this)) {
$r = new \ReflectionMethod($this, __FUNCTION__);
if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
@trigger_error(sprintf('Method %s() will have 9 non-optional arguments in API Platform 3.0. Not passing arguments 7-9 is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED);
}
}
}

$type = (string) $type;
try {
$value = false === strpos($type, '_immutable') ? new \DateTime($value) : new \DateTimeImmutable($value);
} catch (\Exception $e) {
// Silently ignore this filter if it can not be transformed to a \DateTime
$this->logger->notice('Invalid filter ignored', [
'exception' => new InvalidArgumentException(sprintf('The field "%s" has a wrong date format. Use one accepted by the \DateTime constructor', $field)),
]);
$dateTimeClass = false === strpos($type, '_immutable') ? 'DateTime' : 'DateTimeImmutable';

if (null === $value = $this->normalizeValue($value, $dateTimeClass, $property ?? $field, $operator)) {
return;
}

/*
* Doctrine ORM/DBAL uses the PHP default timezone
*
* @see https://www.doctrine-project.org/projects/doctrine-orm/en/current/cookbook/working-with-datetime.html#default-timezone-gotcha
*/
if (false === strpos($type, 'tz')) {
$value = $value->setTimezone(new \DateTimeZone(date_default_timezone_get()));
}

$valueParameter = $queryNameGenerator->generateParameterName($field);
$operatorValue = [
self::PARAMETER_BEFORE => '<=',
Expand All @@ -156,7 +175,7 @@ protected function addWhere(QueryBuilder $queryBuilder, QueryNameGeneratorInterf
} elseif (
(self::INCLUDE_NULL_BEFORE === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true)) ||
(self::INCLUDE_NULL_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true)) ||
(self::INCLUDE_NULL_BEFORE_AND_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER, self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE], true))
(self::INCLUDE_NULL_BEFORE_AND_AFTER === $nullManagement && \in_array($operator, [self::PARAMETER_BEFORE, self::PARAMETER_STRICTLY_BEFORE, self::PARAMETER_AFTER, self::PARAMETER_STRICTLY_AFTER], true))
) {
$queryBuilder->andWhere($queryBuilder->expr()->orX(
$baseWhere,
Expand Down
6 changes: 3 additions & 3 deletions src/Hal/Serializer/CollectionNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final class CollectionNormalizer extends AbstractCollectionNormalizer
*/
protected function getPaginationData($object, array $context = []): array
{
[$paginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems] = $this->getPaginationConfig($object, $context);
[$isPaginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems] = $this->getPaginationConfig($object, $context);
$parsed = IriHelper::parseIri($context['request_uri'] ?? '/', $this->pageParameterName);

$data = [
Expand All @@ -51,7 +51,7 @@ protected function getPaginationData($object, array $context = []): array
$data['_links']['prev']['href'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage - 1.);
}

if ((null !== $lastPage && $currentPage !== $lastPage) || (null === $lastPage && $pageTotalItems >= $itemsPerPage)) {
if (null !== $lastPage && $currentPage !== $lastPage || null === $lastPage && $pageTotalItems >= $itemsPerPage) {
$data['_links']['next']['href'] = IriHelper::createIri($parsed['parts'], $parsed['parameters'], $this->pageParameterName, $currentPage + 1.);
}
}
Expand All @@ -60,7 +60,7 @@ protected function getPaginationData($object, array $context = []): array
$data['totalItems'] = $totalItems;
}

if ($paginator) {
if ($isPaginator) {
$data['itemsPerPage'] = (int) $itemsPerPage;
}

Expand Down
8 changes: 6 additions & 2 deletions src/Hydra/Serializer/PartialCollectionViewNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ public function normalize($object, $format = null, array $context = [])
return $data;
}

$currentPage = $lastPage = $itemsPerPage = $pageTotalItems = null;
$currentPage = null;
$lastPage = null;
$itemsPerPage = null;
$pageTotalItems = null;
if ($paginated = $object instanceof PartialPaginatorInterface) {
if ($object instanceof PaginatorInterface) {
$paginated = 1. !== $lastPage = $object->getLastPage();
$paginated = true;
$lastPage = $object->getLastPage();
} else {
$itemsPerPage = $object->getItemsPerPage();
$pageTotalItems = (float) \count($object);
Expand Down
4 changes: 2 additions & 2 deletions src/JsonApi/Serializer/CollectionNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ final class CollectionNormalizer extends AbstractCollectionNormalizer
*/
protected function getPaginationData($object, array $context = []): array
{
[$paginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems] = $this->getPaginationConfig($object, $context);
[$isPaginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems] = $this->getPaginationConfig($object, $context);
$parsed = IriHelper::parseIri($context['request_uri'] ?? '/', $this->pageParameterName);

$data = [
Expand Down Expand Up @@ -61,7 +61,7 @@ protected function getPaginationData($object, array $context = []): array
$data['meta']['totalItems'] = $totalItems;
}

if ($paginator) {
if ($isPaginator) {
$data['meta']['itemsPerPage'] = (int) $itemsPerPage;
$data['meta']['currentPage'] = (int) $currentPage;
}
Expand Down
17 changes: 12 additions & 5 deletions src/Serializer/AbstractCollectionNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,13 +116,20 @@ protected function normalizeRawCollection($object, $format = null, array $contex
*/
protected function getPaginationConfig($object, array $context = []): array
{
$currentPage = $lastPage = $itemsPerPage = $pageTotalItems = $totalItems = null;
$paginated = $paginator = false;
$currentPage = null;
$lastPage = null;
$itemsPerPage = null;
$pageTotalItems = null;
$totalItems = null;
$paginated = false;
$isPaginator = false;

if ($object instanceof PartialPaginatorInterface) {
$paginated = $paginator = true;
$paginated = true;
$isPaginator = true;
if ($object instanceof PaginatorInterface) {
$paginated = 1. !== $lastPage = $object->getLastPage();
$paginated = true;
$lastPage = $object->getLastPage();
$totalItems = $object->getTotalItems();
} else {
$pageTotalItems = (float) \count($object);
Expand All @@ -134,7 +141,7 @@ protected function getPaginationConfig($object, array $context = []): array
$totalItems = \count($object);
}

return [$paginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems];
return [$isPaginator, $paginated, $currentPage, $itemsPerPage, $lastPage, $pageTotalItems, $totalItems];
}

/**
Expand Down