| 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\Nette\Utils\Strings; |
| 12 |
/** |
| 13 |
* Generates PHP code. |
| 14 |
*/ |
| 15 |
class Printer |
| 16 |
{ |
| 17 |
use \Packetery\Nette\SmartObject; |
| 18 |
/** @var string */ |
| 19 |
protected $indentation = "\t"; |
| 20 |
/** @var int */ |
| 21 |
protected $linesBetweenProperties = 0; |
| 22 |
/** @var int */ |
| 23 |
protected $linesBetweenMethods = 2; |
| 24 |
/** @var string */ |
| 25 |
protected $returnTypeColon = ': '; |
| 26 |
/** @var bool */ |
| 27 |
private $resolveTypes = \true; |
| 28 |
public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null) : string |
| 29 |
{ |
| 30 |
$line = 'function ' . ($function->getReturnReference() ? '&' : '') . $function->getName(); |
| 31 |
$returnType = $this->printReturnType($function, $namespace); |
| 32 |
return Helpers::formatDocComment($function->getComment() . "\n") . self::printAttributes($function->getAttributes(), $namespace) . $line . $this->printParameters($function, $namespace, \strlen($line) + \strlen($returnType) + 2) . $returnType . "\n{\n" . $this->indent(\ltrim(\rtrim($function->getBody()) . "\n")) . "}\n"; |
| 33 |
} |
| 34 |
public function printClosure(Closure $closure) : string |
| 35 |
{ |
| 36 |
$uses = []; |
| 37 |
foreach ($closure->getUses() as $param) { |
| 38 |
$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); |
| 39 |
} |
| 40 |
$useStr = \strlen($tmp = \implode(', ', $uses)) > (new Dumper())->wrapLength && \count($uses) > 1 ? "\n" . $this->indentation . \implode(",\n" . $this->indentation, $uses) . "\n" : $tmp; |
| 41 |
return self::printAttributes($closure->getAttributes(), null, \true) . 'function ' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure, null) . ($uses ? " use ({$useStr})" : '') . $this->printReturnType($closure, null) . " {\n" . $this->indent(\ltrim(\rtrim($closure->getBody()) . "\n")) . '}'; |
| 42 |
} |
| 43 |
public function printArrowFunction(Closure $closure) : string |
| 44 |
{ |
| 45 |
foreach ($closure->getUses() as $use) { |
| 46 |
if ($use->isReference()) { |
| 47 |
throw new \Packetery\Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.'); |
| 48 |
} |
| 49 |
} |
| 50 |
return self::printAttributes($closure->getAttributes(), null) . 'fn' . ($closure->getReturnReference() ? '&' : '') . $this->printParameters($closure, null) . $this->printReturnType($closure, null) . ' => ' . \trim($closure->getBody()) . ';'; |
| 51 |
} |
| 52 |
public function printMethod(Method $method, PhpNamespace $namespace = null) : string |
| 53 |
{ |
| 54 |
$method->validate(); |
| 55 |
$line = ($method->isAbstract() ? 'abstract ' : '') . ($method->isFinal() ? 'final ' : '') . ($method->getVisibility() ? $method->getVisibility() . ' ' : '') . ($method->isStatic() ? 'static ' : '') . 'function ' . ($method->getReturnReference() ? '&' : '') . $method->getName(); |
| 56 |
$returnType = $this->printReturnType($method, $namespace); |
| 57 |
return Helpers::formatDocComment($method->getComment() . "\n") . self::printAttributes($method->getAttributes(), $namespace) . $line . ($params = $this->printParameters($method, $namespace, \strlen($line) + \strlen($returnType) + \strlen($this->indentation) + 2)) . $returnType . ($method->isAbstract() || $method->getBody() === null ? ";\n" : (\strpos($params, "\n") === \false ? "\n" : ' ') . "{\n" . $this->indent(\ltrim(\rtrim($method->getBody()) . "\n")) . "}\n"); |
| 58 |
} |
| 59 |
public function printClass(ClassType $class, PhpNamespace $namespace = null) : string |
| 60 |
{ |
| 61 |
$class->validate(); |
| 62 |
$resolver = $this->resolveTypes && $namespace ? [$namespace, 'unresolveUnionType'] : function ($s) { |
| 63 |
return $s; |
| 64 |
}; |
| 65 |
$traits = []; |
| 66 |
foreach ($class->getTraitResolutions() as $trait => $resolutions) { |
| 67 |
$traits[] = 'use ' . $resolver($trait) . ($resolutions ? " {\n" . $this->indentation . \implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); |
| 68 |
} |
| 69 |
$consts = []; |
| 70 |
foreach ($class->getConstants() as $const) { |
| 71 |
$def = ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; |
| 72 |
$consts[] = Helpers::formatDocComment((string) $const->getComment()) . self::printAttributes($const->getAttributes(), $namespace) . $def . $this->dump($const->getValue(), \strlen($def)) . ";\n"; |
| 73 |
} |
| 74 |
$properties = []; |
| 75 |
foreach ($class->getProperties() as $property) { |
| 76 |
$type = $property->getType(); |
| 77 |
$def = ($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' ' . \ltrim($this->printType($type, $property->isNullable(), $namespace) . ' ') . '$' . $property->getName(); |
| 78 |
$properties[] = Helpers::formatDocComment((string) $property->getComment()) . self::printAttributes($property->getAttributes(), $namespace) . $def . ($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), \strlen($def) + 3)) . ";\n"; |
| 79 |
} |
| 80 |
$methods = []; |
| 81 |
foreach ($class->getMethods() as $method) { |
| 82 |
$methods[] = $this->printMethod($method, $namespace); |
| 83 |
} |
| 84 |
$members = \array_filter([\implode('', $traits), $this->joinProperties($consts), $this->joinProperties($properties), ($methods && $properties ? \str_repeat("\n", $this->linesBetweenMethods - 1) : '') . \implode(\str_repeat("\n", $this->linesBetweenMethods), $methods)]); |
| 85 |
return Strings::normalize(Helpers::formatDocComment($class->getComment() . "\n") . self::printAttributes($class->getAttributes(), $namespace) . ($class->isAbstract() ? 'abstract ' : '') . ($class->isFinal() ? 'final ' : '') . ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '') . ($class->getExtends() ? 'extends ' . \implode(', ', \array_map($resolver, (array) $class->getExtends())) . ' ' : '') . ($class->getImplements() ? 'implements ' . \implode(', ', \array_map($resolver, $class->getImplements())) . ' ' : '') . ($class->getName() ? "\n" : '') . "{\n" . ($members ? $this->indent(\implode("\n", $members)) : '') . '}') . ($class->getName() ? "\n" : ''); |
| 86 |
} |
| 87 |
public function printNamespace(PhpNamespace $namespace) : string |
| 88 |
{ |
| 89 |
$name = $namespace->getName(); |
| 90 |
$uses = $this->printUses($namespace); |
| 91 |
$classes = []; |
| 92 |
foreach ($namespace->getClasses() as $class) { |
| 93 |
$classes[] = $this->printClass($class, $namespace); |
| 94 |
} |
| 95 |
$body = ($uses ? $uses . "\n\n" : '') . \implode("\n", $classes); |
| 96 |
if ($namespace->hasBracketedSyntax()) { |
| 97 |
return 'namespace' . ($name ? " {$name}" : '') . "\n{\n" . $this->indent($body) . "}\n"; |
| 98 |
} else { |
| 99 |
return ($name ? "namespace {$name};\n\n" : '') . $body; |
| 100 |
} |
| 101 |
} |
| 102 |
public function printFile(PhpFile $file) : string |
| 103 |
{ |
| 104 |
$namespaces = []; |
| 105 |
foreach ($file->getNamespaces() as $namespace) { |
| 106 |
$namespaces[] = $this->printNamespace($namespace); |
| 107 |
} |
| 108 |
return Strings::normalize("<?php\n" . ($file->getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '') . "\n" . ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '') . \implode("\n\n", $namespaces)) . "\n"; |
| 109 |
} |
| 110 |
/** @return static */ |
| 111 |
public function setTypeResolving(bool $state = \true) : self |
| 112 |
{ |
| 113 |
$this->resolveTypes = $state; |
| 114 |
return $this; |
| 115 |
} |
| 116 |
protected function indent(string $s) : string |
| 117 |
{ |
| 118 |
$s = \str_replace("\t", $this->indentation, $s); |
| 119 |
return Strings::indent($s, 1, $this->indentation); |
| 120 |
} |
| 121 |
protected function dump($var, int $column = 0) : string |
| 122 |
{ |
| 123 |
return (new Dumper())->dump($var, $column); |
| 124 |
} |
| 125 |
protected function printUses(PhpNamespace $namespace) : string |
| 126 |
{ |
| 127 |
$name = $namespace->getName(); |
| 128 |
$uses = []; |
| 129 |
foreach ($namespace->getUses() as $alias => $original) { |
| 130 |
if ($original !== ($name ? $name . '\\' . $alias : $alias)) { |
| 131 |
$uses[] = $alias === $original || \substr($original, -(\strlen($alias) + 1)) === '\\' . $alias ? "use {$original};" : "use {$original} as {$alias};"; |
| 132 |
} |
| 133 |
} |
| 134 |
return \implode("\n", $uses); |
| 135 |
} |
| 136 |
/** |
| 137 |
* @param Closure|GlobalFunction|Method $function |
| 138 |
*/ |
| 139 |
public function printParameters($function, PhpNamespace $namespace = null, int $column = 0) : string |
| 140 |
{ |
| 141 |
$params = []; |
| 142 |
$list = $function->getParameters(); |
| 143 |
$special = \false; |
| 144 |
foreach ($list as $param) { |
| 145 |
$variadic = $function->isVariadic() && $param === \end($list); |
| 146 |
$type = $param->getType(); |
| 147 |
$promoted = $param instanceof PromotedParameter ? $param : null; |
| 148 |
$params[] = ($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '') . ($attrs = self::printAttributes($param->getAttributes(), $namespace, \true)) . ($promoted ? ($promoted->getVisibility() ?: 'public') . ' ' : '') . \ltrim($this->printType($type, $param->isNullable(), $namespace) . ' ') . ($param->isReference() ? '&' : '') . ($variadic ? '...' : '') . '$' . $param->getName() . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : ''); |
| 149 |
$special = $special || $promoted || $attrs; |
| 150 |
} |
| 151 |
$line = \implode(', ', $params); |
| 152 |
return \count($params) > 1 && ($special || \strlen($line) + $column > (new Dumper())->wrapLength) ? "(\n" . $this->indent(\implode(",\n", $params)) . ($special ? ',' : '') . "\n)" : "({$line})"; |
| 153 |
} |
| 154 |
public function printType(?string $type, bool $nullable = \false, PhpNamespace $namespace = null) : string |
| 155 |
{ |
| 156 |
if ($type === null) { |
| 157 |
return ''; |
| 158 |
} |
| 159 |
if ($this->resolveTypes && $namespace) { |
| 160 |
$type = $namespace->unresolveUnionType($type); |
| 161 |
} |
| 162 |
if ($nullable && \strcasecmp($type, 'mixed')) { |
| 163 |
$type = \strpos($type, '|') === \false ? '?' . $type : $type . '|null'; |
| 164 |
} |
| 165 |
return $type; |
| 166 |
} |
| 167 |
/** |
| 168 |
* @param Closure|GlobalFunction|Method $function |
| 169 |
*/ |
| 170 |
private function printReturnType($function, ?PhpNamespace $namespace) : string |
| 171 |
{ |
| 172 |
return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable(), $namespace)) ? $this->returnTypeColon . $tmp : ''; |
| 173 |
} |
| 174 |
private function printAttributes(array $attrs, ?PhpNamespace $namespace, bool $inline = \false) : string |
| 175 |
{ |
| 176 |
if (!$attrs) { |
| 177 |
return ''; |
| 178 |
} |
| 179 |
$items = []; |
| 180 |
foreach ($attrs as $attr) { |
| 181 |
$args = (new Dumper())->format('...?:', $attr->getArguments()); |
| 182 |
$items[] = $this->printType($attr->getName(), \false, $namespace) . ($args ? "({$args})" : ''); |
| 183 |
} |
| 184 |
return $inline ? '#[' . \implode(', ', $items) . '] ' : '#[' . \implode("]\n#[", $items) . "]\n"; |
| 185 |
} |
| 186 |
private function joinProperties(array $props) |
| 187 |
{ |
| 188 |
return $this->linesBetweenProperties ? \implode(\str_repeat("\n", $this->linesBetweenProperties), $props) : \preg_replace('#^(\\w.*\\n)\\n(?=\\w.*;)#m', '$1', \implode("\n", $props)); |
| 189 |
} |
| 190 |
} |
| 191 |
|