PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/nette/di/src/DI/Compiler.php

https://gitlab.com/kubinos/writeoff
PHP | 428 lines | 321 code | 53 blank | 54 comment | 14 complexity | 2352e1526bea886560173f28dc249c3c MD5 | raw file
  1. <?php
  2. /**
  3. * This file is part of the Nette Framework (https://nette.org)
  4. * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
  5. */
  6. namespace Nette\DI;
  7. use Nette;
  8. use Nette\Utils\Validators;
  9. /**
  10. * DI container compiler.
  11. */
  12. class Compiler extends Nette\Object
  13. {
  14. /** @var CompilerExtension[] */
  15. private $extensions = array();
  16. /** @var ContainerBuilder */
  17. private $builder;
  18. /** @var array */
  19. private $config = array();
  20. /** @var string[] of file names */
  21. private $dependencies = array();
  22. /** @var array reserved section names */
  23. private static $reserved = array('services' => 1, 'parameters' => 1);
  24. public function __construct(ContainerBuilder $builder = NULL)
  25. {
  26. $this->builder = $builder ?: new ContainerBuilder;
  27. }
  28. /**
  29. * Add custom configurator extension.
  30. * @return self
  31. */
  32. public function addExtension($name, CompilerExtension $extension)
  33. {
  34. if (isset($this->extensions[$name]) || isset(self::$reserved[$name])) {
  35. throw new Nette\InvalidArgumentException("Name '$name' is already used or reserved.");
  36. }
  37. $this->extensions[$name] = $extension->setCompiler($this, $name);
  38. return $this;
  39. }
  40. /**
  41. * @return array
  42. */
  43. public function getExtensions($type = NULL)
  44. {
  45. return $type
  46. ? array_filter($this->extensions, function ($item) use ($type) { return $item instanceof $type; })
  47. : $this->extensions;
  48. }
  49. /**
  50. * @return ContainerBuilder
  51. */
  52. public function getContainerBuilder()
  53. {
  54. return $this->builder;
  55. }
  56. /**
  57. * Adds new configuration.
  58. * @return self
  59. */
  60. public function addConfig(array $config)
  61. {
  62. $this->config = Config\Helpers::merge($config, $this->config);
  63. return $this;
  64. }
  65. /**
  66. * Adds new configuration from file.
  67. * @return self
  68. */
  69. public function loadConfig($file)
  70. {
  71. $loader = new Config\Loader;
  72. $this->addConfig($loader->load($file));
  73. $this->addDependencies($loader->getDependencies());
  74. return $this;
  75. }
  76. /**
  77. * Returns configuration.
  78. * @return array
  79. */
  80. public function getConfig()
  81. {
  82. return $this->config;
  83. }
  84. /**
  85. * Adds a files to the list of dependencies.
  86. * @return self
  87. */
  88. public function addDependencies(array $files)
  89. {
  90. $this->dependencies = array_merge($this->dependencies, $files);
  91. return $this;
  92. }
  93. /**
  94. * Returns the unique list of dependent files.
  95. * @return array
  96. */
  97. public function getDependencies()
  98. {
  99. return array_values(array_unique(array_filter($this->dependencies)));
  100. }
  101. /**
  102. * @return Nette\PhpGenerator\ClassType[]|string
  103. */
  104. public function compile(array $config = NULL, $className = NULL, $parentName = NULL)
  105. {
  106. $this->config = $config ?: $this->config;
  107. $this->processParameters();
  108. $this->processExtensions();
  109. $this->processServices();
  110. $classes = $this->generateCode($className, $parentName);
  111. return func_num_args()
  112. ? implode("\n\n\n", $classes) // back compatiblity
  113. : $classes;
  114. }
  115. /** @internal */
  116. public function processParameters()
  117. {
  118. if (isset($this->config['parameters'])) {
  119. $this->builder->parameters = Helpers::expand($this->config['parameters'], $this->config['parameters'], TRUE);
  120. }
  121. }
  122. /** @internal */
  123. public function processExtensions()
  124. {
  125. $last = $this->getExtensions('Nette\DI\Extensions\InjectExtension');
  126. $this->extensions = array_merge(array_diff_key($this->extensions, $last), $last);
  127. $this->config = Helpers::expand(array_diff_key($this->config, self::$reserved), $this->builder->parameters)
  128. + array_intersect_key($this->config, self::$reserved);
  129. foreach ($first = $this->getExtensions('Nette\DI\Extensions\ExtensionsExtension') as $name => $extension) {
  130. $extension->setConfig(isset($this->config[$name]) ? $this->config[$name] : array());
  131. $extension->loadConfiguration();
  132. }
  133. $extensions = array_diff_key($this->extensions, $first);
  134. foreach (array_intersect_key($extensions, $this->config) as $name => $extension) {
  135. if (isset($this->config[$name]['services'])) {
  136. trigger_error("Support for inner section 'services' inside extension was removed (used in '$name').", E_USER_DEPRECATED);
  137. }
  138. $extension->setConfig($this->config[$name] ?: array());
  139. }
  140. foreach ($extensions as $extension) {
  141. $extension->loadConfiguration();
  142. }
  143. if ($extra = array_diff_key($this->extensions, $extensions, $first)) {
  144. $extra = implode("', '", array_keys($extra));
  145. throw new Nette\DeprecatedException("Extensions '$extra' were added while container was being compiled.");
  146. } elseif ($extra = key(array_diff_key($this->config, self::$reserved, $this->extensions))) {
  147. $hint = Nette\Utils\ObjectMixin::getSuggestion(array_keys(self::$reserved + $this->extensions), $extra);
  148. throw new Nette\InvalidStateException(
  149. "Found section '$extra' in configuration, but corresponding extension is missing"
  150. . ($hint ? ", did you mean '$hint'?" : '.')
  151. );
  152. }
  153. }
  154. /** @internal */
  155. public function processServices()
  156. {
  157. $this->parseServices($this->builder, $this->config);
  158. }
  159. /** @internal */
  160. public function generateCode($className, $parentName = NULL)
  161. {
  162. $this->builder->prepareClassList();
  163. foreach ($this->extensions as $extension) {
  164. $extension->beforeCompile();
  165. $rc = new \ReflectionClass($extension);
  166. $this->dependencies[] = $rc->getFileName();
  167. }
  168. $classes = $this->builder->generateClasses($className, $parentName);
  169. $classes[0]->addMethod('initialize');
  170. $this->addDependencies($this->builder->getDependencies());
  171. foreach ($this->extensions as $extension) {
  172. $extension->afterCompile($classes[0]);
  173. }
  174. return $classes;
  175. }
  176. /********************* tools ****************d*g**/
  177. /**
  178. * Parses section 'services' from (unexpanded) configuration file.
  179. * @return void
  180. */
  181. public static function parseServices(ContainerBuilder $builder, array $config, $namespace = NULL)
  182. {
  183. if (!empty($config['factories'])) {
  184. throw new Nette\DeprecatedException("Section 'factories' is deprecated, move definitions to section 'services' and append key 'autowired: no'.");
  185. }
  186. $services = isset($config['services']) ? $config['services'] : array();
  187. $depths = array();
  188. foreach ($services as $name => $def) {
  189. $path = array();
  190. while (Config\Helpers::isInheriting($def)) {
  191. $path[] = $def;
  192. $def = isset($services[$def[Config\Helpers::EXTENDS_KEY]]) ? $services[$def[Config\Helpers::EXTENDS_KEY]] : array();
  193. if (in_array($def, $path, TRUE)) {
  194. throw new ServiceCreationException("Circular reference detected for service '$name'.");
  195. }
  196. }
  197. $depths[$name] = count($path);
  198. }
  199. array_multisort($depths, $services);
  200. foreach ($services as $origName => $def) {
  201. if ((string) (int) $origName === (string) $origName) {
  202. $postfix = $def instanceof Statement && is_string($def->getEntity()) ? '.' . $def->getEntity() : (is_scalar($def) ? ".$def" : '');
  203. $name = (count($builder->getDefinitions()) + 1) . preg_replace('#\W+#', '_', $postfix);
  204. } else {
  205. $name = ($namespace ? $namespace . '.' : '') . strtr($origName, '\\', '_');
  206. }
  207. $params = $builder->parameters;
  208. if (is_array($def) && isset($def['parameters'])) {
  209. foreach ((array) $def['parameters'] as $k => $v) {
  210. $v = explode(' ', is_int($k) ? $v : $k);
  211. $params[end($v)] = $builder::literal('$' . end($v));
  212. }
  213. }
  214. $def = Helpers::expand($def, $params);
  215. if (($parent = Config\Helpers::takeParent($def)) && $parent !== $name) {
  216. $builder->removeDefinition($name);
  217. $definition = $builder->addDefinition(
  218. $name,
  219. $parent === Config\Helpers::OVERWRITE ? NULL : unserialize(serialize($builder->getDefinition($parent))) // deep clone
  220. );
  221. } elseif ($builder->hasDefinition($name)) {
  222. $definition = $builder->getDefinition($name);
  223. } else {
  224. $definition = $builder->addDefinition($name);
  225. }
  226. try {
  227. static::parseService($definition, $def);
  228. } catch (\Exception $e) {
  229. throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
  230. }
  231. if ($definition->getClass() === 'self' || ($definition->getFactory() && $definition->getFactory()->getEntity() === 'self')) {
  232. throw new Nette\DeprecatedException("Replace service definition '$origName: self' with '- $origName'.");
  233. }
  234. }
  235. }
  236. /**
  237. * Parses single service from configuration file.
  238. * @return void
  239. */
  240. public static function parseService(ServiceDefinition $definition, $config)
  241. {
  242. if ($config === NULL) {
  243. return;
  244. } elseif (is_string($config) && interface_exists($config)) {
  245. $config = array('class' => NULL, 'implement' => $config);
  246. } elseif ($config instanceof Statement && is_string($config->getEntity()) && interface_exists($config->getEntity())) {
  247. $config = array('class' => NULL, 'implement' => $config->getEntity(), 'factory' => array_shift($config->arguments));
  248. } elseif (!is_array($config) || isset($config[0], $config[1])) {
  249. $config = array('class' => NULL, 'create' => $config);
  250. }
  251. if (array_key_exists('factory', $config)) {
  252. $config['create'] = $config['factory'];
  253. unset($config['factory']);
  254. };
  255. $known = array('class', 'create', 'arguments', 'setup', 'autowired', 'dynamic', 'inject', 'parameters', 'implement', 'run', 'tags');
  256. if ($error = array_diff(array_keys($config), $known)) {
  257. throw new Nette\InvalidStateException(sprintf("Unknown or deprecated key '%s' in definition of service.", implode("', '", $error)));
  258. }
  259. $config = self::filterArguments($config);
  260. $arguments = array();
  261. if (array_key_exists('arguments', $config)) {
  262. Validators::assertField($config, 'arguments', 'array');
  263. $arguments = $config['arguments'];
  264. $definition->setArguments($arguments);
  265. }
  266. if (array_key_exists('class', $config) || array_key_exists('create', $config)) {
  267. $definition->setClass(NULL);
  268. $definition->setFactory(NULL);
  269. }
  270. if (array_key_exists('class', $config)) {
  271. Validators::assertField($config, 'class', 'string|Nette\DI\Statement|null');
  272. if (!$config['class'] instanceof Statement) {
  273. $definition->setClass($config['class']);
  274. }
  275. $definition->setFactory($config['class'], $arguments);
  276. }
  277. if (array_key_exists('create', $config)) {
  278. Validators::assertField($config, 'create', 'callable|Nette\DI\Statement|null');
  279. $definition->setFactory($config['create'], $arguments);
  280. }
  281. if (isset($config['setup'])) {
  282. if (Config\Helpers::takeParent($config['setup'])) {
  283. $definition->setSetup(array());
  284. }
  285. Validators::assertField($config, 'setup', 'list');
  286. foreach ($config['setup'] as $id => $setup) {
  287. Validators::assert($setup, 'callable|Nette\DI\Statement', "setup item #$id");
  288. $definition->addSetup($setup);
  289. }
  290. }
  291. if (isset($config['parameters'])) {
  292. Validators::assertField($config, 'parameters', 'array');
  293. $definition->setParameters($config['parameters']);
  294. }
  295. if (isset($config['implement'])) {
  296. Validators::assertField($config, 'implement', 'string');
  297. $definition->setImplement($config['implement']);
  298. $definition->setAutowired(TRUE);
  299. }
  300. if (isset($config['autowired'])) {
  301. Validators::assertField($config, 'autowired', 'bool');
  302. $definition->setAutowired($config['autowired']);
  303. }
  304. if (isset($config['dynamic'])) {
  305. Validators::assertField($config, 'dynamic', 'bool');
  306. $definition->setDynamic($config['dynamic']);
  307. }
  308. if (isset($config['inject'])) {
  309. Validators::assertField($config, 'inject', 'bool');
  310. $definition->addTag(Extensions\InjectExtension::TAG_INJECT, $config['inject']);
  311. }
  312. if (isset($config['run'])) {
  313. $config['tags']['run'] = (bool) $config['run'];
  314. }
  315. if (isset($config['tags'])) {
  316. Validators::assertField($config, 'tags', 'array');
  317. if (Config\Helpers::takeParent($config['tags'])) {
  318. $definition->setTags(array());
  319. }
  320. foreach ($config['tags'] as $tag => $attrs) {
  321. if (is_int($tag) && is_string($attrs)) {
  322. $definition->addTag($attrs);
  323. } else {
  324. $definition->addTag($tag, $attrs);
  325. }
  326. }
  327. }
  328. }
  329. /**
  330. * Removes ... and process constants recursively.
  331. * @return array
  332. */
  333. public static function filterArguments(array $args)
  334. {
  335. foreach ($args as $k => $v) {
  336. if ($v === '...') {
  337. unset($args[$k]);
  338. } elseif (is_string($v) && preg_match('#^[\w\\\\]*::[A-Z][A-Z0-9_]*\z#', $v, $m)) {
  339. $args[$k] = ContainerBuilder::literal(ltrim($v, ':'));
  340. } elseif (is_array($v)) {
  341. $args[$k] = self::filterArguments($v);
  342. } elseif ($v instanceof Statement) {
  343. $tmp = self::filterArguments(array($v->getEntity()));
  344. $args[$k] = new Statement($tmp[0], self::filterArguments($v->arguments));
  345. }
  346. }
  347. return $args;
  348. }
  349. }