This is the fifth part of the series Symfony2 Tutorial for Beginners which will describe the process of creating and managing persistable entities.

We will create entities that can be written in a database as well as edited, deleted and of course shown.

Creating the persistable Entities

First, we will create the Admin-, Post-, and Category-Entities with their fields as discussed in the first part of this Tutorial The Planning Stage of Website Development (I will differ from it a little bit for the sake of simplicity, e.g. I’ve dropped the User-Entity).

Persistence in Symfony2 using Doctrine

All these Entities need to be written in a Database, to do this we will use Doctrine. The Metadata needed to do this can either be supplied in Annotations or using an external file in yaml or xml. In this tutorial I will mainly be using Annotations because in my opinion it is cleaner, but feel free to use yaml instead.

<?php
// src/YourIdentifier/YourBundle/Entity/Admin.php

namespace YourIdentifier\YourBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 *
 * @ORM\Entity
 */
class Admin {

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

    /**
     * @ORM\Column(type="string", unique=true)
     */
    protected $username;

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

    /**
     * @ORM\Column(type="string", length=32)
     */
    protected $salt;
}
?>

Above the class we add an annotation defining that this class should be mapped to a relational database. Above each field that needs to be persisted, we need to add the @ORM\Column   annotation and define the type of the field (here is a list of the possible types). This annotation also accepts a list of possible options, in the Admin-Entity above for example we used the unique option on the username as there should not be two admins with the same name.

Every object that gets written in the database should also have a unique id which will be automatically generated.

Managing Entities in the Controller

Now that the Entity-Classes are written lets go ahead and write the Controller-Actions which will write instances of these objects in the database (and also edit or delete them).

For the purpose of this tutorial I will only do this for the Admin-Entity class as we will generate the code for the Category- and Post-Entities in Chaper 7 – Admin Area Content: Code Generation.

Basic Action to show an Entity

Create a php class called AdminController.php:

<?php
// src/YourIdentifier/YourBundle/Controller/AdminController.php
namespace YourIdentifier\YourBundle\Controller;

/**
 * @Route("/admin")
 */
class AdminController  extends Controller {

    /**
     * finds and displays an Admin entity by id.
     *
     * @Route("/{id}/show", name="admin_show")
     * @Template("YourIdentifierYourBundle:Admin:show.html.twig")
     */
    public function showAction($id) {
        $em = $this->getDoctrine()->getEntityManager();
        $entity = $em->find( 'YourIdentifierYourBundle:Admin', $id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Admin entity.');
        }

        return array('entity' => $entity);
    }
}
?>

(Feel free to delete the AdminController class after testing if everything worked. Other than showing how to work on persistable entities it has no real use.)

As you can see, if we want to handle entities in the database at first we need the doctrine entity manager that symfony2 provides by calling $em = $this->getDoctrine()->getEntityManager();   .

Repositories

The EntityManager provides some useful basic methods, but to get even more methods (and to have a place to store the methods we create ourselves that operate on the database objects) we will create a Repository. Go ahead and create a directory called Repository in your bundle directory. Inside this directory create a file called AdminRepository.php:

<?php
// src/YourIdentifier/YourBundle/Repository/AdminRepository.php
namespace YourIdentifier\YourBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class AdminRepository extends EntityRepository implements UserProviderInterface {

    /* methods implementing UserProviderInterface abstract methods */

    public function loadUserByUsername($username) {
        return $this->findOneBy(array('username' => $username));
    }

    public function refreshUser(UserInterface $user) {
        if (!$user instanceof WebserviceUser) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        return $this->loadUserByUsername($user->getUsername());
    }

    public function supportsClass($class) {
        return true;
    }


    /* custom queries */

    /**
     * example custom query returning all admins with the given salt sorted by name.
     */
    public function getAdminsQuery($desired_salt) {
        $query = $this->createQueryBuilder('a')
                        ->select('a')
                >addOrderBy('a.username', 'DESC')
                ->andwhere('a.salt = :salt')
                ->setParameter('salt', $desired_salt);
        return $query->getQuery()->getResult();
    }
}
?>

All Symfony2 Repositories should extend the doctrine EntityRepository. Our Admin Entity also implements the Symfony2 UserProviderInterface which is not relevant until the next part of this tutorial.

We can now get this repository by calling $em->getRepository('YourIdentifierYourBundle:Name')   on the EntityManager where Name is the name of the repository class with the “Repository” dropped (so for our AdminRepository it would be “Admin”).

More database Actions on Entities using Repositories

Now we can use the EntityManager in the Controller to edit, list, delete and show entities from the database:

<?php
// src/YourIdentifier/YourBundle/Controller/AdminController.php

namespace YourIdentifier\YourBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use YourIdentifier\YourBundle\Entity\Admin;
use YourIdentifier\YourBundle\Form\AdminType;

/**
 * @Route("/admin")
 */
class AdminController extends Controller {

    /**
     * lists all Admins.
     *
     * @Route("/", name="list_admin")
     * @Template("YourIdentifierYourBundle:Admin:index.html.twig")
     */
    public function indexAction() {
        $admins = $this->getAdminRepository()->findAll();
        return array('entities' => $admins);
    }

    /**
     * finds and displays an Admin entity by id.
     *
     * @Route("/{id}/show", name="admin_show")
     * @Template("YourIdentifierYourBundle:Admin:show.html.twig")
     */
    public function showAction($id) {
        $admin = $this->getAdminRepository()->find($id);

        if (!$admin) {
            throw $this->createNotFoundException('Unable to find Admin entity.');
        }

        $deleteForm = $this->createDeleteForm($id);
        return array('entity' => $admin, 'delete_form' => $deleteForm->createView());
    }

    /**
     * creates a new Admin entity.
     *
     * @Route("/create", name="admin_new")
     * @Template("YourIdentifierYourBundle:Admin:new.html.twig")
     */
    public function createAction() {
        $entity = new Admin();
        $form = $this->createForm(new AdminType(), $entity);

        $request = $this->getRequest();
        if ($request->getMethod() == 'POST') {
            $form->bindRequest($request);
            if ($form->isValid()) {
                // valid post request: persist entity to database
                $em = $this->getDoctrine()->getEntityManager();
                $em->persist($entity);
                $em->flush();

                return $this->redirect($this->generateUrl('admin_show', array('id' => $entity->getId())));
            }
        }

        // get-request or invalid data. display form:
        return array(
            'entity' => $entity,
            'form' => $form->createView()
        );
    }

    /**
     * edits an existing Admin entity.
     *
     * @Route("/{id}/update", name="admin_update")
     * @Template("YourIdentifierYourBundle:Admin:edit.html.twig")
     */
    public function updateAction($id) {

        $entity = $this->getAdminRepository()->find($id);

        if (!$entity) {
            throw $this->createNotFoundException('Unable to find Admin entity.');
        }

        $editForm = $this->createForm(new AdminType(), $entity);
        $deleteForm = $this->createDeleteForm($id);

        $request = $this->getRequest();
        if ($request->getMethod() == 'POST') {
            $editForm->bindRequest($request);

            if ($editForm->isValid()) {
                $em->persist($entity);
                $em->flush();

                return $this->redirect($this->generateUrl('admin_show', array('id' => $id)));
            }
        }

        return array(
            'entity' => $entity,
            'edit_form' => $editForm->createView(),
            'delete_form' => $deleteForm->createView(),
        );
    }

    /**
     * deletes an Admin entity.
     *
     * @Route("/{id}/delete", name="admin_delete")
     * @Method("post")
     */
    public function deleteAction($id) {
        $form = $this->createDeleteForm($id);
        $request = $this->getRequest();

        $form->bindRequest($request);

        if ($form->isValid()) {
            $entity = $this->getAdminRepository()->find($id);

            if (!$entity) {
                throw $this->createNotFoundException('Unable to find Admin entity.');
            }

            $em->remove($entity);
            $em->flush();
        }

        return $this->redirect($this->generateUrl('list_admin'));
    }

    private function createDeleteForm($id) {
        return $this->createFormBuilder(array('id' => $id))
                        ->add('id', 'hidden')
                        ->getForm()
        ;
    }

    private function getAdminRepository() {
        $em = $this->getDoctrine()->getEntityManager();
        return $em->getRepository('YourIdentifierYourBundle:Admin');
    }
}
?>

This Controller now has actions for all relevant object manipulations: edit, show, list, create and delete.

Even though this might seem like a lot of code, there is not much new here.

