diff --git a/assets/contao/css/debug-uncompressed.css b/assets/contao/css/debug-uncompressed.css index 8aa636a71d..93983dfe5a 100644 --- a/assets/contao/css/debug-uncompressed.css +++ b/assets/contao/css/debug-uncompressed.css @@ -40,7 +40,7 @@ #debug .time { background:url("../../../system/themes/default/images/news.gif") left 0 no-repeat; } -#debug .memory { +#debug .memory,#debug .models { background:url("../../../system/themes/default/images/modules.gif") left 0 no-repeat; } #debug .db { diff --git a/assets/contao/css/debug.css b/assets/contao/css/debug.css index 81e684f980..ef5579bb76 100644 --- a/assets/contao/css/debug.css +++ b/assets/contao/css/debug.css @@ -1,2 +1,2 @@ /* Contao Open Source CMS, (c) 2005-2013 Leo Feyer, LGPL license */ -#debug *{color:#444;line-height:1}#debug>*{z-index:998}#debug p{margin:0;padding:12px 10px;border-top:1px solid #666;border-bottom:1px solid #bbb;position:fixed;bottom:260px;background:#ddd;background-image:-moz-linear-gradient(top,#eee,#ddd);background-image:-webkit-linear-gradient(top,#eee,#ddd);background-image:-o-linear-gradient(top,#eee,#ddd);background-image:-ms-linear-gradient(top,#eee,#ddd);background-image:linear-gradient(top,#eee,#ddd)}#debug p span{padding:1px 20px;font-size:11px;font-family:Verdana,sans-serif}#debug .info{background:url("../../../system/themes/default/images/show.gif") left 0 no-repeat}#debug .time{background:url("../../../system/themes/default/images/news.gif") left 0 no-repeat}#debug .memory{background:url("../../../system/themes/default/images/modules.gif") left 0 no-repeat}#debug .db{background:url("../../../system/themes/default/images/db.gif") left 0 no-repeat}#debug .rows{background:url("../../../system/themes/default/images/rows.gif") left 0 no-repeat}#debug div{height:260px;position:fixed;bottom:0;overflow:auto;background:#fff}#debug pre{padding:10px}#debug pre,#debug pre *{font-family:Courier,monospace}#debug.closed p{bottom:-1px}#debug.closed div{height:0}#debug #tog{position:absolute;top:2px;height:28px;right:28px;background:url("../../../system/themes/default/images/expand.gif") right 5px no-repeat;cursor:pointer}#debug.closed #tog{background-image:url("../../../system/themes/default/images/collapse.gif")} \ No newline at end of file +#debug *{color:#444;line-height:1}#debug>*{z-index:998}#debug p{margin:0;padding:12px 10px;border-top:1px solid #666;border-bottom:1px solid #bbb;position:fixed;bottom:260px;background:#ddd;background-image:-moz-linear-gradient(top,#eee,#ddd);background-image:-webkit-linear-gradient(top,#eee,#ddd);background-image:-o-linear-gradient(top,#eee,#ddd);background-image:-ms-linear-gradient(top,#eee,#ddd);background-image:linear-gradient(top,#eee,#ddd)}#debug p span{padding:1px 20px;font-size:11px;font-family:Verdana,sans-serif}#debug .info{background:url("../../../system/themes/default/images/show.gif") left 0 no-repeat}#debug .time{background:url("../../../system/themes/default/images/news.gif") left 0 no-repeat}#debug .memory,#debug .models{background:url("../../../system/themes/default/images/modules.gif") left 0 no-repeat}#debug .db{background:url("../../../system/themes/default/images/db.gif") left 0 no-repeat}#debug .rows{background:url("../../../system/themes/default/images/rows.gif") left 0 no-repeat}#debug div{height:260px;position:fixed;bottom:0;overflow:auto;background:#fff}#debug pre{padding:10px}#debug pre,#debug pre *{font-family:Courier,monospace}#debug.closed p{bottom:-1px}#debug.closed div{height:0}#debug #tog{position:absolute;top:2px;height:28px;right:28px;background:url("../../../system/themes/default/images/expand.gif") right 5px no-repeat;cursor:pointer}#debug.closed #tog{background-image:url("../../../system/themes/default/images/collapse.gif")} \ No newline at end of file diff --git a/system/docs/UPGRADE.md b/system/docs/UPGRADE.md index a954b56193..086b4088a1 100644 --- a/system/docs/UPGRADE.md +++ b/system/docs/UPGRADE.md @@ -78,3 +78,32 @@ public function addAliasButton($arrButtons) In case you have been using the "buttons_callback", please make sure to adjust your extension accordingly. + + +### `Model::save()` + +In Contao 3.0 and 3.1 it was possible to create two models for the same database +record by passing `true` to the `Model::save()` method. However, this could lead +to a loss of data in certain edge-cases, so we have decided to implement a model +registry to ensure that there is only one model per database record. + +The registry, however, requires to use the `clone` command to duplicate models, +therefore the `$blnForceInsert` argment had to be removed. If you have used it +in your custom extension, be sure to adjust the code accordingly. + +Usage in Contao 3.0 and 3.1: + +```php +$objPage = PageModel::findByPK(1); +$objPage->title = 'New page title'; +$objPage->save(true); +``` + +New usage as of Contao 3.2: + +```php +$objPage = PageModel::findByPK(1); +$objCopy = clone $objPage; +$objCopy->title = 'New page title'; +$objCopy->save(); +``` diff --git a/system/helper/ide_compat.php b/system/helper/ide_compat.php index b5f5cb7d04..eb09504e09 100644 --- a/system/helper/ide_compat.php +++ b/system/helper/ide_compat.php @@ -237,6 +237,7 @@ class Php extends \Contao\Files\Php {} namespace Model { class Collection extends \Contao\Model\Collection {} class QueryBuilder extends \Contao\Model\QueryBuilder {} + class Registry extends \Contao\Model\Registry {} } // devtools diff --git a/system/modules/core/config/autoload.php b/system/modules/core/config/autoload.php index da52f46c81..fb005481bd 100644 --- a/system/modules/core/config/autoload.php +++ b/system/modules/core/config/autoload.php @@ -131,6 +131,7 @@ 'Contao\Message' => 'system/modules/core/library/Contao/Message.php', 'Contao\Model\Collection' => 'system/modules/core/library/Contao/Model/Collection.php', 'Contao\Model\QueryBuilder' => 'system/modules/core/library/Contao/Model/QueryBuilder.php', + 'Contao\Model\Registry' => 'system/modules/core/library/Contao/Model/Registry.php', 'Contao\Model' => 'system/modules/core/library/Contao/Model.php', 'Contao\ModuleLoader' => 'system/modules/core/library/Contao/ModuleLoader.php', 'Contao\Pagination' => 'system/modules/core/library/Contao/Pagination.php', diff --git a/system/modules/core/library/Contao/Model.php b/system/modules/core/library/Contao/Model.php index 676d76d7d4..8618632f48 100644 --- a/system/modules/core/library/Contao/Model.php +++ b/system/modules/core/library/Contao/Model.php @@ -59,16 +59,16 @@ abstract class Model protected static $strPk = 'id'; /** - * Database result - * @var \Database\Result + * Data + * @var array */ - protected $objResult; + protected $arrData = array(); /** - * Data + * Modified keys * @var array */ - protected $arrData = array(); + protected $arrModified = array(); /** * Relations @@ -90,6 +90,8 @@ abstract class Model */ public function __construct(\Database\Result $objResult=null) { + $this->arrModified = array(); + $objRelations = new \DcaExtractor(static::$strTable); $this->arrRelations = $objRelations->getRelations(); @@ -120,23 +122,34 @@ public function __construct(\Database\Result $objResult=null) { $table = $this->arrRelations[$key]['table']; $strClass = static::getClassFromTable($table); + $intPk = $strClass::getPk(); // If the primary key is empty, set null (see #5356) - if (!isset($row[$strClass::getPk()])) + if (!isset($row[$intPk])) { $this->arrRelated[$key] = null; } else { - $this->arrRelated[$key] = new $strClass(); - $this->arrRelated[$key]->setRow($row); + $objRelated = \Model\Registry::getInstance()->fetch($table, $row[$intPk]); + + if ($objRelated !== null) + { + $objRelated->mergeRow($row); + } + else + { + $objRelated = new $strClass(); + $objRelated->setRow($row); + } + + $this->arrRelated[$key] = $objRelated; } } $this->setRow($arrData); // see #5439 + \Model\Registry::getInstance()->register($this); } - - $this->objResult = $objResult; } @@ -145,6 +158,7 @@ public function __construct(\Database\Result $objResult=null) */ public function __clone() { + $this->arrModified = array(); unset($this->arrData[static::$strPk]); } @@ -157,6 +171,17 @@ public function __clone() */ public function __set($strKey, $varValue) { + if ($this->$strKey === $varValue) + { + return; + } + + // Store the original value + if (!isset($this->arrModified[$strKey])) + { + $this->arrModified[$strKey] = $this->arrData[$strKey]; + } + $this->arrData[$strKey] = $varValue; } @@ -215,37 +240,51 @@ public static function getTable() /** - * Return the database result + * Return the current record as associative array * - * @return \Database\Result|null The database result object or null + * @return array The data record */ - public function getResult() + public function row() { - return $this->objResult; + return $this->arrData; } /** - * Return the current record as associative array + * Set the current record from an array * - * @return array The data record + * @param array $arrData The data record + * + * @return \Model The model object */ - public function row() + public function setRow(array $arrData) { - return $this->arrData; + foreach ($arrData as $k=>$v) + { + $this->$k = $v; + } + + return $this; } /** - * Set the current record from an array + * Set the current record from an array preserving modified but unsaved fields * * @param array $arrData The data record * * @return \Model The model object */ - public function setRow(array $arrData) + public function mergeRow(array $arrData) { - $this->arrData = $arrData; + foreach ($arrData as $k=>$v) + { + if (!isset($this->arrModified[$k]) && $this->arrData[$k] !== $v) + { + $this->$k = $v; + } + } + return $this; } @@ -264,24 +303,53 @@ public function current() /** * Save the current record * - * @param boolean $blnForceInsert Force creating a new record - * * @return \Model The model object + * + * @throws \InvalidArgumentException If an argument is passed */ - public function save($blnForceInsert=false) + public function save() { - $arrSet = $this->preSave($this->row()); + if (count(func_get_args())) + { + throw new \InvalidArgumentException('The $blnForceInsert argument has been removed (see system/docs/UPGRADE.md)'); + } - if (isset($this->{static::$strPk}) && !$blnForceInsert) + // The model is in the registry + if (\Model\Registry::getInstance()->isRegistered($this)) { + $arrRow = $this->row(); + $arrSet = array(); + + // Only update modified fields + foreach ($this->arrModified as $k=>$v) + { + $arrSet[$k] = $arrRow[$k]; + } + + $arrSet = $this->preSave($arrSet); + $intPk = $this->{static::$strPk}; + + // Track primary key changes + if (isset($this->arrModified[static::$strPk])) + { + $intPk = $this->arrModified[static::$strPk]; + } + + // Update the row \Database::getInstance()->prepare("UPDATE " . static::$strTable . " %s WHERE " . static::$strPk . "=?") ->set($arrSet) - ->execute($this->{static::$strPk}); + ->execute($intPk); $this->postSave(self::UPDATE); + $this->arrModified = array(); // reset after postSave() } + + // The model is not yet in the registry else { + $arrSet = $this->preSave($this->row()); + + // Insert a new row $stmt = \Database::getInstance()->prepare("INSERT INTO " . static::$strTable . " %s") ->set($arrSet) ->execute(); @@ -292,6 +360,9 @@ public function save($blnForceInsert=false) } $this->postSave(self::INSERT); + $this->arrModified = array(); // reset after postSave() + + \Model\Registry::getInstance()->register($this); } return $this; @@ -320,11 +391,7 @@ protected function postSave($intType) { if ($intType == self::INSERT) { - // Reload the model data (might have been modified by default values or triggers) - $res = \Database::getInstance()->prepare("SELECT * FROM " . static::$strTable . " WHERE " . static::$strPk . "=?") - ->execute($this->{static::$strPk}); - - $this->setRow($res->row()); + $this->refresh(); // might have been modified by default values or triggers } } @@ -336,11 +403,27 @@ protected function postSave($intType) */ public function delete() { + $intPk = $this->{static::$strPk}; + + if (isset($this->arrModified[static::$strPk])) + { + $intPk = $this->arrModified[static::$strPk]; + } + + // Delete the row $intAffected = \Database::getInstance()->prepare("DELETE FROM " . static::$strTable . " WHERE " . static::$strPk . "=?") - ->execute($this->{static::$strPk}) + ->execute($intPk) ->affectedRows; - $this->arrData[static::$strPk] = null; // see #6162 + if ($intAffected) + { + // Unregister the model + \Model\Registry::getInstance()->unregister($this); + + // Remove the primary key (see #6162) + $this->arrData[static::$strPk] = null; + } + return $intAffected; } @@ -407,6 +490,35 @@ public function getRelated($strKey, array $arrOptions=array()) } + /** + * Reload the data from the database discarding all modifications + */ + public function refresh() + { + $intPk = $this->{static::$strPk}; + + if (isset($this->arrModified[static::$strPk])) + { + $intPk = $this->arrModified[static::$strPk]; + } + + // Reload the database record + $res = \Database::getInstance()->prepare("SELECT * FROM " . static::$strTable . " WHERE " . static::$strPk . "=?") + ->execute($intPk); + + $this->setRow($res->row()); + } + + + /** + * Detach the model from the registry + */ + public function detach() + { + \Model\Registry::getInstance()->unregister($this); + } + + /** * Find a single record by its primary key * @@ -417,6 +529,14 @@ public function getRelated($strKey, array $arrOptions=array()) */ public static function findByPk($varValue, array $arrOptions=array()) { + // Try to load from the registry + $objModel = \Model\Registry::getInstance()->fetch(static::$strTable, $varValue); + + if ($objModel !== null) + { + return $objModel; + } + $arrOptions = array_merge ( array @@ -444,6 +564,17 @@ public static function findByPk($varValue, array $arrOptions=array()) */ public static function findByIdOrAlias($varId, array $arrOptions=array()) { + // Try to load from the registry + if (is_numeric($varId)) + { + $objModel = \Model\Registry::getInstance()->fetch(static::$strTable, $varId); + + if ($objModel !== null) + { + return $objModel; + } + } + $t = static::$strTable; $arrOptions = array_merge @@ -478,22 +609,48 @@ public static function findMultipleByIds($arrIds, array $arrOptions=array()) return null; } - $t = static::$strTable; + $arrRegistered = array(); + $arrUnregistered = array(); - $arrOptions = array_merge - ( - array + // Search for registered models + foreach ($arrIds as $intId) + { + $arrRegistered[$intId] = \Model\Registry::getInstance()->fetch(static::$strTable, $intId); + + if ($arrRegistered[$intId] === null) + { + $arrUnregistered[] = $intId; + } + } + + // Fetch only the missing models from the database + if (!empty($arrUnregistered)) + { + $t = static::$strTable; + + $arrOptions = array_merge ( - 'column' => array("$t.id IN(" . implode(',', array_map('intval', $arrIds)) . ")"), - 'value' => null, - 'order' => \Database::getInstance()->findInSet("$t.id", $arrIds), - 'return' => 'Collection' - ), + array + ( + 'column' => array("$t.id IN(" . implode(',', array_map('intval', $arrUnregistered)) . ")"), + 'value' => null, + 'order' => \Database::getInstance()->findInSet("$t.id", $arrIds), + 'return' => 'Collection' + ), - $arrOptions - ); + $arrOptions + ); - return static::find($arrOptions); + $arrMissing = static::find($arrOptions); + + foreach ($arrMissing as $objModel) + { + $intId = $objModel->{static::$strPk}; + $arrRegistered[$intId] = $objModel; + } + } + + return new \Model\Collection(array_filter(array_values($arrRegistered)), static::$strTable); } @@ -508,6 +665,24 @@ public static function findMultipleByIds($arrIds, array $arrOptions=array()) */ public static function findOneBy($strColumn, $varValue, array $arrOptions=array()) { + $intId = is_array($varValue) ? $varValue[0] : $varValue; + + // Try to load from the registry + if (is_array($strColumn)) + { + if (count($strColumn) == 1 && $strColumn[0] == static::$strPk) + { + return static::findByPk($intId, $arrOptions); + } + } + else + { + if ($strColumn == static::$strPk) + { + return static::findByPk($intId, $arrOptions); + } + } + $arrOptions = array_merge ( array @@ -658,11 +833,22 @@ protected static function find(array $arrOptions) if ($arrOptions['return'] == 'Model') { + $strPk = static::$strPk; + $intPk = $objResult->$strPk; + + // Try to load from the registry + $objModel = \Model\Registry::getInstance()->fetch(static::$strTable, $intPk); + + if ($objModel !== null) + { + return $objModel->mergeRow($objResult->row()); + } + return new static($objResult); } else { - return new \Model\Collection($objResult, static::$strTable); + return \Model\Collection::createFromDbResult($objResult, static::$strTable); } } diff --git a/system/modules/core/library/Contao/Model/Collection.php b/system/modules/core/library/Contao/Model/Collection.php index a6ad7a9062..9170a416a8 100644 --- a/system/modules/core/library/Contao/Model/Collection.php +++ b/system/modules/core/library/Contao/Model/Collection.php @@ -23,7 +23,7 @@ * @author Leo Feyer * @copyright Leo Feyer 2005-2013 */ -class Collection +class Collection implements \ArrayAccess, \Countable, \IteratorAggregate { /** @@ -32,24 +32,12 @@ class Collection */ protected $strTable; - /** - * Database result - * @var \Database\Result - */ - protected $objResult; - /** * Current index * @var integer */ protected $intIndex = -1; - /** - * End indicator - * @var boolean - */ - protected $blnDone = false; - /** * Models * @var array @@ -58,15 +46,27 @@ class Collection /** - * Store the database result and table name + * Create a new collection * - * @param \Database\Result $objResult The database result object - * @param string $strTable The table name + * @param array $arrModels An array of models + * @param string $strTable The table name + * + * @throws \InvalidArgumentException */ - public function __construct(\Database\Result $objResult, $strTable) + public function __construct(array $arrModels, $strTable) { - $this->objResult = $objResult; - $this->strTable = $strTable; + $arrModels = array_values($arrModels); + + foreach ($arrModels as $objModel) + { + if (!$objModel instanceof \Model) + { + throw new \InvalidArgumentException('Invalid type: ' . gettype($objModel)); + } + } + + $this->arrModels = $arrModels; + $this->strTable = $strTable; } @@ -129,13 +129,36 @@ public function __isset($strKey) /** - * Return the database result + * Create a new collection from a database result * - * @return \Database\Result|null The database result object or null + * @param \Database\Result $objResult The database result object + * @param string $strTable The table name + * + * @return \Model\Collection The model collection */ - public function getResult() + static public function createFromDbResult(\Database\Result $objResult, $strTable) { - return $this->objResult; + $arrModels = array(); + + while ($objResult->next()) + { + $strClass = \Model::getClassFromTable($strTable); + $strPk = $strClass::getPk(); + $intPk = $objResult->$strPk; + $objModel = \Model\Registry::getInstance()->fetch($strTable, $intPk); + + if ($objModel !== null) + { + $objModel->mergeRow($objResult->row()); + $arrModels[] = $objModel; + } + else + { + $arrModels[] = new $strClass($objResult); + } + } + + return new static($arrModels, $strTable); } @@ -232,7 +255,7 @@ public function getRelated($strKey) */ public function count() { - return $this->objResult->numRows; + return count($this->arrModels); } @@ -243,11 +266,6 @@ public function count() */ public function first() { - if (empty($this->arrModels)) - { - $this->fetchNext(); - } - $this->intIndex = 0; return $this; } @@ -293,18 +311,9 @@ public function current() */ public function next() { - if ($this->blnDone) - { - return false; - } - if (!isset($this->arrModels[$this->intIndex + 1])) { - if ($this->fetchNext() == false) - { - $this->blnDone = true; - return false; - } + return false; } ++$this->intIndex; @@ -319,12 +328,6 @@ public function next() */ public function last() { - if (!$this->blnDone) - { - while ($this->next()); - } - - $this->blnDone = true; $this->intIndex = count($this->arrModels) - 1; return $this; } @@ -338,7 +341,6 @@ public function last() public function reset() { $this->intIndex = -1; - $this->blnDone = false; return $this; } @@ -374,39 +376,62 @@ public function fetchEach($strKey) /** - * Fetch the next result row and create the model + * Fetch all columns of every row * - * @return boolean True if there was another row + * @return array An array with all rows and columns */ - protected function fetchNext() + public function fetchAll() { - if ($this->objResult->next() == false) + $this->reset(); + $return = array(); + + while ($this->next()) { - return false; + $return[] = $this->row(); } - $strClass = \Model::getClassFromTable($this->strTable); - $this->arrModels[$this->intIndex + 1] = new $strClass($this->objResult); + return $return; + } + - return true; + /** + * {@inheritdoc} + */ + public function offsetExists($offset) + { + return isset($this->arrModels[$offset]); } + /** + * {@inheritdoc} + */ + public function offsetGet($offset) + { + return $this->arrModels[$offset]; + } /** - * Fetch all column of each row - * - * @return array An array with all rows and columns + * {@inheritdoc} */ - public function fetchAll() + public function offsetSet($offset, $value) { - $this->reset(); - $return = array(); + throw new \RuntimeException('This collection is immutable'); + } - while ($this->next()) - { - $return[] = $this->row(); - } + /** + * {@inheritdoc} + */ + public function offsetUnset($offset) + { + throw new \RuntimeException('This collection is immutable'); + } - return $return; + + /** + * {@inheritdoc} + */ + public function getIterator() + { + return new \ArrayIterator($this->arrModels); } } diff --git a/system/modules/core/library/Contao/Model/Registry.php b/system/modules/core/library/Contao/Model/Registry.php new file mode 100644 index 0000000000..12eaf3712e --- /dev/null +++ b/system/modules/core/library/Contao/Model/Registry.php @@ -0,0 +1,178 @@ + + * @copyright Leo Feyer 2005-2013 + */ +class Registry implements \Countable +{ + + /** + * Object instance (Singleton) + * @var \Registry + */ + protected static $objInstance; + + /** + * Models by table and PK + * @var array + */ + protected $arrRegistry; + + /** + * Models by object hash + * @var array + */ + protected $arrIdentities; + + + /** + * Prevent direct instantiation (Singleton) + */ + protected function __construct() {} + + + /** + * Prevent cloning of the object (Singleton) + */ + final public function __clone() {} + + + /** + * Return the current object instance (Singleton) + * + * @return \Registry The object instance + */ + public static function getInstance() + { + if (static::$objInstance === null) + { + static::$objInstance = new static(); + } + + return static::$objInstance; + } + + + /** + * {@inheritdoc} + */ + public function count() + { + return count($this->arrIdentities); + } + + + /** + * Fetch a model by table name and primary key + * + * @param string $strTable The table name + * @param integer $intPk The primary key + * + * @return \Model|null The model or null + */ + public function fetch($strTable, $intPk) + { + if (isset($this->arrRegistry[$strTable][$intPk])) + { + return $this->arrRegistry[$strTable][$intPk]; + } + + return null; + } + + + /** + * Register a model in the registry + * + * @param \Model $objModel The model object + * + * @throws \RuntimeException If the instance exists already + */ + public function register(\Model $objModel) + { + $intObjectId = spl_object_hash($objModel); + + // The model has been registered already + if (isset($this->arrIdentities[$intObjectId])) + { + return; + } + + $strTable = $objModel->getTable(); + + if (!is_array($this->arrRegistry[$strTable])) + { + $this->arrRegistry[$strTable] = array(); + } + + $strPk = $objModel->getPk(); + $intPk = $objModel->$strPk; + + // Another model object is pointing to the DB record already + if (isset($this->arrRegistry[$strTable][$intPk])) + { + throw new \RuntimeException("The registry already contains an instance for $strTable::$strPk($intPk)"); + } + + $this->arrIdentities[$intObjectId] = $objModel; + $this->arrRegistry[$strTable][$intPk] = $objModel; + } + + + /** + * Unregister a model from the registry + * + * @param \Model $objModel The model object + */ + public function unregister(\Model $objModel) + { + $intObjectId = spl_object_hash($objModel); + + // The model is not registered + if (!isset($this->arrIdentities[$intObjectId])) + { + return; + } + + $strTable = $objModel->getTable(); + $strPk = $objModel->getPk(); + $intPk = $objModel->$strPk; + + unset($this->arrIdentities[$intObjectId]); + unset($this->arrRegistry[$strTable][$intPk]); + } + + + /** + * Check if a model is registered + * + * @param \Model $objModel The model object + * + * @return boolean True if the model is registered + */ + public function isRegistered(\Model $objModel) + { + $intObjectId = spl_object_hash($objModel); + return isset($this->arrIdentities[$intObjectId]); + } +} diff --git a/system/modules/core/library/Contao/Template.php b/system/modules/core/library/Contao/Template.php index 6862f91311..4e6f4a6534 100644 --- a/system/modules/core/library/Contao/Template.php +++ b/system/modules/core/library/Contao/Template.php @@ -330,9 +330,26 @@ public function output() $strUnit = 'ms'; } - $strDebug = '
' . "\n" - . '

Contao debug information Execution time: ' . $this->getFormattedNumber($intTime, 0) . ' ' . $strUnit . ' Memory usage: ' . $this->getReadableSize(memory_get_peak_usage()) . ' Database queries: ' . count($GLOBALS['TL_DEBUG']['database_queries']) . ' Rows: ' . $intReturned . ' returned, ' . $intAffected . ' affected  

' . "\n" - . '
' . "\n";
+			$strDebug = sprintf(
+				'
' + . '

' + . 'Execution time: %s %s' + . 'Memory usage: %s' + . 'Database queries: %d' + . 'Rows: %d returned, %s affected' + . 'Registered models: %d' + . ' ' + . '

' + . '
',
+				\Input::cookie('CONTAO_CONSOLE'),
+				$this->getFormattedNumber($intTime, 0),
+				$strUnit,
+				$this->getReadableSize(memory_get_peak_usage()),
+				count($GLOBALS['TL_DEBUG']['database_queries']),
+				$intReturned,
+				$intAffected,
+				\Model\Registry::getInstance()->count()
+			);
 
 			ksort($GLOBALS['TL_DEBUG']);
 
diff --git a/system/modules/core/models/PageModel.php b/system/modules/core/models/PageModel.php
index c4efb55b35..147412e6c9 100644
--- a/system/modules/core/models/PageModel.php
+++ b/system/modules/core/models/PageModel.php
@@ -326,7 +326,7 @@ public static function findPublishedSubpagesWithoutGuestsByPid($intPid, $blnShow
 			return null;
 		}
 
