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 SubscribeNuestro sitio tiene usuarios y estas preguntas son creadas por esos usuarios. Por lo tanto, en la base de datos, cada Question
debe estar relacionado con el User
que lo creó mediante una relación Doctrine. Ahora mismo, si abres src/Entity/Question.php
, ese no es el caso. No hay nada que lo relacione con el User
que lo creó. Es hora de arreglar eso. Lo necesitaremos para poder hablar correctamente de los votantes
Busca tu terminal y ejecuta:
symfony console make:entity
Vamos a modificar la entidad Question
y añadir una nueva propiedad llamada owner
, que será el "usuario" que posee este Question
. Necesitamos una relación ManyToOne. Si alguna vez no estás seguro, escribe "relación" y te guiará a través de un asistente para ayudarte. Será una relación con la clase User
... y la propiedad owner
no será anulable: cada pregunta debe ser propiedad de algún usuario.
A continuación nos pregunta si queremos mapear el otro lado de la relación para que podamos decir $user->getQuestions()
. Eso puede ser útil, así que digamos que sí. Y llamaremos a esa propiedad questions
. Por último, voy a decir que no a la eliminación de huérfanos. Y... ¡listo!
Si has pasado por nuestro tutorial sobre las relaciones de Doctrine, sabrás que aquí no hay nada especial. Esto añadió una relación ManyToOne
sobre una nueva propiedad $owner
... e hizo métodos getter y setter en la parte inferior:
... lines 1 - 16 | |
class Question | |
{ | |
... lines 19 - 64 | |
/** | |
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="questions") | |
* @ORM\JoinColumn(nullable=false) | |
*/ | |
private $owner; | |
... lines 70 - 227 | |
public function getOwner(): ?User | |
{ | |
return $this->owner; | |
} | |
public function setOwner(?User $owner): self | |
{ | |
$this->owner = $owner; | |
return $this; | |
} | |
} |
En la clase User
, también mapeó el lado inverso de la relación:
... lines 1 - 5 | |
use Doctrine\Common\Collections\ArrayCollection; | |
use Doctrine\Common\Collections\Collection; | |
... lines 8 - 17 | |
class User implements UserInterface, PasswordAuthenticatedUserInterface | |
{ | |
... lines 20 - 51 | |
/** | |
* @ORM\OneToMany(targetEntity=Question::class, mappedBy="owner") | |
*/ | |
private $questions; | |
public function __construct() | |
{ | |
$this->questions = new ArrayCollection(); | |
} | |
... lines 61 - 190 | |
/** | |
* @return Collection|Question[] | |
*/ | |
public function getQuestions(): Collection | |
{ | |
return $this->questions; | |
} | |
public function addQuestion(Question $question): self | |
{ | |
if (!$this->questions->contains($question)) { | |
$this->questions[] = $question; | |
$question->setOwner($this); | |
} | |
return $this; | |
} | |
public function removeQuestion(Question $question): self | |
{ | |
if ($this->questions->removeElement($question)) { | |
// set the owning side to null (unless already changed) | |
if ($question->getOwner() === $this) { | |
$question->setOwner(null); | |
} | |
} | |
return $this; | |
} | |
} |
Vamos a hacer una migración para este cambio:
symfony console make:migration
Y... como de costumbre, iremos al nuevo archivo de migración... para asegurarnos de que contiene sólo lo que esperamos. Sí: ALTER TABLE question
, añade owner_id
y luego las cosas de la clave foránea:
... lines 1 - 12 | |
final class Version20211012184326 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('ALTER TABLE question ADD owner_id INT NOT NULL'); | |
$this->addSql('ALTER TABLE question ADD CONSTRAINT FK_B6F7494E7E3C61F9 FOREIGN KEY (owner_id) REFERENCES user (id)'); | |
$this->addSql('CREATE INDEX IDX_B6F7494E7E3C61F9 ON question (owner_id)'); | |
} | |
public function down(Schema $schema): void | |
{ | |
// this down() migration is auto-generated, please modify it to your needs | |
$this->addSql('ALTER TABLE question DROP FOREIGN KEY FK_B6F7494E7E3C61F9'); | |
$this->addSql('DROP INDEX IDX_B6F7494E7E3C61F9 ON question'); | |
$this->addSql('ALTER TABLE question DROP owner_id'); | |
} | |
} |
Vamos a ejecutarlo:
symfony console doctrine:migrations:migrate
Y... ¡falló! No pasa nada. Falla porque ya hay filas en la tabla question
. Así que añadir un nuevo owner_id
NOT NULL
hace que esos registros existentes... exploten. En el tutorial de relaciones de Doctrine, hablamos de cómo manejar, arreglar y probar responsablemente las migraciones fallidas. Como ya hablamos de ello allí, voy a tomar el camino más fácil aquí y simplemente eliminar nuestra base de datos:
symfony console doctrine:database:drop --force
Luego crearé una base de datos nueva:
symfony console doctrine:database:create
Y migrar de nuevo.
symfony console doctrine:migrations:migrate
Ahora ya funciona. Recarga las instalaciones:
symfony console doctrine:fixtures:load
Y... ¡eso también explotó! ¡Vamos! La inserción en la pregunta está fallando porqueowner_id
no puede ser nula. Eso tiene sentido: aún no hemos entrado en nuestras instalaciones y no hemos asignado un propietario a cada pregunta.
Hagámoslo. Abre src/Factory/QuestionFactory.php
. Nuestro trabajo en getDefaults()
, es proporcionar un valor por defecto para cada propiedad requerida. Así que ahora voy a añadir una clave owner
establecida en UserFactory::new()
:
... lines 1 - 28 | |
final class QuestionFactory extends ModelFactory | |
{ | |
... lines 31 - 42 | |
protected function getDefaults(): array | |
{ | |
return [ | |
... lines 46 - 52 | |
'owner' => UserFactory::new(), | |
]; | |
} | |
... lines 56 - 68 | |
} |
Gracias a esto, si ejecutamos QuestionFactory
sin anular ninguna variable, se creará un nuevo usuario para cada nueva pregunta.
Pero dentro de nuestros accesorios, eso... no es exactamente lo que queremos. Dirígete a la parte inferior, donde creamos los usuarios. Lo que quiero hacer es crear primero estos usuarios. Y luego, cuando creemos las preguntas aquí arriba... oh, en realidad aquí mismo, quiero usar un usuario aleatorio de los que ya hemos creado.
Para ello, primero tenemos que mover nuestros usuarios a la parte superior para que se creen primero:
... lines 1 - 15 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
UserFactory::createOne([ | |
'email' => 'abraca_admin@example.com', | |
'roles' => ['ROLE_ADMIN'] | |
]); | |
UserFactory::createOne([ | |
'email' => 'abraca_user@example.com', | |
]); | |
UserFactory::createMany(10); | |
TagFactory::createMany(100); | |
... lines 30 - 61 | |
} | |
} |
Luego, aquí abajo para nuestras preguntas principales, pasar una función al segundo argumento y devolver un array... para que podamos anular la propiedad owner
. Ponlo enUserFactory::random()
:
... lines 1 - 15 | |
class AppFixtures extends Fixture | |
{ | |
public function load(ObjectManager $manager) | |
{ | |
... lines 20 - 30 | |
$questions = QuestionFactory::createMany(20, function() { | |
return [ | |
'owner' => UserFactory::random(), | |
]; | |
}); | |
... lines 36 - 61 | |
} | |
} |
No me voy a preocupar de hacer esto también para las preguntas no publicadas aquí abajo... pero podríamos.
Bien: probemos de nuevo los accesorios:
symfony console doctrine:fixtures:load
Esta vez... ¡funcionan!
¡Genial! Así que vamos a aprovechar la nueva relación en nuestro sitio para imprimir el propietario real de cada pregunta. También vamos a iniciar una página de edición de preguntas y luego... tendremos que averiguar cómo hacer que sólo el propietario de cada pregunta pueda acceder a ella.
Hey @Sahetmyrat ,
The Question::$user
field is supposed to be a real User
object while you're trying to set a user ID only. You need to pass the whole User
object here, i.e. do $question->setOwner($user);
.
Cheers!
Hey Sahetmyrat,
Awesome! :) Yeah, Doctrine operates objects because it's ORM, so you need to pass the whole object to it instead of just an ID
Cheers!
// composer.json
{
"require": {
"php": ">=8.1",
"ext-ctype": "*",
"ext-iconv": "*",
"babdev/pagerfanta-bundle": "^3.3", // v3.3.0
"composer/package-versions-deprecated": "^1.11", // 1.11.99.4
"doctrine/annotations": "^1.0", // 1.13.2
"doctrine/doctrine-bundle": "^2.1", // 2.6.3
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
"doctrine/orm": "^2.7", // 2.10.1
"knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
"knplabs/knp-time-bundle": "^1.11", // v1.16.1
"pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
"pagerfanta/twig": "^3.3", // v3.3.0
"phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
"scheb/2fa-bundle": "^5.12", // v5.12.1
"scheb/2fa-qr-code": "^5.12", // v5.12.1
"scheb/2fa-totp": "^5.12", // v5.12.1
"sensio/framework-extra-bundle": "^6.0", // v6.2.0
"stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
"symfony/asset": "5.3.*", // v5.3.4
"symfony/console": "5.3.*", // v5.3.7
"symfony/dotenv": "5.3.*", // v5.3.8
"symfony/flex": "^1.3.1", // v1.17.5
"symfony/form": "5.3.*", // v5.3.8
"symfony/framework-bundle": "5.3.*", // v5.3.8
"symfony/monolog-bundle": "^3.0", // v3.7.0
"symfony/property-access": "5.3.*", // v5.3.8
"symfony/property-info": "5.3.*", // v5.3.8
"symfony/rate-limiter": "5.3.*", // v5.3.4
"symfony/runtime": "5.3.*", // v5.3.4
"symfony/security-bundle": "5.3.*", // v5.3.8
"symfony/serializer": "5.3.*", // v5.3.8
"symfony/stopwatch": "5.3.*", // v5.3.4
"symfony/twig-bundle": "5.3.*", // v5.3.4
"symfony/ux-chartjs": "^1.3", // v1.3.0
"symfony/validator": "5.3.*", // v5.3.8
"symfony/webpack-encore-bundle": "^1.7", // v1.12.0
"symfony/yaml": "5.3.*", // v5.3.6
"symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
"twig/extra-bundle": "^2.12|^3.0", // v3.3.3
"twig/string-extra": "^3.3", // v3.3.3
"twig/twig": "^2.12|^3.0" // v3.3.3
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
"symfony/debug-bundle": "5.3.*", // v5.3.4
"symfony/maker-bundle": "^1.15", // v1.34.0
"symfony/var-dumper": "5.3.*", // v5.3.8
"symfony/web-profiler-bundle": "5.3.*", // v5.3.8
"zenstruck/foundry": "^1.1" // v1.13.3
}
}
Hello Ryan. I wrote route for creating new question, and I don't know correct way to insert owner to
owner_id
in my controller. But I tried this:$userId = $user->getId(); $question->setOwner($userId);
, and givesUnable to guess how to get a Doctrine instance from the request information for parameter "user".
Please give me hint to solve that. Thank you!