Generator kode PHP

Iščete orodje za ustvarjanje kode PHP za razrede, funkcije ali celotne datoteke?
  • Podpira vse najnovejše funkcije PHP (kot so enumi itd.)
  • Omogoča enostavno spreminjanje obstoječih razredov
  • Izhod je skladen s slogom kodiranja PSR-12 / PER
  • Zrela, stabilna in široko uporabljena knjižnica

Namestitev

Prenesite in namestite paket s programom Composer:

composer require nette/php-generator

Za združljivost s PHP glejte tabelo.

Razredi

Začnimo z enostavnim primerom generiranja razreda z uporabo 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');

// za generiranje kode PHP preprosto pretvorite v niz ali uporabite echo:
echo $class;

Rezultat bo naslednji:

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

Za generiranje kode lahko uporabimo tudi tiskalnik, ki ga bomo za razliko od echo $class lahko dodatno konfigurirali:

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

Dodamo lahko konstante (razred Constant) in lastnosti (razred Property):

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

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

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

Ustvari se:

final protected const int ID = 123;

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

public ?array $list = null;

In lahko dodamo metode:

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

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

Rezultat je:

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

Konstruktorju se lahko posredujejo promovirani parametri, ki jih je uvedel PHP 8.0:

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

Rezultat je:

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

Lastnosti in razrede, ki so namenjeni samo branju, lahko označite s spletno stranjo setReadOnly().


Če dodana lastnost, konstanta, metoda ali parameter že obstajajo, se vrže izjema.

Člane lahko odstranite z uporabo removeProperty(), removeConstant(), removeMethod() ali removeParameter().

Razredu lahko dodate tudi obstoječe predmete Method, Property ali 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);

Obstoječe metode, lastnosti in konstante lahko klonirate z drugim imenom z uporabo cloneWithName():

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

Vmesnik ali lastnost

Ustvarite lahko vmesnike in lastnosti (razreda InterfaceType in TraitType):

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

Uporaba lastnosti:

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

Rezultat:

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

Enumi

Enostavno lahko ustvarite enume, ki jih prinaša PHP 8.1 (razred EnumType):

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

echo $enum;

Rezultat:

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

Za ustvarjanje podprtega enuma lahko določite tudi skalarne ekvivalente za primere:

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

Vsakemu primeru je mogoče dodati komentar ali atribute z uporabo addComment() ali addAttribute().

Anonimni razred

Kot ime dajte null in dobili boste anonimni razred:

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

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

Rezultat:

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

Globalna funkcija

Koda funkcije bo ustvarila razred GlobalFunction:

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

// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

Rezultat:

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

Zaprtje

Koda zaprtja bo ustvarila razred Closure:

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

// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Rezultat:

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

Funkcija puščice

Zaključek lahko natisnete tudi s tiskalnikom kot funkcijo puščice:

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

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

Rezultat:

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

Podpis metode in funkcije

Metode so predstavljene z razredom Method. Nastavite lahko vidnost, povratno vrednost, dodate komentarje, atribute itd:

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

Vsak parameter je predstavljen z razredom Parameter. Spet lahko nastavite vse možne lastnosti:

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

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

Za določitev tako imenovanih parametrov variadics (ali tudi operatorjev splat, spread, elipsis, unpacking ali tri pike) uporabite setVariadic():

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

Generira:

function count(...$items)
{
}

Metoda in telo funkcije

Telo lahko metodi setBody() posredujete naenkrat ali zaporedno (vrstico za vrstico) z večkratnim klicem addBody():

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

Rezultat

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

Za priročen način injiciranja spremenljivk lahko uporabite posebna nadomestna imena.

Enostavna nadomestna imena ?

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

Rezultat:

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

Variadični nosilec mesta ...?

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

Rezultat:

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

Poimenovane parametre PHP 8 lahko uporabite tudi z uporabo nadomestnega imena ...?:

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

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

Namestno znamenje pobegnite s poševnico \?

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

Rezultat:

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

Tiskalniki in skladnost s predpisi PSR

Razred Printer se uporablja za ustvarjanje kode PHP:

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

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

Ustvarja lahko kodo za vse druge elemente in ponuja metode, kot so printFunction(), printNamespace() itd.

Poleg tega je na voljo razred PsrPrinter, katerega izpis je v skladu s slogom kodiranja PSR-2 / PSR-12 / PER:

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

Potrebujete natančno prilagoditi obnašanje svojim potrebam? Ustvarite svoj tiskalnik tako, da podedujete razred Printer. Te spremenljivke lahko ponovno konfigurirate:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// dolžina vrstice, po kateri se bo plastica prekinila
	public int $wrapLength = 120;
	// znak za odmik, lahko se nadomesti z zaporedjem presledkov
	public string $indentation = "\t";
	// število praznih vrstic med zadnjimi
	public int $linesBetweenProperties = 0;
	// število praznih vrstic med metodami
	public int $linesBetweenMethods = 2;
	// število praznih vrstic med skupinami izjav o uporabi za razrede, funkcije in konstante
	public int $linesBetweenUseTypes = 0;
	// položaj začetnega oklepaja za funkcije in metode
	public bool $bracesOnNextLine = true;
	// postavi en parameter v eno vrstico, tudi če ima atribut ali je povišan
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// ločilo med desnim oklepajem in tipom vrnitve funkcij in metod
	public string $returnTypeColon = ': ';
}

Kako in zakaj natančno se razlikujeta standardna Printer in PsrPrinter? Zakaj v paketu ni samo enega tiskalnika, PsrPrinter, ki bi se razlikoval?

Standardni Printer oblikuje kodo tako, kot jo oblikujemo v celotnem sistemu Nette. Ker je Nette nastal veliko prej kot PSR in tudi zato, ker PSR dolga leta ni pravočasno dostavljal standardov, temveč včasih celo z večletno zamudo od uvedbe nove funkcije v PHP, je to povzročilo nekaj manjših razlik v standardu kodiranja. Večja razlika je le uporaba tabulatorjev namesto presledkov. Vemo, da z uporabo tabulatorjev v naših projektih omogočamo prilagajanje širine, kar je bistvenega pomena za ljudi z okvarami vida. Primer manjše razlike je postavitev oglatega oklepaja v ločeno vrstico za funkcije in metode ter vedno. Priporočilo PSR se nam zdi nelogično in vodi k zmanjšanju jasnosti kode.

Tipi

Vsak tip ali tip zveze/intersekcije je mogoče posredovati kot niz, uporabite lahko tudi vnaprej določene konstante za domače tipe:

use Nette\PhpGenerator\Type;

$member->setType('array'); // ali 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'); // ali Type::intersection(Foo::class, Bar::class)
$member->setType(null); // odstrani tip

Enako velja za metodo setReturnType().

Literali

S Literal lahko poljubno kodo PHP posredujete na primer privzetim vrednostim lastnosti ali parametrov itd:

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;

Rezultat:

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

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

Rezultat: Na naslov Literal lahko posredujete tudi parametre, ki se s posebnimi nadomestki oblikujejo v veljavno PHP kodo:

new Literal('substr(?, ?)', [$a, $b]);
// generira, na primer: substr('hello', 5);

Literal, ki predstavlja ustvarjanje novega predmeta, se enostavno ustvari z metodo new:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// generira, na primer: new Demo(10, foo: 20)

Atributi

Vsem razredom, metodam, lastnostim, konstantam, imenskim primerom, funkcijam, zaključkom in parametrom lahko dodate atribute PHP 8. Kot vrednosti parametrov lahko uporabite tudi literale.

$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;

Rezultat:

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


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

Imenski prostor

Razrede, lastnosti, vmesnike in enume (v nadaljevanju razredi) lahko združimo v imenski prostor (PhpNamespace):

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

// ustvarjanje novih razredov v imenskem prostoru
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// ali vstavite obstoječi razred v imenski prostor.
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Če razred že obstaja, se vrže izjema.

Določite lahko izjave o uporabi:

// 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');

