Skip to content

Commit

Permalink
Merge pull request #732 from nextras/datetime-property-wrapper
Browse files Browse the repository at this point in the history
Datetime property wrapper
  • Loading branch information
hrach authored Feb 16, 2025
2 parents d05b8d9 + 60c7131 commit 040ad5d
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 99 deletions.
19 changes: 13 additions & 6 deletions src/Collection/Functions/BaseCompareFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\Entity\PropertyComparator;
use function array_map;
use function assert;
use function count;
Expand All @@ -33,16 +34,18 @@ public function processArrayExpression(
$targetValue = $args[1];
}

$comparator = $valueReference->propertyMetadata?->getPropertyComparator();

if ($valueReference->aggregator !== null) {
$values = $this->multiEvaluateInPhp($valueReference->value, $targetValue);
$values = $this->multiEvaluateInPhp($valueReference->value, $targetValue, $comparator);
return new ArrayExpressionResult(
value: $values,
aggregator: $valueReference->aggregator,
propertyMetadata: null,
);
} else {
return new ArrayExpressionResult(
value: $this->evaluateInPhp($valueReference->value, $targetValue),
value: $this->evaluateInPhp($valueReference->value, $targetValue, $comparator),
aggregator: null,
propertyMetadata: null,
);
Expand Down Expand Up @@ -72,18 +75,22 @@ public function processDbalExpression(
}


abstract protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool;
abstract protected function evaluateInPhp(
mixed $sourceValue,
mixed $targetValue,
PropertyComparator|null $comparator,
): bool;


/**
* @param array<mixed> $values
* @return array<mixed>
*/
protected function multiEvaluateInPhp(array $values, mixed $targetValue): array
protected function multiEvaluateInPhp(array $values, mixed $targetValue, PropertyComparator|null $comparator): array
{
return array_map(
function ($value) use ($targetValue): bool {
return $this->evaluateInPhp($value, $targetValue);
function ($value) use ($targetValue, $comparator): bool {
return $this->evaluateInPhp($value, $targetValue, $comparator);
},
$values,
);
Expand Down
24 changes: 18 additions & 6 deletions src/Collection/Functions/CompareEqualsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@


use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
use Nextras\Orm\Entity\PropertyComparator;
use Nextras\Orm\Exception\InvalidArgumentException;
use function count;
use function in_array;
Expand All @@ -12,22 +13,33 @@

class CompareEqualsFunction extends BaseCompareFunction
{
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue, PropertyComparator|null $comparator): bool
{
if (is_array($targetValue)) {
return in_array($sourceValue, $targetValue, true);
if ($comparator === null) {
if (is_array($targetValue)) {
return in_array($sourceValue, $targetValue, strict: true);
} else {
return $sourceValue === $targetValue;
}
} else {
return $sourceValue === $targetValue;
if (is_array($targetValue)) {
foreach ($targetValue as $targetSubValue) {
if ($comparator->equals($sourceValue, $targetSubValue)) return true;
}
return false;
} else {
return $comparator->equals($sourceValue, $targetValue);
}
}
}


protected function multiEvaluateInPhp(array $values, mixed $targetValue): array
protected function multiEvaluateInPhp(array $values, mixed $targetValue, PropertyComparator|null $comparator): array
{
if ($targetValue === null && $values === []) {
return [true];
}
return parent::multiEvaluateInPhp($values, $targetValue);
return parent::multiEvaluateInPhp($values, $targetValue, $comparator);
}


Expand Down
9 changes: 7 additions & 2 deletions src/Collection/Functions/CompareGreaterThanEqualsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@


use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
use Nextras\Orm\Entity\PropertyComparator;
use Nextras\Orm\Exception\InvalidArgumentException;
use function is_array;


class CompareGreaterThanEqualsFunction extends BaseCompareFunction
{
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue, PropertyComparator|null $comparator): bool
{
return $sourceValue >= $targetValue;
if ($comparator === null) {
return $sourceValue >= $targetValue;
} else {
return $comparator->compare($sourceValue, $targetValue) >= 0;
}
}


Expand Down
9 changes: 7 additions & 2 deletions src/Collection/Functions/CompareGreaterThanFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@


use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
use Nextras\Orm\Entity\PropertyComparator;
use Nextras\Orm\Exception\InvalidArgumentException;
use function is_array;


class CompareGreaterThanFunction extends BaseCompareFunction
{
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue, PropertyComparator|null $comparator): bool
{
return $sourceValue > $targetValue;
if ($comparator === null) {
return $sourceValue > $targetValue;
} else {
return $comparator->compare($sourceValue, $targetValue) === 1;
}
}


Expand Down
23 changes: 16 additions & 7 deletions src/Collection/Functions/CompareNotEqualsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,32 @@


use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
use Nextras\Orm\Entity\PropertyComparator;
use Nextras\Orm\Exception\InvalidArgumentException;
use function array_combine;
use function array_map;
use function count;
use function explode;
use function in_array;
use function is_array;


class CompareNotEqualsFunction extends BaseCompareFunction
{
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue, PropertyComparator|null $comparator): bool
{
if (is_array($targetValue)) {
return !in_array($sourceValue, $targetValue, true);
if ($comparator === null) {
if (is_array($targetValue)) {
return !in_array($sourceValue, $targetValue, true);
} else {
return $sourceValue !== $targetValue;
}
} else {
return $sourceValue !== $targetValue;
if (is_array($targetValue)) {
foreach ($targetValue as $targetSubValue) {
if ($comparator->equals($sourceValue, $targetSubValue)) return false;
}
return true;
} else {
return !$comparator->equals($sourceValue, $targetValue);
}
}
}

Expand Down
9 changes: 7 additions & 2 deletions src/Collection/Functions/CompareSmallerThanEqualsFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@


use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
use Nextras\Orm\Entity\PropertyComparator;
use Nextras\Orm\Exception\InvalidArgumentException;
use function is_array;


class CompareSmallerThanEqualsFunction extends BaseCompareFunction
{
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue, PropertyComparator|null $comparator): bool
{
return $sourceValue <= $targetValue;
if ($comparator === null) {
return $sourceValue <= $targetValue;
} else {
return $comparator->compare($sourceValue, $targetValue) <= 0;
}
}


Expand Down
9 changes: 7 additions & 2 deletions src/Collection/Functions/CompareSmallerThanFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@


use Nextras\Orm\Collection\Functions\Result\DbalExpressionResult;
use Nextras\Orm\Entity\PropertyComparator;
use Nextras\Orm\Exception\InvalidArgumentException;
use function is_array;


class CompareSmallerThanFunction extends BaseCompareFunction
{
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue): bool
protected function evaluateInPhp(mixed $sourceValue, mixed $targetValue, PropertyComparator|null $comparator): bool
{
return $sourceValue < $targetValue;
if ($comparator === null) {
return $sourceValue < $targetValue;
} else {
return $comparator->compare($sourceValue, $targetValue) === -1;
}
}


Expand Down
35 changes: 11 additions & 24 deletions src/Collection/Helpers/ArrayCollectionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@


use Closure;
use DateTimeImmutable;
use DateTimeInterface;
use Nette\Utils\Arrays;
use Nextras\Orm\Collection\Aggregations\Aggregator;
use Nextras\Orm\Collection\Functions\CollectionFunction;
Expand Down Expand Up @@ -72,15 +70,20 @@ public function createSorter(array $expressions): Closure

return function ($a, $b) use ($parsedExpressions): int {
foreach ($parsedExpressions as [$function, $ordering, $functionArgs]) {
$_a = $function->processArrayExpression($this, $a, $functionArgs)->value;
$_b = $function->processArrayExpression($this, $b, $functionArgs)->value;
/** @var CollectionFunction $function */
$_aResult = $function->processArrayExpression($this, $a, $functionArgs);
$_bResult = $function->processArrayExpression($this, $b, $functionArgs);

$descReverse = ($ordering === ICollection::ASC || $ordering === ICollection::ASC_NULLS_FIRST || $ordering === ICollection::ASC_NULLS_LAST) ? 1 : -1;
$_a = $_aResult->value;
$_b = $_bResult->value;

$descReverse = ($ordering === ICollection::ASC || $ordering === ICollection::ASC_NULLS_FIRST || $ordering === ICollection::ASC_NULLS_LAST) ? 1 : -1;
$comparator = $_aResult->propertyMetadata?->getPropertyComparator();
if ($_a === null || $_b === null) {
// By default, <=> sorts nulls at the beginning.
$nullsReverse = $ordering === ICollection::ASC_NULLS_FIRST || $ordering === ICollection::DESC_NULLS_FIRST ? 1 : -1;
$result = ($_a <=> $_b) * $nullsReverse;
$nullsFirst = $ordering === ICollection::ASC_NULLS_FIRST || $ordering === ICollection::DESC_NULLS_FIRST ? 1 : -1;
$result = $_b === null ? $nullsFirst : -$nullsFirst;
} elseif ($comparator !== null) {
$result = $comparator->compare($_a, $_b) * $descReverse;
} elseif (is_int($_a) || is_float($_a) || is_int($_b) || is_float($_b)) {
$result = ($_a <=> $_b) * $descReverse;
} else {
Expand Down Expand Up @@ -149,22 +152,6 @@ public function normalizeValue(
$value = $property->convertToRawValue($value);
}
}
if (
(isset($propertyMetadata->types[DateTimeImmutable::class]) || isset($propertyMetadata->types[\Nextras\Dbal\Utils\DateTimeImmutable::class]))
&& $value !== null
) {
$converter = static function ($input): int {
if (!$input instanceof DateTimeInterface) {
$input = new DateTimeImmutable($input);
}
return $input->getTimestamp();
};
if (is_array($value)) {
$value = array_map($converter, $value);
} else {
$value = $converter($value);
}
}

return $value;
}
Expand Down
6 changes: 5 additions & 1 deletion src/Entity/AbstractEntity.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ public function setRawValue(string $name, $value): void
{
$property = $this->metadata->getProperty($name);

if (!isset($this->validated[$name])) {
$this->initProperty($property, $name, initValue: false);
}

if ($property->wrapper !== null) {
if ($this->data[$name] instanceof IProperty) {
$this->data[$name]->setRawValue($value);
Expand Down Expand Up @@ -401,7 +405,7 @@ public function onAfterRemove(): void
private function internalSetValue(PropertyMetadata $metadata, string $name, $value): void
{
if (!isset($this->validated[$name])) {
$this->initProperty($metadata, $name, /* $initValue = */ false);
$this->initProperty($metadata, $name, initValue: false);
}

$property = $this->data[$name];
Expand Down
6 changes: 3 additions & 3 deletions src/Entity/ImmutableDataTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Nextras\Orm\Entity\Reflection\EntityMetadata;
use Nextras\Orm\Entity\Reflection\PropertyMetadata;
use Nextras\Orm\Exception\InvalidArgumentException;
use Nextras\Orm\Exception\InvalidPropertyValueException;
use Nextras\Orm\Exception\InvalidStateException;
use Nextras\Orm\Model\MetadataStorage;

Expand Down Expand Up @@ -77,7 +78,7 @@ private function &internalGetValue(PropertyMetadata $metadata, string $name)
private function internalHasValue(PropertyMetadata $metadata, string $name): bool
{
if (!isset($this->validated[$name])) {
$this->initProperty($metadata, $name, false);
$this->initProperty($metadata, $name, initValue: false);
}

if ($this->data[$name] instanceof IPropertyContainer) {
Expand Down Expand Up @@ -107,8 +108,7 @@ private function internalHasValue(PropertyMetadata $metadata, string $name): boo
protected function validate(PropertyMetadata $metadata, string $name, &$value): void
{
if (!$metadata->isValid($value)) {
$class = get_class($this);
throw new InvalidArgumentException("Value for {$class}::\${$name} property is invalid.");
throw new InvalidPropertyValueException($metadata);
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/Entity/PropertyComparator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

namespace Nextras\Orm\Entity;


/**
* This interface can be implemented by a {@see IPropertyContainer} (a property wrapper) that encapsulates custom types.
*/
interface PropertyComparator
{
/**
* Compares its two values if they are equal. This enables equality comparison for multi-property values,
* like primary-proxied $id, when {@see compare} is not allowed.
*/
function equals(mixed $a, mixed $b): bool;


/**
* Compares its two arguments for order. Returns zero if the arguments are equal, a negative number if
* the first argument is less than the second, or a positive number if the first argument is greater
* than the second.
*/
function compare(mixed $a, mixed $b): int;
}
Loading

0 comments on commit 040ad5d

Please sign in to comment.