Buy

It's you again! Welcome back friend! In this tutorial, we're diving back into Doctrine: this time to master database relations. And to have fun of course - databases are super fun.

Like usual, you should code along with me or risk 7 years of bad luck. Sorry. To do that, download the code from the course page and use the start directory. I already have the code - so I'll startup our fancy, built-in web server:

./bin/console server:run

You may also need to run composer install and a few other tasks. Check the README in the download for those details.

When you're ready, pull up the genus list page at http://localhost:8000/genus. Nice!

Create the GenusNote Entity

Click to view a specific genus. See these notes down here? These are loaded via a ReactJS app that talks to our app like an API. But, the notes themselves are still hardcoded right in a controller. Bummer! Time to make them dynamic - time to create a second database table to store genus notes.

To do that, create a new GenusNote class in the Entity directory. Copy the ORM use statement from Genus that all entities need and paste it here:

80 lines src/AppBundle/Entity/GenusNote.php
... lines 1 - 2
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
... lines 6 - 10
class GenusNote
{
... lines 13 - 78
}

With that, open the "Code"->"Generate" menu - or Cmd + N on a Mac - and select "ORM Class":

80 lines src/AppBundle/Entity/GenusNote.php
... lines 1 - 2
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="genus_note")
*/
class GenusNote
{
... lines 13 - 78
}

Bam! This is now an entity!

Next: add the properties we need. Let's see... we need a username, an userAvatarFilename, notes and a createdAt property:

80 lines src/AppBundle/Entity/GenusNote.php
... lines 1 - 10
class GenusNote
{
... lines 13 - 17
private $id;
... lines 19 - 22
private $username;
... lines 24 - 27
private $userAvatarFilename;
... lines 29 - 32
private $note;
... lines 34 - 37
private $createdAt;
... lines 39 - 78
}

When we add a user table later - we'll replace username with a relationship to that table. But for now, keep it simple.

Open the "Code"->"Generate" menu again and select "ORM Annotation". Make sure each field type looks right. Hmm, we probably want to change $note to be a text type - that type can hold a lot more than the normal 255 characters:

80 lines src/AppBundle/Entity/GenusNote.php
... lines 1 - 10
class GenusNote
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string")
*/
private $username;
/**
* @ORM\Column(type="string")
*/
private $userAvatarFilename;
/**
* @ORM\Column(type="text")
*/
private $note;
/**
* @ORM\Column(type="datetime")
*/
private $createdAt;
... lines 39 - 78
}

Finally, go back to our best friend - the "Code"->"Generate" menu - and generate the getter and setters for every field - except for id. You don't usually want to set the id, but generate a getter for it:

80 lines src/AppBundle/Entity/GenusNote.php
... lines 1 - 10
class GenusNote
{
... lines 13 - 39
public function getUsername()
{
return $this->username;
}
public function setUsername($username)
{
$this->username = $username;
}
public function getUserAvatarFilename()
{
return $this->userAvatarFilename;
}
public function setUserAvatarFilename($userAvatarFilename)
{
$this->userAvatarFilename = $userAvatarFilename;
}
public function getNote()
{
return $this->note;
}
public function setNote($note)
{
$this->note = $note;
}
public function getCreatedAt()
{
return $this->createdAt;
}
public function setCreatedAt($createdAt)
{
$this->createdAt = $createdAt;
}
}

Generate Migrations

Entity done! Well, almost done - we still need to somehow relate each GenusNote to a Genus. We'll handle that in a second.

But first, don't forget to generate a migration for the new table:

./bin/console doctrine:migrations:diff

Open up that file to make sure it looks right - it lives in app/DoctrineMigrations. CREATE TABLE genus_note - it looks great! Head back to the console and run the migration:

./bin/console doctrine:migrations:migrate

Adding Fixtures

Man, that was easy. We'll want some good dummy notes too. Open up the fixtures.yml file and add a new section for AppBundle\Entity\GenusNote. Start just like before: genus.note_ and - let's create 100 notes - so use 1..100:

15 lines src/AppBundle/DataFixtures/ORM/fixtures.yml
... lines 1 - 8
AppBundle\Entity\GenusNote:
genus.note_{1..100}:
... lines 11 - 15

Next, fill in each property using the Faker functions: username: <username()> and then userAvatarFilename: Ok, eventually users might upload their own avatars, but for now, we have two hardcoded options: leanna.jpeg and ryan.jpeg. Let's select one of these randomly with a sweet syntax: 50%? leanna.jpeg : ryan.jpeg. That's Alice awesomeness:

15 lines src/AppBundle/DataFixtures/ORM/fixtures.yml
... lines 1 - 7
AppBundle\Entity\GenusNote:
genus.note_{1..100}:
username: <userName()>
userAvatarFilename: '50%? leanna.jpeg : ryan.jpeg'
... lines 13 - 15

The rest are easy: note: <paragraph()> and createdAt: <dateTimeBetween('-6 months', 'now')>:

15 lines src/AppBundle/DataFixtures/ORM/fixtures.yml
... lines 1 - 8
AppBundle\Entity\GenusNote:
genus.note_{1..100}:
username: <userName()>
userAvatarFilename: '50%? leanna.jpeg : ryan.jpeg'
note: <paragraph()>
createdAt: <dateTimeBetween('-6 months', 'now')>

Ok, run the fixtures!

./bin/console doctrine:fixtures:load

Double-check them with a query:

./bin/console doctrine:query:sql 'SELECT * FROM genus_note'

So awesome! Ok team, we have two entities: let's add a relationship!

