10. Subroutines

Introduction

// Since defined at outermost scope, $greeted may be considered a global variable
$greeted = 0;

// ------------

// Must use, 'global', keyword to inform functions that $greeted already exists as
// a global variable. If this is not done, a local variable of that name is implicitly
// defined
function howManyGreetings()
{
  global $greeted;
  return $greeted;
}

function hello()
{
  global $greeted;
  $greeted++;
  echo "high there!, this procedure has been called {$greeted} times\n";
}

// ------------

hello();
$greetings = howManyGreetings();
echo "bye there!, there have been {$greetings} greetings so far\n";

Accessing Subroutine Arguments

// Conventionally-defined function together with parameter list
function hypotenuse($side1, $side2)
{
  return sqrt(pow($side1, 2) + pow($side2, 2));
}

// ----

// Alternative is to define the function without parameter list, then use
// 'func_get_arg' to extract arguments
function hypotenuse()
{
  // Could check number of arguments passed with: 'func_num_args', which
  // would be the approach used if dealing with variable number of arguments
  $side1 = func_get_arg(0); $side2 = func_get_arg(1);

  return sqrt(pow($side1, 2) + pow($side2, 2));
}

// ------------

// 1. Conventional function call
$diag = hypotenuse(3, 4);

// ------------

// 2. Function call using, 'call_user_func' library routines
$funcname = 'hypotenuse';

// a. Pass function name, and variable number of arguments
$diag = call_user_func($funcname, 3, 4);

// b. Package arguments as array, pass together with function name
$args = array(3, 4); 
$diag = call_user_func_array($funcname, $args);

// ----------------------------

$nums = array(1.4, 3.5, 6.7);

// ------------

// Pass-by-value
function int_all($arr)
{
  return array_map(create_function('$n', 'return (int) $n;'), $arr);
}

// Pass-by-reference
function trunc_em(&$n)
{
  foreach ($n as &$value) $value = (int) $value;
}

// ------------

// $nums untouched; $ints is new array
$ints = int_all($nums);

// $nums updated
trunc_em($nums);

Making Variables Private to a Function

// Strictly-speaking, PHP is neither lexically [no environment capture] nor
// dynamically [no variable shadowing] scoped. A script in which several
// functions have been defined has two, entirely separate, scopes:
//
// * A 'top-level' scope i.e. everything outside each function
//
// * A 'local scope' within each function; each function is a self-contained
//   entity and cannot [via conventional means] access variables outside its
//   local scope. Accessing a variable that has not been locally defined
//   serves to define it i.e. accessing a variable assumed to be global
//   sees a local variable of that name defined
//
// The way 'global' variables are provided is via a predefined array of
// variable names, $GLOBALS [it is one of a special set of variables known
// as 'superglobals'; such variables *are* accessable in all scopes]. Each
// entry in this array is a 'global' variable name, and may be freely
// accessed / updated. A more convenient means of accessing such variables
// is via the 'global' keyword: one or more variables within a function is
// declared 'global', and those names are then taken to refer to entries
// in the $GLOBALS array rather than seeing local variables of that name
// accessed or defined

function some_func()
{
  // Variables declared within a function are local to that function
  $variable = 'something';
}

// ----------------------------

// Top-level declared variables
$name = $argv[1]; $age = $argv[2];

$c = fetch_time();

$condition = 0;

// ------------

function run_check()
{
  // The globally-declared variable, '$condition', is not accessable within
  // the function unless it declared as 'global. Had this not been done then
  // attempts to access, '$condition', would have seen a local variable
  // of that name declared and updated. Same applies to other variables
  global $condition, $name, $age, $c;

  $condition = 1;
  // ...
}

function check_x($x)
{
  $y = 'whatever';

  // This function only has access to the parameter, '$x', and the locally
  // declared variable, '$y'.

  // Whilst 'run_check' has access to several global variables, the current
  // function does not. For it to access the global variable, '$condition',
  // it must be declared 'global'
  run_check();

  global $condition;

  // 'run_check' will have updated, '$condition', and since it has been
  // declared 'global' here, it is accessable

  if ($condition)
  {
    ; // ...
  }
}

Creating Persistent Private Variables

// Local scopes are not created in the same way as in Perl [by simply enclosing
// within braces]: only via the creation of functions are local scopes created

