Skip to content

Commit

Permalink
adds support for service factory
Browse files Browse the repository at this point in the history
  • Loading branch information
schmittjoh committed Jun 16, 2017
1 parent 52072e6 commit 251bd72
Show file tree
Hide file tree
Showing 8 changed files with 186 additions and 82 deletions.
2 changes: 1 addition & 1 deletion Annotation/Service.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

/**
* @Annotation
* @Target("CLASS")
* @Target({"CLASS", "METHOD"})
*/
final class Service
{
Expand Down
6 changes: 5 additions & 1 deletion Metadata/ClassMetadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ class ClassMetadata extends BaseClassMetadata
public $autowire;
public $autowiringTypes;

public $factoryMethods = array();

/**
* @param string $env
*
Expand Down Expand Up @@ -94,6 +96,7 @@ public function serialize()
$this->decorationInnerName,
$this->deprecated,
$this->initMethods,
$this->factoryMethods,
));
}

Expand Down Expand Up @@ -126,7 +129,8 @@ public function unserialize($str)
$this->decoration_inner_name,
$this->decorationInnerName,
$this->deprecated,
$this->initMethods) = $data;
$this->initMethods,
$this->factoryMethods) = $data;

parent::unserialize($parentStr);
}
Expand Down
57 changes: 36 additions & 21 deletions Metadata/Driver/AnnotationDriver.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,27 +66,7 @@ public function loadMetadataForClass(\ReflectionClass $class)

foreach ($this->reader->getClassAnnotations($class) as $annot) {
if ($annot instanceof Service) {
if ($annot->decorationInnerName === null && $annot->decoration_inner_name !== null) {
@trigger_error('@Service(decoration_inner_name="...") is deprecated since version 1.8 and will be removed in 2.0. Use @Service(decorationInnerName="...") instead.', E_USER_DEPRECATED);
}

if (null === $annot->id) {
$metadata->id = $this->namingStrategy->classToServiceName($className);
} else {
$metadata->id = $annot->id;
}

$metadata->parent = $annot->parent;
$metadata->public = $annot->public;
$metadata->scope = $annot->scope;
$metadata->shared = $annot->shared;
$metadata->abstract = $annot->abstract;
$metadata->decorates = $annot->decorates;
$metadata->decorationInnerName = $annot->decorationInnerName ?: $annot->decoration_inner_name;
$metadata->deprecated = $annot->deprecated;
$metadata->environments = $annot->environments;
$metadata->autowire = $annot->autowire;
$metadata->autowiringTypes = $annot->autowiringTypes;
$this->parseServiceAnnotation($annot, $metadata, $this->namingStrategy->classToServiceName($className));
} elseif ($annot instanceof Tag) {
$metadata->tags[$annot->name][] = $annot->attributes;
} elseif ($annot instanceof Validator) {
Expand Down Expand Up @@ -218,6 +198,16 @@ public function loadMetadataForClass(\ReflectionClass $class)
}

$annot->processMetadata($metadata);
} elseif ($annot instanceof Service) {
if ( ! $method->getReturnType() instanceof \ReflectionType) {
throw new \RuntimeException('Return-Type must be a hinted class type on '.$method->class.'::'.$method->name.' (requires PHP 7.0).');
}

$factoryMethod = new ClassMetadata((string)$method->getReturnType());
$inferredName = $this->namingStrategy->classToServiceName(preg_replace('/^(create|get)/', '', $method->name));
$this->parseServiceAnnotation($annot, $factoryMethod, $inferredName);

$metadata->factoryMethods[$method->name] = $factoryMethod;
}
}
}
Expand All @@ -229,6 +219,31 @@ public function loadMetadataForClass(\ReflectionClass $class)
return $metadata;
}

private function parseServiceAnnotation(Service $annot, ClassMetadata $metadata, $inferredServiceName)
{
if ($annot->decorationInnerName === null && $annot->decoration_inner_name !== null) {
@trigger_error('@Service(decoration_inner_name="...") is deprecated since version 1.8 and will be removed in 2.0. Use @Service(decorationInnerName="...") instead.', E_USER_DEPRECATED);
}

if (null === $annot->id) {
$metadata->id = $inferredServiceName;
} else {
$metadata->id = $annot->id;
}

$metadata->parent = $annot->parent;
$metadata->public = $annot->public;
$metadata->scope = $annot->scope;
$metadata->shared = $annot->shared;
$metadata->abstract = $annot->abstract;
$metadata->decorates = $annot->decorates;
$metadata->decorationInnerName = $annot->decorationInnerName ?: $annot->decoration_inner_name;
$metadata->deprecated = $annot->deprecated;
$metadata->environments = $annot->environments;
$metadata->autowire = $annot->autowire;
$metadata->autowiringTypes = $annot->autowiringTypes;
}

private function convertReferenceValue($name, AnnotReference $annot, \ReflectionType $annotatedType = null)
{
if (null === $annot->value) {
Expand Down
145 changes: 87 additions & 58 deletions Metadata/MetadataConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,12 @@
use Metadata\ClassHierarchyMetadata;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Reference;

class MetadataConverter
{
private $count = 0;

/**
* Converts class hierarchy metadata to definition instances.
*
Expand All @@ -34,83 +37,109 @@ class MetadataConverter
*/
public function convert(ClassHierarchyMetadata $metadata)
{
static $count = 0;
$definitions = array();

$previous = null;
foreach ($metadata->classMetadata as $classMetadata) {
if (null === $previous && null === $classMetadata->parent) {
$definition = new Definition();
} else {
$definition = new DefinitionDecorator(
$classMetadata->parent ?: $previous->id
);
}

$definition->setClass($classMetadata->name);
if (null !== $classMetadata->scope) {
$definition->setScope($classMetadata->scope);
}
if (null !== $classMetadata->shared) {
$definition->setShared($classMetadata->shared);
}
if (null !== $classMetadata->public) {
$definition->setPublic($classMetadata->public);
}
if (null !== $classMetadata->abstract) {
$definition->setAbstract($classMetadata->abstract);
}
if (null !== $classMetadata->arguments) {
$definition->setArguments($classMetadata->arguments);
}
if (null !== $classMetadata->autowire) {
if (!method_exists($definition, 'setAutowired')) {
throw new InvalidAnnotationException(sprintf('You must use symfony 2.8 or higher to use autowiring on the class %s.', $classMetadata->name));
/** @var ClassMetadata $classMetadata */
foreach ($classMetadata->factoryMethods as $methodName => $factoryMetadata) {
/** @var ClassMetadata $factoryMetadata */

$factoryServiceDef = $this->convertMetadata($factoryMetadata);

if (method_exists($factoryServiceDef, 'setFactory')) {
$factoryServiceDef->setFactory(array(
new Reference($classMetadata->id),
$methodName,
));
} else {
$factoryServiceDef
->setFactoryService($classMetadata->id)
->setFactoryMethod('selectCollection')
;
}

$definition->setAutowired($classMetadata->autowire);
}
if (null !== $classMetadata->autowiringTypes && method_exists($definition, 'setAutowiringTypes')) {
$definition->setAutowiringTypes($classMetadata->autowiringTypes);
$definitions[$factoryMetadata->id] = $factoryServiceDef;
}

$definition->setMethodCalls($classMetadata->methodCalls);
$definition->setTags($classMetadata->tags);
$definition->setProperties($classMetadata->properties);
$definitions[$classMetadata->id] = $this->convertMetadata($classMetadata, $previous);

if (null !== $classMetadata->decorates) {
if ($classMetadata->decorationInnerName === null && $classMetadata->decoration_inner_name !== null) {
@trigger_error('ClassMetaData::$decoration_inner_name is deprecated since version 1.8 and will be removed in 2.0. Use ClassMetaData::$decorationInnerName instead.', E_USER_DEPRECATED);
}
$previous = $classMetadata;
}

if (!method_exists($definition, 'setDecoratedService')) {
throw new InvalidAnnotationException(sprintf('You must use symfony 2.8 or higher to use decorations on the class %s.', $classMetadata->name));
}
return $definitions;
}

$definition->setDecoratedService($classMetadata->decorates, $classMetadata->decorationInnerName !== null ? $classMetadata->decorationInnerName : $classMetadata->decoration_inner_name);
}
private function convertMetadata(ClassMetadata $classMetadata, ClassMetadata $previous = null)
{
if (null === $previous && null === $classMetadata->parent) {
$definition = new Definition();
} else {
$definition = new DefinitionDecorator(
$classMetadata->parent ?: $previous->id
);
}

if (null !== $classMetadata->deprecated && method_exists($definition, 'setDeprecated')) {
$definition->setDeprecated(true, $classMetadata->deprecated);
$definition->setClass($classMetadata->name);
if (null !== $classMetadata->scope) {
$definition->setScope($classMetadata->scope);
}
if (null !== $classMetadata->shared) {
$definition->setShared($classMetadata->shared);
}
if (null !== $classMetadata->public) {
$definition->setPublic($classMetadata->public);
}
if (null !== $classMetadata->abstract) {
$definition->setAbstract($classMetadata->abstract);
}
if (null !== $classMetadata->arguments) {
$definition->setArguments($classMetadata->arguments);
}
if (null !== $classMetadata->autowire) {
if (!method_exists($definition, 'setAutowired')) {
throw new InvalidAnnotationException(sprintf('You must use symfony 2.8 or higher to use autowiring on the class %s.', $classMetadata->name));
}

if (null === $classMetadata->id) {
$classMetadata->id = '_jms_di_extra.unnamed.service_'.$count++;
$definition->setAutowired($classMetadata->autowire);
}
if (null !== $classMetadata->autowiringTypes && method_exists($definition, 'setAutowiringTypes')) {
$definition->setAutowiringTypes($classMetadata->autowiringTypes);
}

$definition->setMethodCalls($classMetadata->methodCalls);
$definition->setTags($classMetadata->tags);
$definition->setProperties($classMetadata->properties);

if (null !== $classMetadata->decorates) {
if ($classMetadata->decorationInnerName === null && $classMetadata->decoration_inner_name !== null) {
@trigger_error('ClassMetaData::$decoration_inner_name is deprecated since version 1.8 and will be removed in 2.0. Use ClassMetaData::$decorationInnerName instead.', E_USER_DEPRECATED);
}

if (0 !== count($classMetadata->initMethods)) {
foreach ($classMetadata->initMethods as $initMethod) {
$definition->addMethodCall($initMethod);
}
} elseif (null !== $classMetadata->initMethod) {
@trigger_error('ClassMetadata::$initMethod is deprecated since version 1.7 and will be removed in 2.0. Use ClassMetadata::$initMethods instead.', E_USER_DEPRECATED);
$definition->addMethodCall($classMetadata->initMethod);
if (!method_exists($definition, 'setDecoratedService')) {
throw new InvalidAnnotationException(sprintf('You must use symfony 2.8 or higher to use decorations on the class %s.', $classMetadata->name));
}

$definitions[$classMetadata->id] = $definition;
$previous = $classMetadata;
$definition->setDecoratedService($classMetadata->decorates, $classMetadata->decorationInnerName !== null ? $classMetadata->decorationInnerName : $classMetadata->decoration_inner_name);
}

return $definitions;
if (null !== $classMetadata->deprecated && method_exists($definition, 'setDeprecated')) {
$definition->setDeprecated(true, $classMetadata->deprecated);
}

if (null === $classMetadata->id) {
$classMetadata->id = '_jms_di_extra.unnamed.service_'.$this->count++;
}

if (0 !== count($classMetadata->initMethods)) {
foreach ($classMetadata->initMethods as $initMethod) {
$definition->addMethodCall($initMethod);
}
} elseif (null !== $classMetadata->initMethod) {
@trigger_error('ClassMetadata::$initMethod is deprecated since version 1.7 and will be removed in 2.0. Use ClassMetadata::$initMethods instead.', E_USER_DEPRECATED);
$definition->addMethodCall($classMetadata->initMethod);
}

return $definition;
}
}
19 changes: 19 additions & 0 deletions Resources/doc/annotations.rst
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ should be injected into the remaining parameters based on their name.

@Service
~~~~~~~~

On classes
^^^^^^^^^
Marks a class as service:

.. code-block :: php
Expand All @@ -93,6 +96,22 @@ If you do not explicitly define a service id, then we will generated a sensible
based on the fully qualified class name for you. By default, the class will be loaded in all environments
unless you explicitly specify an environment via the ``environments`` attribute.

On methods
^^^^^^^^^^
Marks the method as a factory for a service:

.. code-block :: php
<?php
use JMS\DiExtraBundle\Annotation\Service;
class SomeFactory {
/** @Service("some.service.id") */
public function createService() { /* ... */ }
}
@Tag
~~~~
Adds a tag to the service:
Expand Down
15 changes: 15 additions & 0 deletions Tests/Functional/Bundle/TestBundle/Factory/MyFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace JMS\DiExtraBundle\Tests\Functional\Bundle\TestBundle\Factory;

use JMS\DiExtraBundle\Annotation as DI;

/** @DI\Service("my_factory") */
class MyFactory
{
/** @DI\Service("factory_generic_service") */
public function createGenericService(): \stdClass
{
return new \stdClass();
}
}
21 changes: 21 additions & 0 deletions Tests/Functional/FactoryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace JMS\DiExtraBundle\Tests\Functional;

use JMS\DiExtraBundle\Tests\Functional\BaseTestCase;

class FactoryTest extends BaseTestCase
{
public function testGetFactoryService()
{
if (version_compare(PHP_VERSION, '7.0.0', '<')) {
$this->markTestSkipped('Requires PHP 7.0');
}

$this->createClient();
$container = self::$kernel->getContainer();

$service = $container->get('factory_generic_service');
$this->assertInstanceOf('stdClass', $service);
}
}
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"doctrine/doctrine-bundle": "~1.5",
"doctrine/orm": "~2.3",
"phpcollection/phpcollection": ">=0.2,<0.3-dev",
"symfony/phpunit-bridge": "~3.2"
"symfony/phpunit-bridge": "~3.2",
"phpunit/phpunit": "^5.0.0"
},
"autoload": {
"psr-4": { "JMS\\DiExtraBundle\\": "" }
Expand Down

0 comments on commit 251bd72

Please sign in to comment.