[!!!][TASK] Aggregate validator information in class schema
[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\Persistence\ObjectStorage;
19 use TYPO3\CMS\Extbase\Reflection\ClassSchema;
20 use TYPO3\CMS\Extbase\Validation\Exception\InvalidTypeHintException;
21 use TYPO3\CMS\Extbase\Validation\Exception\InvalidValidationConfigurationException;
22 use TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator;
23 use TYPO3\CMS\Extbase\Validation\Validator\StringLengthValidator;
24
25 /**
26 * Test case
27 */
28 class ClassSchemaTest extends \TYPO3\TestingFramework\Core\Unit\UnitTestCase
29 {
30 /**
31 * @test
32 */
33 public function classSchemaForModelIsSetAggregateRootIfRepositoryClassIsFoundForNamespacedClasses()
34 {
35 /** @var \TYPO3\CMS\Extbase\Reflection\ReflectionService $service */
36 $service = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
37 $classSchema = $service->getClassSchema(Fixture\DummyModel::class);
38 $this->assertTrue($classSchema->isAggregateRoot());
39 }
40
41 public function testClassSchemaHasConstructor()
42 {
43 $classSchema = new ClassSchema(Fixture\DummyClassWithConstructorAndConstructorArguments::class);
44 static::assertTrue($classSchema->hasConstructor());
45 }
46
47 public function testClassSchemaDetectsConstructorArguments()
48 {
49 $classSchema = new ClassSchema(Fixture\DummyClassWithConstructorAndConstructorArguments::class);
50 static::assertTrue($classSchema->hasConstructor());
51
52 $constructorArguments = $classSchema->getConstructorArguments();
53 static::assertArrayHasKey('foo', $constructorArguments);
54 static::assertArrayHasKey('bar', $constructorArguments);
55
56 $classSchema = new ClassSchema(Fixture\DummyClassWithConstructorAndWithoutConstructorArguments::class);
57 static::assertTrue($classSchema->hasConstructor());
58 static::assertSame([], $classSchema->getConstructorArguments());
59
60 $classSchema = new ClassSchema(Fixture\DummyClassWithoutConstructor::class);
61 static::assertFalse($classSchema->hasConstructor());
62 static::assertSame([], $classSchema->getConstructorArguments());
63 }
64
65 public function testClassSchemaDetectsConstructorArgumentsWithDependencies()
66 {
67 $classSchema = new ClassSchema(Fixture\DummyClassWithConstructorAndConstructorArgumentsWithDependencies::class);
68 static::assertTrue($classSchema->hasConstructor());
69
70 $methodDefinition = $classSchema->getMethod('__construct');
71 static::assertArrayHasKey('foo', $methodDefinition['params']);
72 static::assertSame(Fixture\DummyClassWithGettersAndSetters::class, $methodDefinition['params']['foo']['dependency']);
73 }
74
75 public function testClassSchemaGetProperties()
76 {
77 static::assertSame(
78 [
79 'publicProperty',
80 'protectedProperty',
81 'privateProperty',
82 'publicStaticProperty',
83 'protectedStaticProperty',
84 'privateStaticProperty',
85 'propertyWithIgnoredTags',
86 'propertyWithInjectAnnotation',
87 'propertyWithTransientAnnotation',
88 'propertyWithCascadeAnnotation',
89 'propertyWithCascadeAnnotationWithoutVarAnnotation',
90 'propertyWithObjectStorageAnnotation'
91 ],
92 array_keys((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->getProperties())
93 );
94 }
95
96 public function testClassSchemaHasMethod()
97 {
98 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
99 static::assertTrue($classSchema->hasMethod('publicMethod'));
100 static::assertFalse($classSchema->hasMethod('nonExistentMethod'));
101 }
102
103 public function testClassSchemaGetMethods()
104 {
105 static::assertSame(
106 [
107 'publicMethod',
108 'protectedMethod',
109 'privateMethod',
110 'methodWithIgnoredTags',
111 'injectSettings',
112 'injectMethodWithoutParam',
113 'injectMethodThatIsProtected',
114 'injectFoo',
115 'staticMethod',
116 'methodWithMandatoryParam',
117 'methodWithNullableParam',
118 'methodWithDefaultValueParam',
119 'methodWithTypeHintedParam'
120 ],
121 array_keys((new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class))->getMethods())
122 );
123 }
124
125 public function testClassSchemaDetectsMethodVisibility()
126 {
127 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
128
129 $methodDefinition = $classSchema->getMethod('publicMethod');
130 static::assertTrue($methodDefinition['public']);
131 static::assertFalse($methodDefinition['protected']);
132 static::assertFalse($methodDefinition['private']);
133
134 $methodDefinition = $classSchema->getMethod('protectedMethod');
135 static::assertFalse($methodDefinition['public']);
136 static::assertTrue($methodDefinition['protected']);
137 static::assertFalse($methodDefinition['private']);
138
139 $methodDefinition = $classSchema->getMethod('privateMethod');
140 static::assertFalse($methodDefinition['public']);
141 static::assertFalse($methodDefinition['protected']);
142 static::assertTrue($methodDefinition['private']);
143 }
144
145 public function testClassSchemaDetectsInjectProperties()
146 {
147 $classSchema = new ClassSchema(Fixture\DummyClassWithInjectDoctrineAnnotation::class);
148 static::assertTrue($classSchema->hasInjectProperties());
149
150 $injectProperties = $classSchema->getInjectProperties();
151 static::assertArrayHasKey('propertyWithFullQualifiedClassName', $injectProperties);
152 static::assertSame(Fixture\DummyClassWithInjectDoctrineAnnotation::class, $injectProperties['propertyWithFullQualifiedClassName']);
153
154 static::assertArrayHasKey('propertyWithRelativeClassName', $injectProperties);
155 static::assertSame('DummyClassWithInjectDoctrineAnnotation', $injectProperties['propertyWithRelativeClassName']);
156
157 static::assertArrayHasKey('propertyWithImportedClassName', $injectProperties);
158 static::assertSame('ClassSchemaTest', $injectProperties['propertyWithImportedClassName']);
159
160 static::assertArrayHasKey('propertyWithImportedAndAliasedClassName', $injectProperties);
161 static::assertSame('AliasedClassSchemaTest', $injectProperties['propertyWithImportedAndAliasedClassName']);
162 }
163
164 public function testClassSchemaDetectsInjectMethods()
165 {
166 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
167 static::assertTrue($classSchema->hasInjectMethods());
168
169 $methodDefinition = $classSchema->getMethod('injectSettings');
170 static::assertFalse($methodDefinition['injectMethod']);
171
172 $methodDefinition = $classSchema->getMethod('injectMethodWithoutParam');
173 static::assertFalse($methodDefinition['injectMethod']);
174
175 $methodDefinition = $classSchema->getMethod('injectMethodThatIsProtected');
176 static::assertFalse($methodDefinition['injectMethod']);
177
178 $methodDefinition = $classSchema->getMethod('injectFoo');
179 static::assertTrue($methodDefinition['injectMethod']);
180
181 $injectMethods = $classSchema->getInjectMethods();
182 static::assertArrayHasKey('injectFoo', $injectMethods);
183 }
184
185 public function testClassSchemaDetectsPropertiesWithLazyAnnotation()
186 {
187 $classSchema = new ClassSchema(Fixture\DummyClassWithLazyDoctrineAnnotation::class);
188 static::assertTrue($classSchema->getProperty('propertyWithLazyAnnotation')['annotations']['lazy']);
189 }
190
191 public function testClassSchemaDetectsStaticMethods()
192 {
193 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
194
195 $methodDefinition = $classSchema->getMethod('staticMethod');
196 static::assertTrue($methodDefinition['static']);
197 }
198
199 public function testClassSchemaDetectsMandatoryParams()
200 {
201 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
202
203 $methodDefinition = $classSchema->getMethod('methodWithMandatoryParam');
204 static::assertFalse($methodDefinition['params']['param']['optional']);
205 }
206
207 public function testClassSchemaDetectsNullableParams()
208 {
209 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
210
211 $methodDefinition = $classSchema->getMethod('methodWithNullableParam');
212 static::assertTrue($methodDefinition['params']['param']['nullable']);
213 }
214
215 public function testClassSchemaDetectsDefaultValueParams()
216 {
217 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
218
219 $methodDefinition = $classSchema->getMethod('methodWithDefaultValueParam');
220 static::assertSame('foo', $methodDefinition['params']['param']['default']);
221 }
222
223 public function testClassSchemaDetectsParamTypeFromTypeHint()
224 {
225 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfMethods::class);
226
227 $methodDefinition = $classSchema->getMethod('methodWithTypeHintedParam');
228 static::assertSame('string', $methodDefinition['params']['param']['type']);
229 }
230
231 public function testClassSchemaDetectsPropertyVisibility()
232 {
233 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
234
235 $propertyDefinition = $classSchema->getProperty('publicProperty');
236 static::assertTrue($propertyDefinition['public']);
237 static::assertFalse($propertyDefinition['protected']);
238 static::assertFalse($propertyDefinition['private']);
239
240 $propertyDefinition = $classSchema->getProperty('protectedProperty');
241 static::assertFalse($propertyDefinition['public']);
242 static::assertTrue($propertyDefinition['protected']);
243 static::assertFalse($propertyDefinition['private']);
244
245 $propertyDefinition = $classSchema->getProperty('privateProperty');
246 static::assertFalse($propertyDefinition['public']);
247 static::assertFalse($propertyDefinition['protected']);
248 static::assertTrue($propertyDefinition['private']);
249 }
250
251 public function testClassSchemaDetectsInjectProperty()
252 {
253 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
254
255 $propertyDefinition = $classSchema->getProperty('propertyWithInjectAnnotation');
256 static::assertTrue($propertyDefinition['annotations']['inject']);
257 }
258
259 public function testClassSchemaDetectsTransientProperty()
260 {
261 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
262
263 $propertyDefinition = $classSchema->getProperty('propertyWithTransientAnnotation');
264 static::assertTrue($propertyDefinition['annotations']['transient']);
265 }
266
267 public function testClassSchemaDetectsCascadeProperty()
268 {
269 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
270
271 $propertyDefinition = $classSchema->getProperty('propertyWithCascadeAnnotation');
272 static::assertSame('remove', $propertyDefinition['annotations']['cascade']);
273 }
274
275 public function testClassSchemaDetectsCascadePropertyOnlyWithVarAnnotation()
276 {
277 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
278
279 $propertyDefinition = $classSchema->getProperty('propertyWithCascadeAnnotationWithoutVarAnnotation');
280 static::assertNull($propertyDefinition['annotations']['cascade']);
281 }
282
283 public function testClassSchemaDetectsIgnoreValidationAnnotation()
284 {
285 $classSchema = new ClassSchema(Fixture\DummyControllerWithIgnoreValidationDoctrineAnnotation::class);
286 static::assertTrue(isset($classSchema->getMethod('someAction')['tags']['ignorevalidation']));
287 static::assertTrue(in_array('foo', $classSchema->getMethod('someAction')['tags']['ignorevalidation'], true));
288 static::assertTrue(in_array('bar', $classSchema->getMethod('someAction')['tags']['ignorevalidation'], true));
289 static::assertFalse(in_array('baz', $classSchema->getMethod('someAction')['tags']['ignorevalidation'], true));
290 }
291
292 public function testClassSchemaDetectsTypeAndElementType()
293 {
294 $classSchema = new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class);
295
296 $propertyDefinition = $classSchema->getProperty('propertyWithObjectStorageAnnotation');
297 static::assertSame(ObjectStorage::class, $propertyDefinition['type']);
298 static::assertSame(Fixture\DummyClassWithAllTypesOfProperties::class, $propertyDefinition['elementType']);
299 }
300
301 public function testClassSchemaDetectsSingletons()
302 {
303 static::assertTrue((new ClassSchema(Fixture\DummySingleton::class))->isSingleton());
304 }
305
306 public function testClassSchemaDetectsModels()
307 {
308 static::assertTrue((new ClassSchema(Fixture\DummyEntity::class))->isModel());
309 static::assertTrue((new ClassSchema(Fixture\DummyValueObject::class))->isModel());
310 }
311
312 public function testClassSchemaDetectsEntities()
313 {
314 static::assertTrue((new ClassSchema(Fixture\DummyEntity::class))->isEntity());
315 }
316
317 public function testClassSchemaDetectsValueObjects()
318 {
319 static::assertTrue((new ClassSchema(Fixture\DummyValueObject::class))->isValueObject());
320 }
321
322 public function testClassSchemaDetectsClassName()
323 {
324 static::assertSame(Fixture\DummyModel::class, (new ClassSchema(Fixture\DummyModel::class))->getClassName());
325 }
326
327 public function testClassSchemaDetectsNonStaticProperties()
328 {
329 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('publicProperty'));
330 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('protectedProperty'));
331 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('privateProperty'));
332 }
333
334 public function testClassSchemaDetectsStaticProperties()
335 {
336 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('publicStaticProperty'));
337 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('protectedStaticProperty'));
338 static::assertTrue((new ClassSchema(Fixture\DummyClassWithAllTypesOfProperties::class))->hasProperty('privateStaticProperty'));
339 }
340
341 public function testClassSchemaGetTags()
342 {
343 $tags = (new ClassSchema(Fixture\DummyClassWithTags::class))->getTags();
344 static::assertArrayHasKey('see', $tags);
345
346 // test ignored tags
347 static::assertArrayNotHasKey('package', $tags);
348 static::assertArrayNotHasKey('subpackage', $tags);
349 static::assertArrayNotHasKey('license', $tags);
350 static::assertArrayNotHasKey('copyright', $tags);
351 static::assertArrayNotHasKey('author', $tags);
352 static::assertArrayNotHasKey('version', $tags);
353 }
354
355 /**
356 * @test
357 */
358 public function classSchemaDetectsValidateAnnotationsModelProperties()
359 {
360 $classSchema = new ClassSchema(Fixture\DummyModel::class);
361
362 static::assertSame(
363 [],
364 $classSchema->getProperty('propertyWithoutValidateAnnotations')['validators']
365 );
366 static::assertSame(
367 [
368 [
369 'name' => 'StringLength',
370 'options' => [
371 'minimum' => '1',
372 'maximum' => '10',
373 ],
374 'className' => StringLengthValidator::class
375 ],
376 [
377 'name' => 'NotEmpty',
378 'options' => [],
379 'className' => NotEmptyValidator::class
380 ],
381 [
382 'name' => 'TYPO3.CMS.Extbase:NotEmpty',
383 'options' => [],
384 'className' => NotEmptyValidator::class
385 ],
386 [
387 'name' => 'TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator',
388 'options' => [],
389 'className' => Fixture\Validation\Validator\DummyValidator::class
390 ],
391 [
392 'name' => '\TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator',
393 'options' => [],
394 'className' => NotEmptyValidator::class
395 ],
396 [
397 'name' => NotEmptyValidator::class,
398 'options' => [],
399 'className' => NotEmptyValidator::class
400 ]
401 ],
402 $classSchema->getProperty('propertyWithValidateAnnotations')['validators']
403 );
404 }
405
406 /**
407 * @test
408 */
409 public function classSchemaDetectsValidateAnnotationsOfControllerActions()
410 {
411 $classSchema = new ClassSchema(Fixture\DummyController::class);
412
413 static::assertSame(
414 [],
415 $classSchema->getMethod('methodWithoutValidateAnnotationsAction')['params']['fooParam']['validators']
416 );
417
418 static::assertSame(
419 [
420 [
421 'name' => 'StringLength',
422 'options' => [
423 'minimum' => '1',
424 'maximum' => '10',
425 ],
426 'className' => StringLengthValidator::class
427 ],
428 [
429 'name' => 'NotEmpty',
430 'options' => [],
431 'className' => NotEmptyValidator::class
432 ],
433 [
434 'name' => 'TYPO3.CMS.Extbase:NotEmpty',
435 'options' => [],
436 'className' => NotEmptyValidator::class
437 ],
438 [
439 'name' => 'TYPO3.CMS.Extbase.Tests.Unit.Reflection.Fixture:DummyValidator',
440 'options' => [],
441 'className' => Fixture\Validation\Validator\DummyValidator::class
442 ],
443 [
444 'name' => '\TYPO3\CMS\Extbase\Validation\Validator\NotEmptyValidator',
445 'options' => [],
446 'className' => NotEmptyValidator::class
447 ],
448 [
449 'name' => NotEmptyValidator::class,
450 'options' => [],
451 'className' => NotEmptyValidator::class
452 ]
453 ],
454 $classSchema->getMethod('methodWithValidateAnnotationsAction')['params']['fooParam']['validators']
455 );
456 }
457
458 /**
459 * @test
460 */
461 public function classSchemaGenerationThrowsExceptionWithValidateAnnotationsForParamWithoutTypeHint()
462 {
463 $this->expectException(InvalidTypeHintException::class);
464 $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.');
465 $this->expectExceptionCode(1515075192);
466
467 new ClassSchema(Fixture\DummyControllerWithValidateAnnotationWithoutParamTypeHint::class);
468 }
469
470 /**
471 * @test
472 */
473 public function classSchemaGenerationThrowsExceptionWithValidateAnnotationsForMissingParam()
474 {
475 $this->expectException(InvalidValidationConfigurationException::class);
476 $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');
477 $this->expectExceptionCode(1515073585);
478
479 new ClassSchema(Fixture\DummyControllerWithValidateAnnotationWithoutParam::class);
480 }
481 }