diff --git a/library/Icinga/Repository/IniRepository.php b/library/Icinga/Repository/IniRepository.php index 8ff1936bf..04b25c5cb 100644 --- a/library/Icinga/Repository/IniRepository.php +++ b/library/Icinga/Repository/IniRepository.php @@ -5,6 +5,7 @@ namespace Icinga\Repository; use Exception; use Icinga\Application\Config; +use Icinga\Data\ConfigObject; use Icinga\Data\Extensible; use Icinga\Data\Filter\Filter; use Icinga\Data\Updatable; @@ -18,6 +19,7 @@ use Icinga\Exception\StatementException; * Additionally provided features: * */ abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible @@ -29,6 +31,19 @@ abstract class IniRepository extends Repository implements Extensible, Updatable */ protected $ds; + /** + * The tables for which triggers are available when inserting, updating or deleting rows + * + * This may be initialized by concrete repository implementations and describes for which table names triggers + * are available. The repository attempts to find a method depending on the type of event and table for which + * to run the trigger. The name of such a method is expected to be declared using lowerCamelCase. + * (e.g. group_membership will be translated to onUpdateGroupMembership and groupmembership will be translated + * to onUpdateGroupmembership) The available events are onInsert, onUpdate and onDelete. + * + * @var array + */ + protected $triggers; + /** * Create a new INI repository object * @@ -45,6 +60,119 @@ abstract class IniRepository extends Repository implements Extensible, Updatable } } + /** + * Return the tables for which triggers are available when inserting, updating or deleting rows + * + * Calls $this->initializeTriggers() in case $this->triggers is null. + * + * @return array + */ + public function getTriggers() + { + if ($this->triggers === null) { + $this->triggers = $this->initializeTriggers(); + } + + return $this->triggers; + } + + /** + * Overwrite this in your repository implementation in case you need to initialize the triggers lazily + * + * @return array + */ + protected function initializeTriggers() + { + return array(); + } + + /** + * Run a trigger for the given table and row which is about to be inserted + * + * @param string $table + * @param ConfigObject $new + * + * @return ConfigObject + */ + public function onInsert($table, ConfigObject $new) + { + $trigger = $this->getTrigger($table, 'onInsert'); + if ($trigger !== null) { + $row = $this->$trigger($new); + if ($row !== null) { + $new = $row; + } + } + + return $new; + } + + /** + * Run a trigger for the given table and row which is about to be updated + * + * @param string $table + * @param ConfigObject $old + * @param ConfigObject $new + * + * @return ConfigObject + */ + public function onUpdate($table, ConfigObject $old, ConfigObject $new) + { + $trigger = $this->getTrigger($table, 'onUpdate'); + if ($trigger !== null) { + $row = $this->$trigger($old, $new); + if ($row !== null) { + $new = $row; + } + } + + return $new; + } + + /** + * Run a trigger for the given table and row which has been deleted + * + * @param string $table + * @param ConfigObject $old + * + * @return ConfigObject + */ + public function onDelete($table, ConfigObject $old) + { + $trigger = $this->getTrigger($table, 'onDelete'); + if ($trigger !== null) { + $this->$trigger($old); + } + } + + /** + * Return the name of the trigger method for the given table and event-type + * + * @param string $table The table name for which to return a trigger method + * @param string $event The name of the event type + * + * @return string + * + * @throws ProgrammingError In case the table is registered as having triggers but not any trigger is found + */ + protected function getTrigger($table, $event) + { + if (! in_array($table, $this->getTriggers())) { + return; + } + + $identifier = join('', array_map('ucfirst', explode('_', $table))); + if (! method_exists($this, $event . $identifier)) { + throw new ProgrammingError( + 'Cannot find any trigger for table "%s". Add a trigger or remove the table from %s::$triggers', + $table, + get_class($this) + ); + } + + return $event . $identifier; + } + /** * Insert the given data for the given target * @@ -64,7 +192,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section); } - $this->ds->setSection($section, $newData); + $this->ds->setSection($section, $this->onInsert($target, new ConfigObject($newData))); try { $this->ds->saveIni(); @@ -98,6 +226,7 @@ abstract class IniRepository extends Repository implements Extensible, Updatable $query->addFilter($this->requireFilter($target, $filter)); } + /** @var ConfigObject $config */ $newSection = null; foreach ($query as $section => $config) { if ($newSection !== null) { @@ -107,25 +236,32 @@ abstract class IniRepository extends Repository implements Extensible, Updatable ); } + $newConfig = clone $config; foreach ($newData as $column => $value) { if ($column === $keyColumn) { $newSection = $value; } else { - $config->$column = $value; + $newConfig->$column = $value; } } // This is necessary as the query result set contains the key column. - unset($config->$keyColumn); + unset($newConfig->$keyColumn); if ($newSection) { if ($this->ds->hasSection($newSection)) { throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection); } - $this->ds->removeSection($section)->setSection($newSection, $config); + $this->ds->removeSection($section)->setSection( + $newSection, + $this->onUpdate($target, $config, $newConfig) + ); } else { - $this->ds->setSection($section, $config); + $this->ds->setSection( + $section, + $this->onUpdate($target, $config, $newConfig) + ); } } @@ -151,8 +287,10 @@ abstract class IniRepository extends Repository implements Extensible, Updatable $query->addFilter($this->requireFilter($target, $filter)); } + /** @var ConfigObject $config */ foreach ($query as $section => $config) { $this->ds->removeSection($section); + $this->onDelete($target, $config); } try {