[BUGFIX] ClassSchema must analyze all property doc blocks
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Reflection / ClassSchemaTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Reflection;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 use TYPO3\CMS\Core\Utility\GeneralUtility;
18 use TYPO3\CMS\Extbase\Reflection\ClassSchema;
19 use TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException;
20 use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException;
21 use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
22
23 /**
24 * Test case
25 */
26 class ClassSchemaTest extends UnitTestCase
27 {
28 /**
29 * @test
30 */
31 public function classSchemaForModelIsSetAggregateRootIfRepositoryClassIsFoundForNamespacedClasses()
32 {
33 $this->resetSingletonInstances = true;
34 $service = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
35 $classSchema = $service->getClassSchema(Fixture\DummyModel::class);
36 $this->assertTrue($classSchema->isAggregateRoot());
37 }
38
39 /**
40 * @test
41 */
42 public function classSchemaHasConstructor()
43 {
44 $classSchema = new ClassSchema(Fixture\DummyClassWithConstructorAndConstructorArguments::class);
45 static::assertTrue($classSchema->hasConstructor());
46 }
47
48 /**
49 * @test
50 */
51 public function classSchemaGetProperties()
52 {
53 static::assertSame(
54 [
55 'publicProperty',
56 'protectedProperty',
57 'privateProperty',
58 'publicStaticProperty',
59 'protectedStaticProperty',
60 'privateStaticProperty',
61 'publicPropertyWithDefaultValue',
62 'propertyWithIgnoredTags',
63 'propertyWithInjectAnnotation',
64 'propertyWithTransientAnnotation',
65 'propertyWithCascadeAnnotation',
66 'propertyWithCascadeAnnotationWithoutVarAnnotation',
67 'propertyWithObjectStorageAnnotation',
68 'propertyWithObjectStorageAnnotationWithoutFQCN',
69 ],
70 array_keys((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->getProperties())
71 );
72 }
73
74 /**
75 * @test
76 */
77 public function classSchemaHasMethod()
78 {
79 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
80 static::assertTrue($classSchema->hasMethod('publicMethod'));
81 static::assertFalse($classSchema->hasMethod('nonExistentMethod'));
82 }
83
84 /**
85 * @test
86 */
87 public function classSchemaGetMethods()
88 {
89 static::assertSame(
90 [
91 'publicMethod',
92 'protectedMethod',
93 'privateMethod',
94 'methodWithIgnoredTags',
95 'injectSettings',
96 'injectMethodWithoutParam',
97 'injectMethodThatIsProtected',
98 'injectFoo',
99 'staticMethod',
100 'methodWithMandatoryParam',
101 'methodWithNullableParam',
102 'methodWithDefaultValueParam',
103 'methodWithTypeHintedParam',
104 'methodWithDocBlockTypeHintOnly',
105 ],
106 array_keys((new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class))->getMethods())
107 );
108 }
109
110 /**
111 * @test
112 */
113 public function classSchemaDetectsInjectProperties()
114 {
115 $classSchema = new ClassSchema(Fixture\DummyClassWithInjectDoctrineAnnotation::class);
116 static::assertTrue($classSchema->hasInjectProperties());
117
118 $injectProperties = $classSchema->getInjectProperties();
119 static::assertArrayHasKey('propertyWithFullQualifiedClassName', $injectProperties);
120 static::assertSame(Fixture\DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithFullQualifiedClassName']);
121
122 static::assertArrayHasKey('propertyWithRelativeClassName', $injectProperties);
123 static::assertSame(Fixture\DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithRelativeClassName']);
124
125 static::assertArrayHasKey('propertyWithImportedClassName', $injectProperties);
126 static::assertSame(self::class, $injectProperties['propertyWithImportedClassName']);
127
128 static::assertArrayHasKey('propertyWithImportedAndAliasedClassName', $injectProperties);
129 static::assertSame(self::class, $injectProperties['propertyWithImportedAndAliasedClassName']);
130 }
131
132 /**
133 * @test
134 */
135 public function classSchemaDetectsPropertyDefaultValue()
136 {
137 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
138
139 $propertyDefinition = $classSchema->getProperty('publicPropertyWithDefaultValue');
140 static::assertSame('foo', $propertyDefinition->getDefaultValue());
141 }
142
143 /**
144 * @test
145 */
146 public function classSchemaDetectsSingletons()
147 {
148 static::assertTrue((new ClassSchema(Fixture\DummySingleton::class))->isSingleton());
149 }
150
151 /**
152 * @test
153 */
154 public function classSchemaDetectsModels()
155 {
156 static::assertTrue((new ClassSchema(Fixture\DummyEntity::class))->isModel());
157 static::assertTrue((new ClassSchema(Fixture\DummyValueObject::class))->isModel());
158 }
159
160 /**
161 * @test
162 */
163 public function classSchemaDetectsEntities()
164 {
165 static::assertTrue((new ClassSchema(Fixture\DummyEntity::class))->isEntity());
166 }
167
168 /**
169 * @test
170 */
171 public function classSchemaDetectsValueObjects()
172 {
173 static::assertTrue((new ClassSchema(Fixture\DummyValueObject::class))->isValueObject());
174 }
175
176 /**
177 * @test
178 */
179 public function classSchemaDetectsClassName()
180 {
181 $this->resetSingletonInstances = true;
182 static::assertSame(Fixture\DummyModel::class, (new ClassSchema(Fixture\DummyModel::class))->getClassName());
183 }
184
185 /**
186 * @test
187 */
188 public function classSchemaDetectsNonStaticProperties()
189 {
190 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('publicProperty'));
191 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('protectedProperty'));
192 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('privateProperty'));
193 }
194
195 /**
196 * @test
197 */
198 public function classSchemaDetectsStaticProperties()
199 {
200 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('publicStaticProperty'));
201 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('protectedStaticProperty'));
202 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('privateStaticProperty'));
203 }
204
205 /**
206 * @test
207 */
208 public function classSchemaGenerationThrowsExceptionWithValidateDoctrineAnnotationsForParamWithoutTypeHint()
209 {
210 $this->resetSingletonInstances = true;
211 $this->expectException(InvalidTypeHintException::class);
212 $this->expectExceptionMessage('Missing type information for parameter "$fooParam" in TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyControllerWithValidateAnnotationWithoutParamTypeHint->methodWithValidateAnnotationsAction(): Either use an @param annotation or use a type hint.');
213 $this->expectExceptionCode(1515075192);
214
215 new ClassSchema(Fixture\DummyControllerWithValidateAnnotationWithoutParamTypeHint::class);
216 }
217
218 /**
219 * @test
220 */
221 public function classSchemaGenerationThrowsExceptionWithValidateDoctrineAnnotationsForMissingParam()
222 {
223 $this->resetSingletonInstances = true;
224 $this->expectException(InvalidValidationConfigurationException::class);
225 $this->expectExceptionMessage('Invalid validate annotation in TYPO3\CMS\Extbase\Tests\Unit\Reflection\Fixture\DummyControllerWithValidateAnnotationWithoutParam->methodWithValidateAnnotationsAction(): The following validators have been defined for missing param "$fooParam": NotEmpty, StringLength');
226 $this->expectExceptionCode(1515073585);
227
228 new ClassSchema(Fixture\DummyControllerWithValidateAnnotationWithoutParam::class);
229 }
230
231 /**
232 * @test
233 */
234 public function classSchemaDetectsMethodParameterTypeViaReflection(): void
235 {
236 $class = new class {
237 public function foo(string $foo)
238 {
239 }
240
241 public function bar(ClassSchema $foo)
242 {
243 }
244 };
245
246 $classSchema = new ClassSchema(get_class($class));
247 static::assertSame('string', $classSchema->getMethod('foo')->getParameter('foo')->getType());
248 static::assertSame(ClassSchema::class, $classSchema->getMethod('bar')->getParameter('foo')->getType());
249 }
250
251 /**
252 * @test
253 */
254 public function classSchemaPrefersMethodParameterTypeDetectionViaReflection(): void
255 {
256 $class = new class {
257 /**
258 * @param ClassSchema $foo
259 */
260 public function foo(string $foo)
261 {
262 }
263 };
264
265 $classSchema = new ClassSchema(get_class($class));
266 static::assertSame('string', $classSchema->getMethod('foo')->getParameter('foo')->getType());
267 }
268
269 /**
270 * @test
271 */
272 public function classSchemaDetectsMethodParameterTypeDetectionViaDocBlocksIfNoTypeHintIsGiven(): void
273 {
274 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
275 static::assertSame(Fixture\DummyClassWithAllTypesOfMethods::class, $classSchema->getMethod('methodWithDocBlockTypeHintOnly')->getParameter('param')->getType());
276 }
277 }