Skip to content

Commit 169e965

Browse files
committed
Validate the extended type for lazy-loaded type extensions
1 parent cecc2ee commit 169e965

File tree

4 files changed

+138
-42
lines changed

4 files changed

+138
-42
lines changed

UPGRADE-2.8.md

+45-41
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Form
2121

2222
```php
2323
use Symfony\Component\Validator\Constraints\Valid;
24-
24+
2525
$form = $this->createFormBuilder($article)
2626
->add('author', new AuthorType(), array(
2727
'constraints' => new Valid(),
@@ -42,42 +42,42 @@ Form
4242
private $author;
4343
}
4444
```
45-
45+
4646
* Type names were deprecated and will be removed in Symfony 3.0. Instead of
4747
referencing types by name, you should reference them by their
4848
fully-qualified class name (FQCN) instead. With PHP 5.5 or later, you can
4949
use the "class" constant for that:
50-
50+
5151
Before:
52-
52+
5353
```php
5454
$form = $this->createFormBuilder()
5555
->add('name', 'text')
5656
->add('age', 'integer')
5757
->getForm();
5858
```
59-
59+
6060
After:
61-
61+
6262
```php
6363
use Symfony\Component\Form\Extension\Core\Type\IntegerType;
6464
use Symfony\Component\Form\Extension\Core\Type\TextType;
65-
65+
6666
$form = $this->createFormBuilder()
6767
->add('name', TextType::class)
6868
->add('age', IntegerType::class)
6969
->getForm();
7070
```
71-
71+
7272
As a further consequence, the method `FormTypeInterface::getName()` was
7373
deprecated and will be removed in Symfony 3.0. You should remove this method
7474
from your form types.
75-
75+
7676
If you want to customize the block prefix of a type in Twig, you should now
7777
implement `FormTypeInterface::getBlockPrefix()` instead:
78-
78+
7979
Before:
80-
80+
8181
```php
8282
class UserProfileType extends AbstractType
8383
{
@@ -87,9 +87,9 @@ Form
8787
}
8888
}
8989
```
90-
90+
9191
After:
92-
92+
9393
```php
9494
class UserProfileType extends AbstractType
9595
{
@@ -99,53 +99,53 @@ Form
9999
}
100100
}
101101
```
102-
102+
103103
If you don't customize `getBlockPrefix()`, it defaults to the class name
104104
without "Type" suffix in underscore notation (here: "user_profile").
105-
105+
106106
If you want to create types that are compatible with Symfony 2.3 up to 2.8
107107
and don't trigger deprecation errors, implement *both* `getName()` and
108108
`getBlockPrefix()`:
109-
109+
110110
```php
111111
class ProfileType extends AbstractType
112112
{
113113
public function getName()
114114
{
115115
return $this->getBlockPrefix();
116116
}
117-
117+
118118
public function getBlockPrefix()
119119
{
120120
return 'profile';
121121
}
122122
}
123123
```
124-
124+
125125
If you define your form types in the Dependency Injection configuration, you
126126
should further remove the "alias" attribute:
127-
127+
128128
Before:
129-
129+
130130
```xml
131131
<service id="my.type" class="Vendor\Type\MyType">
132132
<tag name="form.type" alias="mytype" />
133133
</service>
134134
```
135-
135+
136136
After:
137-
137+
138138
```xml
139139
<service id="my.type" class="Vendor\Type\MyType">
140140
<tag name="form.type" />
141141
</service>
142142
```
143-
143+
144144
Type extension should return the fully-qualified class name of the extended
145145
type from `FormTypeExtensionInterface::getExtendedType()` now.
146-
146+
147147
Before:
148-
148+
149149
```php
150150
class MyTypeExtension extends AbstractTypeExtension
151151
{
@@ -155,12 +155,12 @@ Form
155155
}
156156
}
157157
```
158-
158+
159159
After:
160-
160+
161161
```php
162162
use Symfony\Component\Form\Extension\Core\Type\FormType;
163-
163+
164164
class MyTypeExtension extends AbstractTypeExtension
165165
{
166166
public function getExtendedType()
@@ -169,14 +169,14 @@ Form
169169
}
170170
}
171171
```
172-
172+
173173
If your extension has to be compatible with Symfony 2.3-2.8, use the
174174
following statement:
175-
175+
176176
```php
177177
use Symfony\Component\Form\AbstractType;
178178
use Symfony\Component\Form\Extension\Core\Type\FormType;
179-
179+
180180
class MyTypeExtension extends AbstractTypeExtension
181181
{
182182
public function getExtendedType()
@@ -185,13 +185,13 @@ Form
185185
}
186186
}
187187
```
188-
188+
189189
* Returning type instances from `FormTypeInterface::getParent()` is deprecated
190190
and will not be supported anymore in Symfony 3.0. Return the fully-qualified
191191
class name of the parent type class instead.
192-
192+
193193
Before:
194-
194+
195195
```php
196196
class MyType
197197
{
@@ -201,9 +201,9 @@ Form
201201
}
202202
}
203203
```
204-
204+
205205
After:
206-
206+
207207
```php
208208
class MyType
209209
{
@@ -213,24 +213,28 @@ Form
213213
}
214214
}
215215
```
216-
216+
217217
* Passing type instances to `Form::add()`, `FormBuilder::add()` and the
218218
`FormFactory::create*()` methods is deprecated and will not be supported
219219
anymore in Symfony 3.0. Pass the fully-qualified class name of the type
220220
instead.
221-
221+
222222
Before:
223-
223+
224224
```php
225225
$form = $this->createForm(new MyType());
226226
```
227-
227+
228228
After:
229-
229+
230230
```php
231231
$form = $this->createForm(MyType::class);
232232
```
233233

