Buy

Databases and Doctrine

Symfony doesn’t care about your database or the code you use to talk to it. Seriously. It’s not trying to be rude, but other libraries already solve this problem. So if you want to make a :phpclass:PDO connection and run raw SQL queries, that’s great! When we create services in Episode 3, you’ll learn some life-saving strategies to organize something like this.

But most people that use Symfony use a third-party library called Doctrine. It has its own website and documentation, though Symfony’s Doctrine documentation is a lot friendlier.

In a nutshell, Doctrine maps rows and columns in your database to objects and properties in PHP. Imagine we have an Event object with name and location properties. If we tell Doctrine to save this object, it inserts a row into a table and puts the data on name and location columns. And when we query for the event, it puts the column data back onto the properties of an Event object.

The big confusing mind-switch is to stop thinking about tables and start thinking about PHP classes.

Creating the Event Entity Class

In fact, let’s create the Event class we were talking about. The console can even make this for us with the doctrine:generate:entity command:

$ php app/console doctrine:generate:entity

Like other commands, this one is self-aware and will start asking you questions. In step 1, enter EventBundle:Event. This is another top-secret shortcut name and it means you want the Event class to live inside the EventBundle.

Now, choose annotation as the configuration format and move on to field creation. Add the following fields:

  • name as a string field;
  • time as a datetime field;
  • location as a string field;
  • and details as a text field.

These types here are configuration that tell Doctrine how each property should be stored in the database.

If you messed anything up, panic! Or just exit with ctrl+c try the command again. Nothing happens until it finishes.

Note

All of the Doctrine data types are explained in their documentation: Doctrine Mapping Types.

Say “yes” for the repository class and confirm generation. A repository is a cool guy we’ll use later to store custom queries.

What just Happened?

Ok! So what did that do? Actually, it just created 2 new classes in an Entity directory in our bundle. And that’s it.

Check out the new Event class:

// src/Yoda/EventBundle/Entity/Event.php
namespace Yoda\EventBundle\Entity;

/**
 * @ORM\Table()
 * @ORM\Entity(repositoryClass="Yoda\EventBundle\Entity\EventRepository")
 */
class Event
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

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

    // ...

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

    public function setName($name)
    {
        $this->name = $name;
    }

    // ...
}

For Doctrine, the word “entity” means a normal PHP class that we will save to the database. So, whenever I say “entity”, just scream: “that’s just a normal PHP class!”. Your co-workers will love you!

If you ignore the PHP comments, you’ll see that this is a plain old PHP class. It doesn’t do anything: it just stores data on its private properties. Getter and setter methods - like getName() and setName() - were generated so we can play with an event’s data. It’s underwhelming, almost disappointing, and that’s what makes Doctrine so interesting.

Now, check out the PHP comments above the class. These comments are called “annotations”, and they’re actually read and parsed by Doctrine. So when you hear “annotations”, shout “PHP comments that are read like configuration!”.

These tell Doctrine how it should save an Event object to the database. Right now, they will save to an event table and each property will be a column in that table. I usually like to prefix all of my table names, so let’s do that by adding a name option to the Table annotation:

/**
 * @ORM\Table(name="yoda_event")
 * @ORM\Entity(repositoryClass="Yoda\EventBundle\Entity\EventRepository")
 */
class Event
{
    // ...
}

Creating the “play” Script

We’re ready to insert data, but first I want to show you a debugging trick. First, copy the web/app_dev.php file to the root of the project and rename it to play.php:

$ cp web/app_dev.php play.php

Open it up and remove the IP protection stuff at the top and update the require paths since we moved things around:

// play.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;
umask(0000);

$loader = require_once __DIR__.'/app/bootstrap.php.cache';
Debug::enable();

require_once __DIR__.'/app/AppKernel.php';
// ...

This script boots Symfony, processes the request, and spits out the page. But I have evil plans to transform it into a debugging monster where we can write random code and execute it from the command line to see what happens.

Replace the last three lines with $kernel->boot():

// ...
require_once __DIR__.'/app/AppKernel.php';

$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
$kernel->boot();

Remember the service container from earlier? We have access to it here. To make it as flexible as possible, I’ll add a few lines that help fake a real request. This is a little jedi mind trick so don’t worry about what these do right now:

// play.php
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Debug\Debug;
umask(0000);

$loader = require_once __DIR__.'/app/bootstrap.php.cache';
Debug::enable();

require_once __DIR__.'/app/AppKernel.php';

$kernel = new AppKernel('dev', true);
$kernel->loadClassCache();
$request = Request::createFromGlobals();
$kernel->boot();

$container = $kernel->getContainer();
$container->enterScope('request');
$container->set('request', $request);

// all our setup is done!!!!!!

Our evil creation is alive! So let’s play around. How could we render a template here? Why, just by grabbing the templating service and using its render() method:

// ...
// all our setup is done!!!!!!
$templating = $container->get('templating');

echo $templating->render(
    'EventBundle:Default:index.html.twig',
    array(
        'name' => 'Yoda',
        'count' => 5,
    )
);

Execute the play script from the command line.

$ php play.php

When I run it, the template is rendered and printed out. How cool is that? This is perfect for whenever we need to quickly test out some code.

Leave a comment!

  • 2016-09-19 weaverryan

    That's what we do on the newer tutorials, except for the beginner PHP or OO tutorials. But, I'm happy that this was a surprise - that's the point!

    Cheers!

  • 2016-09-19 Maksym Minenko

    Wow, what a surprise... And I'm definitely not sure it's the right way. I believe the best tutorials are recorded by the authors themselves.

  • 2016-09-19 weaverryan

    Ah yes, voiced by Leanna, written by me :)

  • 2016-09-19 Maksym Minenko

    Well, ok, but the chapters I commented on were by Leanna...

  • 2016-09-19 weaverryan

    Hey Maksym!

    Hmm, sorry you're disappointed! If I do something different than the "norm", it's usually to show something in the easier, more "pure" way first, and then later we build on the complexity (instead if building the "normal", but more complex thing first). It's definitely not my intention to be extra fancy - quite the opposite!

    And there's a bit of history to this: I work on the Symfony core, and during much of Symfony2, sometimes we (the community) made things too hard. At times, I'll make things simpler in the tutorials because of this. Fortunately, over time, that's changed - a lot of easy-win simplifications have been adopted in Symfony 3 (really, many were adopted towards the end of Symfony2), and now I go "off-script" quite a bit less.

    Anyways, I hope that gives you a bit of background! And if you have any questions about why I did something, or if you find anything specifically confusing, just ask and we'll be happy to have a conversation about that!

    Cheers!

  • 2016-09-19 Maksym Minenko

    Quite disappointing actually, frankly speaking... :( "Let's change that, we don't need this, let's create play.php"...
    Come on! The framework is quite overwhelming as it is, so go with the default flow first, explain everything and only then (possibly) try to introduce some "tricks".
    I hope your Symfony 3 tutorials are better.

  • 2016-09-05 weaverryan

    Woohoooo! Now keep rocking!

  • 2016-09-03 Max

    Hey Ryan!

    It worked using the /app/autoload.php require statement! Awesome! :D Thank you so much for your quick and detailed reply!

    Cheers!
    Max

  • 2016-09-02 weaverryan

    Hi Max!

    Awesome - so glad you're finding the tutorials useful! :D

    Now, about your error. It's interesting... the class you mentioned DOES exist in Symfony 2.8 (https://github.com/symfony/sym.... So, the problem is related to autoloading somehow. Which file are you requiring on top - app/autoload.php or app/bootstrap.php.cache? There's a subtle difference between the two starting in Symfony 2.8 (we tweaked some directory structure things in Symfony). If in doubt, copy the web/app_dev.php file from *your* project as your starting point for the play.php file (instead of making your copy look exactly like mine).

    If you're still having trouble, just post your full play.php file - and also, post the "autoload" section of your composer.json - and we'll work it out :).

    Cheers!

  • 2016-09-02 Max

    Hey there!
    First of all thank you so much for these amazing tutorials! :) I really fell in love with them!

    running the play.php with symfony 2.8 I get the following:

    PHP Fatal error: Class 'Symfony\Component\Debug\Debug' not found in /Users/maxschons/Sites/knpuniversity2/play.php on line 9
    PHP Stack trace:
    PHP 1. {main}() /Users/maxschons/Sites/knpuniversity2/play.php:0

    Fatal error: Class 'Symfony\Component\Debug\Debug' not found in /Users/maxschons/Sites/knpuniversity2/play.php on line 9

    Call Stack:
    0.0016 229856 1. {main}() /Users/maxschons/Sites/knpuniversity2/play.php:0

    Any idea? Thx!

  • 2016-06-14 JLChafardet

    Yeah, i guessed as much, it makes sense, keeping things organized allow for better understanding and easier scalability.
    cheers mate! thanks for taking the time to answer :D

  • 2016-06-14 weaverryan

    Hey JLChafardet! Welcome - late, but still here ;).

    About the "Repository" directory itself, I like this because the only other alternative that people use is to put their *Repository classes into the Entity directory (and then it's a mixture of entities and repositories). But, no deeper meaning :). If you're asking why I like organizing things into repository classes in general, it's because I *love* having 100% of my database queries in 1 spot - it makes future database changes and re-using query logic really easy.

    Anyways - I hope I hit on your question. Cheers!

  • 2016-06-13 JLChafardet

    yo weaverryan thanks for the effort of the screencast, im a tad late but hey! im here! lol.

    I have a question for you, regarding the Repository directory, in regards to structure, etc, why do you like it so? any specific reasons other than just "organization" of your code? or its there a deeper meaning to it?

  • 2015-09-08 Justice Sommer

    I was running MySQL via XAMPP. I manually added a DB named "symfony". then ran the command via the command line again. That seems to have fixed it.

  • 2015-09-08 weaverryan

    Hey Justice!

    Your database doesn't exist yet - so don't forget to have Symfony create it for you :). This is explained in the next chapter: https://knpuniversity.com/scre...

    Cheers!

  • 2015-09-08 Justice Sommer

    after entering the Entity shortcut name I get...

    [Doctrine\DBAL\Exception\ConnectionException]
    An exception occured in driver: SQLSTATE[HY000] [1049] Unknown database 'symfony'

  • 2015-08-04 Shairyar Baig

    Very helpful tutorial

  • 2015-01-29 Diego Aguiar

    I did what you said and it worked!

    I just added a "generateId" method to my repository and made my listener to call it on PrePersist event

    Thanks for your time :D

  • 2015-01-29 weaverryan

    Hmm, you know, I'm not sure :). But I would try it! Remove the `$id` (the normal one that Doctrine adds), then add a property for your varchar one that exists, and give it @ORM\Column and @ORM\Id, but don't give it @ORM\GeneratedValue. I think in theory that should work. Doctrine needs (I believe) someething to be the @ORM\Id of the entity, but really, it doesn't care if it's an integer or auto-increment. Not having auto-increment causes other problems of course: specifically you need to manually set this before saving.

    Let me know how it goes!

  • 2015-01-28 Diego Aguiar

    Hey there!

    I have a question about entities id's column

    I already have an users table in my project and it's ID is not an "AUTO_INCREMENT" id, it is a custom varchar ID

    Is it possible to change the auto generated ID column of Doctrine to this one ?

    Thanks for your time! :]