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