Buy
Buy

The really neat thing about Symfony's security system is that it doesn't care at all about what your User class looks like. As long as it implements UserInterface, so, as long as it has these methods, you can do anything you want with it. Heck, it doesn't even need to be an entity!

Adding more Fields to User

For example, we already have an email field, but I also want to be able to store the first name for each user. Cool: we can just add that field! Find your terminal and run:

php bin/console make:entity

Update the User class and add firstName as a string, length 255 - or shorter if you want - and not nullable. Done!

Check out the User class! Yep, there's the new firstName property and... at the bottom, the getter and setter methods:

... lines 1 - 10
class User implements UserInterface
{
... lines 13 - 29
/**
* @ORM\Column(type="string", length=255)
*/
private $firstName;
... lines 34 - 105
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(string $firstName): self
{
$this->firstName = $firstName;
return $this;
}
}

Awesome!

Setting Doctrine's server_version

I think we're ready to make the migration. But! A word of warning. Check out the roles field on top:

... lines 1 - 10
class User implements UserInterface
{
... lines 13 - 24
/**
* @ORM\Column(type="json")
*/
private $roles = [];
... lines 29 - 116
}

It's an array and its Doctrine type is json. This is really cool. Newer databases - like PostgreSQL and MySQL 5.7 - have a native "JSON" column type that allows you to store an array of data.

But, if you're using MySQL 5.6 or lower, this column type does not exist. And actually, that's not a problem! In that case, Doctrine is smart enough to use a normal text field, json_encode() your array when saving, and json_decode() it automatically when we query. So, no matter what database you use, you can use this json Doctrine column type.

But, here's the catch. Open config/packages/doctrine.yaml. One of the keys here is server_version, which is set to 5.7 by default:

... lines 1 - 7
doctrine:
dbal:
... lines 10 - 11
server_version: '5.7'
... lines 13 - 31

This tells Doctrine that when it interacts with the database, it should expect that our database has all the features supported by MySQL 5.7, including that native JSON column type. If your computer, or more importantly, if your production database is using MySQL 5.6, then you'll get a huge error when Doctrine tries to make queries using the native MySQL JSON column type.

If you're in this situation, just set this back to 5.6:

... lines 1 - 7
doctrine:
dbal:
... lines 10 - 11
server_version: '5.6'
... lines 13 - 31

Doctrine will then create a normal text column for the JSON field.

Generating the Migration

Ok, now run:

php bin/console make:migration

Perfect! Go check that file out in src/Migrations:

... lines 1 - 10
final class Version20180830012659 extends AbstractMigration
{
public function up(Schema $schema) : void
{
... lines 15 - 17
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:json)\', first_name VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
}
... lines 20 - 27
}

And... nice! CREATE TABLE user. Look at the roles field: a LONGTEXT column. If you kept your server_version at 5.7, this would be a json column.

Let's run this:

php bin/console doctrine:migrations:migrate

Adding Fixtures

One last step: we need to add some dummy users into the database. Start with:

php bin/console make:fixtures

Call it UserFixture. Go check that out: src/DataFixtures/UserFixture.php:

... lines 1 - 2
namespace App\DataFixtures;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\Persistence\ObjectManager;
class UserFixture extends Fixture
{
public function load(ObjectManager $manager)
{
// $product = new Product();
// $manager->persist($product);
$manager->flush();
}
}

If you watched our Doctrine tutorial, you might remember that we created a special BaseFixture with some sweet shortcut methods. Before I started recording this tutorial, based on some feedback from you nice people, I made a few improvements to that class. Go team!

... lines 1 - 9
abstract class BaseFixture extends Fixture
{
... lines 12 - 21
public function load(ObjectManager $manager)
{
$this->manager = $manager;
$this->faker = Factory::create();
$this->loadData($manager);
}
... lines 29 - 90
}

The way you use this class is still the same: extend BaseFixture and update the load() method to be protected function loadData(). I'll remove the old use statement.

... lines 1 - 2
namespace App\DataFixtures;
use Doctrine\Common\Persistence\ObjectManager;
class UserFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
... lines 11 - 14
}
}

Inside, call $this->createMany(). The arguments to this method changed a bit since the last tutorial:

... lines 1 - 9
abstract class BaseFixture extends Fixture
{
... lines 12 - 29
/**
* Create many objects at once:
*
* $this->createMany(10, function(int $i) {
* $user = new User();
* $user->setFirstName('Ryan');
*
* return $user;
* });
*
* @param int $count
* @param string $groupName Tag these created objects with this group name,
* and use this later with getRandomReference(s)
* to fetch only from this specific group.
* @param callable $factory
*/
protected function createMany(int $count, string $groupName, callable $factory)
{
for ($i = 0; $i < $count; $i++) {
$entity = $factory($i);
if (null === $entity) {
throw new \LogicException('Did you forget to return the entity object from your callback to BaseFixture::createMany()?');
}
$this->manager->persist($entity);
// store for usage later as groupName_#COUNT#
$this->addReference(sprintf('%s_%d', $groupName, $i), $entity);
}
}
... lines 61 - 90
}

Pass this 10 to create 10 users. Then, pass a "group name" - main_users. Right now, this key is meaningless. But later, we'll use it in a different fixture class to relate other objects to these users. Finally, pass a callback with an $i argument:

... lines 1 - 7
class UserFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
$this->createMany(10, 'main_users', function($i) {
... lines 13 - 17
});
$manager->flush();
}
}

This will be called 10 times and our job inside is simple: create a User, put some data on it and return!

Do it! $user = new User():

... lines 1 - 4
use App\Entity\User;
... lines 6 - 7
class UserFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
$this->createMany(10, 'main_users', function($i) {
$user = new User();
... lines 14 - 17
});
... lines 19 - 20
}
}

Then $user->setEmail() with sprintf() spacebar%d@example.com. For the %d wildcard, pass $i:

... lines 1 - 7
class UserFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
$this->createMany(10, 'main_users', function($i) {
$user = new User();
$user->setEmail(sprintf('spacebar%d@example.com', $i));
... lines 15 - 17
});
... lines 19 - 20
}
}

Which will be one, two, three, four, five, six, seven, eight, nine, ten for the 10 calls.

The only other field is first name. To set this, we an use Faker, which we already setup inside BaseFixture: $this->faker->firstName:

... lines 1 - 7
class UserFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
$this->createMany(10, 'main_users', function($i) {
$user = new User();
$user->setEmail(sprintf('spacebar%d@example.com', $i));
$user->setFirstName($this->faker->firstName);
... lines 16 - 17
});
... lines 19 - 20
}
}

Finally, at the bottom, return $user:

... lines 1 - 7
class UserFixture extends BaseFixture
{
protected function loadData(ObjectManager $manager)
{
$this->createMany(10, 'main_users', function($i) {
$user = new User();
$user->setEmail(sprintf('spacebar%d@example.com', $i));
$user->setFirstName($this->faker->firstName);
return $user;
});
... lines 19 - 20
}
}

And... we're done! This step had nothing to do with security: this is just boring Doctrine & PHP code inside a fancy createMany() method to make life easier.

Load 'em up:

php bin/console doctrine:fixtures:load

Let's see what these look like:

php bin/console doctrine:query:sql 'SELECT * FROM user'

Nice! Our User class is done! Now, it's time to add a login form and a login form authenticator: the first way that we'll allow our users to login.

Leave a comment!

  • 2019-03-18 Дмитрий Ченгаев

    Thank you, Ryan. It is perfectly.

  • 2019-03-18 weaverryan

    Hey Дмитрий Ченгаев!

    Great question! Yes, but you should set this server_version setting to the *lowest* of the versions. This will prevent it from using, for example, any new features in MySQL 5.7 if your production server is actually only on MySQL 5.6.

    Cheers!

  • 2019-03-17 Дмитрий Ченгаев

    Is it possible to work with the database in Symfony, if the version of MySQL on the development and production server is different.

    What to do with config/packages/doctrine.yaml
    and configuring

    server_version: '5.7'

    in this case?

