Skip to content

Commit 27b164b

Browse files
committed
feature #21690 [Form] allow form types + form type extensions + form type guessers to be private services (hhamon)
This PR was merged into the 3.3-dev branch. Discussion ---------- [Form] allow form types + form type extensions + form type guessers to be private services | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | ~ | License | MIT | Doc PR | ~ This pull request is about making internal form services (aka form types, form type extensions and form type guessers) private. They used to be public until Symfony 3.2 for one valid reason: lazyness. However, Symfony 3.3 now comes with built-in mechanism to support effective lazy loading of private services with service locators and proxies. This PR makes the `DependencyInjectionExtension` class of the `Form` component leverage these new DI component mechanisms. Form types, form type extensions and form type guessers can now be declared private as a best practice. We decided to make these services private as of Symfony 3.3 and of course it would break BC. But this PR introduces a BC layer using a Symfony trick to keep internal form services public. The service container currently has a known issue where private services are not really private if they're referenced by at least two other services in the container. We use this trick to maintain the legacy services public even though the new API relies on private ones. This trick is done thanks to the `deprecated.form.registry` and `deprecated.form.registry.csrf` fake services that will be removed in Symfony 4.0. Commits ------- 600e75ce88 [Form] use new service locator in DependencyInjectionExtension class, so that form types can be made private at some point.
2 parents cdb8f69 + ee33ceb commit 27b164b

File tree

5 files changed

+234
-153
lines changed

5 files changed

+234
-153
lines changed

DependencyInjection/FormPass.php

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,18 @@
1111

1212
namespace Symfony\Component\Form\DependencyInjection;
1313

14+
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
15+
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
1416
use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait;
1517
use Symfony\Component\DependencyInjection\ContainerBuilder;
1618
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
19+
use Symfony\Component\DependencyInjection\Definition;
1720
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
21+
use Symfony\Component\DependencyInjection\Reference;
1822

1923
/**
20-
* Adds all services with the tags "form.type" and "form.type_guesser" as
21-
* arguments of the "form.extension" service.
24+
* Adds all services with the tags "form.type", "form.type_extension" and
25+
* "form.type_guesser" as arguments of the "form.extension" service.
2226
*
2327
* @author Bernhard Schussek <bschussek@gmail.com>
2428
*/
@@ -46,29 +50,37 @@ public function process(ContainerBuilder $container)
4650
}
4751

4852
$definition = $container->getDefinition($this->formExtensionService);
53+
$definition->replaceArgument(0, $this->processFormTypes($container, $definition));
54+
$definition->replaceArgument(1, $this->processFormTypeExtensions($container));
55+
$definition->replaceArgument(2, $this->processFormTypeGuessers($container));
56+
}
57+
58+
private function processFormTypes(ContainerBuilder $container, Definition $definition)
59+
{
60+
// Get service locator argument
61+
$servicesMap = array();
62+
$locator = $definition->getArgument(0);
63+
if ($locator instanceof ServiceLocatorArgument) {
64+
$servicesMap = $locator->getValues();
65+
}
4966

5067
// Builds an array with fully-qualified type class names as keys and service IDs as values
51-
$types = array();
5268
foreach ($container->findTaggedServiceIds($this->formTypeTag) as $serviceId => $tag) {
5369
$serviceDefinition = $container->getDefinition($serviceId);
54-
if (!$serviceDefinition->isPublic()) {
55-
throw new InvalidArgumentException(sprintf('The service "%s" must be public as form types are lazy-loaded.', $serviceId));
56-
}
5770

58-
// Support type access by FQCN
59-
$types[$serviceDefinition->getClass()] = $serviceId;
71+
// Add form type service to the service locator
72+
$servicesMap[$serviceDefinition->getClass()] = new Reference($serviceId);
6073
}
6174

62-
$definition->replaceArgument(1, $types);
75+
return new ServiceLocatorArgument($servicesMap);
76+
}
6377

78+
private function processFormTypeExtensions(ContainerBuilder $container)
79+
{
6480
$typeExtensions = array();
65-
6681
foreach ($this->findAndSortTaggedServices($this->formTypeExtensionTag, $container) as $reference) {
6782
$serviceId = (string) $reference;
6883
$serviceDefinition = $container->getDefinition($serviceId);
69-
if (!$serviceDefinition->isPublic()) {
70-
throw new InvalidArgumentException(sprintf('The service "%s" must be public as form type extensions are lazy-loaded.', $serviceId));
71-
}
7284

7385
$tag = $serviceDefinition->getTag($this->formTypeExtensionTag);
7486
if (isset($tag[0]['extended_type'])) {
@@ -77,19 +89,23 @@ public function process(ContainerBuilder $container)
7789
throw new InvalidArgumentException(sprintf('"%s" tagged services must have the extended type configured using the extended_type/extended-type attribute, none was configured for the "%s" service.', $this->formTypeExtensionTag, $serviceId));
7890
}
7991

80-
$typeExtensions[$extendedType][] = $serviceId;
92+
$typeExtensions[$extendedType][] = new Reference($serviceId);
8193
}
8294

83-
$definition->replaceArgument(2, $typeExtensions);
95+
foreach ($typeExtensions as $extendedType => $extensions) {
96+
$typeExtensions[$extendedType] = new IteratorArgument($extensions);
97+
}
8498

85-
$guessers = array_keys($container->findTaggedServiceIds($this->formTypeGuesserTag));
86-
foreach ($guessers as $serviceId) {
87-
$serviceDefinition = $container->getDefinition($serviceId);
88-
if (!$serviceDefinition->isPublic()) {
89-
throw new InvalidArgumentException(sprintf('The service "%s" must be public as form type guessers are lazy-loaded.', $serviceId));
90-
}
99+
return $typeExtensions;
100+
}
101+
102+
private function processFormTypeGuessers(ContainerBuilder $container)
103+
{
104+
$guessers = array();
105+
foreach ($container->findTaggedServiceIds($this->formTypeGuesserTag) as $serviceId => $tags) {
106+
$guessers[] = new Reference($serviceId);
91107
}
92108

93-
$definition->replaceArgument(3, $guessers);
109+
return new IteratorArgument($guessers);
94110
}
95111
}

Extension/DependencyInjection/DependencyInjectionExtension.php

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,49 +11,80 @@
1111

1212
namespace Symfony\Component\Form\Extension\DependencyInjection;
1313

14+
use Psr\Container\ContainerInterface;
1415
use Symfony\Component\Form\FormExtensionInterface;
1516
use Symfony\Component\Form\FormTypeGuesserChain;
1617
use Symfony\Component\Form\Exception\InvalidArgumentException;
17-
use Symfony\Component\DependencyInjection\ContainerInterface;
1818

