Buy Access to Course
03.

Customizing the User Class

|

Share this awesome video!

|

What's cool about the User class is that... it's our class! As long as we implement UserInterface, we can add whatever else we want:

115 lines | src/Entity/User.php
// ... lines 1 - 7
use Symfony\Component\Security\Core\User\UserInterface;
// ... lines 9 - 12
class User implements UserInterface
{
// ... lines 15 - 113
}

For example, I'd like to store the first name of my users. So let's go add a property for that. At your terminal, run:

symfony console make:entity

We'll edit the User entity, add a firstName property, have it be a string, 255 length... and say "yes" to nullable. Let's make this property optional in the database.

Done! Back over in the User class, no surprises! We have a new property... and new getter and setter methods:

132 lines | src/Entity/User.php
// ... lines 1 - 12
class User implements UserInterface
{
// ... lines 15 - 31
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $firstName;
// ... lines 36 - 119
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(string $firstName): self
{
$this->firstName = $firstName;
return $this;
}
}

Go generate a migration for our new User. At the terminal, run

symfony console make:migration

Cool! Spin over and open that up to make sure it's not hiding any surprises:

32 lines | migrations/Version20211001172813.php
// ... lines 1 - 2
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20211001172813 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE user (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, first_name VARCHAR(255) DEFAULT NULL, UNIQUE INDEX UNIQ_8D93D649E7927C74 (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE user');
}
}

Awesome: CREATE TABLE user with id, email, roles and first_name columns. Close this... and run it:

symfony console doctrine:migrations:migrate

Success!

Adding User Fixtures

And because the User entity is... just a normal Doctrine entity, we can also add dummy users to our database using the fixtures system.

Open up src/DataFixtures/AppFixtures.php. We're using Foundry to help us load data. So let's create a new Foundry factory for User. Since we're having SO much fun running commands in this video, let's sneak in one... or three more:

symfony console make:factory

Yup! We want one for User. Go open it up: src/Factory/UserFactory.php:

61 lines | src/Factory/UserFactory.php
// ... lines 1 - 2
namespace App\Factory;
use App\Entity\User;
use App\Repository\UserRepository;
use Zenstruck\Foundry\RepositoryProxy;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Proxy;
// ... lines 10 - 28
final class UserFactory extends ModelFactory
{
public function __construct()
{
parent::__construct();
// TODO inject services if required (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services)
}
protected function getDefaults(): array
{
return [
// TODO add your default values here (https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories)
'email' => self::faker()->text(),
'roles' => [],
'firstName' => self::faker()->text(),
];
}
protected function initialize(): self
{
// see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
return $this
// ->afterInstantiate(function(User $user) {})
;
}
protected static function getClass(): string
{
return User::class;
}
}

Our job in getDefaults() is to make sure that all of the required properties have good default values. Set email to self::faker()->email(), I won't set any roles right now and set firstName to self::faker()->firstName():

59 lines | src/Factory/UserFactory.php
// ... lines 1 - 28
final class UserFactory extends ModelFactory
{
// ... lines 31 - 37
protected function getDefaults(): array
{
return [
'email' => self::faker()->email(),
'firstName' => self::faker()->firstName(),
];
}
// ... lines 45 - 57
}

Cool! Over in AppFixtures, at the bottom, create a user: UserFactory::createOne(). But use a specific email so we can log in using this later. How about, abraca_admin@example.com:

54 lines | src/DataFixtures/AppFixtures.php
// ... lines 1 - 11
use App\Factory\UserFactory;
// ... lines 13 - 15
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
// ... lines 20 - 41
AnswerFactory::new(function() use ($questions) {
// ... lines 43 - 45
})->needsApproval()->many(20)->create();
UserFactory::createOne(['email' => 'abraca_admin@example.com']);
// ... lines 49 - 51
}
}

Then, to fill out the system a bit, add UserFactory::createMany(10):

54 lines | src/DataFixtures/AppFixtures.php
// ... lines 1 - 11
use App\Factory\UserFactory;
// ... lines 13 - 15
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager)
{
// ... lines 20 - 47
UserFactory::createOne(['email' => 'abraca_admin@example.com']);
UserFactory::createMany(10);
// ... lines 50 - 51
}
}

Let's try it! Back at the terminal, run:

symfony console doctrine:fixtures:load

No errors! Check out the new table:

symfony console doctrine:query:sql 'SELECT * FROM user'

And... there they are! Now that we have users in the database, we need to add one or more ways for them to authenticate. It's time to build a login form!