Field-Level Abstraction of Entities

The abstraction of a database through an object relational mapper can become hindered by duplicated code in entities and models. In PHP abstraction of the fields in an entity has not been possible until the introduction of Traits in PHP 5.4.

Where a field is a data column or a one to many identifier on an entity, and where a relation is a many to one identifier on an entity, each of these may be abstracted to its own trait to avoid duplication of form annotations, getters and setters, property declarations, and input filters.

Note Field Trait

<?php

namespace DbField;
use ZendFormAnnotation as Form;
use ZendInputFilterInputFilter;

trait Note
{
    /**
     * @FormType("ZendFormElement")
     * @FormAttributes({"type": "textarea"})
     * @FormAttributes({"id": "note"})
     * @FormOptions({"label": "Note"})
     */
    protected $note;

    public function getNote() {
        return $this->note;
    }

    public function setNote($value) {
        $this->note = $value;
        return $this;
    }

    // Input filters are named 'inputFilterInput[TraitName]'
    // PHP does not allow aliased abstract functions in traits used by traits
    private function inputFilterInputNote($inputFilter = null) {
        if (!$inputFilter) $inputFilter = new InputFilter();

        return $inputFilter->getFactory()->createInput(array(
            'name' => 'note',
            'required' => false,
            'validators' => array(),
        ));
    }
}

Zipcodes Relation Trait

<?php

namespace DbRelation;
use ZendFormAnnotation as Form;
use DoctrineCommonCollectionsArrayCollection;

trait Zipcodes
{
    protected $zipcodes;

    public function getZipcodes()
    {
        if (!$this->zipcodes)
            $this->zipcodes = new ArrayCollection();

        return $this->zipcodes;
    }
}

Entity Leveraging Field Traits

<?php

namespace DbEntity;
use ApplicationEntityAbstractEntity;
use ZendFormAnnotation as Form;

/**
* @FormHydrator("ZendStdlibHydratorObjectProperty")
* @FormName("feedback")
*/
class Feedback extends AbstractEntity
{
    use DbFieldId
        , DbFieldFrom
        , DbFieldTo
        , DbFieldScore
        , DbFieldNote
        , DbFieldReply
        , DbFieldCreatedAt
        ;

    public function getArrayCopy()
    {
        return array(
            'id' => $this->getId(),
            'note' => $this->getNote(),
            'score' => $this->getScore(),
            'reply' => $this->getReply(),
            'createdAt' => $this->getCreatedAt()->format('r'),
        );
    }

    public function exchangeArray($data)
    {
        $this->setNote(isset($data['note']) ? $data['note']: null);
        $this->setScore(isset($data['score']) ? $data['score']: null);
        $this->setReply(isset($data['reply']) ? $data['reply']: null);
    }
}

Model Leveraging Field Traits

?php
namespace DbModel;
use DbModelAbstractModel;
use DbEntityFeedback as FeedbackEntity;
use ZendInputFilterInputFilter;

class Feedback extends AbstractModel
{
    use DbFieldScore
        , DbFieldNote
        , DbFieldReply
        ;

    public function getInputFilter($entity = null)
    {
        $inputFilter = new InputFilter();

        $inputFilter->add($this->inputFilterInputScore());
        $inputFilter->add($this->inputFilterInputNote());
        $inputFilter->add($this->inputFilterInputReply());

        return $inputFilter;
    }
}

Field-Level Abstraction keeps all entities at a manageable size.  Should you ever need to override a Trait you may remove the trait from the entity and create your own traditional property and getter/setter methods for it.

Zend Framework 2 gives you the ability to run multiple applications on the same code base by altering which configuration is loaded in index.php or which index file to load forked at the .htaccess level.  Using this approach you can have fully independent routes and controllers for multiple applications which all use the same database structure if you abstract your database to a Db module which contains no routing or view information.

Field-Level Abstraction is how engineered PHP applicaitons will be built today and in the future.  Traits are available in PHP 5.4 yet it is not widely adopted, but PHP 5.5 is in beta and I expect that version to become as popularly supported as 5.3.

1 thought on “Field-Level Abstraction of Entities”

  1. Great use for traits. I have a comment about your use of list syntax for use statements. I prefer separate use statements for each used class. This seems to be the preferred syntax in ZF2.

    See Zend\Code\Generator\FileGenerator for an example of a generator that makes a separate statement for every use.

    I used slightly different list syntax from what you are using here when I first started working with namespaces, because I was copying from some of the core contributors in ZF2. Your list style is better than the old one I was copying, because the leading commas help readability. That being said, I still prefer not using lists.

    There was a point in ZF2 beta where they made a decision to standardize, and went through and refactored all the list-based use statements as separate use statements for each class. I switched at that point. If you look through the framework, I think you will find they now consistently only do the one-statement-per-class syntax.

    IDEs also will auto-add a use statement for you if you let them, and these will always be a single statement per class, so it would create mixed syntax, or require manual editing to resolve in that case. Another advantage I found of converting to separate lines was because I like option up and down arrow to reorder lines in my IDE, and the list syntax makes that less convenient.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top