1: <?php
2:
3: /**
4: * This file is part of the Nette Framework (https://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
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 IComponentContainer $parent
24: * @package Nette
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 ($a = strrpos($type, '\\')) $type = substr($type, $a + 1); // fix namespace
62: if (!isset($this->monitors[$type])) { // not monitored or not processed yet
63: $obj = $this->parent;
64: $path = self::NAME_SEPARATOR . $this->name;
65: $depth = 1;
66: while ($obj !== NULL) {
67: if ($obj instanceof $type) break;
68: $path = self::NAME_SEPARATOR . $obj->getName() . $path;
69: $depth++;
70: $obj = $obj->getParent(); // IComponent::getParent()
71: if ($obj === $this) $obj = NULL; // prevent cycling
72: }
73:
74: if ($obj) {
75: $this->monitors[$type] = array($obj, $depth, substr($path, 1), FALSE);
76:
77: } else {
78: $this->monitors[$type] = array(NULL, NULL, NULL, FALSE); // not found
79: }
80: }
81:
82: if ($need && $this->monitors[$type][0] === NULL) {
83: throw new InvalidStateException("Component '$this->name' is not attached to '$type'.");
84: }
85:
86: return $this->monitors[$type][0];
87: }
88:
89:
90:
91: /**
92: * Lookup for component specified by class or interface name. Returns backtrace path.
93: * A path is the concatenation of component names separated by self::NAME_SEPARATOR.
94: * @param string class/interface type
95: * @param bool throw exception if component doesn't exist?
96: * @return string
97: */
98: public function lookupPath($type, $need = TRUE)
99: {
100: if ($a = strrpos($type, '\\')) $type = substr($type, $a + 1); // fix namespace
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 ($a = strrpos($type, '\\')) $type = substr($type, $a + 1); // fix namespace
115: if (empty($this->monitors[$type][3])) {
116: if ($obj = $this->lookup($type, FALSE)) {
117: $this->attached($obj);
118: }
119: $this->monitors[$type][3] = TRUE; // mark as monitored
120: }
121: }
122:
123:
124:
125: /**
126: * Stops monitoring.
127: * @param string class/interface type
128: * @return void
129: */
130: public function unmonitor($type)
131: {
132: if ($a = strrpos($type, '\\')) $type = substr($type, $a + 1); // fix namespace
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: */
195: public function setParent(IComponentContainer $parent = NULL, $name = NULL)
196: {
197: if ($parent === NULL && $this->parent === NULL && $name !== NULL) {
198: $this->name = $name; // just rename
199: return $this;
200:
201: } elseif ($parent === $this->parent && $name === NULL) {
202: return $this; // nothing to do
203: }
204:
205: // A component cannot be given a parent if it already has a parent.
206: if ($this->parent !== NULL && $parent !== NULL) {
207: throw new InvalidStateException("Component '$this->name' already has a parent.");
208: }
209:
210: // remove from parent?
211: if ($parent === NULL) {
212: $this->refreshMonitors(0);
213: $this->parent = NULL;
214:
215: } else { // add to parent
216: $this->validateParent($parent);
217: $this->parent = $parent;
218: if ($name !== NULL) $this->name = $name;
219:
220: $tmp = array();
221: $this->refreshMonitors(0, $tmp);
222: }
223: return $this;
224: }
225:
226:
227:
228: /**
229: * Is called by a component when it is about to be set new parent. Descendant can
230: * override this method to disallow a parent change by throwing an InvalidStateException
231: * @param IComponentContainer
232: * @return void
233: * @throws InvalidStateException
234: */
235: protected function validateParent(IComponentContainer $parent)
236: {
237: }
238:
239:
240:
241: /**
242: * Refreshes monitors.
243: * @param int
244: * @param array|NULL (array = attaching, NULL = detaching)
245: * @param array
246: * @return void
247: */
248: private function refreshMonitors($depth, & $missing = NULL, & $listeners = array())
249: {
250: if ($this instanceof IComponentContainer) {
251: foreach ($this->getComponents() as $component) {
252: if ($component instanceof NComponent) {
253: $component->refreshMonitors($depth + 1, $missing, $listeners);
254: }
255: }
256: }
257:
258: if ($missing === NULL) { // detaching
259: foreach ($this->monitors as $type => $rec) {
260: if (isset($rec[1]) && $rec[1] > $depth) {
261: if ($rec[3]) { // monitored
262: $this->monitors[$type] = array(NULL, NULL, NULL, TRUE);
263: $listeners[] = array($this, $rec[0]);
264: } else { // not monitored, just randomly cached
265: unset($this->monitors[$type]);
266: }
267: }
268: }
269:
270: } else { // attaching
271: foreach ($this->monitors as $type => $rec) {
272: if (isset($rec[0])) { // is in cache yet
273: continue;
274:
275: } elseif (!$rec[3]) { // not monitored, just randomly cached
276: unset($this->monitors[$type]);
277:
278: } elseif (isset($missing[$type])) { // known from previous lookup
279: $this->monitors[$type] = array(NULL, NULL, NULL, TRUE);
280:
281: } else {
282: $this->monitors[$type] = NULL; // forces re-lookup
283: if ($obj = $this->lookup($type, FALSE)) {
284: $listeners[] = array($this, $obj);
285: } else {
286: $missing[$type] = TRUE;
287: }
288: $this->monitors[$type][3] = TRUE; // mark as monitored
289: }
290: }
291: }
292:
293: if ($depth === 0) { // call listeners
294: $method = $missing === NULL ? 'detached' : 'attached';
295: foreach ($listeners as $item) {
296: $item[0]->$method($item[1]);
297: }
298: }
299: }
300:
301:
302:
303: /********************* cloneable, serializable ****************d*g**/
304:
305:
306:
307: /**
308: * Object cloning.
309: */
310: public function __clone()
311: {
312: if ($this->parent === NULL) {
313: return;
314:
315: } elseif ($this->parent instanceof NComponentContainer) {
316: $this->parent = $this->parent->_isCloning();
317: if ($this->parent === NULL) { // not cloning
318: $this->refreshMonitors(0);
319: }
320:
321: } else {
322: $this->parent = NULL;
323: $this->refreshMonitors(0);
324: }
325: }
326:
327:
328:
329: /**
330: * Prevents unserialization.
331: */
332: final public function __wakeup()
333: {
334: throw new NotImplementedException;
335: }
336:
337: }
338: