From 98e179ba067e5f90b1322d0fc8097092811275b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 3 Jul 2023 00:28:37 +0200 Subject: [PATCH] Validate bom when adding additional bom entries via addPart controller to prevent invalid BOMs This fixes issue #302 --- src/Controller/ProjectController.php | 23 +++-- .../ProjectSystem/ProjectAddPartsType.php | 84 +++++++++++++++++++ 2 files changed, 95 insertions(+), 12 deletions(-) create mode 100644 src/Form/ProjectSystem/ProjectAddPartsType.php diff --git a/src/Controller/ProjectController.php b/src/Controller/ProjectController.php index 08ddc258..8c192319 100644 --- a/src/Controller/ProjectController.php +++ b/src/Controller/ProjectController.php @@ -26,21 +26,26 @@ use App\DataTables\ProjectBomEntriesDataTable; use App\Entity\Parts\Part; use App\Entity\ProjectSystem\Project; use App\Entity\ProjectSystem\ProjectBOMEntry; +use App\Form\ProjectSystem\ProjectAddPartsType; use App\Form\ProjectSystem\ProjectBOMEntryCollectionType; use App\Form\ProjectSystem\ProjectBuildType; use App\Form\Type\StructuralEntityType; use App\Helpers\Projects\ProjectBuildRequest; use App\Services\ImportExportSystem\BOMImporter; use App\Services\ProjectSystem\ProjectBuildHelper; +use App\Validator\Constraints\UniqueObjectCollection; use Doctrine\Common\Collections\ArrayCollection; use Doctrine\ORM\EntityManagerInterface; use League\Csv\SyntaxError; use Omines\DataTablesBundle\DataTableFactory; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntityValidator; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\FileType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormEvents; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; @@ -201,19 +206,10 @@ class ProjectController extends AbstractController $this->denyAccessUnlessGranted('@projects.edit'); } - $builder = $this->createFormBuilder(); - $builder->add('project', StructuralEntityType::class, [ - 'class' => Project::class, - 'required' => true, - 'disabled' => $project instanceof Project, //If a project is given, disable the field - 'data' => $project, - 'constraints' => [ - new NotNull() - ] + $form = $this->createForm(ProjectAddPartsType::class, null, [ + 'project' => $project, ]); - $builder->add('bom_entries', ProjectBOMEntryCollectionType::class); - $builder->add('submit', SubmitType::class, ['label' => 'save']); - $form = $builder->getForm(); + //Preset the BOM entries with the selected parts, when the form was not submitted yet $preset_data = new ArrayCollection(); @@ -249,8 +245,11 @@ class ProjectController extends AbstractController foreach ($bom_entries as $bom_entry){ $target_project->addBOMEntry($bom_entry); } + + $entityManager->flush(); + //If a redirect query parameter is set, redirect to this page if ($request->query->get('_redirect')) { return $this->redirect($request->query->get('_redirect')); diff --git a/src/Form/ProjectSystem/ProjectAddPartsType.php b/src/Form/ProjectSystem/ProjectAddPartsType.php new file mode 100644 index 00000000..f89f3567 --- /dev/null +++ b/src/Form/ProjectSystem/ProjectAddPartsType.php @@ -0,0 +1,84 @@ +. + */ + +namespace App\Form\ProjectSystem; + +use App\Entity\ProjectSystem\Project; +use App\Entity\ProjectSystem\ProjectBOMEntry; +use App\Form\Type\StructuralEntityType; +use App\Validator\Constraints\UniqueObjectCollection; +use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; +use Symfony\Component\Form\AbstractType; +use Symfony\Component\Form\Extension\Core\Type\SubmitType; +use Symfony\Component\Form\FormBuilderInterface; +use Symfony\Component\Form\FormEvent; +use Symfony\Component\Form\FormEvents; +use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\Validator\Constraints\NotNull; + +class ProjectAddPartsType extends AbstractType +{ + public function buildForm(FormBuilderInterface $builder, array $options) + { + $builder->add('project', StructuralEntityType::class, [ + 'class' => Project::class, + 'required' => true, + 'disabled' => $options['project'] instanceof Project, //If a project is given, disable the field + 'data' => $options['project'], + 'constraints' => [ + new NotNull() + ] + ]); + $builder->add('bom_entries', ProjectBOMEntryCollectionType::class, [ + 'entry_options' => [ + 'constraints' => [ + new UniqueEntity(fields: ['part', 'project'], entityClass: ProjectBOMEntry::class, message: 'project.bom_entry.part_already_in_bom'), + new UniqueEntity(fields: ['name', 'project'], entityClass: ProjectBOMEntry::class, message: 'project.bom_entry.name_already_in_bom', ignoreNull: true), + ] + ], + 'constraints' => [ + new UniqueObjectCollection(fields: ['part'], message: 'project.bom_entry.part_already_in_bom'), + new UniqueObjectCollection(fields: ['name'], message: 'project.bom_entry.name_already_in_bom'), + ] + ]); + $builder->add('submit', SubmitType::class, ['label' => 'save']); + + //After submit set the project for all bom entries, so that it can be validated properly + $builder->addEventListener(FormEvents::SUBMIT, function (FormEvent $event) { + $form = $event->getForm(); + /** @var Project $project */ + $project = $form->get('project')->getData(); + $bom_entries = $form->get('bom_entries')->getData(); + + foreach ($bom_entries as $bom_entry) { + $bom_entry->setProject($project); + } + }); + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefaults([ + 'project' => null, + ]); + + $resolver->setAllowedTypes('project', ['null', Project::class]); + } +} \ No newline at end of file