Nette Documentation Preview

syntax
Nette PhpGenerator
******************

<div class=perex>
Szukasz narzędzia do generowania kodu PHP klas, funkcji albo kompletnych plików?

- Wspiera wszystkie najnowsze funkcje PHP (jak hooki właściwości, enumy, atrybuty itd.)
- Pozwala łatwo modyfikować istniejące klasy
- Wyjście zgodne ze stylem kodowania PSR-12 / PER
- Dojrzała, stabilna i szeroko używana biblioteka
</div>


Instalacja
----------

Pobierz i zainstaluj bibliotekę narzędziem [Composer|best-practices:composer]:

```shell
composer require nette/php-generator
```

Kompatybilność z PHP znajdziesz w [tabeli kompatybilności |#Tabela kompatybilności].


Klasy
-----

Zacznijmy od przykładu utworzenia klasy za pomocą [ClassType |api:Nette\PhpGenerator\ClassType]:

```php
$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');

// kod generujemy po prostu rzutowaniem na ciąg albo przez echo:
echo $class;
```

Zwróci to poniższy wynik:

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

Do wygenerowania kodu możesz też użyć printera, który w przeciwieństwie do `echo $class` da się [dalej skonfigurować |#Printer i zgodność z PSR]:

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

Możesz dodawać stałe (klasa [Constant |api:Nette\PhpGenerator\Constant]) i właściwości (klasa [Property |api:Nette\PhpGenerator\Property]):

```php
$class->addConstant('ID', 123)
	->setProtected() // widoczność stałej
	->setType('int')
	->setFinal();

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

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // wypisze '= null'
```

Generuje to:

```php
final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
```

A możesz dodawać [metody |#Sygnatury metod i funkcji]:

```php
$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // typy zwracane metod
	->setBody('return count($items ?: $this->items);');

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

Wynik to:

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

Parametry promowane wprowadzone w PHP 8.0 można przekazać konstruktorowi:

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

Wynik to:

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

Właściwości i klasy readonly można oznaczyć funkcją `setReadOnly()`.

------

Jeśli dodawana właściwość, stała, metoda albo trait już istnieje, rzucany jest wyjątek. Parametry są natomiast nadpisywane.

Elementy klasy można usuwać za pomocą `removeProperty()`, `removeConstant()`, `removeMethod()` albo `removeParameter()`.

Do klasy możesz też dodawać istniejące obiekty `Method`, `Property` albo `Constant`:

```php
$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);
```

Istniejące metody, właściwości i stałe możesz też klonować pod inną nazwą za pomocą `cloneWithName()`:

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


Interfejsy i traity
-------------------

Możesz tworzyć interfejsy i traity (klasy [InterfaceType |api:Nette\PhpGenerator\InterfaceType] i [TraitType |api:Nette\PhpGenerator\TraitType]):

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

Użycie traitu:

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

Wynik to:

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


Enumy
-----

Enumy wprowadzone w PHP 8.1 możesz łatwo utworzyć tak (klasa [EnumType |api:Nette\PhpGenerator\EnumType]):

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

echo $enum;
```

Wynik to:

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

Możesz też zdefiniować odpowiedniki skalarne i utworzyć backed enum:

```php
$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
```

Każdemu przypadkowi możesz dodać komentarz albo [atrybuty |#Atrybuty] za pomocą `addComment()` albo `addAttribute()`.


Klasy anonimowe
---------------

Przekaż jako nazwę `null`, a masz klasę anonimową:

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

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

Wynik to:

```php
$obj = new class ($val) {
	public function __construct($foo)
	{
	}
};
```


Funkcje globalne
----------------

Kod funkcji globalnych generuje klasa [GlobalFunction |api:Nette\PhpGenerator\GlobalFunction]:

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

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
```

Wynik to:

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


Funkcje anonimowe
-----------------

Kod funkcji anonimowych (domknięć) generuje klasa [Closure |api:Nette\PhpGenerator\Closure]:

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

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
```

Wynik to:

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


Krótkie funkcje strzałkowe
--------------------------

Za pomocą printera możesz wypisać też krótką funkcję strzałkową:

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

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

Wynik to:

```php
fn($a, $b) => $a + $b;
```


Sygnatury metod i funkcji
-------------------------

Metody reprezentuje klasa [Method |api:Nette\PhpGenerator\Method]. Możesz ustawić widoczność, typ zwracany, dodać komentarze, [atrybuty |#Atrybuty] itd.:

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

Poszczególne parametry reprezentuje klasa [Parameter |api:Nette\PhpGenerator\Parameter]. Znów możesz ustawić wszystkie wyobrażalne właściwości:

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

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

Do zdefiniowania parametrów wariadycznych (znanych też jako operator splat) służy `setVariadic()`:

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

Generuje to:

```php
function count(...$items)
{
}
```


Ciała metod i funkcji
---------------------

Ciało można przekazać naraz metodzie `setBody()` albo stopniowo (linia po linii) przez wielokrotne wywoływanie `addBody()`:

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

Wynik to:

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

Do łatwego wstawiania zmiennych możesz używać specjalnych zastępników.

Proste zastępniki `?`:

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

Wynik to:

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

Zastępnik dla wariadycznych `...?`:

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

Wynik to:

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

Możesz też użyć parametrów nazwanych dla PHP 8 za pomocą `...?:`:

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

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

Zastępnik escapuje się odwrotnym ukośnikiem `\?`:

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

Wynik to:

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


Printer i zgodność z PSR
------------------------

Do generowania kodu PHP służy klasa [Printer |api:Nette\PhpGenerator\Printer]:

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

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // to samo co: echo $class
```

Potrafi generować kod wszystkich pozostałych elementów, oferując metody jak `printFunction()`, `printNamespace()` itd.

Jest też klasa [PsrPrinter |api:Nette\PhpGenerator\PsrPrinter], której wyjście odpowiada stylowi kodowania PSR-2 / PSR-12 / PER:

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

Musisz dostosować zachowanie? Utwórz własną wersję, dziedzicząc po klasie `Printer`. Możesz przekonfigurować te zmienne:

```php
class MyPrinter extends Nette\PhpGenerator\Printer
{
	// długość linii, po której następuje zawijanie
	public int $wrapLength = 120;
	// znak wcięcia, można zastąpić sekwencją spacji
	public string $indentation = "\t";
	// liczba pustych linii między właściwościami
	public int $linesBetweenProperties = 0;
	// liczba pustych linii między metodami
	public int $linesBetweenMethods = 2;
	// liczba pustych linii między grupami 'use statement' dla klas, funkcji i stałych
	public int $linesBetweenUseTypes = 0;
	// pozycja otwierającego nawiasu klamrowego funkcji i metod
	public bool $bracesOnNextLine = true;
	// umieszcza pojedynczy parametr w jednej linii, nawet jeśli ma atrybut albo jest promowany
	public bool $singleParameterOnOneLine = false;
	// pomija przestrzenie nazw niezawierające żadnej klasy ani funkcji
	public bool $omitEmptyNamespaces = true;
	// umieszcza declare(strict_types) w tej samej linii co <?php
	public bool $declareOnOpenTag = false;
	// separator między prawym nawiasem a typem zwracanym funkcji i metod
	public string $returnTypeColon = ': ';
}
```

Jak i dlaczego standardowy `Printer` i `PsrPrinter` właściwie się różnią? Dlaczego w pakiecie nie ma tylko jednego printera, `PsrPrinter`?

Standardowy `Printer` formatuje kod tak, jak robimy to w całym Nette. Ponieważ Nette powstało znacznie wcześniej niż PSR, a także dlatego, że standardy PSR często pojawiały się z opóźnieniem (czasem lata po wprowadzeniu nowej funkcji PHP), [standard kodowania Nette |contributing:coding-standard] różni się w kilku drobnych szczegółach. Główną różnicą jest używanie tabulatorów zamiast spacji. Wiemy, że używanie tabulatorów w naszych projektach pozwala dostosować szerokość, co jest niezbędne dla [osób z wadami wzroku |contributing:coding-standard#Tabulatory zamiast spacji]. Przykładem drobnej różnicy jest umieszczanie otwierającego nawiasu klamrowego funkcji i metod zawsze w osobnej linii. Zalecenie PSR wydaje nam się nielogiczne i prowadzi do [zmniejszonej przejrzystości kodu |contributing:coding-standard#Łamanie linii i nawiasy].


Typy
----

Każdy typ albo typ unijny/przecięciowy można przekazać jako ciąg; możesz też używać predefiniowanych stałych dla typów natywnych:

```php
use Nette\PhpGenerator\Type;

$member->setType('array'); // albo Type::Array
$member->setType('?array'); // albo Type::nullable(Type::Array)
$member->setType('array|string'); // albo Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // albo Type::intersection(Foo::class, Bar::class)
$member->setType(null); // usuwa typ
```

To samo dotyczy metody `setReturnType()`.


Literały
--------

Za pomocą `Literal` możesz przekazać dowolny kod PHP, na przykład jako wartości domyślne właściwości albo parametrów:

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

Wynik:

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

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

`Literal` możesz też przekazać parametry i pozwolić sformatować je do poprawnego kodu PHP za pomocą [zastępników |#Ciała metod i funkcji]:

```php
new Literal('substr(?, ?)', [$a, $b]);
// generuje na przykład: substr('hello', 5)
```

Literał reprezentujący utworzenie nowego obiektu da się łatwo wygenerować metodą `new`:

```php
Literal::new(Demo::class, [$a, 'foo' => $b]);
// generuje na przykład: new Demo(10, foo: 20)
```


Atrybuty
--------

Atrybuty PHP 8 można dodawać do wszystkich klas, metod, właściwości, stałych, enumów, funkcji, domknięć i parametrów. Jako wartości parametrów można też używać [literałów |#Literały].

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

Wynik:

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


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


Hooki właściwości
-----------------

Za pomocą hooków właściwości (reprezentowanych przez klasę [PropertyHook|api:Nette\PhpGenerator\PropertyHook]) możesz zdefiniować operacje get i set dla właściwości, czyli funkcję wprowadzoną w PHP 8.4:

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

Generuje to:

```php
class Demo
{
    public string $firstName {
        set(string $value) => strtolower($value);
        get {
            return ucfirst($this->firstName);
        }
    }
}
```

Właściwości i hooki właściwości mogą być abstrakcyjne albo finalne:

```php
$class->addProperty('id')
    ->setType('int')
    ->addHook('get')
        ->setAbstract();

$class->addProperty('role')
    ->setType('string')
    ->addHook('set', 'strtolower($value)')
        ->setFinal();
```


Widoczność asymetryczna
-----------------------

PHP 8.4 wprowadza widoczność asymetryczną właściwości. Możesz ustawić różne poziomy dostępu dla odczytu i zapisu.

Widoczność można ustawić albo metodą `setVisibility()` z dwoma parametrami, albo za pomocą `setPublic()`, `setProtected()` czy `setPrivate()` z parametrem `mode` podającym, czy widoczność dotyczy odczytu, czy zapisu właściwości. Domyślnym trybem jest `'get'`.

```php
$class = new Nette\PhpGenerator\ClassType('Demo');

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public do odczytu, private do zapisu

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // protected do zapisu

echo $class;
```

Generuje to:

```php
class Demo
{
    public private(set) string $name;

    protected(set) int $id;
}
```


Przestrzeń nazw
---------------

Klasy, traity, interfejsy i enumy (dalej nazywane klasami) można grupować w przestrzeniach nazw reprezentowanych przez klasę [PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]:

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

// tworzymy nowe klasy w przestrzeni nazw
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// albo wstawiamy do przestrzeni nazw istniejącą klasę czy funkcję
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
```

Jeśli klasa o tej samej nazwie już w przestrzeni nazw istnieje, rzucany jest wyjątek.

Możesz definiować klauzule use:

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

Do uproszczenia w pełni kwalifikowanej nazwy klasy, funkcji albo stałej na podstawie zdefiniowanych aliasów albo bieżącej przestrzeni nazw służy metoda `simplifyName`:

```php
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', bo 'Foo' to bieżąca przestrzeń nazw
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', dzięki zdefiniowanemu use
```

Odwrotnie, uproszczoną nazwę klasy, funkcji albo stałej możesz przekształcić z powrotem w nazwę w pełni kwalifikowaną metodą `resolveName`:

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


Rozwiązywanie nazw klas
-----------------------

**Gdy klasa jest częścią przestrzeni nazw, renderowana jest nieco inaczej:** wszystkie typy (np. type hinty, typy zwracane, nazwa klasy nadrzędnej, implementowane interfejsy, używane traity i atrybuty) są automatycznie *rozwiązywane* (chyba że to wyłączysz, patrz niżej). Oznacza to, że w definicjach musisz **używać w pełni kwalifikowanych nazw klas**, a w wynikowym kodzie zostaną zastąpione aliasami (na podstawie klauzul use) albo nazwami uproszczonymi (jeśli są w tej samej przestrzeni nazw):

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // zostanie uproszczone do A
	->addTrait('Bar\AliasedClass'); // zostanie uproszczone do AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // w komentarzach upraszczamy ręcznie
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // zostanie przetłumaczone na \Bar\OtherClass

echo $namespace;

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
```

Wynik:

```php
namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

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

Automatyczne rozwiązywanie można wyłączyć tak:

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


Pliki PHP
---------

Klasy, funkcje i przestrzenie nazw można grupować w plikach PHP reprezentowanych przez klasę [PhpFile|api:Nette\PhpGenerator\PhpFile]:

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

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

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

echo $file;

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
```

Wynik:

```php
<?php

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

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}
```

Do pliku możesz też wstawiać istniejące obiekty klas, funkcji i przestrzeni nazw metodą `add()`:

```php
$file = new Nette\PhpGenerator\PhpFile;
$class = new Nette\PhpGenerator\ClassType('Demo');
$file->add($class);
```

**Zwróć uwagę:** do plików nie da się dodać żadnego dodatkowego kodu (jak `echo 'hello'`) poza funkcjami, klasami czy przestrzeniami nazw.


Generowanie z istniejących elementów
------------------------------------

Oprócz modelowania klas i funkcji za pomocą opisanego wyżej API możesz też pozwolić wygenerować je automatycznie na podstawie istniejących, za pomocą refleksji:

```php
// tworzy klasę identyczną z klasą PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// tworzy funkcję identyczną z funkcją trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// tworzy domknięcie na podstawie podanego
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);
```

Domyślnie ciała funkcji i metod są puste. Jeśli chcesz wczytać także je, użyj tej metody (wymaga zainstalowanego pakietu `nikic/php-parser`):

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

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


Wczytywanie z plików PHP
------------------------

Funkcje, klasy, interfejsy i enumy możesz też wczytywać bezpośrednio z ciągu zawierającego kod PHP. Na przykład żeby utworzyć obiekt `ClassType`:

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

	class Demo
	{
		public $foo;
	}
	XX);
```

Przy wczytywaniu klas z kodu PHP jednoliniowe komentarze poza ciałami metod (np. przy właściwościach) są ignorowane, bo ta biblioteka nie ma API do pracy z nimi.

Możesz też wczytać bezpośrednio cały plik PHP, który może zawierać dowolną liczbę klas, funkcji, a nawet przestrzeni nazw:

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

Wczytywany jest też początkowy komentarz pliku i deklaracja `strict_types`. Cały pozostały kod globalny jest natomiast ignorowany.

Wymaga zainstalowanego `nikic/php-parser`.

.[note]
Jeśli potrzebujesz manipulować kodem globalnym w plikach albo poszczególnymi instrukcjami wewnątrz ciał metod, lepiej użyj bezpośrednio biblioteki `nikic/php-parser`.


Manipulator klas
----------------

Klasa [ClassManipulator|api:Nette\PhpGenerator\ClassManipulator] daje narzędzia do manipulowania klasami.

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

Metoda `inheritMethod()` kopiuje metodę z klasy nadrzędnej albo implementowanego interfejsu do Twojej klasy. Pozwala to nadpisać metodę albo rozszerzyć jej sygnaturę:

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

Metoda `inheritProperty()` kopiuje właściwość z klasy nadrzędnej do Twojej klasy. Przydaje się, gdy chcesz mieć w swojej klasie tę samą właściwość, ale ewentualnie z inną wartością domyślną:

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

Metoda `implement()` automatycznie implementuje w Twojej klasie wszystkie abstrakcyjne metody i właściwości z podanego interfejsu albo klasy abstrakcyjnej:

```php
$manipulator->implement(SomeInterface::class);
// Teraz Twoja klasa implementuje SomeInterface i zawiera zaślepki wszystkich jego metod
```


Dumpowanie zmiennych
--------------------

Klasa [Dumper |api:Nette\PhpGenerator\Dumper] konwertuje zmienną na parsowalny kod PHP. Daje lepsze i przejrzystsze wyjście niż standardowa funkcja `var_export()`.

```php
$dumper = new Nette\PhpGenerator\Dumper;

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

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


Tabela kompatybilności
----------------------

PhpGenerator 4.2 jest kompatybilny z PHP od 8.1 do 8.5.


Jeśli aktualizujesz do nowszej wersji, zajrzyj na stronę [aktualizacji |upgrading].

Nette PhpGenerator

Szukasz narzędzia do generowania kodu PHP klas, funkcji albo kompletnych plików?
  • Wspiera wszystkie najnowsze funkcje PHP (jak hooki właściwości, enumy, atrybuty itd.)
  • Pozwala łatwo modyfikować istniejące klasy
  • Wyjście zgodne ze stylem kodowania PSR-12 / PER
  • Dojrzała, stabilna i szeroko używana biblioteka

Instalacja

Pobierz i zainstaluj bibliotekę narzędziem Composer:

composer require nette/php-generator

Kompatybilność z PHP znajdziesz w tabeli kompatybilności.

Klasy

Zacznijmy od przykładu utworzenia klasy za pomocą 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');

// kod generujemy po prostu rzutowaniem na ciąg albo przez echo:
echo $class;

Zwróci to poniższy wynik:

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

Do wygenerowania kodu możesz też użyć printera, który w przeciwieństwie do echo $class da się dalej skonfigurować:

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

Możesz dodawać stałe (klasa Constant) i właściwości (klasa Property):

$class->addConstant('ID', 123)
	->setProtected() // widoczność stałej
	->setType('int')
	->setFinal();

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

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

Generuje to:

final protected const int ID = 123;

/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;

A możesz dodawać metody:

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

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

Wynik to:

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

Parametry promowane wprowadzone w PHP 8.0 można przekazać konstruktorowi:

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

Wynik to:

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

Właściwości i klasy readonly można oznaczyć funkcją setReadOnly().


Jeśli dodawana właściwość, stała, metoda albo trait już istnieje, rzucany jest wyjątek. Parametry są natomiast nadpisywane.

Elementy klasy można usuwać za pomocą removeProperty(), removeConstant(), removeMethod() albo removeParameter().

Do klasy możesz też dodawać istniejące obiekty Method, Property albo 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);

Istniejące metody, właściwości i stałe możesz też klonować pod inną nazwą za pomocą cloneWithName():

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

Interfejsy i traity

Możesz tworzyć interfejsy i traity (klasy InterfaceTypeTraitType):

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

Użycie traitu:

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

Wynik to:

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

Enumy

Enumy wprowadzone w PHP 8.1 możesz łatwo utworzyć tak (klasa EnumType):

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

echo $enum;

Wynik to:

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

Możesz też zdefiniować odpowiedniki skalarne i utworzyć backed enum:

$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');

Każdemu przypadkowi możesz dodać komentarz albo atrybuty za pomocą addComment() albo addAttribute().

Klasy anonimowe

Przekaż jako nazwę null, a masz klasę anonimową:

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

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

Wynik to:

$obj = new class ($val) {
	public function __construct($foo)
	{
	}
};

Funkcje globalne

Kod funkcji globalnych generuje klasa GlobalFunction:

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

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

Wynik to:

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

Funkcje anonimowe

Kod funkcji anonimowych (domknięć) generuje klasa Closure:

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

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Wynik to:

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

Krótkie funkcje strzałkowe

Za pomocą printera możesz wypisać też krótką funkcję strzałkową:

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

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

Wynik to:

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

Sygnatury metod i funkcji

Metody reprezentuje klasa Method. Możesz ustawić widoczność, typ zwracany, dodać komentarze, atrybuty itd.:

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

Poszczególne parametry reprezentuje klasa Parameter. Znów możesz ustawić wszystkie wyobrażalne właściwości:

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

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

Do zdefiniowania parametrów wariadycznych (znanych też jako operator splat) służy setVariadic():

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

Generuje to:

function count(...$items)
{
}

Ciała metod i funkcji

Ciało można przekazać naraz metodzie setBody() albo stopniowo (linia po linii) przez wielokrotne wywoływanie addBody():

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

Wynik to:

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

Do łatwego wstawiania zmiennych możesz używać specjalnych zastępników.

Proste zastępniki ?:

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

Wynik to:

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

Zastępnik dla wariadycznych ...?:

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

Wynik to:

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

Możesz też użyć parametrów nazwanych dla PHP 8 za pomocą ...?::

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

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

Zastępnik escapuje się odwrotnym ukośnikiem \?:

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

Wynik to:

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

Printer i zgodność z PSR

Do generowania kodu PHP służy klasa Printer:

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

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // to samo co: echo $class

Potrafi generować kod wszystkich pozostałych elementów, oferując metody jak printFunction(), printNamespace() itd.

Jest też klasa PsrPrinter, której wyjście odpowiada stylowi kodowania PSR-2 / PSR-12 / PER:

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

Musisz dostosować zachowanie? Utwórz własną wersję, dziedzicząc po klasie Printer. Możesz przekonfigurować te zmienne:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// długość linii, po której następuje zawijanie
	public int $wrapLength = 120;
	// znak wcięcia, można zastąpić sekwencją spacji
	public string $indentation = "\t";
	// liczba pustych linii między właściwościami
	public int $linesBetweenProperties = 0;
	// liczba pustych linii między metodami
	public int $linesBetweenMethods = 2;
	// liczba pustych linii między grupami 'use statement' dla klas, funkcji i stałych
	public int $linesBetweenUseTypes = 0;
	// pozycja otwierającego nawiasu klamrowego funkcji i metod
	public bool $bracesOnNextLine = true;
	// umieszcza pojedynczy parametr w jednej linii, nawet jeśli ma atrybut albo jest promowany
	public bool $singleParameterOnOneLine = false;
	// pomija przestrzenie nazw niezawierające żadnej klasy ani funkcji
	public bool $omitEmptyNamespaces = true;
	// umieszcza declare(strict_types) w tej samej linii co <?php
	public bool $declareOnOpenTag = false;
	// separator między prawym nawiasem a typem zwracanym funkcji i metod
	public string $returnTypeColon = ': ';
}

Jak i dlaczego standardowy Printer i PsrPrinter właściwie się różnią? Dlaczego w pakiecie nie ma tylko jednego printera, PsrPrinter?

Standardowy Printer formatuje kod tak, jak robimy to w całym Nette. Ponieważ Nette powstało znacznie wcześniej niż PSR, a także dlatego, że standardy PSR często pojawiały się z opóźnieniem (czasem lata po wprowadzeniu nowej funkcji PHP), standard kodowania Nette różni się w kilku drobnych szczegółach. Główną różnicą jest używanie tabulatorów zamiast spacji. Wiemy, że używanie tabulatorów w naszych projektach pozwala dostosować szerokość, co jest niezbędne dla osób z wadami wzroku. Przykładem drobnej różnicy jest umieszczanie otwierającego nawiasu klamrowego funkcji i metod zawsze w osobnej linii. Zalecenie PSR wydaje nam się nielogiczne i prowadzi do zmniejszonej przejrzystości kodu.

Typy

Każdy typ albo typ unijny/przecięciowy można przekazać jako ciąg; możesz też używać predefiniowanych stałych dla typów natywnych:

use Nette\PhpGenerator\Type;

$member->setType('array'); // albo Type::Array
$member->setType('?array'); // albo Type::nullable(Type::Array)
$member->setType('array|string'); // albo Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // albo Type::intersection(Foo::class, Bar::class)
$member->setType(null); // usuwa typ

To samo dotyczy metody setReturnType().

Literały

Za pomocą Literal możesz przekazać dowolny kod PHP, na przykład jako wartości domyślne właściwości albo parametrów:

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;

Wynik:

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

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

Literal możesz też przekazać parametry i pozwolić sformatować je do poprawnego kodu PHP za pomocą zastępników:

new Literal('substr(?, ?)', [$a, $b]);
// generuje na przykład: substr('hello', 5)

Literał reprezentujący utworzenie nowego obiektu da się łatwo wygenerować metodą new:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// generuje na przykład: new Demo(10, foo: 20)

Atrybuty

Atrybuty PHP 8 można dodawać do wszystkich klas, metod, właściwości, stałych, enumów, funkcji, domknięć i parametrów. Jako wartości parametrów można też używać literałów.

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

Wynik:

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


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

Hooki właściwości

Za pomocą hooków właściwości (reprezentowanych przez klasę PropertyHook) możesz zdefiniować operacje get i set dla właściwości, czyli funkcję wprowadzoną w 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;

Generuje to:

class Demo
{
    public string $firstName {
        set(string $value) => strtolower($value);
        get {
            return ucfirst($this->firstName);
        }
    }
}

Właściwości i hooki właściwości mogą być abstrakcyjne albo finalne:

$class->addProperty('id')
    ->setType('int')
    ->addHook('get')
        ->setAbstract();

$class->addProperty('role')
    ->setType('string')
    ->addHook('set', 'strtolower($value)')
        ->setFinal();

Widoczność asymetryczna

PHP 8.4 wprowadza widoczność asymetryczną właściwości. Możesz ustawić różne poziomy dostępu dla odczytu i zapisu.

Widoczność można ustawić albo metodą setVisibility() z dwoma parametrami, albo za pomocą setPublic(), setProtected() czy setPrivate() z parametrem mode podającym, czy widoczność dotyczy odczytu, czy zapisu właściwości. Domyślnym trybem jest 'get'.

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

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public do odczytu, private do zapisu

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // protected do zapisu

echo $class;

Generuje to:

class Demo
{
    public private(set) string $name;

    protected(set) int $id;
}

Przestrzeń nazw

Klasy, traity, interfejsy i enumy (dalej nazywane klasami) można grupować w przestrzeniach nazw reprezentowanych przez klasę PhpNamespace:

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

// tworzymy nowe klasy w przestrzeni nazw
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// albo wstawiamy do przestrzeni nazw istniejącą klasę czy funkcję
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Jeśli klasa o tej samej nazwie już w przestrzeni nazw istnieje, rzucany jest wyjątek.

Możesz definiować klauzule 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');

Do uproszczenia w pełni kwalifikowanej nazwy klasy, funkcji albo stałej na podstawie zdefiniowanych aliasów albo bieżącej przestrzeni nazw służy metoda simplifyName:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', bo 'Foo' to bieżąca przestrzeń nazw
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', dzięki zdefiniowanemu use

Odwrotnie, uproszczoną nazwę klasy, funkcji albo stałej możesz przekształcić z powrotem w nazwę w pełni kwalifikowaną metodą resolveName:

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

Rozwiązywanie nazw klas

Gdy klasa jest częścią przestrzeni nazw, renderowana jest nieco inaczej: wszystkie typy (np. type hinty, typy zwracane, nazwa klasy nadrzędnej, implementowane interfejsy, używane traity i atrybuty) są automatycznie rozwiązywane (chyba że to wyłączysz, patrz niżej). Oznacza to, że w definicjach musisz używać w pełni kwalifikowanych nazw klas, a w wynikowym kodzie zostaną zastąpione aliasami (na podstawie klauzul use) albo nazwami uproszczonymi (jeśli są w tej samej przestrzeni nazw):

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // zostanie uproszczone do A
	->addTrait('Bar\AliasedClass'); // zostanie uproszczone do AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // w komentarzach upraszczamy ręcznie
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // zostanie przetłumaczone na \Bar\OtherClass

echo $namespace;

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Wynik:

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

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

Automatyczne rozwiązywanie można wyłączyć tak:

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

Pliki PHP

Klasy, funkcje i przestrzenie nazw można grupować w plikach PHP reprezentowanych przez klasę PhpFile:

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

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

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

echo $file;

// albo użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Wynik:

<?php

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

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Do pliku możesz też wstawiać istniejące obiekty klas, funkcji i przestrzeni nazw metodą add():

$file = new Nette\PhpGenerator\PhpFile;
$class = new Nette\PhpGenerator\ClassType('Demo');
$file->add($class);

Zwróć uwagę: do plików nie da się dodać żadnego dodatkowego kodu (jak echo 'hello') poza funkcjami, klasami czy przestrzeniami nazw.

Generowanie z istniejących elementów

Oprócz modelowania klas i funkcji za pomocą opisanego wyżej API możesz też pozwolić wygenerować je automatycznie na podstawie istniejących, za pomocą refleksji:

// tworzy klasę identyczną z klasą PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// tworzy funkcję identyczną z funkcją trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// tworzy domknięcie na podstawie podanego
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Domyślnie ciała funkcji i metod są puste. Jeśli chcesz wczytać także je, użyj tej metody (wymaga zainstalowanego pakietu nikic/php-parser):

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

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

Wczytywanie z plików PHP

Funkcje, klasy, interfejsy i enumy możesz też wczytywać bezpośrednio z ciągu zawierającego kod PHP. Na przykład żeby utworzyć obiekt ClassType:

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

	class Demo
	{
		public $foo;
	}
	XX);

Przy wczytywaniu klas z kodu PHP jednoliniowe komentarze poza ciałami metod (np. przy właściwościach) są ignorowane, bo ta biblioteka nie ma API do pracy z nimi.

Możesz też wczytać bezpośrednio cały plik PHP, który może zawierać dowolną liczbę klas, funkcji, a nawet przestrzeni nazw:

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

Wczytywany jest też początkowy komentarz pliku i deklaracja strict_types. Cały pozostały kod globalny jest natomiast ignorowany.

Wymaga zainstalowanego nikic/php-parser.

Jeśli potrzebujesz manipulować kodem globalnym w plikach albo poszczególnymi instrukcjami wewnątrz ciał metod, lepiej użyj bezpośrednio biblioteki nikic/php-parser.

Manipulator klas

Klasa ClassManipulator daje narzędzia do manipulowania klasami.

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

Metoda inheritMethod() kopiuje metodę z klasy nadrzędnej albo implementowanego interfejsu do Twojej klasy. Pozwala to nadpisać metodę albo rozszerzyć jej sygnaturę:

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

Metoda inheritProperty() kopiuje właściwość z klasy nadrzędnej do Twojej klasy. Przydaje się, gdy chcesz mieć w swojej klasie tę samą właściwość, ale ewentualnie z inną wartością domyślną:

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

Metoda implement() automatycznie implementuje w Twojej klasie wszystkie abstrakcyjne metody i właściwości z podanego interfejsu albo klasy abstrakcyjnej:

$manipulator->implement(SomeInterface::class);
// Teraz Twoja klasa implementuje SomeInterface i zawiera zaślepki wszystkich jego metod

Dumpowanie zmiennych

Klasa Dumper konwertuje zmienną na parsowalny kod PHP. Daje lepsze i przejrzystsze wyjście niż standardowa funkcja var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

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

Tabela kompatybilności

PhpGenerator 4.2 jest kompatybilny z PHP od 8.1 do 8.5.

Jeśli aktualizujesz do nowszej wersji, zajrzyj na stronę aktualizacji.