Add Composer autoloading functions and PHPStan for testing

This commit is contained in:
Alex Cabal 2019-02-26 13:03:45 -06:00
parent e198c4db65
commit f5d7d4e02a
1518 changed files with 169063 additions and 30 deletions

View file

@ -0,0 +1,495 @@
<?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;
/**
* Class/Interface/Trait description.
*
* @property Method[] $methods
* @property Property[] $properties
*/
final class ClassType
{
use Nette\SmartObject;
use Traits\CommentAware;
public const
TYPE_CLASS = 'class',
TYPE_INTERFACE = 'interface',
TYPE_TRAIT = 'trait';
public const
VISIBILITY_PUBLIC = 'public',
VISIBILITY_PROTECTED = 'protected',
VISIBILITY_PRIVATE = 'private';
/** @var PhpNamespace|null */
private $namespace;
/** @var string|null */
private $name;
/** @var string class|interface|trait */
private $type = self::TYPE_CLASS;
/** @var bool */
private $final = false;
/** @var bool */
private $abstract = false;
/** @var string|string[] */
private $extends = [];
/** @var string[] */
private $implements = [];
/** @var array[] */
private $traits = [];
/** @var Constant[] name => Constant */
private $consts = [];
/** @var Property[] name => Property */
private $properties = [];
/** @var Method[] name => Method */
private $methods = [];
/**
* @param string|object $class
* @return static
*/
public static function from($class): self
{
return (new Factory)->fromClassReflection(new \ReflectionClass($class));
}
public function __construct(string $name = null, PhpNamespace $namespace = null)
{
$this->setName($name);
$this->namespace = $namespace;
}
public function __toString(): string
{
try {
return (new Printer)->printClass($this, $this->namespace);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/**
* Deprecated: an object can be in multiple namespaces.
* @deprecated
*/
public function getNamespace(): ?PhpNamespace
{
return $this->namespace;
}
/**
* @return static
*/
public function setName(?string $name): self
{
if ($name !== null && !Helpers::isIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
}
$this->name = $name;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
/**
* @return static
*/
public function setType(string $type): self
{
if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) {
throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.');
}
$this->type = $type;
return $this;
}
public function getType(): string
{
return $this->type;
}
/**
* @return static
*/
public function setFinal(bool $state = true): self
{
$this->final = $state;
return $this;
}
public function isFinal(): bool
{
return $this->final;
}
/**
* @return static
*/
public function setAbstract(bool $state = true): self
{
$this->abstract = $state;
return $this;
}
public function isAbstract(): bool
{
return $this->abstract;
}
/**
* @param string|string[] $names
* @return static
*/
public function setExtends($names): self
{
if (!is_string($names) && !is_array($names)) {
throw new Nette\InvalidArgumentException('Argument must be string or string[].');
}
$this->validateNames((array) $names);
$this->extends = $names;
return $this;
}
/**
* @return string|string[]
*/
public function getExtends()
{
return $this->extends;
}
/**
* @return static
*/
public function addExtend(string $name): self
{
$this->validateNames([$name]);
$this->extends = (array) $this->extends;
$this->extends[] = $name;
return $this;
}
/**
* @param string[] $names
* @return static
*/
public function setImplements(array $names): self
{
$this->validateNames($names);
$this->implements = $names;
return $this;
}
/**
* @return string[]
*/
public function getImplements(): array
{
return $this->implements;
}
/**
* @return static
*/
public function addImplement(string $name): self
{
$this->validateNames([$name]);
$this->implements[] = $name;
return $this;
}
/**
* @param string[] $names
* @return static
*/
public function setTraits(array $names): self
{
$this->validateNames($names);
$this->traits = array_fill_keys($names, []);
return $this;
}
/**
* @return string[]
*/
public function getTraits(): array
{
return array_keys($this->traits);
}
/**
* @internal
*/
public function getTraitResolutions(): array
{
return $this->traits;
}
/**
* @return static
*/
public function addTrait(string $name, array $resolutions = []): self
{
$this->validateNames([$name]);
$this->traits[$name] = $resolutions;
return $this;
}
/**
* @param Method|Property|Constant $member
* @return static
*/
public function addMember($member): self
{
if ($member instanceof Method) {
if ($this->type === self::TYPE_INTERFACE) {
$member->setBody(null);
}
$this->methods[$member->getName()] = $member;
} elseif ($member instanceof Property) {
$this->properties[$member->getName()] = $member;
} elseif ($member instanceof Constant) {
$this->consts[$member->getName()] = $member;
} else {
throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.');
}
return $this;
}
/**
* @param Constant[]|mixed[] $consts
* @return static
*/
public function setConstants(array $consts): self
{
$this->consts = [];
foreach ($consts as $k => $v) {
$const = $v instanceof Constant ? $v : (new Constant($k))->setValue($v);
$this->consts[$const->getName()] = $const;
}
return $this;
}
/**
* @return Constant[]
*/
public function getConstants(): array
{
return $this->consts;
}
public function addConstant(string $name, $value): Constant
{
return $this->consts[$name] = (new Constant($name))->setValue($value);
}
/**
* @return static
*/
public function removeConstant(string $name): self
{
unset($this->consts[$name]);
return $this;
}
/**
* @param Property[] $props
* @return static
*/
public function setProperties(array $props): self
{
$this->properties = [];
foreach ($props as $v) {
if (!$v instanceof Property) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].');
}
$this->properties[$v->getName()] = $v;
}
return $this;
}
/**
* @return Property[]
*/
public function getProperties(): array
{
return $this->properties;
}
public function getProperty(string $name): Property
{
if (!isset($this->properties[$name])) {
throw new Nette\InvalidArgumentException("Property '$name' not found.");
}
return $this->properties[$name];
}
/**
* @param string $name without $
*/
public function addProperty(string $name, $value = null): Property
{
return $this->properties[$name] = (new Property($name))->setValue($value);
}
/**
* @param string $name without $
* @return static
*/
public function removeProperty(string $name): self
{
unset($this->properties[$name]);
return $this;
}
/**
* @param Method[] $methods
* @return static
*/
public function setMethods(array $methods): self
{
$this->methods = [];
foreach ($methods as $v) {
if (!$v instanceof Method) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].');
}
$this->methods[$v->getName()] = $v;
}
return $this;
}
/**
* @return Method[]
*/
public function getMethods(): array
{
return $this->methods;
}
public function getMethod(string $name): Method
{
if (!isset($this->methods[$name])) {
throw new Nette\InvalidArgumentException("Method '$name' not found.");
}
return $this->methods[$name];
}
public function addMethod(string $name): Method
{
$method = new Method($name);
if ($this->type === self::TYPE_INTERFACE) {
$method->setBody(null);
} else {
$method->setVisibility(self::VISIBILITY_PUBLIC);
}
return $this->methods[$name] = $method;
}
/**
* @return static
*/
public function removeMethod(string $name): self
{
unset($this->methods[$name]);
return $this;
}
/**
* @throws Nette\InvalidStateException
*/
public function validate(): void
{
if ($this->abstract && $this->final) {
throw new Nette\InvalidStateException('Class cannot be abstract and final.');
} elseif (!$this->name && ($this->abstract || $this->final)) {
throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.');
}
}
private function validateNames(array $names): void
{
foreach ($names as $name) {
if (!Helpers::isNamespaceIdentifier($name, true)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid class name.");
}
}
}
public function __clone()
{
$clone = function ($item) { return clone $item; };
$this->consts = array_map($clone, $this->consts);
$this->properties = array_map($clone, $this->properties);
$this->methods = array_map($clone, $this->methods);
}
}

View file

@ -0,0 +1,70 @@
<?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;
/**
* Closure.
*
* @property string $body
*/
final class Closure
{
use Nette\SmartObject;
use Traits\FunctionLike;
/** @var Parameter[] */
private $uses = [];
/**
* @return static
*/
public static function from(\Closure $closure): self
{
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($closure));
}
public function __toString(): string
{
try {
return (new Printer)->printClosure($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/**
* @param Parameter[] $uses
* @return static
*/
public function setUses(array $uses): self
{
(function (Parameter ...$uses) {})(...$uses);
$this->uses = $uses;
return $this;
}
public function getUses(): array
{
return $this->uses;
}
public function addUse(string $name): Parameter
{
return $this->uses[] = new Parameter($name);
}
}

View file

@ -0,0 +1,43 @@
<?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;
/**
* Class constant.
*/
final class Constant
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\VisibilityAware;
use Traits\CommentAware;
/** @var mixed */
private $value;
/**
* @return static
*/
public function setValue($val): self
{
$this->value = $val;
return $this;
}
public function getValue()
{
return $this->value;
}
}

View file

@ -0,0 +1,134 @@
<?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;
/**
* Creates a representation based on reflection.
*/
final class Factory
{
use Nette\SmartObject;
public function fromClassReflection(\ReflectionClass $from): ClassType
{
$class = $from->isAnonymous()
? new ClassType
: new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName()));
$class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS));
$class->setFinal($from->isFinal() && $class->getType() === $class::TYPE_CLASS);
$class->setAbstract($from->isAbstract() && $class->getType() === $class::TYPE_CLASS);
$ifaces = $from->getInterfaceNames();
foreach ($ifaces as $iface) {
$ifaces = array_filter($ifaces, function (string $item) use ($iface): bool {
return !is_subclass_of($iface, $item);
});
}
$class->setImplements($ifaces);
$class->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
if ($from->getParentClass()) {
$class->setExtends($from->getParentClass()->getName());
$class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames()));
}
$props = $methods = [];
foreach ($from->getProperties() as $prop) {
if ($prop->isDefault() && $prop->getDeclaringClass()->getName() === $from->getName()) {
$props[] = $this->fromPropertyReflection($prop);
}
}
$class->setProperties($props);
foreach ($from->getMethods() as $method) {
if ($method->getDeclaringClass()->getName() === $from->getName()) {
$methods[] = $this->fromMethodReflection($method);
}
}
$class->setMethods($methods);
$class->setConstants($from->getConstants());
return $class;
}
public function fromMethodReflection(\ReflectionMethod $from): Method
{
$method = new Method($from->getName());
$method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
$method->setStatic($from->isStatic());
$isInterface = $from->getDeclaringClass()->isInterface();
$method->setVisibility($from->isPrivate()
? ClassType::VISIBILITY_PRIVATE
: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC))
);
$method->setFinal($from->isFinal());
$method->setAbstract($from->isAbstract() && !$isInterface);
$method->setBody($from->isAbstract() ? null : '');
$method->setReturnReference($from->returnsReference());
$method->setVariadic($from->isVariadic());
$method->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
if ($from->hasReturnType()) {
$method->setReturnType((string) $from->getReturnType());
$method->setReturnNullable($from->getReturnType()->allowsNull());
}
return $method;
}
/**
* @return GlobalFunction|Closure
*/
public function fromFunctionReflection(\ReflectionFunction $from)
{
$function = $from->isClosure() ? new Closure : new GlobalFunction($from->getName());
$function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters()));
$function->setReturnReference($from->returnsReference());
$function->setVariadic($from->isVariadic());
if (!$from->isClosure()) {
$function->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
}
if ($from->hasReturnType()) {
$function->setReturnType((string) $from->getReturnType());
$function->setReturnNullable($from->getReturnType()->allowsNull());
}
return $function;
}
public function fromParameterReflection(\ReflectionParameter $from): Parameter
{
$param = new Parameter($from->getName());
$param->setReference($from->isPassedByReference());
$param->setTypeHint($from->hasType() ? (string) $from->getType() : null);
$param->setNullable($from->hasType() && $from->getType()->allowsNull());
if ($from->isDefaultValueAvailable()) {
$param->setDefaultValue($from->isDefaultValueConstant()
? new PhpLiteral($from->getDefaultValueConstantName())
: $from->getDefaultValue());
$param->setNullable($param->isNullable() && $param->getDefaultValue() !== null);
}
return $param;
}
public function fromPropertyReflection(\ReflectionProperty $from): Property
{
$prop = new Property($from->getName());
$prop->setValue($from->getDeclaringClass()->getDefaultProperties()[$prop->getName()] ?? null);
$prop->setStatic($from->isStatic());
$prop->setVisibility($from->isPrivate()
? ClassType::VISIBILITY_PRIVATE
: ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC)
);
$prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment()));
return $prop;
}
}

