Definition.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\DependencyInjection;
  11. use Symfony\Component\DependencyInjection\Argument\BoundArgument;
  12. use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
  13. use Symfony\Component\DependencyInjection\Exception\OutOfBoundsException;
  14. /**
  15. * Definition represents a service definition.
  16. *
  17. * @author Fabien Potencier <fabien@symfony.com>
  18. */
  19. class Definition
  20. {
  21. private $class;
  22. private $file;
  23. private $factory;
  24. private $shared = true;
  25. private $deprecation = [];
  26. private $properties = [];
  27. private $calls = [];
  28. private $instanceof = [];
  29. private $autoconfigured = false;
  30. private $configurator;
  31. private $tags = [];
  32. private $public = false;
  33. private $synthetic = false;
  34. private $abstract = false;
  35. private $lazy = false;
  36. private $decoratedService;
  37. private $autowired = false;
  38. private $changes = [];
  39. private $bindings = [];
  40. private $errors = [];
  41. protected $arguments = [];
  42. private static $defaultDeprecationTemplate = 'The "%service_id%" service is deprecated. You should stop using it, as it will be removed in the future.';
  43. /**
  44. * @internal
  45. *
  46. * Used to store the name of the inner id when using service decoration together with autowiring
  47. */
  48. public $innerServiceId;
  49. /**
  50. * @internal
  51. *
  52. * Used to store the behavior to follow when using service decoration and the decorated service is invalid
  53. */
  54. public $decorationOnInvalid;
  55. public function __construct(string $class = null, array $arguments = [])
  56. {
  57. if (null !== $class) {
  58. $this->setClass($class);
  59. }
  60. $this->arguments = $arguments;
  61. }
  62. /**
  63. * Returns all changes tracked for the Definition object.
  64. *
  65. * @return array An array of changes for this Definition
  66. */
  67. public function getChanges()
  68. {
  69. return $this->changes;
  70. }
  71. /**
  72. * Sets the tracked changes for the Definition object.
  73. *
  74. * @param array $changes An array of changes for this Definition
  75. *
  76. * @return $this
  77. */
  78. public function setChanges(array $changes)
  79. {
  80. $this->changes = $changes;
  81. return $this;
  82. }
  83. /**
  84. * Sets a factory.
  85. *
  86. * @param string|array|Reference $factory A PHP function, reference or an array containing a class/Reference and a method to call
  87. *
  88. * @return $this
  89. */
  90. public function setFactory($factory)
  91. {
  92. $this->changes['factory'] = true;
  93. if (\is_string($factory) && false !== strpos($factory, '::')) {
  94. $factory = explode('::', $factory, 2);
  95. } elseif ($factory instanceof Reference) {
  96. $factory = [$factory, '__invoke'];
  97. }
  98. $this->factory = $factory;
  99. return $this;
  100. }
  101. /**
  102. * Gets the factory.
  103. *
  104. * @return string|array|null The PHP function or an array containing a class/Reference and a method to call
  105. */
  106. public function getFactory()
  107. {
  108. return $this->factory;
  109. }
  110. /**
  111. * Sets the service that this service is decorating.
  112. *
  113. * @param string|null $id The decorated service id, use null to remove decoration
  114. * @param string|null $renamedId The new decorated service id
  115. *
  116. * @return $this
  117. *
  118. * @throws InvalidArgumentException in case the decorated service id and the new decorated service id are equals
  119. */
  120. public function setDecoratedService(?string $id, ?string $renamedId = null, int $priority = 0, int $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE)
  121. {
  122. if ($renamedId && $id === $renamedId) {
  123. throw new InvalidArgumentException(sprintf('The decorated service inner name for "%s" must be different than the service name itself.', $id));
  124. }
  125. $this->changes['decorated_service'] = true;
  126. if (null === $id) {
  127. $this->decoratedService = null;
  128. } else {
  129. $this->decoratedService = [$id, $renamedId, (int) $priority];
  130. if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $invalidBehavior) {
  131. $this->decoratedService[] = $invalidBehavior;
  132. }
  133. }
  134. return $this;
  135. }
  136. /**
  137. * Gets the service that this service is decorating.
  138. *
  139. * @return array|null An array composed of the decorated service id, the new id for it and the priority of decoration, null if no service is decorated
  140. */
  141. public function getDecoratedService()
  142. {
  143. return $this->decoratedService;
  144. }
  145. /**
  146. * Sets the service class.
  147. *
  148. * @return $this
  149. */
  150. public function setClass(?string $class)
  151. {
  152. $this->changes['class'] = true;
  153. $this->class = $class;
  154. return $this;
  155. }
  156. /**
  157. * Gets the service class.
  158. *
  159. * @return string|null The service class
  160. */
  161. public function getClass()
  162. {
  163. return $this->class;
  164. }
  165. /**
  166. * Sets the arguments to pass to the service constructor/factory method.
  167. *
  168. * @return $this
  169. */
  170. public function setArguments(array $arguments)
  171. {
  172. $this->arguments = $arguments;
  173. return $this;
  174. }
  175. /**
  176. * Sets the properties to define when creating the service.
  177. *
  178. * @return $this
  179. */
  180. public function setProperties(array $properties)
  181. {
  182. $this->properties = $properties;
  183. return $this;
  184. }
  185. /**
  186. * Gets the properties to define when creating the service.
  187. *
  188. * @return array
  189. */
  190. public function getProperties()
  191. {
  192. return $this->properties;
  193. }
  194. /**
  195. * Sets a specific property.
  196. *
  197. * @param mixed $value
  198. *
  199. * @return $this
  200. */
  201. public function setProperty(string $name, $value)
  202. {
  203. $this->properties[$name] = $value;
  204. return $this;
  205. }
  206. /**
  207. * Adds an argument to pass to the service constructor/factory method.
  208. *
  209. * @param mixed $argument An argument
  210. *
  211. * @return $this
  212. */
  213. public function addArgument($argument)
  214. {
  215. $this->arguments[] = $argument;
  216. return $this;
  217. }
  218. /**
  219. * Replaces a specific argument.
  220. *
  221. * @param int|string $index
  222. * @param mixed $argument
  223. *
  224. * @return $this
  225. *
  226. * @throws OutOfBoundsException When the replaced argument does not exist
  227. */
  228. public function replaceArgument($index, $argument)
  229. {
  230. if (0 === \count($this->arguments)) {
  231. throw new OutOfBoundsException('Cannot replace arguments if none have been configured yet.');
  232. }
  233. if (\is_int($index) && ($index < 0 || $index > \count($this->arguments) - 1)) {
  234. throw new OutOfBoundsException(sprintf('The index "%d" is not in the range [0, %d].', $index, \count($this->arguments) - 1));
  235. }
  236. if (!\array_key_exists($index, $this->arguments)) {
  237. throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index));
  238. }
  239. $this->arguments[$index] = $argument;
  240. return $this;
  241. }
  242. /**
  243. * Sets a specific argument.
  244. *
  245. * @param int|string $key
  246. * @param mixed $value
  247. *
  248. * @return $this
  249. */
  250. public function setArgument($key, $value)
  251. {
  252. $this->arguments[$key] = $value;
  253. return $this;
  254. }
  255. /**
  256. * Gets the arguments to pass to the service constructor/factory method.
  257. *
  258. * @return array The array of arguments
  259. */
  260. public function getArguments()
  261. {
  262. return $this->arguments;
  263. }
  264. /**
  265. * Gets an argument to pass to the service constructor/factory method.
  266. *
  267. * @param int|string $index
  268. *
  269. * @return mixed The argument value
  270. *
  271. * @throws OutOfBoundsException When the argument does not exist
  272. */
  273. public function getArgument($index)
  274. {
  275. if (!\array_key_exists($index, $this->arguments)) {
  276. throw new OutOfBoundsException(sprintf('The argument "%s" doesn\'t exist.', $index));
  277. }
  278. return $this->arguments[$index];
  279. }
  280. /**
  281. * Sets the methods to call after service initialization.
  282. *
  283. * @return $this
  284. */
  285. public function setMethodCalls(array $calls = [])
  286. {
  287. $this->calls = [];
  288. foreach ($calls as $call) {
  289. $this->addMethodCall($call[0], $call[1], $call[2] ?? false);
  290. }
  291. return $this;
  292. }
  293. /**
  294. * Adds a method to call after service initialization.
  295. *
  296. * @param string $method The method name to call
  297. * @param array $arguments An array of arguments to pass to the method call
  298. * @param bool $returnsClone Whether the call returns the service instance or not
  299. *
  300. * @return $this
  301. *
  302. * @throws InvalidArgumentException on empty $method param
  303. */
  304. public function addMethodCall(string $method, array $arguments = [], bool $returnsClone = false)
  305. {
  306. if (empty($method)) {
  307. throw new InvalidArgumentException('Method name cannot be empty.');
  308. }
  309. $this->calls[] = $returnsClone ? [$method, $arguments, true] : [$method, $arguments];
  310. return $this;
  311. }
  312. /**
  313. * Removes a method to call after service initialization.
  314. *
  315. * @return $this
  316. */
  317. public function removeMethodCall(string $method)
  318. {
  319. foreach ($this->calls as $i => $call) {
  320. if ($call[0] === $method) {
  321. unset($this->calls[$i]);
  322. }
  323. }
  324. return $this;
  325. }
  326. /**
  327. * Check if the current definition has a given method to call after service initialization.
  328. *
  329. * @return bool
  330. */
  331. public function hasMethodCall(string $method)
  332. {
  333. foreach ($this->calls as $call) {
  334. if ($call[0] === $method) {
  335. return true;
  336. }
  337. }
  338. return false;
  339. }
  340. /**
  341. * Gets the methods to call after service initialization.
  342. *
  343. * @return array An array of method calls
  344. */
  345. public function getMethodCalls()
  346. {
  347. return $this->calls;
  348. }
  349. /**
  350. * Sets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
  351. *
  352. * @param ChildDefinition[] $instanceof
  353. *
  354. * @return $this
  355. */
  356. public function setInstanceofConditionals(array $instanceof)
  357. {
  358. $this->instanceof = $instanceof;
  359. return $this;
  360. }
  361. /**
  362. * Gets the definition templates to conditionally apply on the current definition, keyed by parent interface/class.
  363. *
  364. * @return ChildDefinition[]
  365. */
  366. public function getInstanceofConditionals()
  367. {
  368. return $this->instanceof;
  369. }
  370. /**
  371. * Sets whether or not instanceof conditionals should be prepended with a global set.
  372. *
  373. * @return $this
  374. */
  375. public function setAutoconfigured(bool $autoconfigured)
  376. {
  377. $this->changes['autoconfigured'] = true;
  378. $this->autoconfigured = $autoconfigured;
  379. return $this;
  380. }
  381. /**
  382. * @return bool
  383. */
  384. public function isAutoconfigured()
  385. {
  386. return $this->autoconfigured;
  387. }
  388. /**
  389. * Sets tags for this definition.
  390. *
  391. * @return $this
  392. */
  393. public function setTags(array $tags)
  394. {
  395. $this->tags = $tags;
  396. return $this;
  397. }
  398. /**
  399. * Returns all tags.
  400. *
  401. * @return array An array of tags
  402. */
  403. public function getTags()
  404. {
  405. return $this->tags;
  406. }
  407. /**
  408. * Gets a tag by name.
  409. *
  410. * @return array An array of attributes
  411. */
  412. public function getTag(string $name)
  413. {
  414. return $this->tags[$name] ?? [];
  415. }
  416. /**
  417. * Adds a tag for this definition.
  418. *
  419. * @return $this
  420. */
  421. public function addTag(string $name, array $attributes = [])
  422. {
  423. $this->tags[$name][] = $attributes;
  424. return $this;
  425. }
  426. /**
  427. * Whether this definition has a tag with the given name.
  428. *
  429. * @return bool
  430. */
  431. public function hasTag(string $name)
  432. {
  433. return isset($this->tags[$name]);
  434. }
  435. /**
  436. * Clears all tags for a given name.
  437. *
  438. * @return $this
  439. */
  440. public function clearTag(string $name)
  441. {
  442. unset($this->tags[$name]);
  443. return $this;
  444. }
  445. /**
  446. * Clears the tags for this definition.
  447. *
  448. * @return $this
  449. */
  450. public function clearTags()
  451. {
  452. $this->tags = [];
  453. return $this;
  454. }
  455. /**
  456. * Sets a file to require before creating the service.
  457. *
  458. * @return $this
  459. */
  460. public function setFile(?string $file)
  461. {
  462. $this->changes['file'] = true;
  463. $this->file = $file;
  464. return $this;
  465. }
  466. /**
  467. * Gets the file to require before creating the service.
  468. *
  469. * @return string|null The full pathname to include
  470. */
  471. public function getFile()
  472. {
  473. return $this->file;
  474. }
  475. /**
  476. * Sets if the service must be shared or not.
  477. *
  478. * @return $this
  479. */
  480. public function setShared(bool $shared)
  481. {
  482. $this->changes['shared'] = true;
  483. $this->shared = $shared;
  484. return $this;
  485. }
  486. /**
  487. * Whether this service is shared.
  488. *
  489. * @return bool
  490. */
  491. public function isShared()
  492. {
  493. return $this->shared;
  494. }
  495. /**
  496. * Sets the visibility of this service.
  497. *
  498. * @return $this
  499. */
  500. public function setPublic(bool $boolean)
  501. {
  502. $this->changes['public'] = true;
  503. $this->public = $boolean;
  504. return $this;
  505. }
  506. /**
  507. * Whether this service is public facing.
  508. *
  509. * @return bool
  510. */
  511. public function isPublic()
  512. {
  513. return $this->public;
  514. }
  515. /**
  516. * Sets if this service is private.
  517. *
  518. * @return $this
  519. *
  520. * @deprecated since Symfony 5.2, use setPublic() instead
  521. */
  522. public function setPrivate(bool $boolean)
  523. {
  524. trigger_deprecation('symfony/dependency-injection', '5.2', 'The "%s()" method is deprecated, use "setPublic()" instead.', __METHOD__);
  525. return $this->setPublic(!$boolean);
  526. }
  527. /**
  528. * Whether this service is private.
  529. *
  530. * @return bool
  531. */
  532. public function isPrivate()
  533. {
  534. return !$this->public;
  535. }
  536. /**
  537. * Sets the lazy flag of this service.
  538. *
  539. * @return $this
  540. */
  541. public function setLazy(bool $lazy)
  542. {
  543. $this->changes['lazy'] = true;
  544. $this->lazy = $lazy;
  545. return $this;
  546. }
  547. /**
  548. * Whether this service is lazy.
  549. *
  550. * @return bool
  551. */
  552. public function isLazy()
  553. {
  554. return $this->lazy;
  555. }
  556. /**
  557. * Sets whether this definition is synthetic, that is not constructed by the
  558. * container, but dynamically injected.
  559. *
  560. * @return $this
  561. */
  562. public function setSynthetic(bool $boolean)
  563. {
  564. $this->synthetic = $boolean;
  565. if (!isset($this->changes['public'])) {
  566. $this->setPublic(true);
  567. }
  568. return $this;
  569. }
  570. /**
  571. * Whether this definition is synthetic, that is not constructed by the
  572. * container, but dynamically injected.
  573. *
  574. * @return bool
  575. */
  576. public function isSynthetic()
  577. {
  578. return $this->synthetic;
  579. }
  580. /**
  581. * Whether this definition is abstract, that means it merely serves as a
  582. * template for other definitions.
  583. *
  584. * @return $this
  585. */
  586. public function setAbstract(bool $boolean)
  587. {
  588. $this->abstract = $boolean;
  589. return $this;
  590. }
  591. /**
  592. * Whether this definition is abstract, that means it merely serves as a
  593. * template for other definitions.
  594. *
  595. * @return bool
  596. */
  597. public function isAbstract()
  598. {
  599. return $this->abstract;
  600. }
  601. /**
  602. * Whether this definition is deprecated, that means it should not be called
  603. * anymore.
  604. *
  605. * @param string $package The name of the composer package that is triggering the deprecation
  606. * @param string $version The version of the package that introduced the deprecation
  607. * @param string $message The deprecation message to use
  608. *
  609. * @return $this
  610. *
  611. * @throws InvalidArgumentException when the message template is invalid
  612. */
  613. public function setDeprecated(/* string $package, string $version, string $message */)
  614. {
  615. $args = \func_get_args();
  616. if (\func_num_args() < 3) {
  617. trigger_deprecation('symfony/dependency-injection', '5.1', 'The signature of method "%s()" requires 3 arguments: "string $package, string $version, string $message", not defining them is deprecated.', __METHOD__);
  618. $status = $args[0] ?? true;
  619. if (!$status) {
  620. trigger_deprecation('symfony/dependency-injection', '5.1', 'Passing a null message to un-deprecate a node is deprecated.');
  621. }
  622. $message = (string) ($args[1] ?? null);
  623. $package = $version = '';
  624. } else {
  625. $status = true;
  626. $package = (string) $args[0];
  627. $version = (string) $args[1];
  628. $message = (string) $args[2];
  629. }
  630. if ('' !== $message) {
  631. if (preg_match('#[\r\n]|\*/#', $message)) {
  632. throw new InvalidArgumentException('Invalid characters found in deprecation template.');
  633. }
  634. if (false === strpos($message, '%service_id%')) {
  635. throw new InvalidArgumentException('The deprecation template must contain the "%service_id%" placeholder.');
  636. }
  637. }
  638. $this->changes['deprecated'] = true;
  639. $this->deprecation = $status ? ['package' => $package, 'version' => $version, 'message' => $message ?: self::$defaultDeprecationTemplate] : [];
  640. return $this;
  641. }
  642. /**
  643. * Whether this definition is deprecated, that means it should not be called
  644. * anymore.
  645. *
  646. * @return bool
  647. */
  648. public function isDeprecated()
  649. {
  650. return (bool) $this->deprecation;
  651. }
  652. /**
  653. * Message to use if this definition is deprecated.
  654. *
  655. * @deprecated since Symfony 5.1, use "getDeprecation()" instead.
  656. *
  657. * @param string $id Service id relying on this definition
  658. *
  659. * @return string
  660. */
  661. public function getDeprecationMessage(string $id)
  662. {
  663. trigger_deprecation('symfony/dependency-injection', '5.1', 'The "%s()" method is deprecated, use "getDeprecation()" instead.', __METHOD__);
  664. return $this->getDeprecation($id)['message'];
  665. }
  666. /**
  667. * @param string $id Service id relying on this definition
  668. */
  669. public function getDeprecation(string $id): array
  670. {
  671. return [
  672. 'package' => $this->deprecation['package'],
  673. 'version' => $this->deprecation['version'],
  674. 'message' => str_replace('%service_id%', $id, $this->deprecation['message']),
  675. ];
  676. }
  677. /**
  678. * Sets a configurator to call after the service is fully initialized.
  679. *
  680. * @param string|array|Reference $configurator A PHP function, reference or an array containing a class/Reference and a method to call
  681. *
  682. * @return $this
  683. */
  684. public function setConfigurator($configurator)
  685. {
  686. $this->changes['configurator'] = true;
  687. if (\is_string($configurator) && false !== strpos($configurator, '::')) {
  688. $configurator = explode('::', $configurator, 2);
  689. } elseif ($configurator instanceof Reference) {
  690. $configurator = [$configurator, '__invoke'];
  691. }
  692. $this->configurator = $configurator;
  693. return $this;
  694. }
  695. /**
  696. * Gets the configurator to call after the service is fully initialized.
  697. *
  698. * @return callable|array|null
  699. */
  700. public function getConfigurator()
  701. {
  702. return $this->configurator;
  703. }
  704. /**
  705. * Is the definition autowired?
  706. *
  707. * @return bool
  708. */
  709. public function isAutowired()
  710. {
  711. return $this->autowired;
  712. }
  713. /**
  714. * Enables/disables autowiring.
  715. *
  716. * @return $this
  717. */
  718. public function setAutowired(bool $autowired)
  719. {
  720. $this->changes['autowired'] = true;
  721. $this->autowired = $autowired;
  722. return $this;
  723. }
  724. /**
  725. * Gets bindings.
  726. *
  727. * @return array|BoundArgument[]
  728. */
  729. public function getBindings()
  730. {
  731. return $this->bindings;
  732. }
  733. /**
  734. * Sets bindings.
  735. *
  736. * Bindings map $named or FQCN arguments to values that should be
  737. * injected in the matching parameters (of the constructor, of methods
  738. * called and of controller actions).
  739. *
  740. * @return $this
  741. */
  742. public function setBindings(array $bindings)
  743. {
  744. foreach ($bindings as $key => $binding) {
  745. if (0 < strpos($key, '$') && $key !== $k = preg_replace('/[ \t]*\$/', ' $', $key)) {
  746. unset($bindings[$key]);
  747. $bindings[$key = $k] = $binding;
  748. }
  749. if (!$binding instanceof BoundArgument) {
  750. $bindings[$key] = new BoundArgument($binding);
  751. }
  752. }
  753. $this->bindings = $bindings;
  754. return $this;
  755. }
  756. /**
  757. * Add an error that occurred when building this Definition.
  758. *
  759. * @param string|\Closure|self $error
  760. *
  761. * @return $this
  762. */
  763. public function addError($error)
  764. {
  765. if ($error instanceof self) {
  766. $this->errors = array_merge($this->errors, $error->errors);
  767. } else {
  768. $this->errors[] = $error;
  769. }
  770. return $this;
  771. }
  772. /**
  773. * Returns any errors that occurred while building this Definition.
  774. *
  775. * @return array
  776. */
  777. public function getErrors()
  778. {
  779. foreach ($this->errors as $i => $error) {
  780. if ($error instanceof \Closure) {
  781. $this->errors[$i] = (string) $error();
  782. } elseif (!\is_string($error)) {
  783. $this->errors[$i] = (string) $error;
  784. }
  785. }
  786. return $this->errors;
  787. }
  788. public function hasErrors(): bool
  789. {
  790. return (bool) $this->errors;
  791. }
  792. }