Part-DB-server/src/Validator/Constraints/AssemblySystem/AssemblyCycleValidator.php
Marcel Diegelmann 9acca25ac7 BOMImporter und AssemblyCycleValidator: Verbesserte Import-Logik
Die Importmethoden wurden vereinheitlicht, um Projekte und Baugruppen gleichermaßen zu unterstützen. Zudem wurde die Validierung optimiert und die CSV-, JSON- und KiCAD-Parsing-Methoden angepasst, um kontextbasierte BOM-Einträge effizienter zu erstellen.
2025-09-30 16:16:08 +02:00

140 lines
No EOL
5.2 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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\Form\Form;
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() instanceof Form && $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 hasnt 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();
}
}
}