View file

@ -0,0 +1,44 @@
<?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;
/**
* Global function.
*
* @property string $body
*/
final class GlobalFunction
{
use Nette\SmartObject;
use Traits\FunctionLike;
use Traits\NameAware;
use Traits\CommentAware;
/**
* @return static
*/
public static function from(string $function): self
{
return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function));
}
public function __toString(): string
{
try {
return (new Printer)->printFunction($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
}

View file

@ -0,0 +1,273 @@
<?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;
/**
* PHP code generator utils.
*/
final class Helpers
{
use Nette\StaticClass;
public const PHP_IDENT = '[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*';
public const WRAP_LENGTH = 100;
public const INDENT_LENGTH = 4;
private const MAX_DEPTH = 50;
/**
* Returns a PHP representation of a variable.
*/
public static function dump($var): string
{
return self::_dump($var);
}
private static function _dump(&$var, int $level = 0)
{
if ($var instanceof PhpLiteral) {
return (string) $var;
} elseif (is_float($var)) {
if (is_finite($var)) {
$var = var_export($var, true);
return strpos($var, '.') === false ? $var . '.0' : $var; // workaround for PHP < 7.0.2
}
return str_replace('.0', '', var_export($var, true)); // workaround for PHP 7.0.2
} elseif ($var === null) {
return 'null';
} elseif (is_string($var) && (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error())) {
static $table;
if ($table === null) {
foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) {
$table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
}
$table['\\'] = '\\\\';
$table["\r"] = '\r';
$table["\n"] = '\n';
$table["\t"] = '\t';
$table['$'] = '\$';
$table['"'] = '\"';
}
return '"' . strtr($var, $table) . '"';
} elseif (is_string($var)) {
return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|\z)#', '\\\\$0', $var) . "'";
} elseif (is_array($var)) {
$space = str_repeat("\t", $level);
static $marker;
if ($marker === null) {
$marker = uniqid("\x00", true);
}
if (empty($var)) {
$out = '';
} elseif ($level > self::MAX_DEPTH || isset($var[$marker])) {
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
} else {
$out = '';
$outWrapped = "\n$space";
$var[$marker] = true;
$counter = 0;
foreach ($var as $k => &$v) {
if ($k !== $marker) {
$item = ($k === $counter ? '' : self::_dump($k, $level + 1) . ' => ') . self::_dump($v, $level + 1);
$counter = is_int($k) ? max($k + 1, $counter) : $counter;
$out .= ($out === '' ? '' : ', ') . $item;
$outWrapped .= "\t$item,\n$space";
}
}
unset($var[$marker]);
}
$wrap = strpos($out, "\n") !== false || strlen($out) > self::WRAP_LENGTH - $level * self::INDENT_LENGTH;
return '[' . ($wrap ? $outWrapped : $out) . ']';
} elseif ($var instanceof \Serializable) {
$var = serialize($var);
return 'unserialize(' . self::_dump($var, $level) . ')';
} elseif ($var instanceof \Closure) {
throw new Nette\InvalidArgumentException('Cannot dump closure.');
} elseif (is_object($var)) {
$class = get_class($var);
if ((new \ReflectionObject($var))->isAnonymous()) {
throw new Nette\InvalidArgumentException('Cannot dump anonymous class.');
} elseif (in_array($class, ['DateTime', 'DateTimeImmutable'], true)) {
return self::formatArgs("new $class(?, new DateTimeZone(?))", [$var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()]);
}
$arr = (array) $var;
$space = str_repeat("\t", $level);
static $list = [];
if ($level > self::MAX_DEPTH || in_array($var, $list, true)) {
throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.');
} else {
$out = "\n";
$list[] = $var;
if (method_exists($var, '__sleep')) {
foreach ($var->__sleep() as $v) {
$props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true;
}
}
foreach ($arr as $k => &$v) {
if (!isset($props) || isset($props[$k])) {
$out .= "$space\t" . self::_dump($k, $level + 1) . ' => ' . self::_dump($v, $level + 1) . ",\n";
}
}
array_pop($list);
$out .= $space;
}
return $class === 'stdClass'
? "(object) [$out]"
: __CLASS__ . "::createObject('$class', [$out])";
} elseif (is_resource($var)) {
throw new Nette\InvalidArgumentException('Cannot dump resource.');
} else {
return var_export($var, true);
}
}
/**
* Generates PHP statement.
*/
public static function format(string $statement, ...$args): string
{
return self::formatArgs($statement, $args);
}
/**
* Generates PHP statement.
*/
public static function formatArgs(string $statement, array $args): string
{
$tokens = preg_split('#(\.\.\.\?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE);
$res = '';
foreach ($tokens as $n => $token) {
if ($n % 2 === 0) {
$res .= $token;
} elseif ($token === '\\?') {
$res .= '?';
} elseif (!$args) {
throw new Nette\InvalidArgumentException('Insufficient number of arguments.');
} elseif ($token === '?') {
$res .= self::dump(array_shift($args));
} elseif ($token === '...?' || $token === '?*') {
$arg = array_shift($args);
if (!is_array($arg)) {
throw new Nette\InvalidArgumentException('Argument must be an array.');
}
$items = [];
foreach ($arg as $tmp) {
$items[] = self::dump($tmp);
}
$res .= strlen($tmp = implode(', ', $items)) > self::WRAP_LENGTH && count($items) > 1
? "\n" . Nette\Utils\Strings::indent(implode(",\n", $items)) . "\n"
: $tmp;
} else { // $ -> ::
$res .= substr($token, 0, -1) . self::formatMember(array_shift($args));
}
}
if ($args) {
throw new Nette\InvalidArgumentException('Insufficient number of placeholders.');
}
return $res;
}
/**
* Returns a PHP representation of a object member.
*/
public static function formatMember($name): string
{
return $name instanceof PhpLiteral || !self::isIdentifier($name)
? '{' . self::_dump($name) . '}'
: $name;
}
public static function formatDocComment(string $content): string
{
if (($s = trim($content)) === '') {
return '';
} elseif (strpos($content, "\n") === false) {
return "/** $s */\n";
} else {
return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n";
}
}
public static function unformatDocComment(string $comment): string
{
return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*')));
}
public static function isIdentifier($value): bool
{
return is_string($value) && preg_match('#^' . self::PHP_IDENT . '\z#', $value);
}
public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool
{
$re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*\z#';
return is_string($value) && preg_match($re, $value);
}
/**
* @return object
* @internal
*/
public static function createObject(string $class, array $props)
{
return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1));
}
public static function extractNamespace(string $name): string
{
return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : '';
}
public static function extractShortName(string $name): string
{
return ($pos = strrpos($name, '\\')) === false ? $name : substr($name, $pos + 1);
}
public static function tabsToSpaces(string $s, int $count = self::INDENT_LENGTH): string
{
return str_replace("\t", str_repeat(' ', $count), $s);
}
}

