<?php
/*
* This file is part of Contao.
*
* (c) Leo Feyer
*
* @license LGPL-3.0-or-later
*/
namespace Contao\Model;
use Contao\Model;
/**
* Handle a set of models
*
* The class handles traversing a set of models and lazy loads the database
* result rows upon their first usage.
*/
class Registry implements \Countable
{
/**
* Object instance (Singleton)
* @var static
*/
protected static $objInstance;
/**
* Models by table and PK
* @var array
*/
protected $arrRegistry = array();
/**
* Aliases to PK's by table and column
* @var array
*/
protected $arrAliases = array();
/**
* Models by object hash
* @var array
*/
protected $arrIdentities = array();
/**
* Prevent direct instantiation (Singleton)
*/
protected function __construct()
{
}
/**
* Prevent cloning of the object (Singleton)
*/
final public function __clone()
{
}
/**
* Return the current object instance (Singleton)
*
* @return static The object instance
*/
public static function getInstance()
{
if (static::$objInstance === null)
{
static::$objInstance = new static();
}
return static::$objInstance;
}
/**
* Reset the registry
*
* @internal Do not use this method in your code; it should only be called during kernel.reset
*/
public function reset()
{
$this->arrRegistry = array();
$this->arrAliases = array();
$this->arrIdentities = array();
}
/**
* Count the elements
*
* @return integer The number of models
*/
#[\ReturnTypeWillChange]
public function count()
{
return \count($this->arrIdentities);
}
/**
* Fetch a model by table name and primary key
*
* @param string $strTable The table name
* @param mixed $varKey The key
* @param string $strAlias An optional alias
*
* @return Model|null The model or null
*/
public function fetch($strTable, $varKey, $strAlias=null)
{
/** @var Model $strClass */
$strClass = Model::getClassFromTable($strTable);
$strPk = $strClass::getPk();
// Search by PK (most common case)
if ($strAlias === null || $strAlias == $strPk)
{
return $this->arrRegistry[$strTable][$varKey] ?? null;
}
// Try to find the model by one of its aliases
return $this->fetchByAlias($strTable, $strAlias, $varKey);
}
/**
* Fetch a model by one of its aliases
*
* @param string $strTable The table name
* @param string $strAlias The alias
* @param mixed $varValue The alias value
*
* @return Model|null The model or null
*/
public function fetchByAlias($strTable, $strAlias, $varValue)
{
if (isset($this->arrAliases[$strTable][$strAlias][$varValue]))
{
$strPk = $this->arrAliases[$strTable][$strAlias][$varValue];
if (isset($this->arrRegistry[$strTable][$strPk]))
{
return $this->arrRegistry[$strTable][$strPk];
}
}
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_id($objModel);
// The model has been registered already
if (isset($this->arrIdentities[$intObjectId]))
{
return;
}
$strTable = $objModel->getTable();
if (!isset($this->arrAliases[$strTable]))
{
$this->arrAliases[$strTable] = array();
}
if (!isset($this->arrRegistry[$strTable]))
{
$this->arrRegistry[$strTable] = array();
}
$strPk = $objModel->getPk();
$varPk = $objModel->$strPk;
if ($varPk === null)
{
throw new \RuntimeException('The primary key has not been set');
}
// Another model object is pointing to the DB record already
if (isset($this->arrRegistry[$strTable][$varPk]))
{
throw new \RuntimeException("The registry already contains an instance for $strTable::$strPk($varPk)");
}
$this->arrIdentities[$intObjectId] = $objModel;
$this->arrRegistry[$strTable][$varPk] = $objModel;
// Allow the model to modify the registry
$objModel->onRegister($this);
}
/**
* Unregister a model from the registry
*
* @param Model $objModel The model object
*/
public function unregister(Model $objModel)
{
$intObjectId = spl_object_id($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], $this->arrRegistry[$strTable][$intPk]);
// Allow the model to modify the registry
$objModel->onUnregister($this);
}
/**
* 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_id($objModel);
return isset($this->arrIdentities[$intObjectId]);
}
/**
* Register an alias for a model
*
* @param Model $objModel The model object
* @param string $strAlias The alias name
* @param mixed $varValue The value of the alias
*
* @throws \RuntimeException If the alias is already registered
*/
public function registerAlias(Model $objModel, $strAlias, $varValue)
{
$strTable = $objModel->getTable();
$strPk = $objModel->getPk();
$varPk = $objModel->$strPk;
if (isset($this->arrAliases[$strTable][$strAlias][$varValue]))
{
throw new \RuntimeException("The registry already contains an alias for $strTable::$strPk($varPk) ($strAlias/$varValue)");
}
$this->arrAliases[$strTable][$strAlias][$varValue] = $varPk;
}
/**
* Unregister an alias
*
* @param Model $objModel The model object
* @param string $strAlias The alias name
* @param mixed $varValue The value of the alias
*
* @throws \InvalidArgumentException If the alias is not registered
*/
public function unregisterAlias(Model $objModel, $strAlias, $varValue)
{
$strTable = $objModel->getTable();
if (!isset($this->arrAliases[$strTable][$strAlias][$varValue]))
{
$strPk = $objModel->getPk();
$varPk = $objModel->$strPk;
throw new \RuntimeException("The registry does not contain an alias for $strTable::$strPk($varPk) ($strAlias/$varValue)");
}
unset($this->arrAliases[$strTable][$strAlias][$varValue]);
}
/**
* Check if an alias is registered
*
* @param Model $objModel The model object
* @param string $strAlias The alias name
* @param mixed $varValue The value of the alias
*
* @return boolean True if the alias is registered
*/
public function isRegisteredAlias(Model $objModel, $strAlias, $varValue)
{
$strTable = $objModel->getTable();
return isset($this->arrAliases[$strTable][$strAlias][$varValue]);
}
}
class_alias(Registry::class, 'Model\Registry');