PHPコードジェネレータ
- PHPの最新機能(プロパティフック、enum、属性など)をすべてサポート
- 既存のクラスを簡単に変更できます
- 出力コードはPSR-12 / PERコーディングスタイルに準拠しています
- 成熟し、安定し、広く使用されているライブラリ
インストール
ライブラリはComposerツールを使用してダウンロードおよびインストールします:
composer require nette/php-generator
PHPとの互換性は表で確認できます。
クラス
まず、ClassTypeを使用してクラスを作成する例から始めましょう:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("クラスの説明。\n2行目\n")
->addComment('@property-read Nette\Forms\Form $form');
// コードは文字列にキャストするか、echo を使用して簡単に生成できます:
echo $class;
次の結果を返します:
/**
* クラスの説明
* 2行目
*
* @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() // 定数の可視性
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // または setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // '= null' を出力します
生成されるもの:
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
そして、メソッドを追加できます:
$method = $class->addMethod('count')
->addComment('それを数えます。')
->setFinal()
->setProtected()
->setReturnType('?int') // メソッドの戻り値の型
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
結果は:
/**
* それを数えます。
*/
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()
を使用して削除できます。
クラスに既存の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);
インターフェースまたはトレイト
インターフェースとトレイトを作成できます(クラスInterfaceTypeとTraitType):
$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;
}
}
Enum
PHP 8.1で導入されたEnumは、次のように簡単に作成できます:(クラス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;
}
スカラー等価物を定義して、「backed」enumを作成することもできます:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
各caseにコメントまたは属性を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;
// または PsrPrinter を使用して 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;
// または PsrPrinter を使用して 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('それを数えます。')
->setFinal()
->setProtected()
->setReturnType('?int');
個々のパラメータはクラスParameterによって表されます。ここでも、考えられるすべてのプロパティを設定できます:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(array &$items = [])
いわゆる可変長引数パラメータ(またはスプラット演算子)を定義するには、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準拠
PHPコードの生成にはクラスPrinterを使用します:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // 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
{
// 行が折り返される行の長さ
public int $wrapLength = 120;
// インデント文字、スペースのシーケンスに置き換えることができます
public string $indentation = "\t";
// プロパティ間の空行の数
public int $linesBetweenProperties = 0;
// メソッド間の空行の数
public int $linesBetweenMethods = 2;
// クラス、関数、定数の 'use statements' グループ間の空行の数
public int $linesBetweenUseTypes = 0;
// 関数とメソッドの開始波括弧の位置
public bool $bracesOnNextLine = true;
// 属性があるか、サポートされている場合でも、1つのパラメータを1行に配置します
public bool $singleParameterOnOneLine = false;
// クラスや関数を含まない名前空間を省略します
public bool $omitEmptyNamespaces = true;
// 右括弧と関数およびメソッドの戻り値の型の間の区切り文字
public string $returnTypeColon = ': ';
}
標準のPrinter
とPsrPrinter
は実際にはどのように、そしてなぜ異なるのでしょうか?
なぜパッケージにはPsrPrinter
だけでなく、1つのプリンタしかないのでしょうか?
標準のPrinter
は、Nette全体で行っているようにコードをフォーマットします。NetteがPSRよりもずっと前に登場したこと、そしてPSRが長年にわたって標準をタイムリーに提供せず、PHPの新機能が導入されてから数年遅れて提供することもあったため、コーディング標準はいくつかの細かい点で異なります。
大きな違いは、スペースの代わりにタブを使用することだけです。プロジェクトでタブを使用することで、視覚障害を持つ人々にとって不可欠な幅のカスタマイズが可能になることを知っています。
小さな違いの例としては、関数とメソッドの波括弧を常に別の行に配置することです。PSRの推奨は非論理的であり、コードの可読性の低下につながると考えています。
型
各型またはunion/intersection型は文字列として渡すことができ、ネイティブ型には事前定義された定数を使用することもできます:
use Nette\PhpGenerator\Type;
$member->setType('array'); // または 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'); // または Type::intersection(Foo::class, Bar::class)
$member->setType(null); // 型を削除します
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]);
// 例えば、substr('hello', 5); を生成します
新しいオブジェクトの作成を表すリテラルは、new
メソッドを使用して簡単に生成できます:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// 例えば、new Demo(10, foo: 20) を生成します
属性
PHP 8属性は、すべてのクラス、メソッド、プロパティ、定数、enum、関数、クロージャ、およびパラメータに追加できます。パラメータ値としてリテラルを使用することもできます。
$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,
) {
}
}
プロパティフック
プロパティフック(クラスPropertyHookで表される)を使用して、PHP 8.4で導入された機能であるプロパティのgetおよびset操作を定義できます:
$class = new Nette\PhpGenerator\ClassType('Demo');
$prop = $class->addProperty('firstName')
->setType('string');
$prop->addHook('set', 'strtolower($value)')
->addParameter('value')
->setType('string');
$prop->addHook('get')
->setBody('return ucfirst($this->firstName);');
echo $class;
生成されるもの:
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
プロパティとプロパティフックは、abstractまたはfinalにすることができます:
$class->addProperty('id')
->setType('int')
->addHook('get')
->setAbstract();
$class->addProperty('role')
->setType('string')
->addHook('set', 'strtolower($value)')
->setFinal();
非対称可視性
PHP 8.4では、プロパティの非対称可視性が導入されました。読み取りと書き込みに対して異なるアクセスレベルを設定できます。
可視性は、2つのパラメータを持つsetVisibility()
メソッドを使用するか、またはsetPublic()
、setProtected()
、またはsetPrivate()
メソッドを使用し、可視性がプロパティの読み取りまたは書き込みに適用されるかどうかを指定するmode
パラメータを使用して設定できます。デフォルトのモードは'get'
です。
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // 読み取りは public、書き込みは private
$class->addProperty('id')
->setType('int')
->setProtected('set'); // 書き込みは protected
echo $class;
生成されるもの:
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
名前空間
クラス、プロパティ、インターフェース、およびenum(以下、クラス)は、クラスPhpNamespaceで表される名前空間にグループ化できます:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// 名前空間に新しいクラスを作成します
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// または、既存のクラスを名前空間に挿入します
$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'、'Foo' は現在の名前空間のため
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range'、定義された use ステートメントのため
逆に、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') // A に簡略化されます
->addTrait('Bar\AliasedClass'); // AliasedClass に簡略化されます
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // コメント内で手動で簡略化します
$method->addParameter('arg')
->setType('Bar\OtherClass'); // \Bar\OtherClass に変換されます
echo $namespace;
// または PsrPrinter を使用して 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; // または PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
PHPファイル
クラス、関数、および名前空間は、クラスPhpFileで表されるPHPファイルにグループ化できます:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('このファイルは自動生成されました。');
$file->setStrictTypes(); // declare(strict_types=1) を追加します
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// または
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// または PsrPrinter を使用して PSR-2 / PSR-12 / PER に準拠した出力を生成します
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
結果:
<?php
/**
* このファイルは自動生成されました。
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
注意: 関数やクラス以外のコードをファイルに追加することはできません。
既存のものに基づく生成
上記で説明したAPIを使用してクラスや関数をモデル化するだけでなく、既存のパターンに基づいて自動的に生成させることもできます:
// PDO クラスと同じクラスを作成します
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// trim() 関数と同じ関数を作成します
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// 指定されたクロージャに基づいてクロージャを作成します
$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コードを含む文字列から直接、関数、クラス、インターフェース、およびenumを読み込むこともできます。例えば、このようにして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
ライブラリを直接使用する方が良いでしょう。
Class Manipulator
クラスClassManipulatorは、クラスを操作するためのツールを提供します。
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
inheritMethod()
メソッドは、親クラスまたは実装されたインターフェースからメソッドをクラスにコピーします。これにより、メソッドをオーバーライドしたり、そのシグネチャを拡張したりできます:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
```
`inheritProperty()`メソッドは、親クラスからプロパティをクラスにコピーします。これは、同じプロパティをクラスに持ちたいが、例えば異なるデフォルト値を持つ場合に便利です:
```php
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
implement()
メソッドは、指定されたインターフェースまたは抽象クラスのすべてのメソッドとプロパティをクラスに自動的に実装します:
$manipulator->implement(SomeInterface::class);
// これで、クラスは SomeInterface を実装し、そのすべてのメソッドを含みます
変数の出力
Dumper
クラスは、変数を解析可能なPHPコードに変換します。標準のvar_export()
関数よりも優れた、より明確な出力を提供します。
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // ['a', 'b', 123] を出力します
互換性テーブル
PhpGenerator 4.1はPHP 8.0から8.4と互換性があります。