1<?php
2/**
3 * Zend Framework (http://framework.zend.com/)
4 *
5 * @link      http://github.com/zendframework/zf2 for the canonical source repository
6 * @copyright Copyright (c) 2005-2015 Zend Technologies USA Inc. (http://www.zend.com)
7 * @license   http://framework.zend.com/license/new-bsd New BSD License
8 */
9
10namespace Zend\View\Model;
11
12use ArrayAccess;
13use ArrayIterator;
14use Traversable;
15use Zend\Stdlib\ArrayUtils;
16use Zend\View\Exception;
17use Zend\View\Model;
18use Zend\View\Variables as ViewVariables;
19
20class ViewModel implements ModelInterface, ClearableModelInterface, RetrievableChildrenInterface
21{
22    /**
23     * What variable a parent model should capture this model to
24     *
25     * @var string
26     */
27    protected $captureTo = 'content';
28
29    /**
30     * Child models
31     * @var array
32     */
33    protected $children = array();
34
35    /**
36     * Renderer options
37     * @var array
38     */
39    protected $options = array();
40
41    /**
42     * Template to use when rendering this model
43     *
44     * @var string
45     */
46    protected $template = '';
47
48    /**
49     * Is this a standalone, or terminal, model?
50     *
51     * @var bool
52     */
53    protected $terminate = false;
54
55    /**
56     * View variables
57     * @var array|ArrayAccess&Traversable
58     */
59    protected $variables = array();
60
61    /**
62     * Is this append to child  with the same capture?
63     *
64     * @var bool
65     */
66    protected $append = false;
67
68    /**
69     * Constructor
70     *
71     * @param  null|array|Traversable $variables
72     * @param  array|Traversable $options
73     */
74    public function __construct($variables = null, $options = null)
75    {
76        if (null === $variables) {
77            $variables = new ViewVariables();
78        }
79
80        // Initializing the variables container
81        $this->setVariables($variables, true);
82
83        if (null !== $options) {
84            $this->setOptions($options);
85        }
86    }
87
88    /**
89     * Property overloading: set variable value
90     *
91     * @param  string $name
92     * @param  mixed $value
93     * @return void
94     */
95    public function __set($name, $value)
96    {
97        $this->setVariable($name, $value);
98    }
99
100    /**
101     * Property overloading: get variable value
102     *
103     * @param  string $name
104     * @return mixed
105     */
106    public function __get($name)
107    {
108        if (!$this->__isset($name)) {
109            return;
110        }
111
112        $variables = $this->getVariables();
113        return $variables[$name];
114    }
115
116    /**
117     * Property overloading: do we have the requested variable value?
118     *
119     * @param  string $name
120     * @return bool
121     */
122    public function __isset($name)
123    {
124        $variables = $this->getVariables();
125        return isset($variables[$name]);
126    }
127
128    /**
129     * Property overloading: unset the requested variable
130     *
131     * @param  string $name
132     * @return void
133     */
134    public function __unset($name)
135    {
136        if (!$this->__isset($name)) {
137            return;
138        }
139
140        unset($this->variables[$name]);
141    }
142
143    /**
144     * Set a single option
145     *
146     * @param  string $name
147     * @param  mixed $value
148     * @return ViewModel
149     */
150    public function setOption($name, $value)
151    {
152        $this->options[(string) $name] = $value;
153        return $this;
154    }
155
156    /**
157     * Get a single option
158     *
159     * @param  string       $name           The option to get.
160     * @param  mixed|null   $default        (optional) A default value if the option is not yet set.
161     * @return mixed
162     */
163    public function getOption($name, $default = null)
164    {
165        $name = (string) $name;
166        return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
167    }
168
169    /**
170     * Set renderer options/hints en masse
171     *
172     * @param array|Traversable $options
173     * @throws \Zend\View\Exception\InvalidArgumentException
174     * @return ViewModel
175     */
176    public function setOptions($options)
177    {
178        // Assumption is that lowest common denominator for renderer configuration
179        // is an array
180        if ($options instanceof Traversable) {
181            $options = ArrayUtils::iteratorToArray($options);
182        }
183
184        if (!is_array($options)) {
185            throw new Exception\InvalidArgumentException(sprintf(
186                '%s: expects an array, or Traversable argument; received "%s"',
187                __METHOD__,
188                (is_object($options) ? get_class($options) : gettype($options))
189            ));
190        }
191
192        $this->options = $options;
193        return $this;
194    }
195
196    /**
197     * Get renderer options/hints
198     *
199     * @return array
200     */
201    public function getOptions()
202    {
203        return $this->options;
204    }
205
206    /**
207     * Clear any existing renderer options/hints
208     *
209     * @return ViewModel
210     */
211    public function clearOptions()
212    {
213        $this->options = array();
214        return $this;
215    }
216
217    /**
218     * Get a single view variable
219     *
220     * @param  string       $name
221     * @param  mixed|null   $default (optional) default value if the variable is not present.
222     * @return mixed
223     */
224    public function getVariable($name, $default = null)
225    {
226        $name = (string) $name;
227        if (array_key_exists($name, $this->variables)) {
228            return $this->variables[$name];
229        }
230
231        return $default;
232    }
233
234    /**
235     * Set view variable
236     *
237     * @param  string $name
238     * @param  mixed $value
239     * @return ViewModel
240     */
241    public function setVariable($name, $value)
242    {
243        $this->variables[(string) $name] = $value;
244        return $this;
245    }
246
247    /**
248     * Set view variables en masse
249     *
250     * Can be an array or a Traversable + ArrayAccess object.
251     *
252     * @param  array|ArrayAccess|Traversable $variables
253     * @param  bool $overwrite Whether or not to overwrite the internal container with $variables
254     * @throws Exception\InvalidArgumentException
255     * @return ViewModel
256     */
257    public function setVariables($variables, $overwrite = false)
258    {
259        if (!is_array($variables) && !$variables instanceof Traversable) {
260            throw new Exception\InvalidArgumentException(sprintf(
261                '%s: expects an array, or Traversable argument; received "%s"',
262                __METHOD__,
263                (is_object($variables) ? get_class($variables) : gettype($variables))
264            ));
265        }
266
267        if ($overwrite) {
268            if (is_object($variables) && !$variables instanceof ArrayAccess) {
269                $variables = ArrayUtils::iteratorToArray($variables);
270            }
271
272            $this->variables = $variables;
273            return $this;
274        }
275
276        foreach ($variables as $key => $value) {
277            $this->setVariable($key, $value);
278        }
279
280        return $this;
281    }
282
283    /**
284     * Get view variables
285     *
286     * @return array|ArrayAccess|Traversable
287     */
288    public function getVariables()
289    {
290        return $this->variables;
291    }
292
293    /**
294     * Clear all variables
295     *
296     * Resets the internal variable container to an empty container.
297     *
298     * @return ViewModel
299     */
300    public function clearVariables()
301    {
302        $this->variables = new ViewVariables();
303        return $this;
304    }
305
306    /**
307     * Set the template to be used by this model
308     *
309     * @param  string $template
310     * @return ViewModel
311     */
312    public function setTemplate($template)
313    {
314        $this->template = (string) $template;
315        return $this;
316    }
317
318    /**
319     * Get the template to be used by this model
320     *
321     * @return string
322     */
323    public function getTemplate()
324    {
325        return $this->template;
326    }
327
328    /**
329     * Add a child model
330     *
331     * @param  ModelInterface $child
332     * @param  null|string $captureTo Optional; if specified, the "capture to" value to set on the child
333     * @param  null|bool $append Optional; if specified, append to child  with the same capture
334     * @return ViewModel
335     */
336    public function addChild(ModelInterface $child, $captureTo = null, $append = null)
337    {
338        $this->children[] = $child;
339        if (null !== $captureTo) {
340            $child->setCaptureTo($captureTo);
341        }
342        if (null !== $append) {
343            $child->setAppend($append);
344        }
345
346        return $this;
347    }
348
349    /**
350     * Return all children.
351     *
352     * Return specifies an array, but may be any iterable object.
353     *
354     * @return array
355     */
356    public function getChildren()
357    {
358        return $this->children;
359    }
360
361    /**
362     * Does the model have any children?
363     *
364     * @return bool
365     */
366    public function hasChildren()
367    {
368        return (0 < count($this->children));
369    }
370
371    /**
372     * Clears out all child models
373     *
374     * @return ViewModel
375     */
376    public function clearChildren()
377    {
378        $this->children = array();
379        return $this;
380    }
381
382    /**
383     * Returns an array of Viewmodels with captureTo value $capture
384     *
385     * @param string $capture
386     * @param bool $recursive search recursive through children, default true
387     * @return array
388     */
389    public function getChildrenByCaptureTo($capture, $recursive = true)
390    {
391        $children = array();
392
393        foreach ($this->children as $child) {
394            if ($recursive === true) {
395                $children += $child->getChildrenByCaptureTo($capture);
396            }
397
398            if ($child->captureTo() === $capture) {
399                $children[] = $child;
400            }
401        }
402
403        return $children;
404    }
405
406    /**
407     * Set the name of the variable to capture this model to, if it is a child model
408     *
409     * @param  string $capture
410     * @return ViewModel
411     */
412    public function setCaptureTo($capture)
413    {
414        $this->captureTo = (string) $capture;
415        return $this;
416    }
417
418    /**
419     * Get the name of the variable to which to capture this model
420     *
421     * @return string
422     */
423    public function captureTo()
424    {
425        return $this->captureTo;
426    }
427
428    /**
429     * Set flag indicating whether or not this is considered a terminal or standalone model
430     *
431     * @param  bool $terminate
432     * @return ViewModel
433     */
434    public function setTerminal($terminate)
435    {
436        $this->terminate = (bool) $terminate;
437        return $this;
438    }
439
440    /**
441     * Is this considered a terminal or standalone model?
442     *
443     * @return bool
444     */
445    public function terminate()
446    {
447        return $this->terminate;
448    }
449
450    /**
451     * Set flag indicating whether or not append to child  with the same capture
452     *
453     * @param  bool $append
454     * @return ViewModel
455     */
456    public function setAppend($append)
457    {
458        $this->append = (bool) $append;
459        return $this;
460    }
461
462    /**
463     * Is this append to child  with the same capture?
464     *
465     * @return bool
466     */
467    public function isAppend()
468    {
469        return $this->append;
470    }
471
472    /**
473     * Return count of children
474     *
475     * @return int
476     */
477    public function count()
478    {
479        return count($this->children);
480    }
481
482    /**
483     * Get iterator of children
484     *
485     * @return ArrayIterator
486     */
487    public function getIterator()
488    {
489        return new ArrayIterator($this->children);
490    }
491}
492