How To Dynamically Instantiate Classes In PHP

For Annabel, I need to be able to dynamically instantiate classes, based on options in a configuration file (Zend_Config_Ini). However, some of these classes have constructors (some with required options), others don’t even have a constructor. I first tried to instantiate them with call_user_func_array, like this:

$datatype_options = explode(',', $global_config->customer->data->field->$key->type);
$datatype_class = array_shift($datatype_options);
$class = call_user_func_array(array($datatype_class, '__construct'), $datatype_options);

This didn’t work, since some classes don’t have a constructor and they were not static. Then, I came up with another solution: use a generic static method getInstance() to do it, like this:

class Test {
    public static function getInstance($classname)
    {
        return new $classname();
    }
}

$datatype_options = explode(',', $global_config->customer->data->field->$key->type);
$datatype_class = array_shift($datatype_options);
$class = call_user_func_array(array('Test', 'getInstance'), $datatype_options);

This worked, as long as I don’t need any constructor parameters. But for some classes, I do need them. So I went thinking, and came to the conclusion that I would need the Reflection API to do what I want. Finally, I implemented the following solution, which works perfectly. It could probably be cleaned up a little bit, but it basically works:

class Annabel_Data
{
    /**
     * Creates instances of classes in the Annabel_Data package.
     * 
     * To use this, provide arguments in the order you would need them
     * in the instantiated classes, with as the first argument the name
     * of the class to instantiate.
     * 
     * @param $arguments An array with arguments
     * 
     * @return Annabel_Data_Abstract
     */
    public static function factory($arguments)
    {
        $class_name = array_shift($arguments);
        $class_name = 'Annabel_Data_' . ucfirst($class_name);
        Zend_Loader::loadClass($class_name);
        $reflector = new ReflectionClass($class_name);
        if ($reflector->isInstantiable()) {
            $constructor = $reflector->getConstructor();
            if (is_null($constructor)) {
                return $reflector->newInstance();
            }
            $params = $constructor->getNumberOfParameters();
            $req_params = $constructor->getNumberOfRequiredParameters();
            if (count($arguments) > $req_params) {
                throw new Annabel_Data_Exception('Please provide ' . $req_params . ' parameters to instantiate class ' . $class_name);
            }
            if (0 > $params) {
                return $reflector->newInstanceArgs($arguments);
            } else {
                return $reflector->newInstance();
            }
        } else {
            throw new Annabel_Data_Exception("Could not instantiate a class of type '" . $class_name . "'");
        }
    }
}

$datatype_options = explode(',', $global_config->customer->data->field->$key->type);
$class = Annabel_Data::factory($datatype_options);

This works perfectly, for each case I need it (and tested it ;)). I should add some more exception checking and things like that, but those are minor details.