PHP kód generátor
- Ismeri az összes legújabb PHP funkciót (mint a property hookok, enumok, attribútumok stb.)
- Lehetővé teszi a meglévő osztályok egyszerű módosítását
- A kimeneti kód megfelel a PSR-12 / PER kódolási stílusnak
- Érett, stabil és széles körben használt könyvtár
Telepítés
A könyvtárat a Composer segítségével töltheti le és telepítheti:
composer require nette/php-generator
A PHP kompatibilitást a táblázatot találja.
Osztályok
Kezdjük rögtön egy példával egy osztály létrehozására a ClassType segítségével:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class
->setFinal()
->setExtends(ParentClass::class)
->addImplement(Countable::class)
->addComment("Osztály leírása.\nMásodik sor\n")
->addComment('@property-read Nette\Forms\Form $form');
// a kódot egyszerűen generálhatja stringgé alakítással vagy az echo használatával:
echo $class;
A következő eredményt adja vissza:
/**
* Osztály leírása
* Második sor
*
* @property-read Nette\Forms\Form $form
*/
final class Demo extends ParentClass implements Countable
{
}
A kód generálásához használhatunk egy ún. printert is, amelyet az echo $class
-szal ellentétben tovább konfigurálni tudunk:
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class);
Hozzáadhatunk konstansokat (Constant osztály) és propertyket (Property osztály):
$class->addConstant('ID', 123)
->setProtected() // konstansok láthatósága
->setType('int')
->setFinal();
$class->addProperty('items', [1, 2, 3])
->setPrivate() // vagy setVisibility('private')
->setStatic()
->addComment('@var int[]');
$class->addProperty('list')
->setType('?array')
->setInitialized(); // kiírja '= null'
Generálja:
final protected const int ID = 123;
/** @var int[] */
private static $items = [1, 2, 3];
public ?array $list = null;
És hozzáadhatunk metódusokat:
$method = $class->addMethod('count')
->addComment('Számold meg.')
->setFinal()
->setProtected()
->setReturnType('?int') // visszatérési típusok metódusoknál
->setBody('return count($items ?: $this->items);');
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
Az eredmény:
/**
* Számold meg.
*/
final protected function count(array &$items = []): ?int
{
return count($items ?: $this->items);
}
A PHP 8.0 által bevezetett promoted paramétereket átadhatjuk a konstruktornak:
$method = $class->addMethod('__construct');
$method->addPromotedParameter('name');
$method->addPromotedParameter('args', [])
->setPrivate();
Az eredmény:
public function __construct(
public $name,
private $args = [],
) {
}
A csak olvasható propertyket és osztályokat a setReadOnly()
függvénnyel lehet megjelölni.
Ha a hozzáadott property, konstans, metódus vagy paraméter már létezik, kivétel dobódik.
Az osztály tagjait eltávolíthatjuk a removeProperty()
, removeConstant()
,
removeMethod()
vagy removeParameter()
segítségével.
Az osztályhoz hozzáadhatunk meglévő Method
, Property
vagy Constant
objektumokat is:
$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);
Klónozhatunk meglévő metódusokat, propertyket és konstansokat más néven a cloneWithName()
segítségével:
$methodCount = $class->getMethod('count');
$methodRecount = $methodCount->cloneWithName('recount');
$class->addMember($methodRecount);
Interfész vagy Trait
Létrehozhat interfészeket és traitteket (InterfaceType és TraitType osztályok):
$interface = new Nette\PhpGenerator\InterfaceType('MyInterface');
$trait = new Nette\PhpGenerator\TraitType('MyTrait');
Trait használata:
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addTrait('SmartObject');
$class->addTrait('MyTrait')
->addResolution('sayHello as protected')
->addComment('@use MyTrait<Foo>');
echo $class;
Eredmény:
class Demo
{
use SmartObject;
/** @use MyTrait<Foo> */
use MyTrait {
sayHello as protected;
}
}
Enumok
A PHP 8.1 által bevezetett enumokat könnyen létrehozhatja így: (EnumType osztály):
$enum = new Nette\PhpGenerator\EnumType('Suit');
$enum->addCase('Clubs');
$enum->addCase('Diamonds');
$enum->addCase('Hearts');
$enum->addCase('Spades');
echo $enum;
Eredmény:
enum Suit
{
case Clubs;
case Diamonds;
case Hearts;
case Spades;
}
Definiálhat skaláris ekvivalenseket is, és létrehozhat egy “backed” enumot:
$enum->addCase('Clubs', '♣');
$enum->addCase('Diamonds', '♦');
Minden case-hez hozzáadhat kommentet vagy #Attribútumok a addComment()
vagy
addAttribute()
segítségével.
Névtelen osztályok
Névként null
-t adunk át, és máris van egy névtelen osztályunk:
$class = new Nette\PhpGenerator\ClassType(null);
$class->addMethod('__construct')
->addParameter('foo');
echo '$obj = new class ($val) ' . $class . ';';
Eredmény:
$obj = new class ($val) {
public function __construct($foo)
{
}
};
Globális függvények
A függvények kódját a GlobalFunction osztály generálja:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('return $a + $b;');
$function->addParameter('a');
$function->addParameter('b');
echo $function;
// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);
Eredmény:
function foo($a, $b)
{
return $a + $b;
}
Névtelen függvények
A névtelen függvények kódját a Closure osztály generálja:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('return $a + $b;');
$closure->addParameter('a');
$closure->addParameter('b');
$closure->addUse('c')
->setReference();
echo $closure;
// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);
Eredmény:
function ($a, $b) use (&$c) {
return $a + $b;
}
Rövidített nyíl függvények
Kiírhat egy rövidített névtelen függvényt is a printer segítségével:
$closure = new Nette\PhpGenerator\Closure;
$closure->setBody('$a + $b');
$closure->addParameter('a');
$closure->addParameter('b');
echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure);
Eredmény:
fn($a, $b) => $a + $b
Metódus és függvény szignatúrák
A metódusokat a Method osztály reprezentálja. Beállíthatja a láthatóságot, a visszatérési értéket, hozzáadhat kommenteket, attribútumokat stb.:
$method = $class->addMethod('count')
->addComment('Számold meg.')
->setFinal()
->setProtected()
->setReturnType('?int');
Az egyes paramétereket a Parameter osztály reprezentálja. Ismét beállíthat minden elképzelhető tulajdonságot:
$method->addParameter('items', []) // $items = []
->setReference() // &$items = []
->setType('array'); // array &$items = []
// function count(array &$items = [])
Az ún. variadics paraméterek (vagy splat operátor) definiálására a setVariadic()
szolgál:
$method = $class->addMethod('count');
$method->setVariadic(true);
$method->addParameter('items');
Generálja:
function count(...$items)
{
}
Metódus és függvény törzsek
A törzset átadhatjuk egyszerre a setBody()
metódusnak, vagy fokozatosan (soronként) az addBody()
ismételt hívásával:
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('$a = rand(10, 20);');
$function->addBody('return $a;');
echo $function;
Eredmény
function foo()
{
$a = rand(10, 20);
return $a;
}
Speciális helyettesítő karaktereket használhat a változók egyszerű beillesztéséhez.
Egyszerű helyettesítő szimbólumok ?
$str = 'any string';
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addBody('return substr(?, ?);', [$str, $num]);
echo $function;
Eredmény
function foo()
{
return substr('any string', 3);
}
Helyettesítő karakter variadic-hoz ...?
$items = [1, 2, 3];
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->setBody('myfunc(...?);', [$items]);
echo $function;
Eredmény:
function foo()
{
myfunc(1, 2, 3);
}
Használhat névvel ellátott paramétereket is a PHP 8-hoz a ...?:
segítségével
$items = ['foo' => 1, 'bar' => true];
$function->setBody('myfunc(...?:);', [$items]);
// myfunc(foo: 1, bar: true);
A helyettesítő szimbólumot a \
karakterrel lehet escapelni \?
$num = 3;
$function = new Nette\PhpGenerator\GlobalFunction('foo');
$function->addParameter('a');
$function->addBody('return $a \? 10 : ?;', [$num]);
echo $function;
Eredmény:
function foo($a)
{
return $a ? 10 : 3;
}
Printer és PSR megfelelőség
A PHP kód generálására a Printer osztály szolgál:
$class = new Nette\PhpGenerator\ClassType('Demo');
// ...
$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // ugyanaz, mint: echo $class
Képes generálni az összes többi elem kódját, kínál metódusokat, mint a printFunction()
,
printNamespace()
, stb.
Rendelkezésre áll a PsrPrinter
osztály is, amelynek kimenete megfelel a PSR-2 / PSR-12 / PER kódolási
stílusnak:
$printer = new Nette\PhpGenerator\PsrPrinter;
echo $printer->printClass($class);
Szeretné testre szabni a viselkedést? Hozzon létre saját verziót a Printer
osztály öröklésével. Ezeket a
változókat lehet újrakonfigurálni:
class MyPrinter extends Nette\PhpGenerator\Printer
{
// sor hossza, amely után sortörés történik
public int $wrapLength = 120;
// behúzás karaktere, helyettesíthető szóközök sorozatával
public string $indentation = "\t";
// üres sorok száma a propertyk között
public int $linesBetweenProperties = 0;
// üres sorok száma a metódusok között
public int $linesBetweenMethods = 2;
// üres sorok száma az 'use statements' csoportok között osztályokhoz, függvényekhez és konstansokhoz
public int $linesBetweenUseTypes = 0;
// nyitó kapcsos zárójel pozíciója függvényeknél és metódusoknál
public bool $bracesOnNextLine = true;
// helyezzen egy paramétert egy sorba, még akkor is, ha attribútuma van vagy promoted
public bool $singleParameterOnOneLine = false;
// kihagyja azokat a névtereket, amelyek nem tartalmaznak osztályt vagy függvényt
public bool $omitEmptyNamespaces = true;
// elválasztó a jobb zárójel és a függvények és metódusok visszatérési típusa között
public string $returnTypeColon = ': ';
}
Hogyan és miért különbözik valójában a standard Printer
és a PsrPrinter
? Miért nincs csak
egy printer a csomagban, mégpedig a PsrPrinter
?
A standard Printer
úgy formázza a kódot, ahogyan azt az egész Nette-ben tesszük. Mivel a Nette sokkal
korábban jött létre, mint a PSR, és mivel a PSR évekig nem szállított időben szabványokat, hanem például többéves
késéssel az új PHP funkció bevezetése után, előfordult, hogy a kódolási szabvány néhány apróságban eltér. A nagyobb
különbség csak a tabulátorok használata szóközök helyett. Tudjuk, hogy a tabulátorok használata projektjeinkben
lehetővé teszi a szélesség testreszabását, ami látássérültek számára
elengedhetetlen. Egy apró eltérés példája a kapcsos zárójel elhelyezése külön sorban a függvényeknél és
metódusoknál, és ez mindig így van. A PSR ajánlása számunkra logikátlannak tűnik, és a kód olvashatóságának
csökkenéséhez vezet.
Típusok
Minden típust vagy union/intersection típust átadhatunk stringként, használhatunk előre definiált konstansokat is a natív típusokhoz:
use Nette\PhpGenerator\Type;
$member->setType('array'); // vagy Type::Array;
$member->setType('?array'); // vagy Type::nullable(Type::Array);
$member->setType('array|string'); // vagy Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // vagy Type::intersection(Foo::class, Bar::class)
$member->setType(null); // eltávolítja a típust
Ugyanez vonatkozik a setReturnType()
metódusra is.
Literálok
A Literal
segítségével tetszőleges PHP kódot adhatunk át, például propertyk vagy paraméterek
alapértelmezett értékeihez stb.:
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;
Eredmény:
class Demo
{
public $foo = Iterator::SELF_FIRST;
public function bar($id = 1 + 2)
{
}
}
Paramétereket is átadhat a Literal
-nak, és hagyhatja, hogy érvényes PHP kóddá formázza őket helyettesítő karaktereket használva:
new Literal('substr(?, ?)', [$a, $b]);
// generál például: substr('hello', 5);
Egy új objektum létrehozását reprezentáló literált könnyen generálhatunk a new
metódussal:
Literal::new(Demo::class, [$a, 'foo' => $b]);
// generál például: new Demo(10, foo: 20)
Attribútumok
A PHP 8 attribútumokat hozzáadhatja az összes osztályhoz, metódushoz, propertyhez, konstanshoz, enumhoz, függvényhez, closure-höz és paraméterhez. Paraméterértékként #Literálok is használhatók.
$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;
Eredmény:
#[Table(name: 'user', constraints: [new UniqueConstraint(name: 'ean', columns: ['ean'])])]
class Demo
{
#[Deprecated]
public $list;
#[Foo\Cached(mode: true)]
public function count(
#[Bar]
$items,
) {
}
}
Property Hookok
A property hookok segítségével (PropertyHook osztály által reprezentálva) definiálhat get és set műveleteket a propertykhez, ami a PHP 8.4-ben bevezetett funkció:
$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;
Generálja:
class Demo
{
public string $firstName {
set(string $value) => strtolower($value);
get {
return ucfirst($this->firstName);
}
}
}
A propertyk és property hookok lehetnek absztraktak vagy finálak:
$class->addProperty('id')
->setType('int')
->addHook('get')
->setAbstract();
$class->addProperty('role')
->setType('string')
->addHook('set', 'strtolower($value)')
->setFinal();
Aszimmetrikus láthatóság
A PHP 8.4 bevezeti az aszimmetrikus láthatóságot a propertykhez. Különböző hozzáférési szinteket állíthat be az olvasáshoz és íráshoz.
A láthatóságot beállíthatja vagy a setVisibility()
metódussal két paraméterrel, vagy a
setPublic()
, setProtected()
vagy setPrivate()
metódusokkal a mode
paraméterrel, amely meghatározza, hogy a láthatóság az olvasásra vagy az írásra vonatkozik-e. Az alapértelmezett mód
'get'
.
$class = new Nette\PhpGenerator\ClassType('Demo');
$class->addProperty('name')
->setType('string')
->setVisibility('public', 'private'); // public olvasáshoz, private íráshoz
$class->addProperty('id')
->setType('int')
->setProtected('set'); // protected íráshoz
echo $class;
Generálja:
class Demo
{
public private(set) string $name;
protected(set) int $id;
}
Névtér
Az osztályokat, propertyket, interfészeket és enumokat (továbbiakban osztályok) csoportosíthatjuk névterekbe, amelyeket a PhpNamespace osztály reprezentál:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
// új osztályok létrehozása a névtérben
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');
// vagy meglévő osztály beillesztése a névtérbe
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);
Ha az osztály már létezik, kivétel dobódik.
Definiálhat use klózokat:
// 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');
Ha egyszerűsíteni szeretné a teljesen minősített osztály-, függvény- vagy konstansnevet a definiált aliasok szerint,
használja a simplifyName
metódust:
echo $namespace->simplifyName('Foo\Bar'); // 'Bar', mert a 'Foo' az aktuális névtér
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', a definiált use-statement miatt
Az egyszerűsített osztály-, függvény- vagy konstansnevet fordítva átalakíthatja teljesen minősített névre a
resolveName
metódussal:
echo $namespace->resolveName('Bar'); // 'Foo\Bar'
echo $namespace->resolveName('range', $namespace::NameFunction); // 'iter\range'
Osztálynevek fordítása
Ha egy osztály egy névtér része, kissé eltérően jelenik meg: minden típus (például typehintek, visszatérési típusok, szülőosztály neve, implementált interfészek, használt propertyk és attribútumok) automatikusan lefordításra kerül (hacsak nem kapcsolja ki, lásd alább). Ez azt jelenti, hogy a definíciókban teljes osztályneveket kell használnia, és ezeket aliasokra (a use klózok szerint) vagy teljesen minősített nevekre cseréli a végső kódban:
$namespace = new Nette\PhpGenerator\PhpNamespace('Foo');
$namespace->addUse('Bar\AliasedClass');
$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // A-ra lesz egyszerűsítve
->addTrait('Bar\AliasedClass'); // AliasedClass-ra lesz egyszerűsítve
$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // kommentekben manuálisan egyszerűsítünk
$method->addParameter('arg')
->setType('Bar\OtherClass'); // \Bar\OtherClass-ra lesz fordítva
echo $namespace;
// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);
Eredmény:
namespace Foo;
use Bar\AliasedClass;
class Demo implements A
{
use AliasedClass;
/**
* @return D
*/
public function method(\Bar\OtherClass $arg)
{
}
}
Az automatikus fordítást így lehet kikapcsolni:
$printer = new Nette\PhpGenerator\Printer; // vagy PsrPrinter
$printer->setTypeResolving(false);
echo $printer->printNamespace($namespace);
PHP fájlok
Az osztályokat, függvényeket és névtereket PHP fájlokba csoportosíthatjuk, amelyeket a PhpFile osztály reprezentál:
$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('Ez a fájl automatikusan generált.');
$file->setStrictTypes(); // hozzáadja declare(strict_types=1)
$class = $file->addClass('Foo\A');
$function = $file->addFunction('Foo\foo');
// vagy
// $namespace = $file->addNamespace('Foo');
// $class = $namespace->addClass('A');
// $function = $namespace->addFunction('foo');
echo $file;
// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szerinti kimenethez
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);
Eredmény:
<?php
/**
* Ez a fájl automatikusan generált.
*/
declare(strict_types=1);
namespace Foo;
class A
{
}
function foo()
{
}
Figyelmeztetés: A fájlokhoz nem lehet további kódot hozzáadni a függvényeken és osztályokon kívül.
Generálás meglévők alapján
Amellett, hogy az osztályokat és függvényeket a fent leírt API segítségével modellezheti, automatikusan is generáltathatja őket meglévő minták alapján:
// létrehoz egy osztályt, amely megegyezik a PDO osztállyal
$class = Nette\PhpGenerator\ClassType::from(PDO::class);
// létrehoz egy függvényt, amely azonos a trim() függvénnyel
$function = Nette\PhpGenerator\GlobalFunction::from('trim');
// létrehoz egy closure-t a megadott alapján
$closure = Nette\PhpGenerator\Closure::from(
function (stdClass $a, $b = null) {},
);
A függvények és metódusok törzsei alapértelmezés szerint üresek. Ha ezeket is be szeretné tölteni, használja ezt a
módszert (szükséges a nikic/php-parser
csomag telepítése):
$class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true);
$function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true);
Betöltés PHP fájlokból
Függvényeket, osztályokat, interfészeket és enumokat közvetlenül PHP kódot tartalmazó stringből is betölthet.
Például így hozunk létre egy ClassType
objektumot:
$class = Nette\PhpGenerator\ClassType::fromCode(<<<XX
<?php
class Demo
{
public $foo;
}
XX);
Ha osztályokat PHP kódból tölt be, az egysoros kommentek a metódustörzseken kívül (pl. propertyknél stb.) figyelmen kívül maradnak, mivel ez a könyvtár nem rendelkezik API-val a kezelésükhöz.
Közvetlenül betölthet egy teljes PHP fájlt is, amely tetszőleges számú osztályt, függvényt vagy akár névteret tartalmazhat:
$file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php'));
Betöltődik a fájl bevezető kommentje és a strict_types
deklaráció is. Ezzel szemben minden más globális
kód figyelmen kívül marad.
Szükséges, hogy a nikic/php-parser
telepítve legyen.
Ha globális kódot kell manipulálnia fájlokban vagy egyes utasításokat metódustörzsekben, jobb
közvetlenül a nikic/php-parser
könyvtárat használni.
Class Manipulator
A ClassManipulator osztály eszközöket biztosít az osztályok manipulálásához.
$class = new Nette\PhpGenerator\ClassType('Demo');
$manipulator = new Nette\PhpGenerator\ClassManipulator($class);
Az inheritMethod()
metódus átmásol egy metódust a szülőosztályból vagy implementált interfészből az Ön
osztályába. Ez lehetővé teszi a metódus felülírását vagy a szignatúrájának kiterjesztését:
$method = $manipulator->inheritMethod('bar');
$method->setBody('...');
```
Az `inheritProperty()` metódus átmásol egy propertyt a szülőosztályból az Ön osztályába. Ez akkor hasznos, ha ugyanazt a propertyt szeretné az osztályában, de esetleg más alapértelmezett értékkel:
```php
$property = $manipulator->inheritProperty('foo');
$property->setValue('new value');
Az implement()
metódus automatikusan implementálja az összes metódust és propertyt a megadott interfészből
vagy absztrakt osztályból az Ön osztályában:
$manipulator->implement(SomeInterface::class);
// Most az Ön osztálya implementálja a SomeInterface-t és tartalmazza annak összes metódusát
Változók kiírása
A Dumper
osztály átalakít egy változót elemezhető PHP kóddá. Jobb és áttekinthetőbb kimenetet
biztosít, mint a standard var_export()
függvény.
$dumper = new Nette\PhpGenerator\Dumper;
$var = ['a', 'b', 123];
echo $dumper->dump($var); // kiírja ['a', 'b', 123]
Kompatibilitási táblázat
A PhpGenerator 4.1 kompatibilis a PHP 8.0-tól 8.4-ig.