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