Browse Source

[DependencyInjection] added annotations support in the service Definition

Fabien Potencier 15 years ago
parent
commit
7a26b42f19

+ 31 - 0
src/Symfony/Components/DependencyInjection/AnnotatedContainerInterface.php

@@ -0,0 +1,31 @@
+<?php
+
+namespace Symfony\Components\DependencyInjection;
+
+/*
+ * This file is part of the symfony framework.
+ *
+ * (c) Fabien Potencier <fabien.potencier@symfony-project.com>
+ *
+ * This source file is subject to the MIT license that is bundled
+ * with this source code in the file LICENSE.
+ */
+
+/**
+ * AnnotatedContainerInterface is the interface implemented when a container knows how to deals with annotations.
+ *
+ * @package    symfony
+ * @subpackage dependency_injection
+ * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
+ */
+interface AnnotatedContainerInterface
+{
+  /**
+   * Returns service ids for a given annotation.
+   *
+   * @param string $name The annotation name
+   *
+   * @return array An array of annotations
+   */
+  public function findAnnotatedServiceIds($name);
+}

+ 22 - 1
src/Symfony/Components/DependencyInjection/Builder.php

@@ -18,7 +18,7 @@ namespace Symfony\Components\DependencyInjection;
  * @subpackage dependency_injection
  * @author     Fabien Potencier <fabien.potencier@symfony-project.com>
  */
-class Builder extends Container
+class Builder extends Container implements AnnotatedContainerInterface
 {
   protected $definitions = array();
   protected $aliases     = array();
@@ -481,6 +481,27 @@ class Builder extends Container
     return $value;
   }
 
+  /**
+   * Returns service ids for a given annotation.
+   *
+   * @param string $name The annotation name
+   *
+   * @return array An array of annotations
+   */
+  public function findAnnotatedServiceIds($name)
+  {
+    $annotations = array();
+    foreach ($this->getDefinitions() as $id => $definition)
+    {
+      if ($definition->getAnnotation($name))
+      {
+        $annotations[$id] = $definition->getAnnotation($name);
+      }
+    }
+
+    return $annotations;
+  }
+
   static public function getServiceConditionals($value)
   {
     $services = array();

+ 49 - 0
src/Symfony/Components/DependencyInjection/Definition.php

@@ -27,6 +27,7 @@ class Definition
   protected $arguments;
   protected $calls;
   protected $configurator;
+  protected $annotations;
 
   /**
    * Constructor.
@@ -40,6 +41,7 @@ class Definition
     $this->arguments = $arguments;
     $this->calls = array();
     $this->shared = true;
+    $this->annotations = array();
   }
 
   /**
@@ -171,6 +173,53 @@ class Definition
     return $this->calls;
   }
 
+  /**
+   * Returns all annotations.
+   *
+   * @return array An array of annotations
+   */
+  public function getAnnotations()
+  {
+    return $this->annotations;
+  }
+
+  /**
+   * Gets an annotation by name.
+   *
+   * @param  string $name       The annotation name
+   *
+   * @return array  $attributes An array of attributes
+   */
+  public function getAnnotation($name)
+  {
+    if (!isset($this->annotations[$name]))
+    {
+      $this->annotations[$name] = array();
+    }
+
+    return $this->annotations[$name];
+  }
+
+  /**
+   * Adds an annotation for this definition.
+   *
+   * @param  string $name       The annotation name
+   * @param  array  $attributes An array of attributes
+   *
+   * @return Definition The current instance
+   */
+  public function addAnnotation($name, array $attributes = array())
+  {
+    if (!isset($this->annotations[$name]))
+    {
+      $this->annotations[$name] = array();
+    }
+
+    $this->annotations[$name][] = $attributes;
+
+    return $this;
+  }
+
   /**
    * Sets a file to require before creating the service.
    *

+ 37 - 0
src/Symfony/Components/DependencyInjection/Dumper/PhpDumper.php

@@ -48,6 +48,7 @@ class PhpDumper extends Dumper
       $this->startClass($options['class'], $options['base_class']).
       $this->addConstructor().
       $this->addServices().
+      $this->addAnnotations().
       $this->addDefaultParametersMethod().
       $this->endClass()
     ;
@@ -246,6 +247,42 @@ EOF;
     return $code;
   }
 
+  protected function addAnnotations()
+  {
+    $annotations = array();
+    foreach ($this->container->getDefinitions() as $id => $definition)
+    {
+      foreach ($definition->getAnnotations() as $name => $ann)
+      {
+        if (!isset($annotations[$name]))
+        {
+          $annotations[$name] = array();
+        }
+
+        $annotations[$name][$id] = $ann;
+      }
+    }
+    $annotations = var_export($annotations, true);
+
+    return <<<EOF
+
+  /**
+   * Returns service ids for a given annotation.
+   *
+   * @param string \$name The annotation name
+   *
+   * @return array An array of annotations
+   */
+  public function findAnnotatedServiceIds(\$name)
+  {
+    static \$annotations = $annotations;
+
+    return isset(\$annotations[\$name]) ? \$annotations[\$name] : array();
+  }
+
+EOF;
+  }
+
   protected function startClass($class, $baseClass)
   {
     $properties = array();

+ 15 - 0
src/Symfony/Components/DependencyInjection/Dumper/XmlDumper.php

@@ -55,6 +55,21 @@ class XmlDumper extends Dumper
       !$definition->isShared() ? ' shared="false"' : ''
     );
 
