Skip to content

Commit

Permalink
Improve internal type safety
Browse files Browse the repository at this point in the history
  • Loading branch information
mabar committed Feb 16, 2025
1 parent 3da64c6 commit 040fb5c
Show file tree
Hide file tree
Showing 11 changed files with 72 additions and 15 deletions.
4 changes: 4 additions & 0 deletions src/Meta/MetaResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,11 @@
use Reflector;
use function array_key_exists;
use function array_merge;
use function assert;
use function get_class;
use function is_a;
use function is_int;
use function is_string;
use const PHP_VERSION_ID;

/**
Expand Down Expand Up @@ -487,6 +490,7 @@ private function checkFieldNames(ReflectionClass $rootClass, CompileMeta $meta):
foreach ($fieldMeta->getModifiers() as $modifier) {
if ($modifier->getType() === FieldNameModifier::class) {
$fieldName = $modifier->getArgs()[FieldNameModifier::Name];
assert(is_string($fieldName) || is_int($fieldName));

break;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Printers/ErrorVisualPrinter.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
use function get_class;

/**
* @template T of string|array
* @template T of string|array<mixed>
*/
final class ErrorVisualPrinter implements ErrorPrinter, TypePrinter
{
Expand All @@ -25,7 +25,7 @@ final class ErrorVisualPrinter implements ErrorPrinter, TypePrinter
private TypeToPrimitiveConverter $converter;

/**
* @param TypeToPrimitiveConverter<T> $converter
* @param TypeToPrimitiveConverter<covariant T> $converter
*/
public function __construct(TypeToPrimitiveConverter $converter)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Printers/TypeToPrimitiveConverter.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
use Orisai\ObjectMapper\Types\TypeParameter;

/**
* @template T of string|array
* @template T of string|array<mixed>
*/
interface TypeToPrimitiveConverter
{
Expand Down
8 changes: 7 additions & 1 deletion src/Rules/ArrayOfRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
use Orisai\ObjectMapper\Processing\Value;
use Orisai\ObjectMapper\Types\GenericArrayType;
use Orisai\Utils\Arrays\ArrayMerger;
use function assert;
use function count;
use function get_debug_type;
use function is_array;
use function is_int;
use function is_string;
use function sprintf;

/**
Expand Down Expand Up @@ -159,6 +162,7 @@ public function processValue(
$property,
$dynamic->createClone(),
);
assert(is_int($key) || is_string($key));
} catch (ValueDoesNotMatch | InvalidData $exception) {
$type ??= $this->createType($args, $services, $dynamic);
$type->addInvalidKey($key, $exception);
Expand Down Expand Up @@ -221,7 +225,9 @@ public function processValue(
}

if ($args->mergeDefaults && $property->hasDefaultValue()) {
$value = ArrayMerger::merge($property->getDefaultValue(), $value);
$default = $property->getDefaultValue();
assert(is_array($default)); // Rule validates that default is an array
$value = ArrayMerger::merge($default, $value);
}

return $value;
Expand Down
22 changes: 21 additions & 1 deletion src/Rules/ListOfRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Orisai\ObjectMapper\Rules;

use Orisai\Exceptions\Logic\InvalidArgument;
use Orisai\ObjectMapper\Args\Args;
use Orisai\ObjectMapper\Args\ArgsChecker;
use Orisai\ObjectMapper\Exception\InvalidData;
Expand All @@ -15,9 +16,12 @@
use Orisai\ObjectMapper\Types\GenericArrayType;
use Orisai\ObjectMapper\Types\SimpleValueType;
use Orisai\Utils\Arrays\ArrayMerger;
use function assert;
use function count;
use function get_debug_type;
use function is_array;
use function is_int;
use function sprintf;

/**
* @extends MultiValueRule<MultiValueArgs>
Expand Down Expand Up @@ -54,6 +58,20 @@ public function resolveArgs(array $args, MetaFieldContext $context): MultiValueA
$mergeDefaults = $checker->checkBool(self::MergeDefaults);
}

if (
$mergeDefaults
&& $context->hasDefaultValue()
&& !is_array($defaultValue = $context->getDefaultValue())
) {
throw InvalidArgument::create()
->withMessage(sprintf(
'Argument "%s" given to "%s" is set to true but the default value is "%s" insteadof an array.',
self::MergeDefaults,
self::class,
get_debug_type($defaultValue),
));
}

return new MultiValueArgs(
$itemRuleMeta,
$minItems,
Expand Down Expand Up @@ -186,7 +204,9 @@ public function processValue(
}

if ($args->mergeDefaults && $property->hasDefaultValue()) {
$value = ArrayMerger::merge($property->getDefaultValue(), $value);
$default = $property->getDefaultValue();
assert(is_array($default)); // Rule validates that default is an array
$value = ArrayMerger::merge($default, $value);
}

return $value;
Expand Down
7 changes: 7 additions & 0 deletions tests/Doubles/Callbacks/CallbacksVO.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Orisai\ObjectMapper\Rules\MixedValue;
use Orisai\ObjectMapper\Rules\StringValue;
use function array_key_exists;
use function assert;
use function is_array;

/**
Expand Down Expand Up @@ -75,6 +76,8 @@ public static function beforeClass($data, ObjectContext $context)
return $data;
}

assert(is_array($data['array']));
assert(is_array($data['array']['beforeClassCallback']));
$data['array']['beforeClassCallback'][] = $context->shouldInitializeObjects();

// Set default value, processor don't know it's going to be structure and thinks value is required
Expand All @@ -97,6 +100,8 @@ public static function beforeClass($data, ObjectContext $context)
*/
public static function afterClass(array $data, ObjectContext $context): array
{
assert(is_array($data['array']));
assert(is_array($data['array']['afterClassCallback']));
$data['array']['afterClassCallback'][] = $context->shouldInitializeObjects();

if ($context->shouldInitializeObjects() && !$data['structure'] instanceof MappedObject) {
Expand All @@ -118,6 +123,7 @@ public static function afterClass(array $data, ObjectContext $context): array
*/
public static function afterArrayProcessing(array $array, FieldContext $context): array
{
assert(is_array($array['afterArrayProcessingCallback']));
$array['afterArrayProcessingCallback'][] = $context->shouldInitializeObjects();

return $array;
Expand All @@ -129,6 +135,7 @@ public static function afterArrayProcessing(array $array, FieldContext $context)
*/
public static function afterArrayInitialization(array $array, FieldContext $context): array
{
assert(is_array($array['afterArrayInitializationCallback']));
$array['afterArrayInitializationCallback'][] = $context->shouldInitializeObjects();

return $array;
Expand Down
2 changes: 2 additions & 0 deletions tests/Doubles/Inheritance/trait-callback.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Orisai\ObjectMapper\Callbacks\Before;
use Orisai\ObjectMapper\MappedObject;
use Orisai\ObjectMapper\Rules\StringValue;
use function assert;
use function is_array;
use function is_string;

Expand Down Expand Up @@ -42,6 +43,7 @@ private function before($data)
*/
private function after(array $data): array
{
assert(is_string($data['string']));
$data['string'] .= '-A::after';

return $data;
Expand Down
2 changes: 2 additions & 0 deletions tests/Unit/Processing/RawValuesMapTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Orisai\ObjectMapper\Processing\RawValuesMap;
use PHPUnit\Framework\TestCase;
use Tests\Orisai\ObjectMapper\Doubles\DefaultsVO;
use function assert;
use function serialize;
use function unserialize;
use const PHP_VERSION_ID;
Expand Down Expand Up @@ -58,6 +59,7 @@ public function testUnset(): void
$data = serialize($object);
unset($object);
$object = unserialize($data);
assert($object instanceof DefaultsVO);

$this->expectException(InvalidState::class);
$map->getRawValues($object);
Expand Down
13 changes: 13 additions & 0 deletions tests/Unit/Rules/BackedEnumRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Orisai\ObjectMapper\Rules\BackedEnumArgs;
use Orisai\ObjectMapper\Rules\BackedEnumRule;
use Orisai\ObjectMapper\Types\EnumType;
use stdClass;
use Tests\Orisai\ObjectMapper\Doubles\Php81\ExampleIntEnum;
use Tests\Orisai\ObjectMapper\Doubles\Php81\ExampleStringEnum;
use Tests\Orisai\ObjectMapper\Toolkit\ProcessingTestCase;
Expand Down Expand Up @@ -148,6 +149,18 @@ public function provideInvalidValues(): Generator
new BackedEnumArgs(ExampleIntEnum::class, false),
[0, 1],
];

yield [
['foo', 'bar'],
new BackedEnumArgs(ExampleIntEnum::class, false),
[0, 1],
];

yield [
new stdClass(),
new BackedEnumArgs(ExampleIntEnum::class, false),
[0, 1],
];
}

public function testType(): void
Expand Down
17 changes: 7 additions & 10 deletions tests/Unit/Rules/DateTimeRuleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ public function provideValidValues(): Generator
}

/**
* @param mixed $value
* @param array<int, string> $invalidParameters
* @param mixed $value
* @param list<int|string|array<int|string, mixed>> $invalidParameters
*
* @dataProvider provideInvalidValues
*/
Expand Down Expand Up @@ -148,12 +148,9 @@ public function testProcessInvalid(
$parameters = [];
foreach ($type->getParameters() as $parameter) {
if ($parameter->isInvalid()) {
$parameterString = $parameter->getKey();
if ($parameter->hasValue()) {
$parameterString .= ": {$parameter->getValue()}";
}

$parameters[] = $parameterString;
$parameters[] = $parameter->hasValue()
? [$parameter->getKey(), $parameter->getValue()]
: $parameter->getKey();
}
}

Expand All @@ -177,7 +174,7 @@ public function provideInvalidValues(): Generator
]];

yield ['whatever', DateTimeInterface::ATOM, [
'format: Y-m-d\TH:i:sP',
['format', 'Y-m-d\TH:i:sP'],
'A four digit year could not be found',
PHP_VERSION_ID < 8_01_07 ? 'Data missing' : 'Not enough data available to satisfy format',
]];
Expand All @@ -187,7 +184,7 @@ public function provideInvalidValues(): Generator
], 'timestamp'];

yield ['2013-04-12T16:40:00-04:00', DateTimeInterface::COOKIE, [
'format: l, d-M-Y H:i:s T',
['format', 'l, d-M-Y H:i:s T'],
'A textual day could not be found',
'Unexpected data found.',
'The separation symbol could not be found',
Expand Down
6 changes: 6 additions & 0 deletions tools/phpstan.baseline.neon
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
parameters:
ignoreErrors:
-
message: '#^Property Orisai\\ObjectMapper\\Printers\\ErrorVisualPrinter\<T of array\<mixed\>\|string\>\:\:\$converter \(Orisai\\ObjectMapper\\Printers\\TypeToPrimitiveConverter\<T of array\<mixed\>\|string\>\) does not accept Orisai\\ObjectMapper\\Printers\\TypeToPrimitiveConverter\<covariant T of array\<mixed\>\|string\>\.$#'
identifier: assign.propertyType
count: 1
path: ../src/Printers/ErrorVisualPrinter.php

-
message: '#^Unreachable statement \- code above always terminates\.$#'
identifier: deadCode.unreachable
Expand Down

0 comments on commit 040fb5c

Please sign in to comment.