A Hands-On Introduction to Doctrine
Hi, I'm Chris from code review videos.com, and in this video series we're going to be taking a hands-on approach to Doctrine inside a Symfony application. So you don't need to use Doctrine, but it is the easiest way out of the box using Symfony to interact with your database. There's a heck of a lot of terminology around Doctrine, like migrations and hydration, annotations, DQL, ORM stuff, flat-out persistence. There's so many terms that honestly, if you sit there and you try and learn all the terms before you've really given the sort of context to what you're learning, I find it a lot easier to learn what those terms mean after you've seen Doctrine in action. So this is really going to be a hands-on tutorial series. So let's get started with some hands-on.
Exploring Doctrine's Configuration Files
So inside our Symphony 3 application, we have an app folder, then we have a config folder, which is where all our config is stored. And in here there's really three files that we're interested in when we're talking about Doctrine. So there's a config file and the associated environments around that.
And then inside the config, we have the Doctrine configuration. So you can see there's some stuff in here which probably doesn't make a huge amount of sense, dbal, etc. And yeah, you don't really need to know too much about the terminology actually to start working with this database abstraction layer, object relational mapping.
Understanding parameters.yml
So yeah, as I say, you don't really need to know too much about them, but just be aware that that's where the settings live. But you can see here, they have these funny percentage variable type syntax going on. And what these are are parameters. So that's where the parameters file comes in. We put our parameters in here, and then at runtime, they're going to get put inside the right places so that the application works as expected. So the first thing that we really need to do is add in our own database host, database port, and whatnot.
The Role of parameters.yml.dist
If we go to parameters.yml, you can see that those parameters are already set up for us, but of course they're using some fake values. And you may be wondering at this stage, well, what's the purpose of parameters.yml.dist? So this file is the distribution version of parameters, which means to say if you were working on a big project and you was wanting to share your code with a colleague or other people in your team, when you run the composer install, if you were given this project to a colleague, they pulled down the repository from say GitHub or wherever, and then they run the composer install. At the end of that setting, the end of the installation steps, it's going to generate their parameters.yml file from the parameters.yml.dist. So in here, you tend to put all the values, all the parameters that you need, but you might set up a fake value, and then what it's going to do is when it's installing it, it's going to give them the default value that you've specified.
Keeping Configuration in Sync
So let's say your database name is going to be Symfony, but it will allow you to override that. So that's what this file is doing. You need to make sure that you keep it in sync with your parameters file. It won't break, actually, which is a bit weird, but it won't break things when people try and do a composer install on your project. It just comes around when they try and start using the application that they might be missing a few parameters. So yeah, it's just one thing to be aware of. I'll keep the dist file in sync with the parameters file. So if you add a parameter in here, I don't know, datable_db_chris_db whatever, then make sure that you also add it in here, db_chris with some fake value that you want to give them as like a default. That's the gist of the dist file. So sorry, took off a little bit of a tangent there, but it's important to know about that because it's just one of the things that otherwise you're probably left with a question.
Creating the Database
Anyway, get rid of that. And so the gist of this is I've already got a database set up on my local machine where my server's going to be running. So my username is db_user. Now my database name is not db_user, my database name can actually be anything at this point because the user that I'm using, the database user, is going to have permission to create any database. Yours might not be like that, depends on how restrictive your setup is. And it's just db_user and db_password. I'll give my database the name of doctrine_example. And what that's going to do is then allow me to go across to the server. So this is the server, this is the local, this is my laptop effectively, this is on the server. I'm just in the directory where my server lives and then I can do a php bin/console doctrine:database:create.
And it's gone ahead there and created me the database that I've just specified inside my parameters.yml file. So if I go across to that database now and connect again just using db_user and db_password, then you can see I've got my doctrine_example. I also create a dev_db, that's just part of my service setup script. So those things get created for me automatically. If you're interested in that, it's a little bit more of an advanced topic. It's using a program called Ansible for doing my builds, and I'll link to that in the show notes.
What is a Doctrine Entity?
Now the next problem, I suppose, is that we don't actually have any tables. We've just told Symfony to create us a database, or Doctrine to create us a database called doctrine_example, but we don't have any data in there. So I'm going at this with the sort of the data-first approach. You can also come at it where you've already got your tables and you want to map entities to them.
So just to very quickly cover off what I'm going to talk about quite heavily in this series: you've got the concept of an entity in Doctrine, which isn't a one-to-one mapping with your database tables, which is to say that one entity can represent data that comes from multiple tables at the same time. So it's like one, if you think about it in terms of like the old-school approach, you might pull out a row from SQL and, well, it doesn't quite correlate one-to-one in Doctrine, but for 90% of the time, it does.
So for the purposes of this video, I'm going to, whenever I say entity, I'm generally going to be talking about one row from a table inside our database. But that's not, it doesn't have to be like that, just to sort of make things a little bit more confusing. As I say, 90% of the time that's just the way it is. So if you think about the entity as being like an object that is created from a row in our table, that's the gist of what I'm trying to do.
Creating Your First Entity Class
And as I say, you can come at it where the data exists. So we might already have like 12 tables in our database and we want to make entities from those 12 tables, or we can come at it from this perspective where we don't have any data, and so we're going to go ahead and make up our own. And the way that we make up data, or at least sort of start to think about our data, is to work with something called an entity. And an entity, as I say, is really just thinking about it in terms of, like, it's a row in our database, and so we get to design what that row looks like.
I'm going to have the concept of reddit posts because later on in this series, I'm going to end up pulling down data from the Reddit API to sort of populate the database. Anyway, RedditPost.php, not posts, so singular. And it's going to say yes on that in there. And then I'm going to go ahead and create myself an entity. So I need to use namespaces, if I can type this morning. So we're in the AppBundle, and we're in the Entity directory. Good enough. And then I'm going to give it a class of RedditPost. Don't need to extend anything, so I'm just going to pop my open parens and whatnot.
Mapping the Entity to a Table
Now we're going to need something called an annotation, which I'll come to in a sec, which is why I just sort of instinctively did that, but we'll show you what I mean. Anyway, so we're going to go with protected fields, I suppose, because technically we could override this, but yeah, bear with us.
And then protected, so we'll just give it a title. We won't get too heavily involved in all the things that we could pull off our reddit posts or out of the API. So you've got the concept of, a post is going to have an ID because it's going to get saved off into the database and we need some way of referencing this particular post. That's not the ID from Reddit, that's just going to be our internal ID, in as much as any time that you've ever used a database and you need a table so that you need to be able to pull records out of it, you know, you've got to have some sort of ID field to be able to reference that table, that record. And that's really all that's going to be.
But of course, Doctrine isn't clever enough to figure out that this is supposed to be a table and we want it all mapped up. For that, we need to give it some additional help, and we do that in the form of, well, actually there's multiple ways of doing this, but the sort of the best practice way is to do it by annotations.
Understanding Annotations for Mapping
Now annotations themselves are a bit of a content topic. It's because they use comments to do code, which is maybe not the best sort of situation. So our annotations in this instance are going to look like this: @ORM. And this is part of the problem, honestly, as I'll explain as we go through. We're going to tell it that this is an entity, and we also want to tell it, @ORM, that this is a table, so that we're going to have a table that's based off the fields in this class. On our table, we can give it any name, and if you don't give it a name, it's just going to guess at the name, so it's going to call it reddit_post. Now, I like to give my table names the plural version, just as I find that just so it makes more sense inside my head, I guess. But again, you can just call it singular or however you want to call it.
Anyway, to make these annotations work, we actually need to use them. So we need to use Doctrine\ORM\Mapping as ORM, and that's what sort of correlates directly to that bit. And you need to make sure you use the @. As I say, the problem is with these, when you've sort of worked with them for a while and you've worked on larger projects, at some point, inevitably, you will miss out an @ or you'll make a typo or something. And because they're comments, very, very tricky to sort of spot, when especially as your classes grow, and it's very difficult to spot where you've made obvious mistakes. So there can be a little bit of a pain in that regard.
And you don't need to do it in this way. You can use YAML or whatever. But the thing is, if you use YAML, then you can end up in a situation where your definitions of what this is are in a completely different file to what your fields are. So you end up with two files that reference the same thing and this makes it a little bit more tricky to kind of work out what's going on, because you've got to keep two things in your head, or at least open two files to see one thing. Anyway, yeah, so that's why I think that annotations have become best practice. I think that's the reason, and that's personally how I find it, anyway. So yeah, you've got to add these annotations in.
Mapping Properties to Database Columns
And honestly, I mean, as you before, you saw like the first few, if you do, my advice is that you type them in. And but the reason I'm getting autocomplete here, by the way, is a PHPStorm plugin, which I will link to in the show notes. But yeah, I would have actually typed them in the first time, you know, the first 10, 20 times. After you've done that and you're at a point where you're sort of comfortable with them, then by all means just copy-paste them from either an existing entity that you've got in your project or from the documentation, which again, I will link to in the show notes. And then because of, it's always the same sort of thing that you type in. type= and I'm terrible, especially on a Mac with the keys, honestly, my fingers find the wrong keys all the time. Just I don't get enough topics on what, I don't develop on a Mac on a day-to-day basis, I develop on Linux, so yeah, I only record on the Mac because it's easier, but yeah, that's why we're always making typos in the videos.
Anyway, and so yeah, we've got this. This isn't a complete entity. We've not got any getters and setters on there at the moment. We'd actually need them to be able to use this entity or at least make a table from it. So let's go ahead and create ourselves a table from this entity.
Generating the Database Schema
Okay, so we need to do this from the server or wherever your database is or your Symfony project. I'm just going to clear this off. And I want to show you first if we do... make this a little bit bigger as well.
php bin/console and then just return on that. You can see in here we've got all these available commands. And even though it's a little bit difficult to see because, as you can see, one of them's dropped onto a second line, under the Doctrine tag, we've got all these different commands that are available to us. Now, fortunately, we don't need to know all of them. Honestly there's only a few that you'll really use quite frequently. In this instance, we're going to look at the doctrine:schema:update.
So let's give that a shot. And we could see that it's gone off. And I'll force it through, by the way, just to make sure it actually goes through. If you don't add the force, it's not going to do anything. It's just going to sort of dry run it. So let's see what we've got in our database now. We might just need to give it a refresh. Refresh tables. And so we got our reddit_posts and we got our ID and our title. You can see auto-increment is set at zero. If we look at the structure, then you can see this is our primary key, it's got auto-increment on it. And why is that all set up? Because that's what we put on our entity.