2010-04-12 07:04:34 +02:00
< ? php
/**
* Calculate an Aggregate on a particular field of a particular DataObject type ( possibly with
* an additional filter before the aggregate )
*
* Implemented as a class to provide a semi - DSL method of calculating Aggregates . DataObject has a function
* that will create & return an instance of this class with the DataObject type and filter set ,
* but at that point we don ' t yet know the aggregate function or field
*
* This class captures any XML_val or unknown call , and uses that to get the field & aggregate function &
* then return the result
*
* Two ways of calling
*
* $aggregate -> XML_val ( aggregate_function , array ( field )) - For templates
* $aggregate -> aggregate_function ( field ) - For PHP
*
* Aggregate functions are uppercased by this class , but are otherwise assumed to be valid SQL functions . Some
* examples : Min , Max , Avg
*
* Aggregates are often used as portions of a cacheblock key . They are therefore cached themselves , in the 'aggregate'
2010-11-23 06:43:59 +01:00
* cache , although the invalidation logic prefers speed over keeping valid data .
* The aggregate cache is cleared through { @ link DataObject :: flushCache ()}, which in turn is called on
* { @ link DataObject -> write ()} and other write operations .
* This means most write operations to the database will invalidate the cache correctly .
* Use { @ link Aggregate :: flushCache ()} to manually clear .
2010-04-12 07:04:34 +02:00
*
* NOTE : The cache logic uses tags , and so a backend that supports tags is required . Currently only the File
* backend ( and the two - level backend with the File backend as the slow store ) meets this requirement
*
* @ author hfried
2012-04-12 08:02:46 +02:00
* @ package framework
2010-04-23 02:11:41 +02:00
* @ subpackage core
2010-04-12 07:04:34 +02:00
*/
class Aggregate extends ViewableData {
static $cache = null ;
/** Build & cache the cache object */
protected static function cache () {
2010-04-13 03:48:06 +02:00
return self :: $cache ? self :: $cache : ( self :: $cache = SS_Cache :: factory ( 'aggregate' ));
2010-04-12 07:04:34 +02:00
}
/** Clear the aggregate cache for a given type, or pass nothing to clear all aggregate caches */
public static function flushCache ( $class = null ) {
$cache = self :: cache ();
if ( ! $class || $class == 'DataObject' ) {
$cache -> clean ( Zend_Cache :: CLEANING_MODE_MATCHING_TAG , array ( 'aggregate' ));
2011-12-22 22:38:37 +01:00
} else {
$tags = ClassInfo :: ancestry ( $class );
foreach ( $tags as & $tag ) {
$tag = preg_replace ( '/[^a-zA-Z0-9_]/' , '_' , $tag );
}
$cache -> clean ( Zend_Cache :: CLEANING_MODE_MATCHING_ANY_TAG , $tags );
2010-04-12 07:04:34 +02:00
}
}
/**
* Constructor
*
* @ param string $type The DataObject type we are building an aggregate for
* @ param string $filter ( optional ) An SQL filter to apply to the selected rows before calculating the aggregate
*/
public function __construct ( $type , $filter = '' ) {
$this -> type = $type ;
$this -> filter = $filter ;
parent :: __construct ();
}
/**
* Build the SQLQuery to calculate the aggregate
* This is a seperate function so that subtypes of Aggregate can change just this bit
* @ param string $attr - the SQL field statement for selection ( i . e . " MAX(LastUpdated) " )
* @ return SQLQuery
*/
protected function query ( $attr ) {
$singleton = singleton ( $this -> type );
$query = $singleton -> buildSQL ( $this -> filter );
2012-05-11 01:39:06 +02:00
$query -> setSelect ( $attr );
$query -> setOrderBy ( array ());
2010-04-12 07:04:34 +02:00
$singleton -> extend ( 'augmentSQL' , $query );
return $query ;
}
/**
* Entry point for being called from a template .
*
* This gets the aggregate function
*
*/
2012-03-27 06:04:11 +02:00
public function XML_val ( $name , $args = null , $cache = false ) {
2010-04-12 07:04:34 +02:00
$func = strtoupper ( strpos ( $name , 'get' ) === 0 ? substr ( $name , 3 ) : $name );
$attribute = $args ? $args [ 0 ] : 'ID' ;
$table = null ;
foreach ( ClassInfo :: ancestry ( $this -> type , true ) as $class ) {
2010-04-12 23:10:21 +02:00
$fields = DataObject :: database_fields ( $class );
2010-04-12 07:04:34 +02:00
if ( array_key_exists ( $attribute , $fields )) { $table = $class ; break ; }
}
if ( ! $table ) user_error ( " Couldn't find table for field $attribute in type { $this -> type } " , E_USER_ERROR );
$query = $this -> query ( " $func ( \" $table\ " . \ " $attribute\ " ) " );
2010-11-23 06:43:59 +01:00
// Cache results of this specific SQL query until flushCache() is triggered.
2010-04-12 07:04:34 +02:00
$cachekey = sha1 ( $query -> sql ());
$cache = self :: cache ();
if ( ! ( $result = $cache -> load ( $cachekey ))) {
$result = ( string ) $query -> execute () -> value (); if ( ! $result ) $result = '0' ;
2011-12-22 22:38:37 +01:00
$cache -> save ( $result , null , array ( 'aggregate' , preg_replace ( '/[^a-zA-Z0-9_]/' , '_' , $this -> type )));
2010-04-12 07:04:34 +02:00
}
return $result ;
}
/**
* Entry point for being called from PHP .
*/
public function __call ( $method , $arguments ) {
return $this -> XML_val ( $method , $arguments );
}
}
/**
* A subclass of Aggregate that calculates aggregates for the result of a has_many query .
*
* @ author hfried
2012-04-12 08:02:46 +02:00
* @ package framework
2010-04-23 02:11:41 +02:00
* @ subpackage core
2010-04-12 07:04:34 +02:00
*/
class Aggregate_Relationship extends Aggregate {
/**
* Constructor
*
* @ param DataObject $object The object that has_many somethings that we ' re calculating the aggregate for
* @ param string $relationship The name of the relationship
* @ param string $filter ( optional ) An SQL filter to apply to the relationship rows before calculating the aggregate
*/
public function __construct ( $object , $relationship , $filter = '' ) {
$this -> object = $object ;
$this -> relationship = $relationship ;
$this -> has_many = $object -> has_many ( $relationship );
$this -> many_many = $object -> many_many ( $relationship );
if ( ! $this -> has_many && ! $this -> many_many ) user_error ( " Could not find relationship $relationship on object class { $object -> class } in Aggregate Relationship " , E_USER_ERROR );
parent :: __construct ( $this -> has_many ? $this -> has_many : $this -> many_many [ 1 ], $filter );
}
protected function query ( $attr ) {
if ( $this -> has_many ) {
$query = $this -> object -> getComponentsQuery ( $this -> relationship , $this -> filter );
}
else {
$query = $this -> object -> getManyManyComponentsQuery ( $this -> relationship , $this -> filter );
}
2012-05-11 01:39:06 +02:00
$query -> setSelect ( $attr );
$query -> setGroupBy ( array ());
2010-04-12 07:04:34 +02:00
$singleton = singleton ( $this -> type );
$singleton -> extend ( 'augmentSQL' , $query );
return $query ;
}
}