Skip to content

Instantly share code, notes, and snippets.

@rogatec
Last active September 1, 2016 13:34
Show Gist options
  • Save rogatec/a36c599bd5b8fe4d2c0a67cbb63f5e93 to your computer and use it in GitHub Desktop.
Save rogatec/a36c599bd5b8fe4d2c0a67cbb63f5e93 to your computer and use it in GitHub Desktop.
Zend Framework 3 get model db result with foreign keys to another/itself model with custom hydrator and form fieldsets (in progress)
<?php
namespace Employee\Model;
class Bank
{
public $id;
public $name;
public $bic;
/**
* @param array $data
* @param string $prefix if you need only a getBankById you don't have a prefix
* in the employee relation with the 'bank.' prefix you can create your bank object this way
*/
public function exchangeArray(array $data, $prefix = '')
{
$this->id = !empty($data[$prefix.'id']) ? $data[$prefix.'id'] : null;
$this->name = !empty($data[$prefix.'name']) ? $data[$prefix.'name'] : null;
$this->bic = !empty($data[$prefix.'bic']) ? $data[$prefix.'bic'] : null;
}
public function getArrayCopy()
{
return get_object_vars($this);
}
}
<?php
namespace Employee\Form\Fieldset;
use Employee\Model\Bank;
use Employee\Model\BankTable;
use Zend\Form\Element\Select;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Validator\NotEmpty;
class BankFieldset extends Fieldset implements InputFilterProviderInterface
{
/**
* @var array
*/
private $banks = [];
/**
* BankFieldset constructor.
*
* @param BankTable $bankTable
*/
public function __construct(BankTable $bankTable)
{
parent::__construct('bank');
$resultSet = $bankTable->fetchAll();
/** @var Bank $bank */
foreach ($resultSet as $bank) {
$this->banks[$bank->id] = $bank->name . ' - [' . $bank->bic . ']';
}
}
public function init()
{
$this->add([
'type' => Select::class,
'name' => 'description',
'options' => [
'label' => 'Bank',
'label_attributes' => [
'class' => 'control-label',
],
'empty_option' => 'Please select...',
'value_options' => $this->banks,
],
'attributes' => [
'class' => 'form-control',
],
]);
}
/**
* Should return an array specification compatible with
* {@link Zend\InputFilter\Factory::createInputFilter()}.
*
* @return array
*/
public function getInputFilterSpecification()
{
return [
'description' => [
'required' => true,
'validators' => [
[
'name' => NotEmpty::class,
],
],
],
];
}
}
<?php
namespace Employee\Factory;
use Employee\Form\Fieldset\BankFieldset;
use Employee\Model\Bank;
use Employee\Model\BankTable;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\ContainerException;
use Zend\Hydrator\ClassMethods;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface;
class BankFieldsetFactory implements FactoryInterface
{
/**
* Create an object
*
* @param ContainerInterface $container
* @param string $requestedName
* @param null|array $options
*
* @return object
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException if any other error occurs
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$fieldset = new BankFieldset($container->get(BankTable::class));
$fieldset->setObject(new Bank())->setHydrator(new ClassMethods(false));
return $fieldset;
}
}
<?php
namespace Employee\Model;
class Employee
{
public $id;
public $bank_id;
public $salutation;
public $title;
public $forename;
public $surname;
// any other database columns
/**
* @var Bank
*/
private $bank;
// maybe other foreign key models as private properties
/**
* @param array $data
* @param string $prefix this prefix is useful if you have a foreign key to the model itself (example a manager_id)
* due to alias your manager_id result you will have something like manager.id manager.salutation etc.
* with this prefix you can fill your related manager employee model
*/
public function exchangeArray(array $data, $prefix = '')
{
$this->id = !empty($data[$prefix.'id']) ? $data[$prefix.'id'] : null;
$this->bank_id = !empty($data[$prefix.'bank_id']) ? $data[$prefix.'bank_id'] : null;
$this->salutation = !empty($data[$prefix.'salutation']) ? $data[$prefix.'salutation'] : null;
$this->title = !empty($data[$prefix.'title']) ? $data[$prefix.'title'] : null;
$this->forename = !empty($data[$prefix.'forename']) ? $data[$prefix.'forename'] : null;
$this->surname = !empty($data[$prefix.'surname']) ? $data[$prefix.'surname'] : null;
// other database columns
}
public function getArrayCopy()
{
return get_object_vars($this);
}
/**
* @return Bank
*/
public function getBank()
{
return $this->bank;
}
/**
* @param Bank $bank
*
* @return Employee
*/
public function setBank($bank)
{
$this->bank = $bank;
return $this;
}
// maybe other foreign key models with getter/setter
// input filters for form
}
<?php
namespace Employee\Form\Fieldset;
use Zend\Filter\StringTrim;
use Zend\Filter\StripTags;
use Zend\Filter\ToInt;
use Zend\Form\Element\Hidden;
use Zend\Form\Element\Select;
use Zend\Form\Element\Text;
use Zend\Form\Fieldset;
use Zend\InputFilter\InputFilterProviderInterface;
use Zend\Validator\NotEmpty;
use Zend\Validator\StringLength;
class EmployeeFieldset extends Fieldset implements InputFilterProviderInterface
{
public function init()
{
parent::__construct('employee');
$this->add([
'type' => Hidden::class,
'name' => 'id',
]);
$this->add([
'type' => Select::class,
'name' => 'salutation',
'options' => [
'label' => 'Anrede',
'label_attributes' => [
'class' => 'control-label',
],
'empty_option' => 'Bitte wählen Sie aus...',
'value_options' => [
'Frau' => 'Frau',
'Herr' => 'Herr',
],
],
'attributes' => [
'class' => 'form-control',
],
]);
$this->add([
'type' => Text::class,
'name' => 'title',
'options' => [
'label' => 'Titel',
'label_attributes' => [
'class' => 'control-label',
],
],
'attributes' => [
'class' => 'form-control',
],
]);
$this->add([
'type' => Text::class,
'name' => 'forename',
'options' => [
'label' => 'Vorname',
'label_attributes' => [
'class' => 'control-label',
],
],
'attributes' => [
'class' => 'form-control',
],
]);
$this->add([
'type' => Text::class,
'name' => 'surname',
'options' => [
'label' => 'Nachname',
'label_attributes' => [
'class' => 'control-label',
],
],
'attributes' => [
'class' => 'form-control',
],
]);
$this->add([
'type' => BankFieldset::class,
'name' => 'bank',
]);
}
/**
* Should return an array specification compatible with
* {@link Zend\InputFilter\Factory::createInputFilter()}.
*
* @return array
*/
public function getInputFilterSpecification()
{
return [
'id' => [
'required' => true,
'filters' => [
['name' => ToInt::class],
],
],
'salutation' => [
'required' => true,
'validators' => [
[
'name' => NotEmpty::class,
],
],
],
'title' => [
'required' => false,
'filters' => [
['name' => StripTags::class],
['name' => StringTrim::class],
],
'validators' => [
[
'name' => StringLength::class,
'options' => [
'encoding' => 'UTF-8',
'min' => 3,
'max' => 32,
],
],
],
],
'forename' => [
'required' => true,
'filters' => [
['name' => StripTags::class],
['name' => StringTrim::class],
],
'validators' => [
[
'name' => NotEmpty::class,
],
],
],
'surname' => [
'required' => true,
'filters' => [
['name' => StripTags::class],
['name' => StringTrim::class],
],
'validators' => [
[
'name' => NotEmpty::class,
],
],
],
];
}
}
<?php
namespace Employee\Factory;
use Employee\Form\Fieldset\EmployeeFieldset;
use Employee\Model\Employee;
use Employee\Model\Hydrator\EmployeeHydrator;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\ContainerException;
use Zend\Hydrator\Aggregate\AggregateHydrator;
use Zend\Hydrator\ClassMethods;
use Zend\Hydrator\ObjectProperty;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface;
class EmployeeFieldsetFactory implements FactoryInterface
{
/**
* Create an object
*
* @param ContainerInterface $container
* @param string $requestedName
* @param null|array $options
*
* @return object
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException if any other error occurs
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$fieldset = new EmployeeFieldset();
$fieldset->setHydrator(new ObjectProperty())->setObject(new Employee());
return $fieldset;
}
}
<?php
namespace Employee\Form;
use Employee\Form\Fieldset\EmployeeFieldset;
use Zend\Form\Element\Csrf;
use Zend\Form\Element\Submit;
use Zend\Form\Form;
class EmployeeForm extends Form
{
public function init()
{
parent::__construct('create_employee');
$this->add([
'type' => EmployeeFieldset::class,
'options' => [
'use_as_base_fieldset' => true,
],
]);
$this->add([
'type' => Csrf::class,
'name' => 'csrf',
]);
$this->add([
'type' => Submit::class,
'name' => 'submit',
'attributes' => [
'value' => 'save',
'class' => 'btn btn-primary btn-raised',
],
]);
}
}
<?php
namespace Employee\Factory;
use Employee\Form\EmployeeForm;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\ContainerException;
use Zend\Hydrator\ClassMethods;
use Zend\InputFilter\InputFilter;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface;
class EmployeeFormFactory implements FactoryInterface
{
/**
* Create an object
*
* @param ContainerInterface $container
* @param string $requestedName
* @param null|array $options
*
* @return object
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException if any other error occurs
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$form = new EmployeeForm();
$form->setHydrator(new ClassMethods(false))->setInputFilter(new InputFilter());
return $form;
}
}
<?php
namespace Employee\Model\Hydrator;
use Employee\Model\Bank;
use Employee\Model\Employee;
use Zend\Hydrator\Exception\BadMethodCallException;
use Zend\Hydrator\HydratorInterface;
class EmployeeHydrator implements HydratorInterface
{
/**
* Hydrate $object with the provided $data.
*
* @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:'.get_class($object).' to be a PHP Employee object)',
__METHOD__
));
}
// get a clean employee object and get the data from the db result (no prefix here!)
$employee = new Employee();
$employee->exchangeArray($data);
// this is an example when you have a foreign key relation to the employee table itself (see the prefix)
$manager = new Employee();
$manager->exchangeArray($data, 'manager.');
$employee->setManager($manager);
$bank = new Bank();
$bank->exchangeArray($data, 'bank.');
$employee->setBank($bank);
return $employee;
}
/**
* @param Employee $object
*
* @return mixed
*/
public function extract($object)
{
if (!is_callable([$object, 'getArrayCopy'])) {
throw new BadMethodCallException(
sprintf('%s expects the provided object to implement getArrayCopy()', __METHOD__)
);
}
return $object->getArrayCopy();
}
}
<?php
namespace Employee\Model;
use Zend\Db\ResultSet\Exception\InvalidArgumentException;
use Zend\Db\Sql\Expression;
use Zend\Db\Sql\Select;
use Zend\Db\TableGateway\TableGatewayInterface;
class EmployeeTable
{
/**
* @var TableGatewayInterface
*/
private $tableGateway;
/**
* EmployeeTable constructor.
*
* @param TableGatewayInterface $tableGateway
*/
public function __construct(TableGatewayInterface $tableGateway)
{
$this->tableGateway = $tableGateway;
}
/**
* @param int $id
*
* @return Employee
*/
public function getEmployeeById($id)
{
/** @var Select $select */
$select = $this->tableGateway->getSql()->select()
// example if you have a foreign key to the employee table itself
->join(['m' => 'employee'], 'employee.manager_id = m.id',
[
'manager.id' => 'id',
'manager.forename' => 'forename',
'manager.surname' => 'surname',
],
Select::JOIN_LEFT)
->join(['b' => 'bank'], 'employee.bank_id = b.id',
[
'bank.id' => 'id',
'bank.name' => 'name',
'bank.bic' => 'bic',
],
Select::JOIN_LEFT)
->where(['employee.id' => (int)$id]);
$result = $this->tableGateway->selectWith($select);
$row = $result->current(); // here you already have the employee object from your hydration
if (!$row) {
throw new InvalidArgumentException(sprintf(
'Employee with identifier "%s" not found.',
$id
));
}
return $row;
}
// other database query methods
}
<?php
namespace Employee\Factory;
use Employee\Model\Employee;
use Employee\Model\EmployeeTable;
use Employee\Model\Hydrator\EmployeeHydrator;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\ContainerException;
use Zend\Db\Adapter\AdapterInterface;
use Zend\Db\ResultSet\HydratingResultSet;
use Zend\Db\TableGateway\TableGateway;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface;
class EmployeeTableFactory implements FactoryInterface
{
/**
* Create an object
*
* @param ContainerInterface $container
* @param string $requestedName
* @param null|array $options
* @return object
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException if any other error occurs
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$dbAdapter = $container->get(AdapterInterface::class);
$resultSetPrototype = new HydratingResultSet();
$resultSetPrototype->setHydrator(new EmployeeHydrator()); // most interesting part
$resultSetPrototype->setObjectPrototype(new Employee());
$tableGateway = new TableGateway('employee', $dbAdapter, null, $resultSetPrototype);
return new EmployeeTable($tableGateway);
}
}
<?php
namespace Employee\Controller;
use Acl\Model\Role;
use Employee\Form\EmployeeForm;
use Employee\Model\Bank;
use Employee\Model\Employee;
use Employee\Model\EmployeeTable;
use Employee\Model\InsuranceCompany;
use Zend\Authentication\AuthenticationService;
use Zend\Form\FormInterface;
use Zend\Http\Request;
use Zend\Mvc\Controller\AbstractActionController;
class IndexController extends AbstractActionController
{
/**
* @var AuthenticationService
*/
private $auth;
/**
* @var EmployeeTable
*/
private $employeeTable;
/**
* @var EmployeeForm
*/
private $form;
/**
* IndexController constructor.
*
* @param AuthenticationService $auth
* @param EmployeeTable $employeeTable
* @param EmployeeForm $form
*/
public function __construct(AuthenticationService $auth, EmployeeTable $employeeTable, EmployeeForm $form)
{
$this->employeeTable = $employeeTable;
$this->auth = $auth;
$this->form = $form;
}
public function userAction()
{
// get current employee object
$id = (int) $this->auth->getIdentity()['id'];
/** @var Employee $employeeObject */
$employeeObject = $this->employeeTable->getEmployeeById($id);
$this->form->bind($employeeObject);
/** @var Request $request */
$request = $this->getRequest();
$viewData = ['id' => $id, 'form' => $this->form, 'name' => $employeeObject->forename . " " . $employeeObject->surname];
if (!$request->isPost()) {
return $viewData;
}
$this->form->setData($request->getPost());
if (!$this->form->isValid()) {
return $viewData;
}
$this->employeeTable->saveEmployee($employeeObject);
}
}
<?php
namespace Employee\Factory;
use Employee\Controller\IndexController;
use Employee\Form\EmployeeForm;
use Employee\Model\EmployeeTable;
use Interop\Container\ContainerInterface;
use Interop\Container\Exception\ContainerException;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Exception\ServiceNotCreatedException;
use Zend\ServiceManager\Exception\ServiceNotFoundException;
use Zend\ServiceManager\Factory\FactoryInterface;
class IndexControllerFactory implements FactoryInterface
{
/**
* Create an object
*
* @param ContainerInterface $container
* @param string $requestedName
* @param null|array $options
*
* @return object
* @throws ServiceNotFoundException if unable to resolve the service.
* @throws ServiceNotCreatedException if an exception is raised when
* creating a service.
* @throws ContainerException if any other error occurs
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$formManager = $container->get('FormElementManager');
return new IndexController($container->get(AuthenticationService::class), $container->get(EmployeeTable::class), $formManager->get(EmployeeForm::class));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment