PHP: Optional Parameters Out of Order

PHP allows us to define optional parameters for functions by providing a default value for those parameters. It is a useful technique if we know that we will be using the default value the majority of the time. By making it optional we save ourselves the effort of providing that value, except for the rare times we need to use something different than the default. And if we ever need to change that default value we only need to modify the function definition, not every single place we call the function.

But PHP allows us to write optional parameters in an ‘incorrect’ way, which is what we’ll look at today.

Our Simple Example

First we will begin with an example of optional parameters. Let’s say we have a program that defines a lot of error codes as integers. We have a global array called $ERROR_MESSAGES[] that has strings for each error code. So if we have the error code 3 we can log that error with an appropriate message by using the string $ERROR_MESSAGES[3].

We want to write a function that accepts an error code and sends the appropriate message to a log. But sometimes we will also want to add some additional information, so we want the function to accept an optional string to add to the error message. We can write that function like so:

/**
 * This function accepts an error code as an integer and sends an
 * appropriate error message to our logs.  If we want to put
 * additional information in the log we can pass a string as an
 * optional, second parameter.  This function returns a boolean that
 * is true if we successfully logged the error, and false if we could
 * not log the error.
 */
function log_error_for_code($error_code, $additional_info = "")
{
        global $ERROR_MESSAGES;

        $message = $ERROR_MESSAGES[$error_code];

        /* If we have extra info to add we make sure to stick a space
         * between it and the normal error message for readability.
         */
        if ($additional_info)
        {
                $message = sprintf("%s: %s", $message, $additional_info);
        }

        return error_log($message);
}

Now we can use the function like so:

// The usual way we intend to use the function.
log_error_for_code(10);

// An example of providing extra info with the optional parameter.
log_error_for_code(15, sprintf("Missing user id %d", $user->id));

But this is not the only way we could define the function.

The Incorrect Way

Earlier I said PHP allows us to write optional parameters ‘incorrectly’. I quote the word because whether or not PHP treats it as incorrect depends on error-related settings. First let’s look at an example. We will take the function we wrote above and reverse the order of the parameters.

function log_error_for_code($additional_info = "", $error_code)
{
        global $ERROR_MESSAGES;

        $message = $ERROR_MESSAGES[$error_code];

        if ($additional_info)
        {
                $message = sprintf("%s: %s", $message, $additional_info);
        }

        return error_log($message);
}

Because we changed the order of parameters, placing the optional parameter first, we have to flip the arguments in one of our example uses.

// This still works as expected.
log_error_for_code(10);

// But here we must put the additional info first now.
log_error_for_code(sprintf("Missing user id %d", $user->id), 15);

It may surprise some programmers, but this code works in PHP. Usually.

By default PHP issues a warning for functions that put optional parameters before required parameters. Warnings will not stop the program from running. But they are a sign we should reconsider what we are doing, and personally I believe it is best to treat all warnings as if they are serious errors. This is why I quoted ‘incorrect’ earlier; PHP lets us get away with this by default.

However, if we enable all errors, e.g. by using error_reporting(E_ALL) in our code, then any function that uses optional parameters before required parameters generates a fatal error that brings the program to a halt. No need to quote incorrect in that context. When using E_ALL our code is flat-out wrong.

Sometimes we will see PHP code like this out in the wild, with functions that place optional parameters first. We should not write this type of code. The fact it generates warnings and even fatal errors is a strong sign that we should always place optional parameters after all required parameters. Then there is also the fact that other programming languages which allow optional parameters tend to enforce the same practice: required parameters first, optional parameters after that.

Optional parameters are a useful tool, but let’s make sure we always keep them in the best order.

Advertisements

4 thoughts on “PHP: Optional Parameters Out of Order

  1. Nice article, but:

    1. Why are you using the “global” keyword? I used to use it in the past, but I started receiving a lot of hate mail from colleagues.

    2. Why did you use the sprintf() function while you could have just “echoed” the error message?

    3. When are you going to write another PHP article?

  2. 1. If I did not use ‘global’ then the function would not be able to see the global variable where I am storing the error messages. PHP, unlike many languages, forces you to be explicit when you want to access a variable in global scope from inside a function. If I removed ‘global’ then the language would think I was trying to use a non-existent variable of the same name inside that function. As for the hate mail, global variables are one of those things which a lot of programmers love to hate. You will hear many variations of, “global variables are evil and you should never ever use them!” There is some useful wisdom behind that sentiment, but everything has its proper use, including global variables. I do believe that programmers should avoid using global variables as much as possible; it is easier to debug code when you reduce the amount of possible data that code may be interacting with, and it helps prevent bugs caused by situations like different functions stomping over the same data in unexpected ways. But just because you can cause problems with global variables does not mean you should never use them. For example, in one program I use a global array of color names so that throughout the rest of the program I can refer to colors by names like “LightBlue” instead of having to always write RGB values as integers; in my opinion that is a useful example of a global variable that helps improve the readability of the code. So basically, try to avoid global variables as much as possible, but if a global variable is the best choice for a situation then use a global variable. Use the right tools for the job.

    2. That is just a stylistic choice. When I am building a string by combining other strings and data, I personally find it easier to read and write the code by using functions like sprintf(), because that lets me write the ‘template’ for my string with place-holders for where I insert the other elements. I think of it as the ‘building a string’ equivalent of using prepare() from PDO to build a database query with place-holders, instead of concatenating strings together. Sometimes I do use echo though. So it is really just a personal choice depending on what I feel would look the most readable.

    3. Soon. Later today I plan on posting my thoughts about maintaining the php-mode project. But I also have plans to write some articles specifically about the language in the near future, such as writing stream wrappers and using the reflection API.

    1. Thank you for your nice reply.

      Indeed, programmers really hate global variables :D

      So you use sprintf() when you concatenate variables to strings and you use echo when you just output a normal string without concatenated variables?
      What are the other advantages of sprintf() that you prefer it over echo, minding the concatenation?

  3. “So you use sprintf() when you concatenate variables to strings and you use echo when you just output a normal string without concatenated variables?”

    That’s my habit, yes. Sometimes I do concatenate things while using echo, so it’s not an absolute rule or anything.

    “What are the other advantages of sprintf() that you prefer it over echo, minding the concatenation?”

    Using sprintf() makes it more explicit what I expect from the output since the function uses different ‘type specifiers’ for content. For example, using %s makes it clear that I intend to insert a string there; %f indicates a floating-point number, %x a hexadecimal number, and so on. Those specifiers not only let me be more explicit about what I’m including in the string, they also let me control the format of the output by specifying things like padding, precision, alignment, etc. That is particularly useful for numbers. For example, sprintf("%04b", $num) will create a string with $num in it, in binary notation, with no less than four digits, e.g. sprintf("%04b", 2) becomes the string "0010". Enforcing that kind of formatting would require a lot more effort if I only used echo.

Add Your Thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s