Les génériques
(en )
CLERMONT'ECH API HOUR #56
C'est quoi les génériques ?
Définition
Les génériques permettent de rendre paramétrables les types des classes, interfaces et méthodes...
Écrire du code typé mais sans être spécifiquement lié à un type.
Définition
Ajouter une couche d'abstraction sur les types.
Définition
Avantages
Pas de cast (ou documentation)
Optimise et simplifie (?)
Facilite la maintenance du code
Augmente la sécurité du code
Des inconvénients
Notation pas (toujours) simple
Performances au runtime (?)
Aujourd'hui
Concept important
Souvent natif (Java, Go, Rust, ...)
Très attendu
Les cas d'utilisations
Des collections
class Collection
{
public function push(??? $item): void {}
public function pop(): ??? {}
}
class TypedCollection
{
private string $type;
public function push(??? $item): void {}
public function pop(): ??? {}
}
class FooCollection
{
public function push(Foo $item): void {}
public function pop(): Foo {}
}
Des utilitaires
function add(??? $a, ??? $b): ???
{
return $a + $b;
}
$result = add(1, 42); // is a int
$result = add(2.21, 3.14); // is a float
function transformToArray(??? $a, ??? $b): array
{
return [$a, $b];
}
$result = transformToArray(1, 42); // is a int[]
$result = transformToArray(2.21, 3.14); // is a float[]
Des factories
function make(string $className): ???
{
return new $className();
}
$result = make(Foo::class); // is a Foo
$result = make(Exception::class); // is an Exception
Regardons ailleurs
Quels langages ?
Des conventions ?
- T : 1er type
- S : 2ème type
- U : 3ème type
- V : 4ème type
- K : clé d'une map
- V : valeur d'une map
- ...
- TKey
- TValue
- TFooBar
Exemple en Go
Exemple en Go
func SumIntsOrFloats[K comparable, V int64 | float64](m map[K]V) V {
var s V
for _, v := range m {
s += v
}
return s
}
SumIntsOrFloats[string, int64](ints)
SumIntsOrFloats[string, float64](floats)
Comment ça se passe en PHP ?
Pourquoi PHP ne supportera probablement jamais les génériques ?
(parce que Nikita l'a dit)
- https://github.com/PHPGenerics/php-generics-rfc/issues/45
- https://www.redditmedia.com/r/PHP/comments/...
On ne typehint plus ?
// i can take every param and return what i want
function identity($value)
{
return $value;
}
$r1 = identity(2023);
$r2 = identity('Symfony Live Paris');
$r3 = identity(true);
$r4 = identity(['Symfony Live Paris']);
echo $r4[0];
Et si on utilisait mixed ?
- Depuis PHP 8.0
- object|resource|array|string|float|int|bool|null
Si on utilisait ça ?
Et si on utilisait mixed ?
function identity(mixed $value): mixed
{
return $value;
}
$r1 = identity(2023);
$r2 = identity('Symfony Live Paris');
$r3 = identity(true);
$r4 = identity(['Symfony Live Paris']);
echo $r4[0];
Quelles solutions avons-nous alors ?
Les annotations !
- @template
- @extends
- @implements
- class-string
- ...
Les limites des annotations
la syntaxe sous forme de commentaire
coloration syntaxique quasi inexistante
validation du contenu difficile
...
/*
L'écosystème
Des outils pour nous aider
Les annotations
@template
/**
* @template T
* @param T $value
* @return T
*/
function identity(mixed $value): mixed
{
return $value;
}
$r1 = identity(2023);
$r2 = identity('Symfony Live Paris');
$r3 = identity(true);
$r4 = identity(['Symfony Live Paris']);
echo $r4[0];
@template
/**
* @template T
* @param T $value
* @return T
*/
function identity(mixed $value): mixed
{
return [$value];
}
$r1 = identity(2023);
$r2 = identity('Symfony Live Paris');
$r3 = identity(true);
$r4 = identity(['Symfony Live Paris']);
echo $r1[0]; // 2023
echo $r2[0]; // Symfony Live Paris
echo $r3[0]; // 1
echo $r4[0]; // Warning: Array to string conversion
@template
/**
* @template T
*/
interface Collection
{
/**
* @param T $item
*/
public function push(mixed $item): void;
/**
* @return T
*/
public function pop(): mixed;
}
@template
/**
* @template K
* @template V
*/
interface Collection
{
/**
* @param V $item
*/
public function push(mixed $item): void;
/**
* @return V
*/
public function pop(): mixed;
/**
* @param K $index
* @return V
*/
public function get(mixed $index): mixed;
}
@template T of foo
/**
* @template T of \Exception
* @param T $value
* @return T
*/
function identity($value): mixed
{
return $value;
}
$r1 = identity(new LogicException());
assert($r1 instanceof LogicException);
@template T of foo
/**
* @template T of \Exception
* @param T $value
* @return T
*/
function identity($value): mixed
{
return $value;
}
$r1 = identity('Hello Symfony Live Paris!');
assert(is_string($r1));
class-string
/**
* @template T of object
* @param class-string<T> $className
* @return T
*/
function make(string $className): object
{
return new $className;
}
$e = make(Exception::class);
echo $e->getMessage();
class-string
function make(string $className): object
{
return new $className;
}
$e = make(Exception::class);
echo $e->getMessage();
@implements
/**
* @template T
*/
interface Repository
{
/** @param T $item */
public function save($item): void;
/** @return T */
public function get($id): mixed;
}
/** @implements Repository<Bar> */
class BarRepository implements Repository
{
public function add($item): void {}
public function get($id): mixed
{
return $item;
}
}
@implements
/**
* @template T
*/
interface Repository
{
/** @param T $item */
public function save($item): void;
/** @return T */
public function get($id): mixed;
}
/** @implements Repository<Bar> */
class BarRepository implements Repository
{
public function add($item): void {}
public function get($id): mixed
{
return 'Hey';
}
}
@extends
/**
* @template T
*/
class Collection
{
/** T $item */
protected mixed $item;
/** @param T $item */
public function add($item): void {$this->item = $item;}
/** @return T */
public function get(): mixed { return $this->item;}
}
/** @extends Collection<Bar> */
class BarCollection extends Collection
{
public function add($item): void {$this->item = $item;}
public function get(): mixed
{
return new Bar();
}
}
@extends
/**
* @template T
*/
class Collection
{
/** T $item */
protected mixed $item;
/** @return T */
public function get(): mixed { return $this->item;}
}
/** @extends Collection<Bar> */
class BarCollection extends Collection
{
public function get(): mixed
{
return 'Heyyy';
}
}
Covariance et contravariance
Support PHPStorm
PhpStorm 2021.2
Est-ce parfait ?
Est-ce parfait ?
Pas de surcoût au runtime
Couverture fonctionnelle
Source de vérité
PHP + annotations + attributs
Pas d'obligation
Est-ce parfait ?
/**
* Return the given value, optionally passed through the given callback.
*
* @template TValue
* @template TReturn
*
* @param TValue $value
* @param (callable(TValue): (TReturn))|null $callback
* @return ($callback is null ? TValue : TReturn)
*/
function with($value, callable $callback = null)
{
return is_null($callback) ? $value : $callback($value);
}
Conclusion
Conclusion
Concept important (collection, ...)
Pas natif en PHP
Très bien supporté par des outils
https://slides.com/kpn13
Conclusion
@kpn13
https://blog.karimpinchon.com
- https://stitcher.io/blog/php-generics-and-why-we-need-them
- https://psalm.dev/docs/annotating_code/templated_annotations/
- https://phpstan.org/blog/generics-in-php-using-phpdocs
- https://phpstan.org/blog/generics-by-examples
- https://phpstan.org/blog/whats-up-with-template-covariant
- https://go.dev/doc/tutorial/generics
- https://youtrack.jetbrains.com/issue/WI-60894
- https://youtrack.jetbrains.com/issue/WI-61497
- https://github.com/mrsuh/php-generics
- https://arnaud.le-blanc.net/post/phpstan-generics.html
- https://github.com/PHPGenerics/php-generics-rfc/issues/45
- https://www.redditmedia.com/r/PHP/comments/j65968/ama_with_the_phpstorm_team_from_jetbrains_on/g83skiz/
Ressources
Les génériques (en PHP) - Quicky
By Karim PINCHON
Les génériques (en PHP) - Quicky
Les génériques sont un concept des langages de programmation. En bref, ils permettent d'écrire du code typé mais sans être spécifiquement lié à un type particulier. Beaucoup de langages supportent déjà les génériques plus ou moins nativement : Java, C#, Go, Rust Typescript... Ce n'est pas le cas de PHP, mais grâce à sa communauté et son ecosystème, il est tout de même possible de les utiliser. Je vous propose de voir comment et dans quel but, en s'appuyant sur l'exemple de composants Symfony.
- 272