// Doesn't create a local scope; '$myvariable' is created at top-level
{
  $myvariable = 7;
}

// '$myvariable' is accessable here
echo $myvariable . "\n";

// ------------

{
  $counter = 0;

  // Local scope created within function, but not within surrounding braces
  // so:
  // * '$counter' is actually a top-level variable, so globally accessable
  // * 'next_counter' has no implict access to '$counter'; must be granted
  //   via 'global' keyword

  function next_counter() { global $counter; $counter++; }
}

// ----------------------------

// PHP doesn't, AFAIK, offer an equivalent to Perl's BEGIN block. Similar
// behaviour may be obtained by defining a class, and including such code
// in its constructor

class BEGIN
{
  private $myvariable;

  function __construct()
  {
    $this->myvariable = 5;
  }

  function othersub()
  {
    echo $this->myvariable . "\n";
  }
}

// ------------

$b = new BEGIN();

$b->othersub();

// ----------------------------

// PHP, like C, supports 'static' local variables, that is, those that upon
// first access are initialised, and thence retain their value between function
// calls. However, the 'counter' example is better implemented as a class

class Counter
{
  private $counter;

  function __construct($counter_init)
  {
    $this->counter = $counter_init;
  }

  function next_counter() { $this->counter++; return $this->counter; }
  function prev_counter() { $this->counter; return $this->counter; }
}

// ------------

$counter = new Counter(42);
echo $counter->next_counter() . "\n";
echo $counter->next_counter() . "\n";
echo $counter->prev_counter() . "\n";

Determining Current Function Name

// AFAICT there is no means of obtaining the name of the currently executing
// function, or, for that matter, perform any stack / activation record,
// inspection. It *is* possible to:
//
// * Obtain a list of the currently-defined functions ['get_defined_functions']
// * Check whether a specific function exists ['function_exists']
// * Use the 'Reflection API'
//
// So, to solve this problem would seem to require adopting a convention where
// a string representing the function name is passed as an argument, or a local
// variable [perhaps called, '$name'] is so set [contrived, and of limited use]

function whoami()
{
  $name = 'whoami';
  echo "I am: {$name}\n";
}

// ------------

whoami();

Passing Arrays and Hashes by Reference

// In PHP all items exist as 'memory references' [i.e. non-modifiable pointers],
// so when passing an item as a function argument, or returning an item from
// a function, it is this 'memory reference' that is passed, and *not* the
// contents of that item. Should several references to an item exist [e.g. if
// passed to a function then at least two such references would exist in
// different scopes] they would all be refering to the same copy of the item.
// However, if an attempt is made to alter the item is made, a copy is made
// and it is the copy that is altered, leaving the original intact.
// 
// The PHP reference mechanism is used to selectively prevent this behaviour,
// and ensure that if a change is made to an item that no copy is made, and that
// it is the original item that is changed. Importantly, there is no efficiency
// gain from passing function parameters using references if the parameter item
// is not altered.

// A copy of the item referred to by, '$arr', is made, and altered; original
// remains intact
function array_by_value($arr)
{
  $arr[0] = 7;
  echo $arr[0] . "\n"; 
}

// No copy is made; original item referred to by, '$arr', is altered
function array_by_ref(&$arr)
{
  $arr[0] = 7;
  echo $arr[0] . "\n"; 
}

// ------------

$arr = array(1, 2, 3);

echo $arr[0] . "\n";         // output: 1 
array_by_value($arr);        // output: 7
echo $arr[0] . "\n";         // output: 1 

$arr = array(1, 2, 3);

echo $arr[0] . "\n";         // output: 1 
array_by_ref($arr);          // output: 7
echo $arr[0] . "\n";         // output: 7 

// ----------------------------

// Since, 'add_vecpair', does not attempt to alter either, '$x' or '$y', it makes
// no difference whether they are 'passed by value', or 'passed by reference'
function add_vecpair($x, $y)
{
  $r = array();
  $length = count($x);
  for($i = 0; $i < $length; $i++) $r[$i] = $x[$i] + $y[$i];
  return $r;
}

// ...
count($arr1) == count($arr2) || die("usage: add_vecpair ARR1 ARR2\n");

// 'passed by value'
$arr3 = add_vecpair($arr1, $arr2);

// 'passed by reference' [also possible to override default 'passed by value'
// if required]
$arr3 = add_vecpair(&$arr1, &$arr2);

Detecting Return Context

// PHP can be described as a dynamically typed language because variables serve
// as identifiers, and the same variable may refer to data of various types.
// As such, the set of arguments passed to a function may vary in type between
// calls, as can the type of return value. Where this is likely to occur type
// checking should be performed either / both within the function body, and
// when obtaining it's return value. As for Perl-style 'return context', I
// don't believe it is supported by PHP

// Can return any type
function mysub()
{
  // ...
  return 5;
  // ...
  return array(5);
  // ...
  return '5';
}

// Throw away return type [i.e. returns a 'void' type ?]
mysub();

// Check return type. Can do via:
// * gettype($var)
// * is_xxx e.g. is_array($var), is_muneric($var), ...
$ret = mysub();

if (is_numeric($ret))
{
  ; // ...
}

if (is_array($ret))
{
  ; // ...
}

if (is_string($ret))
{
  ; // ...
}

Passing by Named Parameter

// PHP doesn't directly support named / keyword parameters, but these can be
// easily mimiced using a class of key / value pairs, and passing a variable
// number of arguments

class KeyedValue
{
  public $key, $value;
  public function __construct($key, $value) { $this->key = $key; $this->value = $value; }
}

function the_func()
{
  foreach (func_get_args() as $arg)
  {
    printf("Key: %10s|Value:%10s\n", $arg->key, $arg->value);
  }
}

// ----

the_func(new KeyedValue('name', 'Bob'),
         new KeyedValue('age', 36),
         new KeyedValue('income', 51000));

// ----------------------------

// Alternatively, an associative array of key / value pairs may be constructed.
// With the aid of the 'extract' built-in function, the key part of this array
// may be intsntiated to a variable name, thus more closely approximating the
// behaviour of true named parameters

function the_func($var_array)
{
  extract($var_array);

  if (isset($name)) printf("Name:   %s\n", $name);
  if (isset($age)) printf("Age:    %s\n", $age);
  if (isset($income)) printf("Income: %s\n", $income);
}

// ----

the_func(array('name' => 'Bob', 'age' => 36, 'income' => 51000));

// ----------------------------

class RaceTime
{
  public $time, $dim;
  public function __construct($time, $dim) { $this->time = $time; $this->dim = $dim; }
}

function the_func($var_array)
{
  extract($var_array);

  if (isset($start_time)) printf("start:  %d - %s\n", $start_time->time, $start_time->dim);
  if (isset($finish_time)) printf("finish: %d - %s\n", $finish_time->time, $finish_time->dim);
  if (isset($incr_time)) printf("incr:   %d - %s\n", $incr_time->time, $incr_time->dim);
}

// ----

the_func(array('start_time' => new RaceTime(20, 's'),
               'finish_time' => new RaceTime(5, 'm'),
               'incr_time' => new RaceTime(3, 'm')));

the_func(array('start_time' => new RaceTime(5, 'm'),
               'finish_time' => new RaceTime(30, 'm')));

the_func(array('start_time' => new RaceTime(30, 'm')));

Skipping Selected Return Values

// The 'list' keyword [looks like a function but is actually a special language
// construct] may be used to perform multiple assignments from a numerically
// indexed array of values, and offers the added bonus of being able to skip
// assignment of one, or more, of those values

function func() { return array(3, 6, 9); }

// ------------

list($a, $b, $c) = array(6, 7, 8);

// Provided 'func' returns an numerically-indexed array, the following
// multiple assignment will work
list($a, $b, $c) = func();

// Any existing variables no longer wanted would need to be 'unset'
unset($b);

// As above, but second element of return array discarded
list($a,,$c) = func();

// ----------------------------

// Care needed to ensure returned array is numerically-indexed
list($dev, $ino,,,$uid) = array_slice(array_values(stat($filename)), 0, 13);

Returning More Than One Array or Hash

// Multiple return values are possible via packing a set of values within a
// numerically-indexed array and using 'list' to extract them

function some_func() { return array(array(1, 2, 3), array('a' => 1, 'b' => 2)); }

// ------------

list($arr, $hash) = some_func();

// ----------------------------

function some_func(&$arr, &$hash) { return array($arr, $hash); }

// ------------

$arrin = array(1, 2, 3); $hashin = array('a' => 1, 'b' => 2);

list($arr, $hash) = some_func($arrin, $hashin);

Returning Failure

// AFAICT, most of the PHP library functions are designed to return some required 
// value on success, and FALSE on exit. Whilst it is possible to return NULL, or
// one of the recognised 'empty' values [e.g. '' or 0 or an empty array etc],
// FALSE actually seems to be the preferred means of indicating failure

function a_func() { return FALSE; }

a_func() || die("Function failed\n");

if (!a_func()) die("Function failed\n");

Prototyping Functions

// Whether PHP is seen to support prototyping depends on the accepted
// definition of this term:
//
// * Prototyping along the lines used in Ada, Modula X, and even C / C++,
//   in which a function's interface is declared separately from its
//   implementation, is *not* supported
//
// * Prototyping in which, as part of the function definition, parameter
//   information must be supplied. In PHP a function definition neither
//   parameter, nor return type, information needs to be supplied, though
//   it is usual to see a parameter list supplied [indicates the number,
//   positional order, and optionally, whether a parameter is passed by
//   reference; no type information is present]. In short, prototyping in
//   PHP is optional, and limited

function func_with_one_arg($arg1)
{
  ; // ...
}

function func_with_two_arg($arg1, $arg2)
{
  ; // ...
}

function func_with_three_arg($arg1, $arg2, $arg3)
{
  ; // ...
}

// The following may be interpreted as meaning a function accepting no
// arguments:
function func_with_no_arg()
{
  ; // ...
}

// whilst the following may mean a function taking zero or more arguments
function func_with_no_arg_information()
{
  ; // ...
}

Handling Exceptions

// Unlike in Perl, PHP's 'die' [actually an alias for 'exit'] doesn't throw
// an exception, but instead terminates the script, optionally either
// returning an integer value to the operating system, or printing a message.
// So, the following, does not exhibit the same behaviour as the Perl example

die("some message\n"); 

// Instead, like so many modern languages, PHP implements exception handling
// via the 'catch' and 'throw' keywords. Furthermore, a C++ or Java programmer
// would find PHP's exception handling facility remarkably similar to those
// of their respective languages. A simple, canonical example follows:

// Usual to derive new exception classes from the built-in, 'Exception',
// class
class MyException extends Exception
{
  // ...
}

// ...

try
{
  // ...
  if ($some_problem_detected) throw new MyException('some message', $some_error_code);
  // ..
}

catch (MyException $e)
{
  ; // ... handle the problem ...
}

// ----------------------------

class FullMoonException extends Exception
{
  // ...
}

// ...

try
{
  // ...
  if ($some_problem_detected) throw new FullMoonException('...', $full_moon_error_code);
  // ..
}

catch (FullMoonException $e)
{
  // ... rethrow the exception - will propagate to higher level ...
  throw $e;
}

Saving Global Values

// Please refer to discussion about PHP scope in section two of this chapter.
// Briefly, PHP assumes a variable name within a function to be local unless
// it has been specifically declared with the, 'global', keyword, in which
// case it refers to a variable in the 'superglobal' array, '$GLOBALS'. Thus,
// inadvertant variable name shadowing cannot occur since it is it not possible 
// to use the same name to refer to both a local and a global variable. If
// accessing a global variable care should be taken to not accidentally update
// it. The techniques used in this section are simply not required.

// *** NOT TRANSLATED ***

Redefining a Function

// In PHP once a function has been defined it remains defined. In other words,
// it cannot be undefined / deleted, nor can that particular function name be
// reused to reference another function body. Even the lambda-like functions
// created via the 'create_function' built-in, cannot be undefined [they exist
// until script termination, thus creating too many of these can actually
// exhaust memory !]. However, since the latter can be assigned to variables,
// the same variable name can be used to reference difference functions [and
// when this is done the reference to the previous function is lost (unless
// deliberately saved), though the function itself continues to exist].
//
// If, however, all that is needed is a simple function aliasing facility,
// then just assign the function name to a variable, and execute using the
// variable name

// Original function
function expand() { echo "expand\n"; }

// Prove that function exists
echo (function_exists('expand') ? 'yes' : 'no') . "\n";

