1<?php
2
3/*
4 * This file is part of the TYPO3 CMS project.
5 *
6 * It is free software; you can redistribute it and/or modify it under
7 * the terms of the GNU General Public License, either version 2
8 * of the License, or any later version.
9 *
10 * For the full copyright and license information, please read the
11 * LICENSE.txt file that was distributed with this source code.
12 *
13 * The TYPO3 project - inspiring people to share!
14 */
15
16namespace TYPO3\CMS\Extbase\Reflection;
17
18use TYPO3\CMS\Core\Cache\CacheManager;
19use TYPO3\CMS\Core\Cache\Exception\NoSuchCacheException;
20use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
21use TYPO3\CMS\Core\Core\Environment;
22use TYPO3\CMS\Core\Information\Typo3Version;
23use TYPO3\CMS\Core\SingletonInterface;
24use TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException;
25
26/**
27 * Reflection service for acquiring reflection based information.
28 * Originally based on the TYPO3.Flow reflection service.
29 */
30class ReflectionService implements SingletonInterface
31{
32    /**
33     * @var string
34     */
35    private static $cacheEntryIdentifier;
36
37    /**
38     * @var FrontendInterface
39     */
40    protected $dataCache;
41
42    /**
43     * Indicates whether the Reflection cache needs to be updated.
44     *
45     * This flag needs to be set as soon as new Reflection information was
46     * created.
47     *
48     * @var bool
49     */
50    protected $dataCacheNeedsUpdate = false;
51
52    /**
53     * Local cache for Class schemata
54     *
55     * @var array
56     */
57    protected $classSchemata = [];
58
59    /**
60     * @var bool
61     */
62    private $cachingEnabled = false;
63
64    /**
65     * If not $cacheManager is injected, the reflection service does not
66     * cache any data, useful for testing this service in unit tests.
67     *
68     * @param CacheManager $cacheManager
69     */
70    public function __construct(CacheManager $cacheManager = null)
71    {
72        if ($cacheManager instanceof CacheManager) {
73            try {
74                $this->dataCache = $cacheManager->getCache('extbase');
75                $this->cachingEnabled = true;
76            } catch (NoSuchCacheException $ignoredException) {
77                $this->cachingEnabled = false;
78            }
79
80            if ($this->cachingEnabled) {
81                static::$cacheEntryIdentifier = 'ClassSchemata_' . sha1((string)(new Typo3Version()) . Environment::getProjectPath());
82                if (($classSchemata = $this->dataCache->get(static::$cacheEntryIdentifier)) !== false) {
83                    $this->classSchemata = $classSchemata;
84                }
85            }
86        }
87    }
88
89    public function __destruct()
90    {
91        if ($this->dataCacheNeedsUpdate && $this->cachingEnabled) {
92            $this->dataCache->set(static::$cacheEntryIdentifier, $this->classSchemata);
93        }
94    }
95
96    /**
97     * Returns the class schema for the given class
98     *
99     * @param mixed $classNameOrObject The class name or an object
100     * @return ClassSchema
101     * @throws \TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
102     */
103    public function getClassSchema($classNameOrObject): ClassSchema
104    {
105        $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
106        if (isset($this->classSchemata[$className])) {
107            return $this->classSchemata[$className];
108        }
109
110        return $this->buildClassSchema($className);
111    }
112
113    /**
114     * Builds class schemata from classes annotated as entities or value objects
115     *
116     * @param string $className
117     * @throws Exception\UnknownClassException
118     * @return ClassSchema The class schema
119     */
120    protected function buildClassSchema($className): ClassSchema
121    {
122        try {
123            $classSchema = new ClassSchema($className);
124        } catch (\ReflectionException $e) {
125            throw new UnknownClassException($e->getMessage() . '. Reflection failed.', 1278450972, $e);
126        }
127        $this->classSchemata[$className] = $classSchema;
128        $this->dataCacheNeedsUpdate = true;
129        return $classSchema;
130    }
131
132    /**
133     * @internal
134     */
135    public function __sleep(): array
136    {
137        return [];
138    }
139
140    /**
141     * @internal
142     */
143    public function __wakeup(): void
144    {
145        $this->dataCache = null;
146        $this->dataCacheNeedsUpdate = false;
147        $this->classSchemata = [];
148        $this->cachingEnabled = false;
149    }
150}
151