NOTE: This tutorial has been made with Ubuntu 11.04 – Natty Narwhal, it hasn’t been tested on other OSs. This is some kind of cheat sheet to help you if you forget something basic about symfony2, but you obviously must read symfony2 documentation to know what’s all this about http://symfony.com/doc/2.0/book/index.html

Source code of this tutorial can be downloaded at: https://github.com/montes/Adictos-Symfony2-Bundle

UPDATED 5/23/2011: Updated for symfony2 beta2

1. Download

Download Symfony2 Standard Edition (at the moment of writing this, last version is BETA2): http://symfony.com/download

Extract

tar -zxvf Symfony_Standard_Vendors_2.0.0BETA2.tgz

And change permissions to make app/cache and app/logs writable, for example:

chmod 777 app/cache app/logs

Point your server to “web/” as root directory and you already would have to see symfony2 welcome page at http://127.0.0.1/app_dev.php/

2. Setup database

Setup app/config/parameters.ini with your database options (you also can do it from http://127.0.0.1/app_dev.php/_configurator/ ), for mysql would be something like:

[parameters]
    database_driver=pdo_mysql
    database_host=localhost
    database_name=symfony2
    database_user=symfony2
    database_password=password
    mailer_transport=smtp
    mailer_host=localhost
    mailer_user=
    mailer_password=
    locale=en
    csrf_secret=op234j234j2424jojpfwesdcsdc

3. Creating our first bundle

Now we already can create a bundle:

php app/console init:bundle "Montes\AdictosBundle" src

Now add this to app/autoload.php :

$loader->registerNamespaces(array(
    'Montes'                         => __DIR__.'/../src',
    // ...
));

and this to app/AppKernel.php :

$bundles = array(
    // ...
    new Montes\AdictosBundle\MontesAdictosBundle(),
);

4. Route

In order to symfony2 knows where it must send requests, we add to app/config/routing.yml :

homepage:
    pattern:  /adictos
    defaults: { _controller: MontesAdictosBundle:Default:index }

At this point we already have our new bundle working at: http://127.0.0.1/app_dev.php/adictos

5. Controller

The default controller (Montes/AdictosBundle/Controller/DefaultController.php) has been already created by Symfony2, now we are going to create the StoreController.php

//Montes/AdictosBundle/Controller/StoreController.php
<?php

namespace Montes\AdictosBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class StoreController extends Controller
{
    /**
     * @Template()
     */
    public function indexAction($store)
    {
        return array('store' => $store);
    }
}

an add to app/config/routing_dev.yml

store:
    pattern: /adictos/{store}
    defaults: { _controller: MontesAdictosBundle:Store:index }

and a new twig’s template at Montes/AdictosBundle/Resources/Views/Store/index.html.twig

So you want store ""?

And now going to http://127.0.0.1/app_dev.php/adictos/my-store we’ll get:

So you want store “my-store”?

6. Model

First we add to app/config/config.yml “MontesAdictosBundle: ~“ From Beta1 mapping is auto by default.

We start with Doctrine2, we are going to define our model for store/category. Before to start, we must to create the folder Montes/AdictosBundle/Entity where we’ll save all of them.

And we can start, model for Store:

<?php
// Montes/AdictosBundle/Entity/Store.php

namespace Montes\AdictosBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Store
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
     protected $id;

    /**
     * @ORM\ManyToMany(targetEntity="Category")
     * @ORM\JoinTable(name="stores_categories",
     *      joinColumns={@ORM\JoinColumn(name="store_id", referencedColumnName="id")},
     *      inverseJoinColumns={@ORM\JoinColumn(name="category_id", referencedColumnName="id")})
     */
    protected $categories;

    /**
     * @ORM\Column(type="string", length="255")
     */
    protected $url;

    /**
     * @ORM\Column(type="string", length="255")
     */
    protected $name;

    /**
     * @ORM\Column(type="integer")
     */
    protected $clicks = 0;

    /**
     * @ORM\Column(type="boolean")
     */
    protected $validated = false;

    /**
     * @ORM\Column(type="integer")
     */
    protected $pcomments = 0;

    /**
     * @ORM\Column(type="integer")
     */
    protected $ncomments = 0;

    /**
     * @ORM\Column(type="boolean")
     */
    protected $active = false;

    /**
     * @ORM\Column(type="datetime", name="updated_at")
     */
    protected $updatedAt;

    /**
     * @ORM\Column(type="datetime", name="created_at")
     */
    protected $createdAt;

    public function __construct()
    {
        $this->categories = new \Doctrine\Common\Collections\ArrayCollection();
        $this->createdAt = new \DateTime();
        $this->updatedAt = new \DateTime();
    }
}

Model for Category:

<?php
// Montes/AdictosBundle/Entity/Category.php

namespace Montes\AdictosBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 */
class Category
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\OneToMany(targetEntity="Category", mappedBy="parent")
     */
    protected $children;

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="children")
     * @ORM\JoinColumn(name="parent_id", referencedColumnName="id")
     * @ORM\Column(nullable="true")
     */
    protected $parent;

    /**
     * @ORM\ManyToMany(targetEntity="Store", mappedBy="categories")
     */
    protected $stores;

    /**
     * @ORM\Column(type="string", length="255")
     */
    protected $name;

    /**
     * @ORM\Column(type="string", length="255", name="url_string", unique="true")
     */
    protected $urlString;

    public function __construct()
    {
        $this->stores = new \Doctrine\Commmon\Collections\ArrayCollection();
    }
}

And now we go to terminal and run this to generate the database tables:

php app/console doctrine:schema:create

and to complete our model with its getters/setters:

php app/console doctrine:generate:entities MontesAdictosBundle

7. Testing the model

We’re going to change the action that shows our store to it’s own action (storeAction) and now the indexAction will show how many stores we have in the database.

# app/config/routing_dev.yml
store:
    pattern: /adictos/{store}
    defaults: { _controller: MontesAdictosBundle:Store:store }

store_index:
    pattern: /adictos/
    defaults: { _controller: MontesAdictosBundle:Store:index }
<?php
// Montes/AdictosBundle/Controller/StoreController.php

namespace Montes\AdictosBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;

class StoreController extends Controller
{

    /**
     * @Template()
     */
    public function indexAction()
    {
        $em = $this->get('doctrine.orm.entity_manager');
        $stores = $em->createQuery('SELECT count(s.id) AS total FROM Montes\AdictosBundle\Entity\Store s')->getSingleScalarResult();
        return array('stores' => $stores);
    }

    /**
     * @Template()
     */
    public function storeAction($store)
    {
        return array('store' => $store);
    }
}
<!-- Montes/AdictosBundle/Resources/views/Store/index.html.twig -->
We have a total of  stores.
<!-- Montes/AdictosBundle/Resources/views/Store/store.html.twig -->
So you want store ""?

Now when browsing to http://127.0.0.1/app_dev.php/adictos/ we’ll get We have a total of 0 stores and at http://127.0.0.1/app_dev.php/adictos/my-store we’ll get So you want store “my-store”?