This article will talk about generics in PHP and introduce the basic knowledge about generics. I hope it will be helpful to everyone.
Generics in PHP. I knew this was what I wanted. I know a lot of developers want this to use this type. On the other hand, there may be a large group of PHP programmers who don't know what generics are, or why they have this type.
I will be doing a series on Generics and PHP on this blog. Let's start at the beginning and soon we will get to more complex topics. We'll discuss what generics are, why PHP doesn't support them, and what might happen in the future.
Let’s get started.
Every programming language has some type of system. Some languages are very strict in their implementation, while others - and PHP falls into this category - are much looser
Now, there are many reasons to use a type system. The most obvious is type validation.
Suppose we have a function that takes two numbers, two integers; and does some math on them:
function add($a, $b) { return $a + $b; }
PHP allows you to pass any type of data to the function, numbers, strings, booleans doesn't matter . PHP will do its best to transform variables when it makes sense, such as adding them together.
add('1', '2');
But these conversions — type juggling — often lead to unexpected results, or rather: errors and crashes.
add([], true); // ?
Now, we can manually write code to check that our mathematical addition operation will be used for any given input
function add($a, $b) { if (!is_int($a) || !is_int($b)) { return null; } return $a + $b; }
Alternatively, we can use PHPS built-in type hints – this is the built-in shorthand for us to do the operation 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 themselves after all.
However, this reasoning quickly breaks down: you're often not the only one working in that codebase, and you're using code you didn't write yourself - think of how many packages you pull in with Composer. So while this isolated example doesn't seem like a big deal, type checking does come in handy once your code starts growing.
Beyond that, adding type hints not only prevents invalid states, but also clarifies what type of value input we programmers need. Defining a type usually eliminates the need to read external documentation because most of a function's functionality is already encapsulated by its type definition.
IDEs make heavy use of this principle: they can tell the programmer what type of value input a function expects, or what fields and methods are available on an object - because it belongs to a class. IDEs make us write code more efficiently, in large part because they can statically analyze type hints in our code base.
Remember this word: static analysis - this will be very important later in this series. This means that a program, IDE, or other type of "static analyzer" can look at our code and tell us whether it will work without running it - at least to some extent. If we pass a string to our function that only accepts integers, our IDE will tell us what we're doing wrong - which will cause the program to crash at runtime; but our IDE can tell us without actually running the code. .
On the other hand, type systems also 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 many ways to handle any type of input: looping, filtering, mapping, etc.; the collection implementation should not care whether it handles strings or integers.
But let’s look at it from an outsider’s perspective. What happens if we want to make sure that one collection only contains strings and another collection only contains "user" objects. The collection itself doesn't care when looping through its items, but we do. We want to know if the item in the loop is a user or a string - that's completely different. But without the correct type information, our IDE will run in unknown circumstances.
$users = new Collection(); // … foreach ($users as $user) { $user-> // ? }
Now, we can 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? the fourth? Maybe 10 or 20. Managing these codes will become very difficult.
This is where generics come in.
To clarify: PHP does not have generics. It’s a bold statement that takes quite a few detours, which we’ll discuss later in this series. But now I can say that what I'm going to show you doesn't exist in PHP. But it exists in other programming languages.
Many programming languages allow developers to define "generics" on collection classes, rather than implementing separate implementations for each possible type:
class Collection<Type> extends ArrayObject { public function offsetGet(mixed $key): Type { /* … */ } // … }
基本上我们说的是集合类的实现适用于任何类型的输入,但是当我们创建集合的实例时,我们应该指定一个类型。它是一个泛型实现,需要根据程序员的需求来特定:
$users = new Collection<User>(); $slugs = new Collection<string>();
添加类型似乎是一件小事。但这种类型本身就开启了一个充满可能性的世界。 我们的 IDE 现在知道了集合中的数据类型,它可以告诉我们是否添加了错误类型的项;它可以告诉我们在迭代集合时可以对项执行什么操作;它可以告诉我们是否将集合传递给知道如何处理这些特定项的函数。
虽然我们可以通过手动为我们需要的每种类型实现一个集合,在技术上实现同样的效果;对于编写和维护代码的开发人员来说,通用实现将是一项重大改进。
那么,我们为什么不在 PHP 中使用泛型呢?除了无聊的收藏,我们还能用它们做什么?我们能为他们增加支持吗?我们将在这个系列中回答所有这些问题。首先需要澄清的是:我在本系列文章中的目标是教你关于泛型的知识,但同样重要的是,我想让大家意识到我们是如何误解 PHP 的。我想改变这种状况。
推荐学习:《PHP视频教程》