Skip to content

Commit

Permalink
Merge pull request #376 from nextras/aggregation_functions
Browse files Browse the repository at this point in the history
Collection Aggregations 🎉
  • Loading branch information
hrach committed Feb 16, 2020
2 parents 0a11c60 + 05bcab0 commit ae7f590
Show file tree
Hide file tree
Showing 19 changed files with 553 additions and 73 deletions.
24 changes: 24 additions & 0 deletions src/Collection/Functions/AvgAggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Collection\Functions;


class AvgAggregateFunction extends BaseAggregateFunction
{
public function __construct()
{
parent::__construct('AVG');
}


protected function calculateAggregation(array $values)
{
return \array_sum($values) / \count($values);
}
}
66 changes: 66 additions & 0 deletions src/Collection/Functions/BaseAggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Collection\Functions;

use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\InvalidArgumentException;


abstract class BaseAggregateFunction implements IArrayFunction, IQueryBuilderFunction
{
/** @var string */
private $sqlFunction;


protected function __construct(string $sqlFunction)
{
$this->sqlFunction = $sqlFunction;
}


/**
* @param array<number> $values
* @return number
*/
abstract protected function calculateAggregation(array $values);


public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $entity, array $args)
{
\assert(\count($args) === 1 && \is_string($args[0]));

$valueReference = $helper->getValue($entity, $args[0]);
if (!$valueReference->isMultiValue) {
throw new InvalidArgumentException('Aggregation is not called over has many relationship.');
}
\assert(\is_array($valueReference->value));

return $this->calculateAggregation($valueReference->value);
}


public function processQueryBuilderExpression(
DbalQueryBuilderHelper $helper,
QueryBuilder $builder,
array $args
): DbalExpressionResult
{
\assert(\count($args) === 1 && \is_string($args[0]));

$expression = $helper->processPropertyExpr($builder, $args[0]);
return new DbalExpressionResult(
["{$this->sqlFunction}(%ex)", $expression->args],
true
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,28 @@

use Nextras\Dbal\QueryBuilder\QueryBuilder;
use Nextras\Orm\Collection\Helpers\ArrayCollectionHelper;
use Nextras\Orm\Collection\Helpers\ConditionParserHelper;
use Nextras\Orm\Collection\Helpers\DbalExpressionResult;
use Nextras\Orm\Collection\Helpers\DbalQueryBuilderHelper;
use Nextras\Orm\Entity\IEntity;
use Nextras\Orm\InvalidArgumentException;


class ValueOperatorFunction implements IArrayFunction, IQueryBuilderFunction
class CompareFunction implements IArrayFunction, IQueryBuilderFunction
{
public const OPERATOR_EQUAL = '=';
public const OPERATOR_NOT_EQUAL = '!=';
public const OPERATOR_GREATER = '>';
public const OPERATOR_EQUAL_OR_GREATER = '>=';
public const OPERATOR_SMALLER = '<';
public const OPERATOR_EQUAL_OR_SMALLER = '<=';


public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $entity, array $args)
{
assert(count($args) === 3);
$operator = $args[0];
$valueReference = $helper->getValue($entity, $args[1]);
if ($valueReference === null) {
return false;
}
\assert(\count($args) === 3);
$operator = $args[1];

$valueReference = $helper->getValue($entity, $args[0]);
if ($valueReference->propertyMetadata !== null) {
$targetValue = $helper->normalizeValue($args[2], $valueReference->propertyMetadata, true);
} else {
Expand All @@ -49,25 +53,25 @@ public function processArrayExpression(ArrayCollectionHelper $helper, IEntity $e

private function arrayEvaluate(string $operator, $targetValue, $sourceValue): bool
{
if ($operator === ConditionParserHelper::OPERATOR_EQUAL) {
if ($operator === self::OPERATOR_EQUAL) {
if (is_array($targetValue)) {
return in_array($sourceValue, $targetValue, true);
} else {
return $sourceValue === $targetValue;
}
} elseif ($operator === ConditionParserHelper::OPERATOR_NOT_EQUAL) {
} elseif ($operator === self::OPERATOR_NOT_EQUAL) {
if (is_array($targetValue)) {
return !in_array($sourceValue, $targetValue, true);
} else {
return $sourceValue !== $targetValue;
}
} elseif ($operator === ConditionParserHelper::OPERATOR_GREATER) {
} elseif ($operator === self::OPERATOR_GREATER) {
return $sourceValue > $targetValue;
} elseif ($operator === ConditionParserHelper::OPERATOR_EQUAL_OR_GREATER) {
} elseif ($operator === self::OPERATOR_EQUAL_OR_GREATER) {
return $sourceValue >= $targetValue;
} elseif ($operator === ConditionParserHelper::OPERATOR_SMALLER) {
} elseif ($operator === self::OPERATOR_SMALLER) {
return $sourceValue < $targetValue;
} elseif ($operator === ConditionParserHelper::OPERATOR_EQUAL_OR_SMALLER) {
} elseif ($operator === self::OPERATOR_EQUAL_OR_SMALLER) {
return $sourceValue <= $targetValue;
} else {
throw new InvalidArgumentException();
Expand All @@ -86,8 +90,8 @@ public function processQueryBuilderExpression(
{
\assert(\count($args) === 3);

$operator = $args[0];
$expression = $helper->processPropertyExpr($builder, $args[1]);
$operator = $args[1];
$expression = $helper->processPropertyExpr($builder, $args[0]);

if ($expression->valueNormalizer !== null) {
$cb = $expression->valueNormalizer;
Expand All @@ -109,7 +113,7 @@ public function processQueryBuilderExpression(
$columns = null;
}

if ($operator === ConditionParserHelper::OPERATOR_EQUAL) {
if ($operator === self::OPERATOR_EQUAL) {
if (\is_array($value)) {
if ($value) {
if ($columns !== null) {
Expand All @@ -129,7 +133,7 @@ public function processQueryBuilderExpression(
return $expression->append('= %any', $value);
}

} elseif ($operator === ConditionParserHelper::OPERATOR_NOT_EQUAL) {
} elseif ($operator === self::OPERATOR_NOT_EQUAL) {
if (\is_array($value)) {
if ($value) {
if ($columns !== null) {
Expand Down
2 changes: 1 addition & 1 deletion src/Collection/Functions/ConjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private function normalizeFunctions(array $args): array
$processedArgs = [];
foreach ($args as $argName => $argValue) {
[$argName, $operator] = ConditionParserHelper::parsePropertyOperator($argName);
$processedArgs[] = [ValueOperatorFunction::class, $operator, $argName, $argValue];
$processedArgs[] = [CompareFunction::class, $argName, $operator, $argValue];
}
return $processedArgs;
}
Expand Down
24 changes: 24 additions & 0 deletions src/Collection/Functions/CountAggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Collection\Functions;


class CountAggregateFunction extends BaseAggregateFunction
{
public function __construct()
{
parent::__construct('COUNT');
}


protected function calculateAggregation(array $values)
{
return \count($values);
}
}
2 changes: 1 addition & 1 deletion src/Collection/Functions/DisjunctionOperatorFunction.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private function normalizeFunctions(array $args): array
$processedArgs = [];
foreach ($args as $argName => $argValue) {
[$argName, $operator] = ConditionParserHelper::parsePropertyOperator($argName);
$processedArgs[] = [ValueOperatorFunction::class, $operator, $argName, $argValue];
$processedArgs[] = [CompareFunction::class, $argName, $operator, $argValue];
}
return $processedArgs;
}
Expand Down
24 changes: 24 additions & 0 deletions src/Collection/Functions/MaxAggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Collection\Functions;


class MaxAggregateFunction extends BaseAggregateFunction
{
public function __construct()
{
parent::__construct('MAX');
}


protected function calculateAggregation(array $values)
{
return \max($values);
}
}
24 changes: 24 additions & 0 deletions src/Collection/Functions/MinAggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Collection\Functions;


class MinAggregateFunction extends BaseAggregateFunction
{
public function __construct()
{
parent::__construct('MIN');
}


protected function calculateAggregation(array $values)
{
return \min($values);
}
}
24 changes: 24 additions & 0 deletions src/Collection/Functions/SumAggregateFunction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types = 1);

/**
* This file is part of the Nextras\Orm library.
* @license MIT
* @link https://github.com/nextras/orm
*/

namespace Nextras\Orm\Collection\Functions;


class SumAggregateFunction extends BaseAggregateFunction
{
public function __construct()
{
parent::__construct('SUM');
}


protected function calculateAggregation(array $values)
{
return \array_sum($values);
}
}
31 changes: 18 additions & 13 deletions src/Collection/Helpers/ArrayCollectionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,8 @@ public function createSorter(array $expressions): Closure
$_b = $expression[0]->processArrayExpression($this, $b, $expression[2]);
} else {
\assert($expression[2] instanceof EntityMetadata);
$a_ref = $this->getValueByTokens($a, $expression[0], $expression[2]);
$b_ref = $this->getValueByTokens($b, $expression[0], $expression[2]);
if ($a_ref === null || $b_ref === null) {
throw new InvalidStateException('Comparing entities that should not be included in the result. Possible missing filtering configuration for required entity type based on Single Table Inheritance.');
}
$_a = $a_ref->value;
$_b = $b_ref->value;
$_a = $this->getValueByTokens($a, $expression[0], $expression[2])->value;
$_b = $this->getValueByTokens($b, $expression[0], $expression[2])->value;
}

$ordering = $expression[1];
Expand All @@ -100,10 +95,8 @@ public function createSorter(array $expressions): Closure
// By default, <=> sorts nulls at the beginning.
$nullsReverse = $ordering === ICollection::ASC_NULLS_FIRST || $ordering === ICollection::DESC_NULLS_FIRST ? 1 : -1;
$result = ($_a <=> $_b) * $nullsReverse;

} elseif (is_int($_a) || is_float($_a) || is_int($_b) || is_float($_b)) {
$result = ($_a <=> $_b) * $descReverse;

} else {
$result = ((string) $_a <=> (string) $_b) * $descReverse;
}
Expand All @@ -119,10 +112,9 @@ public function createSorter(array $expressions): Closure


/**
* Returns value reference, returns null when entity should not be evaluated at all because of STI condition.
* @param string|array $expr
*/
public function getValue(IEntity $entity, $expr): ?ArrayPropertyValueReference
public function getValue(IEntity $entity, $expr): ArrayPropertyValueReference
{
if (is_array($expr)) {
$function = array_shift($expr);
Expand Down Expand Up @@ -188,10 +180,23 @@ public function normalizeValue($value, PropertyMetadata $propertyMetadata, bool
/**
* @param string[] $expressionTokens
*/
private function getValueByTokens(IEntity $entity, array $expressionTokens, EntityMetadata $sourceEntityMeta): ?ArrayPropertyValueReference
private function getValueByTokens(
IEntity $entity,
array $expressionTokens,
EntityMetadata $sourceEntityMeta
): ArrayPropertyValueReference
{
if (!$entity instanceof $sourceEntityMeta->className) {
return null;
return new ArrayPropertyValueReference(
new class {
public function __toString()
{
return "undefined";
}
},
false,
null
);
}

$isMultiValue = false;
Expand Down
Loading

0 comments on commit ae7f590

Please sign in to comment.