Я много часов пытаюсь сохранить свои встроенные коллекции в БД. Это не работает и даже делает что-то другое.
У меня есть объект User OneToMany с адресом объекта.
У меня есть форма для встраивания, но я не могу сохранить ее в БД.
Хуже того, когда я добавляю свойство by_reference => false в поле для встраивания, оно удаляет существующий адрес, который у меня был :/ .
Я не знаю, что я делаю неправильно.
Я попытался добавить setAdress в свой объект User, но это не помогает.
Я пытался добавить Cascade, но тоже не работает.
Вероятно, это проблема с моим контроллером (edit_profil), но я не вижу, с каким именно.
Теперь я получаю сообщение об ошибке Не удалось определить тип доступа для свойства "адрес" в классе "App\Entity\User".
ОБНОВЛЕНИЕ1
с параметром by_reference, установленным на false, теперь у меня есть эта ошибка: Ожидаемый аргумент типа «App\Entity\Address or null», «экземпляр Doctrine\ORM\PersistentCollection», указанный в пути к свойству «адрес».
ОБНОВЛЕНИЕ2
Я удалил сеттер, потому что у меня есть метод добавления и удаления. Я также использовал множественное и единственное число для адреса, как это делается с тегом в документации.
Теперь я вижу, что адрес формы не отправлен (он даже удаляет существующий)
(удаляю в коде то, что не нужно)
Вот мои файлы:
RegistrationFormType
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Form\CallbackTransformer;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('roles',ChoiceType::class,[
'choices'=>[
'Specialist'=>'Role_Specialist',
'Parent'=>'Role_Parent',
'Center'=>'Role_Center',],
'label'=>"Je m'inscris en tant que"])
->add('name')
//...//
->add('addresses',CollectionType::class,[
'entry_type' => AddressType::class,
'entry_options' => ['label' => false],
'allow_add' => true,
'allow_delete' => true,
'by_reference' =>false,
])
;
Объект пользователя:
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @ORM\Entity(repositoryClass=UserRepository::class)
* @UniqueEntity(fields={"email"}, message="There is already an account with this email")
*/
class User implements UserInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
//...//
/**
* @ORM\OneToMany(targetEntity=Address::class, mappedBy="user", orphanRemoval=true, cascade={"persist"})
*/
private $addresses;
public function __construct()
{
$this->category = new ArrayCollection();
$this->schedules = new ArrayCollection();
$this->bookings = new ArrayCollection();
$this->addresses = new ArrayCollection();
}
//...//
/**
* @return Collection|Addresses[]
*/
public function getAddresses(): Collection
{
return $this->addresses;
}
public function addAddress(Address $address): self
{
$address->setUser($this);
$this->adresse->add($address);
// if (!$this->addresses->contains($address)) {
// $this->addresses[] = $address;
// $address->setUser($this);
// }
return $this;
}
public function removeAddress(Address $address): self
{
if ($this->addresses->removeElement($address)) {
// set the owning side to null (unless already changed)
if ($address->getUser() === $this) {
$address->setUser(null);
}
}
return $this;
}
}
Контроллер регистрации
<?php
namespace App\Controller;
use App\Entity\Address;
use App\Entity\User;
use App\Form\AddressType;
use App\Form\RegistrationFormType;
use App\Security\EmailVerifier;
use App\Security\LoginFormAuthenticator;
use Doctrine\ORM\EntityManager;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
class RegistrationController extends AbstractController
{
private $emailVerifier;
public function __construct(EmailVerifier $emailVerifier)
{
$this->emailVerifier = $emailVerifier;
}
//...//
/**
* @Route("/profil/{id}", name="edit_account")
*/
public function editUser(Request $request,int $id)
{
$entityManager = $this->getDoctrine()->getManager();
//Récupération de l'entité User avec l'ID passé et création du formulaire
$repoUser = $this->getDoctrine()
->getRepository(User::class);
$user = $repoUser->findOneBy([
'id'=>$id,
]);
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$entityManager->persist($user);
$entityManager->flush();
$this->addFlash('success', 'User Created! Knowledge is power!');
return $this->redirectToRoute('edit_account',[
'id'=>$id,
]);
}
return $this->render('profil/edit_profil.html.twig', [
'registrationForm' => $form->createView(),
]);
}
/**
* @Route("/verify/email", name="app_verify_email")
*/
public function verifyUserEmail(Request $request): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
// validate email confirmation link, sets User::isVerified=true and persists
try {
$this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
} catch (VerifyEmailExceptionInterface $exception) {
$this->addFlash('verify_email_error', $exception->getReason());
return $this->redirectToRoute('app_register');
}
// @TODO Change the redirect on success and handle or remove the flash message in your templates
$this->addFlash('success', 'Your email address has been verified.');
return $this->redirectToRoute('app_register');
}
}
Адрес объекта
<?php
namespace App\Entity;
use App\Repository\AddressRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass=AddressRepository::class)
*/
class Address
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=255)
*/
private $type;
/**
* @ORM\Column(type="integer")
*/
private $housenumber;
/**
* @ORM\Column(type="string", length=10, nullable=true)
*/
private $additional_info;
/**
* @ORM\Column(type="string", length=255)
*/
private $street;
/**
* @ORM\OneToMany(targetEntity=Schedule::class, mappedBy="address_fk")
*/
private $schedules;
/**
* @ORM\Column(type="float", nullable=true)
*/
private $latitude;
/**
* @ORM\Column(type="float", nullable=true)
*/
private $longitude;
/**
* @ORM\Column(type="string", length=255)
*/
private $city;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $region;
/**
* @ORM\Column(type="string", length=255)
*/
private $country;
/**
* @ORM\Column(type="string", length=255, nullable=true)
*/
private $postalcode;
/**
* @ORM\ManyToOne(targetEntity=User::class, inversedBy="addresses")
* @ORM\JoinColumn(nullable=false)
*/
private $user;
public function __construct()
{
$this->schedules = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getType(): ?string
{
return $this->type;
}
public function setType(string $type): self
{
$this->type = $type;
return $this;
}
public function getHousenumber(): ?int
{
return $this->housenumber;
}
public function setHousenumber(int $housenumber): self
{
$this->housenumber = $housenumber;
return $this;
}
public function getAdditionalInfo(): ?string
{
return $this->additional_info;
}
public function setAdditionalInfo(?string $additional_info): self
{
$this->additional_info = $additional_info;
return $this;
}
public function getStreet(): ?string
{
return $this->street;
}
public function setStreet(string $street): self
{
$this->street = $street;
return $this;
}
/**
* @return Collection|Schedule[]
*/
public function getSchedules(): Collection
{
return $this->schedules;
}
public function addSchedule(Schedule $schedule): self
{
if (!$this->schedules->contains($schedule)) {
$this->schedules[] = $schedule;
$schedule->setAddressFk($this);
}
return $this;
}
public function removeSchedule(Schedule $schedule): self
{
if ($this->schedules->removeElement($schedule)) {
// set the owning side to null (unless already changed)
if ($schedule->getAddressFk() === $this) {
$schedule->setAddressFk(null);
}
}
return $this;
}
public function getLatitude(): ?float
{
return $this->latitude;
}
public function setLatitude(?float $latitude): self
{
$this->latitude = $latitude;
return $this;
}
public function getLongitude(): ?float
{
return $this->longitude;
}
public function setLongitude(?float $longitude): self
{
$this->longitude = $longitude;
return $this;
}
public function getCity(): ?string
{
return $this->city;
}
public function setCity(string $city): self
{
$this->city = $city;
return $this;
}
public function getRegion(): ?string
{
return $this->region;
}
public function setRegion(?string $region): self
{
$this->region = $region;
return $this;
}
public function getCountry(): ?string
{
return $this->country;
}
public function setCountry(string $country): self
{
$this->country = $country;
return $this;
}
public function getPostalcode(): ?string
{
return $this->postalcode;
}
public function setPostalcode(?string $postalcode): self
{
$this->postalcode = $postalcode;
return $this;
}
public function getUser(): ?User
{
return $this->user;
}
public function setUser(?User $user): self
{
$this->user = $user;
return $this;
}
}
Edit_profil Twig
{% extends 'base.html.twig' %}
{#{% macro niceForm(address) %}#}
{# <div class="form-group col-md-4">{{ form_row(address.type) }}</div>#}
{# <div class="form-row">#}
{# <div class="form-group col-md-4">{{ form_row(address.street) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(address.housenumber) }}</div>#}
{# <div class="form-group col-md-4">{{ form_row(address.additional_info) }}</div>#}
{# </div>#}
{# <div class="form-row">#}
{# <div class="form-group col-md-3">{{ form_row(address.postalcode) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(address.city) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(address.region) }}</div>#}
{# <div class="form-group col-md-3">{{ form_row(address.country) }}</div>#}
{# </div>#}
{#{% endmacro %}#}
{#{% import _self as formMacros %}#}
{% block title %}Edit Profil{% endblock %}
{% block body %}
{% for flashError in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flashError }}</div>
{% endfor %}
<div class="container">
{{ form_start(registrationForm) }}
<div class="form-group">{{ form_row(registrationForm.roles) }}</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.name) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.firstname) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-6">{{ form_row(registrationForm.email) }}</div>
<div class="form-group col-md-6">{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}</div>
</div>
<div class="form-row">
<div class="form-group col-md-2">{{ form_row(registrationForm.prefix) }}</div>
<div class="form-group col-md-3">{{ form_row(registrationForm.phone) }}</div>
<div class="form-group col-md-3">{{ form_row(registrationForm.NISS, {label: 'NISS'}) }}</div>
<div class="form-group col-md-4">{{ form_row(registrationForm.BCE, {label: 'BCE'}) }}</div>
</div>
</div>
{# @TODO formatter pour qu'on differencie bien les addresses entre elles par block. Cf block en dessous#}
<div class="container">
<h3>Addresses</h3>
<ul class="addresses" data-index="{{ registrationForm.addresses|length > 0 ? registrationForm.addresses|last.vars.name + 1 : 0 }}" data-prototype="{{ form_widget(registrationForm.addresses.vars.prototype)|e('html_attr') }}">
{% for address in registrationForm.addresses %}
<li>{{ form_row(address.type) }}</li>
<li>{{ form_row(address.street) }}</li>
<li>{{ form_row(address.housenumber) }}</li>
<li>{{ form_row(address.additional_info) }}</li>
<li>{{ form_row(address.postalcode) }}</li>
<li>{{ form_row(address.city) }}</li>
<li>{{ form_row(address.region) }}</li>
<li>{{ form_row(address.country) }}</li>
{% endfor %}
</ul>
<button type="button" class="add_item_link" data-collection-holder-class="addresses">Add an address</button>
<button type="submit" id="submitRegister" class="btn">Register</button>
{{ form_end(registrationForm) }}
</div>
<script>
const addFormToCollection = (e) => {
const collectionHolder = document.querySelector('.' + e.currentTarget.dataset.collectionHolderClass);
const item = document.createElement('li');
item.innerHTML = collectionHolder
.dataset
.prototype
.replace(
/__name__/g,
collectionHolder.dataset.index
);
collectionHolder.appendChild(item);
collectionHolder.dataset.index++;
};
document
.querySelectorAll('.add_item_link')
.forEach(btn => btn.addEventListener("click", addFormToCollection));
</script>
Надеюсь, один из вас сможет увидеть, какая у меня проблема :)
Спасибо
Вам не нужно создавать экземпляр формы
AddressType
, поскольку она встроена в формуRegistrationType
и отображается в вашем шаблоне изform_row(address.type)
. Удалите$formAddress = $this->createForm(AddressType::class, $address);
и все ссылки на него. Затем добавьте адрес вUser
в вашем контроллере$user->addAddress($address)
. Также ваша ссылкаsetAdress(?Adress $adress):
должна использовать коллекцию$this->address = new ArrayCollection($address ?? [])
, хотя я рекомендую удалить этот метод.Спасибо за ответ. Я заметил что-то непонятное в моем вопросе. Функция, отображающая мою ветку edit_profil, называется editUser. Первая функция используется для регистрации пользователя в первый раз, и именно здесь я создаю экземпляр AdresseType. Идея заключалась в том, чтобы иметь одну форму с одним адресом для заполнения. На самом деле мой вопрос был больше для editUser. Извините за путаницу и спасибо за быстрый ответ!