0

Suppose I have a query that returns the following data:

RangeId | MinValue | MaxValue | Resolution | UnitId | UnitName

I want to hydrate the object MeasurementRange with the above data.

class MeasurementRange {
    public function getRangeId() {...};
    public function setRangeId($id) {...};
    public function getRange() {...};
    public function setRange(Range $range) {...};
    public function getUnit() {...};
    public function setUnit(Unit $unit) {...};
}

class Range {
    public function getMinValue() {...};
    public function setMinValue(float $minVal) {...};
    public function getMaxValue() {...};
    public function setMaxValue(float $maxVal) {...};
    public function getResolution {...};
    public function setResolution(float $resolution) {...};
}

class Unit {
    public function getUnitId() {...};
    public function setUnitId(int $id) {...};
    public function getUnitName() {...};
    public function setUnitName(string $name) {...};
}

As you can see the MeasurementRange object has set Range and Unit objects.

How can I hydrate MeasurementRange and the inner Range and Unit objects from the above query?

PS: I didn't specify protected properties of the objects. I guess they are self-evident.

Dima Dz
  • 512
  • 1
  • 5
  • 17
  • 1
    Maybe you should have a look at [Doctrine ORM](http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/)? It might be useful to you. – Wilt Jan 22 '17 at 11:28
  • @Wilt, thanks for the suggestion. i'm afraid learning a new tool will take time, and i'm quite limited in time for this project. do you have an idea how to do what i want with the native zf2 things? – Dima Dz Jan 23 '17 at 15:16
  • @Wilt, correct me if i'm wrong, but from my quick glance on Doctrine ORM, i'm not sure it'll map these object. it is very good when you have tables corresponding to your entity classes, but that's not what i have. – Dima Dz Jan 28 '17 at 08:05

1 Answers1

1

You need to create a mapper, that will use your dbAdapter to fetch the data as an array, and then use hydrators to hydrate all the objects, and finally add the Range and Unit to MeasurementRange. You could alternatively create a custom hydrator (that would be even better, in terms of single responsibility).

I haven't got time to clean the example below, but that's what it could look like :)

final class LanguageMapper
{
    /**
     * @param LanguageTable   $languageTable
     * @param PackageTable    $packageTable
     * @param Cache           $cache
     * @param LoggerInterface $logger
     */
    public function __construct(LanguageTable $languageTable, PackageTable $packageTable, Cache $cache, LoggerInterface $logger)
    {
        $this->languageTable = $languageTable;
        $this->packageTable = $packageTable;
        $this->cache = $cache;
        $this->logger = $logger;
    }

    /**
     * @param array $where
     *
     * @return array List of active languages
     */
    public function findActive(array $where = [])
    {
        try {
            if (empty($where) && $this->cache->hasItem('active_languages')) {
                return unserialize($this->cache->getItem('active_languages'));
            }
        } catch (RuntimeException $exception) {
             $this->logger->critical($exception->getMessage(), [
                'exception' => $exception,
                'file' => $exception->getFile(),
                'line' => $exception->getLine(),
             ]);
        }
        /* @var $adapter \Zend\Db\Adapter\Adapter  */
        $adapter = $this->languageTable->getGateway()->getAdapter();
        $sql = new Sql($adapter);
        $select = $sql->select()->columns([Select::SQL_STAR])
            ->from('language')
            ->join('package', 'package.id = language.package', Select::SQL_STAR, Select::JOIN_LEFT)
            ->where(array_merge($where, ['active' => true]))
            ->order(['position']);
        $selectString = $sql->buildSqlString($select);

        $resultSet = $adapter->query($selectString, Adapter::QUERY_MODE_EXECUTE);
        $languages = [];
        $hydrator = new ArraySerializable();
        foreach ($resultSet as $result) {
            $language = new Language();
            $package = new Package();
            $hydrator->hydrate((array) $result, $package);
            $hydrator->hydrate((array) $result, $language);
            $language->setPackage($package);
            $languages[] = $language;
        }
        if (empty($where)) {
            try {
                $this->cache->setItem('active_languages', serialize($languages));
            } catch (RuntimeException $exception) {
                $this->logger->warning($exception->getMessage(), [
                    'exception' => $exception,
                    'file' => $exception->getFile(),
                    'line' => $exception->getLine(),
                ]);
            }
        }

        return $languages;
    }
}
Thomas Dutrion
  • 1,844
  • 1
  • 11
  • 9
  • i thought of the same ideas too. problem is it gets quite tedious in both solutions if you have many objects (which what i have). when your mapper gets data, then hydration, then assembling the object -- too many foreaches which may affect the system's performance. writing a dedicated hydrator and using AggregateHydrator to assemble the hydrators leads to many hydrators and hydrators become very dependent on your query: once you change something in query, you have to change the hydrator. so as always pros and cons... i've +1ed your answer and see what other guys have to write. – Dima Dz Jan 28 '17 at 07:51