// Use a variable to alias it
$grow = 'expand';

// Call function via original name, and variable, respectively
expand();

$grow(); 

// Remove alias variable
unset($grow);

// ----------------------------

function fred() { echo "fred\n"; }

$barney = 'fred';

$barney();

unset($barney);

fred();

// ------------

$fred = create_function('', 'echo "fred\n";');

$barney = $fred;

$barney();

unset($barney);

$fred();

// ----------------------------

function red($text) { return "<FONT COLOR='red'>$text</FONT>"; }

echo red('careful here') . "\n";

// ------------

$colour = 'red';

$$colour = create_function('$text', 'global $colour;
return "<FONT COLOR=\'$colour\'>$text</FONT>";');

echo $$colour('careful here') . "\n";

unset($$colour);

// ----

$colours = split(' ', 'red blue green yellow orange purple violet');

foreach ($colours as $colour)
{
  $$colour = create_function('$text', 'global $colour;
  return "<FONT COLOR=\'$colour\'>$text</FONT>";');
}

foreach ($colours as $colour) { echo $$colour("Careful with this $colour, James") . "\n"; }

foreach ($colours as $colour) { unset($$colour); }

Trapping Undefined Function Calls with AUTOLOAD

// PHP sports an AUTOLOAD facility that is quite easy to use, but, AFAICT, is geared
// towards the detection of unavailable classes rather than for individual functions.
// Here is a rudimentary example:

function __autoload($classname)
{
  if (!file_exists($classname))
  {
    // Class file does not exist, so handle situation; in this case,
    // issue error message, and exit program
    die("File for class: {$classname} not found - aborting\n");
  }
  else
  {
    // Class file exists, so load it
    require_once $classname;
  }
}

// ------------

// Attempt to instantiate object of undefined class 
new UnknownClassObject();

// Execution continues here if class exists
// ...

// ----------------------------

// It is also possible to perform [quite extensive] introspection on functions,
// variables etc, so it is possible to check whether a function exists before
// executing it, thus allowing a non-existent functions to be searched for and
// loaded from a source file, or perhaps dynamically defined. An example of what
// could be described as a custom autoload facility appears below.

$colours = array('red', 'blue', 'green', 'yellow', 'orange', 'purple', 'violet');

foreach ($colours as $colour)
{
  $$colour = create_function('$text', 'global $colour;
  return "<FONT COLOR=\'$colour\'>$text</FONT>";');
}

// Let's add a new colour to the list
array_push($colours, 'chartreuse'); 

foreach ($colours as $colour)
{
  // Checking whether function is defined
  if (!function_exists($$colour))
  {
    // Doesn't exist, so dynamically define it
    $$colour = create_function('$text', 'global $colour;
    return "<FONT COLOR=\'$colour\'>$text</FONT>";');

    // Alternatively, if it exists in a source file, 'include' the file:
    // include 'newcolours.php'
  }

  echo $$colour("Careful with this $colour, James") . "\n";
}

foreach ($colours as $colour) unset($$colour); 

Nesting Subroutines

// *** Warning *** Whilst PHP *does* allow functions to be defined within other
// functions it needs to be clearly understood that these 'inner' functions:
// * Do not exist until the outer function is called a first time, at which time
//   they then remain defined
// * Are global in scope, so are accessable outside the function by their name;
//   the fact that they are nested within another function has, AFAICT, no bearing
//   on name resolution
// * Do not form a closure: the inner function is merely 'parked' within the
//   outer function, and has no implicit access to the outer function's variables
//   or other inner functions

function outer($arg)
{
  $x = $arg + 35;
  function inner() { return $x * 19; }

  // *** wrong *** 'inner' returns 0 * 19, not ($arg + 35) * 19
  return $x + inner();
}

// ----------------------------

function outer($arg)
{
  $x = $arg + 35;

  // No implicit access to outer function scope; any required data must be
  // explicity passed
  function inner($x) { return $x * 19; }

  return $x + inner($x);
}

// ------------ 

// Equivalent to previously-shown code
function inner($x)
{
  return $x * 19;
}

function outer($arg)
{
  $x = $arg + 35;
  return $x + inner($x);
}

Program: Sorting Your Mail

// @@INCOMPLETE@@
// @@INCOMPLETE@@