First I've read the following two stackoverflow questions, but they didn't really give me an answer:
- How to extend the ZF2 skeleton application - entities with foreign keys
- Zend Framework 2 model foreign keys
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 calledbank
- 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