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("Description of class.\nSecond line\n")
->addComment('@property-read Nette\Forms\Form $form');
// to generate PHP code simply cast to string or use echo:
echo $class;
このような結果がレンダリングされます。
/**
* Description of class.
* 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() // 可視性を一定にする。
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // or setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // prints '= 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') // method return type
->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);
インターフェイスまたはトレイト
インターフェースや特性(クラス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;
}
}
列挙
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;
}
また、ケースに対してスカラー等価物を定義し、裏付けされたenumを作成することができます。
$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 PsrPrinter for output conforming to 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 PsrPrinter for output conforming to 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 = [])
いわゆる variadics パラメータ(あるいは splat, spread, ellipsis, unpacking, three dots
オペレータ)を定義するには、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()
などのメソッドを提供し、他のすべての要素のコードを生成することができます。
さらに、PSR-2 / PSR-12 / PERのコーディングスタイルに準拠した出力が可能なPsrPrinter
クラスが用意されています:
$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;
// クラス、関数、定数の使用 文のグループ間の空白行数
public int $linesBetweenUseTypes = 0;
// 関数とメソッドの開始波括弧の位置
public bool $bracesOnNextLine = true;
// パラメータが属性を持っていたり、昇格していても、1つのパラメータを1行に配置する。
public bool $singleParameterOnOneLine = false;
// omits namespaces that do not contain any class or function
public bool $omitEmptyNamespaces = true;
// メソッドの右括弧と戻り値の関数との間の区切り文字
public string $returnTypeColon = ': ';
}
標準のPrinter
とPsrPrinter
は、具体的にどのように、なぜ違うのでしょうか?なぜ、パッケージにはPsrPrinter
という1つのプリンターしかないのでしょうか?
標準の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 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]);
// は、例えば次のように生成する。
属性
PHP 8 の属性を、すべてのクラス、メソッド、プロパティ、定数、enum case、関数、クロージャ、パラメータに追加することができます。リテラルもパラメータの値として使用できます。
$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,
) {
}
}
名前空間
クラス、trait、interface、enum(以下クラス)は、名前空間(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-statementsを定義することができます。
// 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 current namespace
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', because of 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') // it will simplify to A
->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass
メソッド->addComment('@return ' . $namespace->simplifyType('FooD')); // コメントで手動で簡略化する
$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // in comments simplify manually
$method->addParameter('arg')
->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass
echo $namespace;
// or use PsrPrinter for output conforming to 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 PsrPrinter for output conforming to 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 trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// creates a closure as specified
$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
ライブラリを直接使用するのがよい。
クラス・マニピュレーター.toc-class-manipulator
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);
// これで、あなたのクラスは SomeInterface を実装し、そのすべてのメソッドを含むようになった。
変数ダンパ
ダンパは、変数のパース可能な PHP 文字列表現を返します。ネイティブ関数var_export()
よりも、より良い、明確な出力を提供します。
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // prints ['a', 'b', 123]
互換性テーブル
PhpGenerator 4.0と4.1はPHP 8.0から8.3と互換性があります。