Starship Entity
We have a database and can connect to it but... it doesn't have any tables!
The Doctrine ORM uses PHP classes to represent tables in the database, like if you need a table for products, you create a Product class. Doctrine calls these classes "entities", but they're really just standard boring PHP classes. I like boring!
In our StarShop app, we need to track Starships... so we need a Starship table... so we need a Starship entity class. What does a Starship look like? In the last tutorial, we created a Starship model class in the src/Model directory. Open it up. We decided that each Starship has an id, name, class, captain, status, and arrivedAt.
This is almost a Doctrine entity: it's just missing some config that helps Doctrine understand how to map this class to a table in the database. We could easily add that by hand. But... we have a tool that can do this for us: the MakerBundle!
make:entity
Run:
symfony console make:entity
For the name, use Starship. We're not using Symfony UX Turbo, so answer no to that question. This already created a Starship class in the Entity/ directory and a StarshipRepository class. We'll talk about that later.
But we're not done! This command is awesome: it interactively asks what properties - or columns if you want to think that way - our entity needs. Jump back to the Starship model to see what we need. MakerBundle will add id automatically, so skip to name. Field type? Use the default: string. Field length? 255 is fine. Can this field be null in the database? No, every Starship needs a name.
Next is class, it'll be the same as name... then captain is also a simple string. Next: status. Doctrine defaults to a string, but... look at our Starship model, status is an enum. How can we map this to a column? Back in the terminal, hit ? to see all the different types we can add. At the bottom... enum! Use that. Enum class? Use the full class name of our enum: App\Model\StarshipStatusEnum.
Can this field store multiple values? No, a Starship can only have one status at a time. Can this field be null? Nope!
Finally, add arrivedAt. Cool! Maker defaults to datetime_immutable instead of string. This is because we suffixed our property name with At. Smart! Can this field be null? No.
[ORM\Entity]
Let's take a look at our newly minted Starship entity: in src/Entity/.
Notice: this is a standard PHP class with properties... and one special thing: some PHP attributes:
The #[ORM\Entity] attribute on the class tells Doctrine that this not just a boring PHP class, but an entity that should be mapped to a table in our database. The table name can be customized, but we'll use the default which is the snake cased class name: starship.
[ORM\Column]
Check out the properties: each has #[ORM\Column]. This tells Doctrine that these properties are columns in our table. For the type, Doctrine is smart and guesses from the type hint. For example, id will be an integer type, name will be a string type, and arrivedAt will be a timestamp type. Nice!
| // ... lines 1 - 8 | |
| #[ORM\Entity(repositoryClass: StarshipRepository::class)] | |
| class Starship | |
| { | |
| #[ORM\Id] | |
| #[ORM\GeneratedValue] | |
| #[ORM\Column] | |
| private ?int $id = null; | |
| #[ORM\Column(length: 255)] | |
| private ?string $name = null; | |
| #[ORM\Column(length: 255)] | |
| private ?string $class = null; | |
| #[ORM\Column(length: 255)] | |
| private ?string $captain = null; | |
| #[ORM\Column(enumType: StarshipStatusEnum::class)] | |
| private ?StarshipStatusEnum $status = null; | |
| #[ORM\Column] | |
| private ?\DateTimeImmutable $arrivedAt = null; | |
| // ... lines 31 - 95 | |
| } |
id has a few extra attributes that mark it as the primary key and tells the database to auto-generate this as an auto-incrementing integer.
Oh, and we can remove the length argument from the string columns: this is the default.
| // ... lines 1 - 9 | |
| class Starship | |
| { | |
| // ... lines 12 - 16 | |
| #[ORM\Column] | |
| private ?string $name = null; | |
| #[ORM\Column] | |
| private ?string $class = null; | |
| #[ORM\Column] | |
| private ?string $captain = null; | |
| // ... lines 25 - 109 | |
| } |
The status property is a StarshipStatusEnum type but Doctrine will store this as a string in the database. Cool! We can actually remove the enumType argument: Doctrine can guess that from the property type too!
| // ... lines 1 - 9 | |
| class Starship | |
| { | |
| // ... lines 12 - 25 | |
| #[ORM\Column] | |
| private ?StarshipStatusEnum $status = null; | |
| // ... lines 28 - 109 | |
| } |
Down below, the maker generated getters and setters for all our properties. Our old Starship model had two extra methods: getStatusString() and getStatusImageFilename(). Copy those from the model class... and at the bottom of the entity class, paste!
| // ... lines 1 - 9 | |
| class Starship | |
| { | |
| // ... lines 12 - 96 | |
| public function getStatusString(): string | |
| { | |
| return $this->status->value; | |
| } | |
| public function getStatusImageFilename(): string | |
| { | |
| return match ($this->status) { | |
| StarshipStatusEnum::WAITING => 'images/status-waiting.png', | |
| StarshipStatusEnum::IN_PROGRESS => 'images/status-in-progress.png', | |
| StarshipStatusEnum::COMPLETED => 'images/status-complete.png', | |
| }; | |
| } | |
| } |
Entity done! We can even double-check our work. At your terminal, run:
Schema Validation
symfony console doctrine:schema:validate
This means that Doctrine sees and can read our attributes. Then... our database is out of sync?
We have an entity class... but we don't actually have the starship table in the database.
There are a few ways to get the table into the database, but the best way is migrations. That's next!
7 Comments
When I run make:entity I get an error : [ERROR] The file "src/Repository/StarshipRepository.php" can't be generated because it already exists.
I've used this from the start of the the exercises - what is the correct way to proceed?
Hey @alimack!
Ah, did you just carry the code over from https://symfonycasts.com/screencast/symfony-fundamentals?
For this course's code, behind the scenes, I moved the old
StartshipRepositoryfromsrc/Repositorytosrc/Model. You can do the same before running this command and that should fix the error.Let me know if that doesn't work!
--Kevin
Many thanks for getting back to me. I did start with the fundamentals course.
Would it be best to just download the new code?
I'll try this tomorrow, dinner beckons!
In general, yes - that's kind of our expectation. While it's mostly a continuation from the final step of the previous course, there are sometimes small changes like this.
why is it that the entity class allows for null values (at the php level) when during the make:entity process the columns were specified as not null?
Hey @Francisc,
This is a great question. The answer is validation. The current suggested way to add validation to entities is to define your constraints directly on your entity's properties. For the validation component to work correctly, these properties need to be
nullable.I admit, this is unfortunate. I dislike the possibility of my entities being in an "invalid state" (a state that, when persisted, gives a database error). The current workaround is to use a separate DTO object that contains your validation constraints. Once that is valid, map this to your entity. Then, you likely need a way to map your entity back to the DTO for doing entity edits. It gets cumbersome...
The good news is there's some efforts into making this easier. Symfony 7.3 added an Object Mapper component that helps with the DTO <-> Entity conversion.
I'm hoping sometime in the future, we can change the current recommendation.
--Kevin
why is there a disconnect between the database configs for column and their corresponding class properties?
"Houston: no signs of life"
Start the conversation!