[!!!][TASK] Replace ClassInfo with ClassSchema
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / Reflection / ReflectionService.php
1 <?php
2 namespace TYPO3\CMS\Extbase\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\Cache\CacheManager;
18 use TYPO3\CMS\Core\SingletonInterface;
19
20 /**
21 * Reflection service for acquiring reflection based information.
22 * Originally based on the TYPO3.Flow reflection service.
23 *
24 * @api
25 */
26 class ReflectionService implements SingletonInterface
27 {
28 const CACHE_IDENTIFIER = 'extbase_reflection';
29 const CACHE_ENTRY_IDENTIFIER = 'ClassSchematas';
30
31 /**
32 * @var \TYPO3\CMS\Core\Cache\Frontend\VariableFrontend
33 */
34 protected $dataCache;
35
36 /**
37 * Indicates whether the Reflection cache needs to be updated.
38 *
39 * This flag needs to be set as soon as new Reflection information was
40 * created.
41 *
42 * @var bool
43 */
44 protected $dataCacheNeedsUpdate = false;
45
46 /**
47 * Local cache for Class schemata
48 *
49 * @var array
50 */
51 protected $classSchemata = [];
52
53 /**
54 * @var bool
55 */
56 private $cachingEnabled = false;
57
58 /**
59 * If not $cacheManager is injected, the reflection service does not
60 * cache any data, useful for testing this service in unit tests.
61 *
62 * @param CacheManager $cacheManager
63 */
64 public function __construct(\TYPO3\CMS\Core\Cache\CacheManager $cacheManager = null)
65 {
66 if ($cacheManager instanceof CacheManager && $cacheManager->hasCache(static::CACHE_IDENTIFIER)) {
67 $this->cachingEnabled = true;
68 $this->dataCache = $cacheManager->getCache(static::CACHE_IDENTIFIER);
69
70 if (($classSchemata = $this->dataCache->has(static::CACHE_ENTRY_IDENTIFIER)) !== false) {
71 $this->classSchemata = $this->dataCache->get(static::CACHE_ENTRY_IDENTIFIER);
72 }
73 }
74 }
75
76 public function __destruct()
77 {
78 if ($this->dataCacheNeedsUpdate && $this->cachingEnabled) {
79 $this->dataCache->set(static::CACHE_ENTRY_IDENTIFIER, $this->classSchemata);
80 }
81 }
82
83 /**
84 * Returns all tags and their values the specified class is tagged with
85 *
86 * @param string $className Name of the class
87 * @return array An array of tags and their values or an empty array if no tags were found
88 */
89 public function getClassTagsValues($className): array
90 {
91 return $this->getClassSchema($className)->getTags();
92 }
93
94 /**
95 * Returns the values of the specified class tag
96 *
97 * @param string $className Name of the class containing the property
98 * @param string $tag Tag to return the values of
99 * @return array An array of values or an empty array if the tag was not found
100 */
101 public function getClassTagValues($className, $tag): array
102 {
103 return $this->getClassSchema($className)->getTags()[$tag];
104 }
105
106 /**
107 * Returns the names of all properties of the specified class
108 *
109 * @param string $className Name of the class to return the property names of
110 * @return array An array of property names or an empty array if none exist
111 */
112 public function getClassPropertyNames($className): array
113 {
114 return array_keys($this->getClassSchema($className)->getProperties());
115 }
116
117 /**
118 * Returns the class schema for the given class
119 *
120 * @param mixed $classNameOrObject The class name or an object
121 * @return ClassSchema
122 * @throws \TYPO3\CMS\Extbase\Reflection\Exception\UnknownClassException
123 * @throws \ReflectionException
124 */
125 public function getClassSchema($classNameOrObject): ClassSchema
126 {
127 $className = is_object($classNameOrObject) ? get_class($classNameOrObject) : $classNameOrObject;
128 if (isset($this->classSchemata[$className])) {
129 return $this->classSchemata[$className];
130 }
131
132 return $this->buildClassSchema($className);
133 }
134
135 /**
136 * Wrapper for method_exists() which tells if the given method exists.
137 *
138 * @param string $className Name of the class containing the method
139 * @param string $methodName Name of the method
140 * @return bool
141 */
142 public function hasMethod($className, $methodName): bool
143 {
144 return $this->getClassSchema($className)->hasMethod($methodName);
145 }
146
147 /**
148 * Returns all tags and their values the specified method is tagged with
149 *
150 * @param string $className Name of the class containing the method
151 * @param string $methodName Name of the method to return the tags and values of
152 * @return array An array of tags and their values or an empty array of no tags were found
153 */
154 public function getMethodTagsValues($className, $methodName): array
155 {
156 return $this->getClassSchema($className)->getMethod($methodName)['tags'];
157 }
158
159 /**
160 * Returns an array of parameters of the given method. Each entry contains
161 * additional information about the parameter position, type hint etc.
162 *
163 * @param string $className Name of the class containing the method
164 * @param string $methodName Name of the method to return parameter information of
165 * @return array An array of parameter names and additional information or an empty array of no parameters were found
166 */
167 public function getMethodParameters($className, $methodName): array
168 {
169 return $this->getClassSchema($className)->getMethod($methodName)['params'];
170 }
171
172 /**
173 * Returns all tags and their values the specified class property is tagged with
174 *
175 * @param string $className Name of the class containing the property
176 * @param string $propertyName Name of the property to return the tags and values of
177 * @return array An array of tags and their values or an empty array of no tags were found
178 */
179 public function getPropertyTagsValues($className, $propertyName): array
180 {
181 return $this->getClassSchema($className)->getProperty($propertyName)['tags'];
182 }
183
184 /**
185 * Returns the values of the specified class property tag
186 *
187 * @param string $className Name of the class containing the property
188 * @param string $propertyName Name of the tagged property
189 * @param string $tag Tag to return the values of
190 * @return array An array of values or an empty array if the tag was not found
191 */
192 public function getPropertyTagValues($className, $propertyName, $tag): array
193 {
194 if (!$this->isPropertyTaggedWith($className, $propertyName, $tag)) {
195 return [];
196 }
197
198 return $this->getClassSchema($className)->getProperty($propertyName)['tags'][$tag];
199 }
200
201 /**
202 * Tells if the specified class is tagged with the given tag
203 *
204 * @param string $className Name of the class
205 * @param string $tag Tag to check for
206 * @return bool TRUE if the class is tagged with $tag, otherwise FALSE
207 */
208 public function isClassTaggedWith($className, $tag): bool
209 {
210 foreach (array_keys($this->getClassSchema($className)->getTags()) as $tagName) {
211 if ($tagName === $tag) {
212 return true;
213 }
214 }
215
216 return false;
217 }
218
219 /**
220 * Tells if the specified class property is tagged with the given tag
221 *
222 * @param string $className Name of the class
223 * @param string $propertyName Name of the property
224 * @param string $tag Tag to check for
225 * @return bool TRUE if the class property is tagged with $tag, otherwise FALSE
226 */
227 public function isPropertyTaggedWith($className, $propertyName, $tag): bool
228 {
229 try {
230 $classSchema = $this->getClassSchema($className);
231 } catch (\Exception $e) {
232 return false;
233 }
234
235 $property = $classSchema->getProperty($propertyName);
236
237 if (empty($property)) {
238 return false;
239 }
240
241 return isset($property['tags'][$tag]);
242 }
243
244 /**
245 * Builds class schemata from classes annotated as entities or value objects
246 *
247 * @param string $className
248 * @throws Exception\UnknownClassException
249 * @return ClassSchema The class schema
250 * @throws \ReflectionException
251 */
252 protected function buildClassSchema($className): ClassSchema
253 {
254 if (!class_exists($className)) {
255 throw new Exception\UnknownClassException('The classname "' . $className . '" was not found and thus can not be reflected.', 1278450972);
256 }
257
258 $classSchema = new ClassSchema($className);
259 $this->classSchemata[$className] = $classSchema;
260 $this->dataCacheNeedsUpdate = true;
261 return $classSchema;
262 }
263 }