| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- <?php
- /**
- * This file is part of the Nette Framework (https://nette.org)
- * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
- */
- declare(strict_types=1);
- namespace Nette\PhpGenerator;
- use Nette;
- use Nette\Utils\Strings;
- /**
- * Generates PHP code.
- */
- class Printer
- {
- use Nette\SmartObject;
- /** @var int */
- public $wrapLength = 120;
- /** @var string */
- protected $indentation = "\t";
- /** @var int */
- protected $linesBetweenProperties = 0;
- /** @var int */
- protected $linesBetweenMethods = 2;
- /** @var string */
- protected $returnTypeColon = ': ';
- /** @var ?PhpNamespace */
- protected $namespace;
- /** @var ?Dumper */
- protected $dumper;
- /** @var bool */
- private $resolveTypes = true;
- public function __construct()
- {
- $this->dumper = new Dumper;
- }
- public function printFunction(GlobalFunction $function, ?PhpNamespace $namespace = null): string
- {
- $this->namespace = $this->resolveTypes ? $namespace : null;
- $line = 'function '
- . ($function->getReturnReference() ? '&' : '')
- . $function->getName();
- $returnType = $this->printReturnType($function);
- $body = Helpers::simplifyTaggedNames($function->getBody(), $this->namespace);
- return Helpers::formatDocComment($function->getComment() . "\n")
- . self::printAttributes($function->getAttributes())
- . $line
- . $this->printParameters($function, strlen($line) + strlen($returnType) + 2) // 2 = parentheses
- . $returnType
- . "\n{\n" . $this->indent(ltrim(rtrim($body) . "\n")) . "}\n";
- }
- public function printClosure(Closure $closure, ?PhpNamespace $namespace = null): string
- {
- $this->namespace = $this->resolveTypes ? $namespace : null;
- $uses = [];
- foreach ($closure->getUses() as $param) {
- $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
- }
- $useStr = strlen($tmp = implode(', ', $uses)) > $this->wrapLength && count($uses) > 1
- ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n"
- : $tmp;
- $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace);
- return self::printAttributes($closure->getAttributes(), true)
- . 'function '
- . ($closure->getReturnReference() ? '&' : '')
- . $this->printParameters($closure)
- . ($uses ? " use ($useStr)" : '')
- . $this->printReturnType($closure)
- . " {\n" . $this->indent(ltrim(rtrim($body) . "\n")) . '}';
- }
- public function printArrowFunction(Closure $closure, ?PhpNamespace $namespace = null): string
- {
- $this->namespace = $this->resolveTypes ? $namespace : null;
- foreach ($closure->getUses() as $use) {
- if ($use->isReference()) {
- throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.');
- }
- }
- $body = Helpers::simplifyTaggedNames($closure->getBody(), $this->namespace);
- return self::printAttributes($closure->getAttributes())
- . 'fn'
- . ($closure->getReturnReference() ? '&' : '')
- . $this->printParameters($closure)
- . $this->printReturnType($closure)
- . ' => ' . trim($body) . ';';
- }
- public function printMethod(Method $method, ?PhpNamespace $namespace = null): string
- {
- $this->namespace = $this->resolveTypes ? $namespace : null;
- $method->validate();
- $line = ($method->isAbstract() ? 'abstract ' : '')
- . ($method->isFinal() ? 'final ' : '')
- . ($method->getVisibility() ? $method->getVisibility() . ' ' : '')
- . ($method->isStatic() ? 'static ' : '')
- . 'function '
- . ($method->getReturnReference() ? '&' : '')
- . $method->getName();
- $returnType = $this->printReturnType($method);
- $params = $this->printParameters($method, strlen($line) + strlen($returnType) + strlen($this->indentation) + 2);
- $body = Helpers::simplifyTaggedNames((string) $method->getBody(), $this->namespace);
- return Helpers::formatDocComment($method->getComment() . "\n")
- . self::printAttributes($method->getAttributes())
- . $line
- . $params
- . $returnType
- . ($method->isAbstract() || $method->getBody() === null
- ? ";\n"
- : (strpos($params, "\n") === false ? "\n" : ' ')
- . "{\n"
- . $this->indent(ltrim(rtrim($body) . "\n"))
- . "}\n");
- }
- public function printClass(ClassType $class, ?PhpNamespace $namespace = null): string
- {
- $this->namespace = $this->resolveTypes ? $namespace : null;
- $class->validate();
- $resolver = $this->namespace
- ? [$namespace, 'simplifyType']
- : function ($s) { return $s; };
- $traits = [];
- foreach ($class->getTraitResolutions() as $trait) {
- $resolutions = $trait->getResolutions();
- $traits[] = Helpers::formatDocComment((string) $trait->getComment())
- . 'use ' . $resolver($trait->getName())
- . ($resolutions
- ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n"
- : ";\n");
- }
- $cases = [];
- foreach ($class->getCases() as $case) {
- $cases[] = Helpers::formatDocComment((string) $case->getComment())
- . self::printAttributes($case->getAttributes())
- . 'case ' . $case->getName()
- . ($case->getValue() === null ? '' : ' = ' . $this->dump($case->getValue()))
- . ";\n";
- }
- $enumType = isset($case) && $case->getValue() !== null
- ? $this->returnTypeColon . Type::getType($case->getValue())
- : '';
- $consts = [];
- foreach ($class->getConstants() as $const) {
- $def = ($const->isFinal() ? 'final ' : '')
- . ($const->getVisibility() ? $const->getVisibility() . ' ' : '')
- . 'const ' . $const->getName() . ' = ';
- $consts[] = Helpers::formatDocComment((string) $const->getComment())
- . self::printAttributes($const->getAttributes())
- . $def
- . $this->dump($const->getValue(), strlen($def)) . ";\n";
- }
- $properties = [];
- foreach ($class->getProperties() as $property) {
- $property->validate();
- $type = $property->getType();
- $def = (($property->getVisibility() ?: 'public')
- . ($property->isStatic() ? ' static' : '')
- . ($property->isReadOnly() && $type ? ' readonly' : '')
- . ' '
- . ltrim($this->printType($type, $property->isNullable()) . ' ')
- . '$' . $property->getName());
- $properties[] = Helpers::formatDocComment((string) $property->getComment())
- . self::printAttributes($property->getAttributes())
- . $def
- . ($property->getValue() === null && !$property->isInitialized()
- ? ''
- : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = '
- . ";\n";
- }
- $methods = [];
- foreach ($class->getMethods() as $method) {
- $methods[] = $this->printMethod($method, $namespace);
- }
- $members = array_filter([
- implode('', $traits),
- $this->joinProperties($cases),
- $this->joinProperties($consts),
- $this->joinProperties($properties),
- ($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
- . implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
- ]);
- return Strings::normalize(
- Helpers::formatDocComment($class->getComment() . "\n")
- . self::printAttributes($class->getAttributes())
- . ($class->isAbstract() ? 'abstract ' : '')
- . ($class->isFinal() ? 'final ' : '')
- . ($class->getName() ? $class->getType() . ' ' . $class->getName() . $enumType . ' ' : '')
- . ($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" : '');
- }
- public function printNamespace(PhpNamespace $namespace): string
- {
- $this->namespace = $this->resolveTypes ? $namespace : null;
- $name = $namespace->getName();
- $uses = $this->printUses($namespace)
- . $this->printUses($namespace, PhpNamespace::NAME_FUNCTION)
- . $this->printUses($namespace, PhpNamespace::NAME_CONSTANT);
- $items = [];
- foreach ($namespace->getClasses() as $class) {
- $items[] = $this->printClass($class, $namespace);
- }
- foreach ($namespace->getFunctions() as $function) {
- $items[] = $this->printFunction($function, $namespace);
- }
- $body = ($uses ? $uses . "\n" : '')
- . implode("\n", $items);
- if ($namespace->hasBracketedSyntax()) {
- return 'namespace' . ($name ? " $name" : '') . "\n{\n"
- . $this->indent($body)
- . "}\n";
- } else {
- return ($name ? "namespace $name;\n\n" : '')
- . $body;
- }
- }
- public function printFile(PhpFile $file): string
- {
- $namespaces = [];
- foreach ($file->getNamespaces() as $namespace) {
- $namespaces[] = $this->printNamespace($namespace);
- }
- 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";
- }
- protected function printUses(PhpNamespace $namespace, string $of = PhpNamespace::NAME_NORMAL): string
- {
- $prefix = [
- PhpNamespace::NAME_NORMAL => '',
- PhpNamespace::NAME_FUNCTION => 'function ',
- PhpNamespace::NAME_CONSTANT => 'const ',
- ][$of];
- $name = $namespace->getName();
- $uses = [];
- foreach ($namespace->getUses($of) as $alias => $original) {
- $uses[] = Helpers::extractShortName($original) === $alias
- ? "use $prefix$original;\n"
- : "use $prefix$original as $alias;\n";
- }
- return implode('', $uses);
- }
- /**
- * @param Closure|GlobalFunction|Method $function
- */
- protected function printParameters($function, int $column = 0): string
- {
- $params = [];
- $list = $function->getParameters();
- $special = false;
- foreach ($list as $param) {
- $param->validate();
- $variadic = $function->isVariadic() && $param === end($list);
- $type = $param->getType();
- $promoted = $param instanceof PromotedParameter ? $param : null;
- $params[] =
- ($promoted ? Helpers::formatDocComment((string) $promoted->getComment()) : '')
- . ($attrs = self::printAttributes($param->getAttributes(), true))
- . ($promoted ?
- ($promoted->getVisibility() ?: 'public')
- . ($promoted->isReadOnly() && $type ? ' readonly' : '')
- . ' ' : '')
- . ltrim($this->printType($type, $param->isNullable()) . ' ')
- . ($param->isReference() ? '&' : '')
- . ($variadic ? '...' : '')
- . '$' . $param->getName()
- . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : '');
- $special = $special || $promoted || $attrs;
- }
- $line = implode(', ', $params);
- return count($params) > 1 && ($special || strlen($line) + $column > $this->wrapLength)
- ? "(\n" . $this->indent(implode(",\n", $params)) . ($special ? ',' : '') . "\n)"
- : "($line)";
- }
- protected function printType(?string $type, bool $nullable): string
- {
- if ($type === null) {
- return '';
- }
- if ($this->namespace) {
- $type = $this->namespace->simplifyType($type);
- }
- if ($nullable && strcasecmp($type, 'mixed')) {
- $type = strpos($type, '|') === false
- ? '?' . $type
- : $type . '|null';
- }
- return $type;
- }
- /**
- * @param Closure|GlobalFunction|Method $function
- */
- private function printReturnType($function): string
- {
- return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable()))
- ? $this->returnTypeColon . $tmp
- : '';
- }
- private function printAttributes(array $attrs, bool $inline = false): string
- {
- if (!$attrs) {
- return '';
- }
- $this->dumper->indentation = $this->indentation;
- $items = [];
- foreach ($attrs as $attr) {
- $args = $this->dumper->format('...?:', $attr->getArguments());
- $args = Helpers::simplifyTaggedNames($args, $this->namespace);
- $items[] = $this->printType($attr->getName(), false) . ($args ? "($args)" : '');
- }
- return $inline
- ? '#[' . implode(', ', $items) . '] '
- : '#[' . implode("]\n#[", $items) . "]\n";
- }
- /** @return static */
- public function setTypeResolving(bool $state = true): self
- {
- $this->resolveTypes = $state;
- return $this;
- }
- protected function indent(string $s): string
- {
- $s = str_replace("\t", $this->indentation, $s);
- return Strings::indent($s, 1, $this->indentation);
- }
- protected function dump($var, int $column = 0): string
- {
- $this->dumper->indentation = $this->indentation;
- $this->dumper->wrapLength = $this->wrapLength;
- $s = $this->dumper->dump($var, $column);
- $s = Helpers::simplifyTaggedNames($s, $this->namespace);
- return $s;
- }
- private function joinProperties(array $props): string
- {
- return $this->linesBetweenProperties
- ? implode(str_repeat("\n", $this->linesBetweenProperties), $props)
- : preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props));
- }
- }
|