Nette PhpGenerator

Vous cherchez un outil pour générer du code PHP de classes, de fonctions ou de fichiers complets ?
  • Prend en charge toutes les dernières fonctionnalités de PHP (property hooks, enums, attributs, etc.)
  • Vous permet de modifier facilement des classes existantes
  • Sortie conforme au style de codage PSR-12 / PER
  • Bibliothèque mature, stable et largement utilisée

Installation

Téléchargez et installez la bibliothèque à l'aide de l'outil Composer :

composer require nette/php-generator

Pour la compatibilité PHP, voir le tableau de compatibilité.

Classes

Commençons par un exemple de création d'une classe à l'aide de ClassType :

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

$class
	->setFinal()
	->setExtends(ParentClass::class)
	->addImplement(Countable::class)
	->addComment("Description de la classe.\nDeuxième ligne\n")
	->addComment('@property-read Nette\Forms\Form $form');

// générez le code simplement en convertissant en chaîne ou avec echo :
echo $class;

Cela renvoie le résultat suivant :

/**
 * Description de la classe.
 * Deuxième ligne
 *
 * @property-read Nette\Forms\Form $form
 */
final class Demo extends ParentClass implements Countable
{
}

Pour générer le code, vous pouvez aussi utiliser un printer qui, contrairement à echo $class, peut être configuré plus finement :

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

Vous pouvez ajouter des constantes (classe Constant) et des propriétés (classe Property) :

$class->addConstant('ID', 123)
	->setProtected() // visibilité de la constante
	->setType('int')
	->setFinal();

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

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

Cela génère :

final protected const int ID = 123;

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

Et vous pouvez ajouter des méthodes :

$method = $class->addMethod('count')
	->addComment('Compte-le.')
	->setFinal()
	->setProtected()
	->setReturnType('?int') // types de retour des méthodes
	->setBody('return count($items ?: $this->items);');

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

Le résultat est :

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

Les paramètres promus introduits par PHP 8.0 peuvent être passés au constructeur :

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

Le résultat est :

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

Les propriétés et les classes readonly peuvent être marquées à l'aide de la fonction setReadOnly().


Si une propriété, une constante, une méthode ou un trait ajouté existe déjà, une exception est levée. Les paramètres, en revanche, sont écrasés.

Les membres d'une classe peuvent être supprimés avec removeProperty(), removeConstant(), removeMethod() ou removeParameter().

Vous pouvez aussi ajouter à la classe des objets Method, Property ou Constant existants :

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

Vous pouvez aussi cloner des méthodes, propriétés et constantes existantes sous un autre nom à l'aide de cloneWithName() :

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

Interfaces et traits

Vous pouvez créer des interfaces et des traits (classes InterfaceType et TraitType) :

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

Utilisation d'un trait :

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

Le résultat est :

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

Enums

Vous pouvez facilement créer les enums introduits par PHP 8.1 de cette façon (classe EnumType) :

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

echo $enum;

Le résultat est :

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

Vous pouvez aussi définir des équivalents scalaires et créer un backed enum :

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

Pour chaque cas, vous pouvez ajouter un commentaire ou des Attributs à l'aide d'addComment() ou addAttribute().

Classes anonymes

Passez null comme nom et vous obtenez une classe anonyme :

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

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

Le résultat est :

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

Fonctions globales

Le code des fonctions globales est généré par la classe GlobalFunction :

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

// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function);

Le résultat est :

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

Fonctions anonymes

Le code des fonctions anonymes (closures) est généré par la classe Closure :

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

// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure);

Le résultat est :

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

Fonctions fléchées courtes

Vous pouvez aussi afficher une fonction fléchée courte à l'aide du printer :

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

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

Le résultat est :

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

Signatures des méthodes et des fonctions

Les méthodes sont représentées par la classe Method. Vous pouvez définir la visibilité, le type de retour, ajouter des commentaires, des Attributs, etc. :

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

Les différents paramètres sont représentés par la classe Parameter. Là encore, vous pouvez définir toutes les propriétés imaginables :

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

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