Če želite poenostaviti polno kvalificirano ime razreda, funkcije ali konstante v skladu z opredeljenimi vzdevki, uporabite metodo simplifyName:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ker je 'Foo' trenutni imenski prostor
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', ker je opredeljena izjava o uporabi

Poenostavljeno ime razreda, funkcije ali konstante pa lahko pretvorite v polno kvalificirano ime z uporabo metode resolveName:

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

Razreševanje imen razredov

Ko je razred del imenskega prostora, se prikaže nekoliko drugače: vsi tipi (npr. namigi na tip, tipi vrnitve, ime nadrejenega razreda, implementirani vmesniki, uporabljene lastnosti in atributi) se samodejno razrešijo (razen če to izklopite, glejte spodaj). To pomeni, da morate v definicijah uporabljati polno kvalificirana imena razredov, ki bodo v dobljeni kodi nadomeščena s približki (na podlagi klavzul use) ali polno kvalificiranimi imeni:

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // se poenostavi na A
	->addTrait('Bar\AliasedClass'); // se poenostavi na AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // v komentarjih poenostavite ročno
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // razreši se na \Bar\OtherClass

echo $namespace;

// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Rezultat:

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

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

Na ta način je mogoče izklopiti samodejno reševanje:

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

Datoteke PHP

Razrede, funkcije in prostore imen lahko združite v datoteke PHP, ki jih predstavlja razred PhpFile:

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

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

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

echo $file;

// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Rezultat:

<?php

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

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Upoštevajte: V datoteke ni mogoče dodajati dodatne kode zunaj funkcij in razredov.

Ustvarjanje glede na obstoječe

Poleg tega, da lahko razrede in funkcije modelirate z uporabo zgoraj opisanega API-ja, jih lahko tudi samodejno generirate na podlagi obstoječih:

// ustvari razred, ki je enak razredu PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// ustvari funkcijo, ki je enaka funkciji trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// ustvari zaprtje, kot je določeno
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Privzeto so telesa funkcij in metod prazna. Če jih želite tudi naložiti, uporabite ta način (zahteva namestitev spletne strani nikic/php-parser ):

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

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

Nalaganje iz datoteke PHP

Funkcije, razrede, vmesnike in enume lahko naložite tudi neposredno iz niza kode PHP. Tako na primer ustvarimo objekt ClassType:

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

	class Demo
	{
		public $foo;
	}
	XX);

Pri nalaganju razredov iz kode PHP se enovrstični komentarji zunaj teles metod ne upoštevajo (npr. za lastnosti itd.), ker ta knjižnica nima API za delo z njimi.

Neposredno lahko naložite tudi celotno datoteko PHP, ki lahko vsebuje poljubno število razredov, funkcij ali celo več imenskih prostorov:

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

Prav tako se naložita začetni komentar datoteke in deklaracija strict_types. Po drugi strani pa se vsa druga globalna koda ne upošteva.

Za to je treba namestiti nikic/php-parser.

Če morate upravljati globalno kodo v datotekah ali posamezne stavke v telesih metod, je bolje, da neposredno uporabite knjižnico nikic/php-parser.

Manipulator razreda

Razred ClassManipulator ponuja orodja za manipulacijo z razredi.

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

Metoda inheritMethod() kopira metodo iz nadrejenega razreda ali implementiranega vmesnika v vaš razred. To vam omogoča, da metodo prekrijete ali razširite njen podpis:

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

Metoda inheritProperty() kopira lastnost iz nadrejenega razreda v vaš razred. To je uporabno, kadar želite imeti isto lastnost v svojem razredu, vendar po možnosti z drugo privzeto vrednostjo:

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

Metoda implementInterface() samodejno implementira vse metode iz danega vmesnika v vaš razred:

$manipulator->implementInterface(SomeInterface::class);
// Zdaj vaš razred implementira SomeInterface in vključuje vse njegove metode

Zbiralnik spremenljivk

Dumper vrne razčlenljiv niz PHP za predstavitev spremenljivke. Zagotavlja boljši in jasnejši izpis kot nativna funkcija var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

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

Preglednica združljivosti

PhpGenerator 4.0 in 4.1 sta združljiva s PHP 8.0 do 8.3

različica: 4.0