PluginProbe
Packeta / 1.6.1
Packeta v1.6.1
2.3.2 2.3.1 trunk 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.3.0 1.3.1 1.3.2 1.4 1.4.1 1.4.2 1.4.3 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 All 56 releases
packeta / deps / nette / php-generator / src / PhpGenerator / Factory.php

Factory.php in Packeta 1.6.1, at deps/nette/php-generator/src/PhpGenerator/Factory.php

273 lines 13.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7 declare (strict_types=1);
8 namespace Packetery\Nette\PhpGenerator;
9
10 use Packetery\Nette;
11 use Packetery\PhpParser;
12 use Packetery\PhpParser\Node;
13 use Packetery\PhpParser\ParserFactory;
14 /**
15 * Creates a representation based on reflection.
16 */
17 final class Factory
18 {
19 use \Packetery\Nette\SmartObject;
20 public function fromClassReflection(\ReflectionClass $from, bool $withBodies = \false) : ClassType
21 {
22 $class = $from->isAnonymous() ? new ClassType() : new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
23 $class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
24 $class->setFinal($from->isFinal() && $class->isClass());
25 $class->setAbstract($from->isAbstract() && $class->isClass());
26 $ifaces = $from->getInterfaceNames();
27 foreach ($ifaces as $iface) {
28 $ifaces = \array_filter($ifaces, function (string $item) use($iface) : bool {
29 return !\is_subclass_of($iface, $item);
30 });
31 }
32 $class->setImplements($ifaces);
33 $class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
34 $class->setAttributes(self::getAttributes($from));
35 if ($from->getParentClass()) {
36 $class->setExtends($from->getParentClass()->name);
37 $class->setImplements(\array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
38 }
39 $props = $methods = $consts = [];
40 foreach ($from->getProperties() as $prop) {
41 if ($prop->isDefault() && $prop->getDeclaringClass()->name === $from->name && (\PHP_VERSION_ID < 80000 || !$prop->isPromoted())) {
42 $props[] = $this->fromPropertyReflection($prop);
43 }
44 }
45 $class->setProperties($props);
46 $bodies = [];
47 foreach ($from->getMethods() as $method) {
48 if ($method->getDeclaringClass()->name === $from->name) {
49 $methods[] = $m = $this->fromMethodReflection($method);
50 if ($withBodies) {
51 $srcMethod = \Packetery\Nette\Utils\Reflection::getMethodDeclaringMethod($method);
52 $srcClass = $srcMethod->getDeclaringClass()->name;
53 $b = $bodies[$srcClass] = $bodies[$srcClass] ?? $this->loadMethodBodies($srcMethod->getDeclaringClass());
54 if (isset($b[$srcMethod->name])) {
55 $m->setBody($b[$srcMethod->name]);
56 }
57 }
58 }
59 }
60 $class->setMethods($methods);
61 foreach ($from->getReflectionConstants() as $const) {
62 if ($const->getDeclaringClass()->name === $from->name) {
63 $consts[] = $this->fromConstantReflection($const);
64 }
65 }
66 $class->setConstants($consts);
67 return $class;
68 }
69 public function fromMethodReflection(\ReflectionMethod $from) : Method
70 {
71 $method = new Method($from->name);
72 $method->setParameters(\array_map([$this, 'fromParameterReflection'], $from->getParameters()));
73 $method->setStatic($from->isStatic());
74 $isInterface = $from->getDeclaringClass()->isInterface();
75 $method->setVisibility($from->isPrivate() ? ClassType::VISIBILITY_PRIVATE : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC)));
76 $method->setFinal($from->isFinal());
77 $method->setAbstract($from->isAbstract() && !$isInterface);
78 $method->setBody($from->isAbstract() ? null : '');
79 $method->setReturnReference($from->returnsReference());
80 $method->setVariadic($from->isVariadic());
81 $method->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
82 $method->setAttributes(self::getAttributes($from));
83 if ($from->getReturnType() instanceof \ReflectionNamedType) {
84 $method->setReturnType($from->getReturnType()->getName());
85 $method->setReturnNullable($from->getReturnType()->allowsNull());
86 } elseif ($from->getReturnType() instanceof \ReflectionUnionType) {
87 $method->setReturnType((string) $from->getReturnType());
88 }
89 return $method;
90 }
91 /** @return GlobalFunction|Closure */
92 public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody = \false)
93 {
94 $function = $from->isClosure() ? new Closure() : new GlobalFunction($from->name);
95 $function->setParameters(\array_map([$this, 'fromParameterReflection'], $from->getParameters()));
96 $function->setReturnReference($from->returnsReference());
97 $function->setVariadic($from->isVariadic());
98 if (!$from->isClosure()) {
99 $function->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
100 }
101 $function->setAttributes(self::getAttributes($from));
102 if ($from->getReturnType() instanceof \ReflectionNamedType) {
103 $function->setReturnType($from->getReturnType()->getName());
104 $function->setReturnNullable($from->getReturnType()->allowsNull());
105 } elseif ($from->getReturnType() instanceof \ReflectionUnionType) {
106 $function->setReturnType((string) $from->getReturnType());
107 }
108 $function->setBody($withBody ? $this->loadFunctionBody($from) : '');
109 return $function;
110 }
111 /** @return Method|GlobalFunction|Closure */
112 public function fromCallable(callable $from)
113 {
114 $ref = \Packetery\Nette\Utils\Callback::toReflection($from);
115 return $ref instanceof \ReflectionMethod ? self::fromMethodReflection($ref) : self::fromFunctionReflection($ref);
116 }
117 public function fromParameterReflection(\ReflectionParameter $from) : Parameter
118 {
119 $param = \PHP_VERSION_ID >= 80000 && $from->isPromoted() ? new PromotedParameter($from->name) : new Parameter($from->name);
120 $param->setReference($from->isPassedByReference());
121 if ($from->getType() instanceof \ReflectionNamedType) {
122 $param->setType($from->getType()->getName());
123 $param->setNullable($from->getType()->allowsNull());
124 } elseif ($from->getType() instanceof \ReflectionUnionType) {
125 $param->setType((string) $from->getType());
126 }
127 if ($from->isDefaultValueAvailable()) {
128 $param->setDefaultValue($from->isDefaultValueConstant() ? new Literal($from->getDefaultValueConstantName()) : $from->getDefaultValue());
129 $param->setNullable($param->isNullable() && $param->getDefaultValue() !== null);
130 }
131 $param->setAttributes(self::getAttributes($from));
132 return $param;
133 }
134 public function fromConstantReflection(\ReflectionClassConstant $from) : Constant
135 {
136 $const = new Constant($from->name);
137 $const->setValue($from->getValue());
138 $const->setVisibility($from->isPrivate() ? ClassType::VISIBILITY_PRIVATE : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC));
139 $const->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
140 $const->setAttributes(self::getAttributes($from));
141 return $const;
142 }
143 public function fromPropertyReflection(\ReflectionProperty $from) : Property
144 {
145 $defaults = $from->getDeclaringClass()->getDefaultProperties();
146 $prop = new Property($from->name);
147 $prop->setValue($defaults[$prop->getName()] ?? null);
148 $prop->setStatic($from->isStatic());
149 $prop->setVisibility($from->isPrivate() ? ClassType::VISIBILITY_PRIVATE : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC));
150 if (\PHP_VERSION_ID >= 70400) {
151 if ($from->getType() instanceof \ReflectionNamedType) {
152 $prop->setType($from->getType()->getName());
153 $prop->setNullable($from->getType()->allowsNull());
154 } elseif ($from->getType() instanceof \ReflectionUnionType) {
155 $prop->setType((string) $from->getType());
156 }
157 $prop->setInitialized($from->hasType() && \array_key_exists($prop->getName(), $defaults));
158 } else {
159 $prop->setInitialized(\false);
160 }
161 $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
162 $prop->setAttributes(self::getAttributes($from));
163 return $prop;
164 }
165 private function loadMethodBodies(\ReflectionClass $from) : array
166 {
167 if ($from->isAnonymous()) {
168 throw new \Packetery\Nette\NotSupportedException('Anonymous classes are not supported.');
169 }
170 [$code, $stmts] = $this->parse($from);
171 $nodeFinder = new PhpParser\NodeFinder();
172 $class = $nodeFinder->findFirst($stmts, function (Node $node) use($from) {
173 return ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) && $node->namespacedName->toString() === $from->name;
174 });
175 $bodies = [];
176 foreach ($nodeFinder->findInstanceOf($class, Node\Stmt\ClassMethod::class) as $method) {
177 /** @var Node\Stmt\ClassMethod $method */
178 if ($method->stmts) {
179 $body = $this->extractBody($nodeFinder, $code, $method->stmts);
180 $bodies[$method->name->toString()] = Helpers::unindent($body, 2);
181 }
182 }
183 return $bodies;
184 }
185 private function loadFunctionBody(\ReflectionFunction $from) : string
186 {
187 if ($from->isClosure()) {
188 throw new \Packetery\Nette\NotSupportedException('Closures are not supported.');
189 }
190 [$code, $stmts] = $this->parse($from);
191 $nodeFinder = new PhpParser\NodeFinder();
192 /** @var Node\Stmt\Function_ $function */
193 $function = $nodeFinder->findFirst($stmts, function (Node $node) use($from) {
194 return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $from->name;
195 });
196 $body = $this->extractBody($nodeFinder, $code, $function->stmts);
197 return Helpers::unindent($body, 1);
198 }
199 /**
200 * @param Node[] $statements
201 */
202 private function extractBody(PhpParser\NodeFinder $nodeFinder, string $originalCode, array $statements) : string
203 {
204 $start = $statements[0]->getAttribute('startFilePos');
205 $body = \substr($originalCode, $start, \end($statements)->getAttribute('endFilePos') - $start + 1);
206 $replacements = [];
207 // name-nodes => resolved fully-qualified name
208 foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $node) {
209 if ($node->hasAttribute('resolvedName') && $node->getAttribute('resolvedName') instanceof Node\Name\FullyQualified) {
210 $replacements[] = [$node->getStartFilePos(), $node->getEndFilePos(), $node->getAttribute('resolvedName')->toCodeString()];
211 }
212 }
213 // multi-line strings => singleline
214 foreach (\array_merge($nodeFinder->findInstanceOf($statements, Node\Scalar\String_::class), $nodeFinder->findInstanceOf($statements, Node\Scalar\EncapsedStringPart::class)) as $node) {
215 /** @var Node\Scalar\String_|Node\Scalar\EncapsedStringPart $node */
216 $token = \substr($body, $node->getStartFilePos() - $start, $node->getEndFilePos() - $node->getStartFilePos() + 1);
217 if (\strpos($token, "\n") !== \false) {
218 $quote = $node instanceof Node\Scalar\String_ ? '"' : '';
219 $replacements[] = [$node->getStartFilePos(), $node->getEndFilePos(), $quote . \addcslashes($node->value, "\x00..\x1f") . $quote];
220 }
221 }
222 // HEREDOC => "string"
223 foreach ($nodeFinder->findInstanceOf($statements, Node\Scalar\Encapsed::class) as $node) {
224 /** @var Node\Scalar\Encapsed $node */
225 if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) {
226 $replacements[] = [$node->getStartFilePos(), $node->parts[0]->getStartFilePos() - 1, '"'];
227 $replacements[] = [\end($node->parts)->getEndFilePos() + 1, $node->getEndFilePos(), '"'];
228 }
229 }
230 //sort collected resolved names by position in file
231 \usort($replacements, function ($a, $b) {
232 return $a[0] <=> $b[0];
233 });
234 $correctiveOffset = -$start;
235 //replace changes body length so we need correct offset
236 foreach ($replacements as [$startPos, $endPos, $replacement]) {
237 $replacingStringLength = $endPos - $startPos + 1;
238 $body = \substr_replace($body, $replacement, $correctiveOffset + $startPos, $replacingStringLength);
239 $correctiveOffset += \strlen($replacement) - $replacingStringLength;
240 }
241 return $body;
242 }
243 private function parse($from) : array
244 {
245 $file = $from->getFileName();
246 if (!\class_exists(ParserFactory::class)) {
247 throw new \Packetery\Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'.");
248 } elseif (!$file) {
249 throw new \Packetery\Nette\InvalidStateException("Source code of {$from->name} not found.");
250 }
251 $lexer = new PhpParser\Lexer(['usedAttributes' => ['startFilePos', 'endFilePos']]);
252 $parser = (new ParserFactory())->create(ParserFactory::ONLY_PHP7, $lexer);
253 $code = \file_get_contents($file);
254 $code = \str_replace("\r\n", "\n", $code);
255 $stmts = $parser->parse($code);
256 $traverser = new PhpParser\NodeTraverser();
257 $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['replaceNodes' => \false]));
258 $stmts = $traverser->traverse($stmts);
259 return [$code, $stmts];
260 }
261 private function getAttributes($from) : array
262 {
263 if (\PHP_VERSION_ID < 80000) {
264 return [];
265 }
266 $res = [];
267 foreach ($from->getAttributes() as $attr) {
268 $res[] = new Attribute($attr->getName(), $attr->getArguments());
269 }
270 return $res;
271 }
272 }
273