<?php

    error_reporting(E_ALL | E_STRICT);

/*

Manual optional type checking for PHP functions

Basic example:

    function log_error($line_number, $filename, $desc)
    {
        CheckFunctionArgs('integer', 'string', 'string');
        [snip]
    }

Object example:

    class LogObject {}
    function register_object($obj)
    {
        //  Check for an object
        CheckFunctionArgs('object');    

        //  When an object is passed, you can optionally check for the class name
        CheckFunctionArgs('LogObject');

        [snip]
    }

Wildcard example:

    function log_anything($line, $thing)
    {
        //  '*' really means anything, included true/false/null or an empty string
        //  however it doesn't mean the argument in optional
        CheckFunctionArgs('integer', '*');
    }

Notes:

    Throws an exception on error
    No support for functions that take a variable number of arguments
    Must define the types of all arguments
    The type '*' acts as a wild card, matching anything (including null)
    Works with public and private methods in classes

*/

    function CheckFunctionArgs()
    {
        $types = func_get_args();
        $stack = debug_backtrace();

        //  Make sure there is some stack information
        //  Stack[1] contains the details about the function that called this function
        if(!isset($stack[1]))
        {
            throw new Exception("No function stack present.  Make sure CheckFunctionArgs() isn't called from the global scope");
        }

        //  The arguments that were passed to the function we are checking
        $arguments = $stack[1]['args'];


        //  Get the name of the class/function/file
        $functionClass = (isset($stack[1]['class'])) ? $stack[1]['class'] . "::" : '';
        $functionFile  = (isset($stack[1]['file'])) ? basename($stack[1]['file']) . ':' . $stack[1]['line'] : '[No file information]';
        $functionName  = "{$functionClass}{$stack[1]['function']}()";


        //  Basic check, make sure the correct numbers of arguments were passed
        if(count($arguments) != count($types))
        {
            $passed = count($arguments);
            $expected = count($types);

            throw new Exception("Incorrect number of argumemts passed to {$functionName} in {$functionFile}.  Expected {$expected} got {$passed}");
        }


        //  Now try and check each argument
        for($i = 0; $i < count($arguments); $i++)
        {
            //  Allow a check to be skiped, if the type equals '*'
            if($types[$i] == '*')
            {
                continue;
            }

            $argumentType = gettype($arguments[$i]);

            //  Check basic types like integer/object ect
            if($argumentType == $types[$i])
            {
                continue;
            }

            //  Check to see if the type matches the classname of an object
            if(($argumentType == 'object') && (get_class($arguments[$i]) == $types[$i]))
            {
                continue;
            }

            throw new Exception("Incorrect argument passed to {$functionName} in {$functionFile}.  Argument {$i} was type {$argumentType} expected {$types[$i]}");
        }


        return true;
    }

    //  Some really basic tests

    function assertException($fun, $args)
    {
        try
        {
            call_user_func_array($fun, $args);
            throw new Exception(sprintf("Error: No exception thrown in function %s\n", $fun));
        }
        catch(Exception $e)
        {
        }
    }

    function assertNoException($fun, $args)
    {
        try
        {
            call_user_func_array($fun, $args);
        }
        catch(Exception $e)
        {
            throw new Exception(sprintf("Error: Exception thrown in function %s\n", $fun));
        }
    }


    function test_string($a)
    {
        CheckFunctionArgs('string');
    }

    assertNoException('test_string', array('abc'));
    assertNoException('test_string', array(''));
    assertNoException('test_string', array('123'));

    @assertException('test_string', array());
    assertException('test_string', array(123));
    assertException('test_string', array(null));


    function test_integer($a)
    {
        CheckFunctionArgs('integer');
    }

    assertNoException('test_integer', array(0));
    assertNoException('test_integer', array(123));

    @assertException('test_integer', array());
    assertException('test_integer', array(null));
    assertException('test_integer', array(''));
    assertException('test_integer', array('a'));
    assertException('test_integer', array(1.0));
    assertException('test_integer', array(1.2));


    function test_wildcard($a)
    {
        CheckFunctionArgs('*');
    }

    assertNoException('test_wildcard', array('a'));
    assertNoException('test_wildcard', array(1233));
    assertNoException('test_wildcard', array(null));
    @assertException('test_wildcard', array());


    class Foobar {}
    $foobar = new Foobar();
    function test_classname($a)
    {
        CheckFunctionArgs('Foobar');
        CheckFunctionArgs('object');
    }

    assertNoException('test_classname', array($foobar));
    assertException('test_classname', array('Foobar'));

?>