Using Named Parameters in PHP Function Calls

Published:
Updated:
Introduction
This article is about a PHP programming practice that can help reduce errors and improve the readability of your PHP programs.  It’s nothing revolutionary — just a new way of looking at one of the problems that has been a part of PHP since the beginning.  Before we jump into the code, let’s start with a little background and context.

Why We Use Programming Languages
Programming languages exist for one purpose and one purpose only: to help you write good programs.  Good programs are those that are bug-free, functional, easy to use, easy to read and understand, and that meet the client’s requirements, preferably in a way that delights the client.  Not-so-good programs are those that fail to measure up in some way, and they are the vast majority of all programs ever written.  Obviously every programming language is a human creation and so each language has its good parts and its bad parts, according to choices made by the language developers at the time they designed the software.  In the case of modern programming languages, the time of design may have been quite a long time ago.  Some of the assumptions of that day may no longer apply today.  And it follows that some of the original design assumptions no longer help us write good programs.  They can even hinder our efforts.

Design Choices
Before we go into our design for a PHP programming practice let’s look at one of those long-ago design choices that made sense at the time, and though it caused a little trouble then, it created a great saving, but unreasonably plagues us today.  This not so much a programming design flaw, as much as a circuitry design flaw, but it helps to make the point about context in design.  It is the use of two’s complement notation for negative numbers.  A positive number is represented by a storage element (byte, word, etc) that has a zero in the high-order bit.  A negative number is represented by a one in the high-order bit.

0111 1111 = positive, but if you add one to that number...
1000 0000 = negative

Where this becomes troublesome is when we begin adding to contextually large numbers.  In a byte context, if you add one to +127, and the sensible answer would appear to be +128, but it doesn’t work that way.  Instead the computer tells us that the resulting number is negative, the most distant possible erroneous value that can occur.  Why would anyone create a system like this?

The answer goes back to the 1950’s when intelligent engineers were looking at the overall design of computer circuits.  In those days memory and circuitry were very, very expensive.  So in an enlightened self-interest, the decision was made to eliminate one of the arithmetic operations from the computer design.  By removing the circuitry for subtraction, many dollars were saved on every machine built.  The only adverse consequence was the risk of negative values on overflow (and a few related issues that could cause the high-order bit to be set accidentally).  It was a sensible tradeoff, given the facts of the day. And they can be forgiven -- nobody knew about Moore's Law yet.

What Was Once Wise...
Today, memory and circuitry are astonishingly inexpensive — the cell phone in your pocket has more computing power than all of NASA had when we put a man on the moon.  But we still live with that early two’s complement design choice, and it still causes program failures that are data-dependent, impossible to test for, and embody the worst characteristics of “spooky action at a distance.”

Lessons Learned and Forgotten
There are similar design flaws and unlearned lessons in modern programming languages.  These are things we should have learned, but didn’t.  Or maybe we learned them, but forgot them along the way.  One of these is the use of named parameters.  They are all around us, but somehow we left them out of the design of PHP function definitions.

One of the obvious places we find named parameters is in the design of the URL.  Look at this URL, which might be used to search for some kind of product information:

/page.php?user=Ray&id=37&group=4A&year=2015&month=05&size=medium&color=red

Now close your eyes and try to say the required order of the URL parameters.  Does color come before size?  Does id come before ion?  Why does year come before month?  Or does it?

The answer is simple:  These are irrelevant questions because the URL parameters have names.  They are not positional, so their order and presence or omission is not important — a valid URL can contain some or all of these parameters in any order.

Now imagine how difficult it would be to remember a URL that had to look like this:

/page.php?Ray&37&4A&2015&05&medium&red

What’s worse, imagine if the URL would not work because you wrote it this way!

/page.php?Ray&37&4A&05&2015&medium&red

PHP Uses Positional Arguments
In addition to URLs, we had named parameters in many programming languages, too.  You’ve got to wonder why PHP function calls lost this design element.  A PHP function definition for that URL might look like this:
function page($user, $id, $group, $year, $month, $size, $color) { /* PHP */ }

Open in new window


However it could also look like this:
function page($u, $i, $g, $y, $m, $z, $c) { /* PHP */ }

Open in new window


The point here is that the PHP function arguments are positional, not named, and the variable names used in the function signature are irrelevant, so long as they are used consistently inside the function code.  You want them to be meaningful for the benefit of readability, but there is no programmatic binding between the names in the function definition and the names used in the function call -- the only binding is the position of the data elements.  And when there are omitted parameters, your function call has to account for the positional nature of these missing fields with something like this:
$page = page($user, NULL, $group, NULL, NULL, $size, $color);

Open in new window


Earlier attempts to solve the positional problem did not work out perfectly.

To reduce the risk of confusion associated with positional parameters, PHP might some day introduce named parameters, but we don’t have to wait for that unicorn.  With a little creativity we can get all of the benefits of named parameters with a simple design practice today.  It requires no special features and will work correctly for any PHP 5+.  Here is what you want to do.

Instead of passing numerous positional parameters to your PHP function definition, pass a single PHP object.  Your function definition will look something like this:
function page($args) { /* PHP */ }

Open in new window


The object contained in the $args variable will have named properties that reflect the inputs to the page() function.  Here is how we might set up that object:
Class RequestArguments
                      {
                          public function __get($x)
                          {
                              return NULL;
                          }
                      }

Open in new window


Yes, it really is that simple.  Then we can inject the properties into the object created from the RequestArguments class.  Since class properties are named, we obtain the advantages of named, not positional, arguments.  Our programming instantly becomes more fluent and easy to read.  Compare these two scenarios:

The old way:
function page($u, $i, $g, $y, $m, $z, $c) {
                          echo PHP_EOL . "User: $u";
                          echo PHP_EOL . "Id: $i";
                          echo PHP_EOL . "Group: $g";
                          echo PHP_EOL . "Year: $y";
                          // etc
                      }

Open in new window


The new way:
function page($args) {
                          echo PHP_EOL . "User: $args->user";
                          echo PHP_EOL . "Id: $args->id";
                          echo PHP_EOL . "Group: $args->group";
                          echo PHP_EOL . "Year: $args->year";
                          // etc
                      }

Open in new window


There is not much difference in the way we use the function arguments inside the function definition.  Instead of referring to the arguments one-at-a-time, we refer to the $args object and identify the named property.  Omitted properties can be treated as NULL values, and even returned as NULL values if we use the __get() magic method.  If new properties are added, these can be put anywhere in the $args object - it does not care about the relative positions of its properties.

Since the $args variable is a known instance of the RequestArguments class, we can add a type-hint to the function call:

function page( RequestArguments $args ) { /* PHP */ }

Open in new window


If we want to become more sophisticated about how we get the request arguments from the URL into the function call, we can add some intelligence to the RequestArguments class, like this:
Class RequestArguments
                      {
                          // THE CANONICAL LIST OF ACCEPTABLE REQUEST PARAMETERS
                          public $user, $id, $group, $year, $month, $size, $color;
                      
                          public function __construct()
                          {
                              $object_vars = get_object_vars($this);
                              foreach ($_GET as $property => $value)
                              {
                                  if (array_key_exists($property, $object_vars)) $this->$property = $value;
                              }
                              return $this;
                          }
                      }

Open in new window


Now We Are Really Making Progress!
Now all we need to do is create an instance of the RequestArguments object.  The object will load its properties and give them names by iterating over the $_GET array.  Any unwanted or unexpected variables will simply be discarded in the process, since they do not appear in the named properties of the RequestArguments class definition.  The class itself becomes the canonical source of the acceptable request arguments.  The property names will be automatically bound to the $_GET request variables. 

Now the order of arguments no longer matters, and all we have to remember are the names — a cognitively simpler task that is easier to describe and less likely to produce confusion or bugs in our application.

We might expand this design to add default values and sanitize filters for the external $_GET variables (external data is always to be considered tainted), but at its core, this design has freed us from one of the design flaws of PHP and many other languages — the need to remember the exact number and order of arguments in a function definition.

Even some of the built-in PHP functions fairly cry out for this kind of design.  I’m looking at you, ImageCopyResampled() where the native function has eight parameters!

Summary
This article has shown a design pattern that allows us to transform the positional nature of PHP function arguments into something easier to remember and test — named parameters — without any change to the design of the PHP language.

Further Reading...
E-E members have suggested that this article could be supplemented with more information on Dependency Injection, and I agree.  Here are two more articles that bring that topic into the light.
Software Design: "Dependencies"
Avoiding Globals

Please give us your feedback!
If you found this article helpful, please click the "thumb's up" button below. Doing so lets the E-E community know what is valuable for E-E members and helps provide direction for future articles.  If you have questions or comments, please add them.  Thanks!

 
5
5,226 Views

Comments (2)

Commented:
Great article!

I have long used/advocated this design pattern. But I also picked up a few tips:

  • use of magic __get()
  • PHP_EOL trick
  • $object_vars = get_object_vars($this) for acceptable params


As you wrote, there are a number of next steps; such as sanitizing $_GET. I think a good addition to this write-up would be to introduce dependency injection. Move reference to $_GET out of the constructor and make it a parameter of the constructor instead, thus decoupling the code and making it more reusable and testable:

Class RequestArguments
{
    // THE CANONICAL LIST OF ACCEPTABLE REQUEST PARAMETERS
    public $user, $id, $group, $year, $month, $size, $color;

    public function __construct($getInstance)
    {
        $object_vars = get_object_vars($this);
        foreach ($getInstance as $property => $value)
        {
            if (array_key_exists($property, $object_vars)) $this->$property = $value;
        }
        return $this;
    }
}

$getInstance = $_GET; // maybe you want to sanitize at this point or whatever else

$requestInstance = new RequestArguments($getInstance)

Open in new window


Looking forward to reading more from you!
Most Valuable Expert 2011
Author of the Year 2014

Author

Commented:
@calslim: thanks for your notes.  Dependency injection seems to be something that takes a bit more than a single article to explain.  Here are the two where I have unwrapped the topic.  Sadly, E-E seems uniquely unable to provide a search function that can lead its users to the Articles on the web site.

https://www.experts-exchange.com/articles/18210/Software-Design-Dependencies.html

https://www.experts-exchange.com/articles/19999/PHP-Design-Avoiding-Globals-with-Dependency-Injection.html

I am optimistic that one day E-E will be able to help users find articles when they ask topically related questions, but we have been waiting for this "super power" since 2007 with no response from the corporate office.  Maybe there will be progress one day.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.