PHP Cookbook/Functions

From WikiContent

(Difference between revisions)
Jump to: navigation, search
m (1 revision(s))
(Initial conversion from Docbook)

Revision as of 13:36, 7 March 2008

PHP Cookbook


Contents

Introduction

Functions help you create organized and reusable code. They allow you to abstract out details so your code becomes more flexible and more readable. Without functions, it is impossible to write easily maintainable programs because you're constantly updating identical blocks of code in multiple places and in multiple files.

With a function you pass a number of arguments in and get a value back:

// add two numbers together
function add($a, $b) {
    return $a + $b;
}

$total = add(2, 2);    // 4

To declare a function, use the function keyword, followed by the name of the function and any parameters in parentheses. To invoke a function, simply use the function name, specifying argument values for any parameters to the function. If the function returns a value, you can assign the result of the function to a variable, as shown in the previous example.

You don't need to predeclare a function before you call it. PHP parses the entire file before it begins executing, so you can intermix function declarations and invocations. You can't, however, redefine a function in PHP. If PHP encounters a function with an identical name to one it's already found, it throws a fatal error and dies.

Sometimes, the standard procedure of passing in a fixed number of arguments and getting one value back doesn't quite fit a particular situation in your code. Maybe you don't know ahead of time exactly how many parameters your function needs to accept. Or, you do know your parameters, but they're almost always the same values, so it's tedious to continue to repass them. Or, you want to return more than one value from your function.

This chapter helps you use PHP to solve these types of problems. We begin by detailing different ways to pass arguments to a function. Recipe 6.2 through Recipe 6.6 cover passing arguments by value, reference, and as named parameters; assigning default parameter values; and functions with a variable number of parameters.

The next four recipes are all about returning values from a function. Recipe 6.7 describes returning by reference, Recipe 6.8 covers returning more than one variable, Recipe 6.9 describes how to skip selected return values, and Recipe 6.10 talks about the best way to return and check for failure from a function. The final three recipes show how to call variable functions, deal with variable scoping problems, and dynamically create a function. There's one recipe on function variables located in Recipe 6.2; if you want a variable to maintain its value between function invocations, see Recipe 5.6.

Accessing Function Parameters

Problem

You want to access the values passed to a function.

Solution

Use the names from the function prototype:

function commercial_sponsorship($letter, $number) { 
    print "This episode of Sesame Street is brought to you by ";
    print "the letter $letter and number $number.\n";
}

commercial_sponsorship('G', 3);
commercial_sponsorship($another_letter, $another_number);

Discussion

Inside the function, it doesn't matter whether the values are passed in as strings, numbers, arrays, or another kind of variable. You can treat them all the same and refer to them using the names from the prototype.

Unlike in C, you don't need to (and, in fact, can't) describe the type of variable being passed in. PHP keeps track of this for you.

Also, unless specified, all values being passed into and out of a function are passed by value, not by reference. This means PHP makes a copy of the value and provides you with that copy to access and manipulate. Therefore, any changes you make to your copy don't alter the original value. Here's an example:

function add_one($number) {
    $number++;
}

$number = 1;
add_one($number);
print "$number\n";
1
            

If the variable was passed by reference, the value of $number would be 2.

In many languages, passing variables by reference also has the additional benefit of being significantly faster than by value. While this is also true in PHP, the speed difference is marginal. For that reason, we suggest passing variables by reference only when actually necessary and never as a performance-enhancing trick.

See Also

Recipe 6.4 to pass values by reference and Recipe 6.7 to return values by reference.

Setting Default Values for Function Parameters

Problem

You want a parameter to have a default value if the function's caller doesn't pass it. For example, a function to draw a table might have a parameter for border width, which defaults to 1 if no width is given.

Solution

Assign the default value to the parameters inside the function prototype:

function wrap_html_tag($string, $tag = 'b') {
    return "<$tag>$string</$tag>";
}

Discussion

The example in the Solution sets the default tag value to b, for bold. For example:

$string = 'I am some HTML';
wrap_html_tag($string);

returns:

<b>I am some HTML</b>

This example:

wrap_html_tag($string, 'i');

returns:

<i>I am some HTML</i>

There are two important things to remember when assigning default values. First, all parameters with default values must appear after parameters without defaults. Otherwise, PHP can't tell which parameters are omitted and should take the default value, and which arguments are overriding the default. So, wrap_html_tag( ) can't be defined as:

function wrap_html_tag($tag = 'i', $string)

If you do this and pass wrap_html_tag( ) only a single argument, PHP assigns the value to $tag and issues a warning complaining of a missing second argument.

Second, the assigned value must be a constant — a string or a number. It can't be a variable. Again, using wrap_html_tag( ) as our example, you can't do this:

$my_favorite_html_tag = 'i';

function wrap_html_tag($string, $tag = $my_favorite_html_tag) {
    ...
}

If you want to assign a default of nothing, one solution is to assign the empty string to your parameter:

function wrap_html_tag($string, $tag = '') {
    if (empty($tag)) return $string;
    return "<$tag>$string</$tag>";
}

This function returns the original string, if no value is passed in for the $tag. Or, if a (nonempty) tag is passed in, it returns the string wrapped inside of tags.

Depending on circumstances, another option for the $tag default value is either 0 or NULL. In wrap_html_tag( ), you don't want to allow an empty valued-tag. However, in some cases, the empty string can be an acceptable option. For instance, join( ) is often called on the empty string, after calling file( ) , to place a file into a string. Also, as the following code shows, you can use a default message if no argument is provided but an empty message if the empty string is passed:

function pc_log_db_error($message = NULL) {
    if (is_null($message)) {
        $message = 'Couldn't connect to DB';
    }

    error_log("[DB] [$message]");
}

See Also

Recipe 6.6 on creating functions that take a variable number of arguments.

Passing Values by Reference

Problem

You want to pass a variable to a function and have it retain any changes made to its value inside the function.

Solution

To instruct a function to accept an argument passed by reference instead of value, prepend an & to the parameter name in the function prototype:

function wrap_html_tag(&$string, $tag = 'b') {
    $string = "<$tag>$string</$tag>";
}

Now there's no need to return the string because the original is modified in-place.

Discussion

Passing a variable to a function by reference allows you to avoid the work of returning the variable and assigning the return value to the original variable. It is also useful when you want a function to return a boolean success value of true or false, but you still want to modify argument values with the function.

You can't switch between passing a parameter by value or reference; it's either one or the other. In other words, there's no way to tell PHP to optionally treat the variable as a reference or as a value.

Actually, that statement isn't 100% true. If the configuration directive allow_call_time_pass_reference is enabled, PHP lets you optionally pass a value by reference by prepending an ampersand to the variable's name. However, this feature has been deprecated since PHP 4.0 Beta 4, and PHP issues explicit warnings that this feature may go away in the future when you employ call-time pass-by-reference. Caveat coder.

Also, if a parameter is declared to accept a value by reference, you can't pass a constant string (or number, etc.), or PHP will die with a fatal error.

See Also

Recipe 6.7 on returning values by reference.

Using Named Parameters

Problem

You want to specify your arguments to a function by name, instead of simply their position in the function invocation.

Solution

Have the function use one parameter but make it an associative array:

function image($img) {
    $tag  = '<img src="' . $img['src'] . '" ';
    $tag .= 'alt="' . ($img['alt'] ? $img['alt'] : '') .'">';
    return $tag;
}

$image = image(array('src' => 'cow.png', 'alt' => 'cows say moo'));
$image = image(array('src' => 'pig.jpeg'));

Discussion

While using named parameters makes the code inside your functions more complex, it ensures the calling code is easier to read. Since a function lives in one place but is called in many, this makes for more understandable code.

When you use this technique, PHP doesn't complain if you accidentally misspell a parameter's name, so you need to be careful because the parser won't catch these types of mistakes. Also, you can't take advantage of PHP's ability to assign a default value for a parameter. Luckily, you can work around this deficit with some simple code at the top of the function:

function image($img) {
    if (! isset($img['src']))    { $img['src']    = 'cow.png';      }
    if (! isset($img['alt']))    { $img['alt']    = 'milk factory'; }
    if (! isset($img['height'])) { $img['height'] = 100;            }
    if (! isset($img['width']))  { $img['width']  = 50;             }
    ...
}

Using the isset( ) function, check to see if a value for each parameter is set; if not, assign a default value.

Alternatively, you can write a short function to handle this:

function pc_assign_defaults($array, $defaults) {
    $a = array( );
    foreach ($defaults as $d => $v) {
        $a[$d] = isset($array[$d]) ? $array[$d] : $v;
    }

    return $a;
}

This function loops through a series of keys from an array of defaults and checks if a given array, $array, has a value set. If it doesn't, the function assigns a default value from $defaults. To use it in the previous snippet, replace the top lines with:

function image($img) {
    $defaults = array('src'    => 'cow.png',
                      'alt'    => 'milk factory',
                      'height' => 100,
                      'width'  => 50
                     );
    $img = pc_assign_defaults($img, $defaults);
    ...
}

This is nicer because it introduces more flexibility into the code. If you want to modify how defaults are assigned, you only need to change it inside pc_assign_defaults( ) and not in hundreds of lines of code inside various functions. Also, it's clearer to have an array of name/value pairs and one line that assigns the defaults instead of intermixing the two concepts in a series of almost identical repeated lines.

See Also

Recipe 6.6 on creating functions that accept a variable number of arguments.

Creating Functions That Take a Variable Number of Arguments

Problem

You want to define a function that takes a variable number of arguments.

Solution

Pass an array and place the variable arguments inside the array:

// find the "average" of a group of numbers
function mean($numbers) {
    // initialize to avoid warnings
    $sum = 0;

    // the number of elements in the array
    $size = count($numbers);

    // iterate through the array and add up the numbers
    for ($i = 0; $i < $size; $i++) {
        $sum += $numbers[$i];
    }

    // divide by the amount of numbers
    $average = $sum / $size;

    // return average
    return $average;
}

$mean = mean(array(96, 93, 97));

Discussion

There are two good solutions, depending on your coding style and preferences. The more traditional PHP method is the one described in the Solution. We prefer this method because using arrays in PHP is a frequent activity; therefore, all programmers are familiar with arrays and their behavior.

So, while this method creates some additional overhead, bundling variables is commonplace. It's done in Recipe 6.5 to create named parameters and in Recipe 6.8 to return more than one value from a function. Also, inside the function, the syntax to access and manipulate the array involves basic commands such as $array[$i] and count($array).

However, this can seem clunky, so PHP provides an alternative and allows you direct access to the argument list:

// find the "average" of a group of numbers
function mean() {
    // initialize to avoid warnings
    $sum = 0;

    // the number of arguments passed to the function
    $size = func_num_args();

    // iterate through the arguments and add up the numbers
    for ($i = 0; $i < $size; $i++) {
        $sum += func_get_arg($i);
    }

    // divide by the amount of numbers
    $average = $sum / $size;

    // return average
    return $average;
}

$mean = mean(96, 93, 97);

This example uses a set of functions that return data based on the arguments passed to the function they are called from. First, func_num_args( ) returns an integer with the number of arguments passed into its invoking function — in this case, mean( ). From there, you can then call func_get_arg( ) to find the specific argument value for each position.

When you call mean(96, 93, 97), func_num_args( ) returns 3. The first argument is in position 0, so you iterate from 0 to 2, not 1 to 3. That's what happens inside the for loop where $i goes from 0 to less than $size. As you can see, this is the same logic used in the first example in which an array was passed. If you're worried about the potential overhead from using func_get_arg( ) inside a loop, don't be. This version is actually faster than the array passing method.

There is a third version of this function that uses func_num_args( ) to return an array containing all the values passed to the function. It ends up looking like hybrid between the previous two functions:

// find the "average" of a group of numbers
function mean() {
    // initialize to avoid warnings
    $sum = 0;

    // load the arguments into $numbers
    $numbers = func_get_args();

    // the number of elements in the array
    $size = count($numbers);

    // iterate through the array and add up the numbers
    for ($i = 0; $i < $size; $i++) {
        $sum += $numbers[$i];
    }

    // divide by the amount of numbers
    $average = $sum / $size;

    // return average
    return $average;
}

$mean = mean(96, 93, 97);

Here you have the dual advantages of not needing to place the numbers inside a temporary array when passing them into mean( ), but inside the function you can continue to treat them as if you did. Unfortunately, this method is slightly slower than the first two.

See Also

Recipe 6.8 on returning multiple values from a function; documentation on func_num_arg( ) at http://www.php.net/func-num-arg, func_get_arg( ) at http://www.php.net/func-get-arg, and func_get_args( ) at http://www.php.net/func-get-args.

Returning Values by Reference

Problem

You want to return a value by reference, not by value. This allows you to avoid making a duplicate copy of a variable.

Solution

The syntax for returning a variable by reference is similar to passing it by reference. However, instead of placing an & before the parameter, place it before the name of the function:

function &wrap_html_tag($string, $tag = 'b') {
    return "<$tag>$string</$tag>";
}

Also, you must use the =& assignment operator instead of plain = when invoking the function:

$html =& wrap_html_tag($string);

Discussion

Unlike passing values into functions, in which an argument is either passed by value or by reference, you can optionally choose not to assign a reference and just take the returned value. Just use = instead of =&, and PHP assigns the value instead of the reference.

See Also

Recipe 6.4 on passing values by reference.

Returning More Than One Value

Problem

You want to return more than one value from a function.

Solution

Return an array and use list( ) to separate elements:

function averages($stats) {
    ...
    return array($median, $mean, $mode);
}

list($median, $mean, $mode) = averages($stats);

Discussion

From a performance perspective, this isn't a great idea. There is a bit of overhead because PHP is forced to first create an array and then dispose of it. That's what is happening in this example:

function time_parts($time) {
    return explode(':', $time);
}

list($hour, $minute, $second) = time_parts('12:34:56');

You pass in a time string as you might see on a digital clock and call explode( ) to break it apart as array elements. When time_parts( ) returns, use list( ) to take each element and store it in a scalar variable. Although this is a little inefficient, the other possible solutions are worse because they can lead to confusing code.

One alternative is to pass the values in by reference. However, this is somewhat clumsy and can be nonintuitive since it doesn't always make logical sense to pass the necessary variables into the function. For instance:

function time_parts($time, &$hour, &$minute, &$second) {
    list($hour, $minute, $second) = explode(':', $time);
}

time_parts('12:34:56', $hour, $minute, $second);

Without knowledge of the function prototype, there's no way to look at this and know $hour, $minute, and $second are, in essence, the return values of time_parts( ).

You can also use global variables, but this clutters the global namespace and also makes it difficult to easily see which variables are being silently modified in the function. For example:

function time_parts($time) {
    global $hour, $minute, $second;
    list($hour, $minute, $second) = explode(':', $time);
}

time_parts('12:34:56');

Again, here it's clear because the function is directly above the call, but if the function is in a different file or written by another person, it'd be more mysterious and thus open to creating a subtle bug.

Our advice is that if you modify a value inside a function, return that value and assign it to a variable unless you have a very good reason, such as significant performance issues. It's cleaner and easier to understand and maintain.

See Also

Recipe 6.4 on passing values by reference and Recipe 6.12 for information on variable scoping.

Skipping Selected Return Values

Problem

A function returns multiple values, but you only care about some of them.

Solution

Omit variables inside of list( ) :

// Only care about minutes
function time_parts($time) {
    return explode(':', $time);
}

list(, $minute,) = time_parts('12:34:56');

Discussion

Even though it looks like there's a mistake in the code, the code in the Solution is valid PHP. This is most frequently seen when a programmer is iterating through an array using each( ), but cares only about the array values:

while (list(,$value) = each($array)) {
    process($value);
}

However, this is more clearly written using a foreach:

foreach ($array as $value) {
    process($value);
}

To reduce confusion, we don't often use this feature, but if a function returns many values, and you only want one or two of them, this technique can come in handy. One example of this case is if you read in fields using fgetcsv( ) , which returns an array holding the fields from the line. In that case, you can use the following:

while ($fields = fgetcsv($fh, 4096)) {
    print $fields[2] . "\n";  // the third field
}

If it's an internally written function and not built-in, you could also make the returning array have string keys, because it's hard to remember, for example, that array element 2 is associated with 'rank':

while ($fields = read_fields($filename)) {
    $rank = $fields['rank']; // the third field is now called rank
    print "$rank\n";
}

However, here's the most efficient method:

while (list(,,$rank,,) = fgetcsv($fh, 4096)) {
    print "$rank\n";         // directly assign $rank
}

Be careful you don't miscount the amount of commas; you'll end up with a bug.

See Also

Recipe 1.10 for more on reading files using fgetcsv( ).

Returning Failure

Problem

You want to indicate failure from a function.

Solution

Return false:

function lookup($name) {
    if (empty($name)) { return false; }
    ...
}

if (false !== lookup($name)) { /* act upon lookup */ }

Discussion

I n PHP, non-true values aren't standardized and can easily cause errors. As a result, it's best if all your functions return the defined false keyword because this works best when checking a logical value.

Other possibilities are '' or 0. However, while all three evaluate to non-true inside an if, there's actually a difference among them. Also, sometimes a return value of 0 is a meaningful result, but you still want to be able to also return failure.

For example, strpos( ) returns the location of the first substring within a string. If the substring isn't found, strpos( ) returns false. If it is found, it returns an integer with the position. Therefore, to find a substring position, you might write:

if (strpos($string, $substring)) { /* found it! */ }

