Buy

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:

93 lines src/Entity/Article.php
... 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:

93 lines src/Entity/Article.php
... 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:

93 lines src/Entity/Article.php
... 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:

93 lines src/Entity/Article.php
... 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 for methods for each one:

93 lines src/Entity/Article.php
... 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.

Leave a comment!

  • 2018-08-20 Victor Bocharsky

    Hey IordanisGR ,

    Yes, exactly! Well, we still use bundles, but now bundles are need to share code between projects only. So you may still want to make a bundle, but when you need to share the code between projects, and this bundle you'll install with Composer, so no bundles in src/ dir now.

    Cheers!

  • 2018-08-20 IordanisGR

    ohh yes. After a lot research, i find out that "bundless" symfony 4 , for a reason wants one namespace for the entities!

  • 2018-08-20 Victor Bocharsky

    Hey IordanisGR ,

    Good question! Currently it's impossible to do in one command run, you can find some related comments in MakerBundle: https://github.com/symfony/... . So, you can cheat: generate entity and its repository in src/Entity/ and src/Repository/ folders, but then manually move them into the bundle you want, and that's it. Just don't forget to tweak namespaces to match the new location.

    Cheers!

  • 2018-08-19 IordanisGR

    How can i generate the entity in my own Bundle ??
    For example. I have UserBundle. By making "make:entity \User\Entity\User" ,
    The command generates the file "User.php" at the desirable location( App/User/Entity).
    But the "UserRepository.php" created at the App/Repository, when the desirable is at User/Repository.
    In a few words, how can i generate the entity with the symfony 3.4 style "MyBundle: EntityName" ?
    Thanks

  • 2018-08-17 weaverryan

    Hey Nikeev!

    Awesome! Woohoo! This "sanity validation" (that's my term) that form fields provide is not a very-well-known feature of the form fields. We normally think of them as just "how the form looks", but it's also "how the data is processed". Anyways - glad it's working!

    Cheers!

  • 2018-08-17 Nikeev

    Hello! Thanks for reply!

    Yes, someNumber is integer value, but I used TextType to get simple text intput without browser up/down number arrows (customer asked =)). Now I made an experiment and it seems you are right, I need to use NumberType. In that case if I put some text value into number field, form saves ok without any errors, but field value becomes null. If I manually change html to input type="text" and put some text value, in that case Symfony form validate and highlight field as wrong value.

    So thank you! Now I know that I shouldn't mess form fields types :)

  • 2018-08-16 weaverryan

    Hey Nikeev!

    GREAT question! What Symfony field type are you using for the someNumber field? Have you tried the NumberType? I believe (but I'm not 100% sure about this) it will throw a validation error *before* setting the bad data on the field.

    So, try that out. If I'm wrong - let me know - I *do* think there may be some cases where the type-hints complicate things with the form+validation layer (but again, I can't remember 100% if this is one of those cases or not).

    Cheers!

  • 2018-08-16 Nikeev

    Hello! Thanks for tutorial!

    I have a question about setters and form validation. Yes, it's not only about entities but maybe you could help.
    Maker bundle and also PhpStorm generates setter with variable type. For, example integer:


    public function setSomeNumber(int $someNumber): self
    {
    $this->someNumber= $someNumber;
    return $this;
    }

    $someNumber has assert annotation.
    * @Assert\Range(
    * min = 0,
    * max = 100
    * )

    So there should be a number between 0 and 100. And it works fine, until user type some text value into field (no html5 validation). In that case app crashes with an error: "Notice: A non well formed numeric value encountered". So, as I understand, first of all form tries to fill entity object and than validate it. But it fails because of type declaration. If I remove it, validation works fine.

    Is there any right way to deal with it? Thanks!

  • 2018-06-25 Diego Aguiar

    Hey Matthieu

    Thanks for your kind words :)

    Answering your question, yes, VichUploaderBundle is a great bundle for handling file uploads in Symfony. What I usually do is to store all uploaded files into a public directory, because I always have to show them to the public, but if you don't have to access them via web, then you can store them in any other place you want

    Cheers!

  • 2018-06-25 Matthieu Lagarde

    Hi,
    I love your tutorial so far, it is really awesome, crystal clear and funny to watch, so congrats and keep up the good work :)!
    I had a question on how to store files uploaded by users (for instance profile pictures) in Symfony 4. I have seen that I can install a bundle dedicated to file upload VicUploaderBundle (https://github.com/dustin10.... Yet, I wonder what should be the best folder to store these files... Should it be in the public folder alongside static files ? Or should it be in a subfolder of the var folder ? Or maybe elsewhere :) ?

  • 2018-05-07 Victor Bocharsky

    Hey Yeison,

    Yeah, you're right. But this tutorial is the third one based on this "Stellar" project and we've already installed this MakerBundle in previous tutorial, see: https://knpuniversity.com/s... . So if you're with us from the beginning of the Symfony 4 track - you should have this bundle installed already :)

    But thanks for this tip for others, it may be useful.

    Cheers!

  • 2018-05-06 Yeison J. Espinoza

    Hi, i think there is a missing part:
    when you use ".bin/console make:entity", you need to install that "make" to be able to run "make" :D