PageRenderTime 38ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/Nette/Component.php

https://github.com/DocX/nette
PHP | 365 lines | 173 code | 81 blank | 111 comment | 38 complexity | cbb6b43b17cdef9155ea5e95743a8689 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. <?php
  2. /**
  3. * Nette Framework
  4. *
  5. * Copyright (c) 2004, 2009 David Grudl (http://davidgrudl.com)
  6. *
  7. * This source file is subject to the "Nette license" that is bundled
  8. * with this package in the file license.txt.
  9. *
  10. * For more information please see http://nettephp.com
  11. *
  12. * @copyright Copyright (c) 2004, 2009 David Grudl
  13. * @license http://nettephp.com/license Nette license
  14. * @link http://nettephp.com
  15. * @category Nette
  16. * @package Nette
  17. */
  18. /*namespace Nette;*/
  19. require_once dirname(__FILE__) . '/IComponent.php';
  20. require_once dirname(__FILE__) . '/Object.php';
  21. /**
  22. * Component is the base class for all components.
  23. *
  24. * Components are objects implementing IComponent. They has parent component and own name.
  25. *
  26. * @author David Grudl
  27. * @copyright Copyright (c) 2004, 2009 David Grudl
  28. * @package Nette
  29. *
  30. * @property-read string $name
  31. * @property IComponentContainer $parent
  32. */
  33. abstract class Component extends Object implements IComponent
  34. {
  35. /** @var IComponentContainer */
  36. private $parent;
  37. /** @var string */
  38. private $name;
  39. /** @var array of [type => [obj, depth, path, is_monitored?]] */
  40. private $monitors = array();
  41. /**
  42. */
  43. public function __construct(IComponentContainer $parent = NULL, $name = NULL)
  44. {
  45. if ($parent !== NULL) {
  46. $parent->addComponent($this, $name);
  47. } elseif (is_string($name)) {
  48. $this->name = $name;
  49. }
  50. }
  51. /**
  52. * Lookup hierarchy for component specified by class or interface name.
  53. * @param string class/interface type
  54. * @param bool throw exception if component doesn't exist?
  55. * @return IComponent
  56. */
  57. public function lookup($type, $need = TRUE)
  58. {
  59. /**/fixNamespace($type);/**/
  60. if (!isset($this->monitors[$type])) { // not monitored or not processed yet
  61. $obj = $this->parent;
  62. $path = self::NAME_SEPARATOR . $this->name;
  63. $depth = 1;
  64. while ($obj !== NULL) {
  65. if ($obj instanceof $type) break;
  66. $path = self::NAME_SEPARATOR . $obj->getName() . $path;
  67. $depth++;
  68. $obj = $obj->getParent(); // IComponent::getParent()
  69. if ($obj === $this) $obj = NULL; // prevent cycling
  70. }
  71. if ($obj) {
  72. $this->monitors[$type] = array($obj, $depth, substr($path, 1), FALSE);
  73. } else {
  74. $this->monitors[$type] = array(NULL, NULL, NULL, FALSE); // not found
  75. }
  76. }
  77. if ($need && $this->monitors[$type][0] === NULL) {
  78. throw new /*\*/InvalidStateException("Component '$this->name' is not attached to '$type'.");
  79. }
  80. return $this->monitors[$type][0];
  81. }
  82. /**
  83. * Lookup for component specified by class or interface name. Returns backtrace path.
  84. * A path is the concatenation of component names separated by self::NAME_SEPARATOR.
  85. * @param string class/interface type
  86. * @param bool throw exception if component doesn't exist?
  87. * @return string
  88. */
  89. public function lookupPath($type, $need = TRUE)
  90. {
  91. /**/fixNamespace($type);/**/
  92. $this->lookup($type, $need);
  93. return $this->monitors[$type][2];
  94. }
  95. /**
  96. * Starts monitoring.
  97. * @param string class/interface type
  98. * @return void
  99. */
  100. public function monitor($type)
  101. {
  102. /**/fixNamespace($type);/**/
  103. if (empty($this->monitors[$type][3])) {
  104. if ($obj = $this->lookup($type, FALSE)) {
  105. $this->attached($obj);
  106. }
  107. $this->monitors[$type][3] = TRUE; // mark as monitored
  108. }
  109. }
  110. /**
  111. * Stops monitoring.
  112. * @param string class/interface type
  113. * @return void
  114. */
  115. public function unmonitor($type)
  116. {
  117. /**/fixNamespace($type);/**/
  118. unset($this->monitors[$type]);
  119. }
  120. /**
  121. * This method will be called when the component (or component's parent)
  122. * becomes attached to a monitored object. Do not call this method yourself.
  123. * @param IComponent
  124. * @return void
  125. */
  126. protected function attached($obj)
  127. {
  128. }
  129. /**
  130. * This method will be called before the component (or component's parent)
  131. * becomes detached from a monitored object. Do not call this method yourself.
  132. * @param IComponent
  133. * @return void
  134. */
  135. protected function detached($obj)
  136. {
  137. }
  138. /********************* interface IComponent ****************d*g**/
  139. /**
  140. * @return string
  141. */
  142. final public function getName()
  143. {
  144. return $this->name;
  145. }
  146. /**
  147. * Returns the container if any.
  148. * @return IComponentContainer|NULL
  149. */
  150. final public function getParent()
  151. {
  152. return $this->parent;
  153. }
  154. /**
  155. * Sets the parent of this component. This method is managed by containers and should.
  156. * not be called by applications
  157. *
  158. * @param IComponentContainer New parent or null if this component is being removed from a parent
  159. * @param string
  160. * @return Component provides a fluent interface
  161. * @throws \InvalidStateException
  162. */
  163. public function setParent(IComponentContainer $parent = NULL, $name = NULL)
  164. {
  165. if ($parent === NULL && $this->parent === NULL && $name !== NULL) {
  166. $this->name = $name; // just rename
  167. return $this;
  168. } elseif ($parent === $this->parent && $name === NULL) {
  169. return $this; // nothing to do
  170. }
  171. // A component cannot be given a parent if it already has a parent.
  172. if ($this->parent !== NULL && $parent !== NULL) {
  173. throw new /*\*/InvalidStateException("Component '$this->name' already has a parent.");
  174. }
  175. // remove from parent?
  176. if ($parent === NULL) {
  177. // parent cannot be removed if is still this component contains
  178. if ($this->parent->getComponent($this->name, FALSE) === $this) {
  179. throw new /*\*/InvalidStateException("The current parent still recognizes component '$this->name' as its child.");
  180. }
  181. $this->refreshMonitors(0);
  182. $this->parent = NULL;
  183. } else { // add to parent
  184. // Given parent container does not already recognize this component as its child.
  185. if ($parent->getComponent($name, FALSE) !== $this) {
  186. throw new /*\*/InvalidStateException("The given parent does not recognize component '$name' as its child.");
  187. }
  188. $this->validateParent($parent);
  189. $this->parent = $parent;
  190. if ($name !== NULL) $this->name = $name;
  191. $tmp = array();
  192. $this->refreshMonitors(0, $tmp);
  193. }
  194. return $this;
  195. }
  196. /**
  197. * Is called by a component when it is about to be set new parent. Descendant can
  198. * override this method to disallow a parent change by throwing an \InvalidStateException
  199. * @param IComponentContainer
  200. * @return void
  201. * @throws \InvalidStateException
  202. */
  203. protected function validateParent(IComponentContainer $parent)
  204. {
  205. }
  206. /**
  207. * Refreshes monitors.
  208. * @param int
  209. * @param array|NULL (array = attaching, NULL = detaching)
  210. * @param array
  211. * @return void
  212. */
  213. private function refreshMonitors($depth, & $missing = NULL, & $listeners = array())
  214. {
  215. if ($this instanceof IComponentContainer) {
  216. foreach ($this->getComponents() as $component) {
  217. if ($component instanceof Component) {
  218. $component->refreshMonitors($depth + 1, $missing, $listeners);
  219. }
  220. }
  221. }
  222. if ($missing === NULL) { // detaching
  223. foreach ($this->monitors as $type => $rec) {
  224. if (isset($rec[1]) && $rec[1] > $depth) {
  225. if ($rec[3]) { // monitored
  226. $this->monitors[$type] = array(NULL, NULL, NULL, TRUE);
  227. $listeners[] = array($this, $rec[0]);
  228. } else { // not monitored, just randomly cached
  229. unset($this->monitors[$type]);
  230. }
  231. }
  232. }
  233. } else { // attaching
  234. foreach ($this->monitors as $type => $rec) {
  235. if (isset($rec[0])) { // is in cache yet
  236. continue;
  237. } elseif (!$rec[3]) { // not monitored, just randomly cached
  238. unset($this->monitors[$type]);
  239. } elseif (isset($missing[$type])) { // known from previous lookup
  240. $this->monitors[$type] = array(NULL, NULL, NULL, TRUE);
  241. } else {
  242. $this->monitors[$type] = NULL; // forces re-lookup
  243. if ($obj = $this->lookup($type, FALSE)) {
  244. $listeners[] = array($this, $obj);
  245. } else {
  246. $missing[$type] = TRUE;
  247. }
  248. $this->monitors[$type][3] = TRUE; // mark as monitored
  249. }
  250. }
  251. }
  252. if ($depth === 0) { // call listeners
  253. $method = $missing === NULL ? 'detached' : 'attached';
  254. foreach ($listeners as $item) {
  255. $item[0]->$method($item[1]);
  256. }
  257. }
  258. }
  259. /********************* cloneable, serializable ****************d*g**/
  260. /**
  261. * Object cloning.
  262. */
  263. public function __clone()
  264. {
  265. if ($this->parent === NULL) {
  266. return;
  267. } elseif ($this->parent instanceof ComponentContainer) {
  268. $this->parent = $this->parent->isCloning();
  269. if ($this->parent === NULL) { // not cloning
  270. $this->refreshMonitors(0);
  271. }
  272. } else {
  273. $this->parent = NULL;
  274. $this->refreshMonitors(0);
  275. }
  276. }
  277. /**
  278. * Prevents unserialization.
  279. */
  280. final public function __wakeup()
  281. {
  282. throw new /*\*/NotImplementedException;
  283. }
  284. }