View file

@ -0,0 +1,134 @@
<?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;
/**
* Class method.
*
* @property string|null $body
*/
final class Method
{
use Nette\SmartObject;
use Traits\FunctionLike;
use Traits\NameAware;
use Traits\VisibilityAware;
use Traits\CommentAware;
/** @var string|null */
private $body = '';
/** @var bool */
private $static = false;
/** @var bool */
private $final = false;
/** @var bool */
private $abstract = false;
/**
* @param string|array $method
* @return static
*/
public static function from($method): self
{
return (new Factory)->fromMethodReflection(Nette\Utils\Callback::toReflection($method));
}
public function __toString(): string
{
try {
return (new Printer)->printMethod($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
/**
* @return static
*/
public function setBody(?string $code, array $args = null): self
{
$this->body = $args === null || $code === null ? $code : Helpers::formatArgs($code, $args);
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
/**
* @return static
*/
public function setStatic(bool $state = true): self
{
$this->static = $state;
return $this;
}
public function isStatic(): bool
{
return $this->static;
}
/**
* @return static
*/
public function setFinal(bool $state = true): self
{
$this->final = $state;
return $this;
}
public function isFinal(): bool
{
return $this->final;
}
/**
* @return static
*/
public function setAbstract(bool $state = true): self
{
$this->abstract = $state;
return $this;
}
public function isAbstract(): bool
{
return $this->abstract;
}
/**
* @throws Nette\InvalidStateException
*/
public function validate(): void
{
if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) {
throw new Nette\InvalidStateException('Method cannot be abstract and final or private.');
}
}
}

View file

@ -0,0 +1,122 @@
<?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;
/**
* Method parameter description.
*
* @property mixed $defaultValue
*/
final class Parameter
{
use Nette\SmartObject;
use Traits\NameAware;
/** @var bool */
private $reference = false;
/** @var string|null */
private $typeHint;
/** @var bool */
private $nullable = false;
/** @var bool */
private $hasDefaultValue = false;
/** @var mixed */
private $defaultValue;
/**
* @return static
*/
public function setReference(bool $state = true): self
{
$this->reference = $state;
return $this;
}
public function isReference(): bool
{
return $this->reference;
}
/**
* @return static
*/
public function setTypeHint(?string $hint): self
{
$this->typeHint = $hint;
return $this;
}
public function getTypeHint(): ?string
{
return $this->typeHint;
}
/**
* @deprecated just use setDefaultValue()
* @return static
*/
public function setOptional(bool $state = true): self
{
trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED);
$this->hasDefaultValue = $state;
return $this;
}
/**
* @return static
*/
public function setNullable(bool $state = true): self
{
$this->nullable = $state;
return $this;
}
public function isNullable(): bool
{
return $this->nullable;
}
/**
* @return static
*/
public function setDefaultValue($val): self
{
$this->defaultValue = $val;
$this->hasDefaultValue = true;
return $this;
}
public function getDefaultValue()
{
return $this->defaultValue;
}
public function hasDefaultValue(): bool
{
return $this->hasDefaultValue;
}
}

View file

@ -0,0 +1,115 @@
<?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;
/**
* Instance of PHP file.
*
* Generates:
* - opening tag (<?php)
* - doc comments
* - one or more namespaces
*/
final class PhpFile
{
use Nette\SmartObject;
use Traits\CommentAware;
/** @var PhpNamespace[] */
private $namespaces = [];
/** @var bool */
private $strictTypes = false;
public function addClass(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addClass(Helpers::extractShortName($name));
}
public function addInterface(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addInterface(Helpers::extractShortName($name));
}
public function addTrait(string $name): ClassType
{
return $this
->addNamespace(Helpers::extractNamespace($name))
->addTrait(Helpers::extractShortName($name));
}
public function addNamespace(string $name): PhpNamespace
{
if (!isset($this->namespaces[$name])) {
$this->namespaces[$name] = new PhpNamespace($name);
foreach ($this->namespaces as $namespace) {
$namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces['']));
}
}
return $this->namespaces[$name];
}
/**
* @return PhpNamespace[]
*/
public function getNamespaces(): array
{
return $this->namespaces;
}
/**
* @return static
*/
public function addUse(string $name, string $alias = null): self
{
$this->addNamespace('')->addUse($name, $alias);
return $this;
}
/**
* Adds declare(strict_types=1) to output.
* @return static
*/
public function setStrictTypes(bool $on = true): self
{
$this->strictTypes = $on;
return $this;
}
public function getStrictTypes(): bool
{
return $this->strictTypes;
}
public function __toString(): string
{
try {
return (new Printer)->printFile($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
}

View file

@ -0,0 +1,32 @@
<?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;
/**
* PHP literal value.
*/
final class PhpLiteral
{
/** @var string */
private $value;
public function __construct(string $value)
{
$this->value = $value;
}
public function __toString(): string
{
return $this->value;
}
}

View file

@ -0,0 +1,199 @@
<?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\InvalidStateException;
use Nette\Utils\Strings;
/**
* Namespaced part of a PHP file.
*
* Generates:
* - namespace statement
* - variable amount of use statements
* - one or more class declarations
*/
final class PhpNamespace
{
use Nette\SmartObject;
private const KEYWORDS = [
'string' => 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1,
'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1,
];
/** @var string */
private $name;
/** @var bool */
private $bracketedSyntax = false;
/** @var string[] */
private $uses = [];
/** @var ClassType[] */
private $classes = [];
public function __construct(string $name)
{
if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
}
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
/**
* @return static
* @internal
*/
public function setBracketedSyntax(bool $state = true): self
{
$this->bracketedSyntax = $state;
return $this;
}
public function getBracketedSyntax(): bool
{
return $this->bracketedSyntax;
}
/**
* @throws InvalidStateException
* @return static
*/
public function addUse(string $name, string $alias = null, string &$aliasOut = null): self
{
$name = ltrim($name, '\\');
if ($alias === null && $this->name === Helpers::extractNamespace($name)) {
$alias = Helpers::extractShortName($name);
}
if ($alias === null) {
$path = explode('\\', $name);
$counter = null;
do {
if (empty($path)) {
$counter++;
} else {
$alias = array_pop($path) . $alias;
}
} while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name);
$alias .= $counter;
} elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) {
throw new InvalidStateException(
"Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'."
);
}
$aliasOut = $alias;
$this->uses[$alias] = $name;
asort($this->uses);
return $this;
}
/**
* @return string[]
*/
public function getUses(): array
{
return $this->uses;
}
public function unresolveName(string $name): string
{
if (isset(self::KEYWORDS[strtolower($name)]) || $name === '') {
return $name;
}
$name = ltrim($name, '\\');
$res = null;
$lower = strtolower($name);
foreach ($this->uses as $alias => $original) {
if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) {
$short = $alias . substr($name, strlen($original));
if (!isset($res) || strlen($res) > strlen($short)) {
$res = $short;
}
}
}
if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) {
return substr($name, strlen($this->name) + 1);
} else {
return $res ?: ($this->name ? '\\' : '') . $name;
}
}
/**
* @return static
*/
public function add(ClassType $class): self
{
$name = $class->getName();
if ($name === null) {
throw new Nette\InvalidArgumentException('Class does not have a name.');
}
$this->addUse($this->name . '\\' . $name);
$this->classes[$name] = $class;
return $this;
}
public function addClass(string $name): ClassType
{
$this->add($class = new ClassType($name, $this));
return $class;
}
public function addInterface(string $name): ClassType
{
return $this->addClass($name)->setType(ClassType::TYPE_INTERFACE);
}
public function addTrait(string $name): ClassType
{
return $this->addClass($name)->setType(ClassType::TYPE_TRAIT);
}
/**
* @return ClassType[]
*/
public function getClasses(): array
{
return $this->classes;
}
public function __toString(): string
{
try {
return (new Printer)->printNamespace($this);
} catch (\Throwable $e) {
trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
}
}
}

