mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-03-01 04:49:36 +00:00
142 lines
5.2 KiB
PHP
142 lines
5.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
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 to check that no child assemblies are referenced in BOM entries.
|
||
*/
|
||
class AssemblyInvalidBomEntryValidator extends ConstraintValidator
|
||
{
|
||
public function validate($value, Constraint $constraint): void
|
||
{
|
||
if (!$constraint instanceof AssemblyInvalidBomEntry) {
|
||
throw new UnexpectedTypeException($constraint, AssemblyInvalidBomEntry::class);
|
||
}
|
||
|
||
if (!$value instanceof Assembly) {
|
||
return;
|
||
}
|
||
|
||
$availableViolations = $this->context->getViolations();
|
||
if (count($availableViolations) > 0) {
|
||
//already violations given, currently no more needed to check
|
||
|
||
return;
|
||
}
|
||
|
||
$bomEntries = [];
|
||
|
||
if ($this->context->getRoot() instanceof Form && $this->context->getRoot()->has('bom_entries')) {
|
||
$bomEntries = $this->context->getRoot()->get('bom_entries')->getData();
|
||
$bomEntries = is_array($bomEntries) ? $bomEntries : iterator_to_array($bomEntries);
|
||
} elseif ($this->context->getRoot() instanceof Assembly) {
|
||
$bomEntries = $value->getBomEntries()->toArray();
|
||
}
|
||
|
||
$relevantEntries = [];
|
||
|
||
foreach ($bomEntries as $bomEntry) {
|
||
if ($bomEntry->getReferencedAssembly() !== null) {
|
||
$relevantEntries[$bomEntry->getId()] = $bomEntry;
|
||
}
|
||
}
|
||
|
||
foreach ($relevantEntries as $bomEntry) {
|
||
$referencedAssembly = $bomEntry->getReferencedAssembly();
|
||
|
||
if ($bomEntry->getAssembly()->getParent()?->getId() === $referencedAssembly->getParent()?->getId()) {
|
||
//Save on the same assembly level
|
||
continue;
|
||
} elseif ($this->isInvalidBomEntry($referencedAssembly, $bomEntry->getAssembly())) {
|
||
$this->addViolation($value, $constraint);
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Determines whether a Bill of Materials (BOM) entry is invalid based on the relationship
|
||
* between the current assembly and the parent assembly.
|
||
*
|
||
* @param Assembly|null $currentAssembly The current assembly being analyzed. Null indicates no assembly is referenced.
|
||
* @param Assembly $parentAssembly The parent assembly to check against the current assembly.
|
||
*
|
||
* @return bool Returns
|
||
*/
|
||
private function isInvalidBomEntry(?Assembly $currentAssembly, Assembly $parentAssembly): bool
|
||
{
|
||
//No assembly referenced -> no problems
|
||
if ($currentAssembly === null) {
|
||
return false;
|
||
}
|
||
|
||
//Check: is the current assembly a descendant of the parent assembly?
|
||
if ($currentAssembly->isChildOf($parentAssembly)) {
|
||
return true;
|
||
}
|
||
|
||
//Recursive check: Analyze the current assembly list
|
||
foreach ($currentAssembly->getBomEntries() as $bomEntry) {
|
||
$referencedAssembly = $bomEntry->getReferencedAssembly();
|
||
|
||
if ($this->isInvalidBomEntry($referencedAssembly, $parentAssembly)) {
|
||
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 AssemblyInvalidBomEntry $constraint The constraint containing the validation details.
|
||
*
|
||
*/
|
||
private function addViolation($value, AssemblyInvalidBomEntry $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();
|
||
}
|
||
}
|
||
}
|