+    foreach ($definition->getAnnotations() as $name => $annotations)
+    {
+      foreach ($annotations as $attributes)
+      {
+        $att = array();
+        foreach ($attributes as $key => $value)
+        {
+          $att[] = sprintf('%s="%s"', $key, $value);
+        }
+        $att = $att ? ' '.implode(' ', $att) : '';
+
+        $code .= sprintf("      <annotation name=\"%s\"%s />\n", $name, $att);
+      }
+    }
+
     if ($definition->getFile())
     {
       $code .= sprintf("      <file>%s</file>\n", $definition->getFile());

+ 20 - 0
src/Symfony/Components/DependencyInjection/Dumper/YamlDumper.php

@@ -42,6 +42,26 @@ class YamlDumper extends Dumper
     $code = "  $id:\n";
     $code .= sprintf("    class: %s\n", $definition->getClass());
 
+    $annotationsCode = '';
+    foreach ($definition->getAnnotations() as $name => $annotations)
+    {
+      foreach ($annotations as $attributes)
+      {
+        $att = array();
+        foreach ($attributes as $key => $value)
+        {
+          $att[] = sprintf('%s: %s', YAML::dump($key), YAML::dump($value));
+        }
+        $att = $att ? ', '.implode(' ', $att) : '';
+
+        $annotationsCode .= sprintf("      - { name: %s%s }\n", YAML::dump($name), $att);
+      }
+    }
+    if ($annotationsCode)
+    {
+      $code .= "    annotations:\n".$annotationsCode;
+    }
+
     if ($definition->getFile())
     {
       $code .= sprintf("    file: %s\n", $definition->getFile());

+ 16 - 0
src/Symfony/Components/DependencyInjection/Loader/XmlFileLoader.php

@@ -178,6 +178,22 @@ class XmlFileLoader extends FileLoader
       $definition->addMethodCall((string) $call['method'], $call->getArgumentsAsPhp('argument'));
     }
 
+    foreach ($service->annotation as $annotation)
+    {
+      $parameters = array();
+      foreach ($annotation->attributes() as $name => $value)
+      {
+        if ('name' === $name)
+        {
+          continue;
+        }
+
+        $parameters[$name] = SimpleXMLElement::phpize($value);
+      }
+
+      $definition->addAnnotation((string) $annotation['name'], $parameters);
+    }
+
     $configuration->setDefinition($id, $definition);
   }
 

+ 11 - 0
src/Symfony/Components/DependencyInjection/Loader/YamlFileLoader.php

@@ -177,6 +177,17 @@ class YamlFileLoader extends FileLoader
       }
     }
 
+    if (isset($service['annotations']))
+    {
+      foreach ($service['annotations'] as $annotation)
+      {
+        $name = $annotation['name'];
+        unset($annotation['name']);
+
+        $definition->addAnnotation($name, $annotation);
+      }
+    }
+
     $configuration->setDefinition($id, $definition);
   }
 

+ 6 - 0
src/Symfony/Components/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

@@ -84,6 +84,7 @@
       <xsd:element name="argument" type="argument" minOccurs="0" maxOccurs="unbounded" />
       <xsd:element name="configurator" type="configurator" minOccurs="0" maxOccurs="1" />
       <xsd:element name="call" type="call" minOccurs="0" maxOccurs="unbounded" />
