Generator.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. namespace Magento\Framework\Code;
  7. use Magento\Framework\Code\Generator\DefinedClasses;
  8. use Magento\Framework\Code\Generator\EntityAbstract;
  9. use Magento\Framework\Code\Generator\Io;
  10. use Magento\Framework\ObjectManagerInterface;
  11. use Magento\Framework\Phrase;
  12. use Magento\Framework\Filesystem\Driver\File;
  13. use Psr\Log\LoggerInterface;
  14. class Generator
  15. {
  16. const GENERATION_SUCCESS = 'success';
  17. const GENERATION_ERROR = 'error';
  18. const GENERATION_SKIP = 'skip';
  19. /**
  20. * @var Io
  21. */
  22. protected $_ioObject;
  23. /**
  24. * @var array
  25. */
  26. protected $_generatedEntities;
  27. /**
  28. * @var DefinedClasses
  29. */
  30. protected $definedClasses;
  31. /**
  32. * @var ObjectManagerInterface
  33. */
  34. protected $objectManager;
  35. /**
  36. * Logger instance
  37. *
  38. * @var LoggerInterface
  39. */
  40. private $logger;
  41. /**
  42. * @param Generator\Io $ioObject
  43. * @param array $generatedEntities
  44. * @param DefinedClasses $definedClasses
  45. * @param LoggerInterface|null $logger
  46. */
  47. public function __construct(
  48. Io $ioObject = null,
  49. array $generatedEntities = [],
  50. DefinedClasses $definedClasses = null,
  51. LoggerInterface $logger = null
  52. ) {
  53. $this->_ioObject = $ioObject ?: new Io(new File());
  54. $this->definedClasses = $definedClasses ?: new DefinedClasses();
  55. $this->_generatedEntities = $generatedEntities;
  56. $this->logger = $logger;
  57. }
  58. /**
  59. * Get generated entities
  60. *
  61. * @return array
  62. */
  63. public function getGeneratedEntities()
  64. {
  65. return $this->_generatedEntities;
  66. }
  67. /**
  68. * Set entity-to-generator map
  69. *
  70. * @param array $generatedEntities
  71. * @return $this
  72. */
  73. public function setGeneratedEntities($generatedEntities)
  74. {
  75. $this->_generatedEntities = $generatedEntities;
  76. return $this;
  77. }
  78. /**
  79. * Generate Class
  80. *
  81. * @param string $className
  82. * @return string | void
  83. * @throws \RuntimeException
  84. * @throws \InvalidArgumentException
  85. */
  86. public function generateClass($className)
  87. {
  88. $resultEntityType = null;
  89. $sourceClassName = null;
  90. foreach ($this->_generatedEntities as $entityType => $generatorClass) {
  91. $entitySuffix = ucfirst($entityType);
  92. // If $className string ends with $entitySuffix substring
  93. if (strrpos($className, $entitySuffix) === strlen($className) - strlen($entitySuffix)) {
  94. $resultEntityType = $entityType;
  95. $sourceClassName = rtrim(
  96. substr($className, 0, -1 * strlen($entitySuffix)),
  97. '\\'
  98. );
  99. break;
  100. }
  101. }
  102. if ($skipReason = $this->shouldSkipGeneration($resultEntityType, $sourceClassName, $className)) {
  103. return $skipReason;
  104. }
  105. $generatorClass = $this->_generatedEntities[$resultEntityType];
  106. /** @var EntityAbstract $generator */
  107. $generator = $this->createGeneratorInstance($generatorClass, $sourceClassName, $className);
  108. if ($generator !== null) {
  109. $this->tryToLoadSourceClass($className, $generator);
  110. if (!($file = $generator->generate())) {
  111. /** @var $logger LoggerInterface */
  112. $errors = $generator->getErrors();
  113. $errors[] = 'Class ' . $className . ' generation error: The requested class did not generate properly, '
  114. . 'because the \'generated\' directory permission is read-only. '
  115. . 'If --- after running the \'bin/magento setup:di:compile\' CLI command when the \'generated\' '
  116. . 'directory permission is set to write --- the requested class did not generate properly, then '
  117. . 'you must add the generated class object to the signature of the related construct method, only.';
  118. $message = implode(PHP_EOL, $errors);
  119. $this->getLogger()->critical($message);
  120. throw new \RuntimeException($message);
  121. }
  122. if (!$this->definedClasses->isClassLoadableFromMemory($className)) {
  123. $this->_ioObject->includeFile($file);
  124. }
  125. return self::GENERATION_SUCCESS;
  126. }
  127. }
  128. /**
  129. * Retrieve logger
  130. *
  131. * @return LoggerInterface
  132. */
  133. private function getLogger()
  134. {
  135. if (!$this->logger) {
  136. $this->logger = $this->getObjectManager()->get(LoggerInterface::class);
  137. }
  138. return $this->logger;
  139. }
  140. /**
  141. * Create entity generator
  142. *
  143. * @param string $generatorClass
  144. * @param string $entityName
  145. * @param string $className
  146. * @return EntityAbstract
  147. */
  148. protected function createGeneratorInstance($generatorClass, $entityName, $className)
  149. {
  150. return $this->getObjectManager()->create(
  151. $generatorClass,
  152. ['sourceClassName' => $entityName, 'resultClassName' => $className, 'ioObject' => $this->_ioObject]
  153. );
  154. }
  155. /**
  156. * Set object manager instance.
  157. *
  158. * @param ObjectManagerInterface $objectManager
  159. * @return $this
  160. */
  161. public function setObjectManager(ObjectManagerInterface $objectManager)
  162. {
  163. $this->objectManager = $objectManager;
  164. return $this;
  165. }
  166. /**
  167. * Get object manager instance.
  168. *
  169. * @return ObjectManagerInterface
  170. */
  171. public function getObjectManager()
  172. {
  173. if (!($this->objectManager instanceof ObjectManagerInterface)) {
  174. throw new \LogicException(
  175. "Object manager was expected to be set using setObjectManger() "
  176. . "before getObjectManager() invocation."
  177. );
  178. }
  179. return $this->objectManager;
  180. }
  181. /**
  182. * Try to load/generate source class to check if it is valid or not.
  183. *
  184. * @param string $className
  185. * @param EntityAbstract $generator
  186. * @return void
  187. * @throws \RuntimeException
  188. */
  189. protected function tryToLoadSourceClass($className, $generator)
  190. {
  191. $sourceClassName = $generator->getSourceClassName();
  192. if (!$this->definedClasses->isClassLoadable($sourceClassName)) {
  193. if ($this->generateClass($sourceClassName) !== self::GENERATION_SUCCESS) {
  194. $phrase = new Phrase(
  195. 'Source class "%1" for "%2" generation does not exist.',
  196. [$sourceClassName, $className]
  197. );
  198. throw new \RuntimeException($phrase->__toString());
  199. }
  200. }
  201. }
  202. /**
  203. * Perform validation surrounding source and result classes and entity type
  204. *
  205. * @param string $resultEntityType
  206. * @param string $sourceClassName
  207. * @param string $resultClass
  208. * @return string|bool
  209. */
  210. protected function shouldSkipGeneration($resultEntityType, $sourceClassName, $resultClass)
  211. {
  212. if (!$resultEntityType || !$sourceClassName) {
  213. return self::GENERATION_ERROR;
  214. } elseif ($this->definedClasses->isClassLoadableFromDisk($resultClass)) {
  215. $generatedFileName = $this->_ioObject->generateResultFileName($resultClass);
  216. /**
  217. * Must handle two edge cases: a competing process has generated the class and written it to disc already,
  218. * or the class exists in committed code, despite matching pattern to be generated.
  219. */
  220. if ($this->_ioObject->fileExists($generatedFileName)
  221. && !$this->definedClasses->isClassLoadableFromMemory($resultClass)
  222. ) {
  223. $this->_ioObject->includeFile($generatedFileName);
  224. }
  225. return self::GENERATION_SKIP;
  226. } elseif (!isset($this->_generatedEntities[$resultEntityType])) {
  227. throw new \InvalidArgumentException('Unknown generation entity.');
  228. }
  229. return false;
  230. }
  231. }