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