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