SyntaxHighlighter

Saturday, 17 September 2011

Favourite PHP/Javascript Functions - checkArray()

In this series of articles I describe simple, elegant functions from my own personal library that have proven useful to me over the course of many projects.


The most frequently used functions in my php library are UtilsValidator::checkArray() and UtilsValidator::checkArrayAndSetDefaults(). I will refer to them as checkArray() and checkArrayAndSetDefaults() for brevity.

Example 1: checkArray()

The checkArray() function validates a php array supplied in the first parameter, according to a specification that is supplied in the second and optional third parameters. Compulsory keys are specified in the second parameter, and optional keys are specified in the optional third parameter. If any of the validation checks fail, the function throws an exception carrying a description of the check that failed.

Utils_validator::checkArray
(
   $bookInfo, array
   (
      'id'    => 'positiveInt',
      'title' => 'string'
   ), array
   (
      'authorNameFirst' => 'string',
      'authorNameLast'  => 'string',
      'isHardcover'     => 'bool'  ,
      'nPages'          => 'positiveInt'
   )
);

The code in the above example will cause an exception to be thrown if any of the following statements are false:

 * $bookInfo is an array containing a minimum of two and a maximum of six elements,
 * $bookInfo['id'             ] is a positive integer,
 * $bookInfo['title'          ] is a string,
 * $bookInfo['authorNameFirst'] is undefined or a string,
 * $bookInfo['authorNameLast' ] is undefined or a string,
 * $bookInfo['isHardcover'    ] is undefined or a boolean,
 * $bookInfo['nPages'         ] is undefined or a positive integer

More useful to the programmer reading the above code, is that if the code executes and does not cause an exception to be thrown, the above statements are true. This is a great aid to code readability.

Example 2: checkArrayAndSetDefaults()

The checkArrayAndSetDefaults() function is a variation on checkArray() that performs the same checks, but also may modify the supplied array if necessary by assigning a default value to each optional key that is undefined. To enable this behaviour, the array to be checked is passed call-by-reference.

Utils_validator::checkArrayAndSetDefaults
(
   $bookInfo, array
   (
      'id'    => 'positiveInt',
      'title' => 'string'
   ), array
   (
      'authorNameFirst' => array('string'      => 'unknown'),
      'authorNameLast'  => array('string'      => 'unknown')
      'isHardcover'     => array('bool'        => false    ),
      'nPages'          => array('positiveInt' => null     ),
   )
);

If the above code executes and does not cause an exception to be thrown, the below statements are true.

 * $bookInfo is an array containing six elements,
 * $bookInfo['id'             ] is a positive integer,
 * $bookInfo['title'          ] is a string,
 * $bookInfo['authorNameFirst'] is the supplied string           or 'unknown' if nothing was supplied,
 * $bookInfo['authorNameLast' ] is the supplied string           or 'unknown' if nothing was supplied,
 * $bookInfo['isHardcover'    ] is the supplied boolean          or false     if nothing was supplied,
 * $bookInfo['nPages'         ] is the supplied positive integer or null      if nothing was supplied

Uses

I find the functions described above to be particularly useful in two situations.

  1. When validating a $_GET or $_POST array following a form submission
  2. Converting a function that accepts many parameters to a more readable and usable form.

The following example illustrates situation number two.

Example 3: Validating Function Parameters

Consider the following example function of many parameters, and the call to that function below the function definition.

function sendEmail($emailTo, $emailFrom, $emailSubject, $emailContent, $emailCc = null, $emailBcc = null)
{
   // code...
}

// ...

sendMail
(
   'bor.alurin@foundation.com'   , // To.
   'hari.seldon@trantor.com'     , // From.
   'Instructions'                , // Subject.
   'Blah, blah, blah, ...'       , // Content.
   'salvor.hardin@foundation.com', // CC.
   'onum.barr@siwenna.com'         // BCC.
);

The problem with functions of many parameters, is that it is very easy for a programmer to make the mistake of supplying parameters in the wrong order.

A partial solution often used is to supply an identifying comment next to each parameter, as in the example code above. Maybe the comments correctly identified the parameters at the time the comments were written, but there is no guarantee that the parameter order remains correct. This problem is the source of many bugs.

Consider the following alternative code, where the parameters are supplied as an array, and validated inside the function. The parameters may now be supplied in any order, since they are identified by descriptive keys. Comments are no longer needed to explain what parameters are passed.

The code below is not as compact as the code above, but is significantly better for long-term maintainability.

function sendEmail($params)
{
    Utils_validator::checkArrayAndSetDefaults
    (
        $params, array
        (
            'emailTo'      => 'string',
            'emailFrom'    => 'string',
            'emailSubject' => 'string',
            'emailContent' => 'string'
        ), array
        (
            'emailCc'  => array('nullOrString', null),
            'emailBcc' => array('nullOrString', null)
        )
    );
    extract($params);

   // code...
}

sendMail
(
   array
   (
      'emailTo'      => 'bor.alurin@foundation.com'   ,
      'emailFrom'    => 'hari.seldon@trantor.com'     ,
      'emailSubject' => 'Instructions'                ,
      'emailContent' => 'Blah, blah, blah, ...'       ,
      'emailCc'      => 'salvor.hardin@foundation.com',
      'emailBcc'     => 'onum.barr@siwenna.com'
   }
);

Note also the use of the native php function extract() in the code above. Since the contents of the $params array are clearly identified by the call to the checkArrayAndSetDefaults() function, the extract() function may be used to automatically declare all array values as variables. Once extract() has been called, the variables passed in the array may be used as if they had been passed directly rather than in the array. See the php documentation for more details.

Full Code of UtilsValidator Class

The full code of the UtilsValidator class is included below. It has no dependencies. Note in the function checkType() below, that many types besides those used in the examples above may be checked for. Note also that user-defined types may be checked for by supplying the name of the class as the type string (this is accomplished by the line labelled 'Catch all').

/**************************************************************************************************\
*
* vim: ts=3 sw=3 et wrap co=100 go-=b
*
* Filename: "Utils_validator.php"
*
* Project: Utilities.
*
* Purpose: Utilities concerning validation.
*
* Author: Tom McDonnell 2008-07-01.
*
\**************************************************************************************************/

// Class definition. ///////////////////////////////////////////////////////////////////////////////

/*
 * Validation functions.  Most will perform some checks, and throw an exception any check fails.
 *
 * Note: Only use these functions where speed is unimportant.
 */
class Utils_validator
{
   // Public functions. -----------------------------------------------------------------------//

   /*
    *
    */
   public function __construct()
   {
      throw new Exception('This class is not intended to be instantiated.');
   }

   /*
    *
    */
   public static function checkArray($array, $typeByRequiredKey, $typeByOptionalKey = array())
   {
      assert('is_array($array            )');
      assert('is_array($typeByRequiredKey)');
      assert('is_array($typeByOptionalKey)');

      $nKeys         = count($array            );
      $nKeysRequired = count($typeByRequiredKey);
      $nKeysOptional = count($typeByOptionalKey);
      $nKeysMax      = $nKeysRequired + $nKeysOptional;

      if ($nKeys < $nKeysRequired || $nKeys > $nKeysMax)
      {
         throw new Exception
         (
            "Incorrect number of keys in array ($nKeys).  " .
            "Expected number in range [$nKeysRequired, $nKeysMax]."
         );
      }

      // Check required keys and types.
      foreach ($typeByRequiredKey as $key => $type)
      {
         if (!array_key_exists($key, $array))
         {
            throw new Exception("Required key '$key' does not exist in array.");
         }

         try
         {
            self::checkType($array[$key], $type);
         }
         catch (Exception $e)
         {
            throw new Exception("Type check failed for required key '$key'.\n" . $e->getMessage());
         }
      }

      // Check optional keys and types.
      $arrayExtra = array_diff_key($array, $typeByRequiredKey);
      foreach (array_keys($arrayExtra) as $key)
      {
         if (!array_key_exists($key, $typeByOptionalKey))
         {
            throw new Exception("Unexpected key '$key' found.");
         }

         try
         {
            self::checkType($array[$key], $typeByOptionalKey[$key]);
         }
         catch (Exception $e)
         {
            throw new Exception("Type check failed for optional key '$key'.\n" . $e->getMessage());
         }
      }
   }

