Welcome to Abdul Malik Ikhsan's Blog

Zend Framework 2 : Using HydratingResultSet and ObjectProperty’s Hydrator

Posted in Tutorial PHP, Zend Framework 2 by samsonasik on September 5, 2013

zf2-zendframework2When joining table using Zend\Db, calling coloumn(s) in other table when make a join is a pain. It because if we use normal Zend\Db\ResultSet\ResultSet and setting up ArrayObjectPrototype with the class object, we only get the current model class only ( that consist of columns of single table). Register other table column on that class is NOT a solution, it will make a confusion on what entity is for. Maybe not best (errr…good) solution but simplest solution is to use Zend\Db\ResultSet\HydratingResultSet and Zend\Stdlib\Hydrator\ObjectProperty.
There is I explain on case situation : “we have a table named album ( with columns : `id`, `artist`, `title` ) and table named track ( with columns : `track_id`, `track_title`, and `album_id` as foreign key agains album )”.
Ok, let’s create model like as the docs :

//filename : module/Album/src/Album/Model/Album.php
namespace Album\Model;

class Album
{
    public $id;
    public $artist;
    public $title;

    public function exchangeArray($data)
    {
        $this->id     = (isset($data['id']))     ? $data['id']     : null;
        $this->artist = (isset($data['artist'])) ? $data['artist'] : null;
        $this->title  = (isset($data['title']))  ? $data['title']  : null;
    }

    public function getArrayCopy()
    {
        return get_object_vars($this);
    }
}

Ok, Let’s create a sample table class for album table :

//filename : module/Album/src/Album/Model/AlbumTable.php
namespace Album\Model;

use Zend\Db\TableGateway\TableGateway;

class AlbumTable
{
    protected $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }
    
   //to retrieve tableGateway object when needed.
    public function getTableGateway()
    {
        return $this->tableGateway;
    }

    public function JoinfetchAll()
    {
        $sqlSelect = $this->tableGateway->getSql()
                          ->select()
                          ->join('track', 'track.album_id = album.id', array('*'), 'left');

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

If you want to reduce closure usage at your application, you can create factory for AlbumTable creation like the following :

//filename : module/Album/src/Album/Factory/Model/AlbumTableFactory.php
namespace Album\Factory\Model;

use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\Db\TableGateway\TableGateway;
use Album\Model\AlbumTable;
use Album\Model\Album;

use Zend\Stdlib\Hydrator\ObjectProperty;
use Zend\Db\ResultSet\HydratingResultSet;

class AlbumTableFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        $db = $serviceLocator->get('Zend\Db\Adapter\Adapter');

        $resultSetPrototype = new HydratingResultSet();
        $resultSetPrototype->setHydrator(new ObjectProperty());
        $resultSetPrototype->setObjectPrototype(new Album());

        $tableGateway       = new TableGateway('album', $db, null, $resultSetPrototype);
        $table              = new AlbumTable($tableGateway);

        return $table;
    }
}

and we just make a call like this :

//filename : module/Album/Module.php
namespace Album;

class Module
{
     // getAutoloaderConfig() and getConfig() methods here

     // Add this method:
     public function getServiceConfig()
     {
         return array(
             'factories' => array(
                 'Album\Model\AlbumTable' =>  'Album\Factory\Model\AlbumTableFactory'
             ),
         );
     }
 }

That’s it, we now can call like this :

$table = $this->getServiceLocator()->get('Album\Model\AlbumTable');
$joinedData = $table->JoinfetchAll();

foreach($joinedData as $row) {
    //$row->track_id will called even not registered
    //in album model class.
    echo $row->artist.' : '.$row->track_id; 
}

We know that we need to retrieve track table column form *JoinfetchAll()* table. but we don’t need to register it to be retrieve-able.

Ok, done, happy hacking 😉

15 Responses

Subscribe to comments with RSS.

  1. Michael said, on September 5, 2013 at 9:34 am

    It’s nice to see your tutorial, i am working with HydratingResultSet and after i get the album entity, I want to save it so I pass to AlbumTable and it show message: track_id, track_title,album_id are not in the fileds list because Album(table database) consist only id, artist and title field.
    May you show me the best way to fix this issue, thanks !

  2. […] Abdul Malik Ikhsan: Using HydratingResultSet and ObjectProperty’s Hydrator […]

  3. Robert said, on September 22, 2013 at 3:09 pm

    Hello I tried your sample. I injected this code in index.phtml.
    $table = $this->getServiceLocator()->get(‘Album\Model\AlbumTable’);
    $joinedData = $table->JoinfetchAll();

    foreach($joinedData as $row) {
    //$row->track_id will called even not registered
    //in album model class.
    echo $row->artist.’ : ‘.$row->track_id;
    }
    i have an error

    get was unable to fetch or create an instance for getsServiceLocator.

    Could you giva nay help

  4. namjitharavind said, on November 26, 2013 at 8:31 pm

    Is there any way to see the $joinedData array_object as simple array rather than looping in for loop?

  5. JOSEPH said, on December 13, 2013 at 6:55 pm

    this is nice..reli worked for me…

  6. […] Zend Framework 2 : Using HydratingResultSet and ObjectProperty’s Hydrator […]

  7. Akash said, on January 9, 2015 at 5:39 pm

    Is it necessary to create factory for AlbumTable, if i already have this:
    ‘Album\Model\TrackTable’ => function($sm) {
    $tableGateway = $sm->get(‘TrackTableGateway’);
    $table = new TrackTable($tableGateway);
    return $table;
    },
    in module.php

  8. Rogaa said, on August 19, 2016 at 7:53 pm

    Hey, thanks for the code sharing.

    Is this best practise? For example when I have a user table which has a foreign key for a role table.
    Should I generate from the fetchAll result a role object and store it in the employee object (via setter/getter functionality) or isn’t this a good approach? <- this could be done by writing a custom hydrator with HydratorInterface implementation.

    As you can image I'm trying to get the user roles after he successfully logged in (via auth stuff). Now I need to store the user_role with the authenticated user to check permissions when navigation (for example if the logged user has permission to navigate to a specific route)

    Unfortunately I don't find any good resource what is the best practise to match the authenticated user with the user in the database (and therefore it's configured role)…

    Any hints are warmly welcome 😉

    Greetings

    • Rogaa said, on August 19, 2016 at 8:11 pm

      Ah I obviously can answer my question itself

      I don’t need this by getting the user object via fetch in my userTable.

      I can get my result via altering the sql @ the authentication db table stuff.
      see here: https://docs.zendframework.com/zend-authentication/adapter/dbtable/credential-treatment/#alter-the-sql-select-directly

      I can then do something like this:

      $adapter = new CredentialTreatmentAdapter(
      $this->dbAdapter,
      ’employee’,
      ‘system_name’,
      ‘password’,
      true
      );

      // get the user role description
      $select = $adapter->getDbSelect();
      $select->join([‘R’ => ‘role’], ‘role_id = R.id’, [‘role’ => ‘description’], Select::JOIN_LEFT);

      and later store it in the loginController via:
      $this->authService->getAuth()->getStorage()->write([
      ‘id’ => $employeeObject->id,
      ‘role’ => $employeeObject->role,
      ]);

      Greetings


Leave a reply to Rogaa Cancel reply