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: */
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: */
193: public function setParent(IComponentContainer $parent = NULL, $name = NULL)
194: {
195: if ($parent === NULL && $this->parent === NULL && $name !== NULL) {
196: $this->name = $name; // just rename
197: return $this;
198:
199: } elseif ($parent === $this->parent && $name === NULL) {
200: return $this; // nothing to do
201: }
202:
203: // A component cannot be given a parent if it already has a parent.
204: if ($this->parent !== NULL && $parent !== NULL) {
205: throw new \InvalidStateException("Component '$this->name' already has a parent.");
206: }
207:
208: // remove from parent?
209: if ($parent === NULL) {
210: $this->refreshMonitors(0);
211: $this->parent = NULL;
212:
213: } else { // add to parent
214: $this->validateParent($parent);
215: $this->parent = $parent;
216: if ($name !== NULL) $this->name = $name;
217:
218: $tmp = array();
219: $this->refreshMonitors(0, $tmp);
220: }
221: return $this;
222: }
223:
224:
225:
226: /**
227: * Is called by a component when it is about to be set new parent. Descendant can
228: * override this method to disallow a parent change by throwing an \InvalidStateException
229: * @param IComponentContainer
230: * @return void
231: * @throws \InvalidStateException
232: */
233: protected function validateParent(IComponentContainer $parent)
234: {
235: }
236:
237:
238:
239: /**
240: * Refreshes monitors.
241: * @param int
242: * @param array|NULL (array = attaching, NULL = detaching)
243: * @param array
244: * @return void
245: */
246: private function refreshMonitors($depth, & $missing = NULL, & $listeners = array())
247: {
248: if ($this instanceof IComponentContainer) {
249: foreach ($this->getComponents() as $component) {
250: if ($component instanceof Component) {
251: $component->refreshMonitors($depth + 1, $missing, $listeners);
252: }
253: }
254: }
255:
256: if ($missing === NULL) { // detaching
257: foreach ($this->monitors as $type => $rec) {
258: if (isset($rec[1]) && $rec[1] > $depth) {
259: if ($rec[3]) { // monitored
260: $this->monitors[$type] = array(NULL, NULL, NULL, TRUE);
261: $listeners[] = array($this, $rec[0]);
262: } else { // not monitored, just randomly cached
263: unset($this->monitors[$type]);
264: }
265: }
266: }
267:
268: } else { // attaching
269: foreach ($this->monitors as $type => $rec) {
270: if (isset($rec[0])) { // is in cache yet
271: continue;
272:
273: } elseif (!$rec[3]) { // not monitored, just randomly cached
274: unset($this->monitors[$type]);
275:
276: } elseif (isset($missing[$type])) { // known from previous lookup
277: $this->monitors[$type] = array(NULL, NULL, NULL, TRUE);
278:
279: } else {
280: $this->monitors[$type] = NULL; // forces re-lookup
281: if ($obj = $this->lookup($type, FALSE)) {
282: $listeners[] = array($this, $obj);
283: } else {
284: $missing[$type] = TRUE;
285: }
286: $this->monitors[$type][3] = TRUE; // mark as monitored
287: }
288: }
289: }
290:
291: if ($depth === 0) { // call listeners
292: $method = $missing === NULL ? 'detached' : 'attached';
293: foreach ($listeners as $item) {
294: $item[0]->$method($item[1]);
295: }
296: }
297: }
298:
299:
300:
301: /********************* cloneable, serializable ****************d*g**/
302:
303:
304:
305: /**
306: * Object cloning.
307: */
308: public function __clone()
309: {
310: if ($this->parent === NULL) {
311: return;
312:
313: } elseif ($this->parent instanceof ComponentContainer) {
314: $this->parent = $this->parent->_isCloning();
315: if ($this->parent === NULL) { // not cloning
316: $this->refreshMonitors(0);
317: }
318:
319: } else {
320: $this->parent = NULL;
321: $this->refreshMonitors(0);
322: }
323: }
324:
325:
326:
327: /**
328: * Prevents unserialization.
329: */
330: final public function __wakeup()
331: {
332: throw new \NotImplementedException;
333: }
334:
335: }
336: