Crear una entidad de usuario
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeNo hablaremos de seguridad en este tutorial. Pero aun así, necesitamos el concepto de usuario... porque cada tesoro de la base de datos será propiedad de un usuario... o, en realidad, de un dragón. Más adelante, utilizaremos esto para permitir a los usuarios de la API ver qué tesoros pertenecen a qué usuario y un montón de cosas más.
make:user
Así pues, vamos a crear esa clase User
. Busca tu terminal y ejecuta:
php bin/console make:user
Podríamos utilizar make:entity
, pero make:user
configurará un poco las cosas de seguridad que necesitaremos en un tutorial futuro. Llamemos a la clase User
, sí, vamos a almacenarlos en la base de datos, y establezcamos email
como campo identificador principal.
A continuación nos pregunta si necesitamos hash y comprobar las contraseñas de los usuarios. Si en tu sistema se va a almacenar la versión hash de las contraseñas de los usuarios, di que sí. Si tus usuarios no van a tener contraseñas -o algún sistema externo comprueba las contraseñas- responde que no. Di que sí a esto.
Esto no hizo mucho... ¡en el buen sentido! Nos dio una entidad User
, la clase repositorio... y una pequeña actualización de config/packages/security.yaml
. Sí, sólo configura el proveedor de usuarios: nada especial. Y, de nuevo, hablaremos de ello en un futuro tutorial.
Añadir una propiedad de nombre de usuario
Vale, dentro del directorio src/Entity/
, tenemos nuestra nueva clase de entidad User
con las propiedadesid
, email
y password
... y los getters y setters a continuación. Nada del otro mundo. Esto implementa dos interfaces que necesitamos para la seguridad... pero no son importantes ahora.
// ... lines 1 - 2 | |
namespace App\Entity; | |
use App\Repository\UserRepository; | |
use Doctrine\ORM\Mapping as ORM; | |
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface; | |
use Symfony\Component\Security\Core\User\UserInterface; | |
#[ORM\Entity(repositoryClass: UserRepository::class)] | |
#[ORM\Table(name: '`user`')] | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
#[ORM\Id] | |
#[ORM\GeneratedValue] | |
#[ORM\Column] | |
private ?int $id = null; | |
#[ORM\Column(length: 180, unique: true)] | |
private ?string $email = null; | |
#[ORM\Column] | |
private array $roles = []; | |
/** | |
* @var string The hashed password | |
*/ | |
#[ORM\Column] | |
private ?string $password = null; | |
// ... lines 30 - 99 | |
} |
Ah, pero quiero añadir un campo más a esta clase: un username
que podamos mostrar en la API.
Así que, vuelve a tu terminal y esta vez ejecuta:
php bin/console make:entity
Actualiza la clase User
, añade una propiedad username
, la longitud de 255
es buena, no nula... y listo. Pulsa enter una vez más para salir.
Vuelve a la clase... ¡perfecto! Ahí está el nuevo campo. Ya que estamos aquí, añadeunique: true
para que sea único en la base de datos.
// ... lines 1 - 11 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
// ... lines 14 - 30 | |
#[ORM\Column(length: 255, unique: true)] | |
private ?string $username = null; | |
// ... lines 33 - 114 | |
} |
¡Entidad terminada! Hagamos una migración para ella. De vuelta en el terminal ejecuta:
symfony console make:migration
Y abre el nuevo archivo de migración. Sin sorpresas: crea la tablauser
:
// ... 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 Version20230104193724 extends AbstractMigration | |
{ | |
// ... lines 15 - 19 | |
public function up(Schema $schema): void | |
{ | |
// this up() migration is auto-generated, please modify it to your needs | |
$this->addSql('CREATE SEQUENCE "user_id_seq" INCREMENT BY 1 MINVALUE 1 START 1'); | |
$this->addSql('CREATE TABLE "user" (id INT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, username VARCHAR(255) NOT NULL, PRIMARY KEY(id))'); | |
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)'); | |
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON "user" (username)'); | |
} | |
// ... lines 28 - 35 | |
} |
Ciérralo y ejecútalo con:
symfony console doctrine:migrations:migrate
Añadir la fábrica y los accesorios
¡Estupendo! Aunque, creo que nuestra nueva entidad se merece unas jugosas fijaciones de datos. Utilicemos Foundry como hicimos para DragonTreasure
. Empieza ejecutando
php bin/console make:factory
para generar la fábrica de User
.
Como antes, en el directorio src/Factory/
, tenemos una nueva clase - UserFactory
- que es realmente buena para crear objetos User
. Lo principal que tenemos que retocar es getDefaults()
para que los datos sean aún mejores. Voy a pegar nuevos contenidos para toda la clase, que puedes copiar del bloque de código de esta página.
// ... lines 1 - 2 | |
namespace App\Factory; | |
use App\Entity\User; | |
use App\Repository\UserRepository; | |
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; | |
use Zenstruck\Foundry\ModelFactory; | |
use Zenstruck\Foundry\Proxy; | |
use Zenstruck\Foundry\RepositoryProxy; | |
/** | |
* @extends ModelFactory<User> | |
// ... lines 14 - 29 | |
*/ | |
final class UserFactory extends ModelFactory | |
{ | |
const USERNAMES = [ | |
'FlamingInferno', | |
'ScaleSorcerer', | |
'TheDragonWithBadBreath', | |
'BurnedOut', | |
'ForgotMyOwnName', | |
'ClumsyClaws', | |
'HoarderOfUselessTrinkets', | |
]; | |
// ... lines 42 - 47 | |
public function __construct( | |
private UserPasswordHasherInterface $passwordHasher | |
) | |
{ | |
parent::__construct(); | |
} | |
// ... lines 54 - 59 | |
protected function getDefaults(): array | |
{ | |
return [ | |
'email' => self::faker()->email(), | |
'password' => 'password', | |
'username' => self::faker()->randomElement(self::USERNAMES) . self::faker()->randomNumber(3), | |
]; | |
} | |
// ... lines 68 - 71 | |
protected function initialize(): self | |
{ | |
return $this | |
->afterInstantiate(function(User $user): void { | |
$user->setPassword($this->passwordHasher->hashPassword( | |
$user, | |
$user->getPassword() | |
)); | |
}) | |
; | |
} | |
protected static function getClass(): string | |
{ | |
return User::class; | |
} | |
} |
Esto actualiza getDefaults()
para que tenga un poco más de chispa y establece password
en password
. Lo sé, creativo. También aprovecho un gancho de afterInstantiation
para hacer hash de esa contraseña.
Por último, para crear realmente algunos accesorios, abre AppFixtures
. Aquí es bastante sencillo: UserFactory::createMany()
y vamos a crear 10.
// ... lines 1 - 5 | |
use App\Factory\UserFactory; | |
// ... lines 7 - 9 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager): void | |
{ | |
DragonTreasureFactory::createMany(40); | |
UserFactory::createMany(10); | |
} | |
} |
Veamos si ha funcionado Gira y ejecuta:
symfony console doctrine:fixtures:load
¡Sin errores!
Comprobación de estado: tenemos una entidad User
y hemos creado una migración para ella. Diablos, ¡incluso hemos cargado algunos schweet data fixtures! Pero todavía no forma parte de nuestra API. Si actualizas la documentación, todavía sólo aparece Treasure
.
Hagamos que forme parte de nuestra API a continuación.
Hi,
Unfortunately there's no code block :/
[Edit] You can use the following (taken from finished project code):