upscale / stdlib-overloading
PHP7 function/method arguments overloading
Requires
- php: >=7.1
Requires (Dev)
- phpunit/phpunit: ^7.0
This package is auto-updated.
Last update: 2024-10-18 06:59:18 UTC
README
This library introduces function/method overloading – varying implementation depending on input arguments.
Features:
- Overloading by argument types
- Overloading by number of arguments
- Efficient native type checks of PHP7
- Informative native error messages of
TypeError
- Lightweight: no OOP, no Reflection
Installation
The library is to be installed via Composer as a dependency:
composer require upscale/stdlib-overloading
Usage
Syntax
Overload a custom function/method:
<?php declare(strict_types=1); use function Upscale\Stdlib\Overloading\overload; function func(...$args) { return overload( function (int $num1, int $num2) { // ... }, function (string $str1, string $str2) { // ... } // ... )(...$args); }
Any number of valid callable
implementations can be declared. Order defines evaluation priority.
Call the overloaded function:
func(1, 2); func('a', 'b');
Example
Arithmetic calculator that works with ordinary integers, arbitrary-length GMP integers, and Money objects.
<?php declare(strict_types=1); use function Upscale\Stdlib\Overloading\overload; class Money { private $amount; private $currency; public function __construct(int $amount, string $currency) { $this->amount = $amount; $this->currency = $currency; } public function add(self $sum): self { if ($sum->currency != $this->currency) { throw new \InvalidArgumentException('Money currency mismatch'); } return new self($this->amount + $sum->amount, $this->currency); } } class Calculator { public function add(...$args) { return overload( function (int $num1, int $num2): int { return $num1 + $num2; }, function (\GMP $num1, \GMP $num2): \GMP { return gmp_add($num1, $num2); }, function (Money $sum1, Money $sum2): Money { return $sum1->add($sum2); } )(...$args); } } $calc = new Calculator(); $one = gmp_init(1); $two = gmp_init(2); $oneUsd = new Money(1, 'USD'); $twoUsd = new Money(2, 'USD'); print_r($calc->add(1, 2)); // 3 print_r($calc->add($one, $two)); // GMP Object([num] => 3) print_r($calc->add($oneUsd, $twoUsd)); // Money Object([amount:Money:private] => 3 [currency:Money:private] => USD) print_r($calc->add(1.25, 2)); // TypeError: Argument 1 passed to Calculator::{closure}() must be an instance of Money, float given
Architecture
The overloading mechanism leverages the native type system of PHP7 and relies on declaration of strict type annotations. It traverses implementation callbacks in the declared order and attempts to invoke each of them with provided arguments. Result of the first compatible invocation is returned and the subsequent callbacks discarded.
Limitations
PHP engine allows to pass more runtime arguments to a function/method than accounted for in its signature declaration.
Excess arguments are being silently discarded without triggering any catchable errors, not even ArgumentCountError
.
The solution is to declare more specific longer signatures before less specific ones with matching subset of arguments.
Optional arguments are problematic as they are call-time compatible with a shorter signature of required arguments only. Consider the following ambiguous declaration that cannot be resolved by reordering:
overload( function (int $num1, int $num2) { // ... }, function (int $num1) { // ... }, function (int $num1, string $str2 = 'default') { // Unreachable because preceding declaration matches first and swallows excess arguments // ... } )
The workaround is to validate the number of arguments to not exceed the declaration:
overload( function (int $num1, int $num2) { // ... }, function (int $num1) { if (func_num_args() > 1) { throw new \ArgumentCountError('Too many arguments provided'); } // ... }, function (int $num1, string $str2 = 'default') { // Reachable with the optional argument passed, but still unreacable without it // ... } )
Contributing
Pull Requests with fixes and improvements are welcome!
License
Copyright © Upscale Software. All rights reserved.
Licensed under the Apache License, Version 2.0.