Work on Flextrine resumes!

After a few months break I have started work on Flextrine once again.  During the break I have been working a lot with Doctrine 2 in pure PHP, and have learnt a lot of things to do with both the internals of Doctrine and its practical application in a real world application.  Now that Doctrine 2 has its first stable release its time to start applying some of these lessons to Flextrine.

Here are a few of the things that will be in the upcoming release of Flextrine:

  • The ability to run multiple Flextrine applications within a single install – Flextrine will ship with a micro-framework to facilitate this along with a command line tool to create new projects.
  • Flextrine Manager functions, including entity generation, will become available through the command line (implemented through the Doctrine console tool) to aid in build processes.  The Flextrine Manager will probably be removed.
  • Configuration files will be YAML instead of PHP.
  • Support for XML, YAML as well as annotations in entities (this was done mainly to enable the use of the awesome ORM Designer) .
  • Integrated support for access control to entities using Zend_Acl.
  • Massive performance boosts when updating large objects graphs.
  • Lazily loaded collection associations can be configured to load on demand, throwing ItemPendingErrors to allow Flex components to react to the updates automatically.
  • Lazily loaded entities can also be configured to load on demand.
  • Improved DQL querying from Flex including support for named parameters.
  • Bi-directional associations are now auto-managed by Flextrine, so there is no longer a need to set both sides of the relationship.
  • An EntityManager::rollback() method that undoes any changes made to the repository.
  • Class table inheritance.
  • By default repositories hold weak references so that unused entities are eligible for garbage collection after Configuration::entityTimeToLive has elapsed (configurable globally or per repository).
  • The option to run Flextrine in PULL mode; this queues up changes but doesn’t apply them to the repositories until a successful return from em.flush() (experimental!)
  • PagedCollections which load query results as the user scrolls (experimental!)
  • Heaps of bug fixes!

Flextrine 0.9 is currently planned for release towards the end of February.

Flextrine Tutorial – CRUD in a simple Flex 4 address book: Creating new entities

Introduction
Setting up the server
Creating the entities
Creating the database schema
Loading the entities
Creating new entities
Deleting entities
Updating entities
Conclusion

To add new entities to the repository we use the EntityManager.persist() method.  Let’s link up the Add group and Add contact buttons and get them to create and persist new entities.

Modify the Button MXML tags for Add Group and Add Contact so that they call addNewGroup() and addNewContact() when they are clicked.  We can only add contacts within a group, so we will also make sure that you can only click addNewContact() if a ContactGroup is selected in the tree.

   1: <s:Button label="New group" click="onNewGroupClick()" />

   2: <s:Button label="New contact" click="onNewContactClick()" enabled="{tree.selectedItem is ContactGroup}" />

Firstly we’ll make the onNewGroupClick() function.  It really is as simple as creating a new ContactGroup(), assigning some properties and calling EntityManger.persist().

   1: private function onNewGroupClick():void {

   2:     var contactGroup:ContactGroup = new ContactGroup();

   3:     contactGroup.name = "New group";

   4:     em.persist(contactGroup);

   5:     

   6:     // Select the new ContactGroup in the tree

   7:     tree.selectedItem = contactGroup;

   8: }

And that’s it :)  When the user clicks Add Group a new ContactGroup entity is created, a name is assigned to it and it is persisted to Flextrine.  This means that the new contactGroup is added to entities in the ContactGroup entity repository (triggering databinding and updating the tree), and the new entity is marked for insertion into the database.  Note that Flextrine doesn’t actually do anything with the database when we call persist, it just notes that we need to insert it on the next flush – we’ll worry about flushing after we have created onNewContactClick().

onNewContactClick() is very similar:

   1: private function onNewContactClick():void {

   2:     var selectedContactGroup:ContactGroup = tree.selectedItem as ContactGroup;

   3:     

   4:     var contact:Contact = new Contact();

   5:     contact.name = "New contact";

   6:     

   7:     contact.contactGroup = selectedContactGroup;

   8:     selectedContactGroup.contacts.addItem(contact);

   9:     

  10:     em.persist(contact);

  11:     

  12:     // Select the new Contact in the tree

  13:     tree.selectedItem = contact;

  14: }

There is one important subtlety here.  Just like JPA, Flextrine does not automatically handle both ends of a bi-directional association, and its the developer’s job to manage the domain model.  Let’s talk about this in more detail.  Our association between ContactGroup and Contact is OneToMany bi-directional.  This means that a ContactGroup has many Contacts, and a Contact has one ContactGroup.  Therefore when we create a new Contact we need to do the following:

  • Set newContact.contactGroup to selectedContactGroup
  • Add newContact to selectedContactGroup.contacts

In this way we maintain both sides of the bi-directional association.  If we were to change the contactGroup of an existing contact we would need to add another step:

  • Remove contact from its current contactGroup.contacts
  • Set contact.contactGroup to selectedContactGroup
  • Set newContact.contactGroup to selectedContactGroup

Let’s encapsulate this functionality within Contact and ContactGroup by creating ContactGroup.addContact, ContactGroup.removeContact and Contact.setContactGroup.  These are called association helpers and its very likely that future versions of Flextrine will auto-generate these for you.  However, for now we’ll code them by hand.

ContactGroup.as

   1: package vo {

   2:     import mx.collections.ArrayCollection;

   3:     

   4:     [RemoteClass(alias="vo/ContactGroup")]

   5:     [Entity]

   6:     public class ContactGroup extends ContactGroupEntityBase {

   7:         

   8:         public function get children():ArrayCollection {

   9:             return contacts;

  10:         }

  11:         

  12:         public function addContact(contact:Contact):void {

  13:             contact.setContactGroup(this);

  14:         }

  15:         

  16:         public function removeContact(contact:Contact):void {

  17:             contacts.removeItemAt(contacts.getItemIndex(contact));

  18:         }

  19:         

  20:     }

  21:  

  22: }

