BranchedObject: initial import

This commit is contained in:
Thomas Gelf 2021-10-05 23:09:33 +02:00
parent b59dab535e
commit 649a5dbe5e
1 changed files with 387 additions and 0 deletions

View File

@ -0,0 +1,387 @@
<?php
namespace Icinga\Module\Director\Db\Branch;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Data\Db\DbObject;
use Icinga\Module\Director\Data\Db\DbObjectTypeRegistry;
use Icinga\Module\Director\Data\Json;
use Icinga\Module\Director\Db;
use Ramsey\Uuid\UuidInterface;
use stdClass;
class BranchedObject
{
/** @var UuidInterface */
protected $branchUuid;
/** @var ?DbObject */
protected $object;
/** @var ?stdClass */
protected $changes;
/** @var bool */
protected $branchDeleted;
/** @var bool */
protected $branchCreated;
/** @var UuidInterface */
private $objectUuid;
/** @var string */
private $objectTable;
/** @var bool */
private $loadedAsBranchedObject = false;
/**
* @param BranchActivity $activity
* @param Db $connection
* @return static
*/
public static function withActivity(BranchActivity $activity, Db $connection)
{
return self::loadOptional(
$connection,
$activity->getObjectTable(),
$activity->getObjectUuid(),
$activity->getBranchUuid()
)->applyActivity($activity, $connection);
}
public function store(Db $connection)
{
if ($this->object && ! $this->object->hasBeenModified() && empty($this->changes)) {
return false;
}
$db = $connection->getDbAdapter();
$properties = [
'branch_deleted' => $this->branchDeleted ? 'y' : 'n',
'branch_created' => $this->branchCreated ? 'y' : 'n',
] + $this->prepareChangedProperties();
$table = 'branched_' . $this->objectTable;
if ($this->loadedAsBranchedObject) {
return $db->update(
$table,
$properties,
$this->prepareWhereString($connection)
) === 1;
} else {
try {
return $db->insert($table, $this->prepareKeyProperties($connection) + $properties) === 1;
} catch (\Exception $e) {
var_dump($e->getMessage());
var_dump($this->prepareKeyProperties($connection) + $properties);
exit;
}
}
}
public function delete(Db $connection)
{
$db = $connection->getDbAdapter();
$table = 'branched_' . $this->objectTable;
$branchCreated = $db->fetchOne($this->filterQuery($db->select()->from($table, 'branch_created'), $connection));
// We do not want to nullify all properties, therefore: delete & insert
$db->delete($table, $this->prepareWhereString($connection));
if (! $branchCreated) {
// No need to insert a deleted object in case this object lived in this branch only
return $db->insert($table, $this->prepareKeyProperties($connection) + [
'branch_deleted' => 'y',
'branch_created' => 'n',
]) === 1;
}
return true;
}
protected function prepareKeyProperties(Db $connection)
{
return [
'uuid' => $connection->quoteBinary($this->objectUuid->getBytes()),
'branch_uuid' => $connection->quoteBinary($this->branchUuid->getBytes()),
];
}
protected function prepareWhereString(Db $connection)
{
$db = $connection->getDbAdapter();
$objectUuid = $connection->quoteBinary($this->objectUuid->getBytes());
$branchUuid = $connection->quoteBinary($this->branchUuid->getBytes());
return $db->quoteInto('uuid = ?', $objectUuid) . $db->quoteInto(' AND branch_uuid = ?', $branchUuid);
}
/**
* @param \Zend_Db_Select $query
* @param Db $connection
* @return \Zend_Db_Select
*/
protected function filterQuery(\Zend_Db_Select $query, Db $connection)
{
return $query->where('uuid = ?', $connection->quoteBinary($this->objectUuid->getBytes()))
->where('branch_uuid = ?', $connection->quoteBinary($this->branchUuid->getBytes()));
}
protected function prepareChangedProperties()
{
$properties = (array) $this->changes;
foreach (BranchSettings::ENCODED_DICTIONARIES as $property) {
$this->combineFlatDictionaries($properties, $property);
}
foreach (BranchSettings::ENCODED_DICTIONARIES as $property) {
if (isset($properties[$property])) {
$properties[$property] = Json::encode($properties[$property]);
}
}
foreach (BranchSettings::ENCODED_ARRAYS as $property) {
if (isset($properties[$property])) {
$properties[$property] = Json::encode($properties[$property]);
}
}
foreach (BranchSettings::RELATED_SETS as $property) {
if (isset($properties[$property])) {
$properties[$property] = Json::encode($properties[$property]);
}
}
$setNull = [];
if (array_key_exists('disabled', $properties) && $properties['disabled'] === null) {
unset($properties['disabled']);
}
foreach ($properties as $key => $value) {
if ($value === null) {
$setNull[] = $key;
}
}
if (empty($setNull)) {
$properties['set_null'] = null;
} else {
$properties['set_null'] = Json::encode($setNull);
}
return $properties;
}
protected function combineFlatDictionaries(&$properties, $prefix)
{
$vars = [];
$length = strlen($prefix) + 1;
foreach ($properties as $key => $value) {
if (substr($key, 0, $length) === "$prefix.") {
$vars[substr($key, $length)] = $value;
}
}
if (! empty($vars)) {
foreach (array_keys($vars) as $key) {
unset($properties["$prefix.$key"]);
}
$properties[$prefix] = (object) $vars;
}
}
public function applyActivity(BranchActivity $activity, Db $connection)
{
if ($activity->isActionDelete()) {
throw new \RuntimeException('Cannot apply a delete action');
}
if ($activity->isActionCreate()) {
if ($this->hasBeenTouchedByBranch()) {
throw new \RuntimeException('Cannot apply a CREATE activity to an already branched object');
} else {
$this->branchCreated = true;
}
}
foreach ($activity->getModifiedProperties()->jsonSerialize() as $key => $value) {
$this->changes[$key] = $value;
}
return $this;
}
/**
* @param Db $connection
* @param string $objectTable
* @param UuidInterface $uuid
* @param Branch $branch
* @return static
* @throws NotFoundError
*/
public static function load(Db $connection, $objectTable, UuidInterface $uuid, Branch $branch)
{
$object = static::loadOptional($connection, $objectTable, $uuid, $branch->getUuid());
if ($object->getOriginalDbObject() === null && ! $object->hasBeenTouchedByBranch()) {
throw new NotFoundError('Not found');
}
return $object;
}
/**
* @return bool
*/
public function hasBeenTouchedByBranch()
{
return $this->loadedAsBranchedObject;
}
/**
* @return bool
*/
public function hasBeenDeletedByBranch()
{
return $this->branchDeleted;
}
/**
* @return bool
*/
public function hasBeenCreatedByBranch()
{
return $this->branchCreated;
}
/**
* @return ?DbObject
*/
public function getOriginalDbObject()
{
return $this->object;
}
/**
* @return ?DbObject
*/
public function getBranchedDbObject(Db $connection)
{
if ($this->object) {
$branched = DbObjectTypeRegistry::newObject($this->objectTable, [], $connection);
// object_type first, to avoid:
// I can only assign for applied objects or objects with native support for assignments
if ($this->object->hasProperty('object_type')) {
$branched->set('object_type', $this->object->get('object_type'));
}
foreach ((array) $this->object->toPlainObject(false, true) as $key => $value) {
if ($key === 'object_type') {
continue;
}
$branched->set($key, $value);
}
$branched->set('id', $this->object->get('id'));
$branched->set('uuid', $this->object->get('uuid'));
} else {
$branched = DbObjectTypeRegistry::newObject($this->objectTable, [], $connection);
$branched->setUniqueId($this->objectUuid);
}
if ($this->changes === null) {
return $branched;
}
foreach ($this->changes as $key => $value) {
if ($key === 'set_null') {
if ($value !== null) {
foreach ($value as $k) {
$branched->set($k, null);
}
}
} else {
$branched->set($key, $value);
}
}
return $branched;
}
/**
* @return UuidInterface
*/
public function getBranchUuid()
{
return $this->branchUuid;
}
/**
* @param Db $connection
* @param string $table
* @param UuidInterface $uuid
* @param ?UuidInterface $branchUuid
* @return static
*/
protected static function loadOptional(
Db $connection,
$table,
UuidInterface $uuid,
UuidInterface $branchUuid = null
) {
$class = DbObjectTypeRegistry::classByType($table);
if ($row = static::optionalTableRowByUuid($connection, $table, $uuid)) {
$object = $class::fromDbRow((array) $row, $connection);
} else {
$object = null;
}
$self = new static();
$self->object = $object;
$self->objectUuid = $uuid;
$self->branchUuid = $branchUuid;
$self->objectTable = $table;
if ($branchUuid && $row = static::optionalBranchedTableRowByUuid($connection, $table, $uuid, $branchUuid)) {
if ($row->branch_deleted === 'y') {
$self->branchDeleted = true;
} elseif ($row->branch_created === 'y') {
$self->branchCreated = true;
}
$self->changes = BranchSettings::normalizeBranchedObjectFromDb($row);
$self->loadedAsBranchedObject = true;
}
return $self;
}
/**
* @param Db $connection
* @param string $table
* @param UuidInterface $uuid
* @return stdClass|boolean
*/
protected static function optionalTableRowByUuid(Db $connection, $table, UuidInterface $uuid)
{
$db = $connection->getDbAdapter();
return $db->fetchRow(
$db->select()->from($table)->where('uuid = ?', $connection->quoteBinary($uuid->getBytes()))
);
}
/**
* @param Db $connection
* @param string $table
* @param UuidInterface $uuid
* @return stdClass|boolean
*/
protected static function optionalBranchedTableRowByUuid(
Db $connection,
$table,
UuidInterface $uuid,
UuidInterface $branchUuid
) {
$db = $connection->getDbAdapter();
$query = $db->select()
->from("branched_$table")
->where('uuid = ?', $connection->quoteBinary($uuid->getBytes()))
->where('branch_uuid = ?', $connection->quoteBinary($branchUuid->getBytes()));
return $db->fetchRow($query);
}
protected function __construct()
{
}
}