An open API service indexing awesome lists of open source software.

https://github.com/jordanrl/operator-overloads-in-php

A quick guide to operator overloads in PHP from the person who wrote the feature
https://github.com/jordanrl/operator-overloads-in-php

Last synced: 4 months ago
JSON representation

A quick guide to operator overloads in PHP from the person who wrote the feature

Awesome Lists containing this project

README

          

# Introduction

Operator overloads are a feature that will be new to many PHP developers. As such, I've created this document as a way for PHP developers to start learning *good* habits with the feature. **This is not a substitute for the actual PHP.net documentation.**

**NOTE:** This feature has not been accepted as an RFC yet.

# Rule 1: All Overloads Must Be Immutable

It is critically important in the vast majority of programs that objects which use operator overloads implement them immutably. For those who are not familiar, an object is *immutable* if it prevents changes to its values after some initialization period. Typically, this is the constructor method. However, individual methods can treat the object as immutable. Consider the following two examples.

## Example 1 (Mutable Implementation)

```php
somevar++;

return $this;
}
}
```

## Example 2 (Immutable Implementation)

```php
somevar + 1);

return $newObj;
}
}
```

These two examples show the key thing to understand about a mutable vs. an immutable implementation: a mutable implementation *modifies the existing object* while an immutable implementation *returns a new object that has the modifications already performed on it*.

So why is this so important for operator overloads? *Because programmers expect operators to do things immutably ALWAYS.*

Consider what we expect the value of `$x` to be throughout this code:

```php
property = 'somevalue'`.

The exceptions to this rule should be extremely rare, and should require a great deal of care. If you think you have an exception, you probably don't. Don't violate this rule, people will hate your code for it.

# Rule 2: Don't Be Too Clever

It really, really sucks when the *problem* with code is not the same place that the *error* happens. If you try to make your operator overload *too* clever so that it can handle all sorts of edge cases, you're probably doing it wrong. Instead, you want your operator overload implementations to be *very* picky about the input data and types, and to error if anything seems out of place.

Consider the following:

```php
currency != $this->currency) {
if ($operandPos == OperandPosition::LeftSide) {
$newCurrency = $this->currency;
$newValue = $this->value + $other->getConvertedValue($newCurrency);
} else {
$newCurrency = $other->currency;
$newValue = $this->getCovertedValue($newCurrency) + $other->value;
}
} else {
$newCurrency = $this->currency;
$newValue = $this->value + $other->value;
}

return new Money($newValue, $newCurrency);
}
}
```

So what exactly is going on here? Well, this implementation is trying to automatically convert currencies if they don't match. Could this sometimes be the correct thing to do? Yes. But most of the time, it would probably be clearer to force the currency conversion to be explicit in the calling code. This implementation probably won't produce any bugs on its own, but what if the currency conversion is based on an API?

If the API breaks, we could see errors in code paths that don't appear to ever call the currency conversion code at all! All we see is us adding two money values. Worse, the error wouldn't indicate what line in our program actually caused the problem without a full backtrace or using xDebug on production! If we mock the API endpoint in our dev environment, we may not even be able to reproduce the error at all.

How clever your code should be is not something there is a single answer to. For instance, a library that does unit values with automatic conversions may have the *expectation* that it would automatically handle such things with operator calls. But if the operator does some kind of work that wouldn't typically be expected by someone using the operator in code, it's probably being too clever.

# Rule 3: Don't Allow Ambiguity

Continuing with the money example we used in the last section, what if we allowed the operator to accept both `int` and `float` also? Seems like a great idea.

```php
currency != $this->currency) {
throw new Exception('Currencies must match');
}
$otherValue = $other->value;
} else {
$otherValue = $other;
}

$newValue = $this->value + $otherValue;

return new Money($newValue, $this->currency);
}
}
```

Great! We even fixed the problem of different currencies from Rule 2. Or... did we?

```php
> $nextItem;
```

There are several problems with this usage of operator overloads. First, the operator's meaning is reversed if the queue is on the right instead of the left: `$queue >> $item` vs. `$item >> $queue`. Second, you cannot pass the `$other` parameter of the operator overload by-reference, making it difficult to assign values to the variable. Third, it is very difficult to implement this behavior without violating Rule 1.

In general, if you need to operate on the *reference* of the other operand, you are probably using operator overloads incorrectly as designed in this RFC.

# Rule 6: Inherited Operators May Cause Trouble

Having operator implementations which are used by child classes may lead to unexpected results. Because of this, it is suggested that operator implementations which are *intended* for inheritance use the `abstract` keyword and leave the implementation up to child classes. Consider the following:

```php
value + $other->value));
}
}

class Fraction extends Number {
public function __construct(public $numerator, public $denominator) {}
}
```

In the above example, the `Fraction` class extends `Number`, since it is in fact a kind of number. However, the way in which it adds to other numbers is different. It needs to first find a common denominator (probably through some implementation of a Least Common Multiple), add the numerators, and then reduce the fraction (probably through some implemntation of a Greatest Common Divisor).

We could fix this by giving `Fraction` its own implementation:

```php
numerator;
$otherDenominator = $other->denominator;

// Do fraction conversions, add numerators, reduce fraction

return new Fraction($newNumerator, $newDenominator);
}
}
```

However, what happens if we end up with a line like this?

```php