Contact.as

   1: package vo {

   2:     import mx.collections.ArrayCollection;

   3:     

   4:     [RemoteClass(alias="vo/Contact")]

   5:     [Entity]

   6:     public class Contact extends ContactEntityBase {

   7:         

   8:         public function setContactGroup(contactGroup:ContactGroup):void {

   9:             if (this.contactGroup) this.contactGroup.removeContact(this);

  10:             this.contactGroup = contactGroup;

  11:             if (contactGroup) contactGroup.contacts.addItem(this);

  12:         }

  13:         

  14:     }

  15:  

  16: }

And now we can change onNewContactClick() to simply read:

   1: private function onNewContactClick():void {

   2:     var selectedContactGroup:ContactGroup = tree.selectedItem as ContactGroup;

   3:     

   4:     var contact:Contact = new Contact();

   5:     contact.name = "New contact";

   6:     selectedContactGroup.addContact(contact);

   7:     em.persist(contact);

   8:     

   9:     // Select the new Contact in the tree

  10:     tree.selectedItem = contact;

  11: }

These complications are only relevant for bi-directional association.  If, for example, ContactGroup has many Contacts, but ContactGroups don’t know anything about their Contacts this would be a ManyToOne uni-directional association and all we would need to do would be to set contactGroup on the Contact to maintain the domain model.

Flushing

At this point nothing has actually been written to the database – all that has happened is that Flextrine has marked our persisted objects for insertion.  In order to tell Flextrine to execute its queue of operations against the database we use EntityManager.flush().  This will create new entities, update changed entities and delete removed entities in the database.

One thing to consider is if for some reason the flush fails (for example if we were to try and delete a ContactGroup without deleting its child Contacts) the client will be out of sync with what is in the database so we are going to add a responder to flush() that will simply reload all the data fresh from the database in case of an error.

Note that once the flush() has completely successfully our persisted ContactGroup and Contact entities will have their id fields automatically updated with the auto increment id from the database.

Update the Save button to call onSaveClick():

   1: <s:Button label="Save" click="onSaveClick()" />

And implement the method and the responders:

   1: private function onSaveClick():void {

   2:     enabled = false;

   3:     

   4:     em.flush().addResponder(new AsyncResponder(onSaveResult, onSaveFault));

   5: }

   6:  

   7: private function onSaveResult(result:Object, token:Object):void {

   8:     enabled = true;

   9: }

  10:  

  11: private function onSaveFault(result:Object, token:Object):void {

  12:     enabled = true;

  13:     

  14:     em.clear();

  15:     em.getRepository(ContactGroup).loadAll();

  16: }

Notice that if there is an error we call EntityManager.clear() before reloading the data – this empties out all the current entities in the repositories.

In the next section of the tutorial we’ll see how we can delete entities.

Flextrine Tutorial – CRUD in a simple Flex 4 address book: Creating the entities

Introduction
Setting up the server
Creating the entities
Creating the database schema
Loading the entities
Creating new entities
Deleting entities
Updating entities
Conclusion

Now that Flextrine has been configured we need to create our entities.  Entities are the objects that we want to enable for reading and writing to the database, and they are defined in PHP in the entities directory of the Flextrine server side component.

The requirements for our simple address book are that we need to have groups containing contacts.  Unfortunately Group is a reserved word in SQL so we’ll call it ContactGroup.  ContactGroups have a name, and many Contacts, and Contacts have a name, telephone number, birthday and a contactGroup.  We are going to place all our entities in the vo package.  This gives us the class diagram below:

Flextrine entities

Create a folder called vo in the entities directory of the Flextrine server side component and create ContactGroup.php and Contact.php with the contents given below.  More information on writing entities can be found in the Doctrine documentation for Basic Mapping and Association Mapping.  The main thing to remember when creating entities is that:

  • All mapped attributes must be public

ContactGroup.php

<?php


namespace vo;


use DoctrineCommonCollectionsArrayCollection;


/**

 * @Entity

 */


class ContactGroup {


    /** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */

    public $id;


    /** @Column(length=100, type="string") */

    public $name;


    /**

     * @OneToMany(targetEntity="Contact", mappedBy="contactGroup")

     */


    public $contacts;


    public function __construct() {

        $this->contacts = new ArrayCollection();

    }


}


?>

Contact.php

<?php

namespace vo;


use DoctrineCommonCollectionsArrayCollection;


/**

 * @Entity

 */


class Contact {


    /** @Id @Column(type="integer") @GeneratedValue(strategy="IDENTITY") */

    public $id;


    /** @Column(length=80, type="string") */

    public $name;


    /** @Column(length=50, type="string", nullable="true") */

    public $telephoneNumber;


    /** @Column(type="date", nullable="true") */

    public $birthday;


    /**

     * @ManyToOne(targetEntity="ContactGroup", inversedBy="contacts")

     */

    public $contactGroup;


    public function __construct() {


    }


}


?>

Now that we have created our entity definitions we need to create the matching database schema.

Flextrine 0.5 released!

I have released the first public version of Flextrine.  Its actually pretty feature rich, and I will be focusing mainly on bug fixes and stability as reports start to come back from the community leading up to the main release on 1st September.

Major features that have made it into 0.5:

  • Entities and the entity manager
  • Single and collection associations
  • The Flextrine Manager
  • AS3 code generation
  • Lazy loading, requireOne and requireMany
  • Full Flex databinding
  • DQL queries

Flextrine can be downloaded from Google Code at http://code.google.com/p/flextrine2/  Please try it out and log any issue you might find in Google Code.