5

First I've read the following two stackoverflow questions, but they didn't really give me an answer:

In my application I have an employee database table, which has numerous properties but the most interesting for me currently is the manager_id and the bank_id which are foreign keys.

  • manager_id is a foreign key to another employee (as you can imagine an employee can have one manager)
  • bank_id is a foreign key to another model/db-table called bank - because an employee can have a bank account ;-)

Now in my EmployeeTable.php file I have those magic methods where I get the database results.

To get one employee I do this:

/**
 * @param int $id
 *
 * @return Employee
 */
public function getEmployeeById($id)
{
    $rowset = $this->tableGateway->select(['id' => (int) $id]);
    /** @var Employee $row */
    $row = $rowset->current();

    if (!$row) {
        throw new RuntimeException(sprintf(
            'Could not find row with identifier %d',
            (int) $id
        ));
    }

    return $row;
}

But without any sql joins I only have the manager_id and bank_id in my returned employee object.

Question: What is the best practise to get those needed information?

So far I have two thoughts:

First

Should I - if the $row isn't empty - call for e.g the bankTable object (via dependency injection) which has an getBankById method. Then I'll extend my Employee.php model with an $bank property with a getter/setter and before the return statement in my getEmployeeId method I would do something like this:

$row->setBank($this->bankTable->getBankById($row->bank_id));

But I'm afraid of a recursive loop doing this for the manager_id because I would call the same method I'm currently in.

Second

Or should I extend my getEmployeeById method with a left join to get the data from the bank table like this:

$select = $this->tableGateway->getSql()->select()
        ->join(['b' => 'bank'], 'bank_id = m.id',
            [
                'bank.id' => 'id',
                'bank.description' => 'description',
                'bank.bic' => 'bic',
            ],
            Select::JOIN_LEFT)
        ->join(['m' => 'employee'], 'manager_id = m.id',
            [
                'manager.id' => 'id',
                'manager.forename' => 'forename',
                'manager.surname' => 'surname',
                // and all the other properties
            ],
            Select::JOIN_LEFT);

$result = $this->tableGateway->selectWith($select);

$row= $result->current();
$resultSet = $this->hydrator->hydrate($this->hydrator->extract($row), $row);

Unfortunately I have to give my joined columns alias names else I would overwrite the id from the employee with the bank id etc.

After this kind of sql statement you can see, that I would extract the result to get the properties as values and then hydrate them.

Hydration would look like this:

/**
 * @param array $data
 * @param Employee $object
 *
 * @return Employee
 */
public function hydrate(array $data, $object)
{
    if (!$object instanceof Employee) {
        throw new \BadMethodCallException(sprintf(
            '%s expects the provided $object to be a PHP Employee object)',
            __METHOD__
        ));
    }

    $employee = new Employee();
    $employee->exchangeArray($data);

    $bank = new Bank();
    $bank->exchangeArray($data, 'bank.');
    $employee->setBank($bank);

    $manager = new Employee();
    $manager->exchangeArray($data, 'manager.');
    $employee->setManager($manager);

    return $employee;
}

As a result of this I have a clean employee model (without those extra alias columns) and additionally 2 new properties which are objects of another employee(manager) and the bank.

But this looks quite overloaded...

Thanks for reading so far - If you have any hints or advices they are warmly welcomed!

EDIT

I've edited my EmployeeTableFactory to do the following (about hydrating):

public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
    $dbAdapter = $container->get(AdapterInterface::class);
    $resultSetPrototype = new HydratingResultSet();
    $resultSetPrototype->setHydrator(new EmployeeHydrator());
    $resultSetPrototype->setObjectPrototype(new Employee());

    $tableGateway = new TableGateway('employee', $dbAdapter, null, $resultSetPrototype);

    return new EmployeeTable($tableGateway);
}

I changed my EmployeeHydrator to implement the HydratorInterface because I was already using the extract stuff and now it matches the necessary interface for the resultSetPrototype->setHydrator() method.

Now things are getting pretty easy in the getEmployeeById method because with the following code I already have my finished employee object and all related foreign key objects (due to my EmployeeHydrator)

$result = $this->tableGateway->selectWith($select);
$row = $result->current(); // the given employee object result already hydrated!

return $row;

I kind of like this implementation

rogaa
  • 370
  • 2
  • 16
  • There are multiple ways of doing so. In my opinion the second way you used is probably the best. You could use an aggregator hydrator, or strategies to deal with the specific part, but the idea is here. On the other hand, you could also define a behavior for a user to fetch the bank data on hydratation & etc. The problem would be that you would make a lot of sql request, but it would be more "ORM-like". – Unex Aug 25 '16 at 12:36
  • 1
    Any reason you are avoiding using an ORM (doctrine2 works excellently with ZF2/3)? When dealing with complex related domain objects, an ORM really is the easiest way to get that shipped. – Tim Klever Dec 06 '16 at 18:31
  • @TimKlever in August the doctrine2 module with zf3 wasn't working properly - and I tried to get the project running with the given tools. – rogaa Dec 07 '16 at 13:41

0 Answers0