PoC_uncached_AbsctractViewHelper_reflection_access.patch

Oliver Eglseder, 2017-02-09 17:59

Download (47 KB)

View differences:

typo3/sysext/extbase/Classes/Reflection/ReflectionService.php (revision )
14 14
 * The TYPO3 project - inspiring people to share!
15 15
 */
16 16

  
17
use TYPO3\CMS\Core\Utility\ClassNamingUtility;
18
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
19

  
20 17
/**
21 18
 * A backport of the TYPO3.Flow reflection service for aquiring reflection based information.
22 19
 * Most of the code is based on the TYPO3.Flow reflection service.
23 20
 *
24 21
 * @api
25 22
 */
26
class ReflectionService implements \TYPO3\CMS\Core\SingletonInterface
23
class ReflectionService extends Refl implements \TYPO3\CMS\Core\SingletonInterface
27 24
{
28
    /**
29
     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
30
     */
31
    protected $objectManager;
32

  
33
    /**
34
     * Whether this service has been initialized.
35
     *
36
     * @var bool
37
     */
38
    protected $initialized = false;
39

  
40
    /**
41
     * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
42
     */
43
    protected $dataCache;
44

  
45
    /**
46
     * Whether class alterations should be detected on each initialization.
47
     *
48
     * @var bool
49
     */
50
    protected $detectClassChanges = false;
51

  
52
    /**
53
     * All available class names to consider. Class name = key, value is the
54
     * UNIX timestamp the class was reflected.
55
     *
56
     * @var array
57
     */
58
    protected $reflectedClassNames = [];
59

  
60
    /**
61
     * Array of tags and the names of classes which are tagged with them.
62
     *
63
     * @var array
64
     */
65
    protected $taggedClasses = [];
66

  
67
    /**
68
     * Array of class names and their tags and values.
69
     *
70
     * @var array
71
     */
72
    protected $classTagsValues = [];
73

  
74
    /**
75
     * Array of class names, method names and their tags and values.
76
     *
77
     * @var array
78
     */
79
    protected $methodTagsValues = [];
80

  
81
    /**
82
     * Array of class names, method names, their parameters and additional
83
     * information about the parameters.
84
     *
85
     * @var array
86
     */
87
    protected $methodParameters = [];
88

  
89
    /**
90
     * Array of class names and names of their properties.
91
     *
92
     * @var array
93
     */
94
    protected $classPropertyNames = [];
95

  
96
    /**
97
     * Array of class names and names of their methods.
98
     *
99
     * @var array
100
     */
101
    protected $classMethodNames = [];
102

  
103
    /**
104
     * Array of class names, property names and their tags and values.
105
     *
106
     * @var array
107
     */
108
    protected $propertyTagsValues = [];
109

  
110
    /**
111
     * List of tags which are ignored while reflecting class and method annotations.
112
     *
113
     * @var array
114
     */
115
    protected $ignoredTags = ['package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const'];
116

  
117
    /**
118
     * Indicates whether the Reflection cache needs to be updated.
119
     *
120
     * This flag needs to be set as soon as new Reflection information was
121
     * created.
122
     *
123
     * @see reflectClass()
124
     * @see getMethodReflection()
125
     * @var bool
126
     */
127
    protected $dataCacheNeedsUpdate = false;
128

  
129
    /**
130
     * Local cache for Class schemata
131
     *
132
     * @var array
133
     */
134
    protected $classSchemata = [];
135

  
136
    /**
137
     * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
138
     */
139
    protected $configurationManager;
140

  
141
    /**
142
     * @var string
143
     */
144
    protected $cacheIdentifier;
145

  
146
    /**
147
     * Internal runtime cache of method reflection objects
148
     *
149
     * @var array
150
     */
151
    protected $methodReflections = [];
152

  
153
    /**
154
     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
155
     */
156
    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
157
    {
158
        $this->objectManager = $objectManager;
159
    }
160

  
161
    /**
162
     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
163
     */
164
    public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
165
    {
166
        $this->configurationManager = $configurationManager;
167
    }
168

  
169
    /**
170
     * Sets the data cache.
171
     *
172
     * The cache must be set before initializing the Reflection Service.
173
     *
174
     * @param \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache Cache for the Reflection service
175
     * @return void
176
     */
177
    public function setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache)
178
    {
179
        $this->dataCache = $dataCache;
180
    }
181

  
182
    /**
183
     * Initializes this service
184
     *
185
     * @throws Exception
186
     * @return void
187
     */
188
    public function initialize()
189
    {
190
        if ($this->initialized) {
191
            throw new Exception('The Reflection Service can only be initialized once.', 1232044696);
192
        }
193
        $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
194
        $this->cacheIdentifier = 'ReflectionData_' . $frameworkConfiguration['extensionName'];
195
        $this->loadFromCache();
196
        $this->initialized = true;
197
    }
198

  
199
    /**
200
     * Returns whether the Reflection Service is initialized.
201
     *
202
     * @return bool true if the Reflection Service is initialized, otherwise false
203
     */
204
    public function isInitialized()
205
    {
206
        return $this->initialized;
207
    }
208

  
209
    /**
210
     * Shuts the Reflection Service down.
211
     *
212
     * @return void
213
     */
214
    public function shutdown()
215
    {
216
        if ($this->dataCacheNeedsUpdate) {
217
            $this->saveToCache();
218
        }
219
        $this->initialized = false;
220
    }
221

  
222
    /**
223
     * Returns all tags and their values the specified class is tagged with
224
     *
225
     * @param string $className Name of the class
226
     * @return array An array of tags and their values or an empty array if no tags were found
227
     */
228
    public function getClassTagsValues($className)
229
    {
230
        if (!isset($this->reflectedClassNames[$className])) {
231
            $this->reflectClass($className);
232
        }
233
        if (!isset($this->classTagsValues[$className])) {
234
            return [];
235
        }
236
        return isset($this->classTagsValues[$className]) ? $this->classTagsValues[$className] : [];
237
    }
238

  
239
    /**
240
     * Returns the values of the specified class tag
241
     *
242
     * @param string $className Name of the class containing the property
243
     * @param string $tag Tag to return the values of
244
     * @return array An array of values or an empty array if the tag was not found
245
     */
246
    public function getClassTagValues($className, $tag)
247
    {
248
        if (!isset($this->reflectedClassNames[$className])) {
249
            $this->reflectClass($className);
250
        }
251
        if (!isset($this->classTagsValues[$className])) {
252
            return [];
253
        }
254
        return isset($this->classTagsValues[$className][$tag]) ? $this->classTagsValues[$className][$tag] : [];
255
    }
256

  
257
    /**
258
     * Returns the names of all properties of the specified class
259
     *
260
     * @param string $className Name of the class to return the property names of
261
     * @return array An array of property names or an empty array if none exist
262
     */
263
    public function getClassPropertyNames($className)
264
    {
265
        if (!isset($this->reflectedClassNames[$className])) {
266
            $this->reflectClass($className);
267
        }
268
        return isset($this->classPropertyNames[$className]) ? $this->classPropertyNames[$className] : [];
269
    }
270

  
271
    /**
272
     * Returns the class schema for the given class
273
     *
274
     * @param mixed $classNameOrObject The class name or an object
275
     * @return ClassSchema
276
     */
277
    public function getClassSchema($classNameOrObject)
278
    {
279
        $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
280
        if (isset($this->classSchemata[$className])) {
281
            return $this->classSchemata[$className];
282
        } else {
283
            return $this->buildClassSchema($className);
284
        }
285
    }
286

  
287
    /**
288
     * Wrapper for method_exists() which tells if the given method exists.
289
     *
290
     * @param string $className Name of the class containing the method
291
     * @param string $methodName Name of the method
292
     * @return bool
293
     */
294
    public function hasMethod($className, $methodName)
295
    {
296
        try {
297
            if (!array_key_exists($className, $this->classMethodNames) || !array_key_exists($methodName, $this->classMethodNames[$className])) {
298
                $this->getMethodReflection($className, $methodName);
299
                $this->classMethodNames[$className][$methodName] = true;
300
            }
301
        } catch (\ReflectionException $e) {
302
            // Method does not exist. Store this information in cache.
303
            $this->classMethodNames[$className][$methodName] = null;
304
        }
305
        return isset($this->classMethodNames[$className][$methodName]);
306
    }
307

  
308
    /**
309
     * Returns all tags and their values the specified method is tagged with
310
     *
311
     * @param string $className Name of the class containing the method
312
     * @param string $methodName Name of the method to return the tags and values of
313
     * @return array An array of tags and their values or an empty array of no tags were found
314
     */
315
    public function getMethodTagsValues($className, $methodName)
316
    {
317
        if (!isset($this->methodTagsValues[$className][$methodName])) {
318
            $method = $this->getMethodReflection($className, $methodName);
319
            $this->methodTagsValues[$className][$methodName] = [];
320
            foreach ($method->getTagsValues() as $tag => $values) {
321
                if (array_search($tag, $this->ignoredTags) === false) {
322
                    $this->methodTagsValues[$className][$methodName][$tag] = $values;
323
                }
324
            }
325
        }
326
        return $this->methodTagsValues[$className][$methodName];
327
    }
328

  
329
    /**
330
     * Returns an array of parameters of the given method. Each entry contains
331
     * additional information about the parameter position, type hint etc.
332
     *
333
     * @param string $className Name of the class containing the method
334
     * @param string $methodName Name of the method to return parameter information of
335
     * @return array An array of parameter names and additional information or an empty array of no parameters were found
336
     */
337
    public function getMethodParameters($className, $methodName)
338
    {
339
        if (!isset($this->methodParameters[$className][$methodName])) {
340
            $method = $this->getMethodReflection($className, $methodName);
341
            $this->methodParameters[$className][$methodName] = [];
342
            foreach ($method->getParameters() as $parameterPosition => $parameter) {
343
                $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
344
            }
345
        }
346
        return $this->methodParameters[$className][$methodName];
347
    }
348

  
349
    /**
350
     * Returns all tags and their values the specified class property is tagged with
351
     *
352
     * @param string $className Name of the class containing the property
353
     * @param string $propertyName Name of the property to return the tags and values of
354
     * @return array An array of tags and their values or an empty array of no tags were found
355
     */
356
    public function getPropertyTagsValues($className, $propertyName)
357
    {
358
        if (!isset($this->reflectedClassNames[$className])) {
359
            $this->reflectClass($className);
360
        }
361
        if (!isset($this->propertyTagsValues[$className])) {
362
            return [];
363
        }
364
        return isset($this->propertyTagsValues[$className][$propertyName]) ? $this->propertyTagsValues[$className][$propertyName] : [];
365
    }
366

  
367
    /**
368
     * Returns the values of the specified class property tag
369
     *
370
     * @param string $className Name of the class containing the property
371
     * @param string $propertyName Name of the tagged property
372
     * @param string $tag Tag to return the values of
373
     * @return array An array of values or an empty array if the tag was not found
374
     */
375
    public function getPropertyTagValues($className, $propertyName, $tag)
376
    {
377
        if (!isset($this->reflectedClassNames[$className])) {
378
            $this->reflectClass($className);
379
        }
380
        if (!isset($this->propertyTagsValues[$className][$propertyName])) {
381
            return [];
382
        }
383
        return isset($this->propertyTagsValues[$className][$propertyName][$tag]) ? $this->propertyTagsValues[$className][$propertyName][$tag] : [];
384
    }
385

  
386
    /**
387
     * Tells if the specified class is known to this reflection service and
388
     * reflection information is available.
389
     *
390
     * @param string $className Name of the class
391
     * @return bool If the class is reflected by this service
392
     */
393
    public function isClassReflected($className)
394
    {
395
        return isset($this->reflectedClassNames[$className]);
396
    }
397

  
398
    /**
399
     * Tells if the specified class is tagged with the given tag
400
     *
401
     * @param string $className Name of the class
402
     * @param string $tag Tag to check for
403
     * @return bool TRUE if the class is tagged with $tag, otherwise FALSE
404
     */
405
    public function isClassTaggedWith($className, $tag)
406
    {
407
        if ($this->initialized === false) {
408
            return false;
409
        }
410
        if (!isset($this->reflectedClassNames[$className])) {
411
            $this->reflectClass($className);
412
        }
413
        if (!isset($this->classTagsValues[$className])) {
414
            return false;
415
        }
416
        return isset($this->classTagsValues[$className][$tag]);
417
    }
418

  
419
    /**
420
     * Tells if the specified class property is tagged with the given tag
421
     *
422
     * @param string $className Name of the class
423
     * @param string $propertyName Name of the property
424
     * @param string $tag Tag to check for
425
     * @return bool TRUE if the class property is tagged with $tag, otherwise FALSE
426
     */
427
    public function isPropertyTaggedWith($className, $propertyName, $tag)
428
    {
429
        if (!isset($this->reflectedClassNames[$className])) {
430
            $this->reflectClass($className);
431
        }
432
        if (!isset($this->propertyTagsValues[$className])) {
433
            return false;
434
        }
435
        if (!isset($this->propertyTagsValues[$className][$propertyName])) {
436
            return false;
437
        }
438
        return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
439
    }
440

  
441
    /**
442
     * Reflects the given class and stores the results in this service's properties.
443
     *
444
     * @param string $className Full qualified name of the class to reflect
445
     * @return void
446
     */
447
    protected function reflectClass($className)
448
    {
449
        $class = new ClassReflection($className);
450
        $this->reflectedClassNames[$className] = time();
451
        foreach ($class->getTagsValues() as $tag => $values) {
452
            if (array_search($tag, $this->ignoredTags) === false) {
453
                $this->taggedClasses[$tag][] = $className;
454
                $this->classTagsValues[$className][$tag] = $values;
455
            }
456
        }
457
        foreach ($class->getProperties() as $property) {
458
            $propertyName = $property->getName();
459
            $this->classPropertyNames[$className][] = $propertyName;
460
            foreach ($property->getTagsValues() as $tag => $values) {
461
                if (array_search($tag, $this->ignoredTags) === false) {
462
                    $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
463
                }
464
            }
465
        }
466
        foreach ($class->getMethods() as $method) {
467
            $methodName = $method->getName();
468
            foreach ($method->getTagsValues() as $tag => $values) {
469
                if (array_search($tag, $this->ignoredTags) === false) {
470
                    $this->methodTagsValues[$className][$methodName][$tag] = $values;
471
                }
472
            }
473
            foreach ($method->getParameters() as $parameterPosition => $parameter) {
474
                $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
475
            }
476
        }
477
        ksort($this->reflectedClassNames);
478
        $this->dataCacheNeedsUpdate = true;
479
    }
480

  
481
    /**
482
     * Builds class schemata from classes annotated as entities or value objects
483
     *
484
     * @param string $className
485
     * @throws Exception\UnknownClassException
486
     * @return ClassSchema The class schema
487
     */
488
    protected function buildClassSchema($className)
489
    {
490
        if (!class_exists($className)) {
491
            throw new Exception\UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
492
        }
493
        $classSchema = $this->objectManager->get(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, $className);
494
        if (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class)) {
495
            $classSchema->setModelType(ClassSchema::MODELTYPE_ENTITY);
496
            $possibleRepositoryClassName = ClassNamingUtility::translateModelNameToRepositoryName($className);
497
            if (class_exists($possibleRepositoryClassName)) {
498
                $classSchema->setAggregateRoot(true);
499
            }
500
        } elseif (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject::class)) {
501
            $classSchema->setModelType(ClassSchema::MODELTYPE_VALUEOBJECT);
502
        }
503
        foreach ($this->getClassPropertyNames($className) as $propertyName) {
504
            if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
505
                $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
506
                $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
507
            }
508
            if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
509
                $classSchema->setUuidPropertyName($propertyName);
510
            }
511
            if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
512
                $classSchema->markAsIdentityProperty($propertyName);
513
            }
514
        }
515
        $this->classSchemata[$className] = $classSchema;
516
        $this->dataCacheNeedsUpdate = true;
517
        return $classSchema;
518
    }
519

  
520
    /**
521
     * Converts the given parameter reflection into an information array
522
     *
523
     * @param ParameterReflection $parameter The parameter to reflect
524
     * @param int $parameterPosition
525
     * @param MethodReflection|NULL $method
526
     * @return array Parameter information array
527
     */
528
    protected function convertParameterReflectionToArray(ParameterReflection $parameter, $parameterPosition, MethodReflection $method = null)
529
    {
530
        $parameterInformation = [
531
            'position' => $parameterPosition,
532
            'byReference' => $parameter->isPassedByReference(),
533
            'array' => $parameter->isArray(),
534
            'optional' => $parameter->isOptional(),
535
            'allowsNull' => $parameter->allowsNull()
536
        ];
537
        $parameterClass = $parameter->getClass();
538
        $parameterInformation['class'] = $parameterClass !== null ? $parameterClass->getName() : null;
539
        if ($parameter->isDefaultValueAvailable()) {
540
            $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
541
        }
542
        if ($parameterClass !== null) {
543
            $parameterInformation['type'] = $parameterClass->getName();
544
        } elseif ($method !== null) {
545
            $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
546
            if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
547
                $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
548
                if (count($explodedParameters) >= 2) {
549
                    if (TypeHandlingUtility::isSimpleType($explodedParameters[0])) {
550
                        // ensure that short names of simple types are resolved correctly to the long form
551
                        // this is important for all kinds of type checks later on
552
                        $typeInfo = TypeHandlingUtility::parseType($explodedParameters[0]);
553
                        $parameterInformation['type'] = $typeInfo['type'];
554
                    } else {
555
                        $parameterInformation['type'] = $explodedParameters[0];
556
                    }
557
                }
558
            }
559
        }
560
        if (isset($parameterInformation['type']) && $parameterInformation['type'][0] === '\\') {
561
            $parameterInformation['type'] = substr($parameterInformation['type'], 1);
562
        }
563
        return $parameterInformation;
564
    }
565

  
566
    /**
567
     * Returns the Reflection of a method.
568
     *
569
     * @param string $className Name of the class containing the method
570
     * @param string $methodName Name of the method to return the Reflection for
571
     * @return MethodReflection the method Reflection object
572
     */
573
    protected function getMethodReflection($className, $methodName)
574
    {
575
        $this->dataCacheNeedsUpdate = true;
576
        if (!isset($this->methodReflections[$className][$methodName])) {
577
            $this->methodReflections[$className][$methodName] = new MethodReflection($className, $methodName);
578
        }
579
        return $this->methodReflections[$className][$methodName];
580
    }
581

  
582
    /**
583
     * Tries to load the reflection data from this service's cache.
584
     *
585
     * @return void
586
     */
587
    protected function loadFromCache()
588
    {
589
        $data = $this->dataCache->get($this->cacheIdentifier);
590
        if ($data !== false) {
591
            foreach ($data as $propertyName => $propertyValue) {
592
                $this->{$propertyName} = $propertyValue;
593
            }
594
        }
595
    }
596

  
597
    /**
598
     * Exports the internal reflection data into the ReflectionData cache.
599
     *
600
     * @throws Exception
601
     * @return void
602
     */
603
    protected function saveToCache()
604
    {
605
        if (!is_object($this->dataCache)) {
606
            throw new Exception('A cache must be injected before initializing the Reflection Service.', 1232044697);
607
        }
608
        $data = [];
609
        $propertyNames = [
610
            'reflectedClassNames',
611
            'classPropertyNames',
612
            'classMethodNames',
613
            'classTagsValues',
614
            'methodTagsValues',
615
            'methodParameters',
616
            'propertyTagsValues',
617
            'taggedClasses',
618
            'classSchemata'
619
        ];
620
        foreach ($propertyNames as $propertyName) {
621
            $data[$propertyName] = $this->{$propertyName};
622
        }
623
        $this->dataCache->set($this->cacheIdentifier, $data);
624
        $this->dataCacheNeedsUpdate = false;
625
    }
626 25
}
typo3/sysext/fluid/Classes/Core/ViewHelper/AbstractViewHelper.php (revision )
17 17
use Psr\Log\LoggerInterface;
18 18
use TYPO3\CMS\Core\Log\LogManager;
19 19
use TYPO3\CMS\Core\Utility\GeneralUtility;
20
use TYPO3\CMS\Extbase\Reflection\Refl;
20 21

  
21 22
/**
22 23
 * The abstract base class for all view helpers.
......
72 73
     */
73 74
    public function injectReflectionService(\TYPO3\CMS\Extbase\Reflection\ReflectionService $reflectionService)
74 75
    {
75
        $this->reflectionService = $reflectionService;
76
        $this->reflectionService = new Refl();
76 77
    }
77 78

  
78 79
    /**
typo3/sysext/extbase/Classes/Reflection/Refl.php (revision )
1
<?php
2
namespace TYPO3\CMS\Extbase\Reflection;
3

  
4
use TYPO3\CMS\Core\Utility\ClassNamingUtility;
5
use TYPO3\CMS\Extbase\Utility\TypeHandlingUtility;
6

  
7
class Refl
8
{
9
    /**
10
     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
11
     */
12
    protected $objectManager;
13

  
14
    /**
15
     * Whether this service has been initialized.
16
     *
17
     * @var bool
18
     */
19
    protected $initialized = false;
20

  
21
    /**
22
     * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
23
     */
24
    protected $dataCache;
25

  
26
    /**
27
     * Whether class alterations should be detected on each initialization.
28
     *
29
     * @var bool
30
     */
31
    protected $detectClassChanges = false;
32

  
33
    /**
34
     * All available class names to consider. Class name = key, value is the
35
     * UNIX timestamp the class was reflected.
36
     *
37
     * @var array
38
     */
39
    protected $reflectedClassNames = [];
40

  
41
    /**
42
     * Array of tags and the names of classes which are tagged with them.
43
     *
44
     * @var array
45
     */
46
    protected $taggedClasses = [];
47

  
48
    /**
49
     * Array of class names and their tags and values.
50
     *
51
     * @var array
52
     */
53
    protected $classTagsValues = [];
54

  
55
    /**
56
     * Array of class names, method names and their tags and values.
57
     *
58
     * @var array
59
     */
60
    protected $methodTagsValues = [];
61

  
62
    /**
63
     * Array of class names, method names, their parameters and additional
64
     * information about the parameters.
65
     *
66
     * @var array
67
     */
68
    protected $methodParameters = [];
69

  
70
    /**
71
     * Array of class names and names of their properties.
72
     *
73
     * @var array
74
     */
75
    protected $classPropertyNames = [];
76

  
77
    /**
78
     * Array of class names and names of their methods.
79
     *
80
     * @var array
81
     */
82
    protected $classMethodNames = [];
83

  
84
    /**
85
     * Array of class names, property names and their tags and values.
86
     *
87
     * @var array
88
     */
89
    protected $propertyTagsValues = [];
90

  
91
    /**
92
     * List of tags which are ignored while reflecting class and method annotations.
93
     *
94
     * @var array
95
     */
96
    protected $ignoredTags = ['package', 'subpackage', 'license', 'copyright', 'author', 'version', 'const'];
97

  
98
    /**
99
     * Indicates whether the Reflection cache needs to be updated.
100
     *
101
     * This flag needs to be set as soon as new Reflection information was
102
     * created.
103
     *
104
     * @see reflectClass()
105
     * @see getMethodReflection()
106
     * @var bool
107
     */
108
    protected $dataCacheNeedsUpdate = false;
109

  
110
    /**
111
     * Local cache for Class schemata
112
     *
113
     * @var array
114
     */
115
    protected $classSchemata = [];
116

  
117
    /**
118
     * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
119
     */
120
    protected $configurationManager;
121

  
122
    /**
123
     * @var string
124
     */
125
    protected $cacheIdentifier;
126

  
127
    /**
128
     * Internal runtime cache of method reflection objects
129
     *
130
     * @var array
131
     */
132
    protected $methodReflections = [];
133

  
134
    /**
135
     * @param \TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager
136
     */
137
    public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
138
    {
139
        $this->objectManager = $objectManager;
140
    }
141

  
142
    /**
143
     * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
144
     */
145
    public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager)
146
    {
147
        $this->configurationManager = $configurationManager;
148
    }
149

  
150
    /**
151
     * Sets the data cache.
152
     *
153
     * The cache must be set before initializing the Reflection Service.
154
     *
155
     * @param \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache Cache for the Reflection service
156
     * @return void
157
     */
158
    public function setDataCache(\TYPO3\CMS\Core\Cache\Frontend\VariableFrontend $dataCache)
159
    {
160
        $this->dataCache = $dataCache;
161
    }
162

  
163
    /**
164
     * Initializes this service
165
     *
166
     * @throws Exception
167
     * @return void
168
     */
169
    public function initialize()
170
    {
171
        if ($this->initialized) {
172
            throw new Exception('The Reflection Service can only be initialized once.', 1232044696);
173
        }
174
        $frameworkConfiguration = $this->configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
175
        $this->cacheIdentifier = 'ReflectionData_' . $frameworkConfiguration['extensionName'];
176
        $this->loadFromCache();
177
        $this->initialized = true;
178
    }
179

  
180
    /**
181
     * Returns whether the Reflection Service is initialized.
182
     *
183
     * @return bool true if the Reflection Service is initialized, otherwise false
184
     */
185
    public function isInitialized()
186
    {
187
        return $this->initialized;
188
    }
189

  
190
    /**
191
     * Shuts the Reflection Service down.
192
     *
193
     * @return void
194
     */
195
    public function shutdown()
196
    {
197
        if ($this->dataCacheNeedsUpdate) {
198
            $this->saveToCache();
199
        }
200
        $this->initialized = false;
201
    }
202

  
203
    /**
204
     * Returns all tags and their values the specified class is tagged with
205
     *
206
     * @param string $className Name of the class
207
     * @return array An array of tags and their values or an empty array if no tags were found
208
     */
209
    public function getClassTagsValues($className)
210
    {
211
        if (!isset($this->reflectedClassNames[$className])) {
212
            $this->reflectClass($className);
213
        }
214
        if (!isset($this->classTagsValues[$className])) {
215
            return [];
216
        }
217
        return isset($this->classTagsValues[$className]) ? $this->classTagsValues[$className] : [];
218
    }
219

  
220
    /**
221
     * Returns the values of the specified class tag
222
     *
223
     * @param string $className Name of the class containing the property
224
     * @param string $tag Tag to return the values of
225
     * @return array An array of values or an empty array if the tag was not found
226
     */
227
    public function getClassTagValues($className, $tag)
228
    {
229
        if (!isset($this->reflectedClassNames[$className])) {
230
            $this->reflectClass($className);
231
        }
232
        if (!isset($this->classTagsValues[$className])) {
233
            return [];
234
        }
235
        return isset($this->classTagsValues[$className][$tag]) ? $this->classTagsValues[$className][$tag] : [];
236
    }
237

  
238
    /**
239
     * Returns the names of all properties of the specified class
240
     *
241
     * @param string $className Name of the class to return the property names of
242
     * @return array An array of property names or an empty array if none exist
243
     */
244
    public function getClassPropertyNames($className)
245
    {
246
        if (!isset($this->reflectedClassNames[$className])) {
247
            $this->reflectClass($className);
248
        }
249
        return isset($this->classPropertyNames[$className]) ? $this->classPropertyNames[$className] : [];
250
    }
251

  
252
    /**
253
     * Returns the class schema for the given class
254
     *
255
     * @param mixed $classNameOrObject The class name or an object
256
     * @return ClassSchema
257
     */
258
    public function getClassSchema($classNameOrObject)
259
    {
260
        $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
261
        if (isset($this->classSchemata[$className])) {
262
            return $this->classSchemata[$className];
263
        } else {
264
            return $this->buildClassSchema($className);
265
        }
266
    }
267

  
268
    /**
269
     * Wrapper for method_exists() which tells if the given method exists.
270
     *
271
     * @param string $className Name of the class containing the method
272
     * @param string $methodName Name of the method
273
     * @return bool
274
     */
275
    public function hasMethod($className, $methodName)
276
    {
277
        try {
278
            if (!array_key_exists($className, $this->classMethodNames) || !array_key_exists($methodName, $this->classMethodNames[$className])) {
279
                $this->getMethodReflection($className, $methodName);
280
                $this->classMethodNames[$className][$methodName] = true;
281
            }
282
        } catch (\ReflectionException $e) {
283
            // Method does not exist. Store this information in cache.
284
            $this->classMethodNames[$className][$methodName] = null;
285
        }
286
        return isset($this->classMethodNames[$className][$methodName]);
287
    }
288

  
289
    /**
290
     * Returns all tags and their values the specified method is tagged with
291
     *
292
     * @param string $className Name of the class containing the method
293
     * @param string $methodName Name of the method to return the tags and values of
294
     * @return array An array of tags and their values or an empty array of no tags were found
295
     */
296
    public function getMethodTagsValues($className, $methodName)
297
    {
298
        if (!isset($this->methodTagsValues[$className][$methodName])) {
299
            $method = $this->getMethodReflection($className, $methodName);
300
            $this->methodTagsValues[$className][$methodName] = [];
301
            foreach ($method->getTagsValues() as $tag => $values) {
302
                if (array_search($tag, $this->ignoredTags) === false) {
303
                    $this->methodTagsValues[$className][$methodName][$tag] = $values;
304
                }
305
            }
306
        }
307
        return $this->methodTagsValues[$className][$methodName];
308
    }
309

  
310
    /**
311
     * Returns an array of parameters of the given method. Each entry contains
312
     * additional information about the parameter position, type hint etc.
313
     *
314
     * @param string $className Name of the class containing the method
315
     * @param string $methodName Name of the method to return parameter information of
316
     * @return array An array of parameter names and additional information or an empty array of no parameters were found
317
     */
318
    public function getMethodParameters($className, $methodName)
319
    {
320
        if (!isset($this->methodParameters[$className][$methodName])) {
321
            $method = $this->getMethodReflection($className, $methodName);
322
            $this->methodParameters[$className][$methodName] = [];
323
            foreach ($method->getParameters() as $parameterPosition => $parameter) {
324
                $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
325
            }
326
        }
327
        return $this->methodParameters[$className][$methodName];
328
    }
329

  
330
    /**
331
     * Returns all tags and their values the specified class property is tagged with
332
     *
333
     * @param string $className Name of the class containing the property
334
     * @param string $propertyName Name of the property to return the tags and values of
335
     * @return array An array of tags and their values or an empty array of no tags were found
336
     */
337
    public function getPropertyTagsValues($className, $propertyName)
338
    {
339
        if (!isset($this->reflectedClassNames[$className])) {
340
            $this->reflectClass($className);
341
        }
342
        if (!isset($this->propertyTagsValues[$className])) {
343
            return [];
344
        }
345
        return isset($this->propertyTagsValues[$className][$propertyName]) ? $this->propertyTagsValues[$className][$propertyName] : [];
346
    }
347

  
348
    /**
349
     * Returns the values of the specified class property tag
350
     *
351
     * @param string $className Name of the class containing the property
352
     * @param string $propertyName Name of the tagged property
353
     * @param string $tag Tag to return the values of
354
     * @return array An array of values or an empty array if the tag was not found
355
     */
