1<?php 2 3/* 4 * This file is part of the Symfony package. 5 * 6 * (c) Fabien Potencier <fabien@symfony.com> 7 * 8 * For the full copyright and license information, please view the LICENSE 9 * file that was distributed with this source code. 10 */ 11 12namespace Symfony\Component\Config\Resource; 13 14use Symfony\Component\DependencyInjection\ServiceSubscriberInterface as LegacyServiceSubscriberInterface; 15use Symfony\Component\EventDispatcher\EventSubscriberInterface; 16use Symfony\Component\Messenger\Handler\MessageSubscriberInterface; 17use Symfony\Contracts\Service\ServiceSubscriberInterface; 18 19/** 20 * @author Nicolas Grekas <p@tchwork.com> 21 * 22 * @final since Symfony 4.3 23 */ 24class ReflectionClassResource implements SelfCheckingResourceInterface 25{ 26 private $files = []; 27 private $className; 28 private $classReflector; 29 private $excludedVendors = []; 30 private $hash; 31 32 public function __construct(\ReflectionClass $classReflector, array $excludedVendors = []) 33 { 34 $this->className = $classReflector->name; 35 $this->classReflector = $classReflector; 36 $this->excludedVendors = $excludedVendors; 37 } 38 39 public function isFresh($timestamp) 40 { 41 if (null === $this->hash) { 42 $this->hash = $this->computeHash(); 43 $this->loadFiles($this->classReflector); 44 } 45 46 foreach ($this->files as $file => $v) { 47 if (false === $filemtime = @filemtime($file)) { 48 return false; 49 } 50 51 if ($filemtime > $timestamp) { 52 return $this->hash === $this->computeHash(); 53 } 54 } 55 56 return true; 57 } 58 59 public function __toString() 60 { 61 return 'reflection.'.$this->className; 62 } 63 64 /** 65 * @internal 66 */ 67 public function __sleep(): array 68 { 69 if (null === $this->hash) { 70 $this->hash = $this->computeHash(); 71 $this->loadFiles($this->classReflector); 72 } 73 74 return ['files', 'className', 'hash']; 75 } 76 77 private function loadFiles(\ReflectionClass $class) 78 { 79 foreach ($class->getInterfaces() as $v) { 80 $this->loadFiles($v); 81 } 82 do { 83 $file = $class->getFileName(); 84 if (false !== $file && file_exists($file)) { 85 foreach ($this->excludedVendors as $vendor) { 86 if (0 === strpos($file, $vendor) && false !== strpbrk(substr($file, \strlen($vendor), 1), '/'.\DIRECTORY_SEPARATOR)) { 87 $file = false; 88 break; 89 } 90 } 91 if ($file) { 92 $this->files[$file] = null; 93 } 94 } 95 foreach ($class->getTraits() as $v) { 96 $this->loadFiles($v); 97 } 98 } while ($class = $class->getParentClass()); 99 } 100 101 private function computeHash(): string 102 { 103 if (null === $this->classReflector) { 104 try { 105 $this->classReflector = new \ReflectionClass($this->className); 106 } catch (\ReflectionException $e) { 107 // the class does not exist anymore 108 return false; 109 } 110 } 111 $hash = hash_init('md5'); 112 113 foreach ($this->generateSignature($this->classReflector) as $info) { 114 hash_update($hash, $info); 115 } 116 117 return hash_final($hash); 118 } 119 120 private function generateSignature(\ReflectionClass $class): iterable 121 { 122 yield $class->getDocComment(); 123 yield (int) $class->isFinal(); 124 yield (int) $class->isAbstract(); 125 126 if ($class->isTrait()) { 127 yield print_r(class_uses($class->name), true); 128 } else { 129 yield print_r(class_parents($class->name), true); 130 yield print_r(class_implements($class->name), true); 131 yield print_r($class->getConstants(), true); 132 } 133 134 if (!$class->isInterface()) { 135 $defaults = $class->getDefaultProperties(); 136 137 foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC | \ReflectionProperty::IS_PROTECTED) as $p) { 138 yield $p->getDocComment().$p; 139 yield print_r(isset($defaults[$p->name]) && !\is_object($defaults[$p->name]) ? $defaults[$p->name] : null, true); 140 } 141 } 142 143 foreach ($class->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) as $m) { 144 $defaults = []; 145 $parametersWithUndefinedConstants = []; 146 foreach ($m->getParameters() as $p) { 147 if (!$p->isDefaultValueAvailable()) { 148 $defaults[$p->name] = null; 149 150 continue; 151 } 152 153 if (!$p->isDefaultValueConstant() || \defined($p->getDefaultValueConstantName())) { 154 $defaults[$p->name] = $p->getDefaultValue(); 155 156 continue; 157 } 158 159 $defaults[$p->name] = $p->getDefaultValueConstantName(); 160 $parametersWithUndefinedConstants[$p->name] = true; 161 } 162 163 if (!$parametersWithUndefinedConstants) { 164 yield preg_replace('/^ @@.*/m', '', $m); 165 } else { 166 $stack = [ 167 $m->getDocComment(), 168 $m->getName(), 169 $m->isAbstract(), 170 $m->isFinal(), 171 $m->isStatic(), 172 $m->isPublic(), 173 $m->isPrivate(), 174 $m->isProtected(), 175 $m->returnsReference(), 176 $m->hasReturnType() ? $m->getReturnType()->getName() : '', 177 ]; 178 179 foreach ($m->getParameters() as $p) { 180 if (!isset($parametersWithUndefinedConstants[$p->name])) { 181 $stack[] = (string) $p; 182 } else { 183 $stack[] = $p->isOptional(); 184 $stack[] = $p->hasType() ? $p->getType()->getName() : ''; 185 $stack[] = $p->isPassedByReference(); 186 $stack[] = $p->isVariadic(); 187 $stack[] = $p->getName(); 188 } 189 } 190 191 yield implode(',', $stack); 192 } 193 194 yield print_r($defaults, true); 195 } 196 197 if ($class->isAbstract() || $class->isInterface() || $class->isTrait()) { 198 return; 199 } 200 201 if (interface_exists(EventSubscriberInterface::class, false) && $class->isSubclassOf(EventSubscriberInterface::class)) { 202 yield EventSubscriberInterface::class; 203 yield print_r($class->name::getSubscribedEvents(), true); 204 } 205 206 if (interface_exists(MessageSubscriberInterface::class, false) && $class->isSubclassOf(MessageSubscriberInterface::class)) { 207 yield MessageSubscriberInterface::class; 208 foreach ($class->name::getHandledMessages() as $key => $value) { 209 yield $key.print_r($value, true); 210 } 211 } 212 213 if (interface_exists(LegacyServiceSubscriberInterface::class, false) && $class->isSubclassOf(LegacyServiceSubscriberInterface::class)) { 214 yield LegacyServiceSubscriberInterface::class; 215 yield print_r([$class->name, 'getSubscribedServices'](), true); 216 } elseif (interface_exists(ServiceSubscriberInterface::class, false) && $class->isSubclassOf(ServiceSubscriberInterface::class)) { 217 yield ServiceSubscriberInterface::class; 218 yield print_r($class->name::getSubscribedServices(), true); 219 } 220 } 221} 222