Joomla and Doctrine, experimenting with automated binding

In my day to day job at the moment I’m working more on Java projects then PHP applications. In our Java project we are using the a very sophisticated framework called Spring Framework. So when I started to work on an new, private PHP project with Joomla I was feeling a bit blunt by all the basic things I have to do to process forms into their representing entities.

Let me state first that I’m not the biggest fan of the techniques used in Joomla to do things. I know that is a bit pigheaded of me, but I just can’t get comfortable with it. Beside that, I love to experiment with things and most of the times reïnventing the wheel to get a better understanding of concepts and workings.

So in this blogpost I’ll show you how I created a basic implementation to automate the binding of the forms to their corresponding entities. Its just an experiment of me which I’d like to share to let others know how I did this. It isn’t the holy grail how to do things.

Let’s start with some perquisites. For this application I have used the latest Joomla 2.5 release and Doctrine 2.2.2. In an earlier blog post I have shown how to integrate Doctrine with Joomla so I won’t elaborate on that in this blog post.

What do we want it to do?

Before we are going to dive into the implementation, let us discuss what we want it to do. What we want is that we are able to bind values representing an entity to an instance of that entity.  So lets say we have an entity like this:

/**
* @Entity
*/
class Post {
private $title;
private $body;
}

and a POST request like this:

array (
post => (
       title => 'Test',
       body => 'Test message'
      )
)

What we then would like to do is something as easy as:

$post = new Post();
$post->populate( $_POST ); // not safe, just an example to show how it should work

This of course is a very basic example. The implementation which I’ll show you will take into account that an entity has zero or more relations to other entities. How we do this, well with a lot of help of the metadata of the domain model which is collected by Doctrine.

Setting up the domain model

Let us start by defining a small domain model. The origin of this model is the application in which I created this experiment. I chose not to create a specific model for this blog post.

Our model consists of two entities which have a bi-directional relation with each other. The first entity is called ‘Team’ and looks like this:


<!--?php namespace Entities\Teams; use Doctrine\Common\Collections\ArrayCollection; /**  * Repesenting a team  *  * @Entity(repositoryClass="\Repositories\Teams\TeamRepository")  * @author pderaaij  */ class Team extends \Entities\BaseModel {  /** @Id @Column(type="integer") @GeneratedValue */  private $id;  /** @Column(type="string") */  private $name;  /**  * @OneToMany(targetEntity="Competition", mappedBy="team", cascade={"persist", "merge"})  */  private $competition;  public function __construct() { $this--->competition = new ArrayCollection();
 }

 public function getId() {
 return $this->id;
 }

public function setId($id) {
 $this->id = $id;
 }

public function getName() {
 return $this->name;
 }

public function setName($name) {
 $this->name = $name;
 }

public function getCompetition() {

return $this->competition;
 }

public function setCompetition($competition) {
 $this->competition = $competition;
 }

}
?>

The second entity is called ‘Competition’ and is defined as follows:


<!--?php namespace Entities\Teams; /**  * Represents a competition  *  * @Entity  * @author pderaaij  */ class Competition extends \Entities\BaseModel { /** @Id @Column(type="integer") @GeneratedValue */  private $id;  /** @Column(type="string") */  private $title;  /** @ManyToOne (targetEntity="Entities\Teams\Team", inversedBy="competition")*/  private $team;  public function getTeam() {  return $this--->team;
 }

public function setTeam($team) {
 $this->team = $team;
 }

 public function getId() {
 return $this->id;
 }

public function setId($id) {
 $this->id = $id;
 }

public function getTitle() {
 return $this->title;
 }

public function setTitle($title) {
 $this->title = $title;
 }

}
?>

So that is our domain model which we are going to use for this blog post.

What you see is that both entities extend from a BaseModel. It is this superclass which holds the binding implementation. So each entity holds the responsibility for binding  a value set to its own instance. One could argue this brakes the SRP principle and I tend to agree with that. If you choose to optimize the concept, this would be a great place to start.

Starting to implement the binding code

As shown in the example how we would like to use this binding functionality, we are going to need a populate method in the BaseModel super class, so we are going to implement that one.

<!--?php <br ?-->namespace Entities;

/**
 * @Author Paul de Raaij
 * @Since 0.1
 */
