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