+      <xsd:element name="annotation" type="annotation" minOccurs="0" maxOccurs="unbounded" />
     </xsd:choice>
     <xsd:attribute name="id" type="xsd:string" />
     <xsd:attribute name="class" type="xsd:string" />
@@ -92,6 +93,11 @@
     <xsd:attribute name="alias" type="xsd:string" />
   </xsd:complexType>
 
+  <xsd:complexType name="annotation">
+    <xsd:attribute name="name" type="xsd:string" />
+    <xsd:anyAttribute namespace="##any" processContents="lax" />
+  </xsd:complexType>
+
   <xsd:complexType name="parameters">
     <xsd:sequence>
       <xsd:element name="parameter" type="parameter" minOccurs="1" maxOccurs="unbounded" />

+ 2 - 0
tests/fixtures/Symfony/Components/DependencyInjection/containers/container9.php

@@ -10,6 +10,8 @@ use Symfony\Components\DependencyInjection\Parameter;
 $container = new Builder();
 $container->
   register('foo', 'FooClass')->
+  addAnnotation('foo', array('foo' => 'foo'))->
+  addAnnotation('foo', array('bar' => 'bar'))->
   setConstructor('getInstance')->
   setArguments(array('foo', new Reference('foo.baz'), array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, new Reference('service_container')))->
   setFile(realpath(__DIR__.'/../includes/foo.php'))->

+ 15 - 0
tests/fixtures/Symfony/Components/DependencyInjection/php/services1-1.php

@@ -13,4 +13,19 @@ use Symfony\Components\DependencyInjection\Parameter;
 class Container extends AbstractContainer
 {
   protected $shared = array();
+
+  /**
+   * Returns service ids for a given annotation.
+   *
+   * @param string $name The annotation name
+   *
+   * @return array An array of annotations
+   */
+  public function findAnnotatedServiceIds($name)
+  {
+    static $annotations = array (
+);
+
+    return isset($annotations[$name]) ? $annotations[$name] : array();
+  }
 }

+ 15 - 0
tests/fixtures/Symfony/Components/DependencyInjection/php/services1.php

@@ -13,4 +13,19 @@ use Symfony\Components\DependencyInjection\Parameter;
 class ProjectServiceContainer extends Container
 {
   protected $shared = array();
+
+  /**
+   * Returns service ids for a given annotation.
+   *
+   * @param string $name The annotation name
+   *
+   * @return array An array of annotations
+   */
+  public function findAnnotatedServiceIds($name)
+  {
+    static $annotations = array (
+);
+
+    return isset($annotations[$name]) ? $annotations[$name] : array();
+  }
 }

+ 15 - 0
tests/fixtures/Symfony/Components/DependencyInjection/php/services8.php

@@ -24,6 +24,21 @@ class ProjectServiceContainer extends Container
     $this->parameters = $this->getDefaultParameters();
   }
 
+  /**
+   * Returns service ids for a given annotation.
+   *
+   * @param string $name The annotation name
+   *
+   * @return array An array of annotations
+   */
+  public function findAnnotatedServiceIds($name)
+  {
+    static $annotations = array (
+);
+
+    return isset($annotations[$name]) ? $annotations[$name] : array();
+  }
+
   /**
    * Gets the default parameters.
    *

+ 29 - 0
tests/fixtures/Symfony/Components/DependencyInjection/php/services9.php

@@ -139,6 +139,35 @@ class ProjectServiceContainer extends Container
     return $this->getFooService();
   }
 
+  /**
+   * Returns service ids for a given annotation.
+   *
+   * @param string $name The annotation name
+   *
+   * @return array An array of annotations
+   */
+  public function findAnnotatedServiceIds($name)
+  {
+    static $annotations = array (
+  'foo' => 
+  array (
+    'foo' => 
+    array (
+      0 => 
+      array (
+        'foo' => 'foo',
+      ),
+      1 => 
+      array (
+        'bar' => 'bar',
+      ),
+    ),
+  ),
+);
+
+    return isset($annotations[$name]) ? $annotations[$name] : array();
+  }
+
   /**
    * Gets the default parameters.
    *

+ 2 - 0
tests/fixtures/Symfony/Components/DependencyInjection/xml/services9.xml

@@ -10,6 +10,8 @@
   </parameters>
   <services>
     <service id="foo" class="FooClass" constructor="getInstance" shared="false">
+      <annotation name="foo" foo="foo" />
+      <annotation name="foo" bar="bar" />
       <file>%path%/foo.php</file>
       <argument>foo</argument>
       <argument type="service" id="foo.baz" />

+ 3 - 0
tests/fixtures/Symfony/Components/DependencyInjection/yaml/services9.yml

@@ -6,6 +6,9 @@ parameters:
 services:
   foo:
     class: FooClass
+    annotations:
+      - { name: foo, foo: foo }
+      - { name: foo, bar: bar }
     file: %path%/foo.php
     constructor: getInstance
     arguments: [foo, '@foo.baz', { '%foo%': 'foo is %foo%', bar: '%foo%' }, true, '@service_container']

+ 18 - 1
tests/unit/Symfony/Components/DependencyInjection/BuilderTest.php

@@ -17,7 +17,7 @@ use Symfony\Components\DependencyInjection\Reference;
 
 $fixturesPath = __DIR__.'/../../../../fixtures/Symfony/Components/DependencyInjection/';
 
-$t = new LimeTest(59);
+$t = new LimeTest(61);
 
 // ->setDefinitions() ->addDefinitions() ->getDefinitions() ->setDefinition() ->getDefinition() ->hasDefinition()
 $t->diag('->setDefinitions() ->addDefinitions() ->getDefinitions() ->setDefinition() ->getDefinition() ->hasDefinition()');
@@ -290,3 +290,20 @@ $container->register('foo', 'FooClass');
 $config->setDefinition('foo', new Definition('BazClass'));
 $container->merge($config);
 $t->is($container->getDefinition('foo')->getClass(), 'BazClass', '->merge() overrides already defined services');
+
+// ->findAnnotatedServiceIds()
+$t->diag('->findAnnotatedServiceIds()');
+$builder = new Builder();
+$builder
+  ->register('foo', 'FooClass')
+  ->addAnnotation('foo', array('foo' => 'foo'))
+  ->addAnnotation('bar', array('bar' => 'bar'))
+  ->addAnnotation('foo', array('foofoo' => 'foofoo'))
+;
+$t->is($builder->findAnnotatedServiceIds('foo'), array(
+  'foo' => array(
+    array('foo' => 'foo'),
+    array('foofoo' => 'foofoo'),
+  )
+), '->findAnnotatedServiceIds() returns an array of service ids and its annotation attributes');
+$t->is($builder->findAnnotatedServiceIds('foobar'), array(), '->findAnnotatedServiceIds() returns an empty array if there is annotated services');

+ 14 - 1
tests/unit/Symfony/Components/DependencyInjection/DefinitionTest.php

@@ -12,7 +12,7 @@ require_once __DIR__.'/../../../bootstrap.php';
 
 use Symfony\Components\DependencyInjection\Definition;
 
-$t = new LimeTest(21);
+$t = new LimeTest(25);
 
 // __construct()
 $t->diag('__construct()');
@@ -69,3 +69,16 @@ $t->diag('->setConfigurator() ->getConfigurator()');
 $def = new Definition('stdClass');
 $t->is(spl_object_hash($def->setConfigurator('foo')), spl_object_hash($def), '->setConfigurator() implements a fluent interface');
 $t->is($def->getConfigurator(), 'foo', '->getConfigurator() returns the configurator');
+
+// ->getAnnotations() ->getAnnotation() ->addAnnotation()
+$t->diag('->getAnnotations() ->getAnnotation() ->addAnnotation()');
+$def = new Definition('stdClass');
+$t->is(spl_object_hash($def->addAnnotation('foo')), spl_object_hash($def), '->addAnnotation() implements a fluent interface');
+$t->is($def->getAnnotation('foo'), array(array()), '->getAnnotation() returns attributes for an annotation name');
+$def->addAnnotation('foo', array('foo' => 'bar'));
+$t->is($def->getAnnotation('foo'), array(array(), array('foo' => 'bar')), '->addAnnotation() can adds the same annotation several times');
+$def->addAnnotation('bar', array('bar' => 'bar'));
+$t->is($def->getAnnotations(), array(
+  'foo' => array(array(), array('foo' => 'bar')),
+  'bar' => array(array('bar' => 'bar')),
+), '->getAnnotations() returns all annotations');