Entities, Doctrine and the Symfony ORM basics
What's up developers and welcome back to a new video where we will dive into entities in Symfony. Quick pause: do you want to support the channel and want me to continue creating content? You can support the channel on Patreon right now where you get benefits just as a private Discord group where you can share your coding issues and other developers will help you out. If you are interested to join the link will be in the description down below.
Up until this point we only worked with static data. Symfony provides a suite of tools for interacting with your application's database. It uses an object relational mapper called Doctrine. Like I mentioned in the previous video, Doctrine is probably one of the most influential features of Symfony because it makes, together with Eloquent, a major difference of PHP frameworks. The main reason that Symfony uses Doctrine is because of its simplicity. Doctrine treats PHP classes and objects like they are tables and records. Therefore we don't need to write SQL queries anymore for create, read, update and delete actions.
You will be hearing the term entity classes a lot when working with Symfony and it's actually pretty simple to get a good understanding of an entity class. If we think about our basic PHP project you've got the MVC layer where the model layer will interact with the tables in your database. With Doctrine it's a little bit different. The MVC part is still the same but you don't call the tables in your database anymore, but you will call entities.
Since we've already set up our database in the previous video we can create our first table or better to say our first entity. We're not going to do this manually inside an interface but just like creating our database Doctrine has a command for that inside the CLI. Let's perform the Symfony console list Doctrine commands. Now let me make the terminal a little bit bigger.
Now even though we're going to do something with Doctrine the entity command will not be visible right here. All we do need to do is to perform the Symfony console make command because we're going to make something. Now what we're going to make starts with the letter e so let's scroll up and right here we're going to make an entity which will create or update a Doctrine entity class and optionally an API platform resource. So let's perform it inside the CLI: let's say Symfony console make:entity. Now you could add the name of your entity as an optional argument but if we hit enter right now it does recognize that we haven't added an entity name so it's making sure that we do that before we can proceed. Entities are the same as models so let's add a singular name of Movie right here. If we hit enter you will see that it has created two files for us. The first one is stored inside the src folder entity folder with a file called Movie.php, but it has also created a Doctrine repository inside the src folder repository folder called MovieRepository.php.
Let's focus on the entity class first before we continue on with the repository. It has also prompted us with another question because we need to add property names right here. Property names will basically be the column name in SQL. By default Doctrine will automatically add the ID for every single entity. Now what does a movie have? Well probably something like a title right, so let's write it down right here.
Next up we need to define the field type or better to say the data type. Now for our title we're simply going to work with a string. Then we need to add the field length, so the amount of characters, and let's keep it 255. Let's hit enter. And the next one is actually pretty important because it's asking us if it can be nullable or not. In our case it can't since it's the title of the movie and that's what the movie is all about, so you can't create a movie without a title. So can it be nullable? No it cannot. Just like the default types it's adding a value of no right here after the question which is the default answer. So if you remove your no answer and hit enter it will define no. I personally like to add it so let's write down no and hit enter.
Now it just told us that it has updated src/entity/Movie.php file and that was it; actually those were the default questions for a property. Now it's also asking us if we want to add more properties and yes we actually do. What's the next one? Let's say something like releaseYear. I prefer to add it in camelCase rather than underscore and I'll show you later on why. Let's hit enter. Now the field type of a releaseYear will be an integer and it cannot be nullable inside a database so let's add a no. Now we're also going to add one more which is the description. It has a field type of string with a total of 255 characters and our description can be nullable so let's write down yes.
Now the last one is our imagePath to save the image banner of our movie. The field type is a string because we're going to add the path of our image, then the field length can be 255 again and it cannot be nullable. All right that was it for our entity. Now in order to exit it we need to hit the Control+C button on our keyboard to interrupt it. Let me make everything smaller again, all right, terminal as well, and let me close off the database that I have right here like I've showed you a couple times before.
The Movie class has been stored inside the src/entity folder where it has created a Movie.php file. Let's open it. Right above our class right here you will see an annotation which will link the repository class. So right here with a MovieRepository::class. What this annotation is doing is telling our ORM that the MovieRepository class is linked to the Movie entity where we're in right now.
Now the class makes sense which is called Movie. If we scroll down you'll see some pretty straightforward things right: we got a couple private properties: id, title, releaseYear and description followed with the imagePath. Every property has its own annotation as you could see right here which is basically metadata for Doctrine. It's simply saying that the type is a string with a length of 255 characters. Description has a nullable as well which is true which has been added here as well. What this allows Doctrine to do is mapping the classes to the right table.
If we scroll down you'll see that every property has its own getters and setters as well right here. We have the setTitle, getReleaseYear which should make sense as well because it's a common thing in object oriented PHP. There's one thing that blows my mind: since this is pure PHP and we don't see anything that's new to us, what is Doctrine syntax? Doctrine will come into play later on and not right here.
Now let's scroll up a little bit to the id property that we have right here and inside the annotations you can see that ORM is calling a GeneratedValue strategy right here. What this will do is generating a unique id for us. We could actually migrate our entity right now and create a table inside a movie table with the name of movie, but I want to take it a step further since this is actually pretty simple. A big advantage that ORMs have over basic databases is the fact that you can link entities. When linking entities you can pretty much define relationships between tables.
Let's think about one: we've got our Entity Movie but we haven't defined actors anywhere in our entity. So how would you do that? Would you add a column name called actor where you store an actor name every single time for a movie, or would you prefer to work with a pivot table? Well that's actually my recommendation, so let's define a new entity first. Let's make the terminal a little bit bigger; inside the CLI let's write down Symfony console make:entity called Actor. All right let's hit enter. It created an entity and repository for our Actor. Now we're going to keep it simple by creating one property called name. The field type is a string, it has 255 characters and it cannot be nullable. Now let's press Control+C to exit our CLI.
Whenever you want to define a relationship between different tables you have to define the first entity again. So let's say Symfony console make:entity called Movie. Let's hit enter. All right it's telling us that the entity already exists and we can add a new property. So let's do that. What we want to do is to add a new property called actors right here which will be the new property. Now let's take a minute and talk about the fact that I've added actors in plural right here. There are multiple relationships available in Symfony and I will create a separate video where I talk about them, but what is the relationship between a movie and an actor? Well one movie can have multiple actors, right, but one actor has also starred in multiple movies. So therefore I recommend using plural right here. Let's hit enter.
For the field type we're not going to define a data type so a string, integer or whatever, but we need to define the relationship. In our case we're talking about a many-to-many relationship. Let's hit enter. It's also asking us what the entity should be related to. Now what we want to do is to use the Actor class which should be in PascalCase. Let's hit enter. Now it's also asking us if you want to add a new property so you can access or update the Movie object from the Actor, so the other way around. In our case let's write down yes. Now do we want to add a new field name inside our Actor movie? Actually not, so let's press Control+C to exit it.
As you can see inside our entity folder it has created an Actor.php file. So let's open it. Now our Actor entity should have two properties which is the private id that has been added by default and the private name that we have created. But as you can see it also has a third one which is the private movies property. Inside the annotations you will see that it has a many-to-many relationship defined. It has a targetEntity class of Movie and it has been mappedBy actors. We also have a constructor defined. Just a quick reminder: a constructor will be executed once the class is being called. So right here it will set the movies property — so this movies which we're referring to the private movies above — to a new ArrayCollection.
If we scroll a bit more down you will see a couple methods that we haven't seen before. The first one is the getMovies which will get all movies from the Actor entity. We have the public function addMovie which is accepting a Movie object as a parameter and then it says :self. Self is probably something that you haven't seen before and it basically refers to the current class. This class is trying to enforce that the returned instance is of the same class. Now what is going on inside of it? It's going to check if the movies array is not containing the Movie and if it doesn't add the Movie right here inside the movies array. Finally it will also add the Actor to the Movie. Down below you will find the removeMovie method.
When we defined our entity we said that whenever a movie gets deleted the actor that is related should be deleted as well. This is a pretty clean way of keeping your database clean. Now that we have generated our first two entities we should be able to see it inside the database right. If we navigate back to my local database and refresh it and open the movies database table you'll actually don't see the new tables called movie and actor. That's happening because an entity needs to be migrated. Obviously Doctrine is smart enough to understand that we need to migrate our entities so it has installed it with the ORM dependency that we added in the previous episode.
Now this can be done inside the CLI. Let's make the CLI a little bit bigger and let me zoom in. What we can do is say Symfony console make:migration. Once again inside the make section you'll find a command called make:migration. What this will do is creating a new migration based on database changes. So let's perform it: Symfony console make:migration. Let's hit enter and as you could see it has returned a success message. It has also created a new migration inside your migrations folder with a pretty difficult name attached to it. The version makes sense but what are all these numbers right here? The first four are the year — as you could see right here it's 2021 — then we got two numbers for the month which is 12 and we got 04 for the day then we have a couple other numbers which are for the hours, minutes and seconds attached to it.
So let's open the file. Let's navigate back to our explorer, let's open the migrations folder inside the root of our directory and let's open the migration. Now let's scroll down to the up method and one thing I really like right here is the fact that Doctrine understands that the Movie and Actor entity have both not been migrated so it basically adds the entire entity right here in SQL language. When a system is being migrated it will wrap the up methods from all migrations available. Right here currently we're working with one migration file so if we run our migration this specific file will be migrated. Now the migration system also allows you to roll back a migration. What this will do then is grabbing the down method right below of it and it will undo whatever has been done inside the up method. So it basically alters the table and it will drop the foreign key, it will drop the table Actor, Movie and the pivot table which is the movie_actor table right here.
Now we had another method right here at the top which was the getDescription method and this method is created for the nullable that we have set to yes. Now let's have a look at our migration one more time. Let me actually zoom out, now let me make this a little bit smaller.
All right now you might wonder why we're going to perform five queries. This is number one, two, three, four and five. We defined a relationship so it first needs to create a table for the two entities. So it will create a table for the Actor and it will create a table for the Movie. Symfony also understands that we need a pivot table which is the third one so we will create a table for the movie_actor. Right here we will store the movie_id which cannot be null and the actor_id which cannot be null as well. Now both ids, so the actor_id and the movie_id needs to come from somewhere which will be done in the last two queries. First one will alter the table movie_actor; it will add a foreign key of movie_id and it will reference id on the table movie and on delete it will cascade it meaning that it will delete the row once a movie has been deleted. The same thing needs to be done for the actor. It will tell us that the actor_id is a foreign key; it will reference on the actor table where it needs to find the id.
A foreign key is basically the column in a relational database table that provides a link between two tables. Now let me zoom in again. All right let me open the sidebar. Now inside the console you'll see that Symfony is already telling us what we need to do next. Instead of running the php bin/console command I prefer to use the Symfony console command but the second part of the command will stay the same. So we can say doctrine:migrations:migrate. Keep in mind that it will create a table which is singular so actor and movie right here. If you want to change it change it up, I'll just keep it as it is. So let's run the command. It's prompting us with a warning right here which is awesome because you will overwrite the tables if you have one defined. So do we want to continue? Of course because we don't have tables defined. Now let's hit enter. As you can see the up method has been migrated.
Every time you run this command it will check whether you have run all available migrations. If you haven't it will run the remaining ones so if we create a new migration right now it won't run the movie migration anymore since we've already done that inside the CLI. You can also see that five SQL queries have been performed which is correct because we have five inside the up method.
So let me open the database client again. I need to refresh it and if we scroll down you see our movies database with our actor table, our doctrine migration version, the movie table and the movie_actor table. So let's open the actor table right here. You'll see that we have an id and a name. If we open a movies table you'll find our id, title, release_year, description and image_path. I've mentioned that I will come back on the camelCase convention I performed on my properties. Remember if you look at the releaseYear and the imagePath you can tell that Doctrine stripped it with an underscore which is incredible work.
Finally we got our pivot table which is the movie_actor which will store two integers which is the movie_id and the actor_id. That being said this was it for this video where I've showed you how you could create entities, link entities and create migrations and migrate them. If you do like my content and you want to see more leave this video a thumbs up and if you're new to this channel please hit the subscribe button.
e for