234+
* Registering type extensions as a service with an alias which does not
235+
match the type returned by `getExtendedType` is now forbidden. Fix your
236+
implementation to define the right type.
237+
234238
Translator
235239
----------
236240

src/Symfony/Component/Form/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ CHANGELOG
99
* deprecated the "cascade_validation" option in favor of setting "constraints"
1010
with the Valid constraint
1111
* moved data trimming logic of TrimListener into StringUtil
12+
* [BC BREAK] When registering a type extension through the DI extension, the tag alias has to match the actual extended type.
1213

1314
2.7.0
1415
-----

src/Symfony/Component/Form/Extension/DependencyInjection/DependencyInjectionExtension.php

+12-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,18 @@ public function getTypeExtensions($name)
8686

8787
if (isset($this->typeExtensionServiceIds[$name])) {
8888
foreach ($this->typeExtensionServiceIds[$name] as $serviceId) {
89-
$extensions[] = $this->container->get($serviceId);
89+
$extensions[] = $extension = $this->container->get($serviceId);
90+
91+
// validate result of getExtendedType() to ensure it is consistent with the service definition
92+
if ($extension->getExtendedType() !== $name) {
93+
throw new InvalidArgumentException(
94+
sprintf('The extended type specified for the service "%s" does not match the actual extended type. Expected "%s", given "%s"',
95+
$serviceId,
96+
$name,
97+
$extension->getExtendedType()
98+
)
99+
);
100+
}
90101
}
91102
}
92103

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Form\Tests\Extension\DependencyInjection;
13+
14+
use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
15+
use Symfony\Component\Form\Extension\DependencyInjection\DependencyInjectionExtension;
16+
17+
class DependencyInjectionExtensionTest extends \PHPUnit_Framework_TestCase
18+
{
19+
public function testGetTypeExtensions()
20+
{
21+
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
22+
23+
$typeExtension1 = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
24+
$typeExtension1->expects($this->any())
25+
->method('getExtendedType')
26+
->willReturn('test');
27+
$typeExtension2 = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
28+
$typeExtension2->expects($this->any())
29+
->method('getExtendedType')
30+
->willReturn('test');
31+
$typeExtension3 = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
32+
$typeExtension3->expects($this->any())
33+
->method('getExtendedType')
34+
->willReturn('other');
35+
36+
$services = array(
37+
'extension1' => $typeExtension1,
38+
'extension2' => $typeExtension2,
39+
'extension3' => $typeExtension3,
40+
);
41+
42+
$container->expects($this->any())
43+
->method('get')
44+
->willReturnCallback(function ($id) use ($services) {
45+
if (isset($services[$id])) {
46+
return $services[$id];
47+
}
48+
49+
throw new ServiceNotFoundException($id);
50+
});
51+
52+
$extension = new DependencyInjectionExtension($container, array(), array('test' => array('extension1', 'extension2'), 'other' => array('extension3')), array());
53+
54+
$this->assertTrue($extension->hasTypeExtensions('test'));
55+
$this->assertFalse($extension->hasTypeExtensions('unknown'));
56+
$this->assertSame(array($typeExtension1, $typeExtension2), $extension->getTypeExtensions('test'));
57+
}
58+
59+
/**
60+
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
61+
*/
62+
public function testThrowExceptionForInvalidExtendedType()
63+
{
64+
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
65+
66+
$typeExtension = $this->getMock('Symfony\Component\Form\FormTypeExtensionInterface');
67+
$typeExtension->expects($this->any())
68+
->method('getExtendedType')
69+
->willReturn('unmatched');
70+
71+
$container->expects($this->any())
72+
->method('get')
73+
->with('extension')
74+
->willReturn($typeExtension);
75+
76+
$extension = new DependencyInjectionExtension($container, array(), array('test' => array('extension')), array());
77+
78+
$extension->getTypeExtensions('test');
79+
}
80+
}

0 commit comments

Comments
 (0)