Следуя документации symfony, я пытаюсь встроить коллекцию в форму. Но при отправке почтового запроса на мою конечную точку встроенная форма с коллекцией People остается пустой. Мои сущности:
user.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* User
*
* @ORM\Entity
* @UniqueEntity(fields = "email", message = "Email already taken")
* @ORM\HasLifecycleCallbacks()
*/
class User implements UserInterface
{
use Mapping\UserTrait;
/**
* @param Person $person
*/
public function addPeople(Person $person)
{
$this->people->add($person);
$person->setOwner($this);
}
/**
* @param Person $person
*/
public function removePeople(Person $person)
{
$this->people->removeElement($person);
}
}
UserTrait.php
<?php
namespace App\Entity\Mapping;
use App\Entity\Person;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* User
*
* @ORM\Table()
*/
trait UserTrait
{
/**
* @var int
*
* @ORM\Column(name = "id", type = "integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy = "AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name = "hash", type = "string")
*/
private $hash;
/**
* @var bool
*
* @ORM\Column(name = "is_active", type = "boolean")
*/
private $isActive;
/**
* @var \DateTime
*
* @ORM\Column(name = "updated_at", type = "datetime")
*/
private $updatedAt;
/**
* @var \DateTime
*
* @ORM\Column(name = "created_at", type = "datetime")
*/
private $createdAt;
/**
* @var string
* @ORM\Column(name = "email", type = "string")
* @Assert\NotBlank
* @Assert\Email
*/
private $email;
/**
* @var \Doctrine\Common\Collections\Collection
*
* @ORM\OneToMany(targetEntity = "App\Entity\Person", mappedBy = "owner", cascade = {"persist"})
*/
private $people;
/**
* @var string
* @Assert\NotBlank
* @Assert\Length(max=4096)
*/
private $plainPassword;
/**
* Constructor
*/
public function __construct()
{
$this->people = new ArrayCollection();
$this->roles = ['ROLE_USER'];
$this->isActive = 1;
}
/**
* @return int
*/
public function getId(): int
{
return $this->id;
}
/**
* @param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* @return bool
*/
public function isActive(): bool
{
return $this->isActive;
}
/**
* @param bool $isActive
*/
public function setIsActive(bool $isActive): void
{
$this->isActive = $isActive;
}
/**
* @return string
*/
public function getEmail()
{
return $this->email;
}
/**
* @param string $email
*/
public function setEmail(string $email): void
{
$this->email = $email;
}
/**
* @return \Doctrine\Common\Collections\Collection
*/
public function getPeople(): \Doctrine\Common\Collections\Collection
{
return $this->people;
}
/**
* @param \Doctrine\Common\Collections\Collection $people
*/
public function setPeople(\Doctrine\Common\Collections\Collection $people): void
{
$this->people = $people;
}
/**
* @return string
*/
public function getPlainPassword()
{
return $this->plainPassword;
}
/**
* @param string $plainPassword
*/
public function setPlainPassword(string $plainPassword): void
{
$this->plainPassword = $plainPassword;
}
/**
* Implementation of UserInterface: Get password hash.
* @return string
*/
public function getPassword()
{
return $this->hash;
}
/**
* Implementation of UserInterface
*/
public function eraseCredentials()
{
$this->plainPassword = null;
}
/**
* Implementation of UserInterface
*/
public function getUsername()
{
return $this->email;
}
public function getSalt()
{
return null;
}
public function getRoles()
{
return ["User"];
}
/**
* @return string
*/
public function getHash(): string
{
return $this->hash;
}
/**
* @param string $hash
*/
public function setHash(string $hash): void
{
$this->hash = $hash;
}
/**
* @ORM\PrePersist
* @ORM\PreUpdate
*/
public function updatedTimestamps(): void
{
$dateTimeNow = new \DateTime('now');
$this->setUpdatedAt($dateTimeNow);
if ($this->getCreatedAt() === null) {
$this->setCreatedAt($dateTimeNow);
}
}
/**
* @return \DateTime
*/
public function getUpdatedAt(): \DateTime
{
return $this->updatedAt;
}
/**
* @param \DateTime $updatedAt
*/
public function setUpdatedAt(\DateTime $updatedAt): void
{
$this->updatedAt = $updatedAt;
}
/**
* @return \DateTime
*/
public function getCreatedAt()
{
return $this->createdAt;
}
/**
* @param \DateTime $createdAt
*/
public function setCreatedAt(\DateTime $createdAt): void
{
$this->createdAt = $createdAt;
}
}
Person.php
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Person
*
* @ORM\Entity
*/
class Person
{
use Mapping\PersonTrait;
}
PersonTrait.php
<?php
namespace App\Entity\Mapping;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* Person
*
* @ORM\Table()
*/
trait PersonTrait
{
/**
* @var int
*
* @ORM\Column(name = "id", type = "integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy = "AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name = "family_name", type = "string")
* @Assert\NotBlank
*/
private $familyName;
/**
* @var string
*
* @ORM\Column(name = "given_name", type = "string")
* @Assert\NotBlank
*/
private $givenName;
/**
* @var string
*
* @ORM\Column(name = "title", type = "string")
*/
private $title;
/**
* @var \DateTime
*
* @ORM\Column(name = "birth_date", type = "datetime")
*/
private $birthDate;
/**
* @var string
*
* @ORM\Column(name = "salutation", type = "string")
*/
private $salutation;
/**
* @var string
*
* @ORM\Column(name = "gender", type = "string")
*/
private $gender;
/**
* @var \App\Entity\User
*
* @ORM\ManyToOne(targetEntity = "App\Entity\User", inversedBy = "people")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name = "owner_id", referencedColumnName = "id")
* })
*/
private $owner;
/**
* @return string
*/
public function getFamilyName()
{
return $this->familyName;
}
/**
* @param string $familyName
*/
public function setFamilyName($familyName)
{
var_dump($familyName);
$this->familyName = $familyName;
}
/**
* @return string
*/
public function getGivenName()
{
return $this->givenName;
}
/**
* @param string $givenName
*/
public function setGivenName($givenName)
{
$this->givenName = $givenName;
}
/**
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* @param string $title
*/
public function setTitle(string $title): void
{
$this->title = $title;
}
/**
* @return \DateTime
*/
public function getBirthDate(): \DateTime
{
return $this->birthDate;
}
/**
* @param \DateTime $birthDate
*/
public function setBirthDate(\DateTime $birthDate): void
{
$this->birthDate = $birthDate;
}
/**
* @return string
*/
public function getSalutation(): string
{
return $this->salutation;
}
/**
* @param string $salutation
*/
public function setSalutation(string $salutation): void
{
$this->salutation = $salutation;
}
/**
* @return string
*/
public function getGender(): string
{
return $this->gender;
}
/**
* @param string $gender
*/
public function setGender(string $gender): void
{
$this->gender = $gender;
}
/**
* @return \App\Entity\User
*/
public function getOwner(): \App\Entity\User
{
return $this->owner;
}
/**
* @param \App\Entity\User $owner
*/
public function setOwner(\App\Entity\User $owner): void
{
$this->owner = $owner;
}
}
Теперь о моих типах форм:
UserType.php
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class UserType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('email', EmailType::class)
->add('plainPassword', PasswordType::class)
->add(
"people",
CollectionType::class,
[
'entry_type' => PersonType::class,
'allow_add' => true,
'by_reference' => false,
]
);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => User::class,
'csrf_protection' => false
]
);
}
}
PersonType.php
<?php
namespace App\Form;
use App\Entity\Person;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
class PersonType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('familyName', TextType::class)
->add('givenName', TextType::class);
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(
[
'data_class' => Person::class,
'csrf_protection' => false,
]
);
}
}
Используя эти типы, я сейчас пытаюсь зарегистрировать пользователя, создать человека и добавить его пользователю, используя этот код:
/**
* @Route("/register")
* @param Request $request
* @param UserPasswordEncoderInterface $passwordEncoder
*/
public function register(Request $request, UserPasswordEncoderInterface $passwordEncoder)
{
$user = new User();
$form = $this->createForm(UserType::class, $user);
$form->submit($request->request->all());
if ($form->isSubmitted() && $form->isValid()) {
$password = $passwordEncoder->encodePassword($user, $user->getPlainPassword());
$user->setHash($password);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
return new Response("ok", 300);
}
return new Response("not ok", 500);
}
Моя проблема сейчас в том, что когда я отправляю почтовый запрос почтальону со следующими параметрами и содержимым:
email: [email protected]
plainPassword: test1234
people.familyName: testLastname
people.givenName: testFirstname
Я получаю следующую ошибку, которая означает, что он не распознает данные для физического лица
"This form should not contain extra fields."
Как заставить формы symfony распознавать, что people.givenName и people.familyName предназначены для создания экземпляра Person
Обновлено: комментарий u_mulder предложил изменить тело сообщения на person [0] .givenName, и теперь я получаю сообщение об ошибке
This value is not valid.
Спасибо, на самом деле хороший намек, спасибо за это! Я это протестирую!
После изменения я получаю «это значение недействительно»
Утверждение, что givenName "TestFirstname" недействительно
не могли бы вы опубликовать вид вашей формы, пожалуйста?
Как я объяснил в вопросе, я использую почтальон для создания запроса POST
Сделайте рендеринг вашей формы, введите несколько значений и опубликуйте. Убедитесь, что все работает, а затем посмотрите опубликованные данные. Компонент форм Symfony использует несколько сложное соглашение об именах элементов, чтобы отображать опубликованные данные обратно в объекты. Как только вы узнаете точный формат, вы можете вернуться к использованию почтальона, если хотите. И да, ответ ниже пытается исправить симптомы, а не основную причину.
@Cerad, а вам не достаточно отладки данных запроса в контроллере? Зачем мне для этого нужна отрисованная форма?
Но на самом деле вы правы, что возникла проблема с содержанием запроса. : D
Потому что я почти уверен, что имена ваших элементов, которые вы используете, неверны. Визуализация формы точно скажет вам, каковы их имена. Как только вы узнаете их наверняка, вы сможете избавиться от визуализированной формы. Опять же, компонент формы использует собственный шаблон именования. И не удивляйся, что я был прав. Юмор.
@Cerad, проблема на самом деле была очень простой: мне нужно назвать параметр people [0] [familyName] вместо people [0] ["familyName"]






У меня было две проблемы, которые нужно было исправить. Первый намек от u_mulder заключался в том, что я должен использовать индекс для людей, так как это коллекция. Так что вместо
people["givenName"]
Мне пришлось использовать
people[0]["givenName"]
Вторая ошибка заключалась в неправильной структуре тела почтальона. Вместо
people[0]["givenName"]
Пришлось убрать кавычки.
people[0][givenName]
После устранения обеих проблем формы работают должным образом.
Коллекция означает наличие массива подэлементов. Итак, подэлементы должны быть как-то проиндексированы, например,
people[0].familyName: testLastnameили что-то подобное.