Merge branch 'feature/inirepository-should-support-multiple-tables-13034'
resolves #13034
This commit is contained in:
commit
1fd0896a7d
|
@ -20,16 +20,28 @@ use Icinga\Exception\StatementException;
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Insert, update and delete capabilities</li>
|
* <li>Insert, update and delete capabilities</li>
|
||||||
* <li>Triggers for inserts, updates and deletions</li>
|
* <li>Triggers for inserts, updates and deletions</li>
|
||||||
|
* <li>Lazy initialization of table specific configs</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible
|
abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* The datasource being used
|
* The configuration files used as table specific datasources
|
||||||
*
|
*
|
||||||
* @var Config
|
* This must be initialized by concrete repository implementations, in the following format
|
||||||
|
* <code>
|
||||||
|
* array(
|
||||||
|
* 'table_name' => array(
|
||||||
|
* 'config' => 'name_of_the_ini_file_without_extension',
|
||||||
|
* 'keyColumn' => 'the_name_of_the_column_to_use_as_key_column',
|
||||||
|
* ['module' => 'the_name_of_the_module_if_any']
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* </code>
|
||||||
|
*
|
||||||
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $ds;
|
protected $configs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The tables for which triggers are available when inserting, updating or deleting rows
|
* The tables for which triggers are available when inserting, updating or deleting rows
|
||||||
|
@ -47,19 +59,73 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
||||||
/**
|
/**
|
||||||
* Create a new INI repository object
|
* Create a new INI repository object
|
||||||
*
|
*
|
||||||
* @param Config $ds The data source to use
|
* @param Config|null $ds The data source to use
|
||||||
*
|
*
|
||||||
* @throws ProgrammingError In case the given data source does not provide a valid key column
|
* @throws ProgrammingError In case the given data source does not provide a valid key column
|
||||||
*/
|
*/
|
||||||
public function __construct(Config $ds)
|
public function __construct(Config $ds = null)
|
||||||
{
|
{
|
||||||
parent::__construct($ds); // First! Due to init().
|
parent::__construct($ds); // First! Due to init().
|
||||||
|
|
||||||
if (! $ds->getConfigObject()->getKeyColumn()) {
|
if ($ds !== null && !$ds->getConfigObject()->getKeyColumn()) {
|
||||||
throw new ProgrammingError('INI repositories require their data source to provide a valid key column');
|
throw new ProgrammingError('INI repositories require their data source to provide a valid key column');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* @return Config
|
||||||
|
*/
|
||||||
|
public function getDataSource($table = null)
|
||||||
|
{
|
||||||
|
if ($this->ds !== null) {
|
||||||
|
return parent::getDataSource($table);
|
||||||
|
}
|
||||||
|
|
||||||
|
$table = $table ?: $this->getBaseTable();
|
||||||
|
$configs = $this->getConfigs();
|
||||||
|
if (! isset($configs[$table])) {
|
||||||
|
throw new ProgrammingError('Config for table "%s" missing', $table);
|
||||||
|
} elseif (! $configs[$table] instanceof Config) {
|
||||||
|
$configs[$table] = $this->createConfig($configs[$table], $table);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $configs[$table]->getConfigObject()->getKeyColumn()) {
|
||||||
|
throw new ProgrammingError(
|
||||||
|
'INI repositories require their data source to provide a valid key column'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $configs[$table];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the configuration files used as table specific datasources
|
||||||
|
*
|
||||||
|
* Calls $this->initializeConfigs() in case $this->configs is null.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getConfigs()
|
||||||
|
{
|
||||||
|
if ($this->configs === null) {
|
||||||
|
$this->configs = $this->initializeConfigs();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->configs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrite this in your repository implementation in case you need to initialize the configs lazily
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
protected function initializeConfigs()
|
||||||
|
{
|
||||||
|
return array();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the tables for which triggers are available when inserting, updating or deleting rows
|
* Return the tables for which triggers are available when inserting, updating or deleting rows
|
||||||
*
|
*
|
||||||
|
@ -177,18 +243,20 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
||||||
*/
|
*/
|
||||||
public function insert($target, array $data)
|
public function insert($target, array $data)
|
||||||
{
|
{
|
||||||
|
$ds = $this->getDataSource($target);
|
||||||
$newData = $this->requireStatementColumns($target, $data);
|
$newData = $this->requireStatementColumns($target, $data);
|
||||||
$config = $this->onInsert($target, new ConfigObject($newData));
|
|
||||||
$section = $this->extractSectionName($config);
|
|
||||||
|
|
||||||
if ($this->ds->hasSection($section)) {
|
$config = $this->onInsert($target, new ConfigObject($newData));
|
||||||
|
$section = $this->extractSectionName($config, $ds->getConfigObject()->getKeyColumn());
|
||||||
|
|
||||||
|
if ($ds->hasSection($section)) {
|
||||||
throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section);
|
throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ds->setSection($section, $config);
|
$ds->setSection($section, $config);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->ds->saveIni();
|
$ds->saveIni();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
throw new StatementException(t('Failed to insert. An error occurred: %s'), $e->getMessage());
|
throw new StatementException(t('Failed to insert. An error occurred: %s'), $e->getMessage());
|
||||||
}
|
}
|
||||||
|
@ -205,8 +273,10 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
||||||
*/
|
*/
|
||||||
public function update($target, array $data, Filter $filter = null)
|
public function update($target, array $data, Filter $filter = null)
|
||||||
{
|
{
|
||||||
|
$ds = $this->getDataSource($target);
|
||||||
$newData = $this->requireStatementColumns($target, $data);
|
$newData = $this->requireStatementColumns($target, $data);
|
||||||
$keyColumn = $this->ds->getConfigObject()->getKeyColumn();
|
|
||||||
|
$keyColumn = $ds->getConfigObject()->getKeyColumn();
|
||||||
if ($filter === null && isset($newData[$keyColumn])) {
|
if ($filter === null && isset($newData[$keyColumn])) {
|
||||||
throw new StatementException(
|
throw new StatementException(
|
||||||
t('Cannot update. Column "%s" holds a section\'s name which must be unique'),
|
t('Cannot update. Column "%s" holds a section\'s name which must be unique'),
|
||||||
|
@ -214,7 +284,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$query = $this->ds->select();
|
$query = $ds->select();
|
||||||
if ($filter !== null) {
|
if ($filter !== null) {
|
||||||
$query->addFilter($this->requireFilter($target, $filter));
|
$query->addFilter($this->requireFilter($target, $filter));
|
||||||
}
|
}
|
||||||
|
@ -242,16 +312,16 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
||||||
unset($newConfig->$keyColumn);
|
unset($newConfig->$keyColumn);
|
||||||
|
|
||||||
if ($newSection) {
|
if ($newSection) {
|
||||||
if ($this->ds->hasSection($newSection)) {
|
if ($ds->hasSection($newSection)) {
|
||||||
throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection);
|
throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ds->removeSection($section)->setSection(
|
$ds->removeSection($section)->setSection(
|
||||||
$newSection,
|
$newSection,
|
||||||
$this->onUpdate($target, $config, $newConfig)
|
$this->onUpdate($target, $config, $newConfig)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->ds->setSection(
|
$ds->setSection(
|
||||||
$section,
|
$section,
|
||||||
$this->onUpdate($target, $config, $newConfig)
|
$this->onUpdate($target, $config, $newConfig)
|
||||||
);
|
);
|
||||||
|
@ -259,7 +329,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->ds->saveIni();
|
$ds->saveIni();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
throw new StatementException(t('Failed to update. An error occurred: %s'), $e->getMessage());
|
throw new StatementException(t('Failed to update. An error occurred: %s'), $e->getMessage());
|
||||||
}
|
}
|
||||||
|
@ -275,36 +345,66 @@ abstract class IniRepository extends Repository implements Extensible, Updatable
|
||||||
*/
|
*/
|
||||||
public function delete($target, Filter $filter = null)
|
public function delete($target, Filter $filter = null)
|
||||||
{
|
{
|
||||||
$query = $this->ds->select();
|
$ds = $this->getDataSource($target);
|
||||||
|
|
||||||
|
$query = $ds->select();
|
||||||
if ($filter !== null) {
|
if ($filter !== null) {
|
||||||
$query->addFilter($this->requireFilter($target, $filter));
|
$query->addFilter($this->requireFilter($target, $filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @var ConfigObject $config */
|
/** @var ConfigObject $config */
|
||||||
foreach ($query as $section => $config) {
|
foreach ($query as $section => $config) {
|
||||||
$this->ds->removeSection($section);
|
$ds->removeSection($section);
|
||||||
$this->onDelete($target, $config);
|
$this->onDelete($target, $config);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->ds->saveIni();
|
$ds->saveIni();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
throw new StatementException(t('Failed to delete. An error occurred: %s'), $e->getMessage());
|
throw new StatementException(t('Failed to delete. An error occurred: %s'), $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create and return a Config for the given meta and table
|
||||||
|
*
|
||||||
|
* @param array $meta
|
||||||
|
* @param string $table
|
||||||
|
*
|
||||||
|
* @return Config
|
||||||
|
*
|
||||||
|
* @throws ProgrammingError In case the given meta is invalid
|
||||||
|
*/
|
||||||
|
protected function createConfig(array $meta, $table)
|
||||||
|
{
|
||||||
|
if (! isset($meta['name'])) {
|
||||||
|
throw new ProgrammingError('Config file name missing for table "%s"', $table);
|
||||||
|
} elseif (! isset($meta['keyColumn'])) {
|
||||||
|
throw new ProgrammingError('Config key column name missing for table "%s"', $table);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($meta['module'])) {
|
||||||
|
$config = Config::module($meta['module'], $meta['name']);
|
||||||
|
} else {
|
||||||
|
$config = Config::app($meta['name']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$config->getConfigObject()->setKeyColumn($meta['keyColumn']);
|
||||||
|
return $config;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract and return the section name off of the given $config
|
* Extract and return the section name off of the given $config
|
||||||
*
|
*
|
||||||
* @param array|ConfigObject $config
|
* @param array|ConfigObject $config
|
||||||
|
* @param string $keyColumn
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*
|
*
|
||||||
* @throws ProgrammingError In case no valid section name is available
|
* @throws ProgrammingError In case no valid section name is available
|
||||||
*/
|
*/
|
||||||
protected function extractSectionName( & $config)
|
protected function extractSectionName( & $config, $keyColumn)
|
||||||
{
|
{
|
||||||
$keyColumn = $this->ds->getConfigObject()->getKeyColumn();
|
|
||||||
if (! is_array($config) && !$config instanceof ConfigObject) {
|
if (! is_array($config) && !$config instanceof ConfigObject) {
|
||||||
throw new ProgrammingError('$config is neither an array nor a ConfigObject');
|
throw new ProgrammingError('$config is neither an array nor a ConfigObject');
|
||||||
} elseif (! isset($config[$keyColumn])) {
|
} elseif (! isset($config[$keyColumn])) {
|
||||||
|
|
|
@ -214,9 +214,10 @@ abstract class Repository implements Selectable
|
||||||
/**
|
/**
|
||||||
* Create a new repository object
|
* Create a new repository object
|
||||||
*
|
*
|
||||||
* @param Selectable $ds The datasource to use
|
* @param Selectable|null $ds The datasource to use.
|
||||||
|
* Only pass null if you have overridden {@link getDataSource()}!
|
||||||
*/
|
*/
|
||||||
public function __construct(Selectable $ds)
|
public function __construct(Selectable $ds = null)
|
||||||
{
|
{
|
||||||
$this->ds = $ds;
|
$this->ds = $ds;
|
||||||
$this->aliasTableMap = array();
|
$this->aliasTableMap = array();
|
||||||
|
@ -263,12 +264,23 @@ abstract class Repository implements Selectable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the datasource being used
|
* Return the datasource being used for the given table
|
||||||
|
*
|
||||||
|
* @param string $table
|
||||||
*
|
*
|
||||||
* @return Selectable
|
* @return Selectable
|
||||||
|
*
|
||||||
|
* @throws ProgrammingError In case no datasource is available
|
||||||
*/
|
*/
|
||||||
public function getDataSource()
|
public function getDataSource($table = null)
|
||||||
{
|
{
|
||||||
|
if ($this->ds === null) {
|
||||||
|
throw new ProgrammingError(
|
||||||
|
'No data source available. It is required to either pass it'
|
||||||
|
. ' at initialization time or by overriding this method.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return $this->ds;
|
return $this->ds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -109,7 +109,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
*/
|
*/
|
||||||
public function from($target, array $columns = null)
|
public function from($target, array $columns = null)
|
||||||
{
|
{
|
||||||
$this->query = $this->repository->getDataSource()->select();
|
$this->query = $this->repository->getDataSource($target)->select();
|
||||||
$this->query->from($this->repository->requireTable($target, $this));
|
$this->query->from($this->repository->requireTable($target, $this));
|
||||||
$this->query->columns($this->prepareQueryColumns($target, $columns));
|
$this->query->columns($this->prepareQueryColumns($target, $columns));
|
||||||
$this->target = $target;
|
$this->target = $target;
|
||||||
|
@ -716,7 +716,7 @@ class RepositoryQuery implements QueryInterface, SortRules, FilterColumns, Itera
|
||||||
if ($this->query instanceof Traversable) {
|
if ($this->query instanceof Traversable) {
|
||||||
$iterator = $this->query;
|
$iterator = $this->query;
|
||||||
} else {
|
} else {
|
||||||
$iterator = $this->repository->getDataSource()->query($this->query);
|
$iterator = $this->repository->getDataSource($this->target)->query($this->query);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($iterator instanceof IteratorAggregate) {
|
if ($iterator instanceof IteratorAggregate) {
|
||||||
|
|
Loading…
Reference in New Issue