Ranges: common class, fix store/delete issues

This removes duplicate code, fixes some issues introduced with UUIDs and does
some cleanup

fixes #2415
fixes #2442
This commit is contained in:
Thomas Gelf 2021-12-14 08:33:50 +01:00
parent eeaf3a84e3
commit bfdcafca52
3 changed files with 331 additions and 594 deletions

View File

@ -0,0 +1,321 @@
<?php
namespace Icinga\Module\Director\Objects;
use Exception;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
abstract class IcingaRanges
{
/** @var IcingaTimePeriodRange[]|IcingaScheduledDowntimeRange[] */
protected $storedRanges = [];
/** @var IcingaTimePeriodRange[]|IcingaScheduledDowntimeRange[] */
protected $ranges = [];
protected $modified = false;
protected $object;
private $position = 0;
protected $idx = array();
public function __construct(IcingaObject $object)
{
$this->object = $object;
}
#[\ReturnTypeWillChange]
public function count()
{
return count($this->ranges);
}
#[\ReturnTypeWillChange]
public function rewind()
{
$this->position = 0;
}
public function hasBeenModified()
{
return $this->modified;
}
#[\ReturnTypeWillChange]
public function current()
{
if (! $this->valid()) {
return null;
}
return $this->ranges[$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->ranges)) {
return $this->ranges[$key];
}
return null;
}
public function getValues()
{
$res = array();
foreach ($this->ranges as $key => $range) {
$res[$key] = $range->range_value;
}
return (object) $res;
}
public function getOriginalValues()
{
$res = array();
foreach ($this->storedRanges as $key => $range) {
$res[$key] = $range->range_value;
}
return (object) $res;
}
public function getRanges()
{
return $this->ranges;
}
protected function modify($range, $value)
{
$this->ranges[$range]->range_key = $value;
}
public function set($ranges)
{
foreach ($ranges as $range => $value) {
$this->setRange($range, $value);
}
$toDelete = array_diff(array_keys($this->ranges), array_keys($ranges));
foreach ($toDelete as $range) {
$this->remove($range);
}
return $this;
}
public function setRange($range, $value)
{
if ($value === null && array_key_exists($range, $this->ranges)) {
$this->remove($range);
return $this;
}
if (array_key_exists($range, $this->ranges)) {
if ($this->ranges[$range]->range_value === $value) {
return $this;
} else {
$this->ranges[$range]->range_value = $value;
$this->modified = true;
}
} else {
$class = $this->getRangeClass();
$this->ranges[$range] = $class::create([
$this->objectIdColumn => $this->object->id,
'range_key' => $range,
'range_value' => $value,
]);
$this->modified = true;
}
return $this;
}
/**
* Magic isset check
*
* @return boolean
*/
public function __isset($range)
{
return array_key_exists($range, $this->ranges);
}
public function remove($range)
{
if (array_key_exists($range, $this->ranges)) {
unset($this->ranges[$range]);
}
$this->modified = true;
$this->refreshIndex();
}
public function clear()
{
$this->ranges = [];
$this->modified = true;
$this->refreshIndex();
}
protected function refreshIndex()
{
ksort($this->ranges);
$this->idx = array_keys($this->ranges);
}
public function listRangesNames()
{
return array_keys($this->ranges);
}
public function getType()
{
return $this->object->getShortTableName();
}
public function getRangeTableName()
{
return $this->object->getTableName() . '_range';
}
protected function loadFromDb()
{
$db = $this->object->getDb();
$connection = $this->object->getConnection();
$table = $this->getRangeTableName();
$query = $db->select()
->from(['o' => $table])
->where('o.' . $this->objectIdColumn . ' = ?', (int) $this->object->get('id'))
->order('o.range_key');
$class = $this->getRangeClass();
$this->ranges = $class::loadAll($connection, $query, 'range_key');
$this->setBeingLoadedFromDb();
return $this;
}
public function setBeingLoadedFromDb()
{
$this->storedRanges = [];
foreach ($this->ranges as $key => $range) {
$range->setBeingLoadedFromDb();
$this->storedRanges[$key] = clone($range);
}
$this->refreshIndex();
$this->modified = false;
}
public function store()
{
$db = $this->object->getConnection();
if (! $this->hasBeenModified()) {
return false;
}
$table = $this->getRangeTableName();
$objectId = (int) $this->object->get('id');
$idColumn = $this->objectIdColumn;
foreach ($this->ranges as $range) {
if ($range->hasBeenModified()) {
$range->setConnection($db);
if ((int) $range->get($idColumn) !== $objectId) {
$range->set($idColumn, $objectId);
}
}
}
foreach (array_diff(array_keys($this->storedRanges), array_keys($this->ranges)) as $delete) {
$range = $this->storedRanges[$delete];
$range->setConnection($db);
$range->set($idColumn, $objectId);
$db->getDbAdapter()->delete($table, $range->createWhere());
unset($this->ranges[$delete]);
}
foreach ($this->ranges as $range) {
$range->store();
}
$this->setBeingLoadedFromDb();
return true;
}
/**
* @return IcingaTimePeriodRange|IcingaScheduledDowntimeRange|string IDE hint
*/
protected function getRangeClass()
{
return $this->rangeClass;
}
public static function loadForStoredObject(IcingaObject $object)
{
$ranges = new static($object);
return $ranges->loadFromDb();
}
public function toConfigString()
{
if (empty($this->ranges) && $this->object->object_type === 'template') {
return '';
}
$string = " ranges = {\n";
foreach ($this->ranges as $range) {
$string .= sprintf(
" %s\t= %s\n",
c::renderString($range->range_key),
c::renderString($range->range_value)
);
}
return $string . " }\n";
}
abstract public function toLegacyConfigString();
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());
}
}
}
}

View File

@ -3,306 +3,16 @@
namespace Icinga\Module\Director\Objects;
use Countable;
use Exception;
use Iterator;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
use Icinga\Module\Director\IcingaConfig\IcingaConfigRenderer;
class IcingaScheduledDowntimeRanges implements Iterator, Countable, IcingaConfigRenderer
class IcingaScheduledDowntimeRanges extends IcingaRanges implements Iterator, Countable, IcingaConfigRenderer
{
protected $storedRanges = [];
protected $ranges = [];
protected $modified = false;
protected $object;
private $position = 0;
protected $idx = array();
public function __construct(IcingaObject $object)
{
$this->object = $object;
}
#[\ReturnTypeWillChange]
public function count()
{
return count($this->ranges);
}
#[\ReturnTypeWillChange]
public function rewind()
{
$this->position = 0;
}
public function hasBeenModified()
{
return $this->modified;
}
#[\ReturnTypeWillChange]
public function current()
{
if (! $this->valid()) {
return null;
}
return $this->ranges[$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->ranges)) {
return $this->ranges[$key];
}
return null;
}
public function getValues()
{
$res = array();
foreach ($this->ranges as $key => $range) {
$res[$key] = $range->range_value;
}
return (object) $res;
}
public function getOriginalValues()
{
$res = array();
foreach ($this->storedRanges as $key => $range) {
$res[$key] = $range->range_value;
}
return (object) $res;
}
public function getRanges()
{
return $this->ranges;
}
protected function modify($range, $value)
{
$this->ranges[$range]->range_key = $value;
}
public function set($ranges)
{
foreach ($ranges as $range => $value) {
$this->setRange($range, $value);
}
$toDelete = array_diff(array_keys($this->ranges), array_keys($ranges));
foreach ($toDelete as $range) {
$this->remove($range);
}
return $this;
}
public function setRange($range, $value)
{
if ($value === null && array_key_exists($range, $this->ranges)) {
$this->remove($range);
return $this;
}
if (array_key_exists($range, $this->ranges)) {
if ($this->ranges[$range]->range_value === $value) {
return $this;
} else {
$this->ranges[$range]->range_value = $value;
$this->modified = true;
}
} else {
$this->ranges[$range] = IcingaScheduledDowntimeRange::create([
'scheduled_downtime_id' => $this->object->id,
'range_key' => $range,
'range_value' => $value,
]);
$this->modified = true;
}
return $this;
}
/**
* Magic isset check
*
* @return boolean
*/
public function __isset($range)
{
return array_key_exists($range, $this->ranges);
}
public function remove($range)
{
if (array_key_exists($range, $this->ranges)) {
unset($this->ranges[$range]);
}
$this->modified = true;
$this->refreshIndex();
}
public function clear()
{
$this->ranges = [];
$this->modified = true;
$this->refreshIndex();
}
protected function refreshIndex()
{
ksort($this->ranges);
$this->idx = array_keys($this->ranges);
}
protected function getRangeClass()
{
return __NAMESPACE__ . '\\Icinga' .ucfirst($this->object->getShortTableName()) . 'Range';
}
public function listRangesNames()
{
return array_keys($this->ranges);
}
public function getType()
{
return $this->object->getShortTableName();
}
public function getRangeTableName()
{
return $this->object->getTableName() . '_range';
}
protected function loadFromDb()
{
$db = $this->object->getDb();
$connection = $this->object->getConnection();
$table = $this->getRangeTableName();
$query = $db->select()
->from(['o' => $table])
->where('o.scheduled_downtime_id = ?', (int) $this->object->get('id'))
->order('o.range_key');
$this->ranges = IcingaScheduledDowntimeRange::loadAll($connection, $query, 'range_key');
$this->setBeingLoadedFromDb();
return $this;
}
public function setBeingLoadedFromDb()
{
$this->storedRanges = [];
foreach ($this->ranges as $key => $range) {
$this->storedRanges[$key] = clone($range);
}
}
public function store()
{
$db = $this->object->getConnection();
foreach ($this->ranges as $range) {
$range->scheduled_downtime_id = $this->object->id;
$range->store($db);
}
foreach (array_diff(array_keys($this->storedRanges), array_keys($this->ranges)) as $delete) {
$db->getDbAdapter()->delete(
'icinga_scheduled_downtime_range',
$this->storedRanges[$delete]->createWhere()
);
}
$this->storedRanges = $this->ranges;
return true;
}
protected function getClass()
{
return __NAMESPACE__ . '\\IcingaScheduledDowntimeRange';
}
public static function loadForStoredObject(IcingaObject $object)
{
$ranges = new static($object);
return $ranges->loadFromDb();
}
public function toConfigString()
{
if (empty($this->ranges) && $this->object->object_type === 'template') {
return '';
}
$string = " ranges = {\n";
foreach ($this->ranges as $range) {
$string .= sprintf(
" %s\t= %s\n",
c::renderString($range->range_key),
c::renderString($range->range_value)
);
}
return $string . " }\n";
}
protected $rangeClass = IcingaScheduledDowntimeRange::class;
protected $objectIdColumn = 'scheduled_downtime_id';
public function toLegacyConfigString()
{
return '';
}
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());
}
}
}
}

