In our first blog post about Symfony2 we discussed really just some basic stuff. We want to go further with this series and the next logical step is to talk about the MVC: Model View Controller implementation. It is the main pattern in which web apps are made these days. And it is also the main reason why eZ 5 is using Symfony2.
A nice summary about MVC is given on Wikipedia. It is interesting to note what Fabien (Symfony2 chief) thinks about the topic.
Basically, pure Symfony2 is not an MVC, it has the V and the C but the M is not in the core. Of course, there are other tools like ORM libraries that can be used to build the Model. E.g. Doctrine is one such tool which is provided as Symfony2 bundle. This goes very much inline with eZ Publish 5 architecture. It offers a Public API which should be used to access and manipulate content. So the Public API is the Model, and in the case of eZ Publish 5 it is deployed as a Symfony2 bundle. We will write about this in some other eZ Publish 5 oriented post.
So far we did just a few Symfony2 projects and we already used different Model types. In one case we used Doctrine ORM mapper, in other case we used an external REST API and yet in another case we accessed LDAP :)
For those 2 latter cases the way to implement the Model is to create a set of classes for handling all requests to the backend. Those classes can be configured and instantiated through Service Container. The service container is a Symfony2 implementation of the Dependency Injection Container (a.k.a. DIC).
In this post we will show a Model example implemented with Doctrine ORM, which is probably the most common type.
Example with Doctrine
Let’s use a very common case of a “user with a role” scenario. You need to persist data about the user and also which role he uses. Usually you would start by creating some tables in the database of your choice. With Doctrine it starts a bit differently.
1. Define the ORM model in Doctrine configuration for each strong entity
https://github.com/ilukac/TestBundle/blob/master/Resources/config/doctrine/User.orm.yml
https://github.com/ilukac/TestBundle/blob/master/Resources/config/doctrine/Role.orm.yml
Showing just a small part here:
Netgen\Bundle\TestBundle\Entity\User:
type: entity
table: user
id:
id:
type: integer
generator: { strategy: AUTO }
fields:
username:
type: string
length: 64
unique: true
firstName:
type: string
length: 128
column: first_name
lastName:
type: string
length: 128
column: last_name
The configuration is in YML syntax and is mostly self explanatory for simple cases. Just note the manyToMany attribute in the User entity. This attribute will be used in the next step to automagically create the additional table for implementing the many-to-many relationship between users and roles.
2. Creating tables
You can see the list of available commands for doctrine command namespace by executing the following line from your project root folder:
php app/console list doctrine
To create tables in your database, execute the following command:
php app/console doctrine:schema:create
Or, if you already have a database schema created, update it with a script:
php app/console doctrine:schema:update
Update script has two parameters available, --force and --dump-sql. Parameter --dump-sql will show you the list of SQL commands generated from the ORM mapping, which will be used in order to update the current database schema, while the --force parameter will automatically execute these commands. If automatic update fails for some reason, e.g. if the script breaks some constraint during the update (foreign key, NOT NULL, unique...), you will receive a warning and the list of SQL commands that were not executed automatically. If this situation occurs, you can simply copy the list of commands and execute them manually to finish the schema update.
3. Creating entities
It is possible to create the entity classes with the script:
php app/console doctrine:generate:entities
Upon executing the script, entity classes and method stubs will be generated from your mapping information in the Entity folder (in our case, TestBundle/Entity). Just a short notice, if you execute the script above, ALL entities will be regenerated. So if you already had some entities created, they will be regenerated, and the backup file for each entity will be created in the /Entity folder. Any custom code that you added in the Entity definitions will be preserved during the entity regeneration. The script will update existing entity definitions by adding the missing methods and properties, but it won’t change or remove already existing methods or properties, whether they were generated by Doctrine or added manually. The User and Role entity classes can be found at:
https://github.com/ilukac/TestBundle/blob/master/Entity/User.php
https://github.com/ilukac/TestBundle/blob/master/Entity/Role.php
And our Model is ready to be used.
We can demonstrate Model usage on a simple code example. For instance, let’s say that we want to create new User object, set some attribute values and store it in the database:
$user = new User();
$user->setUsername( 'UserExample' );
$user->setFirstName( 'User' );
$user->setLastName( 'Example' );
$user->setEmailAddress( '[email protected]' );
$user->setPlainPassword( 'password1234' );
$em = $this->getDoctrine()->getEntityManager();
$em->persist( $user );
$em->flush();
After we have created new user, let’s say that we know the user ID and we want to fetch user from the database:
$user = $this->getDoctrine()
->getRepository('TestBundle:User')
->find($id);
We can check if we successfully fetched User object from database by using instanceof operator:
if ( !$user instanceof User )
{
// do something, e.g. display “user not found” message.
}
Doctrine offers all sorts of helpful methods for handling data from the database. If we don’t know ID of the user we want to fetch, we can find it by some other attribute, for example we can use findOneBy() method to fetch user by username:
$user = $this->getDoctrine()
->getRepository( 'TestBundle:User' )
->findOneBy( array( 'userName' => 'UserExample' ) );
After we got valid User object from the database, we can update User object attributes. For demonstration, we can get the existing ‘editor’ role, assign it to user and store changes to database:
$em = $this->getDoctrine()->getEntityManager();
$role = $this->getDoctrine()
->getRepository( 'TestBundle:Role' )
->findOneBy( array( 'roleIdentifier' => 'editor' ) );
$user->addUserRole( $role );
$em->flush();
Removing existing user from the database is also very simple:
$em = $this->getDoctrine()->getEntityManager();
$em->remove( $user );
$em->flush();
Stay tuned for our next blog post :)