ENHANCEMENT Adding List() in the GenericTemplateGlobalProvider so

templates can use <% cached List(Member).max(LastEdited) %> instead
of deprecated "Aggregate" syntax.
This commit is contained in:
Sean Harvey 2012-05-19 12:55:49 +12:00
parent d7a8fa9266
commit 792c89e103
8 changed files with 67 additions and 30 deletions

View File

@ -193,6 +193,17 @@ The abstract `RelationList` class and its implementations `ManyManyList` and `Ha
are replacing the `ComponentSet` API, which is only relevant if you have instanciated these manually.
Relations are retrieved through the same way (e.g. `$myMember->Groups()`).
### Aggregate changes for partial caching in templates ###
`DataObject::Aggregate()` and `DataObject::RelationshipAggregate()` are now deprecated. To replace your deprecated aggregate calls
in PHP code, you should query with something like `Member::get()->max('LastEdited')`, that is, calling the aggregate on the `DataList` directly.
The same concept applies for replacing `RelationshipAggregate()`, just call the aggregate method on the relationship instead,
so something like `Member::get()->Groups()->max('LastEdited')`.
For partial caching in templates, the syntax `<% cached Aggregate(Page).Max(LastEdited) %>` has been deprecated. The new syntax is similar,
except you use `List()` instead of `Aggregate()`, and the aggregate call `Max()` is now lowercase, as in `max()`.
An example of the new syntax is `<% cached List(Page).max(LastEdited) %>`. Check `DataList` class for more aggregate methods to use.
### `SQLQuery` changes ###
`SQLQuery` has been changed so direct access to internal properties `$from`, `$select`, `$orderby` is

View File

@ -61,21 +61,21 @@ For example, if we have a menu, we want that menu to update whenever _any_ page
otherwise. By using aggregates, that's easy
:::ss
<% cached 'navigation', Aggregate(Page).Max(LastEdited) %>
<% cached 'navigation', List(Page).max(LastEdited) %>
If we have a block that shows a list of categories, we can make sure the cache updates every time a category is added or
edited
:::ss
<% cached 'categorylist', Aggregate(Category).Max(LastEdited) %>
<% cached 'categorylist', List(Category).max(LastEdited) %>
We can also calculate aggregates on relationships. A block that shows the current member's favourites needs to update
whenever the relationship Member::$has_many = array('Favourites' => Favourite') changes.
:::ss
<% cached 'favourites', CurrentMember.ID, CurrentMember.RelationshipAggregate(Favourites).Max(LastEdited) %>
<% cached 'favourites', CurrentMember.ID, CurrentMember.Favourites.max(LastEdited) %>
## Cache key calculated in controller
@ -89,7 +89,7 @@ logic into the controller
return implode('_', array(
'favourites',
$member->ID,
$member->RelationshipAggregate('Favourites')->Max('LastEdited')
$member->Favourites()->max('LastEdited')
));
}
@ -230,10 +230,10 @@ Can be re-written as:
:::ss
<% cached LastEdited %>
<% cached RelationshipAggregate(Children).Max(LastEdited) %>
<% cached Children.max(LastEdited) %>
<% control Children %>
$Name
<% end_control %>
<% end_cached %>
<% end_cached %>
<% end_cached %>

View File

@ -2659,16 +2659,18 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @deprecated 3.0 Use DataList::create and DataList to do your querying
*/
public function Aggregate($class = null) {
Deprecation::notice('3.0', 'Use DataList::create and DataList to do your querying instead.');
Deprecation::notice('3.0', 'Call aggregate methods on a DataList directly instead. In templates ' .
'an example of the new syntax is &lt% cached List(Member).max(LastEdited) %&gt instead (check partial-caching.md documentation ' .
'for more details.)');
if($class) {
if($class) {
$list = new DataList($class);
$list->setDataModel(DataModel::inst());
} else if(isset($this)) {
$list = new DataList(get_class($this));
$list->setDataModel($this->model);
}
else throw new InvalidArgumentException("DataObject::aggregate() must be called as an instance method or passed a classname");
else throw new InvalidArgumentException("DataObject::aggregate() must be called as an instance method or passed a classname");
return $list;
}
@ -2676,9 +2678,9 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @deprecated 3.0 Use DataList::create and DataList to do your querying
*/
public function RelationshipAggregate($relationship) {
Deprecation::notice('3.0', 'Use DataList::create and DataList to do your querying instead.');
Deprecation::notice('3.0', 'Call aggregate methods on a relationship directly instead.');
return $this->$relationship();
return $this->$relationship();
}
/**

View File

@ -315,7 +315,7 @@ class DataQuery {
* Note that this will issue a separate SELECT COUNT() query.
*/
function count() {
$baseClass = ClassInfo::baseDataClass($this->dataClass);
$baseClass = ClassInfo::baseDataClass($this->dataClass);
return $this->getFinalisedQuery()->count("DISTINCT \"$baseClass\".\"ID\"");
}
@ -324,8 +324,8 @@ class DataQuery {
*
* @param String $field Unquoted database column name (will be escaped automatically)
*/
function Max($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('MAX("%s")', Convert::raw2sql($field)))->execute()->value();
function max($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('MAX("%s")', Convert::raw2sql($field)))->execute()->value();
}
/**
@ -333,8 +333,8 @@ class DataQuery {
*
* @param String $field Unquoted database column name (will be escaped automatically)
*/
function Min($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('MIN("%s")', Convert::raw2sql($field)))->execute()->value();
function min($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('MIN("%s")', Convert::raw2sql($field)))->execute()->value();
}
/**
@ -342,8 +342,8 @@ class DataQuery {
*
* @param String $field Unquoted database column name (will be escaped automatically)
*/
function Avg($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('AVG("%s")', Convert::raw2sql($field)))->execute()->value();
function avg($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('AVG("%s")', Convert::raw2sql($field)))->execute()->value();
}
/**
@ -351,8 +351,8 @@ class DataQuery {
*
* @param String $field Unquoted database column name (will be escaped automatically)
*/
function Sum($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('SUM("%s")', Convert::raw2sql($field)))->execute()->value();
function sum($field) {
return $this->getFinalisedQuery()->aggregate(sprintf('SUM("%s")', Convert::raw2sql($field)))->execute()->value();
}
/**

View File

@ -1,7 +1,13 @@
<?php
/*
* A hierarchy of data types, to
* A hierarchy of data types, to...
*
* @deprecated. This is testing`
* {@link DataObject::Aggregate()} and {@link DataObject::RelationshipAggregate()}
* which are deprecated. Aggregates are handled directly by DataList instead.
* This test should be removed or merged into DataListTest once those functions are
* removed from DataObject.
*/
class AggregateTest_Foo extends DataObject implements TestOnly {
static $db = array(

View File

@ -54,7 +54,7 @@ class SSViewerTest extends SapphireTest {
$this->assertFalse((bool)trim($template), "Should be no content in this return.");
}
function testComments() {
$output = $this->render(<<<SS
This is my template<%-- this is a comment --%>This is some content<%-- this is another comment --%>This is the final content

View File

@ -3,7 +3,8 @@ class GenericTemplateGlobalProvider implements TemplateGlobalProvider {
public static function get_template_global_variables() {
return array(
'ModulePath'
'ModulePath',
'List' => 'getDataList'
);
}
@ -30,5 +31,24 @@ class GenericTemplateGlobalProvider implements TemplateGlobalProvider {
}
}
/**
* This allows templates to create a new `DataList` from a known
* DataObject class name, and call methods such as aggregates.
*
* The common use case is for partial caching:
* <code>
* <% cached List(Member).max(LastEdited) %>
* loop members here
* <% end_cached %>
* </code>
*
* @return DataList
*/
public static function getDataList($className) {
$list = new DataList($className);
$list->setDataModel(DataModel::inst());
return $list;
}
}

View File

@ -51,14 +51,13 @@ class SSViewer_Scope {
list($this->item, $this->itemIterator, $this->itemIteratorTotal, $this->popIndex, $this->upIndex, $this->currentIndex) = $this->itemStack[$this->localIndex];
array_splice($this->itemStack, $this->localIndex+1);
}
function getObj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
$on = $this->itemIterator ? $this->itemIterator->current() : $this->item;
return $on->obj($name, $arguments, $forceReturnedObject, $cache, $cacheName);
}
function obj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null){
function obj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {
switch ($name) {
case 'Up':
if ($this->upIndex === null) user_error('Up called when we\'re already at the top of the scope', E_USER_ERROR);
@ -72,13 +71,12 @@ class SSViewer_Scope {
default:
$this->item = $this->getObj($name, $arguments, $forceReturnedObject, $cache, $cacheName);
$this->itemIterator = null;
$this->upIndex = $this->currentIndex ? $this->currentIndex : count($this->itemStack)-1;
$this->currentIndex = count($this->itemStack);
break;
}
$this->itemStack[] = array($this->item, $this->itemIterator, $this->itemIteratorTotal, null, $this->upIndex, $this->currentIndex);
return $this;
}
@ -380,7 +378,6 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
// Then for iterator-specific overrides
else if (array_key_exists($property, self::$iteratorProperties)) {
$source = self::$iteratorProperties[$property];
if ($this->itemIterator) {
// Set the current iterator position and total (the object instance is the first item in the callable array)
$source['implementer']->iteratorProperties($this->itemIterator->key(), $this->itemIteratorTotal);
@ -423,6 +420,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
return $res;
}
}
function getObj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {