Welcome to Abdul Malik Ikhsan's Blog

Using Direct ArrayObject instance as ObjectPrototype in Zend\Db

Posted in Zend Framework 2, Zend Framework 3 by samsonasik on May 25, 2017

When creating a table model for ZF2 or ZF3 application with Zend\DB, direct ArrayObject instance can be usefull as ResultSet object prototype. We can no longer need to create an individual class that has getArrayCopy() or exchangeArray() for data transformation.

For example, we have the following table model:

<?php
namespace Application\Model;

use Zend\Db\TableGateway\AbstractTableGateway;

class CountryTable
{
    public static $table = 'country';
    private $tableGateway;

    public function __construct(AbstractTableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }

    public function getCountriesInAsia()
    {
        $select  = $this->tableGateway->getSql()->select();
        $select->where([
            'continent' => 'ASIA'
        ]);

        return $this->tableGateway->selectWith($select);
    }
}

The ArrayObject usage we can use is:

new ArrayObject([], ArrayObject::ARRAY_AS_PROPS);

So, we can build the factory for above table model as follows:

<?php
namespace Application\Model;

use ArrayObject;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;

class CountryTableFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $resultSetPrototype = new HydratingResultSet();
        $resultSetPrototype->setObjectPrototype(
             new ArrayObject([], ArrayObject::ARRAY_AS_PROPS)
        );
        
        $tableGateway =  new TableGateway(
            CountryTable::$table,
            $container->get('Zend\Db\Adapter\Adapter'),
            null,
            $resultSetPrototype
        );

        return new CountryTable($tableGateway);
    }
}

and register it into service_manager under factories:

<?php
namespace Application;

return [
    // ...
    'service_manager' => [
        'factories' => [
            Model\CountryTable::class => Model\CountryTableFactory:class,
        ],
    ],
];

When retrieving the data, you can do the followings:

use Application\Model\CountryTable;

$countryTable    = $container->get(CountryTable::class);
$countriesInAsia = $countryTable->getCountriesInAsia();

foreach ($countriesInAsia as $key => $row) {

    // dump a copy of the ArrayObject
    var_dump($arrayCopy = $row->getArrayCopy());

    // echoed column as property
    echo $row->name; // with value "INA"
    echo $row->iso;  // with value "ID"
    echo $row->continent; // with value "ASIA"

    // echoed as array with provided key
    echo $row['name']; // with value "INA"
    echo $row['iso'];  // with value "ID"
    echo $row['continent']; // with value "ASIA"

    // modify data via exhangeArray
    $row->exchangeArray(array_merge(
		$arrayCopy,
		[
			'name' => 'INDONESIA',
		]
	));

    // or modify its data by its property
    $row->name = 'INDONESIA';
    // or modify its data by its index array
    $row['name'] = 'INDONESIA';

    echo $row->name; // now has value "INDONESIA"
    echo $row['name']; // now has value "INDONESIA"
}

Bonus:

To avoid repetitive creating factory class for each table model, we can create an abstract factory for it:

<?php

namespace Application\Model;

use ArrayObject;
use Interop\Container\ContainerInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ServiceManager\Factory\AbstractFactoryInterface;

class CommonModelTableFactory implements AbstractFactoryInterface
{
    public function canCreate(ContainerInterface $container, $requestedName)
    {
        return ((substr($requestedName, -5) === 'Table') && class_exists($requestedName));
    }

    public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
    {
        $tableModel = '\\' . $requestedName;

        $resultSetPrototype = new HydratingResultSet();
        $resultSetPrototype->setObjectPrototype(
            new ArrayObject([], ArrayObject::ARRAY_AS_PROPS)
        );

        $tableGateway =  new TableGateway(
            $tableModel::$table,
            $container->get('Zend\Db\Adapter\Adapter'),
            null,
            $resultSetPrototype
        );

        return new $tableModel($tableGateway);
    }
}

So, now, we can have 1 abstract factory for all table model services:

<?php
namespace Application;

return [
    // ...
    'service_manager' => [
        'abstract_factories' => [
            Model\CommonModelTableFactory:class,
        ],
    ],
];

That’s it 😉

Advertisements