mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
4e850f6011
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@102448 467b73ca-7a2a-4603-9d3b-597d59a354a9
160 lines
5.2 KiB
PHP
160 lines
5.2 KiB
PHP
<?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'
|
|
* cache, although the invalidation logic prefers speed over keeping valid data.
|
|
*
|
|
* 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
|
|
*/
|
|
class Aggregate extends ViewableData {
|
|
|
|
static $cache = null;
|
|
|
|
/** Build & cache the cache object */
|
|
protected static function cache() {
|
|
return self::$cache ? self::$cache : (self::$cache = Cache::factory('aggregate'));
|
|
}
|
|
|
|
/** 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'));
|
|
}
|
|
else {
|
|
$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, ClassInfo::ancestry($class));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
$query->select = array($attr);
|
|
$singleton->extend('augmentSQL', $query);
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Entry point for being called from a template.
|
|
*
|
|
* This gets the aggregate function
|
|
*
|
|
*/
|
|
public function XML_val($name, $args) {
|
|
$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) {
|
|
$fields = DataObject::custom_database_fields($class);
|
|
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\")");
|
|
|
|
$cachekey = sha1($query->sql());
|
|
$cache = self::cache();
|
|
|
|
if (!($result = $cache->load($cachekey))) {
|
|
$result = (string)$query->execute()->value(); if (!$result) $result = '0';
|
|
$cache->save($result, null, array('aggregate', $this->type));
|
|
}
|
|
|
|
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
|
|
*/
|
|
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);
|
|
}
|
|
|
|
$query->select = array($attr);
|
|
$query->groupby = array();
|
|
|
|
$singleton = singleton($this->type);
|
|
$singleton->extend('augmentSQL', $query);
|
|
|
|
return $query;
|
|
}
|
|
}
|