SymfonyWorld Online 2021 Winter Edition December 9 – 10, 2021 100% Online +20 talks and workshops
Caution: You are browsing the legacy symfony 1.x part of this website.

Day 02: setting up a data model

Previously on symfony

During day one of this long but interesting tutorial, we saw how to install the symfony framework, setup a new application and development environment, and bring safety to the code with source version control. By the way, the code of the application generated during the first day is available in the askeet SVN repository at:

The objectives for the second day are to define what the final result should be in terms of functionalities, sketch the data model, and begin coding. This will include generating an object-relational mapping and using it interactively to create, retrieve and update records in a database with an application scaffolding.

That's quite a lot. Let's get started.

The project unveiled

What do you want to know? That's an interesting question. There are many interesting questions, like:

  • What shall I do tonight with my girlfriend?
  • How can I generate traffic to my blog?
  • What's the best web application framework?
  • What's the best affordable restaurant in Paris?
  • What's the answer to life, the universe, and everything?

All these questions don't have only one answer, and the best answer is a matter of opinion. As a matter of fact, the questions that only have one answer are often the least interesting (like, how much is 1 + 1?) but the only ones to be solved on the web. That's not fair.

Meet askeet. A website dedicated to help people find answers to their questions. Who will answer those ticklish questions? Everybody. And everybody will be able to rate other people's answers, so that the most popular answers get more visibility. As the number of questions increases, it becomes impossible to organize them in categories and sub-categories, so the creator of a question will be able to tag it with any word he/she wants, "à la" Of course, the popularity of tags will have to be represented through a tag bubble. If one wants to follow the answers to a particular question, he/she can subscribe to this question's RSS feed. All these functionalities have to be elegant and lightweight, so all the interactions that don't actually need a new page have to be of AJAX type. Eventually, a back-end is necessary to moderate questions and answers reported as spam, or to push artificially a question that the administrator finds encouraging.

Then you should ask: Haven't I already seen such a website on the web? Well, if you actually did, we're busted, but if you refer to faqts, eHow, Ask Jeeves or something similar, with no collaborative answers, no AJAX, no RSS and no tags, this is not the same website. We are talking about a web 2.0 application here.

The big deal about askeet is that it is not only a website, it is an application that anyone can download, install at home or in a company Intranet, tweak and add features to. The source code will be released with an open-source license. Your HR head is looking for a knowledge management system? You want to keep track of all the tricks you learned about fixing your car? You don't want to develop a Frequently Asked Questions section for your website? Search no more, for askeet exists. Well, it will exist, that's our Christmas present.

Where to start?

So how are you supposed to start a symfony application? It all depends on you. You could write stories, do a planning game and find a partner to do pair programming if you were an XP adept, or write a detailed specification of the website, together with a sketch of all the objects, states, interactions and so on if you were a UML fan.

But this tutorial isn't about application development in general, so we'll start with a basic relational data model, and add working features one by one. What we need is an application that can be used at the end of every day, not a gigantic ongoing bunch of code that never outputs anything. In an ideal world, we should write unit tests for any feature we add, but we honestly won't have time for that. One day will be dedicated to unit tests though, so keep on reading.

For this project, we will use a MySQL database with the InnoDB table type to take advantage of the integrity constraints and transaction support. We could have used a SQLite database for the first steps, to avoid setting up a real database. This would have required only a few changes in the databases.yml file, that we'll leave for you to investigate as an exercise.

Data Model

Relational model

Obviously, there will be a 'question' and an 'answer' tables. We'll need a 'user' table, and we'll store the interest of users for a question in a 'interest' table, and the relevancy given by a person to an answer in a 'relevancy' table.

Users will have to be identified to add a question, to rate the relevancy an answer, or to declare interest to a question. Users won't need to be identified to add an answer, but an answer will always be linked to a user so that users with popular answers can be distinguished. The answers entered without any identification will be shown as contributions of a generic user, called 'Anonymous Coward'. It's easier to understand with an entity relationship diagram:


Notice that we've declared a created_at field for each table. Symfony recognizes such fields and sets the value to the current system time when a record is created. That's the same for updated_at fields: Their value is set to the system time whenever the record is updated.


The relational model has to be translated to a configuration file for symfony to understand it. That's the purpose of the schema.xml or schema.yml file, located in the askeet/config/ directory. Symfony supports schemas in the XML or YAML format.

There are two ways to write this file: by hand, and that's the way we like it, or from an existing database. Let's see the first solution.

First, we need to remove the sample YAML file installed by default:

$ svn delete config/schema.yml

The syntax of the schema.xml, explained in detail on the Propel website, is relatively simple: It's an XML file, in which <table> tags contain <column>, <foreign-key> and <index> tags. Once you write one, you can write all of them. Here is the schema.xml corresponding to the relational model described previously:

<?xml version="1.0" encoding="UTF-8"?>
 <database name="propel" defaultIdMethod="native" noxsd="true">
   <table name="ask_question" phpName="Question">
     <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
     <column name="user_id" type="integer" />
     <foreign-key foreignTable="ask_user">
       <reference local="user_id" foreign="id"/>
     <column name="title" type="longvarchar" />
     <column name="body" type="longvarchar" />
     <column name="created_at" type="timestamp" />
     <column name="updated_at" type="timestamp" />
   <table name="ask_answer" phpName="Answer">
     <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
     <column name="question_id" type="integer" />
     <foreign-key foreignTable="ask_question">
       <reference local="question_id" foreign="id"/>
     <column name="user_id" type="integer" />
     <foreign-key foreignTable="ask_user">
       <reference local="user_id" foreign="id"/>
     <column name="body" type="longvarchar" />
     <column name="created_at" type="timestamp" />
   <table name="ask_user" phpName="User">
     <column name="id" type="integer" required="true" primaryKey="true" autoIncrement="true" />
     <column name="nickname" type="varchar" size="50" />
     <column name="first_name" type="varchar" size="100" />
     <column name="last_name" type="varchar" size="100" />
     <column name="created_at" type="timestamp" />
   <table name="ask_interest" phpName="Interest">
     <column name="question_id" type="integer" primaryKey="true" />
     <foreign-key foreignTable="ask_question">
       <reference local="question_id" foreign="id"/>
     <column name="user_id" type="integer" primaryKey="true" />
     <foreign-key foreignTable="ask_user">
       <reference local="user_id" foreign="id"/>
     <column name="created_at" type="timestamp" />
   <table name="ask_relevancy" phpName="Relevancy">
     <column name="answer_id" type="integer" primaryKey="true" />
     <foreign-key foreignTable="ask_answer">
       <reference local="answer_id" foreign="id"/>
     <column name="user_id" type="integer" primaryKey="true" />
     <foreign-key foreignTable="ask_user">
       <reference local="user_id" foreign="id"/>
     <column name="score" type="integer" />
     <column name="created_at" type="timestamp" />

Notice that the database name is set to propel in this file, whatever the actual database name. This is a parameter used to connect the Propel layer to the symfony framework. The actual name of the database will be defined in the databases.yml configuration file (see below).

There is another way to create a schema.xml if you have an existing database. That is, if you are familiar with a graphical database design tool, you will prefer to build the schema from the generated MySQL database. Before you do that, you just need to edit the propel.ini file located in the askeet/config/ directory and enter the connection settings to your database:

propel.database.url = mysql://username:[email protected]/databasename

...where username, password, localhost and databasename are the actual connection settings of your database. You can now call the propel-build-schema command (from the askeet/ directory) to generate the schema.xml from the database:

$ symfony propel-build-schema


some tools allow you to build a database graphically (for instance Fabforce's Dbdesigner) and generate directly a schema.xml (with DB Designer 4 TO Propel Schema Converter).

Instead of creating a schema.xml file, you can also create a schema.yml file using the YAML schema format:

  _attributes:   { noXsd: false, defaultIdMethod: none, package: lib.model }
    _attributes: { phpName: Question, idMethod: native }
    id:          { type: integer, required: true, primaryKey: true, autoIncrement: true }
    user_id:     { type: integer, foreignTable: ask_user, foreignReference: id }
    title:       { type: longvarchar }
    body:        { type: longvarchar }
    created_at:  ~
    updated_at:  ~
    _attributes: { phpName: Answer, idMethod: native }
    id:          { type: integer, required: true, primaryKey: true, autoIncrement: true }
    question_id: { type: integer, foreignTable: ask_question, foreignReference: id }
    user_id:     { type: integer, foreignTable: ask_user, foreignReference: id }
    body:        { type: longvarchar }
    created_at:  ~
    _attributes: { phpName: User, idMethod: native }
    id:          { type: integer, required: true, primaryKey: true, autoIncrement: true }
    nickname:    { type: varchar(50), required: true, index: true }
    first_name:  varchar(100)
    last_name:   varchar(100)
    created_at:  ~
    _attributes: { phpName: Interest, idMethod: native }
    question_id: { type: integer, foreignTable: ask_question, foreignReference: id, primaryKey: true }
    user_id:     { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }
    created_at:  ~
    _attributes: { phpName: Relevancy, idMethod: native }
    answer_id:   { type: integer, foreignTable: ask_answer, foreignReference: id, primaryKey: true }
    user_id:     { type: integer, foreignTable: ask_user, foreignReference: id, primaryKey: true }
    score:       { type: integer }
    created_at:  ~

Object model build

To use the InnoDB engine, one line has to be added to the propel.ini file of the askeet/config/ directory:

propel.mysql.tableType = InnoDB

Once the schema.xml is built, you can generate an object model based on the relational model. In symfony, the object relational mapping is handled by Propel, but encapsulated into the symfony command:

$ symfony propel-build-model

This command (you need to call it from the root directory of the askeet project) will generate the classes corresponding to the tables defined in the schema, together with standard accessors (->get() and ->set() methods). You can look at the generated code in the askeet/lib/model/om/ directory. If you wonder why there are two classes per table, go and check the model chapter of the symfony book. These classes will be overridden each time that you do a build-model, and this will happen a lot in this project. So if you need to add methods to the model objects, you have to modify the ones located in the askeet/lib/model/ directory - these classes inherit from the /om ones.

The database


Now that symfony has an object model of the database, it is time to connect your project to the MySQL database. First, you have to create a database in MySQL:

$ mysqladmin -u youruser -p create askeet

Now open the askeet/config/databases.yml configuration file. If this is your first time with symfony, you will discover that the symfony configuration files are written in YAML. The syntax is very simple, but there is one major obligation in YAML files: never use tabulations, always use spaces. Once you know that, you are ready to edit the file and enter the actual connection settings to your database under the all: category:

    class:      sfPropelDatabase
      phptype:  mysql
      host:     localhost
      database: askeet
      username: youruser
      password: yourpasswd

If you want to know more about symfony configuration and YAML files, read the configuration in practice chapter of the symfony book.


If you didn't write the schema.xml file by hand, you probably already have the corresponding tables in your database. You can then skip this part.

For you keyboard fans, here is a surprise: You don't need to create the tables and the columns in the MySQL database. You did it once in the schema.xml, so symfony will build the SQL statement creating all that for you:

$ symfony propel-build-sql

This command creates a lib.model.schema.sql in the askeet/data/sql/ directory. Use it as a SQL command in MySQL:

$ mysql -u youruser -p askeet < data/sql/lib.model.schema.sql

Alternatively, you can also use the propel-insert-sql task:

$ symfony propel-insert-sql

Test data access via a CRUD

It is always good to see that the work done is useful. Until now, your browser wasn't of any use, and yet we are supposed to build a web application... So let's create a basic set of symfony templates and actions to manipulate the data of the 'question' table. This will allow you to create a few questions and display them.

In the askeet/ directory, type:

$ symfony propel-generate-crud frontend question Question

This generates a scaffolding for a question module in the frontend application, based on the Question Propel object model, with basic Create Retrieve Update Delete actions (which explains the CRUD acronym). Don't get confused: A scaffolding is not a finished application, but the basic structure on top of which you can develop new features, add business rules and customize the look and feel.

The list of all the actions created by a CRUD generator is:

Action name Description
list shows all the records of a table
index forwards to list
show shows all the fields of a given record
edit displays a form to create a new record or edit an existing one
update modifies a record according to the parameters given in the request, then forwards to show
delete deletes a given record from the table

You can find more about generated actions in the scaffolding chapter of the symfony book.

In the askeet/apps/frontend/modules/ directory, notice the new question module and browse its source.

Whenever you add a new class that need to be autoloaded, don't forget to clear the config cache (to reload the autoloading cache):

$ symfony cc frontend config

You can now test it online by requesting:


Create a new record List all records

Go ahead, play with it. Add a few questions, edit them, list them, delete them. If it works, this means that the object model is correct, that the connection to the database is correct, and that the mapping between the relational model of the database and the object model of symfony is correct. That's a good functional test.

See you Tomorrow

You didn't write one line of PHP, and yet you have a basic application to use. That's not bad for the second day. Tomorrow, we'll start writing some code in order to have a welcoming home page that displays the list of questions. We will also add test data to our database using a batch process, and learn how to extend the model.

Now that you know what the application will do, you may be able to imagine an additional feature to it. Feel free to suggest anything using the askeet mailing-list, the most popular idea will become the 21st day addition of this symfony advent calendar.

Feel free to browse the source of today's tutorial (tag release_day_2) at: