src/SDL/Reflection/Validation/Definitions/ObjectValidator.php
<?php
/**
* This file is part of Railt package.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace Railt\SDL\Reflection\Validation\Definitions;
use Railt\SDL\Contracts\Definitions\Definition;
use Railt\SDL\Contracts\Definitions\InterfaceDefinition;
use Railt\SDL\Contracts\Definitions\ObjectDefinition;
use Railt\SDL\Contracts\Definitions\TypeDefinition;
use Railt\SDL\Contracts\Dependent\ArgumentDefinition;
use Railt\SDL\Contracts\Dependent\FieldDefinition;
use Railt\SDL\Exceptions\TypeConflictException;
use Railt\SDL\Reflection\Validation\Inheritance;
/**
* Class ObjectValidator
*/
class ObjectValidator extends BaseDefinitionValidator
{
/**
* @param Definition $definition
* @return bool
*/
public function match(Definition $definition): bool
{
return $definition instanceof ObjectDefinition;
}
/**
* @param Definition|ObjectDefinition $object
* @return void
* @throws TypeConflictException
* @throws \OutOfBoundsException
*/
public function validate(Definition $object): void
{
foreach ($object->getInterfaces() as $interface) {
$this->getCallStack()->push($interface);
$this->validateImplementationType($object, $interface);
$this->validateFieldsExistence($object, $interface);
$this->getCallStack()->pop();
}
}
/**
* @param ObjectDefinition $object
* @param TypeDefinition $type
* @return void
*/
private function validateImplementationType(ObjectDefinition $object, TypeDefinition $type): void
{
if ($type instanceof InterfaceDefinition) {
return;
}
$error = 'Only interface can be implemented by the %s, but %s given.';
throw new TypeConflictException(\sprintf($error, $object, $type), $this->getCallStack());
}
/**
* Make sure that all the interface fields are implemented.
*
* @param InterfaceDefinition $interface
* @param ObjectDefinition $object
* @return void
* @throws \OutOfBoundsException
* @throws \Railt\SDL\Exceptions\TypeConflictException
*/
private function validateFieldsExistence(ObjectDefinition $object, InterfaceDefinition $interface): void
{
foreach ($interface->getFields() as $field) {
$this->getCallStack()->push($field);
$exists = $object->hasField($field->getName());
if (! $exists) {
$this->throwFieldNotDefined($interface, $object, $field);
}
$this->validateFieldCompatibility($field, $object->getField($field->getName()));
$this->getCallStack()->pop();
}
}
/**
* @param InterfaceDefinition $i
* @param ObjectDefinition $o
* @param FieldDefinition $f
* @return void
* @throws TypeConflictException
*/
private function throwFieldNotDefined(InterfaceDefinition $i, ObjectDefinition $o, FieldDefinition $f): void
{
$error = \sprintf('%s must contain the remaining %s of the %s', $o, $f, $i);
throw new TypeConflictException($error, $this->getCallStack()->push($o));
}
/**
* We are convinced that the fields have a comparable signature of the type.
*
* @param FieldDefinition $interface
* @param FieldDefinition $object
* @return void
* @throws \Railt\SDL\Exceptions\TypeConflictException
* @throws \OutOfBoundsException
*/
private function validateFieldCompatibility(FieldDefinition $interface, FieldDefinition $object): void
{
$this->getValidator(Inheritance::class)->validate($object, $interface);
$this->validateArgumentExistence($interface, $object);
}
/**
* We are convinced that all the arguments of the parent fields were implemented in the child.
*
* @param FieldDefinition $interface
* @param FieldDefinition $object
* @return void
* @throws TypeConflictException
* @throws \OutOfBoundsException
*/
private function validateArgumentExistence(FieldDefinition $interface, FieldDefinition $object): void
{
foreach ($interface->getArguments() as $argument) {
$this->getCallStack()->push($argument);
$exists = $object->hasArgument($argument->getName());
if (! $exists) {
$this->throwArgumentNotDefined($interface, $object, $argument);
}
$this->validateArgumentCompatibility($argument, $object->getArgument($argument->getName()));
$this->getCallStack()->pop();
}
}
/**
* @param FieldDefinition $fi
* @param FieldDefinition $fo
* @param ArgumentDefinition $a
* @return void
* @throws TypeConflictException
*/
private function throwArgumentNotDefined(FieldDefinition $fi, FieldDefinition $fo, ArgumentDefinition $a): void
{
$error = 'The %s of the %s contains an argument %s, but the %s does not implement it';
$error = \sprintf($error, $fi, $fi->getParent(), $a, $fo->getParent());
throw new TypeConflictException($error, $this->getCallStack()->push($fo));
}
/**
* We are convinced that the arguments have a comparable signature of the type.
*
* @param ArgumentDefinition $interface
* @param ArgumentDefinition $object
* @return void
* @throws \OutOfBoundsException
*/
private function validateArgumentCompatibility(ArgumentDefinition $interface, ArgumentDefinition $object): void
{
$this->getValidator(Inheritance::class)->validate($object, $interface);
}
}