*
  • Insert, update and delete capabilities
  • *
  • Triggers for inserts, updates and deletions
  • * */ abstract class IniRepository extends Repository implements Extensible, Updatable, Reducible { /** * Per-table datasources * * * array( * $table => $datasource * ) * * * @var Config[] */ protected $datasources = array(); /** * 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 * * @param Config $ds The data source to use * * @throws ProgrammingError In case the given data source does not provide a valid key column */ public function __construct(Config $ds) { parent::__construct($ds); // First! Due to init(). if (! $ds->getConfigObject()->getKeyColumn()) { throw new ProgrammingError('INI repositories require their data source to provide a valid key column'); } } /** * {@inheritDoc} */ public function getDataSource($table = null) { return isset($this->datasources[$table]) ? $this->datasources[$table] : parent::getDataSource($table); } /** * 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 */ 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)) { return $event . $identifier; } } /** * Insert the given data for the given target * * $data must provide a proper value for the data source's key column. * * @param string $target * @param array $data * * @throws StatementException In case the operation has failed */ public function insert($target, array $data) { $newData = $this->requireStatementColumns($target, $data); $config = $this->onInsert($target, new ConfigObject($newData)); $section = $this->extractSectionName($config, $target); if ($this->getDataSource($target)->hasSection($section)) { throw new StatementException(t('Cannot insert. Section "%s" does already exist'), $section); } $this->getDataSource($target)->setSection($section, $config); try { $this->getDataSource($target)->saveIni(); } catch (Exception $e) { throw new StatementException(t('Failed to insert. An error occurred: %s'), $e->getMessage()); } } /** * Update the target with the given data and optionally limit the affected entries by using a filter * * @param string $target * @param array $data * @param Filter $filter * * @throws StatementException In case the operation has failed */ public function update($target, array $data, Filter $filter = null) { $newData = $this->requireStatementColumns($target, $data); $keyColumn = $this->getDataSource($target)->getConfigObject()->getKeyColumn(); if ($filter === null && isset($newData[$keyColumn])) { throw new StatementException( t('Cannot update. Column "%s" holds a section\'s name which must be unique'), $keyColumn ); } $query = $this->getDataSource($target)->select(); if ($filter !== null) { $query->addFilter($this->requireFilter($target, $filter)); } /** @var ConfigObject $config */ $newSection = null; foreach ($query as $section => $config) { if ($newSection !== null) { throw new StatementException( t('Cannot update. Column "%s" holds a section\'s name which must be unique'), $keyColumn ); } $newConfig = clone $config; foreach ($newData as $column => $value) { if ($column === $keyColumn) { $newSection = $value; } else { $newConfig->$column = $value; } } // This is necessary as the query result set contains the key column. unset($newConfig->$keyColumn); if ($newSection) { if ($this->getDataSource($target)->hasSection($newSection)) { throw new StatementException(t('Cannot update. Section "%s" does already exist'), $newSection); } $this->getDataSource($target)->removeSection($section)->setSection( $newSection, $this->onUpdate($target, $config, $newConfig) ); } else { $this->getDataSource($target)->setSection( $section, $this->onUpdate($target, $config, $newConfig) ); } } try { $this->getDataSource($target)->saveIni(); } catch (Exception $e) { throw new StatementException(t('Failed to update. An error occurred: %s'), $e->getMessage()); } } /** * Delete entries in the given target, optionally limiting the affected entries by using a filter * * @param string $target * @param Filter $filter * * @throws StatementException In case the operation has failed */ public function delete($target, Filter $filter = null) { $query = $this->getDataSource($target)->select(); if ($filter !== null) { $query->addFilter($this->requireFilter($target, $filter)); } /** @var ConfigObject $config */ foreach ($query as $section => $config) { $this->getDataSource($target)->removeSection($section); $this->onDelete($target, $config); } try { $this->getDataSource($target)->saveIni(); } catch (Exception $e) { throw new StatementException(t('Failed to delete. An error occurred: %s'), $e->getMessage()); } } /** * Extract and return the section name off of the given $config * * @param array|ConfigObject $config * @param string $target The table whose datasource to get the key column from * * @return string * * @throws ProgrammingError In case no valid section name is available */ protected function extractSectionName( & $config, $target) { $keyColumn = $this->getDataSource($target)->getConfigObject()->getKeyColumn(); if (! is_array($config) && !$config instanceof ConfigObject) { throw new ProgrammingError('$config is neither an array nor a ConfigObject'); } elseif (! isset($config[$keyColumn])) { throw new ProgrammingError('$config does not provide a value for key column "%s"', $keyColumn); } $section = $config[$keyColumn]; unset($config[$keyColumn]); return $section; } }