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: * @property-read string $name
19: * @property-read IContainer|NULL $parent
20: */
21: abstract class Component extends Nette\Object implements IComponent
22: {
23: /** @var IContainer */
24: private $parent;
25:
26: /** @var string */
27: private $name;
28:
29: /** @var array of [type => [obj, depth, path, is_monitored?]] */
30: private $monitors = array();
31:
32:
33: public function __construct(IContainer $parent = NULL, $name = NULL)
34: {
35: if ($parent !== NULL) {
36: $parent->addComponent($this, $name);
37:
38: } elseif (is_string($name)) {
39: $this->name = $name;
40: }
41: }
42:
43:
44: /**
45: * Lookup hierarchy for component specified by class or interface name.
46: * @param string class/interface type
47: * @param bool throw exception if component doesn't exist?
48: * @return IComponent
49: */
50: public function lookup($type, $need = TRUE)
51: {
52: if (!isset($this->monitors[$type])) { // not monitored or not processed yet
53: $obj = $this->parent;
54: $path = self::NAME_SEPARATOR . $this->name;
55: $depth = 1;
56: while ($obj !== NULL) {
57: $parent = $obj->getParent();
58: if ($type ? $obj instanceof $type : $parent === NULL) {
59: break;
60: }
61: $path = self::NAME_SEPARATOR . $obj->getName() . $path;
62: $depth++;
63: $obj = $parent; // 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 Nette\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 = NULL, $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 IContainer|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 IContainer New parent or null if this component is being removed from a parent
174: * @param string
175: * @return self
176: * @throws Nette\InvalidStateException
177: * @internal
178: */
179: public function setParent(IContainer $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 Nette\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 Nette\InvalidStateException
216: * @return void
217: * @throws Nette\InvalidStateException
218: */
219: protected function validateParent(IContainer $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 IContainer) {
234: foreach ($this->getComponents() as $component) {
235: if ($component instanceof self) {
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: $prev = array();
279: foreach ($listeners as $item) {
280: if (!in_array($item, $prev, TRUE)) {
281: $item[0]->$method($item[1]);
282: $prev[] = $item;
283: }
284: }
285: }
286: }
287:
288:
289: /********************* cloneable, serializable ****************d*g**/
290:
291:
292: /**
293: * Object cloning.
294: */
295: public function __clone()
296: {
297: if ($this->parent === NULL) {
298: return;
299:
300: } elseif ($this->parent instanceof Container) {
301: $this->parent = $this->parent->_isCloning();
302: if ($this->parent === NULL) { // not cloning
303: $this->refreshMonitors(0);
304: }
305:
306: } else {
307: $this->parent = NULL;
308: $this->refreshMonitors(0);
309: }
310: }
311:
312:
313: /**
314: * Prevents serialization.
315: */
316: public function __sleep()
317: {
318: throw new Nette\NotImplementedException('Object serialization is not supported by class ' . get_class($this));
319: }
320:
321:
322: /**
323: * Prevents unserialization.
324: */
325: public function __wakeup()
326: {
327: throw new Nette\NotImplementedException('Object unserialization is not supported by class ' . get_class($this));
328: }
329:
330: }
331: