mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-01-12 21:29:33 +00:00
Füge Validierung für zyklische Baugruppenreferenzen hinzu
Eine neue Validierung wurde implementiert, um zyklische Referenzen in Baugruppen zu erkennen. Entsprechende Fehlertexte wurden in allen unterstützten Sprachen hinzugefügt. Zudem wurde der Validator in die Entität AssemblyBOMEntry integriert.
This commit is contained in:
parent
9d9cedd222
commit
cbfe1d4cc8
17 changed files with 257 additions and 0 deletions
|
|
@ -23,6 +23,7 @@ declare(strict_types=1);
|
|||
namespace App\Entity\AssemblySystem;
|
||||
|
||||
use App\Repository\AssemblyRepository;
|
||||
use App\Validator\Constraints\AssemblySystem\AssemblyCycle;
|
||||
use Doctrine\Common\Collections\Criteria;
|
||||
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
|
|
@ -113,6 +114,7 @@ class Assembly extends AbstractStructuralDBElement
|
|||
#[Groups(['extended', 'full', 'import'])]
|
||||
#[ORM\OneToMany(targetEntity: AssemblyBOMEntry::class, mappedBy: 'assembly', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.part_already_in_bom', fields: ['part'])]
|
||||
#[AssemblyCycle]
|
||||
#[UniqueReferencedAssembly]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.project_already_in_bom', fields: ['project'])]
|
||||
#[UniqueObjectCollection(message: 'assembly.bom_entry.name_already_in_bom', fields: ['name'])]
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ use App\ApiPlatform\Filter\LikeFilter;
|
|||
use App\Entity\Contracts\TimeStampableInterface;
|
||||
use App\Entity\ProjectSystem\Project;
|
||||
use App\Repository\DBElementRepository;
|
||||
use App\Validator\Constraints\AssemblySystem\AssemblyCycle;
|
||||
use App\Validator\UniqueValidatableInterface;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use App\Entity\Base\AbstractDBElement;
|
||||
|
|
@ -140,6 +141,7 @@ class AssemblyBOMEntry extends AbstractDBElement implements UniqueValidatableInt
|
|||
'(this.getPart() === null or this.getReferencedAssembly() === null) and (this.getName() === null or (this.getName() != null and this.getName() != ""))',
|
||||
message: 'validator.assembly.bom_entry.only_part_or_assembly_allowed'
|
||||
)]
|
||||
#[AssemblyCycle]
|
||||
#[ORM\ManyToOne(targetEntity: Assembly::class)]
|
||||
#[ORM\JoinColumn(name: 'id_referenced_assembly', nullable: true, onDelete: 'SET NULL')]
|
||||
#[Groups(['bom_entry:read', 'bom_entry:write', ])]
|
||||
|
|
|
|||
39
src/Validator/Constraints/AssemblySystem/AssemblyCycle.php
Normal file
39
src/Validator/Constraints/AssemblySystem/AssemblyCycle.php
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace App\Validator\Constraints\AssemblySystem;
|
||||
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
|
||||
/**
|
||||
* This constraint checks that there is no cycle in bom configuration of the assembly
|
||||
*/
|
||||
#[\Attribute(\Attribute::TARGET_PROPERTY)]
|
||||
class AssemblyCycle extends Constraint
|
||||
{
|
||||
public string $message = 'assembly.bom_entry.assembly_cycle';
|
||||
|
||||
public function validatedBy(): string
|
||||
{
|
||||
return AssemblyCycleValidator::class;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
|
||||
*
|
||||
* Copyright (C) 2019 - 2023 Jan Böhmer (https://github.com/jbtronics)
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published
|
||||
* by the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
namespace App\Validator\Constraints\AssemblySystem;
|
||||
|
||||
use App\Entity\AssemblySystem\Assembly;
|
||||
use Symfony\Component\Validator\Constraint;
|
||||
use Symfony\Component\Validator\ConstraintValidator;
|
||||
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
|
||||
use Symfony\Component\Validator\Violation\ConstraintViolationBuilder;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Validator class to check for cycles in assemblies based on BOM entries.
|
||||
*
|
||||
* This validator ensures that the structure of assemblies does not contain circular dependencies
|
||||
* by validating each entry in the Bill of Materials (BOM) of the given assembly. Additionally,
|
||||
* it can handle form-submitted BOM entries to include these in the validation process.
|
||||
*/
|
||||
class AssemblyCycleValidator extends ConstraintValidator
|
||||
{
|
||||
public function validate($value, Constraint $constraint): void
|
||||
{
|
||||
if (!$constraint instanceof AssemblyCycle) {
|
||||
throw new UnexpectedTypeException($constraint, AssemblyCycle::class);
|
||||
}
|
||||
|
||||
if (!$value instanceof Assembly) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bomEntries = $value->getBomEntries()->toArray();
|
||||
|
||||
// Consider additional entries from the form
|
||||
if ($this->context->getRoot()->has('bom_entries')) {
|
||||
$formBomEntries = $this->context->getRoot()->get('bom_entries')->getData();
|
||||
if ($formBomEntries) {
|
||||
$given = is_array($formBomEntries) ? $formBomEntries : iterator_to_array($formBomEntries);
|
||||
foreach ($given as $givenIdx => $entry) {
|
||||
if (in_array($entry, $bomEntries, true)) {
|
||||
continue;
|
||||
} else {
|
||||
$bomEntries[$givenIdx] = $entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$visitedAssemblies = [];
|
||||
foreach ($bomEntries as $bomEntry) {
|
||||
if ($this->hasCycle($bomEntry->getReferencedAssembly(), $value, $visitedAssemblies)) {
|
||||
$this->addViolation($value, $constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function hasCycle(?Assembly $currentAssembly, Assembly $originalAssembly, array &$visitedAssemblies): bool
|
||||
{
|
||||
if ($currentAssembly === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($currentAssembly, $visitedAssemblies, true)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$visitedAssemblies[] = $currentAssembly;
|
||||
|
||||
foreach ($currentAssembly->getBomEntries() as $bomEntry) {
|
||||
if ($this->hasCycle($bomEntry->getReferencedAssembly(), $originalAssembly, $visitedAssemblies)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a violation to the current context if it hasn’t already been added.
|
||||
*
|
||||
* This method checks whether a violation with the same property path as the current violation
|
||||
* already exists in the context. If such a violation is found, the current violation is not added again.
|
||||
* The process involves reflection to access private or protected properties of violation objects.
|
||||
*
|
||||
* @param mixed $value The value that triggered the violation.
|
||||
* @param Constraint $constraint The constraint containing the validation details.
|
||||
*
|
||||
*/
|
||||
private function addViolation($value, Constraint $constraint): void
|
||||
{
|
||||
/** @var ConstraintViolationBuilder $buildViolation */
|
||||
$buildViolation = $this->context->buildViolation($constraint->message)
|
||||
->setParameter('%name%', $value->getName());
|
||||
|
||||
$alreadyAdded = false;
|
||||
|
||||
try {
|
||||
$reflectionClass = new ReflectionClass($buildViolation);
|
||||
$property = $reflectionClass->getProperty('propertyPath');
|
||||
$propertyPath = $property->getValue($buildViolation);
|
||||
|
||||
$availableViolations = $this->context->getViolations();
|
||||
|
||||
foreach ($availableViolations as $tmpViolation) {
|
||||
$tmpReflectionClass = new ReflectionClass($tmpViolation);
|
||||
$tmpProperty = $tmpReflectionClass->getProperty('propertyPath');
|
||||
$tmpPropertyPath = $tmpProperty->getValue($tmpViolation);
|
||||
|
||||
if ($tmpPropertyPath === $propertyPath) {
|
||||
$alreadyAdded = true;
|
||||
}
|
||||
}
|
||||
} catch (\ReflectionException) {
|
||||
}
|
||||
|
||||
if (!$alreadyAdded) {
|
||||
$buildViolation->addViolation();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue