Hi folks. I recently wrote a blog post on behalf of the PHP Foundation about some work Gina has been doing regarding generics. It's a follow-up to last year's post on a similar topic. We are especially looking for feedback from the Internals crew, and voters in particular, on whether or not to proceed.
https://thephp.foundation/blog/2025/08/05/compile-generics/
Wearing my Internals/voter hat, my stance is a very strong "yes please!"
--
Larry Garfield
larry@garfieldtech.com
I'm not a voter nor part of the internals, but I'd say "yes", for sure.
One thing that came to mind while reading the article (keep in mind
that I don't have any knowledge on the internals of PHP, so if it's a
dumb question, sorry about that):
Would it be possible to transform (in compile-time), the first option
(that isn't supported) into the second one?
// Turning this:
$blogPostRepository = new BaseRepository<BlogPost>();
// Into this:
$blogPostRepository = new class extends BaseRepository<BlogPost> { };
If this is possible, although I know it's not ideal, it would allow us
to have that syntax and we wouldn't need to manually create "empty
extending classes".
Hi,
Am 04.08.25 um 17:23 schrieb Larry Garfield:
Hi folks. I recently wrote a blog post on behalf of the PHP Foundation about some work Gina has been doing regarding generics. It's a follow-up to last year's post on a similar topic. We are especially looking for feedback from the Internals crew, and voters in particular, on whether or not to proceed.
https://thephp.foundation/blog/2025/08/05/compile-generics/
Wearing my Internals/voter hat, my stance is a very strong "yes please!"
Thanks for that nice write-up and potentially great improvement.
Can you please elaborate why it wouldn't be possible to create "empty
extending classes" during compile-time of a file when there is a "new
generic" found?
Some example to make it clearer:
Given a generic class
namespace SomeNamespace;
use Some\Entity;
abstract class MyGenericClass<T: Entity>
{
// ...
}
and given an entity
namespace MyEntities;
use Some\Entity;
class UserEntity extends Entity
{
// ...
}
when using a "new
generic" in some file
namespace OtherNamespace;
use SomeNamespace\MyGenericClass;
use MyEntities\UserEntity;
$myGenericObject = new MyGenericClass<UserEntity>;
and when compiling that file
then the compiler creates an "empty extending class" on the fly if there
is none available in the OpCache (from compiling some other file with
the same "new
generic") that has code equivalent to
namespace SomeNamespace;
use MyEntities\UserEntity;
class MyGenericClass<UserEntity> extends MyGenericClass<Entity>
{}
That simply(TM) means, to allow <
and >
in class names but only
internally to the engine and if generated by the compiler itself.
Further question: Would it be possible to automatically mark a class as
abstract if it is a generic class?
Probably you have thought about this option and I just don't understand
anything about internals of PHP. So don't hesitate to just answer very
shortly. ;-)
Best regards
Thomas
Hi folks. I recently wrote a blog post on behalf of the PHP Foundation about some work Gina has been doing regarding generics. It's a follow-up to last year's post on a similar topic. We are especially looking for feedback from the Internals crew, and voters in particular, on whether or not to proceed.
https://thephp.foundation/blog/2025/08/05/compile-generics/
Wearing my Internals/voter hat, my stance is a very strong "yes please!"
--
Larry Garfield
larry@garfieldtech.com
I played with a few iterations of Gina's work. My opinion at that time
was that there are too many restrictions to be very useful. Based on
those evaluations, I would not consider it to be "80% of the
benefit.". With that said, maybe Gina has removed some of the
restrictions. Based on the article it , but I'll commit to trying it
out again.
One thing the article says we're missing and that we we definitely
need is unions, or at the very least the union with null. Nullable
types are everywhere, and even in your interfaces it's going to show
up. For instance, if we had interfaces for Set, Map, etc, there are
going to be operations which return nullable values. Sure, exceptions
can be thrown in some cases but not all of them. For instance, if you
are building a caching data structure, throwing an exceptions on a
cache miss would be very bad for performance*. You can separate it
into has + get, but this has two lookups which is not desirable for
performance. You can return a "tuple" like [found, $value]
where
$value
may be null, but this loses even more type safety, and isn't
a very common pattern in PHP. You really want T|null
.
There are probably other "edges" that are important and should be
handled in this first pass, but missing nullable types is not a small
thing.
- We'll get a mini-optimization in PHP 8.5 for making the exception
object faster to construct, but it doesn't speed up the backtrace,
which is where most of the performance cost comes from in practice. If
you are benchmarking this, make sure to include cases where your stack
is a few dozen frames deep. I work for an observability company for my
day job, and it is insanely normal for there to be 10+ frames, and
still fairly common to be 30+ frames. Think about routing, frameworks,
middleware, helper functions, libraries and abstractions, etc. If you
use middleware in particular, expect 50+ frames to be normal.
Hi folks. I recently wrote a blog post on behalf of the PHP Foundation about some work Gina has been doing regarding generics. It's a follow-up to last year's post on a similar topic. We are especially looking for feedback from the Internals crew, and voters in particular, on whether or not to proceed.
https://thephp.foundation/blog/2025/08/05/compile-generics/
Wearing my Internals/voter hat, my stance is a very strong "yes please!"
--
Larry Garfield
larry@garfieldtech.comI played with a few iterations of Gina's work. My opinion at that time
was that there are too many restrictions to be very useful. Based on
those evaluations, I would not consider it to be "80% of the
benefit.". With that said, maybe Gina has removed some of the
restrictions. Based on the article it , but I'll commit to trying it
out again.One thing the article says we're missing and that we we definitely
need is unions, or at the very least the union with null. Nullable
types are everywhere, and even in your interfaces it's going to show
up. For instance, if we had interfaces for Set, Map, etc, there are
going to be operations which return nullable values. Sure, exceptions
can be thrown in some cases but not all of them. For instance, if you
are building a caching data structure, throwing an exceptions on a
cache miss would be very bad for performance*. You can separate it
into has + get, but this has two lookups which is not desirable for
performance. You can return a "tuple" like[found, $value]
where
$value
may be null, but this loses even more type safety, and isn't
a very common pattern in PHP. You really wantT|null
.There are probably other "edges" that are important and should be
handled in this first pass, but missing nullable types is not a small
thing.
- We'll get a mini-optimization in PHP 8.5 for making the exception
object faster to construct, but it doesn't speed up the backtrace,
which is where most of the performance cost comes from in practice. If
you are benchmarking this, make sure to include cases where your stack
is a few dozen frames deep. I work for an observability company for my
day job, and it is insanely normal for there to be 10+ frames, and
still fairly common to be 30+ frames. Think about routing, frameworks,
middleware, helper functions, libraries and abstractions, etc. If you
use middleware in particular, expect 50+ frames to be normal.
I have a few different ideas for unions/intersections, but to implement them now would be refactoring for the sake of refactoring. There would be no benefit to implement any of it now.
Earlier this year I tried interning types, so that type checking could be simple pointer equality. From there, I implemented a tri, such that intersections and unions were very fast to check.
The result is that it was virtually useless with today’s php, except for the most pathological cases. PHP doesn’t do much type checking — currently.
In any case, the changes required to pull it off (not counting opcache) were huge. If we were to run into the pathological cases more often (such as with runtime generics), it might be worth it.
All that being said, we are talking about compile-time generics. If there is a time to be less efficient, it is during compilation, which generally happens once (esp with opcache). Further, pathological type checking is quite rare in codebases (most types are relatively simple without deep hierarchical intersections and unions), so I assume it will be fine to enable them.
— Rob
Hi folks. I recently wrote a blog post on behalf of the PHP Foundation about some work Gina has been doing regarding generics. It's a follow-up to last year's post on a similar topic. We are especially looking for feedback from the Internals crew, and voters in particular, on whether or not to proceed.
https://thephp.foundation/blog/2025/08/05/compile-generics/
Wearing my Internals/voter hat, my stance is a very strong "yes please!"
--
Larry Garfield
larry@garfieldtech.comI played with a few iterations of Gina's work. My opinion at that time
was that there are too many restrictions to be very useful. Based on
those evaluations, I would not consider it to be "80% of the
benefit.". With that said, maybe Gina has removed some of the
restrictions. Based on the article it , but I'll commit to trying it
out again.One thing the article says we're missing and that we we definitely
need is unions, or at the very least the union with null. Nullable
types are everywhere, and even in your interfaces it's going to show
up. For instance, if we had interfaces for Set, Map, etc, there are
going to be operations which return nullable values. Sure, exceptions
can be thrown in some cases but not all of them. For instance, if you
are building a caching data structure, throwing an exceptions on a
cache miss would be very bad for performance*. You can separate it
into has + get, but this has two lookups which is not desirable for
performance. You can return a "tuple" like[found, $value]
where
$value
may be null, but this loses even more type safety, and isn't
a very common pattern in PHP. You really wantT|null
.There are probably other "edges" that are important and should be
handled in this first pass, but missing nullable types is not a small
thing.
- We'll get a mini-optimization in PHP 8.5 for making the exception
object faster to construct, but it doesn't speed up the backtrace,
which is where most of the performance cost comes from in practice. If
you are benchmarking this, make sure to include cases where your stack
is a few dozen frames deep. I work for an observability company for my
day job, and it is insanely normal for there to be 10+ frames, and
still fairly common to be 30+ frames. Think about routing, frameworks,
middleware, helper functions, libraries and abstractions, etc. If you
use middleware in particular, expect 50+ frames to be normal.I have a few different ideas for unions/intersections, but to implement them now would be refactoring for the sake of refactoring. There would be no benefit to implement any of it now.
Earlier this year I tried interning types, so that type checking could be simple pointer equality. From there, I implemented a tri, such that intersections and unions were very fast to check.
The result is that it was virtually useless with today’s php, except for the most pathological cases. PHP doesn’t do much type checking — currently.
In any case, the changes required to pull it off (not counting opcache) were huge. If we were to run into the pathological cases more often (such as with runtime generics), it might be worth it.
All that being said, we are talking about compile-time generics. If there is a time to be less efficient, it is during compilation, which generally happens once (esp with opcache). Further, pathological type checking is quite rare in codebases (most types are relatively simple without deep hierarchical intersections and unions), so I assume it will be fine to enable them.
— Rob
It’s also worth pointing out that nullable or a union of null and another type is technically just one type with the nullable bit set. So, I suspect nullable types will still obey the “single type” rules proposed here.
— Rob
For instance, if we had interfaces for Set, Map, etc, there are going to be operations which return nullable values.
Wouldn't it be implemented like this?
interface Set<T>
{
public function get(string $key): ?T;
}
I might have missed something, but this doesn't seem to be a
limitation of this implementation, is it?
Am 04.08.2025 um 17:23 schrieb Larry Garfield larry@garfieldtech.com:
Hi folks. I recently wrote a blog post on behalf of the PHP Foundation about some work Gina has been doing regarding generics. It's a follow-up to last year's post on a similar topic. We are especially looking for feedback from the Internals crew, and voters in particular, on whether or not to proceed.
To be up-front: I am not the target demographic for this feature, being an OO and Type minimalist ;-)
One little thing I noted in the side-note about type inference
https://thephp.foundation/blog/2025/08/05/compile-generics/#type-inference
was the part
function add(int $x, int $y)
{
return $x + $y;
}It's readily obvious that the return type of that function is int, ...
Now this is nit-picking but that's not true for PHP in general, if I give it the values PHP_INT_MAX
and 1 the result will be float, not int.
It has little to do with the topic of the whole post but I still felt the urge to point it out. But please do not get into a discussion about automatic type conversions here because of that, it is just a reminder that type inference has some edge cases ;-)
Regards,
- Chris