1919
class DependencyInjectionExtension implements FormExtensionInterface
2020
{
21-
private $container;
22-
private $typeServiceIds;
23-
private $typeExtensionServiceIds;
24-
private $guesserServiceIds;
2521
private $guesser;
2622
private $guesserLoaded = false;
23+
private $typeContainer;
24+
private $typeExtensionServices;
25+
private $guesserServices;
26+
27+
// @deprecated to be removed in Symfony 4.0
28+
private $typeServiceIds;
29+
private $guesserServiceIds;
2730

28-
public function __construct(ContainerInterface $container, array $typeServiceIds, array $typeExtensionServiceIds, array $guesserServiceIds)
31+
/**
32+
* Constructor.
33+
*
34+
* @param ContainerInterface $typeContainer
35+
* @param iterable[] $typeExtensionServices
36+
* @param iterable $guesserServices
37+
*/
38+
public function __construct(ContainerInterface $typeContainer, array $typeExtensionServices, $guesserServices, array $guesserServiceIds = null)
2939
{
30-
$this->container = $container;
31-
$this->typeServiceIds = $typeServiceIds;
32-
$this->typeExtensionServiceIds = $typeExtensionServiceIds;
33-
$this->guesserServiceIds = $guesserServiceIds;
40+
if (null !== $guesserServiceIds) {
41+
@trigger_error(sprintf('Passing four arguments to the %s::__construct() method is deprecated since Symfony 3.3 and will be disallowed in Symfony 4.0. The new constructor only accepts three arguments.', __CLASS__), E_USER_DEPRECATED);
42+
$this->guesserServiceIds = $guesserServiceIds;
43+
$this->typeServiceIds = $typeExtensionServices;
44+
}
45+
46+
$this->typeContainer = $typeContainer;
47+
$this->typeExtensionServices = $typeExtensionServices;
48+
$this->guesserServices = $guesserServices;
3449
}
3550

3651
public function getType($name)
3752
{
38-
if (!isset($this->typeServiceIds[$name])) {
39-
throw new InvalidArgumentException(sprintf('The field type "%s" is not registered with the service container.', $name));
53+
if (null !== $this->guesserServiceIds) {
54+
if (!isset($this->typeServiceIds[$name])) {
55+
throw new InvalidArgumentException(sprintf('The field type "%s" is not registered in the service container.', $name));
56+
}
57+
58+
return $this->typeContainer->get($this->typeServiceIds[$name]);
4059
}
4160

42-
return $this->container->get($this->typeServiceIds[$name]);
61+
if (!$this->typeContainer->has($name)) {
62+
throw new InvalidArgumentException(sprintf('The field type "%s" is not registered in the service container.', $name));
63+
}
64+
65+
return $this->typeContainer->get($name);
4366
}
4467

4568
public function hasType($name)
4669
{
47-
return isset($this->typeServiceIds[$name]);
70+
if (null !== $this->guesserServiceIds) {
71+
return isset($this->typeServiceIds[$name]);
72+
}
73+
74+
return $this->typeContainer->has($name);
4875
}
4976

5077
public function getTypeExtensions($name)
5178
{
5279
$extensions = array();
5380

54-
if (isset($this->typeExtensionServiceIds[$name])) {
55-
foreach ($this->typeExtensionServiceIds[$name] as $serviceId) {
56-
$extensions[] = $extension = $this->container->get($serviceId);
81+
if (isset($this->typeExtensionServices[$name])) {
82+
foreach ($this->typeExtensionServices[$name] as $serviceId => $extension) {
83+
if (null !== $this->guesserServiceIds) {
84+
$extension = $this->typeContainer->get($serviceId = $extension);
85+
}
86+
87+
$extensions[] = $extension;
5788

5889
// validate result of getExtendedType() to ensure it is consistent with the service definition
5990
if ($extension->getExtendedType() !== $name) {
@@ -73,7 +104,7 @@ public function getTypeExtensions($name)
73104

74105
public function hasTypeExtensions($name)
75106
{
76-
return isset($this->typeExtensionServiceIds[$name]);
107+
return isset($this->typeExtensionServices[$name]);
77108
}
78109

79110
public function getTypeGuesser()
@@ -82,11 +113,15 @@ public function getTypeGuesser()
82113
$this->guesserLoaded = true;
83114
$guessers = array();
84115

85-
foreach ($this->guesserServiceIds as $serviceId) {
86-
$guessers[] = $this->container->get($serviceId);
116+
foreach ($this->guesserServices as $serviceId => $service) {
117+
if (null !== $this->guesserServiceIds) {
118+
$service = $this->typeContainer->get($serviceId = $service);
119+
}
120+
121+
$guessers[] = $service;
87122
}
88123

89-
if (count($guessers) > 0) {
124+
if ($guessers) {
90125
$this->guesser = new FormTypeGuesserChain($guessers);
91126
}
92127
}

0 commit comments

Comments
 (0)