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