View file

@ -0,0 +1,226 @@
<?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 string */
protected $indentation = "\t";
/** @var int */
protected $linesBetweenMethods = 2;
public function printFunction(GlobalFunction $function, PhpNamespace $namespace = null): string
{
return Helpers::formatDocComment($function->getComment() . "\n")
. 'function '
. ($function->getReturnReference() ? '&' : '')
. $function->getName()
. $this->printParameters($function, $namespace)
. $this->printReturnType($function, $namespace)
. "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n";
}
public function printClosure(Closure $closure): string
{
$uses = [];
foreach ($closure->getUses() as $param) {
$uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName();
}
$useStr = strlen($tmp = implode(', ', $uses)) > Helpers::WRAP_LENGTH && count($uses) > 1
? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n"
: $tmp;
return 'function '
. ($closure->getReturnReference() ? '&' : '')
. $this->printParameters($closure, null)
. ($uses ? " use ($useStr)" : '')
. $this->printReturnType($closure, null)
. " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}';
}
public function printMethod(Method $method, PhpNamespace $namespace = null): string
{
$method->validate();
return Helpers::formatDocComment($method->getComment() . "\n")
. ($method->isAbstract() ? 'abstract ' : '')
. ($method->isFinal() ? 'final ' : '')
. ($method->getVisibility() ? $method->getVisibility() . ' ' : '')
. ($method->isStatic() ? 'static ' : '')
. 'function '
. ($method->getReturnReference() ? '&' : '')
. $method->getName()
. ($params = $this->printParameters($method, $namespace))
. $this->printReturnType($method, $namespace)
. ($method->isAbstract() || $method->getBody() === null
? ";\n"
: (strpos($params, "\n") === false ? "\n" : ' ')
. "{\n"
. $this->indent(ltrim(rtrim($method->getBody()) . "\n"))
. "}\n");
}
public function printClass(ClassType $class, PhpNamespace $namespace = null): string
{
$class->validate();
$resolver = $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; };
$traits = [];
foreach ($class->getTraitResolutions() as $trait => $resolutions) {
$traits[] = 'use ' . $resolver($trait)
. ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n");
}
$consts = [];
foreach ($class->getConstants() as $const) {
$consts[] = Helpers::formatDocComment((string) $const->getComment())
. ($const->getVisibility() ? $const->getVisibility() . ' ' : '')
. 'const ' . $const->getName() . ' = ' . Helpers::dump($const->getValue()) . ";\n";
}
$properties = [];
foreach ($class->getProperties() as $property) {
$properties[] = Helpers::formatDocComment((string) $property->getComment())
. ($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' $' . $property->getName()
. ($property->getValue() === null ? '' : ' = ' . Helpers::dump($property->getValue()))
. ";\n";
}
$methods = [];
foreach ($class->getMethods() as $method) {
$methods[] = $this->printMethod($method, $namespace);
}
$members = array_filter([
implode('', $traits),
implode('', $consts),
implode("\n", $properties),
($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '')
. implode(str_repeat("\n", $this->linesBetweenMethods), $methods),
]);
return Strings::normalize(
Helpers::formatDocComment($class->getComment() . "\n")
. ($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" : '');
}
public function printNamespace(PhpNamespace $namespace): string
{
$name = $namespace->getName();
$uses = [];
foreach ($namespace->getUses() as $alias => $original) {
if ($original !== ($name ? $name . '\\' . $alias : $alias)) {
if ($alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias) {
$uses[] = "use $original;";
} else {
$uses[] = "use $original as $alias;";
}
}
}
$classes = [];
foreach ($namespace->getClasses() as $class) {
$classes[] = $this->printClass($class, $namespace);
}
$body = ($uses ? implode("\n", $uses) . "\n\n" : '')
. implode("\n", $classes);
if ($namespace->getBracketedSyntax()) {
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->getStrictTypes() ? "declare(strict_types=1);\n\n" : '')
. implode("\n\n", $namespaces)
) . "\n";
}
protected function indent(string $s): string
{
return Strings::indent($s, 1, $this->indentation);
}
/**
* @param Nette\PhpGenerator\Traits\FunctionLike $function
*/
protected function printParameters($function, ?PhpNamespace $namespace): string
{
$params = [];
$list = $function->getParameters();
foreach ($list as $param) {
$variadic = $function->isVariadic() && $param === end($list);
$hint = $param->getTypeHint();
$params[] = ($hint ? ($param->isNullable() ? '?' : '') . ($namespace ? $namespace->unresolveName($hint) : $hint) . ' ' : '')
. ($param->isReference() ? '&' : '')
. ($variadic ? '...' : '')
. '$' . $param->getName()
. ($param->hasDefaultValue() && !$variadic ? ' = ' . Helpers::dump($param->getDefaultValue()) : '');
}
return strlen($tmp = implode(', ', $params)) > Helpers::WRAP_LENGTH && count($params) > 1
? "(\n" . $this->indentation . implode(",\n" . $this->indentation, $params) . "\n)"
: "($tmp)";
}
/**
* @param Nette\PhpGenerator\Traits\FunctionLike $function
*/
protected function printReturnType($function, ?PhpNamespace $namespace): string
{
return $function->getReturnType()
? ': ' . ($function->getReturnNullable() ? '?' : '') . ($namespace ? $namespace->unresolveName($function->getReturnType()) : $function->getReturnType())
: '';
}
}

View file

@ -0,0 +1,64 @@
<?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;
/**
* Class property description.
*
* @property mixed $value
*/
final class Property
{
use Nette\SmartObject;
use Traits\NameAware;
use Traits\VisibilityAware;
use Traits\CommentAware;
/** @var mixed */
private $value;
/** @var bool */
private $static = false;
/**
* @return static
*/
public function setValue($val): self
{
$this->value = $val;
return $this;
}
public function &getValue()
{
return $this->value;
}
/**
* @return static
*/
public function setStatic(bool $state = true): self
{
$this->static = $state;
return $this;
}
public function isStatic(): bool
{
return $this->static;
}
}

View file

@ -0,0 +1,23 @@
<?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;
/**
* Generates PHP code compatible with PSR-2 and PSR-12.
*/
final class PsrPrinter extends Printer
{
/** @var string */
protected $indentation = ' ';
/** @var int */
protected $linesBetweenMethods = 1;
}

View file

@ -0,0 +1,46 @@
<?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\Traits;
/**
* @internal
*/
trait CommentAware
{
/** @var string|null */
private $comment;
/**
* @return static
*/
public function setComment(?string $val): self
{
$this->comment = $val;
return $this;
}
public function getComment(): ?string
{
return $this->comment;
}
/**
* @return static
*/
public function addComment(string $val): self
{
$this->comment .= $this->comment ? "\n$val" : $val;
return $this;
}
}

View file

@ -0,0 +1,189 @@
<?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\Traits;
use Nette;
use Nette\PhpGenerator\Helpers;
use Nette\PhpGenerator\Parameter;
/**
* @internal
*/
trait FunctionLike
{
/** @var string */
private $body = '';
/** @var array of name => Parameter */
private $parameters = [];
/** @var bool */
private $variadic = false;
/** @var string|null */
private $returnType;
/** @var bool */
private $returnReference = false;
/** @var bool */
private $returnNullable = false;
/**
* @return static
*/
public function setBody(string $code, array $args = null): self
{
$this->body = $args === null ? $code : Helpers::formatArgs($code, $args);
return $this;
}
public function getBody(): string
{
return $this->body;
}
/**
* @return static
*/
public function addBody(string $code, array $args = null): self
{
$this->body .= ($args === null ? $code : Helpers::formatArgs($code, $args)) . "\n";
return $this;
}
/**
* @param Parameter[] $val
* @return static
*/
public function setParameters(array $val): self
{
$this->parameters = [];
foreach ($val as $v) {
if (!$v instanceof Parameter) {
throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].');
}
$this->parameters[$v->getName()] = $v;
}
return $this;
}
/**
* @return Parameter[]
*/
public function getParameters(): array
{
return $this->parameters;
}
/**
* @param string $name without $
*/
public function addParameter(string $name, $defaultValue = null): Parameter
{
$param = new Parameter($name);
if (func_num_args() > 1) {
$param->setDefaultValue($defaultValue);
}
return $this->parameters[$name] = $param;
}
/**
* @param string $name without $
* @return static
*/
public function removeParameter(string $name): self
{
unset($this->parameters[$name]);
return $this;
}
/**
* @return static
*/
public function setVariadic(bool $state = true): self
{
$this->variadic = $state;
return $this;
}
public function isVariadic(): bool
{
return $this->variadic;
}
/**
* @return static
*/
public function setReturnType(?string $val): self
{
$this->returnType = $val;
return $this;
}
public function getReturnType(): ?string
{
return $this->returnType;
}
/**
* @return static
*/
public function setReturnReference(bool $state = true): self
{
$this->returnReference = $state;
return $this;
}
public function getReturnReference(): bool
{
return $this->returnReference;
}
/**
* @return static
*/
public function setReturnNullable(bool $state = true): self
{
$this->returnNullable = $state;
return $this;
}
public function getReturnNullable(): bool
{
return $this->returnNullable;
}
/**
* @deprecated
*/
public function setNamespace(PhpNamespace $val = null): self
{
trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED);
return $this;
}
}

