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.