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