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