AutowireRequiredMethodsPass.php 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  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\Compiler;
  11. use Symfony\Component\DependencyInjection\Definition;
  12. use Symfony\Contracts\Service\Attribute\Required;
  13. /**
  14. * Looks for definitions with autowiring enabled and registers their corresponding "@required" methods as setters.
  15. *
  16. * @author Nicolas Grekas <p@tchwork.com>
  17. */
  18. class AutowireRequiredMethodsPass extends AbstractRecursivePass
  19. {
  20. /**
  21. * {@inheritdoc}
  22. */
  23. protected function processValue($value, bool $isRoot = false)
  24. {
  25. $value = parent::processValue($value, $isRoot);
  26. if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
  27. return $value;
  28. }
  29. if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
  30. return $value;
  31. }
  32. $alreadyCalledMethods = [];
  33. $withers = [];
  34. foreach ($value->getMethodCalls() as [$method]) {
  35. $alreadyCalledMethods[strtolower($method)] = true;
  36. }
  37. foreach ($reflectionClass->getMethods() as $reflectionMethod) {
  38. $r = $reflectionMethod;
  39. if ($r->isConstructor() || isset($alreadyCalledMethods[strtolower($r->name)])) {
  40. continue;
  41. }
  42. while (true) {
  43. if (\PHP_VERSION_ID >= 80000 && $r->getAttributes(Required::class)) {
  44. if ($this->isWither($r, $r->getDocComment() ?: '')) {
  45. $withers[] = [$r->name, [], true];
  46. } else {
  47. $value->addMethodCall($r->name, []);
  48. }
  49. break;
  50. }
  51. if (false !== $doc = $r->getDocComment()) {
  52. if (false !== stripos($doc, '@required') && preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@required(?:\s|\*/$)#i', $doc)) {
  53. if ($this->isWither($reflectionMethod, $doc)) {
  54. $withers[] = [$reflectionMethod->name, [], true];
  55. } else {
  56. $value->addMethodCall($reflectionMethod->name, []);
  57. }
  58. break;
  59. }
  60. if (false === stripos($doc, '@inheritdoc') || !preg_match('#(?:^/\*\*|\n\s*+\*)\s*+(?:\{@inheritdoc\}|@inheritdoc)(?:\s|\*/$)#i', $doc)) {
  61. break;
  62. }
  63. }
  64. try {
  65. $r = $r->getPrototype();
  66. } catch (\ReflectionException $e) {
  67. break; // method has no prototype
  68. }
  69. }
  70. }
  71. if ($withers) {
  72. // Prepend withers to prevent creating circular loops
  73. $setters = $value->getMethodCalls();
  74. $value->setMethodCalls($withers);
  75. foreach ($setters as $call) {
  76. $value->addMethodCall($call[0], $call[1], $call[2] ?? false);
  77. }
  78. }
  79. return $value;
  80. }
  81. private function isWither(\ReflectionMethod $reflectionMethod, string $doc): bool
  82. {
  83. $match = preg_match('#(?:^/\*\*|\n\s*+\*)\s*+@return\s++(static|\$this)[\s\*]#i', $doc, $matches);
  84. if ($match && 'static' === $matches[1]) {
  85. return true;
  86. }
  87. if ($match && '$this' === $matches[1]) {
  88. return false;
  89. }
  90. $reflectionType = $reflectionMethod->hasReturnType() ? $reflectionMethod->getReturnType() : null;
  91. return $reflectionType instanceof \ReflectionNamedType && 'static' === $reflectionType->getName();
  92. }
  93. }