The interesting bit is $em->persist($entity); $em->flush();   which does what you would expect: It writes the entity in the database.

Just read the class carefully, you should understand everything pretty much directly by now. If you have problems with parts of it though please feel free to leave a comment.

Displaying the Symfony2 Entities

Create the twig view templates as well as the php form file we are using in the controller above:

<!-- /src/YourIdentifier/YourBundle/Resources/views/Admin/index.html.twig -->
{% extends 'YourIdentifierYourBundle:Admin:layout.html.twig' %}

{% block stylesheets %}
    {% stylesheets filter='yui_css'
    'css/main.css' 'css/admin.css'
    %}
         <link rel="stylesheet" href="{{ asset_url }}" type="text/css" media="screen" />
    {% endstylesheets %}
{% endblock %}

{% block body %}
<h1>Admin list</h1>

<table class="records_list">
    <thead>
        <tr>
            <th>Id</th>
            <th>Username</th>
            <th>Salt</th>
        </tr>
    </thead>
    <tbody>
    {% for entity in entities %}
        <tr>
            <td><a href="{{ path('admin_show', { 'id': entity.id }) }}">{{ entity.id }}</a></td>
            <td>{{ entity.username }}</td>
            <td>{{ entity.salt }}</td>
        </tr>
    {% endfor %}
    </tbody>
</table>

<ul>
    <li>
        <a href="{{ path('admin_new') }}">
            Create a new entry
        </a>
    </li>
</ul>
{% endblock %}

<!-- /src/YourIdentifier/YourBundle/Resources/views/Admin/show.html.twig -->
{% extends 'YourIdentifierYourBundle:Admin:layout.html.twig' %}

{% block stylesheets %}
    {% stylesheets filter='yui_css' 'css/main.css' 'css/admin.css' %}
         <link rel="stylesheet" href="{{ asset_url }}" type="text/css" media="screen" />
    {% endstylesheets %}
{% endblock %}

{% block title %}View Admin{% endblock%}

{% block body %}

<h1>Category</h1>

<table class="record_properties">
    <tbody>
        <tr>
            <th>Id</th>
            <td>{{ entity.id }}</td>
        </tr>
        <tr>
            <th>UserName</th>
            <td>{{ entity.username }}</td>
        </tr>
        <tr>
            <th>Salt</th>
            <td>{{ entity.salt }}</td>
        </tr>
    </tbody>
</table>

<ul class="record_actions">
    <li>
        <a href="{{ path('list_admin') }}">
            Back to the list
        </a>
    </li>
    <li>
        <a href="{{ path('admin_update', { 'id': entity.id }) }}">
            Edit
        </a>
    </li>
    <li>
        <form action="{{ path('admin_delete', { 'id': entity.id }) }}" method="post">
            {{ form_widget(delete_form) }}
            <button type="submit">Delete</button>
        </form>
    </li>
</ul>
{% endblock %}

<!-- /src/YourIdentifier/YourBundle/Resources/views/Admin/new.html.twig -->
{% extends 'YourIdentifierYourBundle:Admin:layout.html.twig' %}

{% block stylesheets %}
    {% stylesheets filter='yui_css' 'css/main.css' 'css/basic_form.css' 'css/admin.css' %}
         <link rel="stylesheet" href="{{ asset_url }}" type="text/css" media="screen" />
    {% endstylesheets %}
{% endblock %}

{% block title %}Add an Admin{% endblock%}

{% block body %}
<h1>Admin creation</h1>

<form action="{{ path('admin_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}
    <p>
        <button type="submit">Create</button>
    </p>
</form>

<ul class="record_actions">
    <li>
        <a href="{{ path('list_admin') }}">
            Back to the list
        </a>
    </li>
</ul>
{% endblock %}

<!-- /src/YourIdentifier/YourBundle/Resources/views/Admin/edit.html.twig -->
{% extends 'YourIdentifierYourBundle:Admin:layout.html.twig' %}

{% block stylesheets %}
    {% stylesheets filter='yui_css' 'css/main.css' 'css/basic_form.css' 'css/admin.css'%}
         <link rel="stylesheet" href="{{ asset_url }}" type="text/css" media="screen" />
    {% endstylesheets %}
{% endblock %}

{% block title %}Edit an Admin{% endblock%}

