Later Update: If you are looking for an article rather on Doctrine 2 than Doctrine 1, then check this one.
This year I finally had the time to play with Doctrine (version 1.x) and Flex. Actually, it was more than playing; I’m using it for a real project that hopefully will enter production pretty soon. To summarize the experience in just a few words: it’s mind-blowing.
OK, I admit I may be exaggerating a little bit. Still, it is something that can change the way you build projects. Doctrine is an ORM (Object Relational Mapper) framework for PHP and it can really speed up the server side development when you have a lot of tables in your database.
In this article, I explore how to work with Doctrine on the server side, Flex on the client side, and remoting to communicate between Flex and PHP (using the Zend Framework for remoting on the PHP side). I also want to share with you some tools and workflows that can save you some time. While most things are fairly straightforward there are a number of tips and tricks that you may find useful if you decide to go down this road. (I will show you how to use plain vanilla value objects and how to handle dates just to give you two examples. Why reinvent the wheel?)
The application I’m going to build in this article is simple but the workflow is the same one I used with a much more complex application. Having said that let’s start by understanding the big picture.
- The Big Picture
- Database Model
- Installing Doctrine
- Configure Doctrine and generate the model files
- IDEs: Eclipse PDT and Flash Builder 4
- Creating the PHP services and PHP value objects
- Installing the Zend Framework
- Setting up the gateway file
- Creating the Flex front-end
- Getting the source code
The Big Picture
Let’s see how these different pieces come together and play nice with each other like one big happy family. Have a look at Figure 1. Yeah, this is what you’ll get if you follow this article.
Breaking down Figure 1:
- On the right is a MySQL database to persist the data. You’ll see the data model in the next chapter.
- On the PHP server I use Doctrine to manage the tables in the database. I use Doctrine features to generate the model classes out of the database structure.
- On the PHP server I have a bunch of PHP services, which will be consumed from Flex using remoting. Typically for each table from the database there is a PHP class that provides, at a minimum, basic CRUD operations (Create/Read/Update/Delete). These PHP services use the Doctrine model classes to read and persist the data. They are completely unaware of the database structure.
- Finally, we have plain vanilla PHP value objects. These may follow the structure of the database. The Doctrine models return instances of these value objects. The PHP services, when called from the Flex side, receive instances of these classes as arguments.
- The last piece on the PHP server is the Zend Framework. I use this framework to enable remoting for PHP (you could use AMFPHP, WebORB for PHP, or Sabre AMF as well). Basically it provides a gateway that represents the entry point to the PHP services for the Flex application.
- On the Flex side there are RemoteObjects that map to the PHP services.
- Then I have ActionScript value objects. For each PHP value object there is a corresponding ActionScript object. When using remoting the data is automatically changed to the right type based on the mappings you define.
- I can bind these ActionScript value objects to the different user interface elements in my application.
It is time to create the database. I’m going to use MySQL for the database. Every time I start a project that someday will be in production and uses a database I use data modeling software. Eight years ago the only viable solutions were Erwin or Power Designer (at least to my knowledge). I found that MySQL Workbench does a decent job and I use it fairly regularly nowadays (you can download MySQL Workbench from here).
Why use database modeling software? It helps you design the database and all the dependencies, share the design with others, and quickly remember the database structure when you go back to the project after couple of months off. And it helps you a lot when you start to change the project as you are in the middle of it. Remember that I’m talking about projects that go into production, and I think there are very few of those that don’t undergo modifications, including changes to the database structure.
You can use MySQL Workbench to create a data model from scratch or reverse engineer an existing database. You can push all the changes you make to the model (new tables/fields/indexes) directly to the database. And, of course, you can print these models.
To start a new model, choose File > New and then click Add Diagram if you want to start from scratch. Then you can use the toolbox on the left to add tables and draw the relationships between them. When you double-click a table you can edit the name or add/edit fields and indexes. On the right side there is a Catalog viewer. By default the model will try to create the tables into a database named mydb. If you want to change the name of the database you can double-click on the current name from the Catalog view.
Once you’ve finished the design of the data model you can add a connection to your database server. The server can be on the localhost or a remote host, in the latter case you need to be careful if you have firewalls. Next use the Database > Synchronize Model wizard to push the new database model to the database server. You can use the same wizard whether you make changes to the data model and need them reflected to the database or you change the database and you want to push them to the data model.
You can download the model I created (see Figure 2) from here.
My application has four tables: students, countries, marks, and courses. A student can be registered to many courses and the mark (or grade) for each student for each course is stored in the marks table (a many-to-many table). Each student originates from a country, and there is a one-to-many relationship between the countries table and students table.
It is always a good practice to ensure that the data are consistent by using all the means a relational database offers (foreign keys, unique keys, and so on). I have unique keys for the name field in the countries and courses tables, on the first_name and last_name fields in the students table.
Now that I have the database in place and I can easy change it using the model, it is time to create the PHP classes that will handle these tables. Which means it is time for Doctrine to take the stage.
Doctrine is one of the few PHP ORMs (Object Relational Mapper) available. The current version (at the time of writing this article) is 1.2.2. You can download it from here. You can also get the library using SVN (here is the link for the repository; you need to look under the tags node).
Why should you consider using Doctrine (or any other ORM for that matter)? Usually, any change you make at the database level will require a change to the PHP code that handles that database. Doctrine can isolate you from these changes. How? It has a feature that lets you generate PHP classes that handle each table from the database, and the input for this feature can be the database itself.
Suppose you add a new table and two new fields in existing tables. You do this in MySQL Workbench, then you use the MySQL Workbench synchronize feature to push all these changes to the database. Next you use Doctrine to generate again the PHP classes that handle this database. In just a couple of minutes you have all the code you need to perform CRUD operations on your tables.
Doctrine also takes care of escaping the values, thus protecting you from SQL injection attacks. Another cool feature of Doctrine is DQL (Doctrine Query Language). This feature makes it easy to write complete queries.
You may be thinking that using the layers on top of PHP (Doctrine and the Zend Framework) can degrade the performance of your application, including how fast it responds to a request, how many clients can connect, and so on. While this is true theoretically speaking, in practice I’d say that the vast majority of projects don’t have performance issues. They have problems keeping up with design changes and new features.
Once you have the library files, create a new folder called doctrine_students under your web root folder. Inside of this folder create these folders: lib, lib/vendor, and lib/vendor/doctrine/. Next copy the Doctrine files to the doctrine_students/ lib/vendor/doctrine/ folder. If you downloaded the archive file, then you have to copy the folders and files from Doctrine-1.2.2/Doctrine-1.2.2/lib/ (there should be a Doctrine.php file and a Doctrine folder).
You can read more about installing Doctrine here.
Configure Doctrine and generate the model files
The configuration of Doctrine is pretty simple. Basically you have to create a bootstrap file and include the Doctrine.php file, and then set the credentials for the database.
I created a file called bootstrap.php inside of the doctrine_students folder. This bootstrap file will be required in any PHP file that uses Doctrine. Inside of this file I added the following code:
Next, create a new PHP file called generate_model.php inside of the doctrine_students folder and add this code as a simple test:
If you navigate to the doctrine_students folder in a terminal/console window and run generate_model.php you should see the absolute path to the doctrine folder:
Create a new folder called models inside of doctrine_students. This is where Doctrine will generate the model files. Then go back to bootstrap.php file and add a new line at the end to create a database connection:
Basically you set the connection type to mysql, then you set the user and password (in my case “mihai” for both) then you set the database IP (don’t use localhost even if you have MySQL installed on your local machine because it will not work), and finally you specify the name of the database to be used.
Back in the generate_model.php file, comment out the echo line and add these lines:
In your console, run php generate_model.php and you should find in the doctrine_students/models/ folder a bunch of files and another folder named generated. Basically you have a file with the same name for each table from your database. You will see in the next chapter how to use these model files to persist and retrieve data.
Having generated the model files, I want to load them by adding these lines to the bootstrap file (at the end of the file):
Doctrine offers another way for creating these model files: YAML schema files. You can write a schema file from scratch and then use this file for generating the models. Alternative, you can use the models you generated using the database structure to create a schema file and then edit this file before using it to regenerate the models.
If you want to create a schema file, just edit the generate_model.php file as follows (I commented the line that generates the model from the database and I added a new line that creates a YAML file from the models):
Return to the console and execute the following command again: php generated_model.php. After this you’ll find a schema.yml file inside of doctrine_students folder.
With all the models in place, it is time to see how you can use these files to manage the database.
IDEs: Eclipse PDT and Flash Builder 4
Up until now you could have used just about any text editor to complete the steps I’ve outlined. As the code you write gets more complex (and soon you will start to write Flex code as well), it is time to introduce you to my favorite setup for working on Flex and PHP projects: Eclipse PDT + Flash Builder 4 plug-in + XDebug (for enabling PHP debugging).
The easiest way to install Eclipse PDT and Flash Builder together is to first grab the Eclipse PDT and then install the plug-in version of Flash Builder on top of Eclipse PDT. You can read more about how to install these products together here.
Now that you have support for both PHP and Flex projects, create a PHP project using the PHP new project wizard from Eclipse PDT (select the PHP perspective and choose File > New > PHP Project). I named the project students.
Then add the doctrine_students folder to the project. Right-click the project name in Eclipse and choose New > Folder. When the wizard opens, click the Advanced button and then select the Link To Folder In The File System option. Next, click Browse, select the doctrine_students folder, and click Finish (see Figure 3).
Then create another two folders inside of doctrine_students folder: services and vo (see Figure 4).
Creating the PHP Services and PHP value objects
It is time to create the PHP services. I’ll use the model files that Doctrine generated earlier.
First I will create a PHP class with two methods: getCourses() and getCountries(). In many applications you have some tables that stores data that almost never changes, for example lists of countries or cities. For such tables, you don’t need to implement CRUD operations you just need the read method. This is the case with the courses and countries tables.
Getting the countries and courses
Create a file named CatalogService.php inside the doctrine_students/services/ folder and add this code:
If this is your first time using Doctrine you’ll probably find this code both strange and familiar at the same time.
As I said earlier, Doctrine offers a query language (DQL). You can learn more about DQL here. The code above uses DQL to retrieve all the records from the courses and countries tables. Some things to remember:
- When you set the from clause (for example, ->from(‘Countries’)) you don’t specify the actual table name but the name of the model PHP class generated by Doctrine to manage that table. In this case it happens to have the same name and the first letter is capitalized.
- DQL works with objects and generates objects or graphs of objects.
- You can control the result format for a query by setting the second parameter of the execute() method on Doctrine_Query. I want to have the result sets as arrays or associative arrays. You could have your own hydration method or you could use one of the built-in hydrators.
- If you want to see the actual SQL that is executed you can call this method: $q->getSqlQuery().
Add some records to the courses and countries tables or use this script to create the database.
Now, you’re ready to test this code. Usually I do this before trying to connect to the PHP code from a Flex application. Just add some simple code at the top of this file in order to initialize the CatalogService object and call the two methods:
If you run CatalogService.php in the browser you should see the courses and countries used in the two tables (see Figure 5).
I have the first service in place, but I’m not entirely satisfied with the way my service works. I want to use some simple value objects to wrap each record instead of having an array of associative arrays like I have now.
I create two value objects called Country and Course in the doctrine_students/vo/ folder:
Now I just have to create a function that transforms the arrays I get from the Doctrine_Query object into arrays of value objects. You could achieve this by creating a custom hydration method or processing the result you get from the array hydration method.
Here’s how I did it. I created a function called prepareForAMF that accepts two arguments. The first is the array you get using the Doctrine_Query object and the second is an array of objects to be used to wrap the associative array. From this array the function retrieves the types it has to use to transform the associative array into objects.
Here is the code for this function:
I chose to declare this function in the bootstrap.php file because this file is already included in all the PHP services.
Having this function defined I rewrote the methods from CatalogService like this:
Next you need to include the two value objects in the CatalogService.php class by adding these lines at the top of it:
If you run CatalogService.php in the browser you’ll see the courses and countries are now listed as objects (see Figure 6).
Note: In Doctrine 2.0 (the upcoming version of Doctrine) support for plain vanilla value objects and for AMF remoting is planned and this trick/hack will not be needed. This is actually the reason why I didn’t bother to write my own hydration method. I’m planning to do a test run with the nightly builds soon.
Creating the StudentsService service
It is time to move on and create another service for the students and marks tables. Create a file inside of doctrine_students/services/ folder named StudentsService.php.
Here is a blue print of the StudentsService class:
Let’s start by filling in the getAll() method. It will use the DQL feature to retrieve not only all the students, but also the country set for each student and all the marks he has (at this point you need to have some data in the students and marks tables):
Save the file and run it in the browser. If you choose view source you should see an array of arrays (see Figure 7).
Notice that for each record from the students table I retrieve all the records from the marks table (student_id = id) and the country record from the countries table (country_id = id). This works because Doctrine takes into account all the relations between the tables when it creates the models.
Because I defined a one-to-many relation between the countries and students tables (a country can be assigned to any number of students) and a many-to-many table (marks) between the students and courses tables (each student can have a mark for each course), Doctrine generated the model (schema.yml) and model classes to support this database schema. Indeed if you open the doctrine_students/schema.yml file and look at the Students entry you’ll see that it has defined two relations: Countries and Marks.
These two relations have the same name as the two fields you saw when you ran the StudentsService->getAll() method.
Next you’ll need to create two value objects (Student and Mark) to use with StudentsService. Add these two classes to the doctrine_students/vo/ folder:
Don’t forget to include these two new classes in the StudentsService.php file.
Now I’ll change the getAll() method to transform the array I get from the Doctrine query into an array of value objects:
Note: The prepareForAMF function works recursively as long as you provide the right mapping between the keys of the arrays and the value objects it needs to create. This time I defined these mappings as a class variable ($types).
If you run the code again you should now see arrays of value objects: Student, Mark, and Country.
There’s something else I want to fix. The registration field from the students table has the Date type. But in the array returned by the getAll() method it is a String. I want to make sure that this field is actually an instance of the DateTime class. This will pay dividends later on when I retrieve these records in the Flex client, because it will receive value objects that have the Date type for the registration field instead of String.
The best place to do this transformation is probably inside the prepareForAMF() function. I added an optional third argument that is an array of the fields that have Date or DateTime type. So here is the modified function:
Then I add a third argument when calling the prepareForAMF() function inside of getAll():
Handling Create, Update, and Delete with Doctrine
It is time to write the StudentsService methods for creating, updating, and deleting records. For this, you need to fill in the blanks for the other two methods defined earlier: save() and delete(). Please notice:
- I want to use the save() method for both creating new records and updating existing ones.
- The save() method receives one argument: an instance of a Student value object. If you want to add marks for the student you have to have Mark objects stored in the mark property.
- The save() method will add or change the related records from the marks table for the current student .
- The delete() method deletes all the related records from the marks table first and then the student (it is transaction safe).
Here is the code for save():
And here is the code for two functions I use in the save() method (I declared these functions in the bootstrap.php file):
There are comments in the code so it should be easy to read and understand. I’ll explain just the parts I think are important:
- First of all, as I said earlier, Doctrine 1.x doesn’t have support for plain vanilla value objects. This is why the first thing I do is to transform the Student value object into an associative array using the makeArrayFromObject() function.
- When I update an existing record I use DQL to create an instance of Students. Once I have the Students object for the given ID, I want to load all the related marks records (by calling $o->Marks). Next I use the synchronizeWithArray() method to apply the changes from the $record array to the existing data (basically synchronizeWithArray() receives an associative array similar in structure to the one you get when using the Array hydration method on the same object). Finally I set the country id, and once I call the save() method on the Students object ($o->save()) the data are persisted.
- When I add a new record, I instantiate a Students object and then I use the fromArray() method to load the data from the $record array. However I found a glitch: for some reason it doesn’t work with the relations (in my case it didn’t save the related records to the marks table). So the workaround I found is to use the fromArray() method to load only the properties stored in the same table (students). This is why I clean the array from all the relations (Marks and Countries) using a call to the cleanReferences() function. I add the Marks objects manually using a for() loop.
- When adding marks records, it is interesting to note that Doctrine takes care of all the plumbing work for me. Suppose you add a new student and at the same time two marks: (course_id:1, mark:10) and (course_id:3, mark:9). The marks table has a third field student_id. When you call the save() method on the Students object, Doctrine will insert a new record in the students table, it will retrieve the id and then fill in the missing student_id for the two Mark records and then insert these two records.
The code for the delete() operation is much simpler. I use DQL to first delete all the related records from the marks table and then I delete the student record using an instance of Students. I make use of Doctrine support for transactions to keep the data consistent.
You now have all the code in place. If you want to verify it you can add testing code (the lines below) in the StudentsService.php file. Then run the file in the browser and then check the database:
Before moving on, don’t forget to comment (or delete) all the code you added in the service classes for testing.
Installing the Zend Framework
The next task I have to complete on the PHP side is to enable support for remoting in PHP. I chose to do so by using the Zend Framework. You can use AMFPHP, WebORB for PHP, or SabreAMF as well.
Download and unzip the framework somewhere on your disk. Then use the php.ini file to include the framework:
Save the file and restart the server. If you don’t have access to the php.ini you probably have to copy the whole framework under your web root, though this is not recommended for production.
Setting up the gateway file
The last step I have to complete on the PHP side before moving to Flex is to create the gateway file. This file will represent the end point for all the remoting calls from the Flex application. It will configure and start the Zend AMF server.
Create a new file called gateway.php inside of doctrine_students folder and then add this code:
Save the file and run it in the browser. You should either see the message “Zend Amf Endpoint” or a save dialog.
Creating the Flex front-end
With all the PHP code in place, it is time to create the Flex front-end. From a workflow perspective you have two options:
- You can create a separate Flex project and have two projects in place: one for Flex and one for PHP.
- You can add Flex nature to the PHP project and use the same project for both Flex and PHP.
I usually prefer the second approach because I work on both Flex and PHP code and it is simple to debug them together.
To add Flex nature to the students project, right-click on the project name in Eclipse, and select Add/Change Project Type > Add Flex Project Type. When the wizard opens, select PHP as the application server type and click Next. Fill in the path to the web server root and URL to reach the root, then click Validate Configuration and then Finish. You might see this error in the Problems view: “Cannot create HTML wrapper. Right-click here to recreate folder html-template.” Just right-click on it and choose Recreate Folder HTML-template.
This wizard will create an MXML file named students.mxml as well. Open this file if you haven’t already and add the following code inside the <fx:Declarations> tag:
The code defines two remoteObjects, one for each service on the PHP side: CatalogService and StudentsService. I added the methods for clarity, although this is not mandatory. Note that the endpoint attribute points to the gateway.php file created earlier.
The next step is to define the onResult and onFault event listeners. Add this code above or below the <fx:Declaration> tag:
To test the code you’ll need to add a button and call one of the service’s methods on click. Next, add two breakpoints on the first line where each event listener is defined and then run the project in debug mode.
After the page loads, click the button and in Eclipse you should see the Flex debugger stopped (see Figure 8).
Creating the ActionScript value objects
When using remoting it is usually a good idea to create value objects on the Flex side that correspond to the value objects you have on the server side. In my case I need to create four value objects: Student, Mark, Course, and Country. I place all these classes in the org.corlan.vo package.
The code is here:
There is nothing special to these value object classes. I’m using the Bindable metadata to make each property bindable and I use the RemoteClass metadata to tell AMF which is the corresponding class on the server side. On the PHP side I have the mapping defined inside of the gateway.php file using the setClassMap() method from AMF server – $server->setClassMap(“Country”, “Country”);.
For the Student value object I use the getter/setter to cast the array of objects received from the server to an array of Mark value objects.
Before testing the code you need to make sure that these four value object classes are included in the compiled SWF. You can simply add these lines in the students.mxml file:
If you run the project and call the studentsService.getAll() method when clicking on the button, you should see the value objects in the Flex debugger (see Figure 9).
Creating the UI
Now you can focus on the UI of this application. It will be a simple UI with a data grid for displaying all the students (see Figure 10). When a student is selected from the data grid, the form on the right is populated and you can use it to change details for the current student. If you want to delete a student, just select a row and click Delete. If you want to add a new record, click New and then fill in the form.
I have a function that is used for creating the text in the courses column, and a bunch of arrays I’m using to store the data models (courses, countries, and students).
Here is the code for the application:
Getting the source code
If you want to have a look at the project files here are the links:
- The doctrine_students folder is here. It includes the Doctrine framework, the gateway.php and bootstrap.php files, the models, services, and value object classes.
- From here you can download the source folder of the Flex project (you need the Flex 4 SDK to compile it).
- Use this script to create the database and populate it with some data.
- You can find the MySQL Workbench model file here.
In my experience, a software application is pretty much like a living thing: it grows and changes over time. Having some help along the way to make these changes smoother, reduce hand-coding, and minimize the potential for injecting bugs can make a big difference.
I think using an ORM framework on the server and letting this framework do the heavy lifting (generating the models based on the database structure, escaping the values to protect against SQL injection, making it easy to create a complex domain model, and making it easy to create CRUD services) is something that you may want to consider for your next project. These capabilities are especially helpful in the world of rich Internet applications where an important part of the business application stays on the client’s machine.
The only drawback I see with the current version of Doctrine (1.x) is that it needs some boiler plate code in order to make it possible to work with remoting and Flex. You can tweak the framework even more to make it generate the value objects as well. It seems the next version of Doctrine will make this integration much easier, and I plan to check this out soon :).
Until next time, please drop a comment and share your experience with PHP ORMs and using them with Flex.