Я использую SYMFONY 5 и настроил форму сбора, в которой пользователь может создать службу и добавить в нее многочисленные вспомогательные службы. Это работает нормально, и пользователь может добавлять / редактировать / показывать / удалять службы, а также подуслуги.
Теперь я хочу проверить, добавлена ли новая подуслуга и форма отправлена, язык элемента вспомогательного обслуживания должен быть языком элемента обслуживания. Если нет, база данных не будет обновлена, и пользователь получит сообщение об ошибке (см. Снимок экрана). Это также работает нормально с одним исключением: я не могу прикрепить сообщение об ошибке к отказавшему вспомогательному сервису! Он появляется в каждой подуслуге.
Вот определение моей службы сущности:
namespace App\Entity;
use Symfony\Component\Validator\Constraints as Assert;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\ServicesRepository")
*/
class Services
{
/**
* @ORM\Id()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string", length=3)
*/
private $sprache;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $transid;
/**
* @ORM\Column(type="string", length=200)
*/
private $header;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $body;
/**
* @ORM\OneToMany(targetEntity="App\Entity\SubServices", mappedBy="services",cascade={"persist"})
* @Assert\Valid()
*/
private $subServices;
public function __construct()
{
$this->subServices = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id): self
{
$this->id = $id;
return $this;
}
public function getTransId(): ?int
{
return $this->transid;
}
public function setTransId(int $transid): self
{
$this->transid = $transid;
return $this;
}
public function getSprache(): ?string
{
return $this->sprache;
}
public function setSprache(string $sprache): self
{
$this->sprache = $sprache;
return $this;
}
public function getHeader(): ?string
{
return $this->header;
}
public function setHeader(string $header): self
{
$this->header = $header;
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
public function setBody(?string $body): self
{
$this->body = $body;
return $this;
}
/**
* @return Collection|SubServices[]
*/
public function getSubServices(): Collection
{
return $this->subServices;
}
public function addSubService(SubServices $subService): self
{
if (!$this->subServices->contains($subService)) {
$this->subServices[] = $subService;
$subService->setServices($this);
}
return $this;
}
public function removeSubService(SubServices $subService): self
{
if ($this->subServices->contains($subService)) {
$this->subServices->removeElement($subService);
// set the owning side to null (unless already changed)
if ($subService->getServices() === $this) {
$subService->setServices(null);
}
}
return $this;
}
}
Как вы можете видеть, в сущности службы я поместил @Assert \ Valid () для субсервисы.
Вот определение сущности субсервиса:
<?php
namespace App\Entity;
use App\Validator\SubServiceSprache;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity(repositoryClass="App\Repository\SubServicesRepository")
* @SubServiceSprache()
*/
class SubServices
{
/**
* @ORM\Id()
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="integer", nullable=true)
*/
private $transsubid;
/**
* @ORM\Column(type="string", length=3)
*/
private $sprache;
/**
* @ORM\ManyToOne(targetEntity="App\Entity\Services", inversedBy="subServices")
*/
private $services;
/**
* @ORM\Column(type="string", length=200)
*/
private $header;
/**
* @ORM\Column(type="text", nullable=true)
*/
private $body;
public function getId(): ?int
{
return $this->id;
}
public function setId(int $id)
{
$this->id = $id;
}
public function getTransSubId(): ?int
{
return $this->transsubid;
}
public function setTransSubId(int $transsubid): self
{
$this->transsubid = $transsubid;
return $this;
}
public function getsprache(): ?string
{
return $this->sprache;
}
public function setsprache(string $sprache): self
{
$this->sprache = $sprache;
return $this;
}
public function getServices(): ?Services
{
return $this->services;
}
public function setServices(?Services $services): self
{
$this->services = $services;
return $this;
}
public function getHeader(): ?string
{
return $this->header;
}
public function setHeader(string $header): self
{
$this->header = $header;
return $this;
}
public function getBody(): ?string
{
return $this->body;
}
public function setBody(?string $body): self
{
$this->body = $body;
return $this;
}
}
Как видите, для всего субсервиса класса я поставил валидацию @SubServiceSprache ().
Вот определение валидатора SubServiceSprache:
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class SubServiceSprache extends Constraint
{
public function validatedBy()
{
return \get_class($this).'Validator';
}
public function getTargets()
{
//PROPERTY_CONSTRAINT wenn zB. EMAIL geprüft werden soll
//CLASS_CONSTRAINT wenn ganze Entity geprüft werden soll
// jeweils das Objekt (EMAIL od. ganzes Klassenobjekt wird übergeben
return self::CLASS_CONSTRAINT;
}
}
А вот логи проверки c в SubServiceSpracheValidator:
<?php
namespace App\Validator;
use App\Entity\Services;
use App\Entity\SubServices;
use Symfony\Component\Validator\ConstraintValidator;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Contracts\Translation\TranslatorInterface;
class SubServiceSpracheValidator extends ConstraintValidator
{
private $em;
private $subservice;
private $translator;
public function __construct(EntityManagerInterface $em, TranslatorInterface $translator)
{
$this->em = $em;
$this->translator = $translator;
$this->subservice = new SubServices();
}
public function validate($object, Constraint $constraint)
{
// Ist die Sprache des SubService die des Service?
if ($object instanceof SubServices) {
if($object->getServices()->getSprache() != $object->getsprache()){
// Message Translation
$message = $this->translator->trans('subservice_sprachcheck',
['subsprache' => object->getsprache(),'servsprache' => $object->getServices()->getsprache()]
);
// Assign message
$this->context->buildViolation($message)
->atPath('sprache')
->addViolation();
}
}
}
}
Вот фрагмент класса формы для услуги:
->add('subservices', CollectionType::class,
array('entry_type' => SubservicesFormType::class,
'label' => false,
'entry_options' => array('label' => false),
'allow_add' => true,
'allow_delete' => true,
'by_reference' => false,
'error_bubbling' => false,
))
->add('save', SubmitType::class,
array('label' => 'Sichern',
'attr' => array('class' => 'buttonsave')
))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => Services::class,
'error_bubbling' => false,
//'newid' => false,
]);
}
и вот один для вспомогательных услуг:
class SubservicesFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('sprache', LanguageType::class,
array('label' => 'Sprache',
'disabled' => false,
'attr' => array('class' => 'form-control'),
'choice_loader' => NULL,
'choices' => ['DEUTSCH' => 'de', 'ENGLISCH' => 'en'],
'choice_translation_domain' => true,
))
->add('header', TextType::class,
array('label' => 'Überschrift',
'attr' => array('class' => 'form-control')))
->add('body', TextareaType::class,
array('label' => 'Beschreibung',
'attr' => array('class' => 'form-control')))
;
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'data_class' => SubServices::class,
'validation_groups' => ['Default'],
]);
}
}
и, наконец, мой файл ветки:
{% extends 'base.html.twig' %}
{% import _self as formMacros %}
{% block title %}UB Mollekopf{% endblock %}
{% block stylesheets %}
<link rel="stylesheet" href="{{ absolute_url('/css/ub_styles.css') }}" type="text/css" media="all">
<link rel="stylesheet" href="{{ absolute_url('css/font-awesome.css') }}">
{% endblock %}
{% macro printSubserviceRow(SubservicesFormType) %}
<td class="subserviceformsprache">{{ form_widget(SubservicesFormType.sprache) }}</td>
<td class="subserviceformheader">{{ form_widget(SubservicesFormType.header) }}</td>
<td class="subserviceformbody">{{ form_widget(SubservicesFormType.body) }}</td>
<td class="subserviceformaction"></td>
{% endmacro %}
{% block body %}
<div class="tableserviceedit">
{{ form_start(form) }}
<div class="tableheadereditservice">
<table id="editserviceheader">
<tr style="white-space: nowrap">
<th style="width: 100%; padding-left: 0.5em">{% trans %}Ändern Service{% endtrans %} {{ form_widget(form.id) }}</th>
</tr>
</table>
</div>
<div class="tablebodyeditservice">
<table id="editservicesingleheader">
<tr>
<th style="width: 3.5em;">{% trans %}Sprache{% endtrans %}</th>
<th style="width: 12em">{% trans %}Überschrift{% endtrans %}</th>
<th style="width: 15em">{% trans %}Beschreibung{% endtrans %}</th>
</tr>
<tr class="editserviceheader">
<td class="serviceformsprache">
{{ form_errors(form.sprache) }}
{{ form_widget(form.sprache) }}
</td>
<td class="serviceformheader">
{{ form_errors(form.header) }}
{{ form_widget(form.header) }}
</td>
<td class="serviceformbody">
{{ form_errors(form.body) }}
{{ form_widget(form.body) }}
</td>
</tr>
</table>
<div class="tablebodysubservices">
<table id="subservices">
<thead>
<tr>
<th style="width: 6em;">{% trans %}Sprache{% endtrans %}</th>
<th style="width: 22.2em">{% trans %}Überschrift{% endtrans %}</th>
<th style="width: 15em">{% trans %}Beschreibung{% endtrans %}</th>
</tr>
</thead>
<tbody id="collector" data-prototype="{{ formMacros.printSubserviceRow(form.subservices.vars.prototype)|e('html_attr') }}">
{% for subservice in form.subservices %}
<tr>
<td colspan="4">
<span style="color:red" > {{ form_errors(form) }}</span>
</td>
</tr>
<tr>
<td class="subserviceformsprache">
{{ form_widget(subservice.sprache) }}
</td>
<td class="subserviceformheader">
{{ form_widget(subservice.header) }}
</td>
<td class="subserviceformbody">
{{ form_widget(subservice.body) }}
</td>
<td class="subserviceformaction"></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="tablefooter" id="fussbereichnewservice">
<div class="btnfooter">{{ form_widget(form.save) }} <button type="" class="buttonabort"><a href="{{path('services_maintain', { _locale: locale }) }}" style="color: white">{% trans %}Abbruch{% endtrans %}</a></button></div>
</div>
{{ form_end(form) }}
{#</div>#}
{{ include('inc/navbar_bottom.html.twig') }}
{% endblock %}
{% block javascripts %}
<script src="{{ absolute_url('/js/main.js') }}"></script>
{% if locale == 'en' %}
<script src="{{ absolute_url('/js/subservicesen.js') }}"></script>
{% else %}
<script src="{{ absolute_url('/js/subservices.js') }}"></script>
{% endif %}
{% endblock %}
В ветке файл шаблона, который я пробовал в нескольких возможностях: если я кодирую {{form_errors (form)}}, сообщение об ошибке будет отображаться в каждом вспомогательном сервисе, если я кодирую {{form_errors (form.sprache)}}, сообщение об ошибке не появится при все.
Кто-нибудь есть идея, чтобы решить эту проблему?