Nette PhpGenerator
- 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 InterfaceType i TraitType):
$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.