   /*
    *
    */
   public static function checkArrayAndSetDefaults
   (
      &$array, $typeByRequiredKey, $typeAndDefaultByOptionalKey = array()
   )
   {
      $typeByOptionalKey = array();

      foreach ($typeAndDefaultByOptionalKey as $key => $typeAndDefault)
      {
         if (!is_array($typeAndDefault) || count($typeAndDefault) != 2)
         {
            throw new Exception
            (
               'Type and default value for optional parameter must be two-element array.'
            );
         }

         $typeByOptionalKey[$key] = $typeAndDefault[0];
      }

      self::checkArray($array, $typeByRequiredKey, $typeByOptionalKey);

      foreach ($typeAndDefaultByOptionalKey as $key => $typeAndDefault)
      {
         if (!array_key_exists($key, $array))
         {
            $array[$key] = $typeAndDefault[1];
         }
      }
   }

   /*
    *
    */
   public static function checkType($v, $type)
   {
      if (!is_string($type))
      {
         throw new Exception('Received non-string for $type.');
      }

      switch ($type)
      {
       // Basic types.
       case 'array'   : $b = is_array($v)   ; break;
       case 'bool'    : // Fall through.
       case 'boolean' : $b = is_bool($v)    ; break;
       case 'float'   : $b = is_float($v)   ; break;
       case 'int'     : $b = is_int($v)     ; break;
       case 'null'    : $b = is_null($v)    ; break;
       case 'numeric' : $b = is_numeric($v) ; break;
       case 'object'  : $b = is_object($v)  ; break;
       case 'resource': $b = is_resource($v); break;
       case 'scalar'  : $b = is_scalar($v)  ; break;
       case 'string'  : $b = is_string($v)  ; break;

       // Combinations of basic types.
       case 'arrayOrString': $b = (is_array($v) || is_string($v)); break;

       // C-type character checks.
       case 'ctype_alnum' : $b = ctype_alnum($v) ; break;
       case 'ctype_alpha' : $b = ctype_alpha($v) ; break;
       case 'ctype_cntrl' : $b = ctype_cntrl($v) ; break;
       case 'ctype_digit' : $b = ctype_digit($v) ; break;
       case 'ctype_graph' : $b = ctype_graph($v) ; break;
       case 'ctype_lower' : $b = ctype_lower($v) ; break;
       case 'ctype_print' : $b = ctype_print($v) ; break;
       case 'ctype_punct' : $b = ctype_punct($v) ; break;
       case 'ctype_space' : $b = ctype_space($v) ; break;
       case 'ctype_upper' : $b = ctype_upper($v) ; break;
       case 'ctype_xdigit': $b = ctype_xdigit($v); break;

       // Basic types with condition.
       case 'character'       : $b = (is_string($v) && strlen($v) == 1); break;
       case 'nonNegativeInt'  : $b = (is_int($v)    && $v         >= 0); break;
       case 'negativeInt'     : $b = (is_int($v)    && $v         <  0); break;
       case 'positiveInt'     : $b = (is_int($v)    && $v         >  0); break;
       case 'nonNegativeFloat': $b = (is_float($v)  && $v         >= 0); break;
       case 'nonPositiveFloat': $b = (is_float($v)  && $v         <= 0); break;
       case 'negativeFloat'   : $b = (is_float($v)  && $v         <  0); break;
       case 'positiveFloat'   : $b = (is_float($v)  && $v         >  0); break;
       case 'nonEmptyString'  : $b = (is_string($v) && strlen($v) >  0); break;
       case 'nonEmptyArray'   : $b = (is_array($v)  && count($v)  >  0); break;

       // Basic types or null.
       case 'nullOrArray'   : $b = (is_null($v) || is_array($v)   ); break;
       case 'nullOrBool'    : $b = (is_null($v) || is_bool($v)    ); break;
       case 'nullOrInt'     : $b = (is_null($v) || is_int($v)     ); break;
       case 'nullOrFloat'   : $b = (is_null($v) || is_float($v)   ); break;
       case 'nullOrNumeric' : $b = (is_null($v) || is_numeric($v) ); break;
       case 'nullOrObject'  : $b = (is_null($v) || is_object($v)  ); break;
       case 'nullOrResource': $b = (is_null($v) || is_resource($v)); break;
       case 'nullOrScalar'  : $b = (is_null($v) || is_scalar($v)  ); break;
       case 'nullOrString'  : $b = (is_null($v) || is_string($v)  ); break;

       // Basic types with condition or null.
       case 'nullOrCharacter'       : $b = (is_null($v) || is_string($v) && strlen($v) == 1); break;
       case 'nullOrNonEmptyString'  : $b = (is_null($v) || is_string($v) && strlen($v) >  0); break;
       case 'nullOrNonEmptyArray'   : $b = (is_null($v) || is_array($v)  && count($v)  >  0); break;
       case 'nullOrPositiveInt'     : $b = (is_null($v) || is_int($v)    && $v         >  0); break;
       case 'nullOrNegativeInt'     : $b = (is_null($v) || is_int($v)    && $v         <  0); break;
       case 'nullOrNonPositiveInt'  : $b = (is_null($v) || is_int($v)    && $v         <= 0); break;
       case 'nullOrNonNegativeInt'  : $b = (is_null($v) || is_int($v)    && $v         >= 0); break;
       case 'nullOrPositiveFloat'   : $b = (is_null($v) || is_float($v)  && $v         >  0); break;
       case 'nullOrNegativeFloat'   : $b = (is_null($v) || is_float($v)  && $v         <  0); break;
       case 'nullOrNonPositiveFloat': $b = (is_null($v) || is_float($v)  && $v         <= 0); break;
       case 'nullOrNonNegativeFloat': $b = (is_null($v) || is_float($v)  && $v         >= 0); break;

       // Date strings.
       case 'date_yyyy-mm-dd': $b = self::checkDateString($v, 'yyyy-mm-dd'); break;
       case 'date_yyyy/mm/dd': $b = self::checkDateString($v, 'yyyy/mm/dd'); break;
       case 'date_dd-mm-yyyy': $b = self::checkDateString($v, 'dd-mm-yyyy'); break;
       case 'date_dd/mm/yyyy': $b = self::checkDateString($v, 'dd/mm/yyyy'); break;

       // Date-time strings.
       case 'Y-m-d H:i:s': $b = self::checkDatetimeString($v, 'Y-m-d H:i:s'); break;

       // Catch all.
       default: $b = (gettype($v) == 'object')? (get_class($v) == $type): false;
      }

      if (!$b)
      {
         throw new Exception
         (
            "Variable type check failed.  Expected '$type', received " .
            (
               (gettype($v) == 'object')?
               'object of class \'' . get_class($v) . '\'.':
               'variable of type \'' . gettype($v) . '\'.'
            )
         );
      }
   }

