/library/Director/Objects/IcingaTemplateResolver.php

https://github.com/Icinga/icingaweb2-module-director · PHP · 479 lines · 360 code · 86 blank · 33 comment · 34 complexity · 4c6e057d8c945c3c300e7cf98bbeb714 MD5 · raw file

  1. <?php
  2. namespace Icinga\Module\Director\Objects;
  3. use Icinga\Application\Benchmark;
  4. use Icinga\Exception\NotFoundError;
  5. use Icinga\Module\Director\Db;
  6. use Icinga\Module\Director\Exception\NestingError;
  7. // TODO: move the 'type' layer to another class
  8. class IcingaTemplateResolver
  9. {
  10. /** @var IcingaObject */
  11. protected $object;
  12. /** @var Db */
  13. protected $connection;
  14. /** @var \Zend_Db_Adapter_Abstract */
  15. protected $db;
  16. protected $type;
  17. protected static $templates = array();
  18. protected static $idIdx = array();
  19. protected static $reverseIdIdx = array();
  20. protected static $nameIdx = array();
  21. protected static $idToName = array();
  22. protected static $nameToId = array();
  23. public function __construct(IcingaObject $object)
  24. {
  25. $this->setObject($object);
  26. }
  27. /**
  28. * Set a specific object for this resolver instance
  29. */
  30. public function setObject(IcingaObject $object)
  31. {
  32. $this->object = $object;
  33. $this->type = $object->getShortTableName();
  34. $this->table = $object->getTableName();
  35. $this->connection = $object->getConnection();
  36. $this->db = $this->connection->getDbAdapter();
  37. return $this;
  38. }
  39. /**
  40. * Forget all template relation of the given object type
  41. *
  42. * @return self
  43. */
  44. public function clearCache()
  45. {
  46. unset(self::$templates[$this->type]);
  47. return $this;
  48. }
  49. /**
  50. * Fetch direct parents
  51. *
  52. * return IcingaObject[]
  53. */
  54. public function fetchParents()
  55. {
  56. // TODO: involve lookup cache
  57. $res = array();
  58. $class = $this->object;
  59. foreach ($this->listParentIds() as $id) {
  60. $object = $class::loadWithAutoIncId($id, $this->connection);
  61. $res[$object->object_name] = $object;
  62. }
  63. return $res;
  64. }
  65. public function listParentIds($id = null)
  66. {
  67. $this->requireTemplates();
  68. if ($id === null) {
  69. $object = $this->object;
  70. if ($object->hasBeenLoadedFromDb()) {
  71. if ($object->gotImports() && $object->imports()->hasBeenModified()) {
  72. return $this->listUnstoredParentIds();
  73. }
  74. $id = $object->id;
  75. } else {
  76. return $this->listUnstoredParentIds();
  77. }
  78. }
  79. $type = $this->type;
  80. if (array_key_exists($id, self::$idIdx[$type])) {
  81. return array_keys(self::$idIdx[$type][$id]);
  82. }
  83. return array();
  84. }
  85. protected function listUnstoredParentIds()
  86. {
  87. return $this->getIdsForNames($this->listUnstoredParentNames());
  88. }
  89. protected function listUnstoredParentNames()
  90. {
  91. return $this->object->imports()->listImportNames();
  92. }
  93. public function listParentNames($name = null)
  94. {
  95. $this->requireTemplates();
  96. if ($name === null) {
  97. $object = $this->object;
  98. if ($object->hasBeenLoadedFromDb()) {
  99. if ($object->gotImports() && $object->imports()->hasBeenModified()) {
  100. return $this->listUnstoredParentNames();
  101. }
  102. $name = $object->object_name;
  103. } else {
  104. return $this->listUnstoredParentNames();
  105. }
  106. }
  107. $type = $this->type;
  108. if (array_key_exists($name, self::$nameIdx[$type])) {
  109. return array_keys(self::$nameIdx[$type][$name]);
  110. }
  111. return array();
  112. }
  113. public function fetchResolvedParents()
  114. {
  115. if ($this->object->hasBeenLoadedFromDb()) {
  116. return $this->fetchObjectsById($this->listResolvedParentIds());
  117. }
  118. $objects = array();
  119. foreach ($this->object->imports()->getObjects() as $parent) {
  120. $objects += $parent->templateResolver()->fetchResolvedParents();
  121. }
  122. return $objects;
  123. }
  124. public function listResolvedParentIds()
  125. {
  126. $this->requireTemplates();
  127. return $this->resolveParentIds();
  128. }
  129. /**
  130. * TODO: unfinished and not used currently
  131. *
  132. * @return array
  133. */
  134. public function listResolvedParentNames()
  135. {
  136. $this->requireTemplates();
  137. if (array_key_exists($name, self::$nameIdx[$type])) {
  138. return array_keys(self::$nameIdx[$type][$name]);
  139. }
  140. return $this->resolveParentNames($this->object->object_name);
  141. }
  142. public function listParentsById($id)
  143. {
  144. return $this->getNamesForIds($this->resolveParentIds($id));
  145. }
  146. public function listParentsByName($name)
  147. {
  148. return $this->resolveParentNames($name);
  149. }
  150. /**
  151. * Gives a list of all object ids met when walking through ancestry
  152. *
  153. * Tree is walked in import order, duplicates are preserved, the given
  154. * objectId is added last
  155. *
  156. * @param int $objectId
  157. *
  158. * @return array
  159. */
  160. public function listFullInheritancePathIds($objectId = null)
  161. {
  162. $parentIds = $this->listParentIds($objectId);
  163. $ids = array();
  164. foreach ($parentIds as $parentId) {
  165. foreach ($this->listFullInheritancePathIds($parentId) as $id) {
  166. $ids[] = $id;
  167. }
  168. $ids[] = $parentId;
  169. }
  170. $object = $this->object;
  171. if ($objectId === null && $object->hasBeenLoadedFromDb()) {
  172. $ids[] = $object->id;
  173. }
  174. return $ids;
  175. }
  176. public function listChildren($objectId = null)
  177. {
  178. if ($objectId === null) {
  179. $objectId = $this->object->id;
  180. }
  181. if (array_key_exists($objectId, self::$reverseIdIdx[$this->type])) {
  182. return self::$reverseIdIdx[$this->type][$objectId];
  183. } else {
  184. return array();
  185. }
  186. }
  187. public function listChildIds($objectId = null)
  188. {
  189. return array_keys($this->listChildren($objectId));
  190. }
  191. public function listDescendantIds($objectId = null)
  192. {
  193. if ($objectId === null) {
  194. $objectId = $this->object->id;
  195. }
  196. }
  197. public function listInheritancePathIds($objectId = null)
  198. {
  199. return $this->uniquePathIds($this->listFullInheritancePathIds($objectId));
  200. }
  201. public function uniquePathIds(array $ids)
  202. {
  203. $single = array();
  204. foreach (array_reverse($ids) as $id) {
  205. if (array_key_exists($id, $single)) {
  206. continue;
  207. }
  208. $single[$id] = $id;
  209. }
  210. return array_reverse(array_keys($single));
  211. }
  212. protected function resolveParentNames($name, &$list = array(), $path = array())
  213. {
  214. $this->assertNotInList($name, $path);
  215. $path[$name] = true;
  216. foreach ($this->listParentNames($name) as $parent) {
  217. $list[$parent] = true;
  218. $this->resolveParentNames($parent, $list, $path);
  219. unset($list[$parent]);
  220. $list[$parent] = true;
  221. }
  222. return array_keys($list);
  223. }
  224. protected function resolveParentIds($id = null, &$list = array(), $path = array())
  225. {
  226. if ($id === null) {
  227. if ($check = $this->object->id) {
  228. $this->assertNotInList($check, $path);
  229. $path[$check] = true;
  230. }
  231. } else {
  232. $this->assertNotInList($id, $path);
  233. $path[$id] = true;
  234. }
  235. foreach ($this->listParentIds($id) as $parent) {
  236. $list[$parent] = true;
  237. $this->resolveParentIds($parent, $list, $path);
  238. unset($list[$parent]);
  239. $list[$parent] = true;
  240. }
  241. return array_keys($list);
  242. }
  243. protected function assertNotInList($id, &$list)
  244. {
  245. if (array_key_exists($id, $list)) {
  246. $list = array_keys($list);
  247. $list[] = $id;
  248. if (is_numeric($id)) {
  249. throw new NestingError(
  250. 'Loop detected: %s',
  251. implode(' -> ', $this->getNamesForIds($list))
  252. );
  253. } else {
  254. throw new NestingError(
  255. 'Loop detected: %s',
  256. implode(' -> ', $list)
  257. );
  258. }
  259. }
  260. }
  261. protected function getNamesForIds($ids)
  262. {
  263. $names = array();
  264. foreach ($ids as $id) {
  265. $names[] = $this->getNameForId($id);
  266. }
  267. return $names;
  268. }
  269. protected function getNameForId($id)
  270. {
  271. return self::$idToName[$this->type][$id];
  272. }
  273. protected function getIdsForNames($names)
  274. {
  275. $this->requireTemplates();
  276. $ids = array();
  277. foreach ($names as $name) {
  278. $ids[] = $this->getIdForName($name);
  279. }
  280. return $ids;
  281. }
  282. protected function getIdForName($name)
  283. {
  284. if (! array_key_exists($name, self::$nameToId[$this->type])) {
  285. throw new NotFoundError('There is no such import: "%s"', $name);
  286. }
  287. return self::$nameToId[$this->type][$name];
  288. }
  289. protected function fetchObjectsById($ids)
  290. {
  291. $class = $this->object;
  292. $connection = $this->connection;
  293. $res = array();
  294. foreach ($ids as $id) {
  295. $res[] = $class::loadWithAutoIncId($id, $connection);
  296. }
  297. return $res;
  298. }
  299. protected function requireTemplates()
  300. {
  301. if (! array_key_exists($this->type, self::$templates)) {
  302. $this->prepareLookupTables();
  303. }
  304. return $this;
  305. }
  306. protected function prepareLookupTables()
  307. {
  308. $type = $this->type;
  309. Benchmark::measure("Preparing '$type' TemplateResolver lookup tables");
  310. $templates = $this->fetchTemplates();
  311. $ids = array();
  312. $reverseIds = array();
  313. $names = array();
  314. $idToName = array();
  315. $nameToId = array();
  316. foreach ($templates as $row) {
  317. $id = $row->id;
  318. $idToName[$id] = $row->name;
  319. $nameToId[$row->name] = $id;
  320. if ($row->parent_id === null) {
  321. continue;
  322. }
  323. $parentId = $row->parent_id;
  324. $parentName = $row->parent_name;
  325. if (array_key_exists($id, $ids)) {
  326. $ids[$id][$parentId] = $parentName;
  327. $names[$row->name][$parentName] = $row->parent_id;
  328. } else {
  329. $ids[$id] = array(
  330. $parentId => $parentName
  331. );
  332. $names[$row->name] = array(
  333. $parentName => $parentId
  334. );
  335. }
  336. if (! array_key_exists($parentId, $reverseIds)) {
  337. $reverseIds[$parentId] = array();
  338. }
  339. $reverseIds[$parentId][$id] = $row->name;
  340. }
  341. self::$idIdx[$type] = $ids;
  342. self::$reverseIdIdx[$type] = $reverseIds;
  343. self::$nameIdx[$type] = $names;
  344. self::$templates[$type] = $templates; // TODO: this is unused, isn't it?
  345. self::$idToName[$type] = $idToName;
  346. self::$nameToId[$type] = $nameToId;
  347. Benchmark::measure('Preparing TemplateResolver lookup tables');
  348. }
  349. protected function fetchTemplates()
  350. {
  351. $db = $this->db;
  352. $type = $this->type;
  353. $table = $this->object->getTableName();
  354. $query = $db->select()->from(
  355. array('o' => $table),
  356. array(
  357. 'id' => 'o.id',
  358. 'name' => 'o.object_name',
  359. 'parent_id' => 'p.id',
  360. 'parent_name' => 'p.object_name',
  361. )
  362. )->joinLeft(
  363. array('i' => $table . '_inheritance'),
  364. 'o.id = i.' . $type . '_id',
  365. array()
  366. )->joinLeft(
  367. array('p' => $table),
  368. 'p.id = i.parent_' . $type . '_id',
  369. array()
  370. )->order('o.id')->order('i.weight');
  371. return $db->fetchAll($query);
  372. }
  373. public function __destruct()
  374. {
  375. unset($this->connection);
  376. unset($this->db);
  377. unset($this->object);
  378. }
  379. public function refreshObject(IcingaObject $object)
  380. {
  381. $type = $object->getShortTableName();
  382. $name = $object->getObjectName();
  383. $parentNames = $object->imports;
  384. self::$nameIdx[$type][$name] = $parentNames;
  385. if ($object->hasBeenLoadedFromDb()) {
  386. $id = $object->getProperty('id');
  387. self::$idIdx[$type][$id] = $this->getIdsForNames($parentNames);
  388. self::$idToName[$type][$id] = $name;
  389. self::$nameToId[$type][$name] = $id;
  390. }
  391. return $this;
  392. }
  393. }