It will take different values.

  • 2019-03-08 Diego Aguiar

    Hey @Galnash

    That's correct, the BaseFixture class got refactored, so I think you only have to download the new one, and probably you'll have to do some tweaks to your Fixture classes.

    Cheers!

  • 2019-03-07 Galnash

    Hello everyone,

    I just finish the course about "Doctrine & The Database" and I want to follow this course.
    I'm working with the same code from the old course. Do you have an idea why I've this error when I try to :fixtures:load ?

    Error message :

    In BaseFixture.php line 57:

    Argument 1 passed to App\DataFixtures\BaseFixture::createMany() must be of the type
    integer, string given, called in /Users/computer/first-project/src/DataFixture
    s/ArticleFixtures.php on line 66

    The line 57 in BaseFixture is :

    protected function createMany(int $count, string $groupName, callable $factory) {

    Do you think the problem is come from the old code form the previous course ?

    Thank you!

  • 2019-01-22 weaverryan

    Hey Emin!

    Sorry for my slow reply! You can't do that. But, the reason *why* is more interesting :). There are two reasons:

    1) Dependency injection is designed to only work with the __construct() method. Now, you might think I'm totally wrong, because we use dependency injection in the methods of our controller! And you're right. For convenience, we made autowiring/DI work for methods in exactly *one* place - your controller methods. The reason is simple: if we made autowiring/DI work only via the constructor (which is sorta how this stuff is supposed to work), then working in your controller would be a bit more annoying - you would need to add a __construct() method and pass in all the services you need. So, we made it possible to "cheat" a little. Second, your controller methods are a very unique part of the system - it's one of the super rare situations where YOU are not calling a method directly - Symfony is actually calling that method on your behalf. And so, it gives Symfony a little "hook" at add that magic.

    2) Dependency injection also only works for services - e.g. EntityManagerInterface or LoggerInterface - it does not work for entities or other "model" objects that simply hold data. Once again, you're probably thinking - "You're wrong! We type-hint entities in our controller arguments all the time!". And you're right :). But that is not actually dependency injection. What I mean is, "dependency injection" usually refers to fetching services out of the container and passing them to you. For your controller methods, if you type-hint an entity class like User - Symfony is NOT fetching that out of the container. Nope - it's a different mechanism entirely - it is looking at your route (e.g. /users/{id}</code)>

  • 2019-01-15 Emin

    Hey weaverryan,

    Fo example your using "$user = new User();" so I was wondering can you put it like this ?


    public function loadData(ObjectManager $manager, User $user)

    Cheers!

  • 2019-01-14 weaverryan

    Hey Emin!

    I might have an answer for you... :). But which part of the code are you referring to? Are you referring to where we create the Factory instance directly in the fixture class? Something different?

    Cheers!

  • 2019-01-14 Emin

    Hey,

    I was wondering why are we not using Dependency Injection like always for better code? :)
    Btw great tutorial as always guys keep up the good work !

    Cheers!

  • 2019-01-14 Victor Bocharsky

    Hey Dinu,

    Thank you for sharing the solution with others! Yeah, you always need to look after your autocomplete, sometimes it does not something you need ;) Glad you figured out the problem yourself!

    Cheers!

  • 2019-01-14 Victor Bocharsky

    Hey OutspOaken,

    Great tip! Thank you for sharing it with us and others! Faker library has a lot of useful stuff ;)

    Cheers!

  • 2019-01-14 Dinu Brie

    bin/console doctrine:fixtures:load

    PHP Fatal error: Class App\DataFixtures\UserFixture contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (App\DataFixtures\BaseFixture::loadData) in /../../src/DataFixtures/UserFixture.php on line 22

    Fatal error: Class App\DataFixtures\UserFixture contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (App\DataFixtures\BaseFixture::loadData) in /../../src/DataFixtures/UserFixture.php on line 22

    In UserFixture.php line 22:

    Error: Class App\DataFixtures\UserFixture contains 1 abstract method and must therefore be declared abstrac
    t or implement the remaining methods (App\DataFixtures\BaseFixture::loadData)

    Upss my bad!

    class UserFixture extends Fixture NOT class UserFixture extends BaseFixture -> autocomplete typo

  • 2019-01-13 OutspOaken

    Hello,

    You can use $this->faker->safeEmail for the emails :)

  • 2018-12-09 weaverryan

    Thanks for the tip Campbell! I'll keep that in the mind for the future - so we can just choose a different table name that works for everyone :).

    Cheers!

  • 2018-12-08 Campbell

    Fun fact, I'm using postgres, and it appears that my User entity created a table that conflicted with postgres users.

    I found this when I got the following error from `AbstractPostgreSQLDriver`:

    `SQLSTATE[42601]: Syntax error: 7 ERROR: syntax error at or near "user"
    LINE 1: DELETE FROM user`

    If anyone else has this problem, it's easy to change the table name for the entity using annotations. Check out https://stackoverflow.com/q....

  • 2018-12-05 Victor Bocharsky

    Hey Kristof,

    Good question, so I decided to add a new code block to show actual changes, see this one:
    https://symfonycasts.com/sc...

    Btw, to look more changes, you can download the code, go to the start/ directory and open the file to see the actual changes.

    Cheers!

  • 2018-12-04 Kristof Kreimeyer

    Okay code blocks are added. Cool. But i have a question. Could you tell me what changes in createMany() ?
    Because i cannot find the changes in one of the code blocks but the text says: "The arguments to this method changed a bit since the last tutorial. "

  • 2018-11-05 Dan Abrey

    Many thanks, Ryan, that makes sense. I'll use the maker-bundle command a bit and take a look at the output improvements!

  • 2018-11-05 weaverryan

    Hey Dan Abrey!

    No big reason :). It's just a wrapper - I added it because it's shorter, has slightly better output, and you can find it when you're looking through the make: commands. You could use doctrine:migrations:diff exclusively and have the exact same result and experience.

    Cheers!

  • 2018-11-01 Dan Abrey

    Is there any reason to use `make:migration` instead of `doctrine:migrations:diff`? Or is it just a wrapper?

  • 2018-10-25 Diego Aguiar

    haha, someone is spying on you!

  • 2018-10-25 Matt Johnson

    My faker generated: 'firstname' => string 'Mathew'

    Bad faker... 2 Ts!

  • 2018-10-08 Diego Aguiar

    Hey Mamunur Rashid

    Good question. If you look at our "BaseFixture" class you can see that we set a Faker's generator object on the "faker" property of our class, that object has an interesting implementation, you can call the prorperty $this->faker->firstName and it will act as a method that returns a random name from a harcoded list that lives in the FakerBundle. It feel like magic but it works great

    Cheers!

  • 2018-10-08 Mamunur Rashid

    in `UserFixture`,
    `$user->setFirstName($this->faker->firstName);`
    i am sure about `$this->faker->firstName`, how faker is working here, like, it is a method?
    in the `BaseFixture.php`, you declare $faker as a variable,
    will you please explain it?

  • 2018-09-26 Fabio Restrepo

    Thanks @weaverryan, yes i had a typo in the class name. Solved.

  • 2018-09-25 weaverryan

    Hey Fabio Restrepo!

    Hmmm! That's interesting! So, in the video, I call it UserFixture (with no "s"). However, if you used the make:fixtures command and typed "UserFixtures" with an "s", that should be no problem - your filename and class would just be called different than mine. But, no big deal :). What's interesting is that the error suggests that you have a UserFixtures class (with an "s") but that the class name inside is UserFixture (no "s"). Perhaps you generated a UserFixtures class but then renamed it to UserFixture by watching the video? If so, just rename UserFixtures.php to UserFixture.php and (with any luck) that should fix everything.

    Let us know if that helps!

    Cheers!

  • 2018-09-22 Fabio Restrepo

    Hi, when run bin/console doctrine:fixtures:load throw a console error :

    In FileLoader.php line 168:

    The autoloader expected class "App\DataFixtures\UserFixtures" to be defined in file "/home/fabio/www/upgrade-heos/v
    endor/composer/../../src/DataFixtures/UserFixtures.php". The file was found but the class was not in it, the class
    name or namespace probably has a typo in /home/fabio/www/upgrade-heos/config/services.yaml (which is loaded in reso
    urce "/home/fabio/www/upgrade-heos/config/services.yaml").

    In DebugClassLoader.php line 288:

    The autoloader expected class "App\DataFixtures\UserFixtures" to be defined in file "/home/fabio/www/upgrade-heos/v
    endor/composer/../../src/DataFixtures/UserFixtures.php". The file was found but the class was not in it, the class
    name or namespace probably has a typo.

    Thanks.

  • 2018-09-12 weaverryan

    Hey Stijn!

    Ah, great question! You can find it in the course downloads for this course. Also, when we add code blocks for this chapter (will be very soon), we'll include a code block for BaseFixture so that you can expand its contents and get everything that way too :).

    Cheers!

  • 2018-09-12 Stijn Coolen

    Great video! But one thing, where can I find the improvements to the BaseFixture class?