-		return new \Model\Collection($objSubpages, 'tl_page');
+		return \Model\Collection::createFromDbResult($objSubpages, 'tl_page');
 	}
 
 
@@ -418,7 +418,7 @@ public static function findParentsById($intId)
 			return null;
 		}
 
-		return new \Model\Collection($objPages, 'tl_page');
+		return \Model\Collection::createFromDbResult($objPages, 'tl_page');
 	}
 
 
diff --git a/system/modules/core/models/StyleSheetModel.php b/system/modules/core/models/StyleSheetModel.php
index 2f0a59c4b7..4323c45c11 100644
--- a/system/modules/core/models/StyleSheetModel.php
+++ b/system/modules/core/models/StyleSheetModel.php
@@ -55,6 +55,6 @@ public static function findByIds($arrIds)
 		$arrIds = array_map('intval', $arrIds);
 
 		$objResult = $objDatabase->execute("SELECT *, (SELECT tstamp FROM tl_theme WHERE tl_theme.id=tl_style_sheet.pid) AS tstamp3, (SELECT MAX(tstamp) FROM tl_style WHERE tl_style.pid=tl_style_sheet.id) AS tstamp2, (SELECT COUNT(*) FROM tl_style WHERE tl_style.selector='@font-face' AND tl_style.invisible='' AND tl_style.pid=tl_style_sheet.id) AS hasFontFace FROM tl_style_sheet WHERE id IN (" . implode(',', $arrIds) . ") ORDER BY " . $objDatabase->findInSet('id', $arrIds));
-		return new \Model\Collection($objResult, 'tl_style_sheet');
+		return \Model\Collection::createFromDbResult($objResult, 'tl_style_sheet');
 	}
 }