Nette Documentation Preview

syntax
Generator kodu PHP
******************

<div class=perex>
Szukasz narzędzia do generowania kodu PHP dla klas, funkcji lub całych plików?

- Obsługuje wszystkie najnowsze funkcje PHP (takie jak wyliczenia itp.)
- Pozwala na łatwą modyfikację istniejących klas
- Wyjście zgodne ze stylem kodowania PSR-12 / PER
- Dojrzała, stabilna i szeroko stosowana biblioteka
</div>


Instalacja .[#toc-installation]
-------------------------------

Pobierz i zainstaluj pakiet za pomocą [Composera |best-practices:composer]:

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

W celu uzyskania informacji o kompatybilności z PHP, patrz [tabela |#Compatibility Table].


Klasy .[#toc-classes]
---------------------

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

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

// aby wygenerować kod PHP wystarczy rzutować na string lub użyć echo:
echo $class;
```

Wyrenderuje on taki wynik:

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

Do wygenerowania kodu możemy również użyć drukarki, którą w przeciwieństwie do `echo $class`, będziemy mogli [dodatkowo skonfigurować |#Printers and PSR compliance]:

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

Możemy dodać 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() // stała widoczność
	->setType('int')
	->setFinal();

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

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

Generuje:

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

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

public ?array $list = null;
```

I możemy dodać [metody |#Method and Function Signature]:

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

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

Wynika z tego, że:

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

Parametry promowane wprowadzone przez PHP 8.0 mogą być przekazywane do konstruktora:

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

Skutkuje to:

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

Właściwości i klasy readonly mogą być oznaczone poprzez `setReadOnly()`.

------

Jeśli dodana właściwość, stała, metoda lub parametr już istnieje, rzuca wyjątek.

Członków można usunąć używając `removeProperty()`, `removeConstant()`, `removeMethod()` lub `removeParameter()`.

Możesz również dodać do klasy istniejące obiekty `Method`, `Property` lub `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);
```

Możesz sklonować istniejące metody, właściwości i stałe z inną nazwą używając `cloneWithName()`:

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


Interface lub Trait .[#toc-interface-or-trait]
----------------------------------------------

Można tworzyć interfejsy i cechy (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');
```

Wykorzystanie cech:

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

Wynik:

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


Enums .[#toc-enums]
-------------------

Możesz łatwo stworzyć enum, które przynosi PHP 8.1 (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:

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

Możesz również zdefiniować skalarne odpowiedniki dla przypadków, aby utworzyć backed enum:

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

Możliwe jest dodanie komentarza lub [atrybutów |#attributes] do każdego przypadku za pomocą `addComment()` lub `addAttribute()`.


Klasa anonimowa .[#toc-anonymous-class]
---------------------------------------

Podaj `null` jako nazwę i masz klasę anonimową:

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

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

Wynik:

```php
$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};
```


Funkcja globalna .[#toc-global-function]
----------------------------------------

Kod funkcji wygeneruje 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;

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

Result:

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


Zamknięcie .[#toc-closure]
--------------------------

Kod zamknięcia wygeneruje klasę [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;

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

Wynik:

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


Funkcja strzałki .[#toc-arrow-function]
---------------------------------------

Możesz również wydrukować zamknięcie jako funkcję strzałki za pomocą drukarki:

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

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

Wynik:

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


Sygnatura metody i funkcji .[#toc-method-and-function-signature]
----------------------------------------------------------------

Metody są reprezentowane przez klasę [Method |api:Nette\PhpGenerator\Method]. Możesz ustawić widoczność, wartość zwrotną, dodać komentarze, [atrybuty |#Attributes] itp:

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

Każdy parametr jest reprezentowany przez klasę [Parameter |api:Nette\PhpGenerator\Parameter]. Ponownie, możesz ustawić każdą możliwą właściwość:

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

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

Aby zdefiniować tzw. parametry variadics (lub również operator splat, spread, elipsa, rozpakowywanie czy trzy kropki), należy użyć `setVariadics()`:

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

Generates:

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


Metoda i ciało funkcji .[#toc-method-and-function-body]
-------------------------------------------------------

Ciało może być przekazane do metody `setBody()` jednorazowo lub sekwencyjnie (linia po linii) poprzez wielokrotne wywołanie `addBody()`:

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

Result

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

Możesz użyć specjalnych zamienników dla poręcznego sposobu wstrzykiwania zmiennych.

Proste symbole miejsc `?`

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

Wynik:

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

Variadic placeholder `...?`

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

Wynik:

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

Możesz również użyć PHP 8 nazwanych parametrów używając placeholder `...?:`

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

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

Ucieknij od placeholder używając slash `\?`

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

Wynik:

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


Drukarki i zgodność z PSR .[#toc-printers-and-psr-compliance]
-------------------------------------------------------------

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

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

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

Może generować kod dla wszystkich innych elementów, oferując metody takie jak `printFunction()`, `printNamespace()`, itp.

Dodatkowo dostępna jest klasa `PsrPrinter`, której dane wyjściowe są zgodne ze stylem kodowania PSR-2 / PSR-12 / PER:

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

Chcesz dostosować zachowanie do swoich potrzeb? Utwórz własną drukarkę, dziedzicząc z klasy `Printer`. Możesz ponownie skonfigurować te zmienne:

```php
class MyPrinter extends Nette\PhpGenerator\Printer
{
	// długość linii, po której linia zostanie przerwana
	public int $wrapLength = 120;
	// znak wcięcia, może być zastąpiony ciągiem 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 instrukcji użycia dla klas, funkcji i stałych
	public int $linesBetweenUseTypes = 0;
	// pozycja nawiasu klamrowego otwierającego dla funkcji i metod
	public bool $bracesOnNextLine = true;
	// umieszczenie jednego parametru w jednej linii, nawet jeśli ma atrybut lub jest promowany
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// separator między prawym nawiasem a typem zwracanej funkcji i metody
	public string $returnTypeColon = ': ';
}
```

Czym i dlaczego dokładnie różnią się standardowe `Printer` i `PsrPrinter`? Dlaczego w pakiecie nie ma tylko jednej drukarki, `PsrPrinter`?

Standard `Printer` formatuje kod tak, jak robimy to we wszystkich Nette. Ponieważ Nette powstało znacznie wcześniej niż PSR, a także dlatego, że PSR przez wiele lat nie dostarczało standardów na czas, ale czasami nawet z kilkuletnim opóźnieniem od wprowadzenia nowej funkcji w PHP, spowodowało to kilka drobnych różnic w [standardzie kodowania |contributing:coding-standard].
Większą różnicą jest właśnie używanie tabulatorów zamiast spacji. Wiemy, że stosując tabulatory w naszych projektach umożliwiamy dostosowanie szerokości, co jest [istotne dla osób z wadami wzroku |contributing:coding-standard#Tabs Instead of Spaces].
Przykładem drobnej różnicy jest umieszczenie nawiasu klamrowego w osobnej linii dla funkcji i metod oraz zawsze. Uważamy, że zalecenie PSR jest nielogiczne i [prowadzi do zmniejszenia przejrzystości kodu |contributing:coding-standard#Wrapping and Braces].


Typy .[#toc-types]
------------------

Każdy typ lub typ unii / przecięcia może być przekazany jako ciąg, możesz również użyć predefiniowanych stałych dla typów natywnych:

```php
use Nette\PhpGenerator\Type;

$member->setType('array'); // lub 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'); // lub Type::intersection(Foo::class, Bar::class)
$member->setType(null); // usuwa typ
```

To samo dotyczy metody `setReturnType()`.


Literały .[#toc-literals]
-------------------------

Dzięki `Literal` możesz przekazać dowolny kod PHP do, na przykład, domyślnych wartości właściwości lub parametrów itp:

```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)
	{
	}
}
```

Możesz również przekazać parametry do `Literal` i mieć je sformatowane w poprawny kod PHP za pomocą [specjalnych placeholderów |#method-and-function-body-generator]:

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

Literał reprezentujący utworzenie nowego obiektu jest łatwo generowany przez metodę `new`:

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


Atrybuty .[#toc-attributes]
---------------------------

Możesz dodać atrybuty PHP 8 do wszystkich klas, metod, właściwości, stałych, przypadków enum, funkcji, domknięć i parametrów. [Literały |#Literals] mogą być również używane jako wartości parametrów.

```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,
	) {
	}
}
```


Przestrzeń nazw .[#toc-namespace]
---------------------------------

Klasy, cechy, interfejsy i enum (dalej klasy) mogą być grupowane w przestrzenie nazw[(PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]):

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

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

// lub wstawić istniejącą klasę do przestrzeni nazw
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
```

Jeśli klasa już istnieje, to rzuca wyjątek.

Możesz zdefiniować oświadczenia o użyciu:

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

Aby uprościć w pełni kwalifikowaną nazwę klasy, funkcji lub stałej zgodnie ze zdefiniowanymi aliasami, użyj metody `simplifyName`:

```php
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ponieważ 'Foo' jest bieżącą przestrzenią nazw
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', z powodu zdefiniowanej deklaracji użycia
```

I odwrotnie, można przekształcić uproszczoną nazwę klasy, funkcji lub stałej na w pełni kwalifikowaną, używając metody `resolveName`:

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


Class Names Resolving (rozwiązywanie nazw klas) .[#toc-class-names-resolving]
-----------------------------------------------------------------------------

**Gdy klasa jest częścią przestrzeni nazw, jest renderowana nieco inaczej:** wszystkie typy (np. podpowiedzi typów, typy zwracane, nazwa klasy nadrzędnej, zaimplementowane interfejsy, używane cechy i atrybuty) są automatycznie *rozwiązywane* (chyba że to wyłączysz, patrz poniżej).
Oznacza to, że musisz używać **w pełni kwalifikowanych nazw klas** w definicjach, a zostaną one zastąpione aliasami (opartymi na klauzulach użycia) lub w pełni kwalifikowanymi nazwami w wynikowym kodzie:

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // to się uprości do A
	->addTrait('Bar\AliasedClass'); // uprości się do AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo')); // w komentarzach uprościć ręcznie
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // zostanie rozwiązany do klasy \Bar\Bar\Class

echo $namespace;

// lub użyć 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)
	{
	}
}
```

Auto-resolving można wyłączyć w ten sposób:

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


Pliki PHP. .[#toc-php-files]
----------------------------

Klasy, funkcje i przestrzenie nazw mogą być pogrupowane w pliki PHP reprezentowane 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');

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

echo $file;

// lub 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()
{
}
```

**Uwaga:** Do plików nie można dodawać żadnego dodatkowego kodu poza funkcjami i klasami.


Generowanie według istniejących .[#toc-generating-according-to-existing-ones]
-----------------------------------------------------------------------------

Oprócz możliwości modelowania klas i funkcji za pomocą opisanego powyżej API, można również zlecić ich automatyczne generowanie według istniejących:

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

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

// tworzy zamknięcie identyczne jak
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);
```

Ciała funkcji i metod są domyślnie puste. Jeśli chcesz je również załadować, użyj tego sposobu
(wymaga on zainstalowania `nikic/php-parser` ):

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

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


Ładowanie z pliku PHP .[#toc-loading-from-php-file]
---------------------------------------------------

Można też ładować funkcje, klasy, interfejsy i enumy bezpośrednio z ciągu kodu PHP. Na przykład tworzymy obiekt `ClassType` w ten sposób:

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

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

Podczas ładowania klas z kodu PHP, komentarze jednolinijkowe poza ciałami metod są ignorowane (np. dla właściwości itp.), ponieważ ta biblioteka nie posiada API do pracy z nimi.

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

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

Ładowany jest również początkowy komentarz do pliku oraz deklaracja `strict_types`. Z drugiej strony, wszystkie inne globalne kody są ignorowane.

Wymaga to zainstalowania `nikic/php-parser`.

.[note]
Jeśli musisz manipulować globalnym kodem w plikach lub pojedynczymi stwierdzeniami w ciałach metod, lepiej jest użyć bezpośrednio biblioteki `nikic/php-parser`.


Zrzutka zmiennych .[#toc-variables-dumper]
------------------------------------------

Dumper zwraca parsowalną reprezentację zmiennej w PHP. Zapewnia lepsze i bardziej przejrzyste wyjście niż funkcja natywna `var_export()`.

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

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

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


Tabela kompatybilności .[#toc-compatibility-table]
--------------------------------------------------

PhpGenerator 4.0 i 4.1 są kompatybilne z PHP 8.0 do 8.3.

{{leftbar: nette:@menu-topics}}

Generator kodu PHP

Szukasz narzędzia do generowania kodu PHP dla klas, funkcji lub całych plików?
  • Obsługuje wszystkie najnowsze funkcje PHP (takie jak wyliczenia itp.)
  • Pozwala na łatwą modyfikację istniejących klas
  • Wyjście zgodne ze stylem kodowania PSR-12 / PER
  • Dojrzała, stabilna i szeroko stosowana biblioteka

Instalacja

Pobierz i zainstaluj pakiet za pomocą Composera:

composer require nette/php-generator

W celu uzyskania informacji o kompatybilności z PHP, patrz tabela.

Klasy

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

// aby wygenerować kod PHP wystarczy rzutować na string lub użyć echo:
echo $class;

Wyrenderuje on taki wynik:

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

Do wygenerowania kodu możemy również użyć drukarki, którą w przeciwieństwie do echo $class, będziemy mogli dodatkowo skonfigurować:

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

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

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

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

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

Generuje:

final protected const int ID = 123;

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

public ?array $list = null;

I możemy dodać metody:

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

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

Wynika z tego, że:

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

Parametry promowane wprowadzone przez PHP 8.0 mogą być przekazywane do konstruktora:

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

Skutkuje to:

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

Właściwości i klasy readonly mogą być oznaczone poprzez setReadOnly().


Jeśli dodana właściwość, stała, metoda lub parametr już istnieje, rzuca wyjątek.

Członków można usunąć używając removeProperty(), removeConstant(), removeMethod() lub removeParameter().

Możesz również dodać do klasy istniejące obiekty Method, Property lub 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);

Możesz sklonować istniejące metody, właściwości i stałe z inną nazwą używając cloneWithName():

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

Interface lub Trait

Można tworzyć interfejsy i cechy (klasy InterfaceTypeTraitType):

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

Wykorzystanie cech:

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

Wynik:

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

Enums

Możesz łatwo stworzyć enum, które przynosi PHP 8.1 (klasa EnumType):

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

echo $enum;

Wynik:

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

Możesz również zdefiniować skalarne odpowiedniki dla przypadków, aby utworzyć backed enum:

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

Możliwe jest dodanie komentarza lub atrybutów do każdego przypadku za pomocą addComment() lub addAttribute().

Klasa anonimowa

Podaj null jako nazwę i masz klasę anonimową:

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

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

Wynik:

$obj = new class ($val) {

	public function __construct($foo)
	{
	}
};

Funkcja globalna

Kod funkcji wygeneruje klasa GlobalFunction:

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

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

Result:

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

Zamknięcie

Kod zamknięcia wygeneruje klasę Closure:

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

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

Wynik:

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

Funkcja strzałki

Możesz również wydrukować zamknięcie jako funkcję strzałki za pomocą drukarki:

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

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

Wynik:

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

Sygnatura metody i funkcji

Metody są reprezentowane przez klasę Method. Możesz ustawić widoczność, wartość zwrotną, dodać komentarze, atrybuty itp:

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

Każdy parametr jest reprezentowany przez klasę Parameter. Ponownie, możesz ustawić każdą możliwą właściwość:

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

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

Aby zdefiniować tzw. parametry variadics (lub również operator splat, spread, elipsa, rozpakowywanie czy trzy kropki), należy użyć setVariadics():

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

Generates:

function count(...$items)
{
}

Metoda i ciało funkcji

Ciało może być przekazane do metody setBody() jednorazowo lub sekwencyjnie (linia po linii) poprzez wielokrotne wywołanie addBody():

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

Result

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

Możesz użyć specjalnych zamienników dla poręcznego sposobu wstrzykiwania zmiennych.

Proste symbole miejsc ?

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

Wynik:

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

Variadic placeholder ...?

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

Wynik:

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

Możesz również użyć PHP 8 nazwanych parametrów używając placeholder ...?:

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

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

Ucieknij od placeholder używając slash \?

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

Wynik:

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

Drukarki i zgodność z PSR

Klasa Printer służy do generowania kodu PHP:

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

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

Może generować kod dla wszystkich innych elementów, oferując metody takie jak printFunction(), printNamespace(), itp.

Dodatkowo dostępna jest klasa PsrPrinter, której dane wyjściowe są zgodne ze stylem kodowania PSR-2 / PSR-12 / PER:

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

Chcesz dostosować zachowanie do swoich potrzeb? Utwórz własną drukarkę, dziedzicząc z klasy Printer. Możesz ponownie skonfigurować te zmienne:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// długość linii, po której linia zostanie przerwana
	public int $wrapLength = 120;
	// znak wcięcia, może być zastąpiony ciągiem 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 instrukcji użycia dla klas, funkcji i stałych
	public int $linesBetweenUseTypes = 0;
	// pozycja nawiasu klamrowego otwierającego dla funkcji i metod
	public bool $bracesOnNextLine = true;
	// umieszczenie jednego parametru w jednej linii, nawet jeśli ma atrybut lub jest promowany
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// separator między prawym nawiasem a typem zwracanej funkcji i metody
	public string $returnTypeColon = ': ';
}

Czym i dlaczego dokładnie różnią się standardowe Printer i PsrPrinter? Dlaczego w pakiecie nie ma tylko jednej drukarki, PsrPrinter?

Standard Printer formatuje kod tak, jak robimy to we wszystkich Nette. Ponieważ Nette powstało znacznie wcześniej niż PSR, a także dlatego, że PSR przez wiele lat nie dostarczało standardów na czas, ale czasami nawet z kilkuletnim opóźnieniem od wprowadzenia nowej funkcji w PHP, spowodowało to kilka drobnych różnic w standardzie kodowania. Większą różnicą jest właśnie używanie tabulatorów zamiast spacji. Wiemy, że stosując tabulatory w naszych projektach umożliwiamy dostosowanie szerokości, co jest istotne dla osób z wadami wzroku. Przykładem drobnej różnicy jest umieszczenie nawiasu klamrowego w osobnej linii dla funkcji i metod oraz zawsze. Uważamy, że zalecenie PSR jest nielogiczne i prowadzi do zmniejszenia przejrzystości kodu.

Typy

Każdy typ lub typ unii / przecięcia może być przekazany jako ciąg, możesz również użyć predefiniowanych stałych dla typów natywnych:

use Nette\PhpGenerator\Type;

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

To samo dotyczy metody setReturnType().

Literały

Dzięki Literal możesz przekazać dowolny kod PHP do, na przykład, domyślnych wartości właściwości lub parametrów itp:

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)
	{
	}
}

Możesz również przekazać parametry do Literal i mieć je sformatowane w poprawny kod PHP za pomocą specjalnych placeholderów:

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

Literał reprezentujący utworzenie nowego obiektu jest łatwo generowany przez metodę new:

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

Atrybuty

Możesz dodać atrybuty PHP 8 do wszystkich klas, metod, właściwości, stałych, przypadków enum, funkcji, domknięć i parametrów. Literały mogą być również używane jako wartości parametró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,
	) {
	}
}

Przestrzeń nazw

Klasy, cechy, interfejsy i enum (dalej klasy) mogą być grupowane w przestrzenie nazw(PhpNamespace):

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

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

// lub wstawić istniejącą klasę do przestrzeni nazw
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Jeśli klasa już istnieje, to rzuca wyjątek.

Możesz zdefiniować oświadczenia o użyciu:

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

Aby uprościć w pełni kwalifikowaną nazwę klasy, funkcji lub stałej zgodnie ze zdefiniowanymi aliasami, użyj metody simplifyName:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', ponieważ 'Foo' jest bieżącą przestrzenią nazw
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', z powodu zdefiniowanej deklaracji użycia

I odwrotnie, można przekształcić uproszczoną nazwę klasy, funkcji lub stałej na w pełni kwalifikowaną, używając metody resolveName:

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

Class Names Resolving (rozwiązywanie nazw klas)

Gdy klasa jest częścią przestrzeni nazw, jest renderowana nieco inaczej: wszystkie typy (np. podpowiedzi typów, typy zwracane, nazwa klasy nadrzędnej, zaimplementowane interfejsy, używane cechy i atrybuty) są automatycznie rozwiązywane (chyba że to wyłączysz, patrz poniżej). Oznacza to, że musisz używać w pełni kwalifikowanych nazw klas w definicjach, a zostaną one zastąpione aliasami (opartymi na klauzulach użycia) lub w pełni kwalifikowanymi nazwami w wynikowym kodzie:

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // to się uprości do A
	->addTrait('Bar\AliasedClass'); // uprości się do AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo')); // w komentarzach uprościć ręcznie
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // zostanie rozwiązany do klasy \Bar\Bar\Class

echo $namespace;

// lub użyć 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)
	{
	}
}

Auto-resolving można wyłączyć w ten sposób:

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

Pliki PHP.

Klasy, funkcje i przestrzenie nazw mogą być pogrupowane w pliki PHP reprezentowane 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');

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

echo $file;

// lub 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()
{
}

Uwaga: Do plików nie można dodawać żadnego dodatkowego kodu poza funkcjami i klasami.

Generowanie według istniejących

Oprócz możliwości modelowania klas i funkcji za pomocą opisanego powyżej API, można również zlecić ich automatyczne generowanie według istniejących:

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

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

// tworzy zamknięcie identyczne jak
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Ciała funkcji i metod są domyślnie puste. Jeśli chcesz je również załadować, użyj tego sposobu (wymaga on zainstalowania nikic/php-parser ):

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

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

Ładowanie z pliku PHP

Można też ładować funkcje, klasy, interfejsy i enumy bezpośrednio z ciągu kodu PHP. Na przykład tworzymy obiekt ClassType w ten sposób:

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

	class Demo
	{
		public $foo;
	}
	XX);

Podczas ładowania klas z kodu PHP, komentarze jednolinijkowe poza ciałami metod są ignorowane (np. dla właściwości itp.), ponieważ ta biblioteka nie posiada API do pracy z nimi.

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

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

Ładowany jest również początkowy komentarz do pliku oraz deklaracja strict_types. Z drugiej strony, wszystkie inne globalne kody są ignorowane.

Wymaga to zainstalowania nikic/php-parser.

Jeśli musisz manipulować globalnym kodem w plikach lub pojedynczymi stwierdzeniami w ciałach metod, lepiej jest użyć bezpośrednio biblioteki nikic/php-parser.

Zrzutka zmiennych

Dumper zwraca parsowalną reprezentację zmiennej w PHP. Zapewnia lepsze i bardziej przejrzyste wyjście niż funkcja natywna var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

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

Tabela kompatybilności

PhpGenerator 4.0 i 4.1 są kompatybilne z PHP 8.0 do 8.3.