# Named arguments in PHP 5 and 7

Unlike many languages, PHP (before version 8) does not allow to pass arguments by their name. Here is a generic adapter to add this possibility to all your classes.

A while ago I was rereading a piece of code and came across an original way to pass arguments, using an array. Something like this:

$res =$obj->method(["foo" => "bar"]) ;

Since this is the first time that I have encountered this idiom in this code, I go to see the called method which I find easily and basically works as follows:

class SomeClass
{

public function method($params) { // Get Parameters$foo = $params["foo"] ?: "default" ;$bar = $params["bar"] ?: "default" ; // Compute result return$foo . $bar ; } } I'm amazed that this function doesn't just declare two arguments with their types and defaults and I'm already imagining all the maintenance risks it implies... • How can I easily know which arguments are valid since there is no documentation? How to ensure that the documentation is up to date elsewhere? • How to ensure that the codes calling this method are up to date if this list of parameters were to change? • What should happen if a caller adds unexpected parameters? • If type checks are necessary, they must be added, which complicates the code... So, because I guess the author of these lines had good reasons for writing them the way he did, I go hunting for information and ended up finding the reason for this idiom: It is to pass them by names, as in python. I would prefer to write method(foo: "bar") but php does not allow it. Tintin (not his real name) And yes, unlike other languages like python, C# and others, PHP (5 and 7) does not allow to pass arguments by names. A feature we miss a few times. So without further introduction, here is a method to add this feature to PHP without touching the called functions! # Example of class to adapt Before we start, here is an example of a PHP class that we are going to adapt. Since the only purpose of this class is to serve as an example, its methods do nothing useful. class Sample { public static function logMsg($foo = "foo", $bar = "bar",$method = null)
{
echo ($method ?: "logMsg") ; echo " - foo =>$foo" ;
echo " - bar => $bar\n" ; } public$foo ;
public $bar ; public function __construct($foo = "foo", $bar = "bar") {$this->foo = $foo ;$this->bar = $bar ; self::logMsg($foo, $bar, "__construct") ; } public function doStuff($foo = "foo", $bar = "bar") { self::logMsg($foo, $bar, "doStuff") ; } public function __toString() { return "{foo : {$this->foo}, bar : {$this->bar}}" ; } } # A methods to adapt them all So let's start with the method common to all cases, and which takes care of the heart of the matter: managing the array in arguments and call the method you want. For that, we will use the PHP reflection API and in particular, the ReflectionMethod class which allows you to manipulate the methods as if they were objects. function invokeWithArray($method, $instance,$parameters)
{
$args = [] ; foreach ($method->getParameters() as $param) {$name = $param->getName() ; if (isset($parameters[$name])) {$args[$name] =$parameters[$name] ; unset($parameters[$name]) ; } else if ($param->isDefaultValueAvailable()) {
$args[$name] = $param->getDefaultValue() ; } else { throw new \Exception("parameter missing") ; } } if (count($parameters) > 0) {
throw new \Exception("Too many arguments") ;
}

// Appel de la méthode avec les paramètres
return $method->invokeArgs($instance, $args) ; } Of course, we could imagine improving this function by adding type management (with something like instance of$params->getType()) but that weighs down the example and will be done anyway during the call to invokeArgs ().

With this you can already call any method. You get the corresponding ReflectionMethod then make your call.

$refClass = new ReflectionClass(get_class($obj) ;
$refMethod =$refClass->getMethod("method") ;
$res = invokeWithArray($refMethod, $obj, ["foo" => "bar"]) ; But that's not enough yet. If the goal was to simplify, it is a bit of a failure... We will therefore improve the system. # A class to adapt them all To easily adapt any class, we will define an abstract class which will serve as a basis for all the others. And to avoid having to define all the possible methods, we use the magic methods of the PHP, namely: • __call() which is called when a method called on an object does not exist. The first parameter is the name of the method, the second is the array containing the parameters. • __callStatic() which does the same thing but for static class methods. • __invoke() which allows to use the object as a function. I'm using it to dereference the adapter and get the inner object, just in case. abstract class ArrayAdapter { abstract public static function getReflectionClass() ; private$target ;

public function __construct($args) {$class = static::getReflectionClass() ;
$this->target =$class->newInstanceWithoutConstructor() ;

$ctor =$class->getConstructor() ;
invokeWithArray($ctor,$this->target, $args) ; } public function __call($name, $args) { if (count($args) != 1) {
throw new \Exception("Wrong number of arguments") ;
}
$class = static::getReflectionClass() ;$method = $class->getMethod($name) ;
return invokeWithArray($method,$this->target, $args[0]) ; } public static function __callStatic($name, $args) { if (count($args) != 1) {
throw new \Exception("Wrong number of arguments") ;
}
$class = static::getReflectionClass() ;$method = $class->getMethod($name) ;
return invokeWithArray($method, null,$args[0]) ;
}

public function __invoke()
{
return $this->target ; } } To build an adapter for a class (i.e. the example one), it suffices to create a class which extends the ArrayAdapter and to define the getReflectionClass() method. Something like this: class AdaptedSample extends \ArrayAdapter { public static function getReflectionClass() { return new \ReflectionClass("Sample") ; } } It's already better, but if we have to create a new class every time, it’s not going to clean the code. This is where PHP goes beyond a lot of languages, you can automate all of that. # A loader to make it magic To adapt any class without having to write any code, we are going to use three other very practical features of PHP: The idea is therefore to define a namespace dedicated to adapters (e.g. \\Adapted\\ in our case) in which the classes will be built automatically when needed. spl_autoload_register(function ($name) {

if (strpos($name, "Adapted") !== 0) { return ; }$parts = explode("\\", $name) ;$classname = array_pop($parts) ;$namespace = implode("\\", $parts) ;$targetClass = str_replace("Adapted\\", "", $name) ; // Here be dragons eval(" namespace$namespace ;
class $classname extends \ArrayAdapter { public static function getReflectionClass() { return new \ReflectionClass('$targetClass') ;
}
}
") ;

}) ;

With that, you don't even have to write code to adapt your methods anymore, the autoloader does it for you :).

use Adapted\Sample ;

$a = new Sample(["bar" => "new"]) ; // __construct - foo => foo - bar => new$a->doStuff(["bar" => "new"]) ;
// doStuff     - foo => foo - bar => new

Sample::logMsg(["bar" => "new"]) ;
// logMsg      - foo => foo - bar => new

echo \$a() . "\n";
// {foo : foo, bar : new}

# And after ?

The interest of languages interpreted on compiled ones is, among other things, their ease of introspection. As seen here, it is possible to write code that will automatically adapt to any code.

In this case, this allows us to add a missing functionality to the language, passing parameters by names. We can then pass them in the order we want and the calling code will be more readable since the name of the parameter is indicated directly.

But this possibility of named parameters is dangerous for me because it masks a deeper problem of maintainability by making it easier to define methods with a large number of parameters. This type of programming should therefore only be used if no cleaning of the code is possible.