PHP wishlist for 2024
It’s never too late to make the language cooler through some hypothetical features
These are some ideas to make PHP a more cooler language, keeping it fresh but also more expressive without trying to be more difficult to work with.
Immutable values
PHP was never conceived as a “safe language”, but rather as a template language sitting between a C backend and an HTML frontend. Being unsafe allows for some creativity and flexibility on your code and external libraries, but nevertheless, sometimes that safeness needs to be enforced to ensure less errors, and sometimes more performance.
An immutable variable is a nice place to enforce safeness.
The best tool to make a variable immutable is to basically wrap it into an anonymous object that makes it “read only”, and access that value through the object itself.
// Before
$immutable = new class() {
public function __construct(public readonly $bool = true)
};
// After
readonly $immutable = true;
We can borrow that same readonly
syntax for immutable class properties and apply it to variables. This way any change to the value will throw an Exception, and also tell the interpreter to safely allocate the value without fear of changing its value or type later.
Another alternative is to use const
, much like JavaScript does, that’s also used to name constants in Classes.
const $immutable = true;
I presume that making immutable variables may come in handy for memory consumption. I can imagine the interpreter allocating the exact size of an array
or string
, instead of leaving some space to grow, which may add to memory fragmentation.
Of course, modifying an array item or object property won’t return any error. For the case of objects, it will affect only its reference.
Return $this
Another wish for PHP is to finally adopt the fluent method pattern by returning $this
. Not only it makes class methods more declarative, but also saves a couple of lines, as there shouldn’t be any need to explicitly declare the return of the object in the method body.
// Before
class Cool
{
/** @return $this */
public function warm(): static
{
$this->temp = 20.0;
return $this;
}
}
// After
class Cool
{
public function warm(): $this
{
$this->temp = 20.0;
}
}
This may also help the interpreter for some memory optimizations. I can imagine the interpreter copying the object pointer in memory preemptively rather than guessing what could come and waste some cycles.
Of course, this pattern won’t break an early exit. The only requisite is to declare return $this
.
public function warm(): $this
{
if ($this->temp === null) {
return $this;
}
$this->temp = 20.0;
}
Return doubles
This is a pattern taken out from Go. In Go, functions can return multiple values. The closest that PHP has for returning multiples values is an array
that can hold any value type, which is then unpacked at the receiving end, and (hopefully) being properly documented.
// Before
/** @return array{int:bool,int:int} */
function something(): array
{
return [true, 10];
}
// After
function something(): (bool, int)
{
return (true, 10);
}
// This works with both
[$bool, $int] = something();
By avoiding the array construction, not only we are consuming less memory — an array is always allocated with more space to allow it to get bigger — but also helping the interpreter with type hints so it can optimize further memory allocations.
Also, by reusing the same unpacking syntax for variables, we make this approach backwards compatible. Of course, an error will be returned if there is a mismatch of expected and returned values, but should be easily catchable by any competent developer and IDE.
Deferred return
This is another patter from Go. There is a very useful way to return values from a function without exiting its context immediately: deferred returns.
The closest to a deferred return that PHP has is the tap()
function that is included within the Laravel framework. Without it, you will have to declare the value, manipulate it, and return it.
// Before
public function something(): Warm
{
$warm = $this->heatUp();
while ($warm->burns()) {
$warm->lowerTemp();
}
return $warm;
}
// Laravel
public function something(): Warm
{
return tap($this->heatUp(), function ($warm) {
while ($warm->burns()) {
$warm->lowerTemp();
}
);
}
// After
function something($cool): Warm
{
defer $warm = $this->heatUp();
while ($warm->burns()) {
$warm->lowerTemp();
}
}
The defer
keyword would work brilliantly to signal the interpreter to hold that value while the next code is executed, not exiting the context. This is more useful when we need to call a method that is not fluent or sets values after constructing the object.
PHP allows multiple returns inside a function, but only the first is executed. We can use the same pattern for deferring a return: if there is another defer
in the function, it would overwrite the previous. A plain return
would just take precedence over any deferred value.
public function multiple(int $number): Number|Number
{
// Defer returning the instance
defer $instance = new Number($number);
// Forcefully return a new instance
if ($instance->isNegative()) {
return new Number(0);
}
// Overwrite the previous deferred return
if ($instance->isTooBig()) {
defer new BigNumber($number);
}
if ($instance->isFloat()) {
$instance->ceil();
}
// Return the deferred `$instance` value.
}
Loop as expression
One drawback of PHP are how inflexible loops are. It’s not difficult to find places where you need to first initialize a variable into an array, then make a loop to add the values to the previous variable, and then return that variable.
// Until this day...
function uncoolCode($items): array
{
$array = []
foreach ($items as $item) {
if ($array->isValid()) {
$array[] = $item
}
}
return $array;
}
The closest I’ve been to make a initialize-with-loop is by using iterator_to_array()
and a function that returns a Generator
, which itself is an iterator. I’ve been using it some years ago.
// Until this day...
function uncoolCode($items): array
{
return iterator_to_array((function ($items) {
foreach ($items as $item) {
if ($item->valid) {
yield $item;
}
}
})($items));
}
By using the each
keyword, which was freed from PHP 8.0, we may create loops that return an array
by looping over another iterable value, like another array
or even a traversable object.
// After
$accepted = each ($items as $item) {
if ($item->valid) {
yield $item;
}
};
Now, we could make a case to use foreach
as an expression, but that could give some headaches and edge cases, especially when using the return
keyword to exit the loop.
Going back to the each
concept, we can, for example, create an array of items with the range()
method, and iterate through it easily.
// After
$items = each(range(0, 10) as $key => $item) {
yield $key => $item * 100 / 3
}
Now, you may be wondering why I’m using yield
rather than return
, and it’s because we are still on the same context of the variable. That doesn’t mean that we can’t use return
; if we do, we would exit the context, which is just like happens through a foreach
loop.
To put it in another words, we can skip the item with continue
, stop the loop through the break
keyword, throw an exception, or exit the function through a return
.
/**
* Return all the billable items from the cart.
*
* @return \App\Item[]
*/
public function getBillableItems(Cart $cart): array
{
return each($cart->items as $item) {
// Discard the loop and return another value
if ($item->isCompensationCoupon()) {
return [];
}
// Discard the loop and throw an exception.
if ($item->isNotForSale()) {
throw new Exception("The $item->name cannot be sold");
}
// Continue with the next item.
if ($item->isFreeGift()) {
continue;
}
// Stop the loop and return it.
if ($item->isBackordered()) {
break;
}
yield $part;
}
}
What do you think about this wishful thinking for PHP in 2024? Do you think there is a chance to have them in PHP 8.4? Are there better ideas?