开发者

Make my own (non-database) fetch_object function

开发者 https://www.devze.com 2023-01-30 18:47 出处:网络
In php mysql / mysqli / postgre / etc... there are fetch_object functions where you can get an object for your row of data. By default it will return an object of stdClass, but you can also define a c

In php mysql / mysqli / postgre / etc... there are fetch_object functions where you can get an object for your row of data. By default it will return an object of stdClass, but you can also define a class_name and an array of params for the constructor.

I would like to do the same开发者_JS百科 thing with a plain set of values. Preferably, setting the properties of the object before calling the constructor, which is the same behaviour the database-functions show. However, this doesn't seem to be possible.

The only way to even create an object with properties set seems to be to unserialize a preconstructed string. But that example still creates a new object as well, then sets the properties of that object from the unserialized object to ensure the constructor is called. But this means the constructor is called before the properties are set.

In short: I would like the following:

array_fetch_object(array $properties, string $class_name [, array $params ])

with the constructor called after the properties are set.


In the end I wrote the following class which performs the task using unserializing a fabricated string. It uses reflection to determine what the access-type of properties is and what the name of the constructor is (if any).

The heart of the class is the following line:

$object = unserialize('O:'.strlen($class_name).':"'.$class_name.'"'.substr(serialize($properties), 1));

which serializes the property-array and renames the serialized to the desired class_name.

class ObjectFactory {

    private $properties;
    private $constructors;

    public function __construct() {
        $this->properties   = array();
        $this->constructors = array();
    }

    private function setClass($class_name) {

        $class = new ReflectionClass($class_name);
        $this->properties[$class_name] = array();

        foreach($class->getProperties() as $property) {

            $name     = $property->getName();
            $modifier = $property->getModifiers();

            if($modifier & ReflectionProperty::IS_STATIC) {
                continue;
            } else if($modifier & ReflectionProperty::IS_PUBLIC) {
                $this->properties[$class_name][$name] = $name;
            } else if($modifier & ReflectionProperty::IS_PROTECTED) {
                $this->properties[$class_name][$name] = "\0*\0".$name; // prefix * with \0's unserializes to protected property
            } else if($modifier & ReflectionProperty::IS_PRIVATE) {
                $this->properties[$class_name][$name] = "\0".$class_name."\0".$name; // prefix class_name with \0's unserializes to private property
            }
        }

        if($constructor = $class->getConstructor()) {
            $this->constructors[$class_name] = $constructor->getName();
        }
    }

    private function hasClassSet($class_name) {

        return array_key_exists($class_name, $this->properties);
    }

    private function hasClassProperty($class_name, $property_name) {

        if(!$this->hasClassSet($class_name))
            $this->setClass($class_name);

        return array_key_exists($property_name, $this->properties[$class_name]);
    }

    private function getClassProperty($class_name, $property_name) {

        if(!$this->hasClassProperty($class_name, $property_name))
            return false;

        return $this->properties[$class_name][$property_name];
    }

    private function hasClassConstructor($class_name) {

        if(!$this->hasClassSet($class_name))
            $this->setClass($class_name);

        return $this->constructors[$class_name] !== false;
    }

    private function getClassConstructor($class_name) {

        if(!$this->hasClassConstructor($class_name))
            return false;

        return $this->constructors[$class_name];
    }

    public function fetch_object(array $assoc, $class_name = 'stdClass', array $params = array()) {

        $properties = array();

        foreach($assoc as $key => $value) {
            if($property = $this->getClassProperty($class_name, $key)) {
                $properties[$property] = $value;
            }
        }

        $object = unserialize('O:'.strlen($class_name).':"'.$class_name.'"'.substr(serialize($properties), 1));

        if($constructor = $this->getClassConstructor($class_name)) {
            call_user_func_array(array($object, $constructor), $params);
        }

        return $object;
    }
}


Well, you can cast an array to an object, like this:

$array = array('a' => 'a', 'b' => 'c');
$object = (object) $array;

or just:

$object = (object) array('a' => 'a', 'b' => 'c');

This will give you a stdClass object with the properties of the array.


Ok, I didn't expect this to work, but it does:

class TestObject {

    public $property;
    public $preset;

    public function __construct($param) {

        $this->property = $param;
    }
}

$object = unserialize('O:10:"TestObject":1:{s:6:"preset";i:1;}');
$object->__construct(1);

print_r($object);

results:

TestObject Object
(
    [property] => 1
    [preset] => 1
)

I just have to check the access type of the property before creating the serialized string because the classname is prepended for private properties. However, is calling the constructor of an already constructed object expected to stay working?

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号