Skip to content

Instantly share code, notes, and snippets.

@julianwachholz
Created November 21, 2011 17:06
Show Gist options
  • Save julianwachholz/1383256 to your computer and use it in GitHub Desktop.
Save julianwachholz/1383256 to your computer and use it in GitHub Desktop.

Simple aggregate fields in FLOW3

Using these two classes, getting aggregate values out of your models is a piece of cake!

To use them, simply create a ghost-getter method and annotate it as follows:

/**
 * @FEATURE\Aggregate(property="entries", targetProperty="amount", function="SUM")
 * @return float
 */
public function getBalance() {
    return $this->balance;
}

I've used the example Entities for as mentioned in the Doctrine Blog: http://www.doctrine-project.org/blog/implementing-aggregate-fields (only modified it so it fits into FLOW3 and added this method)

Of course the $balance field doesn't actually exist, but that doesn't matter since the method call is intercepted by the AggregateField aspect. Now in your template, all you need to do to get the actual SUM of all entries' values is use the getter of the model instance as always:

<p>Balance: {account.balance -> f:format.currency(currencySign: '$')}</p>

Now I'm not yet sure if this is a rather huge performance gain or loss, so don't blame me. But at least you now have a very simple way of getting aggregates, right?

<?php
namespace Feature\Testing\Annotations;
/* *
* This script does not yet belong to the FLOW3 framework. *
* *
* It is free software; you can redistribute it and/or modify it under *
* the terms of the GNU Lesser General Public License, either version 3 *
* of the License, or (at your option) any later version. *
* *
* The TYPO3 project - inspiring people to share! *
* */
use Doctrine\Common\Annotations\Annotation as DoctrineAnnotation;
/**
* @Annotation
* @DoctrineAnnotation\Target({"METHOD"})
*/
final class Aggregate {
/**
* @var array
*/
private static $aggregateFunctions = array(
'AVG', 'COUNT', 'MIN', 'MAX', 'SUM'
);
/**
* The target property
* @var string
*/
public $property;
/**
* The target property to apply the aggregate function on
* @var string
*/
public $targetProperty;
/**
* The aggregate function to apply on the entities' properties
* @var string
*/
public $function;
/**
* @param array $values
*/
public function __construct(array $values) {
if (!isset($values['property']) || !isset($values['targetProperty']) || !isset($values['function'])) {
throw new \InvalidArgumentException('Aggregate annotations mut be given a property, targetProperty and a function.', 1321879001);
}
if (!in_array($values['function'], self::$aggregateFunctions)) {
throw new \InvalidArgumentException('Unknown aggregate function "'.$values['function'].'", expected one of ' . implode(', ', self::$aggregateFunctions), 1321885561);
}
$this->property = $values['property'];
$this->targetProperty = $values['targetProperty'];
$this->function = strtoupper($values['function']);
}
}
?>
<?php
namespace Feature\Testing\Aspect;
/* *
* This script does not yet belong to the FLOW3 framework. *
* *
* It is free software; you can redistribute it and/or modify it under *
* the terms of the GNU Lesser General Public License, either version 3 *
* of the License, or (at your option) any later version. *
* *
* The TYPO3 project - inspiring people to share! *
* */
use TYPO3\FLOW3\Annotations as FLOW3;
/**
* @FLOW3\Aspect
*/
class AggregateField {
const ANNOTATION_NAME = 'Feature\Testing\Annotations\Aggregate';
/**
* @var \TYPO3\FLOW3\Reflection\ReflectionService
* @FLOW3\Inject
*/
protected $reflectionService;
/**
* @var \Doctrine\ORM\EntityManager
*/
protected $entityManager;
/**
* @param \TYPO3\FLOW3\Persistence\Doctrine\EntityManagerFactory $entityManagerFactory
* @return void
*/
public function injectEntityManagerFactory(\TYPO3\FLOW3\Persistence\Doctrine\EntityManagerFactory $entityManagerFactory) {
$this->entityManager = $entityManagerFactory->create();
}
/**
* Creates an aggregate field value
*
* @param \TYPO3\FLOW3\AOP\JoinPointInterface $joinPoint
*
* @FLOW3\Around("methodAnnotatedWith(Feature\Testing\Annotations\Aggregate)")
*/
public function getAggregateValue(\TYPO3\FLOW3\AOP\JoinPointInterface $joinPoint) {
$proxy = $joinPoint->getProxy();
$entityClassname = get_class($proxy);
/** @var \Feature\Testing\Annotations\Aggregate $annotation */
$aggregate = $this->reflectionService->getMethodAnnotation($entityClassname, $joinPoint->getMethodName(), static::ANNOTATION_NAME);
$targetReflection = $this->reflectionService->getPropertyTagsValues($entityClassname, $aggregate->property);
preg_match('/\<(.*?)\>/i', $targetReflection['var'][0], $match);
$targetEntityClassName = trim($match[1], '\\');
$mappedByProperty = NULL;
foreach ($targetReflection as $annotation => $value) {
if (!isset($value[0])) {
continue;
}
if (strstr($value[0], 'mappedBy') !== FALSE) {
preg_match('/mappedBy="([A-Za-z0-9_]+)/', $value[0], $match);
if (!empty($match[1])) {
$mappedByProperty = $match[1];
}
break;
}
}
if (!$mappedByProperty) {
throw new \InvalidArgumentException('Property configured for an aggregate value "'.$joinPoint->getMethodName().'" lacks a relational annotation with the mappedBy attribute, which is required.'.print_r($targetReflection, TRUE), 1321888444);
}
$dql = "SELECT {$aggregate->function}(e.{$aggregate->targetProperty}) AS aggregate FROM {$targetEntityClassName} e "
. "WHERE e.{$mappedByProperty} = ?1";
return $this->entityManager->createQuery($dql)
->setParameter(1, $proxy)
->getSingleScalarResult();
}
}
?>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment