summaryrefslogtreecommitdiffstats
path: root/library/Director/Objects/IcingaObjectMultiRelations.php
diff options
context:
space:
mode:
Diffstat (limited to 'library/Director/Objects/IcingaObjectMultiRelations.php')
-rw-r--r--library/Director/Objects/IcingaObjectMultiRelations.php454
1 files changed, 454 insertions, 0 deletions
diff --git a/library/Director/Objects/IcingaObjectMultiRelations.php b/library/Director/Objects/IcingaObjectMultiRelations.php
new file mode 100644
index 0000000..a1ec9a2
--- /dev/null
+++ b/library/Director/Objects/IcingaObjectMultiRelations.php
@@ -0,0 +1,454 @@
+<?php
+
+namespace Icinga\Module\Director\Objects;
+
+use Exception;
+use Icinga\Exception\ProgrammingError;
+use Iterator;
+use Countable;
+use Icinga\Module\Director\IcingaConfig\IcingaConfigRenderer;
+use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
+use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
+
+class IcingaObjectMultiRelations implements Iterator, Countable, IcingaConfigRenderer
+{
+ protected $stored = array();
+
+ protected $relations = array();
+
+ protected $modified = false;
+
+ protected $object;
+
+ protected $propertyName;
+
+ protected $relatedObjectClass;
+
+ protected $relatedTableName;
+
+ protected $relationIdColumn;
+
+ protected $relatedShortName;
+
+ protected $legacyPropertyName;
+
+ private $position = 0;
+
+ private $db;
+
+ protected $idx = array();
+
+ public function __construct(IcingaObject $object, $propertyName, $config)
+ {
+ $this->object = $object;
+ $this->propertyName = $propertyName;
+
+ if (is_object($config) || is_array($config)) {
+ foreach ($config as $k => $v) {
+ $this->$k = $v;
+ }
+ } else {
+ $this->relatedObjectClass = $config;
+ }
+ }
+
+ public function getObjects()
+ {
+ return $this->relations;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function count()
+ {
+ return count($this->relations);
+ }
+
+ #[\ReturnTypeWillChange]
+ public function rewind()
+ {
+ $this->position = 0;
+ }
+
+ public function hasBeenModified()
+ {
+ return $this->modified;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function current()
+ {
+ if (! $this->valid()) {
+ return null;
+ }
+
+ return $this->relations[$this->idx[$this->position]];
+ }
+
+ #[\ReturnTypeWillChange]
+ public function key()
+ {
+ return $this->idx[$this->position];
+ }
+
+ #[\ReturnTypeWillChange]
+ public function next()
+ {
+ ++$this->position;
+ }
+
+ #[\ReturnTypeWillChange]
+ public function valid()
+ {
+ return array_key_exists($this->position, $this->idx);
+ }
+
+ public function get($key)
+ {
+ if (array_key_exists($key, $this->relations)) {
+ return $this->relations[$key];
+ }
+
+ return null;
+ }
+
+ public function set($relation)
+ {
+ if (! is_array($relation)) {
+ if ($relation === null) {
+ $relation = array();
+ } else {
+ $relation = array($relation);
+ }
+ }
+
+ $existing = array_keys($this->relations);
+ $new = array();
+ $class = $this->getRelatedClassName();
+ $unset = array();
+
+ foreach ($relation as $k => $ro) {
+ if ($ro instanceof $class) {
+ $new[] = $ro->object_name;
+ } else {
+ if (empty($ro)) {
+ $unset[] = $k;
+ continue;
+ }
+
+ $new[] = $ro;
+ }
+ }
+
+ foreach ($unset as $k) {
+ unset($relation[$k]);
+ }
+
+ sort($existing);
+ sort($new);
+ if ($existing === $new) {
+ return $this;
+ }
+
+ $this->relations = array();
+ if (empty($relation)) {
+ $this->modified = true;
+ $this->refreshIndex();
+ return $this;
+ }
+
+ return $this->add($relation);
+ }
+
+ /**
+ * Magic isset check
+ *
+ * @return boolean
+ */
+ public function __isset($relation)
+ {
+ return array_key_exists($relation, $this->relations);
+ }
+
+ public function remove($relation)
+ {
+ if (array_key_exists($relation, $this->relations)) {
+ unset($this->relations[$relation]);
+ }
+
+ $this->modified = true;
+ $this->refreshIndex();
+ }
+
+ protected function refreshIndex()
+ {
+ ksort($this->relations);
+ $this->idx = array_keys($this->relations);
+ }
+
+ public function add($relation, $onError = 'fail')
+ {
+ // TODO: only one query when adding array
+ if (is_array($relation)) {
+ foreach ($relation as $r) {
+ $this->add($r, $onError);
+ }
+ return $this;
+ }
+
+ if (array_key_exists($relation, $this->relations)) {
+ return $this;
+ }
+
+ $class = $this->getRelatedClassName();
+
+ if ($relation instanceof $class) {
+ $this->relations[$relation->object_name] = $relation;
+ } elseif (is_string($relation)) {
+ $connection = $this->object->getConnection();
+ try {
+ // Related services can only be objects, used by ServiceSets
+ if ($class === 'Icinga\\Module\\Director\\Objects\\IcingaService') {
+ $relation = $class::load(array(
+ 'object_name' => $relation,
+ 'object_type' => 'template'
+ ), $connection);
+ } else {
+ $relation = $class::load($relation, $connection);
+ }
+ } catch (Exception $e) {
+ switch ($onError) {
+ case 'autocreate':
+ $relation = $class::create(array(
+ 'object_type' => 'object',
+ 'object_name' => $relation
+ ));
+ $relation->store($connection);
+ // TODO
+ case 'fail':
+ throw new ProgrammingError(
+ 'The related %s "%s" doesn\'t exists: %s',
+ $this->getRelatedTableName(),
+ $relation,
+ $e->getMessage()
+ );
+ break;
+ case 'ignore':
+ return $this;
+ }
+ }
+ } else {
+ throw new ProgrammingError(
+ 'Invalid related object: %s',
+ var_export($relation, 1)
+ );
+ }
+
+ $this->relations[$relation->object_name] = $relation;
+ $this->modified = true;
+ $this->refreshIndex();
+
+ return $this;
+ }
+
+ protected function getPropertyName()
+ {
+ return $this->propertyName;
+ }
+
+ protected function getRelatedShortName()
+ {
+ if ($this->relatedShortName === null) {
+ /** @var IcingaObject $class */
+ $class = $this->getRelatedClassName();
+ $this->relatedShortName = $class::create()->getShortTableName();
+ }
+
+ return $this->relatedShortName;
+ }
+
+ protected function getTableName()
+ {
+ return $this->object->getTableName() . '_' . $this->getRelatedShortName();
+ }
+
+ protected function getRelatedTableName()
+ {
+ if ($this->relatedTableName === null) {
+ /** @var IcingaObject $class */
+ $class = $this->getRelatedClassName();
+ $this->relatedTableName = $class::create()->getTableName();
+ }
+
+ return $this->relatedTableName;
+ }
+
+ protected function getRelationIdColumn()
+ {
+ if ($this->relationIdColumn === null) {
+ $this->relationIdColumn = $this->getRelatedShortName();
+ }
+
+ return $this->relationIdColumn;
+ }
+
+ public function listRelatedNames()
+ {
+ return array_keys($this->relations);
+ }
+
+ public function listOriginalNames()
+ {
+ return array_keys($this->stored);
+ }
+
+ public function getType()
+ {
+ return $this->object->getShortTableName();
+ }
+
+ protected function loadFromDb()
+ {
+ $db = $this->getDb();
+ $connection = $this->object->getConnection();
+
+ $type = $this->getType();
+ $objectIdCol = $type . '_id';
+ $relationIdCol = $this->getRelationIdColumn() . '_id';
+
+ $query = $db->select()->from(
+ array('r' => $this->getTableName()),
+ array()
+ )->join(
+ array('ro' => $this->getRelatedTableName()),
+ sprintf('r.%s = ro.id', $relationIdCol),
+ '*'
+ )->where(
+ sprintf('r.%s = ?', $objectIdCol),
+ (int) $this->object->id
+ )->order('ro.object_name');
+
+ $class = $this->getRelatedClassName();
+ $this->relations = $class::loadAll($connection, $query, 'object_name');
+ $this->setBeingLoadedFromDb();
+
+ return $this;
+ }
+
+ public function store()
+ {
+ $db = $this->getDb();
+ $stored = array_keys($this->stored);
+ $relations = array_keys($this->relations);
+
+ $objectId = $this->object->id;
+ $type = $this->getType();
+ $objectCol = $type . '_id';
+ $relationCol = $this->getRelationIdColumn() . '_id';
+
+ $toDelete = array_diff($stored, $relations);
+ foreach ($toDelete as $relation) {
+ // We work with cloned objects. (why?)
+ // As __clone drops the id, we need to access original properties
+ $orig = $this->stored[$relation]->getOriginalProperties();
+ $where = sprintf(
+ $objectCol . ' = %d AND ' . $relationCol . ' = %d',
+ $objectId,
+ $orig['id']
+ );
+
+ $db->delete(
+ $this->getTableName(),
+ $where
+ );
+ }
+
+ $toAdd = array_diff($relations, $stored);
+ foreach ($toAdd as $related) {
+ $db->insert(
+ $this->getTableName(),
+ array(
+ $objectCol => $objectId,
+ $relationCol => $this->relations[$related]->id
+ )
+ );
+ }
+ $this->setBeingLoadedFromDb();
+
+ return true;
+ }
+
+ public function setBeingLoadedFromDb()
+ {
+ $this->stored = array();
+ foreach ($this->relations as $k => $v) {
+ $this->stored[$k] = clone($v);
+ }
+ }
+
+ protected function getRelatedClassName()
+ {
+ return __NAMESPACE__ . '\\' . $this->relatedObjectClass;
+ }
+
+ protected function getDb()
+ {
+ if ($this->db === null) {
+ $this->db = $this->object->getDb();
+ }
+
+ return $this->db;
+ }
+
+ public static function loadForStoredObject(IcingaObject $object, $propertyName, $relatedObjectClass)
+ {
+ $relations = new static($object, $propertyName, $relatedObjectClass);
+ return $relations->loadFromDb();
+ }
+
+ public function toConfigString()
+ {
+ $relations = array_keys($this->relations);
+
+ if (empty($relations)) {
+ return '';
+ }
+
+ return c::renderKeyValue($this->propertyName, c::renderArray($relations));
+ }
+
+ public function __toString()
+ {
+ try {
+ return $this->toConfigString();
+ } catch (Exception $e) {
+ trigger_error($e);
+ $previousHandler = set_exception_handler(
+ function () {
+ }
+ );
+ restore_error_handler();
+ if ($previousHandler !== null) {
+ call_user_func($previousHandler, $e);
+ die();
+ } else {
+ die($e->getMessage());
+ }
+ }
+ }
+
+ public function toLegacyConfigString()
+ {
+ $relations = array_keys($this->relations);
+
+ if (empty($relations)) {
+ return '';
+ }
+
+ if ($this->legacyPropertyName === null) {
+ return ' # not supported in legacy: ' .
+ c1::renderKeyValue($this->propertyName, c1::renderArray($relations), '');
+ }
+
+ return c1::renderKeyValue($this->legacyPropertyName, c1::renderArray($relations));
+ }
+}