Installing Doctrine

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

Well hey friends! And bienvenidos to our tutorial about learning Spanish! What? That's next week? Doctrine?

Ah: welcome to our tutorial all about making Symfony talk to a database... in English.

We learned a ton in the first two courses of this series, especially the last tutorial where we demystified services, autowiring and configuration. That hard work is about to pay off as we take our app to the next level by adding a database. That's going to make things way, way more interesting.

Who is Doctrine Exactly?

In truth, Symfony has no database layer at all. Instead, it leverages another library called Doctrine, which has been around for a long time and is incredible. Symfony and Doctrine are, sort of, the BFF's of programming, the Batman and Robin of web development, the Bert & Ernie of HTTP! They're both powerful, but they have such a strong integration that it feels like you're using one library.

And not only is Doctrine powerful, but it's also easy to use. I'll admit that this was not always the case. But Doctrine is now more accessible and fun to use than ever before. I think you're going to love it.

Project Setup

To learn the most about Doctrine - and to become the third amigo - you should definitely code along with me by downloading the course code from this page. After you unzip the file, you'll find a start/ directory with the same code that you see here. Check out this README.md file for all the setup fun!

The last step will be to open a terminal and use the Symfony binary to start a local web server - you can download the binary at https://symfony.com/download. Run:

symfony serve -d

This starts a web server in the background on port 8000. I'll copy the URL, spin over to my browser and say hello to... Cauldron Overflow! Our question and answer site for witches and wizards: a place to debug what went wrong when you tried to make your cat invisible and instead made your car invisible.

So far, we have a homepage that lists questions and you can view each individual question and its answers. But... this is all hardcoded! None of this is coming from a database... yet. That is our job.

Installing Doctrine

Now, remember: Symfony starts small: it does not come with every feature and library that you might ever need. And so, Doctrine is not installed yet.

To get it, find your terminal and run:

composer require orm

Auto-Unpacked Packs

Let's... "unpack" this command!

First, orm is one of those Symfony Flex aliases. We only need to say composer require orm but, in reality, this is a shortcut for a library called symfony/orm-pack.

Also, we talked about "packs" in a previous course. A pack is a, sort of, fake package that exists simply to help you install several other packages.

Let me show you: copy the name of the package, and go open it in GitHub: https://github.com/symfony/orm-pack. Yep! It's nothing more than a single composer.json file! The whole point of this library is that it requires a few other packages. That means that we can composer require this one package, but in reality, we will get all four of these libraries.

Now, one of the other packages that we have in our project is symfony/flex, which is what powers the alias and recipe systems. Starting in symfony/flex version 1.9 - which I am using in this project - when you install a pack, Flex does something special.

Go and look at your composer.json file. What you would expect to see is one new line for symfony/orm-pack: the one library that we just required. In reality, Composer would also download its 4 dependencies... but only the pack would show up here. But... surprise! Instead of symfony/orm-pack, the 4 packages it requires are here instead!

84 lines composer.json
{
... lines 2 - 3
"require": {
... lines 5 - 7
"composer/package-versions-deprecated": "^1.8",
"doctrine/doctrine-bundle": "^2.1",
"doctrine/doctrine-migrations-bundle": "^3.0",
"doctrine/orm": "^2.7",
... lines 12 - 26
},
... lines 28 - 82
}

Here's the deal: before symfony/flex 1.9, when you required a pack, nothing special happened: Composer added the one new package to composer.json. But starting in symfony/flex 1.9, instead of adding the pack, it adds the individual libraries that the pack requires: these 4 lines. It does this because it makes it much easier for us to manage the versions of each package independently.

The point is: a pack is nothing more than a shortcut to install several packages. And in the latest version of Flex, it adds those "several" packages to your composer.json file automatically to make life easier.

DoctrineBundle Recipe & DATABASE_URL

Anyways, if we scroll down... you can ignore this zend-framework abandoned warning. That's a distant dependency and it won't cause us problems. And... ah! It looks like this installed two recipes... and one of those gives us a nice set of instructions at the bottom. We'll learn all about this.

To see what the recipes did, I'll clear my screen and say:

git status

Ok: in addition to the normal files that we expect to be modified, the recipe also modified .env and created some new files.

Go check out .env. At the bottom... here it is: it added a new DATABASE_URL. This is the environment variable that Doctrine uses to connect to the database.

30 lines .env
... lines 1 - 22
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8"
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7
###

And... we can see this! The recipe also added another file called config/packages/doctrine.yaml

This file is responsible for configuring DoctrineBundle. And you can actually see that this doctrine.dbal.url key points to the environment variable! We won't need to do much work in this file, but I wanted you to see that the environment variable is passed to the bundle.

doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '5.7'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
type: annotation
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App

The recipe also added a few directories src/Entity/, src/Repository/, and migrations/, which we'll talk about soon.

So all we need to do to start working with Doctrine is configure this DATABASE_URL environment variable to point to a database that we have running somewhere.

To do that, we're going to do something special in this tutorial. Instead of telling you to install MySQL locally, we're going to use Docker. If you already use Docker, great! But if you haven't used Docker... or you tried it and didn't like it, give me just a few minutes to convince you - I think you're going to love how Symfony integrates with Docker. That's next!

Leave a comment!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.2.5",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.8", // 1.8.2
        "doctrine/doctrine-bundle": "^2.1", // 2.1.0
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.1
        "doctrine/orm": "^2.7", // v2.7.3
        "knplabs/knp-markdown-bundle": "^1.8", // 1.8.1
        "knplabs/knp-time-bundle": "^1.11", // v1.12.0
        "sensio/framework-extra-bundle": "^5.5", // v5.6.1
        "sentry/sentry-symfony": "^3.4", // 3.5.2
        "stof/doctrine-extensions-bundle": "^1.4", // v1.4.0
        "symfony/asset": "5.1.*", // v5.1.2
        "symfony/console": "5.1.*", // v5.1.2
        "symfony/dotenv": "5.1.*", // v5.1.2
        "symfony/flex": "^1.3.1", // v1.9.0
        "symfony/framework-bundle": "5.1.*", // v5.1.2
        "symfony/monolog-bundle": "^3.0", // v3.5.0
        "symfony/stopwatch": "5.1.*", // v5.1.2
        "symfony/twig-bundle": "5.1.*", // v5.1.2
        "symfony/webpack-encore-bundle": "^1.7", // v1.7.3
        "symfony/yaml": "5.1.*", // v5.1.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.0.4
        "twig/twig": "^2.12|^3.0" // v3.0.4
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.3.1
        "symfony/debug-bundle": "5.1.*", // v5.1.2
        "symfony/maker-bundle": "^1.15", // v1.20.0
        "symfony/var-dumper": "5.1.*", // v5.1.2
        "symfony/web-profiler-bundle": "5.1.*", // v5.1.2
        "zenstruck/foundry": "^1.1" // v1.1.0
    }
}