How to Dynamically Instantiate Classes in PHP
Published on 10 March 2009 -This content is old and might be outdated.
For Annabel (the name of the product that I currently work on), 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:
<?php
$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:
<?php
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 thought some more, 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, but it basically works:
<?php
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. Overall, I'm pretty happy with the solution!