   /*
    *
    */
   public static function checkDateString($dateStr, $format = 'yyyy-mm-dd')
   {
      switch ($format)
      {
       case 'yyyy-mm-dd': $regEx = '/^(\d{4})-(\d{2})-(\d{2})$/'  ; $y = 1; $m = 2; $d = 3; break;
       case 'yyyy/mm/dd': $regEx = '/^(\d{4})\/(\d{2})\/(\d{2})$/'; $y = 1; $m = 2; $d = 3; break;
       case 'dd-mm-yyyy': $regEx = '/^(\d{2})-(\d{2})-(\d{4})$/'  ; $d = 1; $m = 2; $y = 3; break;
       case 'dd/mm/yyyy': $regEx = '/^(\d{2})\/(\d{2})\/(\d{4})$/'; $d = 1; $m = 2; $y = 3; break;
       default: throw new Exception("Unknown format string '$format'");
      }

      return
      (
         preg_match($regEx, $dateStr, $matches) &&
         checkdate($matches[$m], $matches[$d], $matches[$y])
      );
   }

   /*
    *
    */
   public static function checkDatetimeString($datetimeStr, $format = 'Y-m-d H:i:s')
   {
      switch ($format)
      {
       case 'Y-m-d H:i:s':
         $regEx = '/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/';
         $y = 1; $m = 2; $d = 3; $h = 4; $i = 5; $s = 6;
         break;
       default:
         throw new Exception("Unknown format string '$format'");
      }

      return
      (
         (
            preg_match($regEx, $datetimeStr, $matches) &&
            checkdate($matches[$m], $matches[$d], $matches[$y]) &&
            (0 <= $matches[$h] && $matches[$h] <= 23) &&
            (0 <= $matches[$i] && $matches[$i] <= 59) &&
            (0 <= $matches[$s] && $matches[$s] <= 59)
         ) ||
         (
            // A zero date is allowed for debugging purposes
            // and for queries designed to include all.
            preg_match($regEx, $datetimeStr, $matches) &&
            $matches[$y] == 0 && $matches[$m] == 0 && $matches[$d] == 0 &&
            $matches[$h] == 0 && $matches[$i] == 0 && $matches[$s] == 0
         )
      );
   }
}

/*******************************************END*OF*FILE********************************************/

Javascript Version

I have written a similar class in Javascript that I have found to be similarly useful. Find it here. Note however that the file linked to has dependencies, and so is not usable by itself.

Saturday, 30 July 2011

Converting Numbers to Words in Javascript

I do not like using numeric characters in prose. In my opinion, writing looks better and reads more easily when numbers are spelled out in words.

I have written a Javascript class to enable my web applications to print numbers as words. The code may be useful to others I think, and so I have written this blog post.

The code linked to below will convert to words, any integer in the range [0, 1034 - 1].

The code could easily be extended to work with even larger numbers, but as numbers grow very large, the practicality of writing them as words diminishes. 1034 is of course overkill, and the fact that I choose to use scientific notation to express it implies that even I, a man who spells out numbers as words even in SMS messages, think that that number should not be written in words.

Up to the limit I decided on though, the value proposition of adding a single word (eg. 'heptillion') to the code and thereby increasing its range of application a thousandfold was just too much for me to resist. My resistance kicked in at 1034 for no better reasons than that the range had to end somewhere, that a 'decillion' is a nice round number, and that the word 'decillion' seems to have an air of finality to it, perhaps due to similarity with other words 'decease', 'decay', 'decapitate' etc.

See Names of Large Numbers in Wikipedia.

Link to Code

The code is too long to include in this post, so I go one better than Pierre de Fermat, and provide a link to it here. The file linked to has no dependencies.

Digression

A fact I found interesting while researching the origin of the word 'decillion' is that the word 'decimate' has a specific and gruesome meaning. The word 'decimate' refers to 'the practice of punishing mutinous military units by capital execution of one in every 10, by lot. Killing one in ten, chosen by lots, from a rebellious city or a mutinous army was a common punishment in classical times.' (Reference: Online Etymology Dictionary.) Note that the Online Etymology Dictionary does not follow my suggested rules for the output of numbers in sentences.

Test Results

1 one
10 ten
100 one hundred
1000 one thousand
10000 ten thousand
100000 one hundred thousand
1000000 one million
10000000 ten million
100000000 one hundred million
1000000000 one billion
10000000000 ten billion
9 nine
99 ninety-nine
999 nine hundred and ninety-nine
9999 nine thousand, nine hundred and ninety-nine
99999 ninety-nine thousand, nine hundred and ninety-nine
999999 nine hundred and ninety-nine thousand, nine hundred and ninety-nine
9999999 nine million, nine hundred and ninety-nine thousand, nine hundred and ninety-nine
99999999 ninety-nine million, nine hundred and ninety-nine thousand, nine hundred and ninety-nine
999999999 nine hundred and ninety-nine million, nine hundred and ninety-nine thousand, nine hundred and ninety-nine
9999999999 nine billion, nine hundred and ninety-nine million, nine hundred and ninety-nine thousand, nine hundred and ninety-nine
99999999999 ninety-nine billion, nine hundred and ninety-nine million, nine hundred and ninety-nine thousand, nine hundred and ninety-nine
2 two
12 twelve
112 one hundred and twelve
2112 two thousand, one hundred and twelve
12112 twelve thousand, one hundred and twelve
112112 one hundred and twelve thousand, one hundred and twelve
2112112 two million, one hundred and twelve thousand, one hundred and twelve
12112112 twelve million, one hundred and twelve thousand, one hundred and twelve
112112112 one hundred and twelve million, one hundred and twelve thousand, one hundred and twelve
2112112112 two billion, one hundred and twelve million, one hundred and twelve thousand, one hundred and twelve
12112112112 twelve billion, one hundred and twelve million, one hundred and twelve thousand, one hundred and twelve
5 five
50 fifty
505 five hundred and five
5050 five thousand and fifty
50505 fifty thousand, five hundred and five
505050 five hundred and five thousand and fifty
5050505 five million, fifty thousand, five hundred and five
50505050 fifty million, five hundred and five thousand and fifty
505050505 five hundred and five million, fifty thousand, five hundred and five
5050505050 five billion, fifty million, five hundred and five thousand and fifty
50505050505 fifty billion, five hundred and five million, fifty thousand, five hundred and five
2 two
22 twenty-two
202 two hundred and two
2002 two thousand and two
20002 twenty thousand and two
200002 two hundred thousand and two
2000002 two million and two
20000002 twenty million and two
200000002 two hundred million and two
2000000002 two billion and two
20000000002 twenty billion and two

Thursday, 14 July 2011

Favourite PHP/Javascript Functions - switchAssign()

In this series of articles I describe simple, elegant functions from my own personal library that have proven useful to me over the course of many projects.


The switchAssign() functions make my list of favourites because they are elegant, concise, and make code more readable.

Have you ever written code similar to this?

switch ($carManufacturerName)
{
 case 'ford'      : $bestSellingCarModelName = 'falcon'   ; break;
 case 'holden'    : $bestSellingCarModelName = 'commodore'; break;
 case 'mitsubishi': $bestSellingCarModelName = 'lancer'   ; break;
 case 'toyota'    : $bestSellingCarModelName = 'corolla'  ; break;
 default          : throw new Exception("Unknown car manufacturer '$carManufacturerName'.");
}

Or this?

$bestSellingCarModelNameByCarManufacturerName = array
(
   'ford'       => 'falcon'   ,
   'holden'     => 'commodore',
   'mitsubishi' => 'lancer'   ,
   'toyota'     => 'corolla'
);

if (!array_key_exists($carManufacturerName, $bestSellingCarModelByCarManufacturerName))
{
   throw new Exception('Unknown car manufacturer '$carManufacturerName'.");
}

$bestSellingCarModelName = $bestSellingCarModelNameByCarManufacturerName[$carManufacturerName];

The first alternative is compact, but repeats the words 'case' and 'break' and the name of the variable to be assigned for each case.

The second alternative is less text-heavy, but also less encapsulated. It works well when enclosed in a function, but when used on the fly this method clutters code, since it requires three blocks of code to accomplish the single task of assigning a value to a variable.

Often code of this sort is only required once, and in such cases wrapping the assignment code in its own function adds unnecessarily to the number of functions in the file or class, negatively impacting readability and maintainability.

Instead of the two alternatives described above, I prefer writing

$bestSellingCarModelName = switchAssign
(
   $carManufacturerName, array
   (
      'ford'       => 'falcon'   ,
      'holden'     => 'commodore',
      'mitsubishi' => 'lancer'   ,
      'toyota'     => 'corolla'
   )
);

Which uses the simple function switchAssign (defined below). The effect of the above code is exactly the same as in the earlier two examples, with an exception being thrown if the car manufacturer is not recognised.

If it is desired that a default case be provided, the function can be used as follows.

$bestSellingCarModelName = switchAssign
(
   $carManufacturerName, array
   (
      'ford'       => 'falcon'   ,
      'holden'     => 'commodore',
      'mitsubishi' => 'lancer'   ,
      'toyota'     => 'corolla'
   ), 'unknown'
);

In the above example, if no case matches the supplied car manufacturer name, then the $bestSellingCarName variable is set to 'unknown'.

See PHP and Javascript versions of function switchAssign() below, and note that in either, null can be used as a default assignment value.

PHP Version


/*
 * Usage example:
 *    $bestSellingCarModelName = Utils_misc::switchAssign
 *    (
 *       $carManufacturerName, array
 *       (
 *          'ford'   => 'falcon',
 *          'holden' => 'commodore'
 *       )
 *    );
 *
 * The above code is equivalent to:
 *    switch ($carManufacturerName)
 *    {
 *     case 'ford'  : $bestSellingCarModelName = 'falcon'   ; break;
 *     case 'holden': $bestSellingCarModelName = 'commodore'; break;
 *     default      : throw new Exception("Unknown car manufacturer '$carManufacturerName'.");
 *    }
 *
 * @param $defaultOutputValue {any type}
 *     This parameter is optional.  If it is not supplied, then there will be no output value
 *     for the default case specified and so an exception will be thrown in the default case.
 */
function switchAssign($inputValue, $outputValueByInputValue, $defaultOutputValue = null)
{
   if (array_key_exists($inputValue, $outputValueByInputValue))
   {
      return $outputValueByInputValue[$inputValue];
   }

   if (func_num_args() == 3)
   {
      return $defaultOutputValue;
   }

   throw new Exception
   (
      "Case '$inputValue' not handled in switchAssign and no default supplied."
   );
}

Javascript version

/*
 * Usage example:
 *    var bestSellingCarModelName = Utils_misc::switchAssign
 *    (
 *       carManufacturerName,
 *       {
 *          ford  : 'falcon',
 *          holden: 'commodore'
 *       }
 *    );
 *
 * The above code is equivalent to:
 *    switch (carManufacturerName)
 *    {
 *     case 'ford'  : var bestSellingCarModelName = 'falcon'   ; break;
 *     case 'holden': var bestSellingCarModelName = 'commodore'; break;
 *     default      : throw new Exception('Unknown car manufacturer "' + carManufacturerName + '".');
 *    }
 *
 * @param defaultOutputValue {any type}
 *     This parameter is optional.  If it is not supplied, then there will be no output value
 *     for the default case specified and so an exception will be thrown in the default case.
 */
function (inputValue, outputValueByInputValue, defaultOutputValue)
{
   if (typeof outputValueByInputValue[inputValue] != 'undefined')
   {
      return outputValueByInputValue[inputValue];
   }

   if (arguments.length == 3)
   {
      return defaultOutputValue;
   }

   throw new Exception
   (
      'Case "' + inputValue + '" not handled in switchAssign and no default supplied.'
   );
};

If anyone wishes to use these functions, I suggest adding them to a class so they can be called in php like

$x = Utils::switchAssign(...);

or in javascript

var x = Utils.switchAssign(...);