View File

@ -2,313 +2,19 @@
namespace Icinga\Module\Director\Objects;
use Exception;
use Iterator;
use Countable;
use Iterator;
use Icinga\Module\Director\IcingaConfig\IcingaConfigRenderer;
use Icinga\Module\Director\IcingaConfig\IcingaConfigHelper as c;
use Icinga\Module\Director\IcingaConfig\IcingaLegacyConfigHelper as c1;
class IcingaTimePeriodRanges implements Iterator, Countable, IcingaConfigRenderer
class IcingaTimePeriodRanges extends IcingaRanges implements Iterator, Countable, IcingaConfigRenderer
{
/** @var IcingaTimePeriodRange[] */
protected $storedRanges = array();
/** @var IcingaTimePeriodRange[] */
protected $ranges = array();
protected $modified = false;
protected $object;
private $position = 0;
protected $idx = array();
public function __construct(IcingaObject $object)
{
$this->object = $object;
}
#[\ReturnTypeWillChange]
public function count()
{
return count($this->ranges);
}
#[\ReturnTypeWillChange]
public function rewind()
{
$this->position = 0;
}
public function hasBeenModified()
{
return $this->modified;
}
#[\ReturnTypeWillChange]
public function current()
{
if (! $this->valid()) {
return null;
}
return $this->ranges[$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->ranges)) {
return $this->ranges[$key];
}
return null;
}
public function getValues()
{
$res = array();
foreach ($this->ranges as $key => $range) {
$res[$key] = $range->range_value;
}
return (object) $res;
}
public function getOriginalValues()
{
$res = array();
foreach ($this->storedRanges as $key => $range) {
$res[$key] = $range->range_value;
}
return (object) $res;
}
public function getRanges()
{
return $this->ranges;
}
protected function modify($range, $value)
{
$this->ranges[$range]->range_key = $value;
}
public function set($ranges)
{
foreach ($ranges as $range => $value) {
$this->setRange($range, $value);
}
$toDelete = array_diff(array_keys($this->ranges), array_keys($ranges));
foreach ($toDelete as $range) {
$this->remove($range);
}
return $this;
}
public function setRange($range, $value)
{
if ($value === null && array_key_exists($range, $this->ranges)) {
$this->remove($range);
return $this;
}
if (array_key_exists($range, $this->ranges)) {
if ($this->ranges[$range]->range_value === $value) {
return $this;
} else {
$this->ranges[$range]->range_value = $value;
$this->modified = true;
}
} else {
$this->ranges[$range] = IcingaTimePeriodRange::create(array(
'timeperiod_id' => $this->object->id,
'range_key' => $range,
'range_value' => $value,
));
$this->modified = true;
}
return $this;
}
/**
* Magic isset check
*
* @return boolean
*/
public function __isset($range)
{
return array_key_exists($range, $this->ranges);
}
public function remove($range)
{
if (array_key_exists($range, $this->ranges)) {
unset($this->ranges[$range]);
}
$this->modified = true;
$this->refreshIndex();
}
public function clear()
{
$this->ranges = array();
$this->modified = true;
$this->refreshIndex();
}
protected function refreshIndex()
{
ksort($this->ranges);
$this->idx = array_keys($this->ranges);
}
protected function getRangeClass()
{
return __NAMESPACE__ . '\\Icinga' .ucfirst($this->object->getShortTableName()) . 'Range';
}
public function listRangesNames()
{
return array_keys($this->ranges);
}
public function getType()
{
return $this->object->getShortTableName();
}
public function getRangeTableName()
{
return $this->object->getTableName() . '_range';
}
protected function loadFromDb()
{
$db = $this->object->getDb();
$connection = $this->object->getConnection();
$table = $this->getRangeTableName();
$query = $db->select()->from(
array('o' => $table)
)->where('o.timeperiod_id = ?', (int) $this->object->id)
->order('o.range_key');
/** @var IcingaTimePeriodRange $class */
$class = $this->getClass();
$this->ranges = $class::loadAll($connection, $query, 'range_key');
$this->setBeingLoadedFromDb();
return $this;
}
public function setBeingLoadedFromDb()
{
$this->storedRanges = [];
foreach ($this->ranges as $key => $range) {
$this->storedRanges[$key] = clone($range);
}
}
public function store()
{
$db = $this->object->getConnection();
foreach ($this->ranges as $range) {
$range->timeperiod_id = $this->object->id;
$range->store($db);
}
foreach (array_diff(array_keys($this->storedRanges), array_keys($this->ranges)) as $delete) {
$db->getDbAdapter()->delete(
'icinga_timeperiod_range',
$this->storedRanges[$delete]->createWhere()
);
}
$this->storedRanges = $this->ranges;
return true;
}
protected function getClass()
{
return __NAMESPACE__ . '\\IcingaTimePeriodRange';
}
public static function loadForStoredObject(IcingaObject $object)
{
$ranges = new static($object);
return $ranges->loadFromDb();
}
public function toConfigString()
{
if (empty($this->ranges) && $this->object->object_type === 'template') {
return '';
}
$string = " ranges = {\n";
foreach ($this->ranges as $range) {
$string .= sprintf(
" %s\t= %s\n",
c::renderString($range->range_key),
c::renderString($range->range_value)
);
}
return $string . " }\n";
}
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());
}
}
}
protected $rangeClass = IcingaTimePeriodRange::class;
protected $objectIdColumn = 'timeperiod_id';
public function toLegacyConfigString()
{
if (empty($this->ranges) && $this->object->object_type === 'template') {
if (empty($this->ranges) && $this->object->isTemplate()) {
return '';
}
@ -316,8 +22,8 @@ class IcingaTimePeriodRanges implements Iterator, Countable, IcingaConfigRendere
foreach ($this->ranges as $range) {
$out .= c1::renderKeyValue(
$range->range_key,
$range->range_value
$range->get('range_key'),
$range->get('range_value')
);
}
if ($out !== '') {