View file

@ -0,0 +1,49 @@
<?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\Traits;
use Nette;
/**
* @internal
*/
trait NameAware
{
/** @var string */
private $name;
public function __construct(string $name)
{
if (!Nette\PhpGenerator\Helpers::isIdentifier($name)) {
throw new Nette\InvalidArgumentException("Value '$name' is not valid name.");
}
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
/**
* Returns clone with a different name.
* @return static
*/
public function cloneWithName(string $name): self
{
$dolly = clone $this;
$dolly->__construct($name);
return $dolly;
}
}

View file

@ -0,0 +1,43 @@
<?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\Traits;
use Nette;
use Nette\PhpGenerator\ClassType;
/**
* @internal
*/
trait VisibilityAware
{
/** @var string|null public|protected|private */
private $visibility;
/**
* @param string|null $val public|protected|private
* @return static
*/
public function setVisibility(?string $val): self
{
if (!in_array($val, [ClassType::VISIBILITY_PUBLIC, ClassType::VISIBILITY_PROTECTED, ClassType::VISIBILITY_PRIVATE, null], true)) {
throw new Nette\InvalidArgumentException('Argument must be public|protected|private.');
}
$this->visibility = $val;
return $this;
}
public function getVisibility(): ?string
{
return $this->visibility;
}
}