Nette Documentation Preview

syntax
Generator de coduri PHP
***********************

<div class=perex>
Sunteți în căutarea unui instrument pentru a genera cod PHP pentru clase, funcții sau fișiere complete?

- Suportă toate cele mai recente caracteristici PHP (cum ar fi enums etc.)
- Vă permite să modificați cu ușurință clasele existente
- Ieșire conformă cu stilul de codare PSR-12 / PER
- Bibliotecă matură, stabilă și utilizată pe scară largă
</div>


Instalare .[#toc-installation]
------------------------------

Descărcați și instalați pachetul folosind [Composer |best-practices:composer]:

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

Pentru compatibilitatea PHP, consultați [tabelul |#Compatibility Table].


Clase .[#toc-classes]
---------------------

Să începem cu un exemplu simplu de generare a unei clase folosind [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');

// pentru a genera cod PHP, pur și simplu transformați în șir de caractere sau utilizați echo:
echo $class;
```

Acesta va reda acest rezultat:

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

De asemenea, putem folosi o imprimantă pentru a genera codul, pe care, spre deosebire de `echo $class`, o vom putea [configura ulterior |#Printers and PSR compliance]:

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

Putem adăuga constante (clasa [Constant |api:Nette\PhpGenerator\Constant]) și proprietăți (clasa [Property |api:Nette\PhpGenerator\Property]):

```php
$class->addConstant('ID', 123)
	->setProtected() // vizibilitate constantă
	->setType('int')
	->setFinal();

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

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // tipărește '= null'
```

Se generează:

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

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

public ?array $list = null;
```

Și putem adăuga [metode |#Method and Function Signature]:

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

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

Rezultă:

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

Parametrii promovați introduși de PHP 8.0 pot fi trecuți la constructor:

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

Aceasta are ca rezultat:

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

Proprietățile și clasele de citire exclusivă pot fi marcate prin intermediul `setReadOnly()`.

------

În cazul în care proprietatea, constanta, metoda sau parametrul adăugat există deja, se aruncă o excepție.

Membrii pot fi eliminați utilizând `removeProperty()`, `removeConstant()`, `removeMethod()` sau `removeParameter()`.

De asemenea, se pot adăuga la clasă obiecte existente `Method`, `Property` sau `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);
```

Puteți clona metodele, proprietățile și constantele existente cu un nume diferit folosind `cloneWithName()`:

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


Interfață sau trăsătură .[#toc-interface-or-trait]
--------------------------------------------------

Puteți crea interfețe și trăsături (clasele [InterfaceType |api:Nette\PhpGenerator\InterfaceType] și [TraitType |api:Nette\PhpGenerator\TraitType]):

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

Utilizarea trăsăturilor:

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

Rezultat:

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


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

Puteți crea cu ușurință enumerațiile pe care le aduce PHP 8.1 (clasa [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;
```

Rezultat:

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

De asemenea, puteți defini echivalenți scalari pentru cazuri pentru a crea un enum susținut:

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

Este posibil să se adauge un comentariu sau [atribute |#attributes] la fiecare caz folosind `addComment()` sau `addAttribute()`.


Clasa Anonymous .[#toc-anonymous-class]
---------------------------------------

Dați `null` ca nume și veți avea o clasă anonimă:

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

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

Rezultat:

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

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


Funcția globală .[#toc-global-function]
---------------------------------------

Codul de funcții va genera clasa [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;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
```

Rezultat:

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


Închidere .[#toc-closure]
-------------------------

Codul de închidere va genera clasa [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;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
```

Rezultat:

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


Funcția săgeată .[#toc-arrow-function]
--------------------------------------

De asemenea, puteți imprima închiderea ca funcție săgeată folosind imprimanta:

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

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

Rezultat:

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


Semnătura metodei și a funcției .[#toc-method-and-function-signature]
---------------------------------------------------------------------

Metodele sunt reprezentate de clasa [Method |api:Nette\PhpGenerator\Method]. Puteți seta vizibilitatea, valoarea de returnare, adăuga comentarii, [atribute |#Attributes] etc:

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

Fiecare parametru este reprezentat de o clasă [Parameter |api:Nette\PhpGenerator\Parameter]. Din nou, puteți seta toate proprietățile imaginabile:

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

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

Pentru a defini așa-numiții parametri variadici (sau, de asemenea, operatorul splat, spread, elipsis, unpacking sau trei puncte), utilizați `setVariadics()`:

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

Generates:

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


Metoda și corpul funcției .[#toc-method-and-function-body]
----------------------------------------------------------

Corpul poate fi transmis metodei `setBody()` o dată sau secvențial (linie cu linie) prin apelarea repetată a metodei `addBody()`:

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

Rezultat

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

Puteți utiliza caractere de poziție speciale pentru a injecta variabilele în mod practic.

Semne de poziție simple `?`

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

Rezultat:

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

Rezultat:

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

De asemenea, puteți utiliza parametrii numiți din PHP 8 folosind caractere de poziție `...?:`

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

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

Scăpați de locul rezervat utilizând slash `\?`

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

Rezultatul:

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


Imprimantele și conformitatea PSR .[#toc-printers-and-psr-compliance]
---------------------------------------------------------------------

Clasa [Printer |api:Nette\PhpGenerator\Printer] este utilizată pentru a genera cod PHP:

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

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // la fel ca: echo $class
```

Acesta poate genera cod pentru toate celelalte elemente, oferind metode precum `printFunction()`, `printNamespace()`, etc.

În plus, este disponibilă clasa `PsrPrinter`, a cărei ieșire este conformă cu stilul de codare PSR-2 / PSR-12 / PER:

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

Aveți nevoie să ajustați comportamentul la nevoile dumneavoastră? Creați-vă propria imprimantă moștenind din clasa `Printer`. Puteți reconfigura aceste variabile:

```php
class MyPrinter extends Nette\PhpGenerator\Printer
{
	// lungimea liniei după care se va întrerupe linia
	public int $wrapLength = 120;
	// caracter de indentare, poate fi înlocuit cu o secvență de spații
	public string $indentation = "\t";
	// numărul de linii goale între proprietăți
	public int $linesBetweenProperties = 0;
	// numărul de linii goale între metode
	public int $linesBetweenMethods = 2;
	// numărul de linii goale între grupuri de declarații de utilizare pentru clasă, funcții și constantă
	public int $linesBetweenUseTypes = 0;
	// poziția bretonului de deschidere pentru funcții și metode
	public bool $bracesOnNextLine = true;
	// plasează un parametru pe o singură linie, chiar dacă are un atribut sau este promovat
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// separator între paranteza dreaptă și tipul return al funcțiilor și metodelor
	public string $returnTypeColon = ': ';
}
```

Cum și de ce anume diferă standardul `Printer` și `PsrPrinter`? De ce nu există doar o singură imprimantă, `PsrPrinter`, în pachet?

Standardul `Printer` formatează codul așa cum o facem în toată Nette. Deoarece Nette a fost creat mult mai devreme decât PSR și, de asemenea, deoarece PSR timp de mulți ani nu a livrat standardele la timp, ci uneori chiar cu o întârziere de câțiva ani de la introducerea unei noi caracteristici în PHP, acest lucru a dus la câteva diferențe minore în [standardul de codare |contributing:coding-standard].
Cea mai mare diferență este doar utilizarea tabulatoarelor în loc de spații. Știm că, prin utilizarea tabulațiilor în proiectele noastre, permitem ajustarea lățimii, ceea ce este [esențial pentru persoanele cu deficiențe de vedere |contributing:coding-standard#Tabs Instead of Spaces].
Un exemplu de diferență minoră este plasarea acoladei curly brace pe o linie separată pentru funcții și metode și întotdeauna. Considerăm că recomandarea PSR este ilogică și [duce la o scădere a clarității codului |contributing:coding-standard#Wrapping and Braces].


Tipuri .[#toc-types]
--------------------

Fiecare tip sau tip de uniune/intersecție poate fi transmis ca un șir de caractere, puteți utiliza, de asemenea, constante predefinite pentru tipurile native:

```php
use Nette\PhpGenerator\Type;

$member->setType('array'); // sau 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'); // sau Type::intersection(Foo::class, Bar::class)
$member->setType(null); // elimină tipul
```

Același lucru este valabil și pentru metoda `setReturnType()`.


Literali .[#toc-literals]
-------------------------

Cu `Literal` puteți transmite cod PHP arbitrar, de exemplu, pentru valori implicite ale proprietăților sau parametrilor etc:

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

Rezultat:

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

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

Puteți, de asemenea, să transmiteți parametri la `Literal` și să formatați codul PHP valid folosind [caractere de poziție speciale |#method-and-function-body-generator]:

```php
new Literal('substr(?, ?)', [$a, $b]);
// generează, de exemplu: substr('hello', 5);
```

Literalul care reprezintă crearea unui nou obiect este generat cu ușurință prin metoda `new`:

```php
Literal::new(Demo::class, [$a, 'foo' => $b]);
// generează, de exemplu: new Demo(10, foo: 20)
```


Atribute .[#toc-attributes]
---------------------------

Puteți adăuga atribute PHP 8 la toate clasele, metodele, proprietățile, constantele, cazurile enum, funcțiile, închiderile și parametrii. [Literalii |#Literals] pot fi, de asemenea, utilizați ca valori de parametru.

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

Rezultat:

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


Spațiul de nume .[#toc-namespace]
---------------------------------

Clasele, trăsăturile, interfețele și enumerațiile (denumite în continuare clase) pot fi grupate în spații de nume ([PhpNamespace |api:Nette\PhpGenerator\PhpNamespace]):

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

// crearea de noi clase în spațiul de nume
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// sau inserați o clasă existentă în spațiul de nume
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
```

În cazul în care clasa există deja, se aruncă o excepție.

Puteți defini declarații de utilizare:

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

Pentru a simplifica un nume de clasă, funcție sau constantă complet calificat în conformitate cu aliasurile definite, utilizați metoda `simplifyName`:

```php
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', deoarece 'Foo' este spațiul de nume curent
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // "range", din cauza declarației de utilizare definite
```

În schimb, puteți converti un nume simplificat de clasă, funcție sau constantă într-un nume complet calificat utilizând metoda `resolveName`:

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


Rezolvarea numelor de clase .[#toc-class-names-resolving]
---------------------------------------------------------

**Când o clasă face parte dintr-un spațiu de nume, aceasta este redată în mod ușor diferit:** toate tipurile (de exemplu, indicii de tip, tipuri de returnare, numele clasei părinte, interfețe implementate, trăsături utilizate și atribute) sunt automat *rezolvate* (dacă nu le dezactivați, a se vedea mai jos).
Acest lucru înseamnă că trebuie să utilizați **nume de clasă complet calificate** în definiții, iar acestea vor fi înlocuite cu pseudonime (pe baza clauzelor de utilizare) sau cu nume complet calificate în codul rezultat:

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // se va simplifica la A
	->addTrait('Bar\AliasedClass'); // se va simplifica în AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // în comentarii simplificați manual
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // se va rezolva în \Bar\OtherClass

echo $namespace;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
```

Rezultat:

```php
namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

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

Rezolvarea automată poate fi dezactivată în acest mod:

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


Fișiere PHP .[#toc-php-files]
-----------------------------

Clasele, funcțiile și spațiile de nume pot fi grupate în fișiere PHP reprezentate de clasa [PhpFile |api:Nette\PhpGenerator\PhpFile]:

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

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

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

echo $file;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
```

Rezultat:

```php
<?php

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

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}
```

**Atenție:** Nu se poate adăuga niciun cod suplimentar la fișiere în afara funcțiilor și claselor.


Generarea în funcție de cele existente .[#toc-generating-according-to-existing-ones]
------------------------------------------------------------------------------------

Pe lângă posibilitatea de a modela clase și funcții utilizând API-ul descris mai sus, puteți, de asemenea, să le generați automat în funcție de cele existente:

```php
// creează o clasă identică cu clasa PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// creează o funcție identică cu trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// creează o închidere așa cum este specificat
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);
```

Corpurile funcțiilor și metodelor sunt goale în mod implicit. Dacă doriți să le încărcați și pe acestea, utilizați această modalitate
(necesită instalarea `nikic/php-parser` ):

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

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


Încărcare din fișierul PHP .[#toc-loading-from-php-file]
--------------------------------------------------------

De asemenea, puteți încărca funcții, clase, interfețe și enume direct dintr-un șir de cod PHP. De exemplu, creăm obiectul `ClassType` în acest mod:

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

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

La încărcarea claselor din codul PHP, comentariile pe o singură linie din afara corpului metodelor sunt ignorate (de exemplu, pentru proprietăți etc.), deoarece această bibliotecă nu dispune de un API pentru a lucra cu acestea.

De asemenea, puteți încărca direct întregul fișier PHP, care poate conține orice număr de clase, funcții sau chiar mai multe spații de nume:

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

Se încarcă, de asemenea, comentariul inițial al fișierului și declarația `strict_types`. Pe de altă parte, toate celelalte coduri globale sunt ignorate.

Acest lucru necesită instalarea `nikic/php-parser`.

.[note]
Dacă aveți nevoie să manipulați codul global din fișiere sau declarațiile individuale din corpurile metodelor, este mai bine să utilizați direct biblioteca `nikic/php-parser`.


Descărcător de variabile .[#toc-variables-dumper]
-------------------------------------------------

Dumper returnează o reprezentare de tip șir de caractere PHP analizabilă a unei variabile. Oferă o ieșire mai bună și mai clară decât funcția nativă `var_export()`.

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

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

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


Tabel de compatibilitate .[#toc-compatibility-table]
----------------------------------------------------

PhpGenerator 4.0 și 4.1 sunt compatibile cu PHP 8.0 până la 8.3

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

Generator de coduri PHP

Sunteți în căutarea unui instrument pentru a genera cod PHP pentru clase, funcții sau fișiere complete?
  • Suportă toate cele mai recente caracteristici PHP (cum ar fi enums etc.)
  • Vă permite să modificați cu ușurință clasele existente
  • Ieșire conformă cu stilul de codare PSR-12 / PER
  • Bibliotecă matură, stabilă și utilizată pe scară largă

Instalare

Descărcați și instalați pachetul folosind Composer:

composer require nette/php-generator

Pentru compatibilitatea PHP, consultați tabelul.

Clase

Să începem cu un exemplu simplu de generare a unei clase folosind 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');

// pentru a genera cod PHP, pur și simplu transformați în șir de caractere sau utilizați echo:
echo $class;

Acesta va reda acest rezultat:

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

De asemenea, putem folosi o imprimantă pentru a genera codul, pe care, spre deosebire de echo $class, o vom putea configura ulterior:

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

Putem adăuga constante (clasa Constant) și proprietăți (clasa Property):

$class->addConstant('ID', 123)
	->setProtected() // vizibilitate constantă
	->setType('int')
	->setFinal();

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

$class->addProperty('list')
	->setType('?array')
	->setInitialized(); // tipărește '= null'

Se generează:

final protected const const int ID = 123;

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

public ?array $list = null;

Și putem adăuga metode:

$method = $class->addMethod('count')
	->addComment('Count it.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // tipul de returnare a metodei
	->setBody('return count($items ?: $this->items);');

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

Rezultă:

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

Parametrii promovați introduși de PHP 8.0 pot fi trecuți la constructor:

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

Aceasta are ca rezultat:

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

Proprietățile și clasele de citire exclusivă pot fi marcate prin intermediul setReadOnly().


În cazul în care proprietatea, constanta, metoda sau parametrul adăugat există deja, se aruncă o excepție.

Membrii pot fi eliminați utilizând removeProperty(), removeConstant(), removeMethod() sau removeParameter().

De asemenea, se pot adăuga la clasă obiecte existente Method, Property sau 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);

Puteți clona metodele, proprietățile și constantele existente cu un nume diferit folosind cloneWithName():

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

Interfață sau trăsătură

Puteți crea interfețe și trăsături (clasele InterfaceType și TraitType):

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

Utilizarea trăsăturilor:

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

Enums

Puteți crea cu ușurință enumerațiile pe care le aduce PHP 8.1 (clasa 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;
}

De asemenea, puteți defini echivalenți scalari pentru cazuri pentru a crea un enum susținut:

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

Este posibil să se adauge un comentariu sau atribute la fiecare caz folosind addComment() sau addAttribute().

Clasa Anonymous

Dați null ca nume și veți avea o clasă anonimă:

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

Funcția globală

Codul de funcții va genera clasa GlobalFunction:

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

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

Rezultat:

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

Închidere

Codul de închidere va genera clasa Closure:

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

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Rezultat:

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

Funcția săgeată

De asemenea, puteți imprima închiderea ca funcție săgeată folosind imprimanta:

$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

Semnătura metodei și a funcției

Metodele sunt reprezentate de clasa Method. Puteți seta vizibilitatea, valoarea de returnare, adăuga comentarii, atribute etc:

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

Fiecare parametru este reprezentat de o clasă Parameter. Din nou, puteți seta toate proprietățile imaginabile:

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

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

Pentru a defini așa-numiții parametri variadici (sau, de asemenea, operatorul splat, spread, elipsis, unpacking sau trei puncte), utilizați setVariadics():

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

Generates:

function count(...$items)
{
}

Metoda și corpul funcției

Corpul poate fi transmis metodei setBody() o dată sau secvențial (linie cu linie) prin apelarea repetată a metodei 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;
}

Puteți utiliza caractere de poziție speciale pentru a injecta variabilele în mod practic.

Semne de poziție simple ?

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

Variadic placeholder ...?

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

Rezultat:

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

De asemenea, puteți utiliza parametrii numiți din PHP 8 folosind caractere de poziție ...?:

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

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

Scăpați de locul rezervat utilizând slash \?

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

Rezultatul:

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

Imprimantele și conformitatea PSR

Clasa Printer este utilizată pentru a genera cod PHP:

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

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // la fel ca: echo $class

Acesta poate genera cod pentru toate celelalte elemente, oferind metode precum printFunction(), printNamespace(), etc.

În plus, este disponibilă clasa PsrPrinter, a cărei ieșire este conformă cu stilul de codare PSR-2 / PSR-12 / PER:

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

Aveți nevoie să ajustați comportamentul la nevoile dumneavoastră? Creați-vă propria imprimantă moștenind din clasa Printer. Puteți reconfigura aceste variabile:

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// lungimea liniei după care se va întrerupe linia
	public int $wrapLength = 120;
	// caracter de indentare, poate fi înlocuit cu o secvență de spații
	public string $indentation = "\t";
	// numărul de linii goale între proprietăți
	public int $linesBetweenProperties = 0;
	// numărul de linii goale între metode
	public int $linesBetweenMethods = 2;
	// numărul de linii goale între grupuri de declarații de utilizare pentru clasă, funcții și constantă
	public int $linesBetweenUseTypes = 0;
	// poziția bretonului de deschidere pentru funcții și metode
	public bool $bracesOnNextLine = true;
	// plasează un parametru pe o singură linie, chiar dacă are un atribut sau este promovat
	public bool $singleParameterOnOneLine = false;
	// omits namespaces that do not contain any class or function
	public bool $omitEmptyNamespaces = true;
	// separator între paranteza dreaptă și tipul return al funcțiilor și metodelor
	public string $returnTypeColon = ': ';
}

Cum și de ce anume diferă standardul Printer și PsrPrinter? De ce nu există doar o singură imprimantă, PsrPrinter, în pachet?

Standardul Printer formatează codul așa cum o facem în toată Nette. Deoarece Nette a fost creat mult mai devreme decât PSR și, de asemenea, deoarece PSR timp de mulți ani nu a livrat standardele la timp, ci uneori chiar cu o întârziere de câțiva ani de la introducerea unei noi caracteristici în PHP, acest lucru a dus la câteva diferențe minore în standardul de codare. Cea mai mare diferență este doar utilizarea tabulatoarelor în loc de spații. Știm că, prin utilizarea tabulațiilor în proiectele noastre, permitem ajustarea lățimii, ceea ce este esențial pentru persoanele cu deficiențe de vedere. Un exemplu de diferență minoră este plasarea acoladei curly brace pe o linie separată pentru funcții și metode și întotdeauna. Considerăm că recomandarea PSR este ilogică și duce la o scădere a clarității codului.

Tipuri

Fiecare tip sau tip de uniune/intersecție poate fi transmis ca un șir de caractere, puteți utiliza, de asemenea, constante predefinite pentru tipurile native:

use Nette\PhpGenerator\Type;

$member->setType('array'); // sau 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'); // sau Type::intersection(Foo::class, Bar::class)
$member->setType(null); // elimină tipul

Același lucru este valabil și pentru metoda setReturnType().

Literali

Cu Literal puteți transmite cod PHP arbitrar, de exemplu, pentru valori implicite ale proprietăților sau parametrilor etc:

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

Puteți, de asemenea, să transmiteți parametri la Literal și să formatați codul PHP valid folosind caractere de poziție speciale:

new Literal('substr(?, ?)', [$a, $b]);
// generează, de exemplu: substr('hello', 5);

Literalul care reprezintă crearea unui nou obiect este generat cu ușurință prin metoda new:

Literal::new(Demo::class, [$a, 'foo' => $b]);
// generează, de exemplu: new Demo(10, foo: 20)

Atribute

Puteți adăuga atribute PHP 8 la toate clasele, metodele, proprietățile, constantele, cazurile enum, funcțiile, închiderile și parametrii. Literalii pot fi, de asemenea, utilizați ca valori de parametru.

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

Spațiul de nume

Clasele, trăsăturile, interfețele și enumerațiile (denumite în continuare clase) pot fi grupate în spații de nume (PhpNamespace):

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

// crearea de noi clase în spațiul de nume
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// sau inserați o clasă existentă în spațiul de nume
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

În cazul în care clasa există deja, se aruncă o excepție.

Puteți defini declarații de utilizare:

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

Pentru a simplifica un nume de clasă, funcție sau constantă complet calificat în conformitate cu aliasurile definite, utilizați metoda simplifyName:

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', deoarece 'Foo' este spațiul de nume curent
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // "range", din cauza declarației de utilizare definite

În schimb, puteți converti un nume simplificat de clasă, funcție sau constantă într-un nume complet calificat utilizând metoda resolveName:

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

Rezolvarea numelor de clase

Când o clasă face parte dintr-un spațiu de nume, aceasta este redată în mod ușor diferit: toate tipurile (de exemplu, indicii de tip, tipuri de returnare, numele clasei părinte, interfețe implementate, trăsături utilizate și atribute) sunt automat rezolvate (dacă nu le dezactivați, a se vedea mai jos). Acest lucru înseamnă că trebuie să utilizați nume de clasă complet calificate în definiții, iar acestea vor fi înlocuite cu pseudonime (pe baza clauzelor de utilizare) sau cu nume complet calificate în codul rezultat:

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // se va simplifica la A
	->addTrait('Bar\AliasedClass'); // se va simplifica în AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // în comentarii simplificați manual
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // se va rezolva în \Bar\OtherClass

echo $namespace;

// sau utilizați PsrPrinter pentru o ieșire conformă cu 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)
	{
	}
}

Rezolvarea automată poate fi dezactivată în acest mod:

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

Fișiere PHP

Clasele, funcțiile și spațiile de nume pot fi grupate în fișiere PHP reprezentate de clasa PhpFile:

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

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

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

echo $file;

// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Rezultat:

<?php

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

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Atenție: Nu se poate adăuga niciun cod suplimentar la fișiere în afara funcțiilor și claselor.

Generarea în funcție de cele existente

Pe lângă posibilitatea de a modela clase și funcții utilizând API-ul descris mai sus, puteți, de asemenea, să le generați automat în funcție de cele existente:

// creează o clasă identică cu clasa PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// creează o funcție identică cu trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// creează o închidere așa cum este specificat
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Corpurile funcțiilor și metodelor sunt goale în mod implicit. Dacă doriți să le încărcați și pe acestea, utilizați această modalitate (necesită instalarea nikic/php-parser ):

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

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

Încărcare din fișierul PHP

De asemenea, puteți încărca funcții, clase, interfețe și enume direct dintr-un șir de cod PHP. De exemplu, creăm obiectul ClassType în acest mod:

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

	class Demo
	{
		public $foo;
	}
	XX);

La încărcarea claselor din codul PHP, comentariile pe o singură linie din afara corpului metodelor sunt ignorate (de exemplu, pentru proprietăți etc.), deoarece această bibliotecă nu dispune de un API pentru a lucra cu acestea.

De asemenea, puteți încărca direct întregul fișier PHP, care poate conține orice număr de clase, funcții sau chiar mai multe spații de nume:

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

Se încarcă, de asemenea, comentariul inițial al fișierului și declarația strict_types. Pe de altă parte, toate celelalte coduri globale sunt ignorate.

Acest lucru necesită instalarea nikic/php-parser.

Dacă aveți nevoie să manipulați codul global din fișiere sau declarațiile individuale din corpurile metodelor, este mai bine să utilizați direct biblioteca nikic/php-parser.

Descărcător de variabile

Dumper returnează o reprezentare de tip șir de caractere PHP analizabilă a unei variabile. Oferă o ieșire mai bună și mai clară decât funcția nativă var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

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

Tabel de compatibilitate

PhpGenerator 4.0 și 4.1 sunt compatibile cu PHP 8.0 până la 8.3