Pour définir des paramètres variadiques (aussi appelés opérateur splat), utilisez setVariadic() :

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

Cela génère :

function count(...$items)
{
}

Corps des méthodes et des fonctions

Le corps peut être passé d'un coup à la méthode setBody() ou progressivement (ligne par ligne) par des appels répétés à addBody() :

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

Le résultat est :

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

Vous pouvez utiliser des placeholders particuliers pour insérer facilement des variables.

Placeholders simples ? :

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

Le résultat est :

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

Placeholder variadique ...? :

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

Le résultat est :

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

Vous pouvez aussi utiliser les paramètres nommés de PHP 8 avec ...?: :

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

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

Le placeholder s'échappe par un antislash \? :

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

Le résultat est :

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

Printer et conformité PSR

La classe Printer sert à générer le code PHP :

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

$printer = new Nette\PhpGenerator\Printer;
echo $printer->printClass($class); // équivaut à : echo $class

Elle sait générer le code de tous les autres éléments et offre des méthodes comme printFunction(), printNamespace(), etc.

Il existe aussi la classe PsrPrinter, dont la sortie est conforme au style de codage PSR-2 / PSR-12 / PER :

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

Besoin d'adapter le comportement ? Créez votre propre version en héritant de la classe Printer. Vous pouvez reconfigurer ces variables :

class MyPrinter extends Nette\PhpGenerator\Printer
{
	// longueur de ligne au-delà de laquelle le retour à la ligne a lieu
	public int $wrapLength = 120;
	// caractère d'indentation, peut être remplacé par une suite d'espaces
	public string $indentation = "\t";
	// nombre de lignes vides entre les propriétés
	public int $linesBetweenProperties = 0;
	// nombre de lignes vides entre les méthodes
	public int $linesBetweenMethods = 2;
	// nombre de lignes vides entre les groupes de 'use statement' pour les classes, fonctions et constantes
	public int $linesBetweenUseTypes = 0;
	// position de l'accolade ouvrante des fonctions et méthodes
	public bool $bracesOnNextLine = true;
	// place un paramètre unique sur une seule ligne, même s'il a un attribut ou est promu
	public bool $singleParameterOnOneLine = false;
	// omet les espaces de noms qui ne contiennent aucune classe ni fonction
	public bool $omitEmptyNamespaces = true;
	// place declare(strict_types) sur la même ligne que <?php
	public bool $declareOnOpenTag = false;
	// séparateur entre la parenthèse fermante et le type de retour des fonctions et méthodes
	public string $returnTypeColon = ': ';
}

En quoi et pourquoi les Printer et PsrPrinter standards diffèrent-ils au juste ? Pourquoi n'y a-t-il pas dans le paquet un seul printer, PsrPrinter ?

Le Printer standard met en forme le code comme nous le faisons dans tout Nette. Parce que Nette est né bien avant PSR, et aussi parce que les standards PSR sont souvent arrivés tardivement (parfois des années après l'introduction d'une nouvelle fonctionnalité de PHP), le standard de codage de Nette diffère sur quelques détails mineurs. La principale différence est l'usage de tabulations plutôt que d'espaces. Nous savons que l'usage de tabulations dans nos projets permet d'en ajuster la largeur, ce qui est essentiel pour les personnes malvoyantes. Un exemple de différence mineure est le placement de l'accolade ouvrante sur une ligne distincte pour les fonctions et méthodes, toujours. La recommandation PSR nous paraît illogique et conduit à une moindre lisibilité du code.

Types

Tout type, y compris les types union et intersection, peut être passé sous forme de chaîne ; vous pouvez aussi utiliser les constantes prédéfinies des types natifs :

use Nette\PhpGenerator\Type;

$member->setType('array'); // ou Type::Array
$member->setType('?array'); // ou Type::nullable(Type::Array)
$member->setType('array|string'); // ou Type::union(Type::Array, Type::String)
$member->setType('Foo&Bar'); // ou Type::intersection(Foo::class, Bar::class)
$member->setType(null); // supprime le type

Il en va de même pour la méthode setReturnType().

Littéraux

À l'aide de Literal, vous pouvez passer n'importe quel code PHP, par exemple pour les valeurs par défaut des propriétés ou des paramètres :

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;

Résultat :

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

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

Vous pouvez aussi passer des paramètres à Literal et les faire mettre en forme en code PHP valide à l'aide des placeholders :

new Literal('substr(?, ?)', [$a, $b]);
// génère par exemple : substr('hello', 5)

Un littéral représentant la création d'un nouvel objet se génère facilement à l'aide de la méthode new :

Literal::new(Demo::class, [$a, 'foo' => $b]);
// génère par exemple : new Demo(10, foo: 20)

Attributs

Les attributs de PHP 8 peuvent être ajoutés à toutes les classes, méthodes, propriétés, constantes, enums, fonctions, closures et paramètres. Les Littéraux peuvent aussi être utilisés comme valeurs de paramètres.

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

Résultat :

#[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 hooks

À l'aide des property hooks (représentés par la classe PropertyHook), vous pouvez définir les opérations get et set des propriétés, une fonctionnalité introduite par 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;

Cela génère :

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

Les propriétés et les property hooks peuvent être abstraits ou finaux :

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

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

Visibilité asymétrique

PHP 8.4 introduit la visibilité asymétrique des propriétés. Vous pouvez définir des niveaux d'accès différents en lecture et en écriture.

La visibilité se définit soit par la méthode setVisibility() avec deux paramètres, soit par setPublic(), setProtected() ou setPrivate() avec le paramètre mode indiquant si la visibilité concerne la lecture ou l'écriture de la propriété. Le mode par défaut est 'get'.

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

$class->addProperty('name')
    ->setType('string')
    ->setVisibility('public', 'private'); // public en lecture, private en écriture

$class->addProperty('id')
    ->setType('int')
    ->setProtected('set'); // protected en écriture

echo $class;

Cela génère :

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

    protected(set) int $id;
}

Espace de noms

Les classes, traits, interfaces et enums (ci-après appelés classes) peuvent être regroupés dans des espaces de noms représentés par la classe PhpNamespace :

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

// crée de nouvelles classes dans l'espace de noms
$class = $namespace->addClass('Task');
$interface = $namespace->addInterface('Countable');
$trait = $namespace->addTrait('NameAware');

// ou insère une classe ou une fonction existante dans l'espace de noms
$class = new Nette\PhpGenerator\ClassType('Task');
$namespace->add($class);

Si une classe du même nom existe déjà dans l'espace de noms, une exception est levée.

Vous pouvez définir des clauses 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');

Pour simplifier un nom pleinement qualifié de classe, de fonction ou de constante d'après les alias définis ou l'espace de noms courant, utilisez la méthode simplifyName :

echo $namespace->simplifyName('Foo\Bar'); // 'Bar', car 'Foo' est l'espace de noms courant
echo $namespace->simplifyName('iter\range', $namespace::NameFunction); // 'range', grâce au use défini

Inversement, vous pouvez reconvertir un nom simplifié de classe, de fonction ou de constante en nom pleinement qualifié à l'aide de la méthode resolveName :

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

Résolution des noms de classes

Quand une classe fait partie d'un espace de noms, elle est rendue un peu différemment : tous les types (par exemple les déclarations de type, les types de retour, le nom de la classe parente, les interfaces implémentées, les traits utilisés et les attributs) sont automatiquement résolus (à moins que vous ne le désactiviez, voir plus bas). Cela signifie que vous devez utiliser des noms de classes pleinement qualifiés dans les définitions, et qu'ils seront remplacés dans le code obtenu par des alias (d'après les clauses use) ou par des noms simplifiés (si l'on est dans le même espace de noms) :

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

$class = $namespace->addClass('Demo');
$class->addImplement('Foo\A') // sera simplifié en A
	->addTrait('Bar\AliasedClass'); // sera simplifié en AliasedClass

$method = $class->addMethod('method');
$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // nous simplifions manuellement dans les commentaires
$method->addParameter('arg')
	->setType('Bar\OtherClass'); // sera traduit en \Bar\OtherClass

echo $namespace;

// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace);

Résultat :

namespace Foo;

use Bar\AliasedClass;

class Demo implements A
{
	use AliasedClass;

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

La résolution automatique peut être désactivée ainsi :

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

Fichiers PHP

Les classes, fonctions et espaces de noms peuvent être regroupés dans des fichiers PHP représentés par la classe PhpFile :

$file = new Nette\PhpGenerator\PhpFile;
$file->addComment('Ce fichier est généré automatiquement.');
$file->setStrictTypes(); // ajoute declare(strict_types=1)

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

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

echo $file;

// ou utilisez PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER
// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file);

Résultat :

<?php

/**
 * Ce fichier est généré automatiquement.
 */

declare(strict_types=1);

namespace Foo;

class A
{
}

function foo()
{
}

Vous pouvez aussi insérer dans le fichier des objets classe, fonction et espace de noms existants à l'aide de la méthode add() :

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

Attention : aucun code supplémentaire (comme echo 'hello') ne peut être ajouté aux fichiers en dehors des fonctions, classes ou espaces de noms.

Générer à partir d'éléments existants

Outre la modélisation des classes et des fonctions par l'API décrite ci-dessus, vous pouvez aussi les faire générer automatiquement à partir d'éléments existants, par réflexion :

// crée une classe identique à la classe PDO
$class = Nette\PhpGenerator\ClassType::from(PDO::class);

// crée une fonction identique à la fonction trim()
$function = Nette\PhpGenerator\GlobalFunction::from('trim');

// crée une closure à partir de celle fournie
$closure = Nette\PhpGenerator\Closure::from(
	function (stdClass $a, $b = null) {},
);

Par défaut, les corps des fonctions et méthodes sont vides. Si vous voulez les charger aussi, utilisez cette méthode (elle exige que le paquet nikic/php-parser soit installé) :

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

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

Charger depuis des fichiers PHP

Vous pouvez aussi charger des fonctions, classes, interfaces et enums directement depuis une chaîne contenant du code PHP. Par exemple, pour créer un objet ClassType :

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

	class Demo
	{
		public $foo;
	}
	XX);

Lors du chargement de classes depuis du code PHP, les commentaires sur une ligne situés en dehors des corps de méthodes (par exemple sur les propriétés) sont ignorés, car cette bibliothèque n'a pas d'API pour les manipuler.

Vous pouvez aussi charger directement un fichier PHP entier, qui peut contenir un nombre quelconque de classes, de fonctions, voire d'espaces de noms :

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

Le commentaire initial du fichier et la déclaration strict_types sont chargés eux aussi. Tout le reste du code global est cependant ignoré.

Exige que nikic/php-parser soit installé.

Si vous avez besoin de manipuler le code global des fichiers ou les instructions individuelles à l'intérieur des corps de méthodes, il vaut mieux utiliser directement la bibliothèque nikic/php-parser.

Class Manipulator

La classe ClassManipulator fournit des outils de manipulation des classes.

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

La méthode inheritMethod() copie dans votre classe une méthode d'une classe parente ou d'une interface implémentée. Cela vous permet de redéfinir la méthode ou d'étendre sa signature :

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

La méthode inheritProperty() copie dans votre classe une propriété d'une classe parente. C'est utile quand vous voulez avoir la même propriété dans votre classe, mais éventuellement avec une autre valeur par défaut :

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

La méthode implement() implémente automatiquement dans votre classe toutes les méthodes et propriétés abstraites de l'interface ou de la classe abstraite donnée :

$manipulator->implement(SomeInterface::class);
// Votre classe implémente désormais SomeInterface et contient des stubs de toutes ses méthodes

Dump de variables

La classe Dumper convertit une variable en code PHP analysable. Elle offre une sortie meilleure et plus claire que la fonction standard var_export().

$dumper = new Nette\PhpGenerator\Dumper;

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

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

Tableau de compatibilité

PhpGenerator 4.2 est compatible avec PHP 8.1 à 8.5.

Si vous passez à une version plus récente, voir la page mise à niveau.

version: 4.x