1<?php
2
3/*
4 * This file is part of the symfony package.
5 * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11/**
12 * sfServiceContainerBuilder is a DI container that provides an interface to build the services.
13 *
14 * @package    symfony
15 * @subpackage service
16 * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
17 * @version    SVN: $Id: sfServiceContainerBuilder.php 269 2009-03-26 20:39:16Z fabien $
18 */
19class sfServiceContainerBuilder extends sfServiceContainer
20{
21  protected
22    $definitions = array(),
23    $aliases     = array(),
24    $loading     = array();
25
26  /**
27   * Sets a service.
28   *
29   * @param string $id      The service identifier
30   * @param object $service The service instance
31   */
32  public function setService($id, $service)
33  {
34    unset($this->aliases[$id]);
35
36    parent::setService($id, $service);
37  }
38
39  /**
40   * Returns true if the given service is defined.
41   *
42   * @param  string  $id      The service identifier
43   *
44   * @return Boolean true if the service is defined, false otherwise
45   */
46  public function hasService($id)
47  {
48    return isset($this->definitions[$id]) || isset($this->aliases[$id]) || parent::hasService($id);
49  }
50
51  /**
52   * Gets a service.
53   *
54   * @param  string $id The service identifier
55   *
56   * @return object The associated service
57   *
58   * @throw InvalidArgumentException if the service is not defined
59   * @throw LogicException if the service has a circular reference to itself
60   */
61  public function getService($id)
62  {
63    try
64    {
65      return parent::getService($id);
66    }
67    catch (InvalidArgumentException $e)
68    {
69      if (isset($this->loading[$id]))
70      {
71        throw new LogicException(sprintf('The service "%s" has a circular reference to itself.', $id));
72      }
73
74      if (!$this->hasServiceDefinition($id) && isset($this->aliases[$id]))
75      {
76        return $this->getService($this->aliases[$id]);
77      }
78
79      $definition = $this->getServiceDefinition($id);
80
81      $this->loading[$id] = true;
82
83      if ($definition->isShared())
84      {
85        $service = $this->services[$id] = $this->createService($definition);
86      }
87      else
88      {
89        $service = $this->createService($definition);
90      }
91
92      unset($this->loading[$id]);
93
94      return $service;
95    }
96  }
97
98  /**
99   * Gets all service ids.
100   *
101   * @return array An array of all defined service ids
102   */
103  public function getServiceIds()
104  {
105    return array_unique(array_merge(array_keys($this->getServiceDefinitions()), array_keys($this->aliases), parent::getServiceIds()));
106  }
107
108  /**
109   * Sets an alias for an existing service.
110   *
111   * @param string $alias The alias to create
112   * @param string $id    The service to alias
113   */
114  public function setAlias($alias, $id)
115  {
116    $this->aliases[$alias] = $id;
117  }
118
119  /**
120   * Gets all defined aliases.
121   *
122   * @return array An array of aliases
123   */
124  public function getAliases()
125  {
126    return $this->aliases;
127  }
128
129  /**
130   * Registers a service definition.
131   *
132   * This methods allows for simple registration of service definition
133   * with a fluid interface.
134   *
135   * @param  string $id    The service identifier
136   * @param  string $class The service class
137   *
138   * @return sfServiceDefinition A sfServiceDefinition instance
139   */
140  public function register($id, $class)
141  {
142    return $this->setServiceDefinition($id, new sfServiceDefinition($class));
143  }
144
145  /**
146   * Adds the service definitions.
147   *
148   * @param array $definitions An array of service definitions
149   */
150  public function addServiceDefinitions(array $definitions)
151  {
152    foreach ($definitions as $id => $definition)
153    {
154      $this->setServiceDefinition($id, $definition);
155    }
156  }
157
158  /**
159   * Sets the service definitions.
160   *
161   * @param array $definitions An array of service definitions
162   */
163  public function setServiceDefinitions(array $definitions)
164  {
165    $this->definitions = array();
166    $this->addServiceDefinitions($definitions);
167  }
168
169  /**
170   * Gets all service definitions.
171   *
172   * @return array An array of sfServiceDefinition instances
173   */
174  public function getServiceDefinitions()
175  {
176    return $this->definitions;
177  }
178
179  /**
180   * Sets a service definition.
181   *
182   * @param  string              $id         The service identifier
183   * @param  sfServiceDefinition $definition A sfServiceDefinition instance
184   *
185   * @return sfServiceDefinition
186   */
187  public function setServiceDefinition($id, sfServiceDefinition $definition)
188  {
189    unset($this->aliases[$id]);
190
191    return $this->definitions[$id] = $definition;
192  }
193
194  /**
195   * Returns true if a service definition exists under the given identifier.
196   *
197   * @param  string  $id The service identifier
198   *
199   * @return Boolean true if the service definition exists, false otherwise
200   */
201  public function hasServiceDefinition($id)
202  {
203    return array_key_exists($id, $this->definitions);
204  }
205
206  /**
207   * Gets a service definition.
208   *
209   * @param  string  $id The service identifier
210   *
211   * @return sfServiceDefinition A sfServiceDefinition instance
212   *
213   * @throw InvalidArgumentException if the service definition does not exist
214   */
215  public function getServiceDefinition($id)
216  {
217    if (!$this->hasServiceDefinition($id))
218    {
219      throw new InvalidArgumentException(sprintf('The service definition "%s" does not exist.', $id));
220    }
221
222    return $this->definitions[$id];
223  }
224
225  /**
226   * Creates a service for a service definition.
227   *
228   * @param  sfServiceDefinition $definition A service definition instance
229   *
230   * @return object              The service described by the service definition
231   */
232  protected function createService(sfServiceDefinition $definition)
233  {
234    if (null !== $definition->getFile())
235    {
236      require_once $this->resolveValue($definition->getFile());
237    }
238
239    $r = new ReflectionClass($this->resolveValue($definition->getClass()));
240
241    $arguments = $this->resolveServices($this->resolveValue($definition->getArguments()));
242
243    if (null !== $definition->getConstructor())
244    {
245      $service = call_user_func_array(array($this->resolveValue($definition->getClass()), $definition->getConstructor()), $arguments);
246    }
247    else
248    {
249      $service = null === $r->getConstructor() ? $r->newInstance() : $r->newInstanceArgs($arguments);
250    }
251
252    foreach ($definition->getMethodCalls() as $call)
253    {
254      call_user_func_array(array($service, $call[0]), $this->resolveServices($this->resolveValue($call[1])));
255    }
256
257    if ($callable = $definition->getConfigurator())
258    {
259      if (is_array($callable) && is_object($callable[0]) && $callable[0] instanceof sfServiceReference)
260      {
261        $callable[0] = $this->getService((string) $callable[0]);
262      }
263      elseif (is_array($callable))
264      {
265        $callable[0] = $this->resolveValue($callable[0]);
266      }
267
268      if (!is_callable($callable))
269      {
270        throw new InvalidArgumentException(sprintf('The configure callable for class "%s" is not a callable.', get_class($service)));
271      }
272
273      call_user_func($callable, $service);
274    }
275
276    return $service;
277  }
278
279  /**
280   * Replaces parameter placeholders (%name%) by their values.
281   *
282   * @param  mixed $value A value
283   *
284   * @return mixed The same value with all placeholders replaced by their values
285   *
286   * @throw RuntimeException if a placeholder references a parameter that does not exist
287   */
288  public function resolveValue($value)
289  {
290    if (is_array($value))
291    {
292      $args = array();
293      foreach ($value as $k => $v)
294      {
295        $args[$this->resolveValue($k)] = $this->resolveValue($v);
296      }
297
298      $value = $args;
299    }
300    else if (is_string($value))
301    {
302      if (preg_match('/^%([^%]+)%$/', $value, $match))
303      {
304        $value = $this->getParameter($match[1]);
305      }
306      else
307      {
308        $value = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', array($this, 'replaceParameter'), $value));
309      }
310    }
311
312    return $value;
313  }
314
315  /**
316   * Replaces service references by the real service instance.
317   *
318   * @param  mixed $value A value
319   *
320   * @return mixed The same value with all service references replaced by the real service instances
321   */
322  public function resolveServices($value)
323  {
324    if (is_array($value))
325    {
326      $value = array_map(array($this, 'resolveServices'), $value);
327    }
328    else if (is_object($value) && $value instanceof sfServiceReference)
329    {
330      $value = $this->getService((string) $value);
331    }
332
333    return $value;
334  }
335
336  protected function replaceParameter($match)
337  {
338    return $this->getParameter($match[2]);
339  }
340}
341