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