Browse Source

[DependencyInjection] added annotations support in the service Definition

Fabien Potencier 15 năm trước cách đây
mục cha
commit
7a26b42f19
18 tập tin đã thay đổi với 320 bổ sung3 xóa
  1. 31 0
      src/Symfony/Components/DependencyInjection/AnnotatedContainerInterface.php
  2. 22 1
      src/Symfony/Components/DependencyInjection/Builder.php
  3. 49 0
      src/Symfony/Components/DependencyInjection/Definition.php
  4. 37 0
      src/Symfony/Components/DependencyInjection/Dumper/PhpDumper.php
  5. 15 0
      src/Symfony/Components/DependencyInjection/Dumper/XmlDumper.php
  6. 20 0
      src/Symfony/Components/DependencyInjection/Dumper/YamlDumper.php
  7. 16 0
      src/Symfony/Components/DependencyInjection/Loader/XmlFileLoader.php
  8. 11 0
      src/Symfony/Components/DependencyInjection/Loader/YamlFileLoader.php
  9. 6 0
      src/Symfony/Components/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd
  10. 2 0
      tests/fixtures/Symfony/Components/DependencyInjection/containers/container9.php
  11. 15 0
      tests/fixtures/Symfony/Components/DependencyInjection/php/services1-1.php
  12. 15 0
      tests/fixtures/Symfony/Components/DependencyInjection/php/services1.php
  13. 15 0
      tests/fixtures/Symfony/Components/DependencyInjection/php/services8.php
  14. 29 0
      tests/fixtures/Symfony/Components/DependencyInjection/php/services9.php
  15. 2 0
      tests/fixtures/Symfony/Components/DependencyInjection/xml/services9.xml
  16. 3 0
      tests/fixtures/Symfony/Components/DependencyInjection/yaml/services9.yml
  17. 18 1
      tests/unit/Symfony/Components/DependencyInjection/BuilderTest.php
  18. 14 1
      tests/unit/Symfony/Components/DependencyInjection/DefinitionTest.php

+ 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');