Leave a comment!

  • 2016-11-14 Nobuyuki Fujioka

    Hi, Victor
    Thank you for your reply. My first try seemed to have worked. But, will see.
    I will try your solution as well.

    I have many other questions. so, will ask you later.

    Cheers,
    Noby

  • 2016-11-14 Victor Bocharsky

    Hey Nobuyuki,

    I think you can. Have you tried it? Btw, what about just map $id field to the different column name in database:


    /**
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    * @ORM\Column(name="connector_id", type="integer")
    */
    private $id;


    This should work as well.

    Cheers!

  • 2016-11-13 Nobuyuki Fujioka

    Hi,

    When I create an entity class, can I have different names for id like below?
    (@ORM\Id and private $connectorId)
    /**
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    * @ORM\Column(type="integer")
    */
    private $connectorId;

    Cheers,
    Noby

  • 2016-11-11 Victor Bocharsky

    You're welcome!

    Cheers!

  • 2016-11-11 Nobuyuki Fujioka

    Hi, Victor

    Thank you very much. I understand it better now thanks to your explanation.

    Kind regards,
    Noby

  • 2016-11-11 Victor Bocharsky

    Hey Nobuyuki,

    My PhpStorm creates setters and getters with annotation too, so probably Ryan has different settings, or just an older version of PhpStorm, or it just was a bug in PhpStorm :)

    This annotation called PHP DocBlock comments. You can read more about it here: https://phpdoc.org/docs/latest... . Basically, this annotations helps IDEs like PhpStorm to do autocompletion. Usually, it's unnecessary for IDE because it can resolve returned type base on @var property annotation. The types of method arguments can be resolved base on typehint, but it don't work for scalar type, that's why @param annotation for methods with scalar values would be good here. But nothing critical won't happen without these annotations for getters/setters, you just don't have an autocompletion in some places of your code.

    Actually, here's my recent merged PR to the Symfony Demo project about annotation: https://github.com/symfony/sym... . I think you might be interested in it - there's a small discussion about annotation best practice.

    Cheers!

  • 2016-11-11 Nobuyuki Fujioka

    Just a quick question, in your video, when you create getter and setter for a parameter in GenusNote class, phpStorm is creating getter and setter functions without annotations. But, with my PHPstorm, it is creating annotation for each one.
    Below is an example:
    ```
    /**
    * @return mixed
    */
    public function getUsername()
    {
    return $this->username;
    }

    /**
    * @param mixed $username
    */
    public function setUsername($username)
    {
    $this->username = $username;
    }
    ```
    What are these
    /**
    * @return mixed
    */
    and
    /**
    * @param mixed $username
    */
    ?

    Do we need them?

    Cheers,
    Noby

  • 2016-09-28 Victor Bocharsky

    haha, yes, it's :D

  • 2016-09-28 Maksym Minenko

    Ryan would say: "But that's so boooring" :D

    Ok, ok, it's quite suffice for me, thank you! ;)

  • 2016-09-28 Victor Bocharsky

    Yes, agree, but I think that's the simplest solution. BTW, I just use username: user<current()>.

  • 2016-09-28 Maksym Minenko

    Do you mean like this, for example?
    username: <username()>_<current()>

    Well, not the most elegant solution, but... it works. :) I guess it's quite ok for development purposes.

  • 2016-09-28 Victor Bocharsky

    Yo Maksym,

    The <current()> function return a current index of each iteration. Combine it with username to make it 100% unique.

    Cheers!

  • 2016-09-28 Maksym Minenko

    Out of 100 usernames three are exactly the same. Is there an easy way to make them unique?

  • 2016-08-10 Victor Bocharsky

    Hey Henry,

    In Symfony project: just change "database_driver" to "pdo_sqlsrv" instead of "pdo_mysql" in your "app/config/parameters.yml". For custom project based on Doctrine - find a place where configuration parameters are setting according to the Doctrine configuration.

    P.S. And also don't forget to tweak other DB credentials as host, port, username and password.

    Cheers!

  • 2016-08-09 Henry

    Hi how can I connect to a MSSQL database with doctrine please?

  • 2016-07-05 Victor Bocharsky

    Hey Ilya,

    Great! I think the problem was that you used `createAt` instead of `createdAt`, right? It should be `createdAt`. Your explanation could help other devs with similar problem.

    Cheers!

  • 2016-07-05 Ilya

    It's ok evrything is workin

  • 2016-07-05 Ilya

    Hi,guys
    after load bin/console doctrine:fixtures:load
    unexpectedvalueexception could not determine how to assign createAt to a AppBundle\Entity\GenusNote object

  • 2016-06-14 weaverryan

    I like this stuff - good tips!

    And no reason about server:start versus server:run. I feel like new people may have less trouble with server:run, as they may accidentally keep server:start running in the background, then come back later, try to start it again, and get an error.

    Cheers!

  • 2016-06-14 JLChafardet

    weaverryan

    I just noticed, I have a couple of little things that you could integrate into your files to help the viewers in the long run.

    create a symlink for bin/console
    ln -s bin/console console
    add an alias for the console command
    (be it on .aliases or your .bash_rc file)
    alias console="php console"

    type "console"

    See the results here http://goo.gl/n1P4CU

    oh my terminal? well thats powerline-shell -> https://github.com/milkbikis/p... <- at work (linux here, works in the mac too for those interested)

    also I notice you use php bin/console server:run and not server:start ? any particular reason for it?

  • 2016-05-11 Ленур

    thanks, problem solved :)

  • 2016-05-11 weaverryan

    Ah, make sure you have the PHP Annotations plugin installed for PhpStorm. In the first course, I did *not* mention this - we're making an edit to the video to add this missing item!

  • 2016-05-08 Ленур

    I do you *not* see the option at all.

  • 2016-05-07 weaverryan

    Hi again!

    Hmm, is it inactive, or do you *not* see the option at all? Make sure your cursor is *inside* your entity class when you use the Code->Generate menu.

    Let me know what you find out :)

  • 2016-05-07 Ленур

    Hello Ryan.

    How to enable ORM Annotation on code generate menu? In my menu this option inactive.

    Thanks.