6

Is there a way in PHP to use a function which has optional parameters in its declaration where I do not have to pass an optional arguments which already have values declared and just pass the next argument(s) which have different values that are further down the parameter list.

Assuming I have a function that has 4 arguments, 2 mandatory, 2 optional. I don't want to use null values for the optional arguments. In usage, there are cases where I want to use the function and the value of the 3rd argument is the same as the default value but the value of the 4th argument is different.

I am looking for a not so verbose solution that allows me to just pass the argument that differs from the default value without considering the order in the function declaration.

  createUrl($host, $path, $protocol='http', $port = 80) {
    //doSomething
    return $protocol.'://'.$host.':'.$port.'/'.$path;
  }

I find myself repeating declaring variables so that I could use a function i.e to use $port, I redeclare $protocol with the default value outside the function scope i.e

$protocol = "http";
$port = 8080;

Is there any way to pass the 2nd optional parameter($port) without passing $protocol and it would "automatically" fill in the default value of $protocol i.e

 getHttpUrl($server, $path, $port);

This is possible in some languages like Dart in the form of Named Optional parameters.See usage in this SO thread. Is their a similar solution in PHP

Blacksmith
  • 712
  • 8
  • 21
  • no, not that I know. BUT you can 'overload' functions. Meaning don't declare paramters, send those you need and use [func_get_args](http://php.net/manual/en/function.func-get-args.php). This obviously only makes sense when using different types as parameters. – Jeff Aug 28 '18 at 19:44
  • 1
    I've been similarly curious about this, but that's also where the importance of ordering your parameters comes in when designing the functions/methods. – kchason Aug 28 '18 at 19:44
  • 1
    I feel like this is not your REAL problem, but you are using this as an this adding example, just as an example. There are ways to fix the problem you are showing us here, but it might not be possible to apply that solution to your real problem. Can you clarify a bit? – Severin Aug 28 '18 at 19:45
  • 1
    another possibility is using [Variable-length argument lists](http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list) – Jeff Aug 28 '18 at 19:49
  • Sorry @Jeff posted before I saw your comment. – thecoolestguyever123 Aug 28 '18 at 19:51
  • @Luminoslty I guess is was simultaniously, no prob! – Jeff Aug 28 '18 at 19:55
  • 1
    @SeverinDK will edit and clarify – Blacksmith Aug 28 '18 at 19:57
  • @jeff. Variable length arg lists still use order – Blacksmith Aug 28 '18 at 20:25
  • 1
    yes. a function (or you as a coder) have two possibilities to distiguish passed in parameters: order or type. Or you use a construction as @ChrisForrence described in his answer. So the answer to your question is: NO. But there are workarounds you can find in the answers. – Jeff Aug 28 '18 at 21:01
  • @kchason, I have adapted one of the answers using associative arrays to solve this issue [link]https://stackoverflow.com/a/52067090/1180807 – Blacksmith Aug 29 '18 at 00:35

8 Answers8

4

You could potentially use a variadic function for this.

Example:

<?php

function myFunc(...$args){
    $sum = 0;
    foreach ($args as $arg) {
        $sum += $arg;
    }

    return $sum;
}

Documentation: http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list

3

PHP doesn't allow at this state to call functions parameters in the order we want.Maybe in the future it will.However you can easily achieve your purpose by using an associative array as the only argument, and then define, the default parameter in the function.For the call you will need to pass an array with only the values which interest you.This array will be merged with the default array.You can even implement required parameters and call them in any order you want. example:

    function mysweetcode($argument){
    $required=['first'];//specify required parameters here
    $default=['first'=>0,'second'=>1,'third'=>2];//define all parameters with their default values here
    $missing=[];
    if(!is_array($argument)) return false;
    $argument=array_intersect_key($argument,$default);
    foreach($required as $k=>$v){//check for missing required parameters
        if(!isset($argument[$v]))
            $missing[]=$v;
    }
    if(!empty($missing)){// if required are missing trigger or throw error according to the PHP version 
        $cm=count($missing);
        if (version_compare(PHP_VERSION, '7.0.0') < 0) {
            trigger_error(call_user_func_array('sprintf',
            array_merge(array('Required '.(($cm>1)?'parameters:':'parameter:').
            str_repeat('%s,',$cm).(($cm>1)?' are':' is').' missing'),$missing)),
            E_USER_ERROR);
        }else{
            throw new Error(call_user_func_array('sprintf',array_merge(
            array('Required '.(($cm>1)?'parameters:':'parameter:').
            str_repeat('%s',$cm).(($cm>1)?' are':' is').' missing'),$missing)));
        }
    }
    $default=array_merge($default,$argument);//assign given values to parameters
    extract($default);/*extract the parameters to allow further checking    
    and other operations in the function or method*/
    unset($required,$missing,$argument,$default,$k,$v);//gain some space 

    //then you can use $first,$second,$third in your code

    return $first+$second+$third;

}  

var_dump(mysweetcode(['first'=>9,'third'=>8]));//the output is 18

var_dump(mysweetcode(['third'=>8]));//this throws Error on PHP7 and trigger fatal error on PHP5 

You can check a live working code here

Elementary
  • 1,443
  • 1
  • 7
  • 17
  • This is in the direction Im looking at albeit I now have more typing using associative arrays – Blacksmith Aug 28 '18 at 21:35
  • Hi @AlbertM the answer has been updated ...Not in order to be marked again as the solution to your problem but to give you more idea for your future implementations...have a nice day. – Elementary Oct 07 '18 at 11:54
2

Well, this should work:

function myFunc($arg1, $arg2, $arg3=null, $arg4= null){
  if ( is_null( $arg3 ) && is_null( $arg4 ) {
    $arg3 = 3;
    $arg4 = 4;
  } else if ( is_null( $arg4 ) ) {
    $arg4 = $arg3;
    $arg3 = 3;
  }
    echo $arg1 + $arg2 + $arg3 + $arg4;
}

However I suggest you to rethink your problem (as a whole) because this is not a very good idea.

Łukasz
  • 89
  • 8
1

You could refactor this to use a parameter object; this way, you could include the default parameters in this object and set them in any order (with a trade-off of more verbose code). As an example using your above code,

<?php

class AdditionParameters
{
    private $arg1 = 0;
    private $arg2 = 0;
    private $arg3 = 3;
    private $arg4 = 4;

    public function getArg1() { return $this->arg1; }
    public function getArg2() { return $this->arg2; }
    public function getArg3() { return $this->arg3; }
    public function getArg4() { return $this->arg4; }

    public function setArg1($value) { $this->arg1 = $value; return $this; }
    public function setArg2($value) { $this->arg2 = $value; return $this; }
    public function setArg3($value) { $this->arg3 = $value; return $this; }
    public function setArg4($value) { $this->arg4 = $value; return $this; }
}

From there, you could simply call the function while passing in this new object.

function myFunc(AdditionParameters $request) {
    return $request->getArg1()
        + $request->getArg2()
        + $request->getArg3()
        + $request->getArg4();
}

echo myFunc((new AdditionParameters)->setArg1(1)->setArg2(2)->setArg4(6));
// or echo myFunc((new AdditionParameters)->setArg1(1)->setArg4(6)->setArg2(2));

Otherwise, PHP doesn't allow you to have named optional parameters. (e.g. myFunc(1, 2, DEFAULT, 4);)

Chris Forrence
  • 10,042
  • 11
  • 48
  • 64
0

You have the response in your question, you can declare your function like

function myFunc($arg1, $arg2, $arg3 = null, $arg4 = null){
    //here you check if the $arg3 and $arg4 are null
}

then you call your function using

myFunc($arg1, $arg2);
Carlos Salazar
  • 1,818
  • 5
  • 25
  • 48
  • 2
    but he/she want's to call `myFunc($arg1, $arg2, NOTHING, $arg4);` – Jeff Aug 28 '18 at 19:58
  • Thanks. I do not want to have null values. Looking for a way which I dont have to pass arguments that are already have default values. – Blacksmith Aug 28 '18 at 20:10
0

There is no such way in PHP(like in python for example).

You have to use some tricks in order to do that but will not always work. For example:

// creates instances of a class with $properties.
// if $times is bigger than 1 an array of instances will be returned instead.(this is just an example function)
function getInstance($class, $properties = [], $times = 1){
    //my code
}

$user = getInstance("User", ["name" => "John"]); // get one instance
$users = getInstance("User", ["name" => "John"],2); // get two instances.

If you want to use the function without passing the $parameters argument, like this:

$users = getInstance("User",2);

you can change the function to:

// creates instances of a class with $properties.
// if times is bigger than 1 an array of instances will be returned instead.
function getInstance($class, $properties = [], $times = 1){
    if(is_numberic($properties)){
        $times = $properties;
        $properties = [];
    }
    //my code
}

Of course, this strategy will work only if you parameters have different types.

PS. This method is use in the Laravel Framework a lot. From there I got the inspiration.

Ionel Lupu
  • 2,695
  • 6
  • 29
  • 53
0

This is modified from one of the answers and allows arguments to be added in any order using associative arrays for the optional arguments

 function createUrl($host, $path, $argument = []){
   $optionalArgs = [
    'protocol'=>'http',
    'port'=>80];
  if( !is_array ($argument) ) return false;
  $argument = array_intersect_key($argument,$optionalArgs);
  $optionalArgs = array_merge($optionalArgs,$argument);
  extract($optionalArgs);
  return $protocol.'://'.$host.':'.$port.'/'.$path;
 } 



  //No arguments with function call
  echo createUrl ("www.example.com",'no-arguments'); 
  // returns http://www.example.com:80/no-arguments

  $argList=['port'=>9000];
  //using port argument only
  echo createUrl ("www.example.com",'one-args', $argList); 
  //returns http://www.example.com:9000/one-args

  //Use of both parameters as arguments. Order does not matter
  $argList2 = ['port'=>8080,'protocol'=>'ftp'];
  echo createUrl ("www.example.com",'two-args-no-order', $argList2); 
  //returns ftp://www.example.com:8080/two-args-no-order
Blacksmith
  • 712
  • 8
  • 21
0

As of version 8.0, PHP now has named arguments. If you name the arguments when calling the function, you can pass them in any order and you can skip earlier default values without having to explicitly pass a value for them.

For example:

function createUrl($host, $path, $protocol = 'http', $port = 80)
{
    return "$protocol://$host:$port/$path";
}

createUrl(host: 'example.com', path: 'foo/bar', port: 8080);

// returns: "http://example.com:8080/foo/bar"
wunch
  • 1,092
  • 9
  • 12