DefaultFactory.php
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2015 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of the MIT License.
7  *
8  * See the LICENSE file distributed with this work for
9  * additional information.
10  */
11 namespace wcmf\lib\core\impl;
12 
17 
18 /**
19  * DefaultFactory is used to create service instances. The concrete service
20  * implementations and building parameters are described in the given configuration.
21  * Dependencies are injected in the following ways:
22  *
23  * - _Constructor injection_: Each constructor parameter is resolved either from
24  * the instance configuration or, if not specified, from an existing class that
25  * is of the same type as defined in the type hinting.
26  * - _Setter injection_: Each parameter of the instance configuration, that is
27  * not part of the constructor is injected through an appropriate setter
28  * method, if existing.
29  *
30  * To ensure that an instance implements a certain interface, the interface
31  * name may be passed together with the instance name to the DefaultFactory::addInterfaces
32  * method. The interfaces required by framework classes are already defined
33  * internally (see DefaultFactory::$_requiredInterfaces).
34  *
35  * The following configuration shows the definition of the View instance:
36  *
37  * @code
38  * [View]
39  * __class = wcmf\lib\presentation\view\impl\SmartyView
40  * __shared = false
41  * compileCheck = true
42  * caching = false
43  * cacheLifetime = 3600
44  * cacheDir = app/cache/smarty/
45  * @endcode
46  *
47  * In this example views are instances of _SmartyView_. They are not shared,
48  * meaning that clients get a fresh instance each time they request a view.
49  * The parameters compileCheck, caching, ... are either defined in the constructor
50  * of _SmartyView_ or there is an appropriate setter method (e.g.
51  * _SmartyView::setCompileCheck_). Any other constructor parameter of _SmartyView_
52  * is tried to resolve from either another instance defined in the configuration
53  * or an existing class.
54  *
55  * @author ingo herwig <ingo@wemove.com>
56  */
57 class DefaultFactory implements Factory {
58 
59  /**
60  * Interfaces that must be implemented by the given instances used
61  * in the framework.
62  */
63  protected $_requiredInterfaces = array(
64  'eventManager' => 'wcmf\lib\core\EventManager',
65  'logger' => 'wcmf\lib\core\Logger',
66  'logManager' => 'wcmf\lib\core\LogManager',
67  'session' => 'wcmf\lib\core\Session',
68  'configuration' => 'wcmf\lib\config\Configuration',
69  'localization' => 'wcmf\lib\i18n\Localization',
70  'message' => 'wcmf\lib\i18n\Message',
71  'cache' => 'wcmf\lib\io\Cache',
72  'persistenceFacade' => 'wcmf\lib\persistence\PersistenceFacade',
73  'transaction' => 'wcmf\lib\persistence\Transaction',
74  'concurrencyManager' => 'wcmf\lib\persistence\concurrency\ConcurrencyManager',
75  'actionMapper' => 'wcmf\lib\presentation\ActionMapper',
76  'request' => 'wcmf\lib\presentation\Request',
77  'response' => 'wcmf\lib\presentation\Response',
78  'listStrategies' => 'wcmf\lib\presentation\control\lists\ListStrategy',
79  'formats' => 'wcmf\lib\presentation\format\Format',
80  'formatter' => 'wcmf\lib\presentation\format\Formatter',
81  'view' => 'wcmf\lib\presentation\view\View',
82  'authenticationManager' => 'wcmf\lib\security\AuthenticationManager',
83  'permissionManager' => 'wcmf\lib\security\PermissionManager',
84  'principalFactory' => 'wcmf\lib\security\principal\PrincipalFactory',
85  'user' => 'wcmf\lib\security\principal\User',
86  'role' => 'wcmf\lib\security\principal\Role',
87  );
88 
89  /**
90  * The registry for already created instances
91  */
92  protected $_instances = array();
93 
94  /**
95  * The Configuration instance that holds the instance definitions
96  */
97  protected $_configuration = null;
98 
99  /**
100  * Constructor.
101  * @param $configuration Configuration instance used to construct instances.
102  */
103  public function __construct(Configuration $configuration) {
104  // TODO get additional interfaces from configuration
105  $this->_configuration = $configuration;
106  }
107 
108  /**
109  * @see Factory::getInstance()
110  */
111  public function getInstance($name, $dynamicConfiguration=array()) {
112  $instance = null;
113 
114  // dynamic configuration must be included in internal instance key
115  $instanceKey = sizeof($dynamicConfiguration) == 0 ? strtolower($name) :
116  strtolower($name.json_encode($dynamicConfiguration));
117 
118  // check if the instance is registered already
119  if (!isset($this->_instances[$instanceKey])) {
120  // get static instance configuration
121  $staticConfiguration = $this->_configuration->getSection($name);
122  $configuration = array_merge($staticConfiguration, $dynamicConfiguration);
123  $instance = $this->createInstance($name, $configuration, $instanceKey);
124  }
125  else {
126  $instance = $this->_instances[$instanceKey];
127  }
128  return $instance;
129  }
130 
131  /**
132  * @see Factory::getClassInstance()
133  */
134  public function getClassInstance($class, $dynamicConfiguration=array()) {
135  $configuration = array_merge(array(
136  '__class' => $class,
137  '__shared' => false
138  ), $dynamicConfiguration);
139 
140  $instance = $this->createInstance($class, $configuration, null);
141  return $instance;
142  }
143 
144  /**
145  * @see Factory::registerInstance()
146  */
147  public function registerInstance($name, $instance) {
148  $instanceKey = strtolower($name);
149  $this->_instances[$instanceKey] = $instance;
150  }
151 
152  /**
153  * @see Factory::addInterfaces()
154  */
155  public function addInterfaces(array $interfaces) {
156  $this->_requiredInterfaces = array_merge($this->_requiredInterfaces, $interfaces);
157  }
158 
159  /**
160  * @see Factory::clear()
161  */
162  public function clear() {
163  $this->_instances = array();
164  }
165 
166  /**
167  * Get the setter method name for a property.
168  * @param $property
169  * @return String
170  */
171  protected function getSetterName($property) {
172  return 'set'.ucfirst(StringUtil::underScoreToCamelCase($property, true));
173  }
174 
175  /**
176  * Create an instance from the given configuration array.
177  * @param $name The name by which the instance may be retrieved later
178  * using Factory::getInstance(). The name is also used to check the interface
179  * (see Factory::addInterfaces())
180  * @param $configuration Associative array with key value pairs for instance properties
181  * passed to the constructor, setters or public properties and the special keys '__class'
182  * (optional, denotes the instance class name), '__shared' (optional, if true, the
183  * instance may be reused using Factory::getInstance())
184  * @param $instanceKey The name under which the instance is registered for later retrieval
185  * @return Object
186  */
187  protected function createInstance($name, $configuration, $instanceKey) {
188  $instance = null;
189 
190  if (isset($configuration['__class'])) {
191  // the instance belongs to the given class
192  $className = $configuration['__class'];
193 
194  // class definition must be supplied by autoloader
195  if (class_exists($className)) {
196 
197  // collect constructor parameters
198  $cParams = array();
199  $refClass = new \ReflectionClass($className);
200  if ($refClass->hasMethod('__construct')) {
201  $refConstructor = new \ReflectionMethod($className, '__construct');
202  $refParameters = $refConstructor->getParameters();
203  foreach ($refParameters as $param) {
204  $paramName = $param->name;
205  $paramInstanceKey = strtolower($paramName);
206  if (isset($this->_instances[$paramInstanceKey])) {
207  // parameter is already registered
208  $cParams[$paramName] = $this->_instances[$paramInstanceKey];
209  }
210  elseif (isset($configuration[$paramName])) {
211  // parameter is explicitly defined in instance configuration
212  $cParams[$paramName] = $this->resolveValue($configuration[$paramName]);
213  }
214  elseif ($this->_configuration->hasSection($paramName)) {
215  // parameter exists in configuration
216  $cParams[$paramName] = $this->getInstance($paramName);
217  }
218  elseif (($paramClass = $param->getClass()) != null) {
219  // check for parameter's class from type hint
220  // will cause an exception, if the class does not exist
221  $cParams[$paramName] = $this->getClassInstance($paramClass->name);
222  }
223  else {
224  throw new ConfigurationException('Constructor parameter \''.$paramName.
225  '\' in class \''.$className.'\' cannot be injected.');
226  }
227  // delete resolved parameters from configuration
228  unset($configuration[$paramName]);
229  }
230  }
231 
232  // create the instance
233  $obj = $refClass->newInstanceArgs($cParams);
234 
235  // check against interface
236  $interface = $this->getInterface($name);
237  if ($interface != null && !($obj instanceof $interface)) {
238  throw new ConfigurationException('Class \''.$className.
239  '\' is required to implement interface \''.$interface.'\'.');
240  }
241 
242  // register the instance if it is shared (default)
243  // NOTE we do this before setting the instance properties in order
244  // to allow to resolve circular dependencies (dependent objects that
245  // are injected into the current instance via property injection can
246  // already use this instance)
247  if (!isset($configuration['__shared']) || $configuration['__shared'] == 'true') {
248  $this->registerInstance($instanceKey, $obj);
249  }
250 
251  // set the instance properties from the remaining configuration
252  foreach ($configuration as $key => $value) {
253  // exclude properties starting with __ and constructor parameters
254  if (strpos($key, '__') !== 0 && !isset($cParams[$key])) {
255  $value = $this->resolveValue($value);
256  // set the property
257  $setterName = $this->getSetterName($key);
258  if (method_exists($obj, $setterName)) {
259  $obj->$setterName($value);
260  }
261  else {
262  $obj->$key = $value;
263  }
264  }
265  }
266  $instance = $obj;
267  }
268  else {
269  throw new ConfigurationException('Class \''.$className.'\' is not found.');
270  }
271  }
272  else {
273  // the instance is a map
274 
275  // get interface that map values should implement
276  $interface = $this->getInterface($name);
277 
278  foreach ($configuration as $key => $value) {
279  // create instances for variables denoted by a leading $
280  if (strpos($value, '$') === 0) {
281  $obj = $this->getInstance(preg_replace('/^\$/', '', $value));
282  // check against interface
283  if ($interface != null && !($obj instanceof $interface)) {
284  throw new ConfigurationException('Class of \''.$name.'.'.$key.
285  '\' is required to implement interface \''.$interface.'\'.');
286  }
287  $configuration[$key] = $obj;
288  }
289  }
290  // always register maps
291  $this->registerInstance($instanceKey, $configuration);
292  $instance = $configuration;
293  }
294  return $instance;
295  }
296 
297  /**
298  * Resolve a configuration value into a parameter
299  * @param $value
300  * @return Mixed
301  */
302  protected function resolveValue($value) {
303  // special treatments, if value is a string
304  if (is_string($value)) {
305  // replace variables denoted by a leading $
306  if (strpos($value, '$') === 0) {
307  $value = $this->getInstance(preg_replace('/^\$/', '', $value));
308  }
309  else {
310  // convert booleans
311  $lower = strtolower($value);
312  if ($lower === 'true') {
313  $value = true;
314  }
315  if ($lower === 'false') {
316  $value = false;
317  }
318  }
319  }
320  // special treatments, if value is an array
321  if (is_array($value)) {
322  $result = array();
323  $containsInstance = false;
324  // check for variables
325  foreach ($value as $val) {
326  if (is_string($val) && strpos($val, '$') === 0) {
327  $result[] = $this->getInstance(preg_replace('/^\$/', '', $val));
328  $containsInstance = true;
329  }
330  else {
331  $result[] = $val;
332  }
333  }
334  // only replace value, if the array containes an variable
335  if ($containsInstance) {
336  $value = $result;
337  }
338  }
339  return $value;
340  }
341 
342  /**
343  * Get the interface that is required to be implemented by the given instance
344  * @param $name The name of the instance
345  * @return Interface or null, if undefined
346  */
347  protected function getInterface($name) {
348  if (isset($this->_requiredInterfaces[$name])) {
349  return $this->_requiredInterfaces[$name];
350  }
351  return null;
352  }
353 }
354 ?>
$_requiredInterfaces
Interfaces that must be implemented by the given instances used in the framework. ...
__construct(Configuration $configuration)
Constructor.
DefaultFactory is used to create service instances.
createInstance($name, $configuration, $instanceKey)
Create an instance from the given configuration array.
getInstance($name, $dynamicConfiguration=array())
resolveValue($value)
Resolve a configuration value into a parameter.
getSetterName($property)
Get the setter method name for a property.
Implementations of Configuration give access to the application configuration.
Interface for Factory implementations.
Definition: Factory.php:19
static underScoreToCamelCase($string, $firstLowerCase=false)
Convert a string in underscore notation to camel case notation.
Definition: StringUtil.php:263
$_configuration
The Configuration instance that holds the instance definitions.
getClassInstance($class, $dynamicConfiguration=array())
$_instances
The registry for already created instances.
ConfigurationException signals an exception in the configuration.