However, if $substring is found at the exact start of $string, the value returned is 0. Unfortunately, inside the if, this evaluates to false, so the conditional is not executed. Here's the correct way to handle the return value of strpos( ):

if (false !== strpos($string, $substring)) { /* found it! */ }

Also, false is always guaranteed to be false — in the current version of PHP and forever more. Other values may not guarantee this. For example, in PHP 3, empty('0') was true, but it changed to false in PHP 4.

See Also

The introduction to Chapter 5 for more on the truth values of variables; documentation on strpos( ) at http://www.php.net/strpos and empty( ) at http://www.php.net/empty; information on migrating from PHP 3 to PHP 4 at http://www.php.net/migration4.

Calling Variable Functions

Problem

You want to call different functions depending on a variable's value.

Solution

Use variable variables:

function eat_fruit($fruit) { print "chewing $fruit."; }

$function = 'eat_fruit';
$fruit = 'kiwi';

$function($fruit); // calls eat_fruit( )

Discussion

If you have multiple possibilities to call, use an associative array of function names:

$dispatch = array(
    'add'      => 'do_add',
    'commit'   => 'do_commit',
    'checkout' => 'do_checkout',
    'update'   => 'do_update'
);

$cmd = (isset($_REQUEST['command']) ? $_REQUEST['command'] : '');

if (array_key_exists($cmd, $dispatch)) {
    $function = $dispatch[$cmd];
    $function(); // call function
} else {
    error_log("Unknown command $cmd");
}

This code takes the command name from a request and executes that function. Note the check to see that the command is in a list of acceptable command. This prevents your code from calling whatever function was passed in from a request, such as phpinfo( ) . This makes your code more secure and allows you to easily log errors.

Another advantage is that you can map multiple commands to the same function, so you can have a long and a short name:

$dispatch = array(
    'add'      => 'do_add',
    'commit'   => 'do_commit',   'ci' => 'do_commit', 
    'checkout' => 'do_checkout', 'co' => 'do_checkout',
    'update'   => 'do_update',   'up' => 'do_update'
);

See Also

Recipe 5.5 for more on variable variables.

Accessing a Global Variable Inside a Function

Problem

You need to access a global variable inside a function.

Solution

Bring the global variable into local scope with the global keyword:

function eat_fruit($fruit) {
   global $chew_count;
 
   for ($i = $chew_count; $i > 0; $i--) {
       ...
   }
}

Or reference it directly in $GLOBALS:

function eat_fruit($fruit) {
   for ($i = $GLOBALS['chew_count']; $i > 0; $i--) {
       ...
   }
}

Discussion

If you use a number of global variables inside a function, the global keyword may make the syntax of the function easier to understand, especially if the global variables are interpolated in strings.

You can use the global keyword to bring multiple global variables into local scope by specifying the variables as a comma-separated list:

global $age,$gender,shoe_size;

You can also specify the names of global variables using variable variables:

$which_var = 'age';
global $$which_var; // refers to the global variable $age

However, if you call unset( ) on a variable brought into local scope using the global keyword, the variable is unset only within the function. To unset the variable in the global scope, you must call unset( ) on the element of the $GLOBALS array:

$food =  'pizza';
$drink = 'beer';

function party( ) {
    global $food, $drink;
    
    unset($food);             // eat pizza
    unset($GLOBALS['drink']); // drink beer
}

print "$food: $drink\n";
party( );
print "$food: $drink\n";
pizza: beer
               pizza:
            

You can see that $food stayed the same, while $drink was unset. Declaring a variable global inside a function is similar to assigning a reference of the global variable to the local one:

$food = &GLOBALS['food'];

See Also

Documentation on variable scope at http://www.php.net/variables.scope and variable references at http://www.php.net/language.references.

Creating Dynamic Functions

Problem

You want to create and define a function as your program is running.

Solution

Use create_function( ):

$add = create_function('$i,$j', 'return $i+$j;');

$add(1, 1); // returns 2

Discussion

The first parameter to create_function( ) is a string that contains the arguments for the function, and the second is the function body. Using create_function( ) is exceptionally slow, so if you can predefine the function, it's best to do so.

The most frequently used case of create_function( ) in action is to create custom sorting functions for usort( ) or array_walk( ):

// sort files in reverse natural order
usort($files, create_function('$a, $b', 'return strnatcmp($b, $a);'));

See Also

Recipe 4.18 for information on usort( ); documentation on create_function( ) at http://www.php.net/create-function and on usort( ) at http://www.php.net/usort.

Personal tools