Skip to content

Commit

Permalink
Merge remote-tracking branch 'refs/remotes/origin/master' into fix/pr…
Browse files Browse the repository at this point in the history
…operly-handle-class-sharing-class-name-and-namespace-group-name
  • Loading branch information
romm committed Sep 18, 2024
2 parents ae0f5bd + 8742b27 commit f82591c
Show file tree
Hide file tree
Showing 7 changed files with 53 additions and 19 deletions.
26 changes: 16 additions & 10 deletions src/Mapper/Tree/Shell.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public static function root(
return (new self($settings, $type))->withValue($value);
}

public function child(string $name, Type $type, Attributes $attributes = null): self
public function child(string $name, Type $type, ?Attributes $attributes = null): self
{
$instance = new self($this->settings, $type);
$instance->name = $name;
Expand All @@ -80,6 +80,7 @@ public function withType(Type $newType): self
{
$clone = clone $this;
$clone->type = $newType;
$clone->value = self::castCompatibleValue($newType, $this->value);

return $clone;
}
Expand All @@ -91,17 +92,9 @@ public function type(): Type

public function withValue(mixed $value): self
{
// When the value is an integer and the type is a float, the value is
// cast to float, to follow the rule of PHP regarding acceptance of an
// integer value in a float type. Note that PHPStan/Psalm analysis
// applies the same rule.
if ($this->type instanceof FloatType && is_int($value)) {
$value = (float)$value;
}

$clone = clone $this;
$clone->hasValue = true;
$clone->value = $value;
$clone->value = self::castCompatibleValue($clone->type, $value);

return $clone;
}
Expand Down Expand Up @@ -173,4 +166,17 @@ public function path(): string

return implode('.', $path);
}

private static function castCompatibleValue(Type $type, mixed $value): mixed
{
// When the value is an integer and the type is a float, the value is
// cast to float, to follow the rule of PHP regarding acceptance of an
// integer value in a float type. Note that PHPStan/Psalm analysis
// applies the same rule.
if ($type instanceof FloatType && is_int($value)) {
return (float)$value;
}

return $value;
}
}
8 changes: 6 additions & 2 deletions src/Normalizer/Formatter/JsonFormatter.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use function is_scalar;
use function json_encode;

use const JSON_FORCE_OBJECT;
use const JSON_THROW_ON_ERROR;

/** @internal */
Expand Down Expand Up @@ -54,8 +55,11 @@ public function format(mixed $value): void
// afterward, this leads to a JSON array being written, while it
// should have been an object. This is a trade-off we accept,
// considering most generators starting at 0 are actually lists.
$isList = ($value instanceof Generator && $value->key() === 0)
|| (is_array($value) && array_is_list($value));
$isList = ! ($this->jsonEncodingOptions & JSON_FORCE_OBJECT)
&& (
($value instanceof Generator && $value->key() === 0)
|| (is_array($value) && array_is_list($value))
);

$isFirst = true;

Expand Down
4 changes: 3 additions & 1 deletion src/Normalizer/JsonNormalizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use function is_resource;
use function stream_get_contents;

use const JSON_FORCE_OBJECT;
use const JSON_HEX_AMP;
use const JSON_HEX_APOS;
use const JSON_HEX_QUOT;
Expand All @@ -34,7 +35,8 @@
*/
final class JsonNormalizer implements Normalizer
{
private const ACCEPTABLE_JSON_OPTIONS = JSON_HEX_QUOT
private const ACCEPTABLE_JSON_OPTIONS = JSON_FORCE_OBJECT
| JSON_HEX_QUOT
| JSON_HEX_TAG
| JSON_HEX_AMP
| JSON_HEX_APOS
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Parser/Lexer/TokenizedAnnotation.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ final class TokenizedAnnotation
public function __construct(
/** @var non-empty-string */
private string $name,
/** @var non-empty-list<string>> */
/** @var non-empty-list<string> */
private array $tokens,
) {}

Expand Down
2 changes: 1 addition & 1 deletion src/Type/Types/ClassStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ final class ClassStringType implements StringType, CompositeType

private string $signature;

public function __construct(ObjectType|UnionType $subType = null)
public function __construct(ObjectType|UnionType|null $subType = null)
{
if ($subType instanceof UnionType) {
foreach ($subType->types() as $type) {
Expand Down
9 changes: 9 additions & 0 deletions tests/Integration/Mapping/UnionMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@ public static function union_mapping_works_properly_data_provider(): iterable
'assertion' => fn (mixed $result) => self::assertNull($result),
];

yield 'nullable float with integer value' => [
'type' => 'float|null',
'source' => 42,
'assertion' => function (mixed $result) {
self::assertIsFloat($result);
self::assertEquals(42.0, $result);
},
];

yield 'string or list of string, with string' => [
'type' => 'string|list<string>',
'source' => 'foo',
Expand Down
21 changes: 17 additions & 4 deletions tests/Integration/Normalizer/NormalizerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

use function array_merge;

use const JSON_FORCE_OBJECT;
use const JSON_HEX_TAG;
use const JSON_THROW_ON_ERROR;

Expand Down Expand Up @@ -194,6 +195,21 @@ public static function normalize_basic_values_yields_expected_output_data_provid
'expected json' => '{"foo":"foo","bar":"bar"}',
];

yield 'list' => [
'input' => ['foo', 'bar'],
'expected array' => ['foo', 'bar'],
'expected json' => '["foo","bar"]',
];

yield 'list kept as object in json' => [
'input' => ['foo', 'bar'],
'expected array' => ['foo', 'bar'],
'expected json' => '{"0":"foo","1":"bar"}',
[],
[],
JSON_FORCE_OBJECT
];

yield 'ArrayObject' => [
'input' => new ArrayObject(['foo' => 'foo', 'bar' => 'bar']),
'expected array' => [
Expand Down Expand Up @@ -1148,16 +1164,13 @@ public function test_json_transformer_will_always_throw_on_error(): void

public function test_json_transformer_only_accepts_acceptable_json_options(): void
{
$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_FORCE_OBJECT);
self::assertSame(JSON_THROW_ON_ERROR, (fn () => $this->jsonEncodingOptions)->call($normalizer));

$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_PARTIAL_OUTPUT_ON_ERROR);
self::assertSame(JSON_THROW_ON_ERROR, (fn () => $this->jsonEncodingOptions)->call($normalizer));

$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_PRETTY_PRINT);
self::assertSame(JSON_THROW_ON_ERROR, (fn () => $this->jsonEncodingOptions)->call($normalizer));

$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_FORCE_OBJECT | JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_PRETTY_PRINT);
$normalizer = $this->mapperBuilder()->normalizer(Format::json())->withOptions(JSON_PARTIAL_OUTPUT_ON_ERROR | JSON_PRETTY_PRINT);
self::assertSame(JSON_THROW_ON_ERROR, (fn () => $this->jsonEncodingOptions)->call($normalizer));
}
}
Expand Down

0 comments on commit f82591c

Please sign in to comment.