class BaseModel
{
    /**
     * Populate the model with the given data. This method expects an array with keys matching the property name.
     *
     * @param array $data
     */
    public function populate(\JInput $input, $passedEntities = array(), $propertyName = null)
    {
        $em = $this->getEntityManager();
        $metadataFactory = $em->getMetadataFactory();
        $entityMetadataInfo = $metadataFactory->getMetadataFor(get_class($this));

        // We use the class name as an identifier key in the value set. If no
        // identifier is given, use the class name of the entity as default
        if( $propertyName == null) {
            $parentClass = strtolower(substr(get_class($this), strrpos(get_class($this), '\\') + 1));
        } else {
            $parentClass = $propertyName;
        }

        // Hold a reference of the entities we have processed already
        $passedEntities[] = get_class($this);
        foreach( $entityMetadataInfo->getFieldNames() as $property ) {
            if ($this->hasSetterForProperty($property) && !$entityMetadataInfo->hasAssociation($property)) {
                $inputData = $input->getData($parentClass);

                // Check if the property is present in the given value set
                if(array_key_exists($property, $inputData) === false) {
                    continue;
                }

                $value = $inputData[$property];

                if( $entityMetadataInfo->getTypeOfField($property) == 'datetime') {
                    $this->setPropertyValue($property, new \DateTime($value));
                } else {
                    $this->setPropertyValue($property, $value);
                }
            }
        }

        // Walk throug all associations to set the correct value if found in the value set
        foreach( $entityMetadataInfo->getAssociationMappings() as $association ) {
            $getterMethodName = 'get' . ucfirst($association['fieldName']);
            $entity = $this->$getterMethodName();
            $inputData = $input->getData($parentClass);

            // See if we have data for the association in the given value set
            if(array_key_exists($association['fieldName'], $inputData) === false) {
                continue;
            }

            $value = $inputData[$association['fieldName']];

            if( !is_null($entity) && !in_array(get_class($entity), $passedEntities)) {

                // If the association is a collection process it by each element
                if ( $entityMetadataInfo->isCollectionValuedAssociation($association['fieldName']) ) {;
                    $repository =  $this->getEntityManager()->getRepository($entity->getTypeClass()->getName());
                    $associatedEntity = $repository->find( $value );
                    if( $entity instanceof \Doctrine\Common\Collections\ArrayCollection ) {
                        $entity->add($associatedEntity);
                    } else {
                        if( isset( $association['mappedBy']) ) {
                            $associatedEntity->setPropertyValue($association['mappedBy'], $this);
                        }

                        $collection = new \Doctrine\Common\Collections\ArrayCollection();
                        $collection->add($associatedEntity);
                        $this->setPropertyValue($association['fieldName'], $collection);
                    }

                } else {
                    // We only have one entity, so call the populate for the associated
                    // entity and do the same thing again
                    $entity->populate($input, $passedEntities, $association['fieldName']);
                }
            }
        }
    }

     /**
     *
     * Determine the setter name of the function
     *
     * @param $property
     * @return string
     */
    private function getPropertySetterName($property)
    {
        return 'set' . ucfirst($property);
    }

    private function hasSetterForProperty($property)
    {
        $setterName = $this->getPropertySetterName($property);
        return is_callable(array($this, $setterName));
    }

    private function setPropertyValue($property, $value)
    {
        $value = $this->findValueRelationForProperty($property, $value);
        call_user_func(array($this, $this->getPropertySetterName($property)), $value);
    }

    /**
     *
     * It is likely that a property is a reference to an other entity. We can't just store the id to the property, but need a instance to
     * be set on the property.
     *
     * This function checks the Doctrine metadata information for a relation and if so it uses that information to bind the right
     * instance on the property.
     *
     * @param $property
     * @param $value
     * @return mixed
     * @throws \InvalidArgumentException
     */
    private function findValueRelationForProperty($property, $value)
    {
        $em = $this->getEntityManager();
        $metadataFactory = $em->getMetadataFactory();
        $entityMetadataInfo = $metadataFactory->getMetadataFor(get_class($this));

        try {
            $associationMapping = $entityMetadataInfo->getAssociationMapping($property);
        } catch (\Exception $e) {
            $associationMapping = null;
        }

        if ($associationMapping == null) {
            return $value;
        }

        $repository = $em->getRepository($associationMapping['targetEntity']);

        if ($repository == null) {
            throw new \InvalidArgumentException('Not able to bind reference');
        }

        $items = $repository->findById($value);
        return $items[0];
    }
}

That is it. This does all the magic and I agree it takes you some line of code, but it’s pretty decent. The majority of work is done by Doctrine and its metadata information and helpers. We just use that information to set the correct values on the right attributes. Is it perfect? I’m sure it isn’t, but it gives you a idea how you could implement this yourself.

How does my form needs to look like?

Well, not that spectacular. Here’s just a snippet of one of the forms in my test application.

<li>
                    <label>Naam</label>
                    <input type='text' name="team[name]" class="inputbox required" value="<?php echo $this->team->getName();?>"/>
                </li>
                <li>
                    <label>Competitie</label>
                    <select id="competitionSelector" name="team[competition]" multiple="multiple" style="width: 450px;">
                        <?php foreach( $this->competitions as $competition):?>
                            <option value="<?php echo $competition->getId();?>" <?php echo ($this->team->getCompetition()[0]->getId() == $competition->getId()) ? 'selected="selected"' : '';?>><?php echo $competition->getTitle() . ' | ' . $competition->getType();?></option>
                        <?php endforeach; ?>
                    </select>
                </li>

And now?

Well… nothing special. I hope it has given you some ideas how you could implement this yourself or perhaps you have a great idea or addition for me. Just let me know!

Happy Codin’