356
    public function getPropertyTagValues($className, $propertyName, $tag)
357
    {
358
        if (!isset($this->reflectedClassNames[$className])) {
359
            $this->reflectClass($className);
360
        }
361
        if (!isset($this->propertyTagsValues[$className][$propertyName])) {
362
            return [];
363
        }
364
        return isset($this->propertyTagsValues[$className][$propertyName][$tag]) ? $this->propertyTagsValues[$className][$propertyName][$tag] : [];
365
    }
366

  
367
    /**
368
     * Tells if the specified class is known to this reflection service and
369
     * reflection information is available.
370
     *
371
     * @param string $className Name of the class
372
     * @return bool If the class is reflected by this service
373
     */
374
    public function isClassReflected($className)
375
    {
376
        return isset($this->reflectedClassNames[$className]);
377
    }
378

  
379
    /**
380
     * Tells if the specified class is tagged with the given tag
381
     *
382
     * @param string $className Name of the class
383
     * @param string $tag Tag to check for
384
     * @return bool TRUE if the class is tagged with $tag, otherwise FALSE
385
     */
386
    public function isClassTaggedWith($className, $tag)
387
    {
388
        if ($this->initialized === false) {
389
            return false;
390
        }
391
        if (!isset($this->reflectedClassNames[$className])) {
392
            $this->reflectClass($className);
393
        }
394
        if (!isset($this->classTagsValues[$className])) {
395
            return false;
396
        }
397
        return isset($this->classTagsValues[$className][$tag]);
398
    }
399

  
400
    /**
401
     * Tells if the specified class property is tagged with the given tag
402
     *
403
     * @param string $className Name of the class
404
     * @param string $propertyName Name of the property
405
     * @param string $tag Tag to check for
406
     * @return bool TRUE if the class property is tagged with $tag, otherwise FALSE
407
     */
408
    public function isPropertyTaggedWith($className, $propertyName, $tag)
409
    {
410
        if (!isset($this->reflectedClassNames[$className])) {
411
            $this->reflectClass($className);
412
        }
413
        if (!isset($this->propertyTagsValues[$className])) {
414
            return false;
415
        }
416
        if (!isset($this->propertyTagsValues[$className][$propertyName])) {
417
            return false;
418
        }
419
        return isset($this->propertyTagsValues[$className][$propertyName][$tag]);
420
    }
421

  
422
    /**
423
     * Reflects the given class and stores the results in this service's properties.
424
     *
425
     * @param string $className Full qualified name of the class to reflect
426
     * @return void
427
     */
428
    protected function reflectClass($className)
429
    {
430
        $class = new ClassReflection($className);
431
        $this->reflectedClassNames[$className] = time();
432
        foreach ($class->getTagsValues() as $tag => $values) {
433
            if (array_search($tag, $this->ignoredTags) === false) {
434
                $this->taggedClasses[$tag][] = $className;
435
                $this->classTagsValues[$className][$tag] = $values;
436
            }
437
        }
438
        foreach ($class->getProperties() as $property) {
439
            $propertyName = $property->getName();
440
            $this->classPropertyNames[$className][] = $propertyName;
441
            foreach ($property->getTagsValues() as $tag => $values) {
442
                if (array_search($tag, $this->ignoredTags) === false) {
443
                    $this->propertyTagsValues[$className][$propertyName][$tag] = $values;
444
                }
445
            }
446
        }
447
        foreach ($class->getMethods() as $method) {
448
            $methodName = $method->getName();
449
            foreach ($method->getTagsValues() as $tag => $values) {
450
                if (array_search($tag, $this->ignoredTags) === false) {
451
                    $this->methodTagsValues[$className][$methodName][$tag] = $values;
452
                }
453
            }
454
            foreach ($method->getParameters() as $parameterPosition => $parameter) {
455
                $this->methodParameters[$className][$methodName][$parameter->getName()] = $this->convertParameterReflectionToArray($parameter, $parameterPosition, $method);
456
            }
457
        }
458
        ksort($this->reflectedClassNames);
459
        $this->dataCacheNeedsUpdate = true;
460
    }
461

  
462
    /**
463
     * Builds class schemata from classes annotated as entities or value objects
464
     *
465
     * @param string $className
466
     * @throws Exception\UnknownClassException
467
     * @return ClassSchema The class schema
468
     */
469
    protected function buildClassSchema($className)
470
    {
471
        if (!class_exists($className)) {
472
            throw new Exception\UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
473
        }
474
        $classSchema = $this->objectManager->get(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class, $className);
475
        if (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractEntity::class)) {
476
            $classSchema->setModelType(ClassSchema::MODELTYPE_ENTITY);
477
            $possibleRepositoryClassName = ClassNamingUtility::translateModelNameToRepositoryName($className);
478
            if (class_exists($possibleRepositoryClassName)) {
479
                $classSchema->setAggregateRoot(true);
480
            }
481
        } elseif (is_subclass_of($className, \TYPO3\CMS\Extbase\DomainObject\AbstractValueObject::class)) {
482
            $classSchema->setModelType(ClassSchema::MODELTYPE_VALUEOBJECT);
483
        }
484
        foreach ($this->getClassPropertyNames($className) as $propertyName) {
485
            if (!$this->isPropertyTaggedWith($className, $propertyName, 'transient') && $this->isPropertyTaggedWith($className, $propertyName, 'var')) {
486
                $cascadeTagValues = $this->getPropertyTagValues($className, $propertyName, 'cascade');
487
                $classSchema->addProperty($propertyName, implode(' ', $this->getPropertyTagValues($className, $propertyName, 'var')), $this->isPropertyTaggedWith($className, $propertyName, 'lazy'), $cascadeTagValues[0]);
488
            }
489
            if ($this->isPropertyTaggedWith($className, $propertyName, 'uuid')) {
490
                $classSchema->setUuidPropertyName($propertyName);
491
            }
492
            if ($this->isPropertyTaggedWith($className, $propertyName, 'identity')) {
493
                $classSchema->markAsIdentityProperty($propertyName);
494
            }
495
        }
496
        $this->classSchemata[$className] = $classSchema;
497
        $this->dataCacheNeedsUpdate = true;
498
        return $classSchema;
499
    }
500

  
501
    /**
502
     * Converts the given parameter reflection into an information array
503
     *
504
     * @param ParameterReflection $parameter The parameter to reflect
505
     * @param int $parameterPosition
506
     * @param MethodReflection|NULL $method
507
     * @return array Parameter information array
508
     */
509
    protected function convertParameterReflectionToArray(ParameterReflection $parameter, $parameterPosition, MethodReflection $method = null)
510
    {
511
        $parameterInformation = [
512
            'position' => $parameterPosition,
513
            'byReference' => $parameter->isPassedByReference(),
514
            'array' => $parameter->isArray(),
515
            'optional' => $parameter->isOptional(),
516
            'allowsNull' => $parameter->allowsNull()
517
        ];
518
        $parameterClass = $parameter->getClass();
519
        $parameterInformation['class'] = $parameterClass !== null ? $parameterClass->getName() : null;
520
        if ($parameter->isDefaultValueAvailable()) {
521
            $parameterInformation['defaultValue'] = $parameter->getDefaultValue();
522
        }
523
        if ($parameterClass !== null) {
524
            $parameterInformation['type'] = $parameterClass->getName();
525
        } elseif ($method !== null) {
526
            $methodTagsAndValues = $this->getMethodTagsValues($method->getDeclaringClass()->getName(), $method->getName());
527
            if (isset($methodTagsAndValues['param']) && isset($methodTagsAndValues['param'][$parameterPosition])) {
528
                $explodedParameters = explode(' ', $methodTagsAndValues['param'][$parameterPosition]);
529
                if (count($explodedParameters) >= 2) {
530
                    if (TypeHandlingUtility::isSimpleType($explodedParameters[0])) {
531
                        // ensure that short names of simple types are resolved correctly to the long form
532
                        // this is important for all kinds of type checks later on
533
                        $typeInfo = TypeHandlingUtility::parseType($explodedParameters[0]);
534
                        $parameterInformation['type'] = $typeInfo['type'];
535
                    } else {
536
                        $parameterInformation['type'] = $explodedParameters[0];
537
                    }
538
                }
539
            }
540
        }
541
        if (isset($parameterInformation['type']) && $parameterInformation['type'][0] === '\\') {
542
            $parameterInformation['type'] = substr($parameterInformation['type'], 1);
543
        }
544
        return $parameterInformation;
545
    }
546

  
547
    /**
548
     * Returns the Reflection of a method.
549
     *
550
     * @param string $className Name of the class containing the method
551
     * @param string $methodName Name of the method to return the Reflection for
552
     * @return MethodReflection the method Reflection object
553
     */
554
    protected function getMethodReflection($className, $methodName)
555
    {
556
        $this->dataCacheNeedsUpdate = true;
557
        if (!isset($this->methodReflections[$className][$methodName])) {
558
            $this->methodReflections[$className][$methodName] = new MethodReflection($className, $methodName);
559
        }
560
        return $this->methodReflections[$className][$methodName];
561
    }
562

  
563
    /**
564
     * Tries to load the reflection data from this service's cache.
565
     *
566
     * @return void
567
     */
568
    protected function loadFromCache()
569
    {
570
        $data = $this->dataCache->get($this->cacheIdentifier);
571
        if ($data !== false) {
572
            foreach ($data as $propertyName => $propertyValue) {
573
                $this->{$propertyName} = $propertyValue;
574
            }
575
        }
576
    }
577

  
578
    /**
579
     * Exports the internal reflection data into the ReflectionData cache.
580
     *
581
     * @throws Exception
582
     * @return void
583
     */
584
    protected function saveToCache()
585
    {
586
        if (!is_object($this->dataCache)) {
587
            throw new Exception('A cache must be injected before initializing the Reflection Service.', 1232044697);
588
        }
589
        $data = [];
590
        $propertyNames = [
591
            'reflectedClassNames',
592
            'classPropertyNames',
593
            'classMethodNames',
594
            'classTagsValues',
595
            'methodTagsValues',
596
            'methodParameters',
597
            'propertyTagsValues',
598
            'taggedClasses',
599
            'classSchemata'
600
        ];
601
        foreach ($propertyNames as $propertyName) {
602
            $data[$propertyName] = $this->{$propertyName};
603
        }
604
        $this->dataCache->set($this->cacheIdentifier, $data);
605
        $this->dataCacheNeedsUpdate = false;
606
    }
607
}