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\Di;
11
12use SplDoublyLinkedList;
13use Zend\Di\Definition\RuntimeDefinition;
14
15/**
16 * Class definition based on multiple definitions
17 */
18class DefinitionList extends SplDoublyLinkedList implements Definition\DefinitionInterface
19{
20    protected $classes = array();
21    protected $runtimeDefinitions;
22
23    /**
24     * @param Definition\DefinitionInterface|Definition\DefinitionInterface[] $definitions
25     */
26    public function __construct($definitions)
27    {
28        $this->runtimeDefinitions = new SplDoublyLinkedList();
29        if (!is_array($definitions)) {
30            $definitions = array($definitions);
31        }
32        foreach ($definitions as $definition) {
33            $this->addDefinition($definition, true);
34        }
35    }
36
37    /**
38     * Add definitions
39     *
40     * @param  Definition\DefinitionInterface $definition
41     * @param  bool                           $addToBackOfList
42     * @return void
43     */
44    public function addDefinition(Definition\DefinitionInterface $definition, $addToBackOfList = true)
45    {
46        if ($addToBackOfList) {
47            $this->push($definition);
48        } else {
49            $this->unshift($definition);
50        }
51    }
52
53    protected function getDefinitionClassMap(Definition\DefinitionInterface $definition)
54    {
55        $definitionClasses = $definition->getClasses();
56        if (empty($definitionClasses)) {
57            return array();
58        }
59        return array_combine(array_values($definitionClasses), array_fill(0, count($definitionClasses), $definition));
60    }
61
62    public function unshift($definition)
63    {
64        $result = parent::unshift($definition);
65        if ($definition instanceof RuntimeDefinition) {
66            $this->runtimeDefinitions->unshift($definition);
67        }
68        $this->classes = array_merge($this->classes, $this->getDefinitionClassMap($definition));
69        return $result;
70    }
71
72    public function push($definition)
73    {
74        $result = parent::push($definition);
75        if ($definition instanceof RuntimeDefinition) {
76            $this->runtimeDefinitions->push($definition);
77        }
78        $this->classes = array_merge($this->getDefinitionClassMap($definition), $this->classes);
79        return $result;
80    }
81
82    /**
83     * @param  string       $type
84     * @return Definition\DefinitionInterface[]
85     */
86    public function getDefinitionsByType($type)
87    {
88        $definitions = array();
89        foreach ($this as $definition) {
90            if ($definition instanceof $type) {
91                $definitions[] = $definition;
92            }
93        }
94
95        return $definitions;
96    }
97
98    /**
99     * Get definition by type
100     *
101     * @param  string                         $type
102     * @return Definition\DefinitionInterface
103     */
104    public function getDefinitionByType($type)
105    {
106        foreach ($this as $definition) {
107            if ($definition instanceof $type) {
108                return $definition;
109            }
110        }
111
112        return false;
113    }
114
115    /**
116     * @param  string                              $class
117     * @return bool|Definition\DefinitionInterface
118     */
119    public function getDefinitionForClass($class)
120    {
121        if (array_key_exists($class, $this->classes)) {
122            return $this->classes[$class];
123        }
124        /** @var $definition Definition\DefinitionInterface */
125        foreach ($this->runtimeDefinitions as $definition) {
126            if ($definition->hasClass($class)) {
127                return $definition;
128            }
129        }
130
131        return false;
132    }
133
134    /**
135     * @param  string                              $class
136     * @return bool|Definition\DefinitionInterface
137     */
138    public function forClass($class)
139    {
140        return $this->getDefinitionForClass($class);
141    }
142
143    /**
144     * {@inheritDoc}
145     */
146    public function getClasses()
147    {
148        return array_keys($this->classes);
149    }
150
151    /**
152     * {@inheritDoc}
153     */
154    public function hasClass($class)
155    {
156        if (array_key_exists($class, $this->classes)) {
157            return true;
158        }
159        /** @var $definition Definition\DefinitionInterface */
160        foreach ($this->runtimeDefinitions as $definition) {
161            if ($definition->hasClass($class)) {
162                return true;
163            }
164        }
165
166        return false;
167    }
168
169    /**
170     * {@inheritDoc}
171     */
172    public function getClassSupertypes($class)
173    {
174        if (false === ($classDefinition = $this->getDefinitionForClass($class))) {
175            return array();
176        }
177        $supertypes = $classDefinition->getClassSupertypes($class);
178        if (! $classDefinition instanceof Definition\PartialMarker) {
179            return $supertypes;
180        }
181        /** @var $definition Definition\DefinitionInterface */
182        foreach ($this as $definition) {
183            if ($definition === $classDefinition) {
184                continue;
185            }
186            if ($definition->hasClass($class)) {
187                $supertypes = array_merge($supertypes, $definition->getClassSupertypes($class));
188                if ($definition instanceof Definition\PartialMarker) {
189                    continue;
190                }
191
192                return $supertypes;
193            }
194        }
195        return $supertypes;
196    }
197
198    /**
199     * {@inheritDoc}
200     */
201    public function getInstantiator($class)
202    {
203        if (! $classDefinition = $this->getDefinitionForClass($class)) {
204            return false;
205        }
206        $value = $classDefinition->getInstantiator($class);
207        if (!is_null($value)) {
208            return $value;
209        }
210        if (! $classDefinition instanceof Definition\PartialMarker) {
211            return false;
212        }
213        /** @var $definition Definition\DefinitionInterface */
214        foreach ($this as $definition) {
215            if ($definition === $classDefinition) {
216                continue;
217            }
218            if ($definition->hasClass($class)) {
219                $value = $definition->getInstantiator($class);
220                if ($value === null && $definition instanceof Definition\PartialMarker) {
221                    continue;
222                }
223
224                return $value;
225            }
226        }
227
228        return false;
229    }
230
231    /**
232     * {@inheritDoc}
233     */
234    public function hasMethods($class)
235    {
236        if (! $classDefinition = $this->getDefinitionForClass($class)) {
237            return false;
238        }
239        if (false !== ($methods = $classDefinition->hasMethods($class))) {
240            return $methods;
241        }
242        if (! $classDefinition instanceof Definition\PartialMarker) {
243            return false;
244        }
245        /** @var $definition Definition\DefinitionInterface */
246        foreach ($this as $definition) {
247            if ($definition === $classDefinition) {
248                continue;
249            }
250            if ($definition->hasClass($class)) {
251                if ($definition->hasMethods($class) === false && $definition instanceof Definition\PartialMarker) {
252                    continue;
253                }
254
255                return $definition->hasMethods($class);
256            }
257        }
258
259        return false;
260    }
261
262    /**
263     * {@inheritDoc}
264     */
265    public function hasMethod($class, $method)
266    {
267        if (!$this->hasMethods($class)) {
268            return false;
269        }
270        $classDefinition = $this->getDefinitionForClass($class);
271        if ($classDefinition->hasMethod($class, $method)) {
272            return true;
273        }
274        /** @var $definition Definition\DefinitionInterface */
275        foreach ($this->runtimeDefinitions as $definition) {
276            if ($definition === $classDefinition) {
277                continue;
278            }
279            if ($definition->hasClass($class) && $definition->hasMethod($class, $method)) {
280                return true;
281            }
282        }
283
284        return false;
285    }
286
287    /**
288     * {@inheritDoc}
289     */
290    public function getMethods($class)
291    {
292        if (false === ($classDefinition = $this->getDefinitionForClass($class))) {
293            return array();
294        }
295        $methods = $classDefinition->getMethods($class);
296        if (! $classDefinition instanceof Definition\PartialMarker) {
297            return $methods;
298        }
299        /** @var $definition Definition\DefinitionInterface */
300        foreach ($this as $definition) {
301            if ($definition === $classDefinition) {
302                continue;
303            }
304            if ($definition->hasClass($class)) {
305                if (!$definition instanceof Definition\PartialMarker) {
306                    return array_merge($definition->getMethods($class), $methods);
307                }
308
309                $methods = array_merge($definition->getMethods($class), $methods);
310            }
311        }
312
313        return $methods;
314    }
315
316    /**
317     * {@inheritDoc}
318     */
319    public function hasMethodParameters($class, $method)
320    {
321        $methodParameters = $this->getMethodParameters($class, $method);
322
323        return ($methodParameters !== array());
324    }
325
326    /**
327     * {@inheritDoc}
328     */
329    public function getMethodParameters($class, $method)
330    {
331        if (false === ($classDefinition = $this->getDefinitionForClass($class))) {
332            return array();
333        }
334        if ($classDefinition->hasMethod($class, $method) && $classDefinition->hasMethodParameters($class, $method)) {
335            return $classDefinition->getMethodParameters($class, $method);
336        }
337        /** @var $definition Definition\DefinitionInterface */
338        foreach ($this as $definition) {
339            if ($definition === $classDefinition) {
340                continue;
341            }
342            if ($definition->hasClass($class)
343                && $definition->hasMethod($class, $method)
344                && $definition->hasMethodParameters($class, $method)
345            ) {
346                return $definition->getMethodParameters($class, $method);
347            }
348        }
349
350        return array();
351    }
352}
353