Flextrine 0.9 released

I am very proud to announce the release of Flextrine 0.9.  This is a major release with changes across the board; much of the codebase has been improved, optimized and cleaned up, and a host of new features have been added.  Flextrine has also moved from Google Code to Github, and the documentation has been completely updated for the new version and is available on the Github wiki.  Some of the new features are listed below:

  • 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 has been removed.
  • Configuration files are 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) .
  • 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 be configured to load on demand.
  • Improved DQL querying from Flex including support for named parameters and hydration modes including scalar queries.
  • 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 and single 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!)
  • Integrated support for access control to entities using Zend_Acl (experimental!)
  • Heaps of bug fixes!

Flextrine v0.9 is available from www.flextrine.com

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 0.7.3 released

The Flextrine releases are flying out hard and fast!  This release gives another batch of bug fixes, enhanced functionality in various areas, better error reporting and lots more API documentation.  Flash on the Beach is just around the corner, and I’m hoping that my presentation will bring a new batch of users into the Flextrine camp.

Pick it up at www.flextrine.com

SPOILER ALERT (only if you are coming to Flash on the Beach) – here is a sneak preview of my slides for the presentation http://prezi.com/6vrft-2h4sjy/flextrine/

Flextrine 0.7 released – now with ZendAMF!

After much soul searching I eventually decided to put in the time to convert Flextrine from using AMFPHP to ZendAMF.  There were a few reasons I decided to make this move, but eventually it boiled down to the fact that quite a few people had asked me to do so.  In retrospect it was certainly the right thing to do – ZendAMF has the green light from Adobe, its licence is LGPL instead of GPL and the cleaner code base means that the changes Flextrine requires can be implemented as overridden classes instead of having to modify the ZendAMF core.  This particularly is great news because it means that you can use Flextrine directly with the standard Zend framework installation, whereas previously you could only use the version of AMFPHP bundled with Flextrine.

Functionally speaking things remain exactly the same, and we continue to approach our stable release alongside Doctrine 2.

The new version of Flextrine can be downloaded from www.flextrine.com.  Note that if you are upgrading from a previous version of Flextrine you will need to regenerate your AS3 entities.

Flextrine 0.6.5 released

Another released of Flextrine!  Since 0.6.1 there have been a number of changes to the Flextrine core.  The entity merging algorithm has been altered to take better advantage of Doctrine 2 merging giving us a hefty performance boost (thanks for Benjamin Eberlei at Doctrine for pointing this out), the requirement to use $_explicitType in Doctrine entities has now been removed, bootstrapping and configuration code has been extended and neatened up and there have been numerous bug fixes at all levels of the application.

My continued thanks to the community for their help in testing and using Flextrine!

Flextrine Tutorial – CRUD in a simple Flex 4 address book: Conclusion

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

I hope that this tutorial has given you some insight into the power of Flextrine.  In fact there are many areas of Flextrine that we didn’t touch on here including one to one and many to many associations, lazy loading, DQL, fetch joins, custom PHP functions and more – check the documentation for more details.

Both Flextrine and Doctrine 2 are currently in beta; the planned release date for both products is 1st September 2010 by which time I would hope to be able to confidently pronounce Flextrine as stable.  That’s where you come in!  If you find any issues in Flextrine please log them at http://code.google.com/p/flextrine2/issues/list and help me to make Flextrine the best Flex ORM library there is 🙂

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

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

Once an entity is in an EntityRepository (either because it was loaded from the database or it was persisted) it is known as a MANAGED entity.  This means that Flextrine will constantly watch the entity to see if anything changes, and if so will mark the entity for updating on the next flush().

Therefore updating entities is extremely simple – as long as the entity is managed there is literally nothing to do 🙂

Lets create an editor for the ContactGroups (in the same directory as Main.mxml):

ContactGroupEditor.mxml

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"

   3:          xmlns:s="library://ns.adobe.com/flex/spark"

   4:          xmlns:mx="library://ns.adobe.com/flex/mx">

   5:     

   6:     <fx:Script>

   7:         <![CDATA[

   8:         import vo.ContactGroup;

   9:         

  10:         [Bindable]

  11:         public var contactGroup:ContactGroup;

  12:         

  13:         ]]>

  14:     </fx:Script>

  15:     

  16:     <mx:Form labelWidth="150" width="100%" height="100%">

  17:         

  18:         <mx:FormHeading label="Edit group" />

  19:         

  20:         <mx:FormItem label="Name">

  21:             <s:TextInput text="@{contactGroup.name}" />

  22:         </mx:FormItem>

  23:         

  24:     </mx:Form>

  25:     

  26: </s:Group>

The component takes a contactGroup and provides a TextInput that edits its name attribute using Flex 4 two-way databinding.  In fact you need to be careful with two-way databinding on some components (e.g. DateChooser) as it can cause unnecessary updates, but it works fine for Spark’s TextInput.

Now we need to add the ContactGroupEditor into Main.mxml:

   1: ...

   2: <s:VGroup width="100%" height="100%">

   3:             <!-- The editors -->

   4:             <local:ContactGroupEditor contactGroup="{tree.selectedItem as ContactGroup}" enabled="{tree.selectedItem is ContactGroup}" />

   5: ...

And that’s it for the group editor.  When we select a ContactGroup in the tree, the ContactGroupEditor becomes enabled, and the selected item is passed to the editor’s contactGroup attribute.  Flex updates the contactGroup’s name attribute when we change it in the editor and Flextrine automatically picks up on the change and marks the entity for updating.  On the next flush() the changes will be written to the database.

Now for the ContactEditor.

ContactEditor.mxml

   1: <?xml version="1.0" encoding="utf-8"?>

   2: <s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"

   3:          xmlns:s="library://ns.adobe.com/flex/spark"

   4:          xmlns:mx="library://ns.adobe.com/flex/mx">

   5:     

   6:     <fx:Script>

   7:         <![CDATA[

   8:         import org.davekeen.flextrine.orm.EntityManager;

   9:         import vo.ContactGroup;

  10:         import vo.Contact;

  11:         

  12:         [Bindable]

  13:         public var contact:Contact;

  14:         

  15:         ]]>

  16:     </fx:Script>

  17:     

  18:     <mx:Form labelWidth="150" width="100%" height="100%">

  19:         

  20:         <mx:FormHeading label="Edit contact" />

  21:         

  22:         <mx:FormItem label="Name">

  23:             <s:TextInput text="@{contact.name}" />

  24:         </mx:FormItem>

  25:         

  26:         <mx:FormItem label="Telephone number">

  27:             <s:TextInput text="@{contact.telephoneNumber}" />

  28:         </mx:FormItem>

  29:         

  30:         <mx:FormItem label="Birthday">

  31:             <mx:DateChooser selectedDate="{contact.birthday}" change="contact.birthday = event.currentTarget.selectedDate" />

  32:         </mx:FormItem>

  33:         

  34:         <mx:FormItem label="Group">

  35:             <s:DropDownList dataProvider="{EntityManager.getInstance().getRepository(ContactGroup).entities}"

  36:                             selectedItem="{contact.contactGroup}"

  37:                             change="contact.setContactGroup(event.currentTarget.selectedItem);"

  38:                             labelField="name" />

  39:         </mx:FormItem>

  40:         

  41:     </mx:Form>

  42:     

  43: </s:Group>

The contact editor is marginally more complicated, but still fairly simple.  A few points:

  • Note is that as mentioned above we don’t use two-way databinding on any component apart from TextInput.
  • Although it is perfectly legal to retrieve the singleton EntityManager in the ContactEditor as we have done in the DropDownList dataProvider, in a real application we probably wouldn’t do this as it breaks encapsulation.  I’ve done it here for the sake of simplicity 🙂

Finally we need to add the editor to Main.mxml:

   1: ...

   2:  

   3: <s:VGroup width="100%" height="100%">

   4:     <!-- The editors -->

   5:     <local:ContactGroupEditor contactGroup="{tree.selectedItem as ContactGroup}" enabled="{tree.selectedItem is ContactGroup}" />

   6:     <mx:HRule width="100%" />

   7:     <local:ContactEditor contact="{tree.selectedItem as Contact}" enabled="{tree.selectedItem is Contact}" />

   8: ...

An that’s it!  A simple but fully functioning, database aware Flex application.

And now, the thrilling conclusion.

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

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

Deleting entities is very simple.  Update the delete button MXML tag to call onDeleteClick() when clicked, and to only be enabled if there is a selection in the tree.

   1: <s:Button label="Delete" click="onDeleteClick()" enabled="{tree.selectedItem != null}" />

And implement the onDeleteClick() method:

   1: private function onDeleteClick():void {

   2:     em.remove(tree.selectedItem);

   3:     

   4:     if (tree.selectedItem is Contact)

   5:         tree.selectedItem.contactGroup.removeContact(tree.selectedItem);

   6:     

   7: }

Notice that we maintain the bi-directional association if the deleted item is a contact by removing it from its associated contactGroup.

And that’s it :)  The entity will be removed from its entity repository (updating the tree via databinding) and on the next flush() it will be removed from the database.

The final step in our little address book is to allow the user to update existing entities.

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.