问答社区
最新的 PHP 功能为您生成简洁的 PHP 代码。支持 PHP 8.3 新功能。
分类:技术分享
安装
composer require nette/php-generator
类
从一个使用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 { }
要生成代码,您还可以使用所谓的printClass
,与echo $class
不同,它可以进一步配置:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
您可以添加常量和属性
$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 = [],
) {
}
Readonly 属性和类使用setReadOnly()
函数进行标记。如果已存在添加的属性、常量、方法或参数,则会引发异常。
可以使用removeProperty()
、removeConstant()、
removeMethod()
或 removeParameter()
删除类成员。
您还可以将现有的Method
、Property
或Constant
对象添加到类中:
$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);
接口或Traits
您可以创建接口和Trait(类InterfaceType
和TraitType
:
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
使用 trait
$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 中引入的枚举,如下所示
$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;
}
您还可以定义标量等效项并创建一个 “backed” 枚举:
$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;
}
Github:https://github.com/nette/php-generator
全局函数
函数的代码由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;
}
全局功能
函数的代码由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、ellipsis、unpacking 或三点运算符),请使用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?为什么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 子句:
// 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'
类名解析
当一个类是命名空间的一部分时,它的呈现方式略有不同:所有类型(例如,类型提示、返回类型、父类名称、实现的接口、使用的特征和属性)都会自动解析(除非您将其关闭,请参见下文) )。这意味着您必须在定义中使用完全限定的类名,并且它们将在结果代码中替换为别名(基于 use 子句)或完全限定的名称:
$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 对类和函数进行建模之外,您还可以使用现有的 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]