PageRenderTime 36ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/Classes/TYPO3/FLOW3/Object/CompileTimeObjectManager.php

https://github.com/christianjul/FLOW3-Composer
PHP | 373 lines | 201 code | 41 blank | 131 comment | 30 complexity | ca321d04ba64011ae13544a3a4a5338d MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0
  1. <?php
  2. namespace TYPO3\FLOW3\Object;
  3. /* *
  4. * This script belongs to the FLOW3 framework. *
  5. * *
  6. * It is free software; you can redistribute it and/or modify it under *
  7. * the terms of the GNU Lesser General Public License, either version 3 *
  8. * of the License, or (at your option) any later version. *
  9. * *
  10. * The TYPO3 project - inspiring people to share! *
  11. * */
  12. use TYPO3\FLOW3\Object\Configuration\Configuration;
  13. use TYPO3\FLOW3\Object\Configuration\ConfigurationProperty as Property;
  14. use TYPO3\FLOW3\Reflection\ObjectAccess;
  15. use Doctrine\ORM\Mapping as ORM;
  16. use TYPO3\FLOW3\Annotations as FLOW3;
  17. /**
  18. * A specialized Object Manager which is able to do some basic dependency injection for
  19. * singleton scoped objects. This Object Manager is used during compile time when the proxy
  20. * class based DI mechanism is not yet available.
  21. *
  22. * @FLOW3\Scope("singleton")
  23. * @FLOW3\Proxy(false)
  24. */
  25. class CompileTimeObjectManager extends ObjectManager {
  26. /**
  27. * @var \TYPO3\FLOW3\Cache\Frontend\VariableFrontend
  28. */
  29. protected $configurationCache;
  30. /**
  31. * @var \TYPO3\FLOW3\Reflection\ReflectionService
  32. */
  33. protected $reflectionService;
  34. /**
  35. * @var \TYPO3\FLOW3\Configuration\ConfigurationManager
  36. */
  37. protected $configurationManager;
  38. /**
  39. * @var \TYPO3\FLOW3\Log\SystemLoggerInterface
  40. */
  41. protected $systemLogger;
  42. /**
  43. * @var array
  44. */
  45. protected $objectConfigurations;
  46. /**
  47. * A list of all class names known to the Object Manager
  48. *
  49. * @var array
  50. */
  51. protected $registeredClassNames = array();
  52. /**
  53. * @var array
  54. */
  55. protected $objectNameBuildStack = array();
  56. /**
  57. * @var array
  58. */
  59. protected $cachedClassNamesByScope = array();
  60. /**
  61. * @param \TYPO3\FLOW3\Reflection\ReflectionService $reflectionService
  62. * @return void
  63. */
  64. public function injectReflectionService(\TYPO3\FLOW3\Reflection\ReflectionService $reflectionService) {
  65. $this->reflectionService = $reflectionService;
  66. }
  67. /**
  68. * @param \TYPO3\FLOW3\Configuration\ConfigurationManager $configurationManager
  69. * @return void
  70. */
  71. public function injectConfigurationManager(\TYPO3\FLOW3\Configuration\ConfigurationManager $configurationManager) {
  72. $this->configurationManager = $configurationManager;
  73. }
  74. /**
  75. * Injects the configuration cache of the Object Framework
  76. *
  77. * @param \TYPO3\FLOW3\Cache\Frontend\VariableFrontend $configurationCache
  78. * @return void
  79. */
  80. public function injectConfigurationCache(\TYPO3\FLOW3\Cache\Frontend\VariableFrontend $configurationCache) {
  81. $this->configurationCache = $configurationCache;
  82. }
  83. /**
  84. * @param \TYPO3\FLOW3\Log\SystemLoggerInterface $systemLogger
  85. * @return void
  86. */
  87. public function injectSystemLogger(\TYPO3\FLOW3\Log\SystemLoggerInterface $systemLogger) {
  88. $this->systemLogger = $systemLogger;
  89. }
  90. /**
  91. * Initializes the the object configurations and some other parts of this Object Manager.
  92. *
  93. * @param array $packages An array of active packages to consider
  94. * @return void
  95. */
  96. public function initialize(array $packages) {
  97. $this->registeredClassNames = $this->registerClassFiles($packages);
  98. $this->reflectionService->buildReflectionData($this->registeredClassNames);
  99. $rawCustomObjectConfigurations = $this->configurationManager->getConfiguration(\TYPO3\FLOW3\Configuration\ConfigurationManager::CONFIGURATION_TYPE_OBJECTS);
  100. $configurationBuilder = new \TYPO3\FLOW3\Object\Configuration\ConfigurationBuilder();
  101. $configurationBuilder->injectReflectionService($this->reflectionService);
  102. $configurationBuilder->injectSystemLogger($this->systemLogger);
  103. $this->objectConfigurations = $configurationBuilder->buildObjectConfigurations($this->registeredClassNames, $rawCustomObjectConfigurations);
  104. $this->setObjects($this->buildObjectsArray());
  105. }
  106. /**
  107. * Sets the instance of the given object
  108. *
  109. * In the Compile Time Object Manager it is even allowed to set instances of not-yet-known objects as long as the Object
  110. * Manager is not initialized, because some few parts need an object registry even before the Object Manager is fully
  111. * functional.
  112. *
  113. * @param string $objectName The object name
  114. * @param object $instance A prebuilt instance
  115. * @return void
  116. */
  117. public function setInstance($objectName, $instance) {
  118. if ($this->registeredClassNames === array()) {
  119. $this->objects[$objectName]['i'] = $instance;
  120. } else {
  121. parent::setInstance($objectName, $instance);
  122. }
  123. }
  124. /**
  125. * Returns a list of all class names, grouped by package key, which were registered by registerClassFiles()
  126. *
  127. * @return array
  128. */
  129. public function getRegisteredClassNames() {
  130. return $this->registeredClassNames;
  131. }
  132. /**
  133. * Returns a list of class names, which are configured with the given scope
  134. *
  135. * @param integer $scope One of the ObjectConfiguration::SCOPE_ constants
  136. * @return array An array of class names configured with the given scope
  137. */
  138. public function getClassNamesByScope($scope) {
  139. if (!isset($this->cachedClassNamesByScope[$scope])) {
  140. foreach ($this->objects as $objectName => $information) {
  141. if ($information['s'] === $scope) {
  142. if (isset($information['c'])) {
  143. $this->cachedClassNamesByScope[$scope][] = $information['c'];
  144. } else {
  145. $this->cachedClassNamesByScope[$scope][] = $objectName;
  146. }
  147. }
  148. }
  149. }
  150. return $this->cachedClassNamesByScope[$scope];
  151. }
  152. /**
  153. * Traverses through all class files of the active packages and registers collects the class names as
  154. * "all available class names". If the respective FLOW3 settings say so, also function test classes
  155. * are registered.
  156. *
  157. * For performance reasons this function ignores classes whose name ends with "Exception".
  158. *
  159. * @param array $packages A list of packages to consider
  160. * @return array A list of class names which were discovered in the given packages
  161. */
  162. protected function registerClassFiles(array $packages) {
  163. $availableClassNames = array('' => array('DateTime'));
  164. foreach ($packages as $packageKey => $package) {
  165. if ($package->isObjectManagementEnabled()) {
  166. $classFiles = $package->getClassFiles();
  167. if (count($classFiles) === 0) {
  168. throw new \TYPO3\FLOW3\Package\Exception\CorruptPackageException('Package "' . $packageKey . '" did not contain any files', 1338987564);
  169. }
  170. if ($this->allSettings['TYPO3']['FLOW3']['object']['registerFunctionalTestClasses'] === TRUE) {
  171. $classFiles = array_merge($classFiles, $package->getFunctionalTestsClassFiles());
  172. }
  173. foreach (array_keys($classFiles) as $fullClassName) {
  174. if (substr($fullClassName, -9, 9) !== 'Exception') {
  175. $availableClassNames[$packageKey][] = $fullClassName;
  176. }
  177. }
  178. $availableClassNames[$packageKey] = array_unique($availableClassNames[$packageKey]);
  179. }
  180. }
  181. return $this->filterClassNamesFromConfiguration($availableClassNames);
  182. }
  183. /**
  184. * Given an array of class names by package key this filters out all classes that
  185. * have been configured to be excluded from object management.
  186. *
  187. * @param array $classNames 2-level array - key of first level is package key, value of second level is classname (FQN)
  188. * @return array The input array with all configured to be excluded from object management filtered out
  189. * @throws \TYPO3\FLOW3\Configuration\Exception\InvalidConfigurationTypeException
  190. * @throws \TYPO3\FLOW3\Configuration\Exception\NoSuchOptionException
  191. */
  192. protected function filterClassNamesFromConfiguration(array $classNames) {
  193. if (isset($this->allSettings['TYPO3']['FLOW3']['object']) && isset($this->allSettings['TYPO3']['FLOW3']['object']['excludeClasses'])) {
  194. if (!is_array($this->allSettings['TYPO3']['FLOW3']['object']['excludeClasses'])) {
  195. throw new \TYPO3\FLOW3\Configuration\Exception\InvalidConfigurationTypeException('The setting "TYPO3.FLOW3.object.excludeClasses" is invalid (it must be an array).');
  196. }
  197. foreach ($this->allSettings['TYPO3']['FLOW3']['object']['excludeClasses'] as $packageKey => $filterExpressions) {
  198. if (!array_key_exists($packageKey, $classNames)) {
  199. throw new \TYPO3\FLOW3\Configuration\Exception\NoSuchOptionException('The package "' . $packageKey . '" specified in the setting "TYPO3.FLOW3.object.excludeClasses" does not exist or is not active.');
  200. }
  201. if (!is_array($filterExpressions)) {
  202. throw new \TYPO3\FLOW3\Configuration\Exception\InvalidConfigurationTypeException('The value given for setting "TYPO3.FLOW3.object.excludeClasses.\'' . $packageKey . '\'" is invalid (it must be an array).');
  203. }
  204. foreach ($filterExpressions as $filterExpression) {
  205. $classNames[$packageKey] = array_filter(
  206. $classNames[$packageKey],
  207. function ($className) use ($filterExpression) {
  208. $match = preg_match('/' . $filterExpression . '/', $className);
  209. return $match !== 1;
  210. }
  211. );
  212. }
  213. }
  214. }
  215. return $classNames;
  216. }
  217. /**
  218. * Builds the objects array which contains information about the registered objects,
  219. * their scope, class, built method etc.
  220. *
  221. * @return array
  222. */
  223. protected function buildObjectsArray() {
  224. $objects = array();
  225. foreach ($this->objectConfigurations as $objectConfiguration) {
  226. $objectName = $objectConfiguration->getObjectName();
  227. $objects[$objectName] = array(
  228. 'l' => strtolower($objectName),
  229. 's' => $objectConfiguration->getScope(),
  230. 'p' => $objectConfiguration->getPackageKey()
  231. );
  232. if ($objectConfiguration->getClassName() !== $objectName) {
  233. $objects[$objectName]['c'] = $objectConfiguration->getClassName();
  234. }
  235. if ($objectConfiguration->getFactoryObjectName() !== '') {
  236. $objects[$objectName]['f'] = array(
  237. $objectConfiguration->getFactoryObjectName(),
  238. $objectConfiguration->getFactoryMethodName()
  239. );
  240. $objects[$objectName]['fa'] = array();
  241. $factoryMethodArguments = $objectConfiguration->getArguments();
  242. if (count($factoryMethodArguments) > 0) {
  243. foreach ($factoryMethodArguments as $index => $argument) {
  244. $objects[$objectName]['fa'][$index] = array(
  245. 't' => $argument->getType(),
  246. 'v' => $argument->getValue()
  247. );
  248. }
  249. }
  250. }
  251. }
  252. $this->configurationCache->set('objects', $objects);
  253. return $objects;
  254. }
  255. /**
  256. * Returns object configurations which were previously built by the ConfigurationBuilder.
  257. *
  258. * @return array
  259. */
  260. public function getObjectConfigurations() {
  261. return $this->objectConfigurations;
  262. }
  263. /**
  264. * Returns a fresh or existing instance of the object specified by $objectName.
  265. *
  266. * This specialized get() method is able to do setter injection for properties
  267. * defined in the object configuration of the specified object.
  268. *
  269. * @param string $objectName The name of the object to return an instance of
  270. * @return object The object instance
  271. * @throws \TYPO3\FLOW3\Object\Exception\CannotBuildObjectException
  272. * @throws \TYPO3\FLOW3\Object\Exception\UnresolvedDependenciesException
  273. * @throws \TYPO3\FLOW3\Object\Exception\UnknownObjectException
  274. */
  275. public function get($objectName) {
  276. if (isset($this->objects[$objectName]['i'])) {
  277. return $this->objects[$objectName]['i'];
  278. }
  279. if (isset($this->objectConfigurations[$objectName]) && count($this->objectConfigurations[$objectName]->getArguments()) > 0) {
  280. throw new Exception\CannotBuildObjectException('Cannot build object "' . $objectName . '" because constructor injection is not available in the compile time Object Manager. Refactor your code to use setter injection instead. Configuration source: ' . $this->objectConfigurations[$objectName]->getConfigurationSourceHint() . '. Build stack: ' . implode(', ', $this->objectNameBuildStack), 1297090026);
  281. }
  282. if (!isset($this->objects[$objectName])) {
  283. throw new Exception\UnknownObjectException('Cannot build object "' . $objectName . '" because it is unknown to the compile time Object Manager.', 1301477694);
  284. }
  285. if ($this->objects[$objectName]['s'] !== Configuration::SCOPE_SINGLETON) {
  286. throw new Exception\CannotBuildObjectException('Cannot build object "' . $objectName . '" because the get() method in the compile time Object Manager only supports singletons.', 1297090027);
  287. }
  288. $this->objectNameBuildStack[] = $objectName;
  289. $object = parent::get($objectName);
  290. foreach ($this->objectConfigurations[$objectName]->getProperties() as $propertyName => $property) {
  291. if ($property->getAutowiring() !== Configuration::AUTOWIRING_MODE_ON) {
  292. continue;
  293. }
  294. switch ($property->getType()) {
  295. case Property::PROPERTY_TYPES_STRAIGHTVALUE:
  296. $value = $property->getValue();
  297. break;
  298. case Property::PROPERTY_TYPES_SETTING:
  299. $value = \TYPO3\FLOW3\Utility\Arrays::getValueByPath($this->allSettings, explode('.', $property->getValue()));
  300. break;
  301. case Property::PROPERTY_TYPES_OBJECT:
  302. $propertyObjectName = $property->getValue();
  303. if (!is_string($propertyObjectName)) {
  304. throw new Exception\CannotBuildObjectException('The object definition of "' . $objectName . '::' . $propertyName . '" is too complex for the compile time Object Manager. You can only use plain object names, not factories and the like. Check configuration in ' . $this->objectConfigurations[$objectName]->getConfigurationSourceHint() . ' and objects which depend on ' . $objectName. '.', 1297099659);
  305. }
  306. $value = $this->get($propertyObjectName);
  307. break;
  308. default:
  309. throw new Exception\CannotBuildObjectException('Invalid property type.', 1297090029);
  310. break;
  311. }
  312. if (method_exists($object, $setterMethodName = 'inject' . ucfirst($propertyName))) {
  313. $object->$setterMethodName($value);
  314. } elseif (method_exists($object, $setterMethodName = 'set' . ucfirst($propertyName))) {
  315. $object->$setterMethodName($value);
  316. } else {
  317. throw new Exception\UnresolvedDependenciesException('Could not inject configured property "' . $propertyName . '" into "' . $objectName . '" because no injection method exists, but for compile time use this is required. Configuration source: ' . $this->objectConfigurations[$objectName]->getConfigurationSourceHint() . '.', 1297110953);
  318. }
  319. }
  320. $initializationLifecycleMethodName = $this->objectConfigurations[$objectName]->getLifecycleInitializationMethodName();
  321. if (method_exists($object, $initializationLifecycleMethodName)) {
  322. $object->$initializationLifecycleMethodName();
  323. }
  324. $shutdownLifecycleMethodName = $this->objectConfigurations[$objectName]->getLifecycleShutdownMethodName();
  325. if (method_exists($object, $shutdownLifecycleMethodName)) {
  326. $this->shutdownObjects[$object] = $shutdownLifecycleMethodName;
  327. }
  328. array_pop($this->objectNameBuildStack);
  329. return $object;
  330. }
  331. }
  332. ?>