Generator PHP kode
- Zna vse najnovejše funkcije v PHP (kot so property hooks, enumi, atributi itd.)
- Omogoča vam enostavno spreminjanje obstoječih razredov
- Izhodna koda je v skladu s PSR-12 / PER coding style
- Zrela, stabilna in široko uporabljana knjižnica
Namestitev
Knjižnico prenesete in namestite z orodjem Composer:
composer require nette/php-generator
Združljivost s PHP najdete v tabeli.
Razredi
Začnimo kar s primerom ustvarjanja razreda z uporabo ClassType:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Opis razreda.\nDruga vrstica\n")
->addComment('@property-read Nette\Forms\Form $form');
// kodo enostavno generirate s pretvorbo v niz ali z uporabo echo:
echo $class;
Vrne naslednji rezultat:
/**
* Opis razreda
* Druga vrstica
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
Za generiranje kode lahko uporabimo tudi t.i. printer, ki ga bomo za razliko od echo $class
lahko nadalje konfigurirali:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Lahko dodamo konstante (razred Constant) in spremenljivke (razred Property):
$class->addConstant('ID', 123)
->setProtected() // vidnost konstant
->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'
Generira:
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('Preštej.')
->setFinal()
->setProtected()
->setReturnType('?int') // povratni tipi pri metodah
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
Rezultat je:
/**
* Preštej.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
Promovirane parametre, uvedene v PHP 8.0, lahko predate konstruktorju:
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Rezultat je:
public function __construct(
public $name,
private $args = [],
) {
}
Lastnosti in razrede, namenjene samo za branje, lahko označite s funkcijo setReadOnly()
.
Če dodana lastnost, konstanta, metoda ali parameter že obstajajo, se vrže izjema.
Člane razreda lahko odstranite z uporabo removeProperty()
, removeConstant()
,
removeMethod()
ali removeParameter()
.
V razred lahko dodate tudi obstoječe objekte 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);
Lahko tudi klonirate obstoječe metode, lastnosti in konstante pod drugim imenom z uporabo cloneWithName()
:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
Vmesnik ali lastnost
Lahko ustvarjate 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
Naštevne tipe, ki jih prinaša PHP 8.1, lahko enostavno ustvarite takole: (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;
}
Lahko tudi definirate skalarne ekvivalente in tako ustvarite “backed” naštevni tip:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
Vsakemu case je mogoče dodati komentar ali atributy z uporabo addComment()
ali addAttribute()
.
Anonimni razredi
Kot ime predamo null
in imamo 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)
{
}
};
Globalne funkcije
Kodo funkcij generira 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;
}
Anonimne funkcije
Kodo anonimnih funkcij generira 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;
}
Skrajšane puščične funkcije
Lahko tudi izpišete skrajšano anonimno funkcijo z uporabo printerja:
$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
Signature metod in funkcij
Metode predstavlja razred Method. Lahko nastavite vidnost, povratno vrednost, dodate komentarje, atribute itd:
$method = $class->addMethod('count')
->addComment('Preštej.')
->setFinal()
->setProtected()
->setReturnType('?int');
Posamezne parametre predstavlja razred Parameter. Spet lahko nastavite vse mogoče lastnosti:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(array &$items = [])
Za definiranje t.i. variadics parametrov (ali tudi splat operator) služi setVariadic()
:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Generira:
function count(...$items)
{
}
Telesa metod in funkcij
Telo lahko predate naenkrat metodi setBody()
ali postopoma (po vrsticah) 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;
}
Lahko uporabite posebne ograde za enostavno vstavljanje spremenljivk.
Enostavni simboli za ograde ?
$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);
}
Ograda za variadic ...?
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
Rezultat:
function foo()
{
myfunc(1, 2, 3);
}
Lahko uporabite tudi imenovane parametre za PHP 8 z uporabo ...?:
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
Ograda se ubeži z uporabo poševnice \?
$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;
}
Printer in skladnost s PSR
Za generiranje PHP kode služi razred Printer:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // enako kot: echo $class
Zna generirati kodo vseh drugih elementov, ponuja metode kot so printFunction()
,
printNamespace()
, itd.
Na voljo je tudi razred PsrPrinter
, katerega izpis je v skladu s PSR-2 / PSR-12 / PER coding style:
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Potrebujete obnašanje prilagoditi po meri? Ustvarite svojo različico z dedovanjem razreda Printer
. Lahko
prekonfigurirate te spremenljivke:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// dolžina vrstice, po kateri pride do preloma vrstice
public int $wrapLength = 120;
// znak za zamik, lahko ga nadomesti zaporedje presledkov
public string $indentation = "\t";
// število praznih vrstic med lastnostmi
public int $linesBetweenProperties = 0;
// število praznih vrstic med metodami
public int $linesBetweenMethods = 2;
// število praznih vrstic med skupinami 'use statements' za razrede, funkcije in konstante
public int $linesBetweenUseTypes = 0;
// položaj odpirajočega zavitega oklepaja za funkcije in metode
public bool $bracesOnNextLine = true;
// postavite en parameter na eno vrstico, tudi če ima atribut ali je podprt
public bool $singleParameterOnOneLine = false;
// izpusti imenske prostore, ki ne vsebujejo nobenega razreda ali funkcije
public bool $omitEmptyNamespaces = true;
// ločilo med desnim oklepajem in povratnim tipom funkcij in metod
public string $returnTypeColon = ': ';
}
Kako in zakaj se pravzaprav razlikujeta standardni Printer
in PsrPrinter
? Zakaj v paketu ni samo en
printer, in sicer PsrPrinter
?
Standardni Printer
formatira kodo tako, kot to počnemo v celotnem Nette. Ker je Nette nastal veliko prej kot
PSR, in tudi zato, ker PSR dolga leta ni pravočasno zagotavljal standardov, ampak na primer šele z večletno zamudo po uvedbi
nove funkcije v PHP, je prišlo do tega, da se standard
kodiranja v nekaj manjših podrobnostih razlikuje. Večja razlika je le uporaba tabulatorjev namesto presledkov. Vemo, da
z uporabo tabulatorjev v naših projektih omogočamo prilagajanje širine, kar je za ljudi z okvaro vida nujno. Primer
manjše razlike je postavitev zavitega oklepaja na samostojno vrstico pri funkcijah in metodah, in to vedno. Priporočilo PSR se
nam zdi nelogično in vodi k zmanjšanju
preglednosti kode.
Tipi
Vsak tip ali union/intersection tip lahko predate kot niz, lahko pa uporabite tudi preddefinirane konstante za nativne tipe:
use Nette\PhpGenerator\Type;
$member->setType('array'); // ali Type::Array;
$member->setType('?array'); // ali Type::nullable(Type::Array);
$member->setType('array|string'); // ali 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
Z uporabo Literal
lahko predate poljubno PHP kodo, na primer za privzete vrednosti 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)
{
}
}
Lahko tudi predate parametre v Literal
in jih pustite formatirati v veljavno PHP kodo z uporabo ograd:
new Literal('substr(?, ?)', [$a, $b]);
// generira na primer: substr('hello', 5);
Literal, ki predstavlja ustvarjanje novega objekta, lahko enostavno generirate z metodo new
:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// generira na primer: new Demo(10, foo: 20)
Atributi
PHP 8 atribute lahko dodate vsem razredom, metodam, lastnostim, konstantam, enumom, funkcijam, closureom in parametrom. Kot vrednosti parametrov lahko uporabljate tudi literály.
$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,
) {
}
}
Property Hooks
Z uporabo property hooks (predstavljenih z razredom PropertyHook) lahko definirate operacije get in set za lastnosti, kar je funkcija, uvedena v PHP 8.4:
$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;
Generira:
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
Lastnosti in property hooks so lahko abstraktne ali končne:
$class->addProperty('id')
->setType('int')
->addHook('get')
->setAbstract();
$class->addProperty('role')
->setType('string')
->addHook('set', 'strtolower($value)')
->setFinal();
Asimetrična vidnost
PHP 8.4 uvaja asimetrično vidnost za lastnosti. Lahko nastavite različne ravni dostopa za branje in pisanje.
Vidnost lahko nastavite bodisi z metodo setVisibility()
z dvema parametroma, bodisi z setPublic()
,
setProtected()
ali setPrivate()
s parametrom mode
, ki določa, ali se vidnost nanaša na
branje ali pisanje lastnosti. Privzeti način je 'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // public za branje, private za pisanje
$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected za pisanje
echo $class;
Generira:
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
Imenski prostor
Razrede, lastnosti, vmesnike in naštevne tipe (v nadaljevanju razredi) lahko združimo v imenske prostore, ki jih predstavlja razred PhpNamespace:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// ustvarimo nove razrede v imenskem prostoru
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// ali vstavimo obstoječi razred v imenski prostor
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Če razred že obstaja, se vrže izjema.
Lahko definirate klavzule 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');
Če želite poenostaviti polno kvalificirano ime razreda, funkcije ali konstante glede na definirane aliase, uporabite metodo
simplifyName
:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ker je 'Foo' trenutni imenski prostor
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', zaradi definiranega use-statement
Poenostavljeno ime razreda, funkcije ali konstante lahko nasprotno pretvorite v polno kvalificirano ime z metodo
resolveName
:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Prevodi imen razredov
Ko je razred del imenskega prostora, je izrisan nekoliko drugače: vsi tipi (na primer typehinty, povratni tipi, ime starševskega razreda, implementirani vmesniki, uporabljene lastnosti in atributi) se samodejno prevajajo (razen če to izklopite, glejte spodaj). To pomeni, da morate v definicijah uporabljati polna imena razredov in ta bodo v končni kodi nadomeščena z aliasi (glede na klavzule use) ali s polno kvalificiranimi imeni:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // bo poenostavljen na A
->addTrait('Bar\AliasedClass'); // bo poenostavljen na AliasedClass
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // v komentarjih poenostavimo ročno
$method->addParameter('arg')
->setType('Bar\OtherClass'); // bo preveden 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)
{
}
}
Samodejno prevajanje lahko izklopite na ta način:
$printer = new Nette\PhpGenerator\Printer; // ali PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
PHP datoteke
Razrede, funkcije in imenske prostore lahko združimo v PHP datoteke, ki jih predstavlja razred PhpFile:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('Ta datoteka je samodejno generirana.');
$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
/**
* Ta datoteka je samodejno generirana.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Opozorilo: V datoteke ni mogoče dodajati nobene druge kode razen funkcij in razredov.
Generiranje po obstoječih
Poleg tega, da lahko razrede in funkcije modelirate z zgoraj opisanim API-jem, jih lahko pustite tudi samodejno generirati po obstoječih vzorcih:
// ustvari razred enak kot razred PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// ustvari funkcijo enako funkciji trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// ustvari closure po navedeni
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
Telesa funkcij in metod so privzeto prazna. Če jih želite tudi naložiti, uporabite ta način (zahteva namestitev paketa
nikic/php-parser
):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Nalaganje iz PHP datotek
Funkcije, razrede, vmesnike in enume lahko nalagate tudi neposredno iz niza, ki vsebuje PHP kodo. Na primer, tako ustvarimo
objekt ClassType
:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Pri nalaganju razredov iz PHP kode so enovrstični komentarji zunaj teles metod prezrti (npr. pri lastnostih itd.), ker ta knjižnica nima API-ja za delo z njimi.
Lahko tudi naložite neposredno celotno PHP datoteko, ki lahko vsebuje poljubno število razredov, funkcij ali celo imenskih prostorov:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Naloži se tudi uvodni komentar k datoteki in deklaracija strict_types
. Nasprotno pa je vsa ostala globalna koda
prezrta.
Zahteva se, da je nameščen nikic/php-parser
.
Če morate manipulirati z globalno kodo v datotekah ali s posameznimi stavki v telesih metod, je bolje
uporabiti neposredno knjižnico nikic/php-parser
.
Class Manipulator
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 starševskega razreda ali implementiranega vmesnika v vaš razred. To vam
omogoča, da prepišete metodo ali razširite njeno signaturo:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
```
Metoda `inheritProperty()` kopira lastnost iz starševskega razreda v vaš razred. To je uporabno, ko želite v svojem razredu imeti enako lastnost, vendar morda z drugačno privzeto vrednostjo:
```php
$property = $manipulator->inheritProperty('foo');
$property->setValue('nova vrednost');
Metoda implement()
samodejno implementira vse metode in lastnosti iz danega vmesnika ali abstraktnega razreda
v vašem razredu:
$manipulator->implement(SomeInterface::class);
// Zdaj vaš razred implementira SomeInterface in vsebuje vse njegove metode
Izpis spremenljivk
Razred Dumper
pretvori spremenljivko v razčlenljivo PHP kodo. Zagotavlja boljši in preglednejši izpis kot
standardna funkcija var_export()
.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // izpiše ['a', 'b', 123]
Tabela združljivosti
PhpGenerator 4.1 je združljiv s PHP 8.0 do 8.4.