Creating an Entity Class
Doctrine is an ORM, or object relational mapper. A fancy term for a pretty cool idea. It means that each table in the database will have a corresponding class in our code. So if we want to create an article
table, it means that we need to create an Article
class. You can totally make this class by hand - it's just a normal PHP class.
Generating with make:entity
But there's a really nice generation tool from MakerBundle. We installed MakerBundle in the last tutorial, and before I started coding, I updated it to the latest version to get this new command. At your terminal, run:
php bin/console make:entity
Stop! That word "entity": that's important. This is the word that Doctrine gives to the classes that are saved to the database. As you'll see in a second, these are just normal PHP classes. So, when you hear "entity", think:
That's a normal PHP class that I can save to the database.
Let's call our class Article
, and then, cool! We can start giving it fields right here. We need a title
field. For field "type", hmm, hit "?" to see what all the different types are.
Notice, these are not MySQL types, like varchar. Doctrine has its own types that map to MySQL types. For example, let's use "string" and let the length be 255. Ultimately, that'll create a varchar column. Oh, and because we probably want this column to be required in the database, answer "no" for nullable.
Next, create a field called slug
, use the string
type again, and let's make it's length be 100, and no for nullable.
Next, content
, set this to text
and "yes" to nullable: maybe we allow articles to be drafted without content at first. And finally, a publishedAt
field with a type set to datetime
and yes to nullable. If this field is null, we'll know that the article has not been published.
When you're done, hit enter to finish. And don't worry if you make a mistake. You can always update things later, or delete the new entity class and start over.
Investigating the Entity Class
So... what did that just do? Only one thing: in src/Entity
, this command generated a new Article
class:
// ... lines 1 - 2 | |
namespace App\Entity; | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ORM\Entity(repositoryClass="App\Repository\ArticleRepository") | |
*/ | |
class Article | |
{ | |
/** | |
* @ORM\Id() | |
* @ORM\GeneratedValue() | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column(type="string", length=255) | |
*/ | |
private $title; | |
/** | |
* @ORM\Column(type="string", length=100) | |
*/ | |
private $slug; | |
/** | |
* @ORM\Column(type="text", nullable=true) | |
*/ | |
private $content; | |
/** | |
* @ORM\Column(type="datetime", nullable=true) | |
*/ | |
private $publishedAt; | |
public function getId() | |
{ | |
return $this->id; | |
} | |
public function getTitle(): ?string | |
{ | |
return $this->title; | |
} | |
public function setTitle(string $title): self | |
{ | |
$this->title = $title; | |
return $this; | |
} | |
public function getSlug(): ?string | |
{ | |
return $this->slug; | |
} | |
public function setSlug(string $slug): self | |
{ | |
$this->slug = $slug; | |
return $this; | |
} | |
public function getContent(): ?string | |
{ | |
return $this->content; | |
} | |
public function setContent(?string $content): self | |
{ | |
$this->content = $content; | |
return $this; | |
} | |
public function getPublishedAt(): ?\DateTimeInterface | |
{ | |
return $this->publishedAt; | |
} | |
public function setPublishedAt(?\DateTimeInterface $publishedAt): self | |
{ | |
$this->publishedAt = $publishedAt; | |
return $this; | |
} | |
} |
Well... to be fully honest, there is also a new ArticleRepository
class, but I want you to ignore that for now. It's not important yet.
Anyways, this Article
class is your entity. And, check it out! It's a normal, boring PHP class with a property for each column: id
, title
, slug
, content
, and publishedAt
:
// ... lines 1 - 9 | |
class Article | |
{ | |
// ... lines 12 - 16 | |
private $id; | |
// ... lines 18 - 21 | |
private $title; | |
// ... lines 23 - 26 | |
private $slug; | |
// ... lines 28 - 31 | |
private $content; | |
// ... lines 33 - 36 | |
private $publishedAt; | |
// ... lines 38 - 91 | |
} |
What makes this class special are the annotations! The @ORM\Entity
above the class tells Doctrine that this is an entity that should be mapped to the database:
// ... lines 1 - 4 | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ORM\Entity(repositoryClass="App\Repository\ArticleRepository") | |
*/ | |
class Article | |
{ | |
// ... lines 12 - 91 | |
} |
Then, above each property, we have some annotations that help doctrine know how to store that exact column:
// ... lines 1 - 4 | |
use Doctrine\ORM\Mapping as ORM; | |
/** | |
* @ORM\Entity(repositoryClass="App\Repository\ArticleRepository") | |
*/ | |
class Article | |
{ | |
/** | |
* @ORM\Id() | |
* @ORM\GeneratedValue() | |
* @ORM\Column(type="integer") | |
*/ | |
private $id; | |
/** | |
* @ORM\Column(type="string", length=255) | |
*/ | |
private $title; | |
/** | |
* @ORM\Column(type="string", length=100) | |
*/ | |
private $slug; | |
/** | |
* @ORM\Column(type="text", nullable=true) | |
*/ | |
private $content; | |
/** | |
* @ORM\Column(type="datetime", nullable=true) | |
*/ | |
private $publishedAt; | |
// ... lines 38 - 91 | |
} |
Actually, find your browser and Google for "doctrine annotations reference" to find a cool page. This shows you every annotation in Doctrine and every option for each one.
Back at the code, the properties are private. So, at the bottom of the class, the command generated getter and setter methods for each one:
// ... lines 1 - 9 | |
class Article | |
{ | |
// ... lines 12 - 38 | |
public function getId() | |
{ | |
return $this->id; | |
} | |
public function getTitle(): ?string | |
{ | |
return $this->title; | |
} | |
public function setTitle(string $title): self | |
{ | |
$this->title = $title; | |
return $this; | |
} | |
public function getSlug(): ?string | |
{ | |
return $this->slug; | |
} | |
public function setSlug(string $slug): self | |
{ | |
$this->slug = $slug; | |
return $this; | |
} | |
public function getContent(): ?string | |
{ | |
return $this->content; | |
} | |
public function setContent(?string $content): self | |
{ | |
$this->content = $content; | |
return $this; | |
} | |
public function getPublishedAt(): ?\DateTimeInterface | |
{ | |
return $this->publishedAt; | |
} | |
public function setPublishedAt(?\DateTimeInterface $publishedAt): self | |
{ | |
$this->publishedAt = $publishedAt; | |
return $this; | |
} | |
} |
There's one really important thing to realize: this class is 100% your class. Feel free to add, remove or rename any properties or methods you want.
And... yea! With one command, our entity is ready! But, the database is still empty! We need to tell Doctrine to create the corresponding article
table in the database. We do this with migrations.
One advice from my side:
If your $content property can be set publicly, I propose to change the annotation of "private $content;":
Old:
* @ORM\Column(type="text", nullable=true)
New:
* @ORM\Column(type="text", length=65535, nullable=true)
Before the change, Doctrine sets the field inside the DB to "longtext", which accepts string up to 4GB of space, which is in my case unwanted because everyone can set this field.
After the change Doctrine sets the type="text" correctly (Which means max. 65535 Chars)