{"id":15952293,"url":"https://github.com/jordanrl/operator-overloads-in-php","last_synced_at":"2026-03-19T03:12:24.960Z","repository":{"id":40595811,"uuid":"443353864","full_name":"JordanRL/operator-overloads-in-php","owner":"JordanRL","description":"A quick guide to operator overloads in PHP from the person who wrote the feature","archived":false,"fork":false,"pushed_at":"2022-09-11T21:20:03.000Z","size":75,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-06-17T01:06:56.798Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JordanRL.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-12-31T13:40:38.000Z","updated_at":"2022-01-10T13:39:49.000Z","dependencies_parsed_at":"2022-09-18T04:02:49.213Z","dependency_job_id":null,"html_url":"https://github.com/JordanRL/operator-overloads-in-php","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/JordanRL/operator-overloads-in-php","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanRL%2Foperator-overloads-in-php","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanRL%2Foperator-overloads-in-php/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanRL%2Foperator-overloads-in-php/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanRL%2Foperator-overloads-in-php/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JordanRL","download_url":"https://codeload.github.com/JordanRL/operator-overloads-in-php/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JordanRL%2Foperator-overloads-in-php/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28987278,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-01T18:17:03.387Z","status":"ssl_error","status_checked_at":"2026-02-01T18:16:57.287Z","response_time":56,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-07T13:08:00.165Z","updated_at":"2026-02-01T19:33:20.217Z","avatar_url":"https://github.com/JordanRL.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introduction\n\nOperator 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.**\n\n**NOTE:** This feature has not been accepted as an RFC yet.\n\n# Rule 1: All Overloads Must Be Immutable\n\nIt 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.\n\n## Example 1 (Mutable Implementation)\n\n```php\n\u003c?php\n\nclass SomeClass {\n  protected int $somevar = 0;\n  \n  public function increment(): SomeClass {\n    $this-\u003esomevar++;\n    \n    return $this;\n  }\n}\n```\n\n## Example 2 (Immutable Implementation)\n\n```php\n\u003c?php\n\nclass SomeClass {\n  public function __construct(protected int $somevar) {}\n  \n  public function increment(): SomeClass {\n    $newObj = new SomeClass($this-\u003esomevar + 1);\n    \n    return $newObj;\n  }\n}\n```\n\nThese 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*.\n\nSo why is this so important for operator overloads? *Because programmers expect operators to do things immutably ALWAYS.*\n\nConsider what we expect the value of `$x` to be throughout this code:\n\n```php\n\u003c?php\n\n$x = 0; // We expect $x to be 0\n\n$x = $x + 2; // We expect $x to be 2\n\n$y = $x + 5; // We expect $x to be 2\n\n$x += 3; // We expect $x to be 5\n\n++$x; // We expect $x to be 6\n\n$z = $y + 3; // We expect $x to be 6\n```\n\nNow consider what would *actually* happen if $x behaved like a mutable object:\n\n```php\n\u003c?php\n\n$x = 0; // $x == 0\n\n$x = $x + 2; // $x == 2\n\n$y = $x + 5; // $x == 7, $y == 7 (is a reference to $x)\n\n$x += 3; // $x == 10\n\n++$x; // $x == 11\n\n$z = $y + 3; // $x == 14, $y == 14, $z == 14 ($y and $z are references to $x)\n```\n\nSo this is the first rule of operator overloads: you cannot implement them mutably. That means that you cannot have *any* lines in the operator method that look like `$this-\u003eproperty = 'somevalue'`.\n\nThe 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.\n\n# Rule 2: Don't Be Too Clever\n\nIt 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.\n\nConsider the following:\n\n```php\n\u003c?php\n\nclass Money {\n  public function __construct(readonly public float $value, readonly public string $currency) {}\n  \n  public function getConvertedValue($currency): float {\n    // Perform currency conversion\n  }\n  \n  operator +(Money $other, OperandPosition $operandPos) {\n    if ($other-\u003ecurrency != $this-\u003ecurrency) {\n      if ($operandPos == OperandPosition::LeftSide) {\n        $newCurrency = $this-\u003ecurrency;\n        $newValue = $this-\u003evalue + $other-\u003egetConvertedValue($newCurrency);\n      } else {\n        $newCurrency = $other-\u003ecurrency;\n        $newValue = $this-\u003egetCovertedValue($newCurrency) + $other-\u003evalue;\n      }\n    } else {\n      $newCurrency = $this-\u003ecurrency;\n      $newValue = $this-\u003evalue + $other-\u003evalue;\n    }\n    \n    return new Money($newValue, $newCurrency);\n  }\n}\n```\n\nSo 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?\n\nIf 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.\n\nHow 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.\n\n# Rule 3: Don't Allow Ambiguity\n\nContinuing 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.\n\n```php\n\u003c?php\n\nclass Money {\n  public function __construct(readonly public float $value, readonly public string $currency) {}\n  \n  public function getConvertedValue($currency): float {\n    // Perform currency conversion\n  }\n  \n  operator +(Money|int|float $other, OperandPosition $operandPos) {\n    if ($other instanceof Money) {\n      if ($other-\u003ecurrency != $this-\u003ecurrency) {\n        throw new Exception('Currencies must match');\n      }\n      $otherValue = $other-\u003evalue;\n    } else {\n      $otherValue = $other;\n    }\n    \n    $newValue = $this-\u003evalue + $otherValue;\n    \n    return new Money($newValue, $this-\u003ecurrency);\n  }\n}\n```\n\nGreat! We even fixed the problem of different currencies from Rule 2. Or... did we?\n\n```php\n\u003c?php\n$money = new Money($_GET['amount'], $_GET['currency']);\n\n$money += 5; // $5 fee added\n\n...\n```\n\nIt seems like this should work, except... it *won't* add $5, it'll add 5 in whatever currency `$money` happens to be in! If it's *in* dollars then it will work as intended, but what if it's in Euros, or Pounds, or Yuan, or Yen? Adding 5 Yen instead of 5 dollars would mean that our result is off by quite a lot! $1 US is about 100 Yen, so in that case we should be adding nearly 500 instead.\n\nSo couldn't we add a conversion factor for ints and floats? We could. It would have some of the same problems explored in Rule 2, but it's possible. However, that assumes that all developers are programming in the same currency as you. What happens if one of your colleagues thinks in a different currency? What if they get assigned a feature to add a 25 Ruble surcharge, and don't realize that the program assumes all integers are in dollars?\n\nIn this case, integers and floats are missing an important dimension: the currency. It's unsafe to assume the intended currency of a particular integer or float. That's in fact the whole *point* of the Money class, to supplement the integer and float types because they don't capture this information.\n\n# Rule 4: Supporting Implied Operators Is Non-Optional\n\nThe reassignment operators, such as `+=` and `*=` are referred to as \"implied operators\" in the RFC. The reason for this is that these operators are optimized within the PHP engine separate from this RFC.\n\n```php\n\u003c?php\n\n$x += 5; // My code contains the reassignment operator\n```\n\nHowever, the PHP engine actually executes this line as:\n\n```php\n\u003c?php\n\n$x = $x + 5; // The operation that is executed\n```\n\nThe two lines are not 100% equivalent, as a different part of the VM is run for both of the lines above. The difference is that for the `$x += 5` line, the engine *first* executes logic that is specific to reassignment operators (such as allocating temporary values) and *then* calls the normal opcode for the `+` operator.\n\nThis is also the case for post-increment and pre-increment, `++$x` and `$x++`. In these lines, the engine *first* executes code that is specific to the increment and *then* calls the normal operator. For the post-increment line, it sets the return value of the line *before* calling the normal operator, while for the pre-increment line it sets it after.\n\nAll of the operators that are listed under \"implied operators\" are non-optional. Not only is there no way to overload the normal operators *without* overloading the coresponding implied operators, but doing so would involve removing or significantly altering many optimizations within the PHP engine that are unrelated to operator overloads.\n\nBecause of this, you must design your operator overloads with the understanding that you cannot prevent the implied operators from being supported if you support the associated normal operator.\n\n**NOTE**: By not accepting integers in the operator overload, you can cause the `++$x` and `$x++` lines to result in an error. \n\n# Rule 5: Operands Are Never Passed By Reference\n\nLets look at a usage of the operator overloads that would probably be contraversial among many PHP developers:\n\n```php\n\u003c?php\n\n$item = new Item();\n$queue = new Queue();\n\n$queue = $queue \u003c\u003c $item;\n```\n\nThis code attempts to use the bitwise shift left operator to put an item into a queue. If you did something like this, it would follow that you could use the bitwise shift right operator to pull the next item out of the queue.\n\n```php\n\u003c?php\n\n$queue = $queue \u003e\u003e $nextItem;\n```\n\nThere 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 \u003e\u003e $item` vs. `$item \u003e\u003e $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.\n\nIn 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.\n\n# Rule 6: Inherited Operators May Cause Trouble\n\nHaving 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:\n\n```php\n\u003c?php\n\nclass Number {\n  public function __construct(public $value) {}\n  \n  operator +(Number $other, OperandPosition $operandPos): Number {\n    return new Number(bcadd($this-\u003evalue + $other-\u003evalue));\n  }\n}\n\nclass Fraction extends Number {\n  public function __construct(public $numerator, public $denominator) {}\n}\n```\n\nIn 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).\n\nWe could fix this by giving `Fraction` its own implementation:\n\n```php\n\u003c?php\n\nclass Fraction extends Number {\n  operator +(Number $other, OperandPosition $operandPos): Number {\n    $otherNumerator = $other-\u003enumerator;\n    $otherDenominator = $other-\u003edenominator;\n    \n    // Do fraction conversions, add numerators, reduce fraction\n    \n    return new Fraction($newNumerator, $newDenominator);\n  }\n}\n```\n\nHowever, what happens if we end up with a line like this?\n\n```php\n\u003c?php\n\n$answer = $number + $fraction;\n```\n\nWe'll still get the wrong answer, because the operator overload for the `Number` class will be checked first, as it's the left operand. There is a planned follow-up RFC, \"Polymorphic Operator Handler Resolution\" that aims to solve this. It would call the `Fraction` overload first, since it is a child class of `Number`. \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordanrl%2Foperator-overloads-in-php","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjordanrl%2Foperator-overloads-in-php","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjordanrl%2Foperator-overloads-in-php/lists"}