Skip to content

Commit

Permalink
ReflectionMetaSource: improve error messages, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
mabar committed Jul 9, 2023
1 parent 465d635 commit e974721
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 30 deletions.
75 changes: 45 additions & 30 deletions src/Meta/Source/ReflectorMetaSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Orisai\ObjectMapper\Meta\Source;

use Orisai\Exceptions\Logic\InvalidArgument;
use Orisai\Exceptions\Message;
use Orisai\ObjectMapper\Callbacks\CallbackDefinition;
use Orisai\ObjectMapper\Docs\DocDefinition;
use Orisai\ObjectMapper\MappedObject;
Expand Down Expand Up @@ -48,8 +49,8 @@ public function load(ReflectionClass $class): CompileMeta
}

return new CompileMeta(
$this->loadClassMeta($structures),
$this->loadPropertiesMeta($structures),
$this->loadClassMeta($class, $structures),
$this->loadPropertiesMeta($class, $structures),
$sources,
);
}
Expand All @@ -67,9 +68,10 @@ private function getStructureGroup(ReflectionClass $class): StructureGroup
}

/**
* @param ReflectionClass<MappedObject> $rootClass
* @return list<ClassCompileMeta>
*/
private function loadClassMeta(StructureGroup $group): array
private function loadClassMeta(ReflectionClass $rootClass, StructureGroup $group): array
{
$resolved = [];
foreach ($group->getClasses() as $class) {
Expand All @@ -84,12 +86,16 @@ private function loadClassMeta(StructureGroup $group): array
$definition = $this->checkDefinitionType($definition);

if ($definition instanceof RuleDefinition) {
throw InvalidArgument::create()
->withMessage(sprintf(
'Rule definition %s (subtype of %s) cannot be used on class, only properties are allowed',
$message = Message::create()
->withContext("Resolving metadata of mapped object '{$rootClass->getName()}'.")
->withProblem(sprintf(
"Rule definition '%s' (subtype of '%s') cannot be used on class, only properties are allowed.",
get_class($definition),
RuleDefinition::class,
));

throw InvalidArgument::create()
->withMessage($message);
}

if ($definition instanceof CallbackDefinition) {
Expand Down Expand Up @@ -121,9 +127,10 @@ private function loadClassMeta(StructureGroup $group): array
}

/**
* @param ReflectionClass<MappedObject> $rootClass
* @return list<FieldCompileMeta>
*/
private function loadPropertiesMeta(StructureGroup $group): array
private function loadPropertiesMeta(ReflectionClass $rootClass, StructureGroup $group): array
{
$resolved = [];
foreach ($group->getGroupedProperties() as $groupedProperty) {
Expand All @@ -132,9 +139,6 @@ private function loadPropertiesMeta(StructureGroup $group): array
$reflector = $propertyStructure->getSource()->getReflector();
$definitions = $this->reader->readProperty($reflector, MetaDefinition::class);

$property = $propertyStructure->getContextReflector();
$class = $property->getDeclaringClass();

$callbacks = [];
$docs = [];
$modifiers = [];
Expand All @@ -145,15 +149,18 @@ private function loadPropertiesMeta(StructureGroup $group): array

if ($definition instanceof RuleDefinition) {
if ($rule !== null) {
$message = Message::create()
->withContext("Resolving metadata of mapped object '{$rootClass->getName()}'.")
->withProblem(
"Property '{$propertyStructure->getSource()->toString()}' has"
. ' multiple rule definitions, but only one is allowed.',
)
->withSolution(
sprintf("Combine multiple with '%s' or '%s'.", AnyOf::class, AllOf::class),
);

throw InvalidArgument::create()
->withMessage(sprintf(
'Mapped property %s::$%s has multiple expectation definitions, while only one is allowed. ' .
'Combine multiple with %s or %s',
$class->getName(),
$property->getName(),
AnyOf::class,
AllOf::class,
));
->withMessage($message);
}

$rule = new RuleCompileMeta(
Expand Down Expand Up @@ -183,11 +190,15 @@ private function loadPropertiesMeta(StructureGroup $group): array
}

if ($rule === null) {
throw InvalidArgument::create()
->withMessage(
"Property {$class->getName()}::\${$property->getName()} has mapped object definition, " .
'but no rule definition.',
$message = Message::create()
->withContext("Resolving metadata of mapped object '{$rootClass->getName()}'.")
->withProblem(
"Property '{$propertyStructure->getSource()->toString()}' has"
. ' mapped object definition, but no rule definition.',
);

throw InvalidArgument::create()
->withMessage($message);
}

$resolvedGroup[] = new FieldCompileMeta(
Expand All @@ -203,7 +214,7 @@ private function loadPropertiesMeta(StructureGroup $group): array
continue;
}

$this->checkFieldInvariance($resolvedGroup);
$this->checkFieldInvariance($rootClass, $resolvedGroup);
$resolved[] = $resolvedGroup[array_key_first($resolvedGroup)];
}

Expand All @@ -223,7 +234,7 @@ private function checkDefinitionType(MetaDefinition $definition): MetaDefinition
) {
throw InvalidArgument::create()
->withMessage(sprintf(
'Definition %s (subtype of %s) should implement %s, %s %s or %s',
"Definition '%s' (subtype of '%s') should implement '%s', '%s', '%s' or '%s'.",
get_class($definition),
MetaDefinition::class,
CallbackDefinition::class,
Expand All @@ -237,19 +248,23 @@ private function checkDefinitionType(MetaDefinition $definition): MetaDefinition
}

/**
* @param ReflectionClass<MappedObject> $rootClass
* @param list<FieldCompileMeta> $resolvedGroup
*/
private function checkFieldInvariance(array $resolvedGroup): void
private function checkFieldInvariance(ReflectionClass $rootClass, array $resolvedGroup): void
{
$previousFieldMeta = null;
foreach ($resolvedGroup as $fieldMeta) {
if ($previousFieldMeta !== null && !$fieldMeta->hasEqualMeta($previousFieldMeta)) {
throw InvalidArgument::create()
->withMessage(
"Definition of property '{$fieldMeta->getClass()->getContextReflector()->getName()}"
. "::\${$fieldMeta->getProperty()->getContextReflector()->getName()}'"
. " can't be changed but it differs from definition in '{$previousFieldMeta->getClass()->getContextReflector()->getName()}'.",
$message = Message::create()
->withContext("Resolving metadata of mapped object '{$rootClass->getName()}'.")
->withProblem(
"Definition of property '{$fieldMeta->getProperty()->getSource()->toString()}'"
. " can't be changed but it differs from definition '{$previousFieldMeta->getProperty()->getSource()->toString()}'.",
);

throw InvalidArgument::create()
->withMessage($message);
}

$previousFieldMeta = $fieldMeta;
Expand Down
28 changes: 28 additions & 0 deletions tests/Doubles/Definition/TargetLessRuleDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Definition;

use Attribute;
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
use Orisai\ObjectMapper\Rules\MixedRule;
use Orisai\ObjectMapper\Rules\RuleDefinition;

/**
* @Annotation
* @NamedArgumentConstructor()
*/
#[Attribute()]
final class TargetLessRuleDefinition implements RuleDefinition
{

public function getType(): string
{
return MixedRule::class;
}

public function getArgs(): array
{
return [];
}

}
26 changes: 26 additions & 0 deletions tests/Doubles/Definition/UnsupportedDefinition.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Definition;

use Attribute;
use Orisai\ObjectMapper\Meta\MetaDefinition;
use stdClass;

/**
* @Annotation
*/
#[Attribute]
final class UnsupportedDefinition implements MetaDefinition
{

public function getType(): string
{
return stdClass::class;
}

public function getArgs(): array
{
return [];
}

}
18 changes: 18 additions & 0 deletions tests/Doubles/Meta/FieldWithMultipleRulesVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Meta;

use Orisai\ObjectMapper\MappedObject;
use Orisai\ObjectMapper\Rules\IntValue;
use Orisai\ObjectMapper\Rules\StringValue;

final class FieldWithMultipleRulesVO implements MappedObject
{

/**
* @StringValue()
* @IntValue()
*/
public string $field;

}
14 changes: 14 additions & 0 deletions tests/Doubles/Meta/FieldWithNoRuleVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Meta;

use Orisai\ObjectMapper\MappedObject;
use Orisai\ObjectMapper\Modifiers\FieldName;

final class FieldWithNoRuleVO implements MappedObject
{

/** @FieldName("foo") */
public string $field;

}
14 changes: 14 additions & 0 deletions tests/Doubles/Meta/RuleAboveClassVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Meta;

use Orisai\ObjectMapper\MappedObject;
use Tests\Orisai\ObjectMapper\Doubles\Definition\TargetLessRuleDefinition;

/**
* @TargetLessRuleDefinition()
*/
final class RuleAboveClassVO implements MappedObject
{

}
14 changes: 14 additions & 0 deletions tests/Doubles/Meta/UnsupportedClassDefinitionVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Meta;

use Orisai\ObjectMapper\MappedObject;
use Tests\Orisai\ObjectMapper\Doubles\Definition\UnsupportedDefinition;

/**
* @UnsupportedDefinition()
*/
final class UnsupportedClassDefinitionVO implements MappedObject
{

}
14 changes: 14 additions & 0 deletions tests/Doubles/Meta/UnsupportedPropertyDefinitionVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Meta;

use Orisai\ObjectMapper\MappedObject;
use Tests\Orisai\ObjectMapper\Doubles\Definition\UnsupportedDefinition;

final class UnsupportedPropertyDefinitionVO implements MappedObject
{

/** @UnsupportedDefinition() */
public string $field;

}
14 changes: 14 additions & 0 deletions tests/Doubles/Meta/VariantFieldParentVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Meta;

use Orisai\ObjectMapper\MappedObject;
use Orisai\ObjectMapper\Rules\StringValue;

abstract class VariantFieldParentVO implements MappedObject
{

/** @StringValue(notEmpty=true) */
public string $field;

}
13 changes: 13 additions & 0 deletions tests/Doubles/Meta/VariantFieldVO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php declare(strict_types = 1);

namespace Tests\Orisai\ObjectMapper\Doubles\Meta;

use Orisai\ObjectMapper\Rules\StringValue;

final class VariantFieldVO extends VariantFieldParentVO
{

/** @StringValue() */
public string $field;

}
6 changes: 6 additions & 0 deletions tests/Unit/Meta/MetaLoaderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,13 @@ public function testPreload(): void
$excludes[] = __DIR__ . '/../../Doubles/Meta/EnumVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/FieldMetaInvalidScopeRootVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/FieldTraitMetaInvalidScopeRootVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/FieldWithMultipleRulesVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/FieldWithNoRuleVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/RuleAboveClassVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/StaticMappedPropertyVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/UnsupportedClassDefinitionVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/UnsupportedPropertyDefinitionVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/VariantFieldVO.php';
$excludes[] = __DIR__ . '/../../Doubles/Meta/WrongArgsTypeVO.php';

if (PHP_VERSION_ID < 8_00_00) {
Expand Down
Loading

0 comments on commit e974721

Please sign in to comment.