1404 1473 1721 1381 1649 1444 1532 1573 1301 1282 1363 1911 1446 1259 1247 1681 1440 1701 1878 1643 1261 1605 1035 1418 1173 1024 1131 1372 1219 1883 1639 1433 1637 1203 1911 1959 1997 1282 1141 1697 1731 1347 1255 1245 1999 1126 1149 1653 1033 1006 1777 1226 1041 1584 1663 1870 1037 1596 1439 1383 1522 1665 1237 1669 1490 1561 1740 1249 1508 1354 1249 1134 1379 1202 1516 1675 1018 1594 1624 1652 1709 1301 1860 1367 1341 1242 1410 1524 1309 1206 1079 1775 1862 1248 1075 1204 1681 1633 1897 Generics in PHP: The basics | PHPnews.io

PHPnews.io

Generics in PHP: The basics

Written by Stitcher.io / Original link on Mar. 22, 2022

Generics in PHP. I know I’d want them. And I know a lot of developers who agree. On the other hand, there is a group of PHP programmers, maybe even larger, that say they don’t know what generics are, or why they should care.

I’m going to do a series on this blog about generics and PHP. We’ll start from the beginning, but quickly work our way to the more complex topics. We’ll talk about what generics are, why PHP doesn’t support them, and what’s possible in the future.

Let’s get started.

generics-thumb-1.png

You can watch the video instead of reading a blog post — if you prefer that!

Every programming language has some kind of type system. Some languages have a very strict implementation, while others — PHP falls in this category — are much more lenient.

Now, type systems are used for a variety of reasons; the most obvious one is type validation.

Let’s imagine we have a function that takes two numbers, two integers; and does some kind of maths operation on them:

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

PHP will happily allow you to pass any kind of data to that function, numbers, strings, booleans, doesn’t matter. PHP will try its best to convert a variable whenever it makes sense, like for example adding them together.

add('1', '2');

But those conversions — type juggling — often lead to unexpected results, if not to say: bugs and crashes.

add([], true); // ?

Now, we could manually write code to check whether our maths addition will work with any given input:

function add($a, $b) 
{
    if (!is_int($a) || !is_int($b)) {
        return null;
    }
    
    return $a + $b;
}

Or we could make use of PHPs built-in type hints — built-in shorthands for what we’d otherwise do manually:

function add(int $a, int $b): int 
{
    return $a + $b;
}

Many developers in the PHP community say they don’t really care about these type hints because they know they should only pass integers to this function — they wrote it, after all.

However, that kind of reasoning quickly falls apart: you’re often not the only one working in that codebase, you’re also using code that you haven’t written yourself — think about how many packages you’re pulling in with composer. And so, while this example in isolation might not seem to be that big a deal, type checking does come in handy once your code starts to grow.

Besides that, adding type hints not only guards against invalid state, but they also clarify what kind of input is expected from us, programmers. Types often make it so that you don’t need to read external documentation, because much of what a function does is already encapsulated by its type definition.

IDEs make heavy use of this principle: they can tell the programmer what kind of input is expected by a function or what fields and methods are available on an object — because it belongs to a class. IDEs make our code writing so much more productive, in large part because they can statically analyse type hints across our codebase.

Keep that word in mind: static analysis — it’s going to be very important later in this series. It means that programs, IDEs or other kinds of “static analysers” can look at our code, and without running it tell us whether it will work or not — at least, to some degree. If we’re passing a string to our function that takes an integer, our IDE will tell us we’re doing something wrong — something that would lead to a crashing program at runtime; but our IDE is able to tell us without having to actually run the code.

On the other hand, type systems have their limitations. A common example is a “list of items”:

class Collection extends ArrayObject
{
    public function offsetGet(mixed $key): mixed 
    { /* … */ }
    
    public function filter(Closure $fn): self 
    { /* … */ }
    
    public function map(Closure $fn): self 
    { /* … */ }
}

A collection has a bunch of methods that work with any kind of input: looping, filtering, mapping, you name it; a collection implementation shouldn’t care about whether it’s dealing with strings or integers.

But let’s look at it from an outsider’s perspective. What happens if we want to be sure that one collection only contains strings, and another one only contains User objects. The collection itself doesn’t care when looping over its items, but we do. We want to know whether this item in a loop is a User or a string — that’s quite the difference. But without proper type information, our IDE is operating in the dark.

$users = new Collection();

// …

foreach ($users as $user) {
    $user-> // ?
}

Now, we could create separate implementations for each collection: one that only works with strings, and another that only works with User objects:

class StringCollection extends Collection
{
    public function offsetGet(mixed $key): string 
    { /* … */ }
}

class UserCollection extends Collection
{
    public function offsetGet(mixed $key): User 
    { /* … */ }
}

But what if we need a third implementation? A fourth? Maybe ten or twenty. It becomes quite painful to manage all that code.

That’s where generics come in.

Now, to be clear: PHP doesn’t have generics. That’s a bold statement cutting quite a lot of corners, and we’re coming back to that later in this series. But for now it’s sufficient to say that what I’m showing next isn’t possible in PHP. But it is in many other languages.

Instead of creating a separate implementation for every possible type, many programming languages allow developers to define a “generic” type on the collection class:

class Collection<Type> extends ArrayObject
{
    public function offsetGet(mixed $key): Type 
    { /* … */ }
    
    // …
}

Basically we’re saying that the implementation of the collection class will work for any kind of input, but when we create an instance of a collection, we should specify a type. It’s a generic implementation, but it’s made specific depending on the programmer’s needs:

$users = new Collection<User>();

$slugs = new Collection<string>();

It might seem like a small thing to do: adding a type. But that type alone opens a world of possibilities. Our IDE now knows what kind of data is in a collection, it can tell us whether we’re adding an item with the wrong type; it can tell us what we can do with items when iterating over a collection, it can tell us whether we’re passing the collection to a function that knows how to work with those specific items.

And while we could technically achieve the same by manually implementing a collection for every type we need; a generic implementation would be a significant improvement for you and me, developers who are writing and maintaining code.

So, why don’t we have generics in PHP? What other things can we do with them besides a boring collection? Can we add support for them? We’re going to answer all those questions in this mini series. And to be clear up front: my goal with this series is to teach you about generics, but equally important is that I want to create awareness about how we’re missing out with PHP. I want that to change.

Noticed a tpyo? You can submit a PR to fix it. If you want to stay up to date about what's happening on this blog, you can follow me on Twitter or subscribe to my newsletter: Email Subscribe

stitcher.io

« Use Laravel Dusk to Test JS-Based Applications - 2 weeks left before SymfonyLive Paris 2022 pre-conference workshops »