icingaweb2-module-director/library/Director/Objects/IcingaTemplateResolver.php

436 lines
11 KiB
PHP

<?php
namespace Icinga\Module\Director\Objects;
use Icinga\Exception\NotFoundError;
use Icinga\Module\Director\Db;
use Icinga\Module\Director\Exception\NestingError;
// TODO: move the 'type' layer to another class
class IcingaTemplateResolver
{
/** @var IcingaObject */
protected $object;
/** @var Db */
protected $connection;
/** @var \Zend_Db_Adapter_Abstract */
protected $db;
protected $type;
protected static $templates = array();
protected static $idIdx = array();
protected static $nameIdx = array();
protected static $idToName = array();
protected static $nameToId = array();
public function __construct(IcingaObject $object)
{
$this->setObject($object);
}
/**
* Set a specific object for this resolver instance
*/
public function setObject(IcingaObject $object)
{
$this->object = $object;
$this->type = $object->getShortTableName();
$this->table = $object->getTableName();
$this->connection = $object->getConnection();
$this->db = $this->connection->getDbAdapter();
return $this;
}
/**
* Forget all template relation of the given object type
*
* @return self
*/
public function clearCache()
{
unset(self::$templates[$this->type]);
return $this;
}
/**
* Fetch direct parents
*
* return IcingaObject[]
*/
public function fetchParents()
{
// TODO: involve lookup cache
$res = array();
$class = $this->object;
foreach ($this->listParentIds() as $id) {
$object = $class::loadWithAutoIncId($id, $this->connection);
$res[$object->object_name] = $object;
}
return $res;
}
public function listParentIds($id = null)
{
$this->requireTemplates();
if ($id === null) {
$object = $this->object;
if ($object->hasBeenLoadedFromDb()) {
if ($object->gotImports() && $object->imports()->hasBeenModified()) {
return $this->listUnstoredParentIds();
}
$id = $object->id;
} else {
return $this->listUnstoredParentIds();
}
}
$type = $this->type;
if (array_key_exists($id, self::$idIdx[$type])) {
return array_keys(self::$idIdx[$type][$id]);
}
return array();
}
protected function listUnstoredParentIds()
{
return $this->getIdsForNames($this->listUnstoredParentNames());
}
protected function listUnstoredParentNames()
{
return $this->object->imports()->listImportNames();
}
public function listParentNames($name = null)
{
$this->requireTemplates();
if ($name === null) {
$object = $this->object;
if ($object->hasBeenLoadedFromDb()) {
if ($object->gotImports() && $object->imports()->hasBeenModified()) {
return $this->listUnstoredParentNames();
}
$name = $object->object_name;
} else {
return $this->listUnstoredParentNames();
}
}
$type = $this->type;
if (array_key_exists($name, self::$nameIdx[$type])) {
return array_keys(self::$nameIdx[$type][$name]);
}
return array();
}
public function fetchResolvedParents()
{
if ($this->object->hasBeenLoadedFromDb()) {
return $this->fetchObjectsById($this->listResolvedParentIds());
}
$objects = array();
foreach ($this->object->imports()->getObjects() as $parent) {
$objects += $parent->templateResolver()->fetchResolvedParents();
}
return $objects;
}
public function listResolvedParentIds()
{
$this->requireTemplates();
return $this->resolveParentIds();
}
/**
* TODO: unfinished and not used currently
*
* @return array
*/
public function listResolvedParentNames()
{
$this->requireTemplates();
if (array_key_exists($name, self::$nameIdx[$type])) {
return array_keys(self::$nameIdx[$type][$name]);
}
return $this->resolveParentNames($this->object->object_name);
}
public function listParentsById($id)
{
return $this->getNamesForIds($this->resolveParentIds($id));
}
public function listParentsByName($name)
{
return $this->resolveParentNames($name);
}
/**
* Gives a list of all object ids met when walking through ancestry
*
* Tree is walked in import order, duplicates are preserved, the given
* objectId is added last
*
* @param int $objectId
*
* @return array
*/
public function listFullInheritancePathIds($objectId = null)
{
$parentIds = $this->listParentIds($objectId);
$ids = array();
foreach ($parentIds as $parentId) {
foreach ($this->listFullInheritancePathIds($parentId) as $id) {
$ids[] = $id;
}
$ids[] = $parentId;
}
$object = $this->object;
if ($objectId === null && $object->hasBeenLoadedFromDb()) {
$ids[] = $object->id;
}
return $ids;
}
public function listInheritancePathIds($objectId = null)
{
$full = $this->listFullInheritancePathIds($objectId);
$single = array();
foreach (array_reverse($full) as $id) {
if (array_key_exists($id, $single)) {
continue;
}
$single[$id] = $id;
}
return array_reverse(array_keys($single));
}
protected function resolveParentNames($name, &$list = array(), $path = array())
{
$this->assertNotInList($name, $path);
$path[$name] = true;
foreach ($this->listParentNames($name) as $parent) {
$list[$parent] = true;
$this->resolveParentNames($parent, $list, $path);
unset($list[$parent]);
$list[$parent] = true;
}
return array_keys($list);
}
protected function resolveParentIds($id = null, &$list = array(), $path = array())
{
if ($id === null) {
if ($check = $this->object->id) {
$this->assertNotInList($check, $path);
$path[$check] = true;
}
} else {
$this->assertNotInList($id, $path);
$path[$id] = true;
}
foreach ($this->listParentIds($id) as $parent) {
$list[$parent] = true;
$this->resolveParentIds($parent, $list, $path);
unset($list[$parent]);
$list[$parent] = true;
}
return array_keys($list);
}
protected function assertNotInList($id, & $list)
{
if (array_key_exists($id, $list)) {
$list = array_keys($list);
$list[] = $id;
if (is_numeric($id)) {
throw new NestingError(
'Loop detected: %s',
implode(' -> ', $this->getNamesForIds($list))
);
} else {
throw new NestingError(
'Loop detected: %s',
implode(' -> ', $list)
);
}
}
}
protected function getNamesForIds($ids)
{
$names = array();
foreach ($ids as $id) {
$names[] = $this->getNameForId($id);
}
return $names;
}
protected function getNameForId($id)
{
return self::$idToName[$this->type][$id];
}
protected function getIdsForNames($names)
{
$this->requireTemplates();
$ids = array();
foreach ($names as $name) {
$ids[] = $this->getIdForName($name);
}
return $ids;
}
protected function getIdForName($name)
{
if (! array_key_exists($name, self::$nameToId[$this->type])) {
throw new NotFoundError('There is no such import: "%s"', $name);
}
return self::$nameToId[$this->type][$name];
}
protected function fetchObjectsById($ids)
{
$class = $this->object;
$connection = $this->connection;
$res = array();
foreach ($ids as $id) {
$res[] = $class::loadWithAutoIncId($id, $connection);
}
return $res;
}
protected function requireTemplates()
{
if (! array_key_exists($this->type, self::$templates)) {
$this->prepareLookupTables();
}
return $this;
}
protected function prepareLookupTables()
{
$type = $this->type;
$templates = $this->fetchTemplates();
$ids = array();
$names = array();
$idToName = array();
$nameToId = array();
foreach ($templates as $row) {
$idToName[$row->id] = $row->name;
$nameToId[$row->name] = $row->id;
if ($row->parent_id === null) {
continue;
}
if (array_key_exists($row->id, $ids)) {
$ids[$row->id][$row->parent_id] = $row->parent_name;
$names[$row->name][$row->parent_name] = $row->parent_id;
} else {
$ids[$row->id] = array(
$row->parent_id => $row->parent_name
);
$names[$row->name] = array(
$row->parent_name => $row->parent_id
);
}
}
self::$idIdx[$type] = $ids;
self::$nameIdx[$type] = $names;
self::$templates[$type] = $templates;
self::$idToName[$type] = $idToName;
self::$nameToId[$type] = $nameToId;
}
protected function fetchTemplates()
{
$db = $this->db;
$type = $this->type;
$table = $this->object->getTableName();
$query = $db->select()->from(
array('o' => $table),
array(
'id' => 'o.id',
'name' => 'o.object_name',
'parent_id' => 'p.id',
'parent_name' => 'p.object_name',
)
)->joinLeft(
array('i' => $table . '_inheritance'),
'o.id = i.' . $type . '_id',
array()
)->joinLeft(
array('p' => $table),
'p.id = i.parent_' . $type . '_id',
array()
)->order('o.id')->order('i.weight');
return $db->fetchAll($query);
}
public function __destruct()
{
unset($this->connection);
unset($this->db);
unset($this->object);
}
public function refreshObject(IcingaObject $object)
{
$parentNames = $object->imports;
self::$nameIdx[$object->object_name] = $parentNames;
if ($object->hasBeenLoadedFromDb()) {
$id = $object->getId();
if (! is_array($id)) {
self::$idIdx[$id] = $this->getIdsForNames($parentNames);
}
}
return $this;
}
}