{% block body %}

<h1>Admin edit</h1>

<form action="{{ path('admin_update', { 'id': entity.id }) }}" method="post" {{ form_enctype(edit_form) }}>
    {{ form_widget(edit_form) }}
    <p>
        <button type="submit">Edit</button>
    </p>
</form>

<ul class="record_actions">
    <li>
        <a href="{{ path('list_admin') }}">
            Back to the list
        </a>
    </li>
    <li>
        <form action="{{ path('admin_delete', { 'id': entity.id }) }}" method="post">
            {{ form_widget(delete_form) }}
            <button type="submit">Delete</button>
        </form>
    </li>
</ul>
{% endblock %}

<!-- /src/YourIdentifier/YourBundle/Resources/views/Admin/layout.html.twig -->
{% extends 'YourIdentifierYourBundle::base.html.twig' %}

{% block navigation %}
    <nav>
        <ul class="navigation">
            <li><a class="" href="{{ path('list_admin') }}">List Admins</a></li>
        </ul>
    </nav>
{% endblock %}

{% block sidebar %}
    admin Sidebar content
{% endblock %}

<!-- /src/YourIdentifier/YourBundle/Resources/views/Admin/form.html.twig -->
<form action="{{ path('PoliticiansSaysWhatWebSiteBundle_admin_create', {  } ) }}" method="post" {{ form_enctype(form) }} class="basic">
    {{ form_widget(form) }}
    <p>
        <input type="submit" value="Submit">
    </p>
</form>

<?php
// src/YourIdentifier/YourBundle/Form/AdminType.php
namespace YourIdentifier\YourBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;

class AdminType extends AbstractType
{
    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder->add('username');
        $builder->add('password');
        $builder->add('salt', null, array('max_length' => 100));
    }

    public function getName()
    {
        return 'admin';
    }
}
?>

(If you are unsure of how these files work you may want to (re-)read Part 3: The first static Page: Routing, Templates, Twig and Part 4: User Submissions: Controller, Forms, E-Mail of this symfony2 tutorial.)

We now have everything ready to test the Admin entity. If you want to you can go ahead and follow the few short steps under Finishing Up and test it out.

Or you can first keep reading the next chapter which looks a little bit closer at Cardinality.

Persistence in Symfony2 using Doctrine: advanced (relations)

Sometimes, Entities have relations towards each other. One example is our Category Entity. A Category holds zero to n Posts and a Post may be in zero to n Categories. This is also called a many to many relationship.

Relational Entities: Post and Category

<?php
// src/YourIdentifier/YourBundle/Entity/Post.php
namespace YourIdentifier\YourBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity(repositoryClass="YourIdentifier\YourBundle\Repository\PostRepository")
 * @ORM\Table(name="posts")
 * @ORM\HasLifecycleCallbacks()
 */
class Post {

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

    /**
     * @ORM\Column(type="string")
     */
    protected $title;

    /**
     * @ORM\Column(type="string")
     */
    protected $slug;

    /**
     * @ORM\Column(type="text")
     */
    protected $content;

    /**
     * @ORM\Column(type="string")
     */
    protected $user;

    /**
     * @ORM\ManyToMany(targetEntity="Category", inversedBy="categories")
     * @ORM\JoinTable(name="post_category")
     **/
    protected $categories;

    /**
     * Add categories
     */
    public function addCategory($category) {
        $this->categories[] = $category;
    }

    /**
     * Get categories
     *
     * @return Doctrine\Common\Collections\Collection
     */
    public function getCategories() {
        return $this->categories;
    }

    /**
     * Get id
     *
     * @return integer
     */
    public function getId() {
        return $this->id;
    }

    /**
     * Set title. also sets slug.
     *
     * @param string $title
     */
    public function setTitle($title) {
        $this->title = $title;
        $this->setSlug($this->title);
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle() {
        return $this->title;
    }

    /**
     * Set content
     *
     * @param text $content
     */
    public function setContent($content) {
        $this->content = $content;
    }

    /**
     * returns content.
     *
     * @return text
     */
    public function getContent($length = null) {
        return $this->content;
    }

    /**
     * Set publisher
     *
     * @param string $publisher
     */
    public function setUser($user) {
        $this->user = $user;
    }

    /**
     * Get publisher
     *
     * @return string
     */
    public function getUser() {
        return $this->user;
    }

    public function __toString() {
        return $this->getTitle();
    }

    /**
     * Set slug. calls slugify() first.
     *
     * @param string $slug
     */
    public function setSlug($slug) {
        $this->slug = $this->slugify($slug);
    }

    /**
     * Get slug
     *
     * @return string
     */
    public function getSlug() {
        return $this->slug;
    }

    public function slugify($text) {
        // replace non letter or digits by -
        $text = preg_replace('#[^\\pL\d]+#u', '-', $text);
        // trim
        $text = trim($text, '-');
        // transliterate
        if (function_exists('iconv')) {
            $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
        }
        // lowercase
        $text = strtolower($text);
        // remove unwanted characters
        $text = preg_replace('#[^-\w]+#', '', $text);
        if (empty($text)) {
            return 'n-a';
        }
        return $text;
    }

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

And:

<?php
// src/YourIdentifier/YourBundle/Entity/Category.php
namespace YourIdentifier\YourBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 *
 * @ORM\Table(name="category")
 * @ORM\Entity
 */
class Category {

    /**
     * @var integer $id
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string $name
     *
     * @Assert\NotBlank()
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="string")
     */
    protected $slug;

    /**
     * @ORM\ManyToMany(targetEntity="Post", mappedBy="categories", cascade={"persist"})
     */
    private $posts;

    public function addPost($post) {
        $post->addCategory($this);
        $this->posts[] = $post;
    }

    /**
     * overrides the array of posts belonging to this category in $posts
     * with the given array of posts.
     * Also adds this category to every post in the array.
     *
     */
    public function setPosts($posts) {
        foreach ($posts as $post) {
            $post->addCategory($this);
        }
        $this->posts = $post;
    }

    public function getPosts() {
        return $this->posts;
    }

    public function getId() {
        return $this->id;
    }

    /**
     * sets name. also sets slug.
     *
     * @param string $name
     */
    public function setName($name) {
        $this->name = $name;
        $this->setSlug($this->name);
    }

    public function getName() {
        return $this->name;
    }

    /**
     * sets slug. calls slugify() first.
     *
     * @param string $slug
     */
    public function setSlug($slug) {
        $this->slug = $this->slugify($slug);
    }

    /**
     * Get slug
     *
     * @return string
     */
    public function getSlug() {
        return $this->slug;
    }

    public function slugify($text) {
        // replace non letter or digits by -
        $text = preg_replace('#[^\\pL\d]+#u', '-', $text);
        // trim
        $text = trim($text, '-');
        // transliterate
        if (function_exists('iconv')) {
            $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text);
        }
        // lowercase
        $text = strtolower($text);
        // remove unwanted characters
        $text = preg_replace('#[^-\w]+#', '', $text);
        if (empty($text)) {
            return 'n-a';
        }
        return $text;
    }

    public function __toString() {
        return $this->getName();
    }

    public function __construct() {
        $this->posts = new \Doctrine\Common\Collections\ArrayCollection();
    }
}
?>

And again there is a lot of code but not too much of it is really relevant. we defined the fields that the category and post classes should have as well as the appropriate getters and setters. We also added a slug field and a slugify method which may be used inside an url (as the slugify method cleans the string so that it does not contain illegal characters).

The really interesting parts are once again the annotations which define how these entities are to be persisted to the database as well as the relation they have to each other.

The new annotations are the once for the categories field in the Post entity and the posts field in the Category entity.

In this example we are using a simple bidirectional many to many relation. I will leave it at that for now as this is not really a database tutorial. If you want to know more about relations in doctrine, read this nice and short overview about Association Mapping and if you want to read more about relational databases, wikipedia is good start.

Do not forget to reload the database schema using doctrine:schema:update  

Additional Repository-Methods, Forms and Twig templates

The code to manipulate and show relational entities is only presented here in excerpts as the full code to handle it will be added in Chaper 7 – Admin Area Content: Code Generation.

Form for many to many relationship in symfony2

The code for a method that creates a form which is used to create or edit a Post (and add it to one or more categories) looks like this:

    public function buildForm(FormBuilder $builder, array $options)
    {
        $builder
            ->add('title')
            ->add('description')
            ->add('categories', null, array('required' => false,
                            'multiple' => true,
                            'expanded' => true,
                ))
        ;
    }

Twig Template for many to many relationship in symfony2

In a twig template we can simply access the categories field via the getter (the categories do not have to be loaded by us per hand, they are loaded automatically):

{% for category in post.categories %}
    <a href="{{ path('category_show', { 'id': category.id, 'slug': category.slug }) }}">{{ category.name }}</a>,
{% endfor %}

Complex Sql Queries in the Repository

You can also do a little bit more complex queries now inside the Repository:

    /**
     * returns all posts by the user 'admin' in the category
     * identified by the given id sorted descending by creation date.
     *
     * @param type $category_id
     */
    public function getLatestPosts($category_id) {
        $query = $this->createQueryBuilder('p')
                ->select('p', 'c')
                ->leftJoin('p.categories', 'c')
                ->addOrderBy('p.created_at', 'DESC')
                ->andwhere('p.user = admin')
                ->andWhere('c.id = :c_id')
                ->setParameter('c_id', $category_id);
        return $qb->getQuery()->getResult();
    }

Test Data: DataFixtures

If you want to test the Entities without completing the code to create them over a browser or if you are generally interested in creating test data you might want to create data fixtures:

<?php
// src/YourIdentifier/YourBundle/DataFixtures/ORM/PostFixtures.php
namespace YourIdentifier\YourBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use YourIdentifier\YourBundle\Entity\Post;

class PostFixtures extends AbstractFixture implements OrderedFixtureInterface {

    public function load(ObjectManager $manager) {
        // create 20 posts with the references 'post-0' to 'post-19'
        for ($i = 0; $i < 20; $i++) {
            $Post = new Post();
            $Post->setTitle(getRandEntryTitle());
            $Post->setContent(getRandEntryDesc());
            $Post->setUser(getRandCatTitle());
            $manager->persist($Post);
            $this->addReference('post-' . $i, $Post);
        }

        // create one more post
        $Post = new Post();
        $Post->setTitle('TEST');
        $Post->setContent(getRandEntryDesc());
        $Post->setUser(getRandCatTitle());
        $manager->persist($Post);
        $this->addReference('post-21', $Post);
        $manager->flush();
    }

    public function getOrder() {
        return 1;
    }
}
?>

<?php
// src/YourIdentifier/YourBundle/DataFixtures/ORM/CategoryFixtures.php
namespace YourIdentifier\YourBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Common\Persistence\ObjectManager;
use YourIdentifier\YourBundle\Entity\Category;
use YourIdentifier\YourBundle\Entity\Post;

class CategoryFixtures extends AbstractFixture implements OrderedFixtureInterface {

    public function load(ObjectManager $manager) {
        // create 30 categories and add the post identified by reference 'post-21' to each
        for ($i = 0; $i < 30; $i++) {
            $category = new Category();
            $category->setName(getRandCatTitle());
            $category->addPost($manager->merge($this->getReference('post-21')));
            $manager->persist($category);
        }

        // create one more category and add the posts from referennce 'post-0' to 'post-20' to it
        $category = new Category();
        $category->setName('TEST');
        for ($i = 0; $i < 20; $i++) {
            $category->addPost($manager->merge($this->getReference('post-' . $i)));
            $manager->persist($category);
        }
        $manager->flush();
    }

    public function getOrder() {
        // we use a post reference, so load these fixtures after the post fixtures
        return 2;
    }
}
?>

To load these fixtures, we have to enable fixtures in symfony2 first. To do this just follow these instructions.

If you now run the command doctrine:fixtures:load   the category and post tables should be populated.

Finishing Up

If you did not already do so add your database information in the parameters.ini (/app/config/parameters.ini or your own if you included one in the main config.yml).

Then run

doctrine:database:create

to create the database (if you did not already do that by hand).

Afterwards run

doctrine:schema:create

to create the necessary tables for the entities we defined above.

Now either create all needed getters and setters by hand or call:

doctrine:generate:entities YourIdentifier/YourBundle

which will generate the needed getters and setters for you (it will not delete existing getters, setters or any other methods).

Everything should be working now, so go visit some of the routes we set up in the controllers (e.g. http://localhost/your_identifier/web/app_dev.php/admin/ and then use the links). If you do get any errors, please feel free to comment below (with the exact error message please).