0

I've been playing with the Zend Hydrator class today and just found the Naming strategies for converting the input keys on the fly. But when playing with the MapNamingStrategy in conjunction with the ObjectProperty hydrator, it seems to add properties that didn't initially exist in the object if the input array contained them.

Is there any way to restrict it from adding new properties and only populating/hydrating existing ones in the input object?

Scott
  • 7,983
  • 2
  • 26
  • 41
  • I'm pulling an address record from a database and trying to populate another library's Address() class. Besides some key names being different such as 'address_1' vs 'address' the database also includes extra fields not in the third-party library. I only need to populate keys that match existing properties. – Scott Dec 09 '16 at 21:06

1 Answers1

0

Still no response on this - what I ended up doing was using one of two scenarios but it is still not ideal. The first is to use Class Reflection myself to get a list of keys that are accessible or to search for standard name accessors for same. (this, of course, would not find magic method accessors) The second was to pre-define a map that didn't only include mismatched key->property mappings but also included all the one-to-one (matched) key->property mappings then filter the input using PHP's array functions prior to running the hydration using the map's key/value pairs. But this kind of defeats the purpose of using hydration as by that point in time, I may as well have used a foreach loop instead. And it eliminates any ability to use abstract destinations in that you have to know all potential input/output key->property relationships in advance.

I ended up doing my own implementation of the first method (again, that will not necessarily handle magic method accessors) which looks for public properties and/or public accessors fitting the standard camel-cased setPropertyName()/getPropertyName() accessor methods.:

<?php
/**
 * simple object hydrator using class reflection to find publicly accessible properties and/or methods
 *
 * Created by PhpStorm.
 * User: scottw
 * Date: 12/12/16
 * Time: 12:06 PM
 */

namespace Finao\Util;

class SimpleHydrator
{
    /**
     * whether to reset the keyMap following each hydration to clear the hydrator for other data/object pairs
     *
     * @var bool $resetMap
     */
    private static $resetMap = true;

    /**
     * associative array of key mappings between incoming data and object property names/accessors
     * @var array $keyMap
     */
    private static $keyMap = array();
    public static function setKeyMap($map) {
        if(self::is_assoc($map))
            static::$keyMap = $map;
    }
    public static function populateObject(&$targetObject, $dataArray)
    {
        if (self::is_assoc($dataArray) && is_object($targetObject)) {
            // step through array elements and see if there are matching properties or methods
            try {
                foreach ($dataArray as $k => $v) {
                    $key = $k;
                    if(self::is_assoc(static::$keyMap) && array_key_exists($k))
                        $key = static::$keyMap[$k];
                    // if original value contains an object, try populating it if the associated value is also array
                    $origVal = self::getObjectPropertyValue($targetObject, $key);
                    if (is_object($origVal) && self::is_assoc($v)) {
                        self::populateObject($origVal, $v);
                        $v = $origVal;
                    }

                    $accessor = 'set' . ucfirst($key);
                    if (in_array($key, self::getObjectPublicProperties($targetObject)))
                        $targetObject->$key = $v;
                    elseif (in_array($accessor, self::getObjectPublicMethods($targetObject)))
                        $targetObject->$accessor($v);

                }
            } catch (\Exception $d) {
                // do something with failures
            }

            if(static::$resetMap) static::$keyMap = array();
        }

        return $targetObject;
    }

    public static function getObjectPropertyValue($object, $property)
    {
        $objectReflection = new \ReflectionClass($object);
        if ($objectReflection->hasProperty($property) && $objectReflection->getProperty($property)->isPublic())
            return $object->$property;
        else {
            $accessor = 'get' . ucfirst($property);
            if ($objectReflection->hasProperty($accessor) && $objectReflection->getMethod($accessor)->isPublic())
                return $object->$accessor();
        }
    }

    public static function getObjectPublicProperties($object)
    {
        if (is_object($object)) {
            $publicProperties = array();
            $objectReflection = new \ReflectionClass($object);
            foreach ($objectReflection->getProperties(\ReflectionProperty::IS_PUBLIC) as $p)
                array_push($publicProperties, $p->name);
            return $publicProperties;
        }
    }

    public static function getObjectPublicMethods($object)
    {
        if (is_object($object)) {
            $publicMethods = array();
            $objectReflection = new \ReflectionClass($object);
            foreach ($objectReflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $p)
                array_push($publicMethods, $p->name);
            return $publicMethods;
        }
    }

    /**
     * Determine if a variable is an associative array.
     *
     * @param  mixed   Input variable
     * @return boolean If the input variable is an associative array.
     * @see http://us2.php.net/manual/en/function.is-array.php
     */
    public static function is_assoc($array) {
        return (is_array($array) && 0 !== count(array_diff_key($array, array_keys(array_keys($array)))));
    }
}

I eventually added a simple key mapping ability to it. (Note that this has not been rigorously tested and, as the name suggests, is just a simple solution.)

Scott
  • 7,983
  • 2
  • 26
  • 41