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