1<?php
2/**
3 * Copyright 2009-2016 Horde LLC (http://www.horde.org/)
4 *
5 * See the enclosed file COPYING for license information (BSD). If you
6 * did not receive this file, see http://www.horde.org/licenses/bsd.
7 *
8 * @category  Horde
9 * @copyright 2009-2016 Horde LLC
10 * @license   http://www.horde.org/licenses/bsd BSD
11 * @package   Injector
12 */
13
14/**
15 * This is a binder that finds methods marked with @inject and calls them with
16 * their dependencies. It must be stacked on another binder that actually
17 * creates the instance.
18 *
19 * @author    Bob Mckee <bmckee@bywires.com>
20 * @author    James Pepin <james@jamespepin.com>
21 * @author    Chuck Hagenbuch <chuck@horde.org>
22 * @category  Horde
23 * @copyright 2009-2016 Horde LLC
24 * @license   http://www.horde.org/licenses/bsd BSD
25 * @package   Injector
26 */
27class Horde_Injector_Binder_AnnotatedSetters implements Horde_Injector_Binder
28{
29    /**
30     * @var Horde_Injector_Binder
31     */
32    private $_binder;
33
34    /**
35     * @var Horde_Injector_DependencyFinder
36     */
37    private $_dependencyFinder;
38
39    /**
40     * Constructor.
41     *
42     * @param Horde_Injector_Binder $binder
43     * @param Horde_Injector_DependencyFinder $finder
44     *
45     */
46    public function __construct(Horde_Injector_Binder $binder,
47                                Horde_Injector_DependencyFinder $finder = null)
48    {
49        $this->_binder = $binder;
50        $this->_dependencyFinder = is_null($finder)
51            ? new Horde_Injector_DependencyFinder()
52            : $finder;
53    }
54
55    /**
56     * @param Horde_Injector_Binder $binder
57     *
58     * @return boolean  Equality.
59     */
60    public function equals(Horde_Injector_Binder $otherBinder)
61    {
62        return ($otherBinder instanceof Horde_Injector_Binder_AnnotatedSetters) &&
63            $this->getBinder()->equals($otherBinder->getBinder());
64    }
65
66    /**
67     * @return Horde_Injector_Binder
68     */
69    public function getBinder()
70    {
71        return $this->_binder;
72    }
73
74    /**
75     */
76    public function create(Horde_Injector $injector)
77    {
78        $instance = $this->_binder->create($injector);
79
80        try {
81            $reflectionClass = new ReflectionClass(get_class($instance));
82        } catch (ReflectionException $e) {
83            throw new Horde_Injector_Exception($e);
84        }
85        $setters = $this->_findAnnotatedSetters($reflectionClass);
86        $this->_callSetters($setters, $injector, $instance);
87
88        return $instance;
89    }
90
91    /**
92     * Find all public methods in $reflectionClass that are annotated with
93     * @inject.
94     *
95     * @param ReflectionClass $reflectionClass
96     *
97     * @return array
98     */
99    private function _findAnnotatedSetters(ReflectionClass $reflectionClass)
100    {
101        $setters = array();
102        foreach ($reflectionClass->getMethods(ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
103            if ($this->_isSetterMethod($reflectionMethod)) {
104                $setters[] = $reflectionMethod;
105            }
106        }
107
108        return $setters;
109    }
110
111    /**
112     * Is a method a setter method, by the criteria we define (has a doc
113     * comment that includes @inject).
114     *
115     * @param ReflectionMethod $reflectionMethod
116     */
117    private function _isSetterMethod(ReflectionMethod $reflectionMethod)
118    {
119        $docBlock = $reflectionMethod->getDocComment();
120        if ($docBlock) {
121            if (strpos($docBlock, '@inject') !== false) {
122                return true;
123            }
124        }
125
126        return false;
127    }
128
129    /**
130     * Call each ReflectionMethod in the $setters array, filling in its
131     * dependencies with the $injector.
132     *
133     * @param array $setters            Array of ReflectionMethods to call.
134     * @param Horde_Injector $injector  The injector to get dependencies from.
135     * @param object $instance          The object to call setters on.
136     */
137    private function _callSetters(array $setters, Horde_Injector $injector,
138                                  $instance)
139    {
140        foreach ($setters as $setterMethod) {
141            $setterMethod->invokeArgs(
142                $instance,
143                $this->_dependencyFinder->getMethodDependencies($injector, $setterMethod)
144            );
145        }
146    }
147
148}
149