nette/php-generator

🐘 Nette PHP Generator:为您生成整洁的PHP代码。支持PHP 8.3的新特性。

安装次数: 40 754 638

依赖项: 513

建议者: 5

安全性: 0

星标: 2 086

关注者: 63

分支: 134

开放问题: 4


README

Nette PHP Generator

Latest Stable Version Downloads this Month

 

您是否在寻找用于生成类、函数或完整PHP文件的工具?

✅ 支持所有最新的PHP特性,如枚举、属性等。
✅ 允许您轻松修改现有类
✅ 输出符合PSR-12 / PER编码风格的代码
✅ 高度成熟、稳定且广泛使用的库

 

安装

使用Composer工具下载并安装库

composer require nette/php-generator

PhpGenerator 4.1与PHP 8.0至8.4兼容。文档可以在库的网站上找到:库网站

 

支持我

你喜欢PHP Generator吗?你是否期待新特性?

Buy me a coffee

谢谢!

 

让我们从一个使用ClassType创建类的例子开始

$class = new Nette\PhpGenerator\ClassType('Demo');

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Class description.\nSecond line\n")
	->addComment('@property-read Nette\Forms\Form $form');

// generate code simply by typecasting to string or using echo:
echo $class;

这将返回

/**
 * Class description
 * Second line
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

要生成代码,您还可以使用所谓的打印机,它不同于echo $class,可以进行进一步配置

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);

您可以添加常量(类Constant)和属性(类Property

$class->addConstant('ID', 123)
	->setProtected() // constant visibility
	->setType('int')
	->setFinal();

$class->addProperty('items', [1, 2, 3])
	->setPrivate() // or setVisibility('private')
	->setStatic()
	->addComment('@var int[]');

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // outputs '= null'

这将生成

final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];

public ?array $list = null;

您还可以添加方法

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // return types for methods
	->setBody('return count($items ?: $this->items);');

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

结果是

/**
 * Count it.
 */
final protected function count(array &$items = []): ?int
{
	return count($items ?: $this->items);
}

PHP 8.0中引入的推广参数可以传递给构造函数

$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
	->setPrivate();

结果是

public function __construct(
	public $name,
	private $args = [],
) {
}

使用setReadOnly()函数可以标记只读属性和类。

如果添加的属性、常量、方法或参数已存在,则会抛出异常。

可以使用removeProperty()removeConstant()removeMethod()removeParameter()移除类成员。

您还可以将现有的MethodPropertyConstant对象添加到类中

$method = new Nette\PhpGenerator\Method('getHandle');
$property = new Nette\PhpGenerator\Property('handle');
$const = new Nette\PhpGenerator\Constant('ROLE');

$class = (new Nette\PhpGenerator\ClassType('Demo'))
	->addMember($method)
	->addMember($property)
	->addMember($const);

您还可以使用cloneWithName()在不同名称下克隆现有的方法、属性和常量

$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);

 

接口或特质

您可以创建接口和特质(类InterfaceTypeTraitType

$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');

使用特质

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
	->addResolution('sayHello as protected')
	->addComment('@use MyTrait<Foo>');
echo $class;

结果是

class Demo
{
	use SmartObject;
	/** @use MyTrait<Foo> */
	use MyTrait {
		sayHello as protected;
	}
}

 

枚举

您可以轻松创建PHP 8.1中引入的枚举,如下所示(类EnumType

$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');

echo $enum;

结果是

enum Suit
{
	case Clubs;
	case Diamonds;
	case Hearts;
	case Spades;
}

您还可以定义标量等效项并创建“支持”枚举

$enum->addCase('Clubs', '');
$enum->addCase('Diamonds', '');

对于每个案例,您可以使用addComment()addAttribute()添加注释或属性

 

匿名类

将名称传递为null,您就有一个匿名类

$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
	->addParameter('foo');

echo '$obj = new class ($val) ' . $class . ';';

结果是

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

 

全局函数

函数的代码由类GlobalFunction生成

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;

// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

结果是

function foo($a, $b)
{
	return $a + $b;
}

 

匿名函数

匿名函数的代码由类Closure生成

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
	->setReference();
echo $closure;

// or use the PsrPrinter for output compliant with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

结果是

function ($a, $b) use (&$c) {
	return $a + $b;
}

 

短箭头函数

您还可以使用打印器输出短匿名函数

$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');

echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);

结果是

fn($a, $b) => $a + $b

 

方法和函数签名

方法由类Method表示。您可以设置可见性、返回值、添加注释、属性等。

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int');

单个参数由类Parameter表示。同样,您可以设置所有可想象到的属性

$method->addParameter('items', []) // $items = []
	->setReference()           // &$items = []
	->setType('array');        // array &$items = []

// function count(&$items = [])

要定义所谓的变长参数(或也称为splat、spread、省略号、解包或三点运算符),请使用setVariadic()

$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');

这将生成

function count(...$items)
{
}

 

方法和函数体

可以将整个体一次传递给setBody()方法,或者逐行(逐行)通过重复调用addBody()来逐步传递

$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;

结果是

function foo()
{
	$a = rand(10, 20);
	return $a;
}

您可以使用特殊占位符轻松插入变量。

简单占位符?

$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;

结果是

function foo()
{
	return substr('any string', 3);
}

变长占位符...?

$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;

结果是

function foo()
{
	myfunc(1, 2, 3);
}

您还可以使用PHP 8的命名参数...?:

$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);

// myfunc(foo: 1, bar: true);

占位符使用反斜杠\?转义

$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;

结果是

function foo($a)
{
	return $a ? 10 : 3;
}

 

打印器和PSR合规性

使用Printer类生成PHP代码

$class = new Nette\PhpGenerator\ClassType('Demo');
// ...

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // same as: echo $class

它可以生成所有其他元素的代码,提供如printFunction()printNamespace()等方法。

还有PsrPrinter类,它按照PSR-2 / PSR-12 / PER编码风格输出

$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);

需要自定义行为?通过继承Printer类创建自己的版本。您可以重新配置这些变量

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// length of the line after which the line will break
	public int $wrapLength = 120;
	// indentation character, can be replaced with a sequence of spaces
	public string $indentation = "\t";
	// number of blank lines between properties
	public int $linesBetweenProperties = 0;
	// number of blank lines between methods
	public int $linesBetweenMethods = 2;
	// number of blank lines between 'use statements' groups for classes, functions, and constants
	public int $linesBetweenUseTypes = 0;
	// position of the opening curly brace for functions and methods
	public bool $bracesOnNextLine = true;
	// place one parameter on one line, even if it has an attribute or is supported
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// separator between the right parenthesis and return type of functions and methods
	public string $returnTypeColon = ': ';
}

标准和Printer之间的差异是什么?为什么包中没有只有一个打印器,即PsrPrinter

标准Printer将代码格式化为我们在Nette中一直所做的那样。由于Nette建立的时间比PSR早得多,而且PSR花了多年时间按计划交付标准,有时甚至在PHP推出新功能后几年,这导致了一个在几个小方面有所不同的编码标准。主要区别在于使用制表符而不是空格。我们知道,在我们的项目中使用制表符,我们可以进行宽度自定义,这对有视觉障碍的人是必不可少的。一个小差异的例子是将花括号放在函数和方法的单独一行上,始终如此。PSR的建议在我们看来似乎是不合逻辑的,并且会导致代码清晰度降低

 

类型

每个类型或联合/交集类型都可以作为字符串传递;您还可以使用原生类型的预定义常量

use Nette\PhpGenerator\Type;

$member->setType('array'); // or Type::Array;
$member->setType('?array'); // or Type::nullable(Type::Array);
$member->setType('array|string'); // or Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // or Type::intersection(Foo::class, Bar::class)
$member->setType(null); // removes the type

同样也适用于setReturnType()方法。

 

文字

使用Literal,您可以传递任何PHP代码,例如,用于默认属性值或参数等

use Nette\PhpGenerator\Literal;

$class = new Nette\PhpGenerator\ClassType('Demo');

$class->addProperty('foo', new Literal('Iterator::SELF_FIRST'));

$class->addMethod('bar')
	->addParameter('id', new Literal('1 + 2'));

echo $class;

结果

class Demo
{
	public $foo = Iterator::SELF_FIRST;

	public function bar($id = 1 + 2)
	{
	}
}

您还可以向 Literal 传递参数,并使用占位符将它们格式化为有效的 PHP 代码,具体请参阅 占位符

new Literal('substr(?, ?)', [$a, $b]);
// generates for example: substr('hello', 5);

可以使用 new 方法轻松生成表示创建新对象的字面量

Literal::new(Demo::class, [$a, 'foo' => $b]);
// generates for example: new Demo(10, foo: 20)

 

属性

从 PHP 8 开始,您可以向所有类、方法、属性、常量、枚举情况、函数、闭包和参数添加属性。您还可以将 字面量 用作参数值。

$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addAttribute('Table', [
	'name' => 'user',
	'constraints' => [
		Literal::new('UniqueConstraint', ['name' => 'ean', 'columns' => ['ean']]),
	],
]);

$class->addProperty('list')
	->addAttribute('Deprecated');

$method = $class->addMethod('count')
	->addAttribute('Foo\Cached', ['mode' => true]);

$method->addParameter('items')
	->addAttribute('Bar');

echo $class;

结果

#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
	#[Deprecated]
	public $list;


	#[Foo\Cached(mode: true)]
	public function count(
		#[Bar]
		$items,
	) {
	}
}

 

命名空间

类、特性、接口和枚举(以下统称为类)可以组合成由 PhpNamespace 类表示的命名空间

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');

// create new classes in the namespace
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// or insert an existing class into the namespace
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

如果类已经存在,则会抛出异常。

您可以定义使用条款

// use Http\Request;
$namespace->addUse(Http\Request::class);
// use Http\Request as HttpReq;
$namespace->addUse(Http\Request::class, 'HttpReq');
// use function iter\range;
$namespace->addUseFunction('iter\range');

要简化基于定义的别名的完全限定类、函数或常量名称,请使用 simplifyName 方法

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', because 'Foo' is the current namespace
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', due to the defined use-statement

相反,您可以使用 resolveName 方法将简化的类、函数或常量名称转换回完全限定名称

echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'

 

类名称解析

当类属于命名空间时,其表示方式略有不同:所有类型(例如,类型提示、返回类型、父类名称、实现的接口、使用的特性、属性和属性)都会自动 解析(除非您将其关闭,见下文)。这意味着您必须在定义中使用 完全限定类名称,它们将在生成的代码中替换为基于使用条款的别名或完全限定名称

$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // will be simplified to A
	->addTrait('Bar\AliasedClass'); // will be simplified to AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // we manually simplify in comments
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // will be translated to \Bar\OtherClass

echo $namespace;

// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

结果

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

	/**
	 * @return D
	 */
	public function method(\Bar\OtherClass $arg)
	{
	}
}

可以通过这种方式关闭自动解析

$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);

 

PHP 文件

类、函数和命名空间可以组合成由 PhpFile 类表示的 PHP 文件

$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('This file is auto-generated.');
$file->setStrictTypes(); // adds declare(strict_types=1)

$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');

// or
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');

echo $file;

// or use the PsrPrinter for output in accordance with PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

结果

<?php

/**
 * This file is auto-generated.
 */

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

请注意:除函数和类之外,文件中不能添加其他代码。

 

从现有内容生成

除了可以使用上述 API 模型类和函数之外,您还可以使用现有内容自动生成它们

// creates a class identical to the PDO class
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// creates a function identical to the trim() function
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// creates a closure based on the provided one
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

默认情况下,函数和方法体是空的。如果您也想加载它们,请使用此方法(需要安装 nikic/php-parser 包)

$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);

$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);

 

从 PHP 文件加载

您还可以从包含 PHP 代码的字符串中直接加载函数、类、接口和枚举。例如,要创建一个 ClassType 对象

$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
	<?php

	class Demo
	{
		public $foo;
	}
	XX);

在从 PHP 代码加载类时,方法体外的单行注释将被忽略(例如,用于属性等),因为此库没有与之交互的 API。

您还可以直接加载整个 PHP 文件,该文件可以包含任何数量的类、函数,甚至是命名空间

$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));

文件的初始注释和 strict_types 声明也会被加载。但是,所有其他全局代码将被忽略。

需要安装 nikic/php-parser

(如果您需要在文件中操作全局代码或方法体中的单个语句,则最好直接使用 nikic/php-parser 库。)

 

类操作器

ClassManipulator 类提供用于操作类的工具。

$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);

inheritMethod() 方法将父类或实现的接口中的方法复制到您的类中。这允许您覆盖方法或扩展其签名

$method = $manipulator->inheritMethod('bar');
$method->setBody('...');

inheritProperty() 方法将父类中的属性复制到您的类中。当您想在类中拥有相同的属性,但可能具有不同的默认值时,这很有用

$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');

implementInterface() 方法自动在您的类中实现给定接口的所有方法

$manipulator->implementInterface(SomeInterface::class);
// Now your class implements SomeInterface and includes all its methods

 

变量转储

《Dumper》类将变量转换为可解析的PHP代码。它提供的输出比标准的《var_export()`函数》更清晰、更易于阅读。

$dumper = new Nette\PhpGenerator\Dumper;

$var = ['a', 'b', 123];

echo $dumper->dump($var); // outputs ['a', 'b', 123]