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\Application
11: */
12:
13:
14:
15: /**
16: * PresenterComponent is the base class for all presenters components.
17: *
18: * Components are persistent objects located on a presenter. They have ability to own
19: * other child components, and interact with user. Components have properties
20: * for storing their status, and responds to user command.
21: *
22: * @author David Grudl
23: *
24: * @property-read NPresenter $presenter
25: * @package Nette\Application
26: */
27: abstract class NPresenterComponent extends NComponentContainer implements ISignalReceiver, IStatePersistent, ArrayAccess
28: {
29: /** @var array */
30: protected $params = array();
31:
32:
33:
34: /**
35: */
36: public function __construct(IComponentContainer $parent = NULL, $name = NULL)
37: {
38: $this->monitor('NPresenter');
39: parent::__construct($parent, $name);
40: }
41:
42:
43:
44: /**
45: * Returns the presenter where this component belongs to.
46: * @param bool throw exception if presenter doesn't exist?
47: * @return NPresenter|NULL
48: */
49: public function getPresenter($need = TRUE)
50: {
51: return $this->lookup('NPresenter', $need);
52: }
53:
54:
55:
56: /**
57: * Returns a fully-qualified name that uniquely identifies the component
58: * within the presenter hierarchy.
59: * @return string
60: */
61: public function getUniqueId()
62: {
63: return $this->lookupPath('NPresenter', TRUE);
64: }
65:
66:
67:
68: /**
69: * This method will be called when the component (or component's parent)
70: * becomes attached to a monitored object. Do not call this method yourself.
71: * @param IComponent
72: * @return void
73: */
74: protected function attached($presenter)
75: {
76: if ($presenter instanceof NPresenter) {
77: $this->loadState($presenter->popGlobalParams($this->getUniqueId()));
78: }
79: }
80:
81:
82:
83: /**
84: * Calls public method if exists.
85: * @param string
86: * @param array
87: * @return bool does method exist?
88: */
89: protected function tryCall($method, array $params)
90: {
91: $rc = $this->getReflection();
92: if ($rc->hasMethod($method)) {
93: $rm = $rc->getMethod($method);
94: if ($rm->isPublic() && !$rm->isAbstract() && !$rm->isStatic()) {
95: $rm->invokeNamedArgs($this, $params);
96: return TRUE;
97: }
98: }
99: return FALSE;
100: }
101:
102:
103:
104: /**
105: * Access to reflection.
106: * @return NPresenterComponentReflection
107: */
108: public function getReflection()
109: {
110: return new NPresenterComponentReflection($this);
111: }
112:
113:
114:
115: /********************* interface IStatePersistent ****************d*g**/
116:
117:
118:
119: /**
120: * Loads state informations.
121: * @param array
122: * @return void
123: */
124: public function loadState(array $params)
125: {
126: foreach ($this->getReflection()->getPersistentParams() as $nm => $meta)
127: {
128: if (isset($params[$nm])) { // ignore NULL values
129: if (isset($meta['def'])) {
130: if (is_array($params[$nm]) && !is_array($meta['def'])) {
131: $params[$nm] = $meta['def']; // prevents array to scalar conversion
132: } else {
133: settype($params[$nm], gettype($meta['def']));
134: }
135: }
136: $this->$nm = & $params[$nm];
137: }
138: }
139: $this->params = $params;
140: }
141:
142:
143:
144: /**
145: * Saves state informations for next request.
146: * @param array
147: * @param NPresenterComponentReflection (internal, used by Presenter)
148: * @return void
149: */
150: public function saveState(array & $params, $reflection = NULL)
151: {
152: $reflection = $reflection === NULL ? $this->getReflection() : $reflection;
153: foreach ($reflection->getPersistentParams() as $nm => $meta)
154: {
155: if (isset($params[$nm])) {
156: $val = $params[$nm]; // injected value
157:
158: } elseif (array_key_exists($nm, $params)) { // $params[$nm] === NULL
159: continue; // means skip
160:
161: } elseif (!isset($meta['since']) || $this instanceof $meta['since']) {
162: $val = $this->$nm; // object property value
163:
164: } else {
165: continue; // ignored parameter
166: }
167:
168: if (is_object($val)) {
169: throw new InvalidStateException("Persistent parameter must be scalar or array, {$this->reflection->name}::\$$nm is " . gettype($val));
170:
171: } else {
172: if (isset($meta['def'])) {
173: settype($val, gettype($meta['def']));
174: if ($val === $meta['def']) $val = NULL;
175: } else {
176: if ((string) $val === '') $val = NULL;
177: }
178: $params[$nm] = $val;
179: }
180: }
181: }
182:
183:
184:
185: /**
186: * Returns component param.
187: * If no key is passed, returns the entire array.
188: * @param string key
189: * @param mixed default value
190: * @return mixed
191: */
192: final public function getParam($name = NULL, $default = NULL)
193: {
194: if (func_num_args() === 0) {
195: return $this->params;
196:
197: } elseif (isset($this->params[$name])) {
198: return $this->params[$name];
199:
200: } else {
201: return $default;
202: }
203: }
204:
205:
206:
207: /**
208: * Returns a fully-qualified name that uniquely identifies the parameter.
209: * @return string
210: */
211: final public function getParamId($name)
212: {
213: $uid = $this->getUniqueId();
214: return $uid === '' ? $name : $uid . self::NAME_SEPARATOR . $name;
215: }
216:
217:
218:
219: /**
220: * Returns array of classes persistent parameters. They have public visibility and are non-static.
221: * This default implementation detects persistent parameters by annotation @persistent.
222: * @return array
223: */
224: public static function getPersistentParams()
225: {
226: $rc = new NClassReflection(func_get_arg(0));
227: $params = array();
228: foreach ($rc->getProperties(ReflectionProperty::IS_PUBLIC) as $rp) {
229: if (!$rp->isStatic() && $rp->hasAnnotation('persistent')) {
230: $params[] = $rp->getName();
231: }
232: }
233: return $params;
234: }
235:
236:
237:
238: /********************* interface ISignalReceiver ****************d*g**/
239:
240:
241:
242: /**
243: * Calls signal handler method.
244: * @param string
245: * @return void
246: * @throws NBadSignalException if there is not handler method
247: */
248: public function signalReceived($signal)
249: {
250: if (!$this->tryCall($this->formatSignalMethod($signal), $this->params)) {
251: throw new NBadSignalException("There is no handler for signal '$signal' in {$this->reflection->name} class.");
252: }
253: }
254:
255:
256:
257: /**
258: * Formats signal handler method name -> case sensitivity doesn't matter.
259: * @param string
260: * @return string
261: */
262: public function formatSignalMethod($signal)
263: {
264: return $signal == NULL ? NULL : 'handle' . $signal; // intentionally ==
265: }
266:
267:
268:
269: /********************* navigation ****************d*g**/
270:
271:
272:
273: /**
274: * Generates URL to presenter, action or signal.
275: * @param string destination in format "[[module:]presenter:]action" or "signal!" or "this"
276: * @param array|mixed
277: * @return string
278: * @throws NInvalidLinkException
279: */
280: public function link($destination, $args = array())
281: {
282: if (!is_array($args)) {
283: $args = func_get_args();
284: array_shift($args);
285: }
286:
287: try {
288: return $this->getPresenter()->createRequest($this, $destination, $args, 'link');
289:
290: } catch (NInvalidLinkException $e) {
291: return $this->getPresenter()->handleInvalidLink($e);
292: }
293: }
294:
295:
296:
297: /**
298: * Returns destination as Link object.
299: * @param string destination in format "[[module:]presenter:]view" or "signal!"
300: * @param array|mixed
301: * @return NLink
302: */
303: public function lazyLink($destination, $args = array())
304: {
305: if (!is_array($args)) {
306: $args = func_get_args();
307: array_shift($args);
308: }
309:
310: return new NLink($this, $destination, $args);
311: }
312:
313:
314:
315: /**
316: * @deprecated
317: */
318: public function ajaxLink($destination, $args = array())
319: {
320: throw new DeprecatedException(__METHOD__ . '() is deprecated.');
321: }
322:
323:
324:
325: /**
326: * Redirect to another presenter, action or signal.
327: * @param int [optional] HTTP error code
328: * @param string destination in format "[[module:]presenter:]view" or "signal!"
329: * @param array|mixed
330: * @return void
331: * @throws NAbortException
332: */
333: public function redirect($code, $destination = NULL, $args = array())
334: {
335: if (!is_numeric($code)) { // first parameter is optional
336: $args = $destination;
337: $destination = $code;
338: $code = NULL;
339: }
340:
341: if (!is_array($args)) {
342: $args = func_get_args();
343: if (is_numeric(array_shift($args))) array_shift($args);
344: }
345:
346: $presenter = $this->getPresenter();
347: $presenter->redirectUri($presenter->createRequest($this, $destination, $args, 'redirect'), $code);
348: }
349:
350:
351:
352: /********************* interface ArrayAccess ****************d*g**/
353:
354:
355:
356: /**
357: * Adds the component to the container.
358: * @param string component name
359: * @param IComponent
360: * @return void
361: */
362: final public function offsetSet($name, $component)
363: {
364: $this->addComponent($component, $name);
365: }
366:
367:
368:
369: /**
370: * Returns component specified by name. Throws exception if component doesn't exist.
371: * @param string component name
372: * @return IComponent
373: * @throws InvalidArgumentException
374: */
375: final public function offsetGet($name)
376: {
377: return $this->getComponent($name, TRUE);
378: }
379:
380:
381:
382: /**
383: * Does component specified by name exists?
384: * @param string component name
385: * @return bool
386: */
387: final public function offsetExists($name)
388: {
389: return $this->getComponent($name, FALSE) !== NULL;
390: }
391:
392:
393:
394: /**
395: * Removes component from the container.
396: * @param string component name
397: * @return void
398: */
399: final public function offsetUnset($name)
400: {
401: $component = $this->getComponent($name, FALSE);
402: if ($component !== NULL) {
403: $this->removeComponent($component);
404: }
405: }
406:
407: }
408: