Buy

Inserting and Querying Data

We can use the play script to create and save our first Event object. Start by importing the Event class’s namespace:

// ...
// all our setup is done!!!!!!
use Yoda\EventBundle\Entity\Event;

Let’s flex our mad PHP skills and put some data on each property. Remember, this is just a normal PHP object, it doesn’t have any jedi magic and doesn’t know anything about a database:

use Yoda\EventBundle\Entity\Event;

$event = new Event();
$event->setName('Darth\'s surprise birthday party');
$event->setLocation('Deathstar');
$event->setTime(new \DateTime('tomorrow noon'));
$event->setDetails('Ha! Darth HATES surprises!!!!');

Let’s save this wild event! To do that, we use a special object called the “entity manager”. It’s basically the most important object in Doctrine and is in charge of saving objects and fetching them back out. To get the entity manager, first grab a service from the container called doctrine and call getManager on it:

$em = $container->get('doctrine')->getManager();

Saving is a two-step process: persist() and then flush():

$em = $container->get('doctrine')->getManager();
$em->persist($event);
$em->flush();

Two steps! Yea, and for an awesome reason. The first tells Doctrine “hey, you should know about this object”. But no queries are made yet. When we call flush(), Doctrine actually executes the INSERT query.

The awesome is that if you need to save a bunch of objects at once, you can persist each of them and call flush once. Doctrine will then pack these operations into as few queries as possible.

Now, when we execute our play script, it blows up!

PDOException: SQLSTATE[42000] [1049] Unknown database ‘symfony’

Scroll up to see the error message: “Unknown database symfony”. Duh! We skipped one important step: setting up the database config.

Configuring the Database

Database config is usually stored in app/config/parameters.yml. Change the database name to “yoda_event”. For my super-secure computer, the database user root with no password is perfect:

# app/config/parameters.yml
parameters:
    database_driver:   pdo_mysql
    database_host:     127.0.0.1
    database_port:     null
    database_name:     yoda_event
    database_user:     root
    database_password: null
    # ...

We can haz Database

Now, I know you’re super-smart and capable, but let’s be lazy again and use the console to create the database for us with the doctrine:database:create command:

$ php app/console doctrine:database:create

There’s also a command to drop the database. That’s great, until you realize that you just ran it on production... and everyone is running around with their hair on fire. Yea, so that’s a healthy reminder to not give your production db user access to drop the database.

A Table for Events, Please

We have a database, but no tables. Any ideas who might help us with this? Oh yeah, our friend console! Run the doctrine:schema:create command. This finds all your entities, reads their annotation mapping config, and creates all the tables:

$ php app/console doctrine:schema:create

Time to try out the play script again:

$ php play.php

What? No errors! Did it work? Use the doctrine:query:sql command to run a raw query against the database:

$ php app/console doctrine:query:sql "SELECT * FROM yoda_event"

And voila! There’s our event.

Making nullable Fields

Let’s get crazy and leave the details field blank:

// play.php
// ...
$event->setTime(new \DateTime('tomorrow noon'));
//$event->setDetails('Ha! Darth HATES surprises!!!!');

When we run the script, another explosion! Scrolling up, the error straight from MySQL saying that the details column can’t be null.

SQLSTATE[23000]: Integrity constraint violation: 1048 Column ‘details’ cannot be null

So Doctrine assumes by default that all of your columns should be set to NOT NULL when creating the table. To change this, add a nullable option to the details property inside the entity:

// src/Yoda/EventBundle/Entity/Event.php

/**
 * @ORM\Column(name="details", type="text", nullable=true)
 */
private $details;

// ...

Tip

Doctrine has a killer page that shows all of the annotations and their options. See Annotations Reference.

But before this does anything, the actual column in the database needs to be modified to reflect the change. Hey, console to the rescue! Run the doctrine:schema:update command:

$ php app/console doctrine:schema:update

This is pretty sweet: it looks at your annotations mapping config, compares it against the current state of the database, and figures out exactly what queries we need to run to update the database structure.

But the command didn’t do anything yet. Pass --dump-sql to see the queries it wants to run and --force to actually run them:

$ php app/console doctrine:schema:update --force

Run the play script again. Alright, no errors means that the new event is saved without a problem.

Querying for Objects

Putting stuff into the database is nice, but let’s learn how to get stuff out. Open up the DefaultController class we’ve been playing with. First, we need to get the all-important entity manager. That’s old news for us. Like before, just get the doctrine service from the container and call getManager on it.

This works, but since we’re extending the base controller, we can use its getDoctrine() to get the doctrine service. That’ll save us a few keystrokes:

// src/Yoda/EventBundle/Controller/DefaultController.php
// ...

public function indexAction($count, $firstName)
{
    // these 2 lines are equivalent
    // $em = $this->container->get('doctrine')->getManager();
    $em = $this->getDoctrine()->getManager();

    // ...
}

To query for something, we always first get an entity’s repository object:

public function indexAction($count, $firstName)
{
    // these 2 lines are equivalent
    // $em = $this->container->get('doctrine')->getManager();
    $em = $this->getDoctrine()->getManager();
    $repo = $em->getRepository('EventBundle:Event');

    // ...
}

A repository has just one job: to help query for one type of object, like Event objects. The EventBundle:Event string is the same top-secret shortcut syntax we used when we generated the entity - it’s like the entity’s nickname.

Tip

If you like typing, you can use the full class name anywhere the entity “alias” is used:

$em->getRepository(‘YodaEventBundleEntityEvent’);

Use the repository’s findOneBy method to get an Event object by name. There are other shortcut methods too, like findAll, findBy, and find:

// src/Yoda/EventBundle/Controller/DefaultController.php
// ...

public function indexAction($count, $firstName)
{
    // these 2 lines are equivalent
    // $em = $this->container->get('doctrine')->getManager();
    $em = $this->getDoctrine()->getManager();
    $repo = $em->getRepository('EventBundle:Event');

    $event = $repo->findOneBy(array(
        'name' => 'Darth\'s surprise birthday party',
    ));

    return $this->render(
        'EventBundle:Default:index.html.twig',
        array(
            'name' => $firstName,
            'count' => $count,
            'event'=> $event,
        )
    );
}

Tip

In Episode 2, we’ll add more methods to the repository and write some custom queries.

Rendering Entities in Twig

Ok - let’s pass the Event object into the template as a variable. We can use Twig’s render syntax to print out the name and location properties. Internally, Twig is smart enough to call getName and getLocation, since the properties are private:

{% block body %}
    {# ... #}

    {{ event.name }}<br/>
    {{ event.location }}<br/>

{% endblock %}

Refresh the page! I can see our event data, so all the magic Doctrine querying must be working. Actually, check out out the web debug toolbar. The cute box icon jumped from zero to one, which is the number of queries used for the page. When we click the little boxes, we can even see what those queries are and even run EXPLAIN on them.

Good work young jedi! Seriously, you know the basics of Doctrine, and that’s not easy. In the next 2 episodes, we’ll create custom queries and use cool things like events that let you “hook” into Doctrine as entities are inserted, updated or removed from the database.

Oh, and don’t forget Doctrine has its own documentation, though the most helpful pages are the Annotations Reference and Doctrine Mapping Types reference pages. And by the way, when you see annotations in the Doctrine docs, prefix them with @ORM\ before putting them in Symfony. That’s because of this use statement above our entity:

// src/Yoda/EventBundle/Entity/Event.php
use Doctrine\ORM\Mapping as ORM;
// ..

If that’s in your class and you have @ORM\ at the start of all of your Doctrine annotations, you’re killing it.

Leave a comment!

  • 2016-10-25 Max

    You are a problem-solving-monster :D I had the plugin installed, though not activated... now everything runs smoothly!

  • 2016-10-24 weaverryan

    Hey Max!

    Ah, then 2 things:

    1) I repeat my advice from earlier about "Excluding" the directory - but instead, include app/cache. Symfony had app/cache in 2.x and var/cache in 3.x (they're the same thing, we just changed the dir name).

    2) Do you have the Symfony plugin installed (*and* enabled for this project)? https://knpuniversity.com/scre... Also make sure you have the "PHP Annotations" plugin

    Lemme know if either of these help :)

  • 2016-10-24 Max

    Nope, I'm running symfony 2.8 and sadly there is no var/cache directory...

    $em=$this->getDoctrine()->getManager(); works fine and I get autocompletion for every step.
    $em2=$this->container->get('doctrine')->getManager(); stops the autocompletion after get('doctrine').

  • 2016-10-24 weaverryan

    Hey Max!

    Hmm. Assuming you're on Symfony 3, make sure you right click on the var/cache directory and "Mark Directory As" -> "Excluded". We do it at about the 2:00 mark in this video: http://knpuniversity.com/scree...

    Lemme know if that works! The cache directory holds some duplicate Symfony files (for performance reasons) but this can confuse PhpStorm. If that is *not* the problem/fix... lemme know!

    Cheers!

  • 2016-10-23 Max

    The autocompletion for ->getManager is not working in my phpstorm IDE though the code is executed without problems - am I missing something? Thanks!

  • 2015-10-08 Annemarieke van Maris

    Hey, I sort of fixed it just before I received your message. I reset everything to the first commit and followed the tutorial again, and now it works!

    I think I made some mistakes while trying to reset to my first commit earlier and some code may have been doubled or erased.

    Anyway, thanks for your swift reply. I'm really enjoying the course so far!

  • 2015-10-08 weaverryan

    Hey there!

    Hmm, so the class - Yoda\EventBundle\Entity\Event - looks good to me, so the likely cause is the autoloader. Do you still have the original autoload require line? https://github.com/symfony/sym... And is your code after this? If it *is* there, post your full file on a Gist and we can figure it out :).

    Cheers!

  • 2015-10-08 Annemarieke van Maris

    Hi!

    When I run the play.php in the cli, I get the following error message:

    Fatal error: Class 'Yoda\EventBundle\Entity\Event' not found in C:\wamp\www\starwarsevent\play.php on line 26

    Call Stack:

    0.0002 243240 1. {main}() C:\wamp\www\starwarsevent\play.php:0

    What can I do to fix this? I'm using Symfony 2.7 (newest).

  • 2015-08-18 Victor Bocharsky

    Thanks for your reply!

    The reason to avoid `nullable=true` for scalar types nice uncovered in http://stackoverflow.com/a/126... answer. I think `nullable=true` mainly should use for foreign keys, however in some cases it could applied for scalar types, but such cases not much.

    Also, I'm looking forward to a PHP7 stable release with its scalar type hinting and super performance :)

  • 2015-08-18 weaverryan

    Hey!

    I like it just fine! I don't usually type-hint, because Doctrine will eventually cast (for example) a string to an int. But technically, this is more strict - since your object will *always* (for example) have an int for a price, instead of a string. I'll probably start doing this more when scalar type-casting is available. But I like it just fine :).

    About nullable=false, I use this specifically if I want to guarantee that I don't mess something up and forget to set a field. For example, I might set nullable=false on price, because I *want* there to be a huge exception if I somehow create a Product and forget to set this fields - i.e. having a real value here is so important, the site doesn't really work without explicitly setting it. So I think both methods are valid: nullable = false to be crazy strict, and using default values when having a default value makes sense.

    What do you think?

  • 2015-08-17 Victor Bocharsky

    For a scalar types I don't like use `nullable=true` option. I prefer to set a default value on property definition and then in setter I use type casting to correct scalar type or add simple check like `isset($description) ? $description : 'Some default value'`. Please, check my entity example where `description` and `price` properties are optional:

    /**
    * Class Payment
    */
    class Payment
    {
    /**
    * @var string
    *
    * @ORM\Column(type="string")
    */
    private $name;

    /**
    * @var string
    *
    * @ORM\Column(type="text")
    */
    private $description = '';

    /**
    * @var int
    *
    * @ORM\Column(type="integer")
    */
    private $price = 0;

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

    public function setDescription($description)
    {
    $this->description = (string) $description;
    }

    public function setPrice($price)
    {
    $this->price = (int) $price;
    }
    }

    Ryan, what do you think about this way?

  • 2015-04-18 weaverryan

    Hi Alex!

    First, I think you're probably right about the missing pdo_mysql driver. This sounds like a classic case of having 2 PHP's installed on the system - the one from the command line has pdo_mysql installed, but the one used by your web server is using a different PHP that does not.

    Are you using the built-in PHP web server? If you are, then I'm wrong :). But if you aren't, let's try it. So, from the root of your project:

    php app/console server:run

    Then go to http://localhost:8000 in your browser. This will run your site, but use your cli PHP, which we're pretty sure has pdo_mysql installed. If this page loads, then the problem is with a different PHP being used by your web server. If you still get the error, then something else is going on.

    Let me know!

  • 2015-04-17 Alex

    Hi guys, thanks so much for the super helpful tutorials. Unfortunately I've hit an odd wall with a PDOException: "could not find driver", presumably for pdo_mysql. Ive confirmed that it is installed/enabled. I can access tables with php app/console doctrine:query:sql, and anything I run from the play.php script works fine. When I move on to running it in the browser, I get this driver error. I loaded phpinfo on a page and produced a path to my cli/php.ini. Double checked that and everything seems to be ok there. Any other thoughts? Thanks for your help.

  • 2014-11-23 Enkhbilguun E.

    [SOLVED]
    It looks my composer was using my php built-in with Mac OSX Yosemite while I was thinking it was running on MAMP.

    Pointed the path to the PHP in MAMP, and the installation has been completed smoothly. For those who may experience the same, have a look at: http://laravel.io/forum/05-02-...

  • 2014-11-23 Enkhbilguun E.

    I'm getting an exception and an error right after pressing enter button multiple times for parameters. I'm trying to install the latest version v2.5 with "composer create-project symfony/framework-standard-edition starwarevents @stable" command.

    What could I do?
    ==========
    Writing lock file
    Generating autoload files
    Would you like to install Acme demo bundle? [y/N]
    Creating the "app/config/parameters.yml" file
    Some parameters are missing. Please provide them.
    database_driver (pdo_mysql):
    database_host (127.0.0.1):
    database_port (null):
    database_name (symfony):
    database_user (root):
    database_password (null):
    mailer_transport (smtp):
    mailer_host (127.0.0.1):
    mailer_user (null):
    mailer_password (null):
    locale (en):
    secret (ThisTokenIsNotSoSecretChangeIt):
    debug_toolbar (true):
    debug_redirects (false):
    use_assetic_controller (true):

    [Symfony\Component\Debug\Exception\ContextErrorException]

    Warning: date_default_timezone_get(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. in /Users/beregu/Dropbox/Sites/starwarevents/vendor/monolog/monolog/src/Monolog/Logger.php line 233

    cache:clear [--no-warmup] [--no-optional-warmers]

    Script Sensio\Bundle\DistributionBundle\Composer\ScriptHandler::clearCache handling the post-install-cmd event terminated with an exception

    [RuntimeException]

    An error occurred when executing the "'cache:clear --no-warmup'" command.

    create-project [-s|--stability="..."] [--prefer-source] [--prefer-dist] [--repository-url="..."] [--dev] [--no-dev] [--no-plugins] [--no-custom-installers] [--no-scripts] [--no-progress] [--keep-vcs] [--no-install] [package] [directory] [version]

  • 2014-11-23 Enkhbilguun E.

    Thanks Ryan. That's exactly what I asked. :-)

  • 2014-11-18 weaverryan

    Hey Enkhbilguun!

    My pleasure! If you want to use Twig and Bootstrap together, that really has (almost) nothing to do with Symfony - just include the CSS file in your base template and start using the markup. The only place where they integrate is when you're rendering forms. If you're asking about that, it *can* get quite tricky. Fortunately, Symfony 2.6 comes with a Twitter Bootstrap form theme out of the box. You can read about it here: https://github.com/symfony/sym.... If you are not using Symfony 2.6 (it's not even out yet), you can just copy the form theme into your project and use it as a form theme as if you had created it yourself. The source file is here: https://github.com/symfony/sym...

    I hope that helps!

  • 2014-11-18 Enkhbilguun E.

    Hi Ryan,

    Could you please instruct us how to use Twig with Bootstrap CSS framework in Symfony2?

    P.S. updated tutorials are fantastic, and thanks for the free all access. :-)