Skip to content

Commit

Permalink
Merge pull request #1317 from scaytrase/pr/1167
Browse files Browse the repository at this point in the history
Check data can be casted before actual casting
  • Loading branch information
goetas authored May 3, 2021
2 parents b4b7fa9 + 667f2c0 commit bd50175
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 8 deletions.
47 changes: 47 additions & 0 deletions src/AbstractVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

namespace JMS\Serializer;

use JMS\Serializer\Exception\NonFloatCastableTypeException;
use JMS\Serializer\Exception\NonIntCastableTypeException;
use JMS\Serializer\Exception\NonStringCastableTypeException;

/**
* @internal
*/
Expand Down Expand Up @@ -39,4 +43,47 @@ protected function getElementType(array $typeArray): ?array
return $typeArray['params'][0];
}
}

/**
* logic according to strval https://www.php.net/manual/en/function.strval.php
* "You cannot use strval() on arrays or on objects that do not implement the __toString() method."
*
* @param mixed $value
*/
protected function assertValueCanBeCastToString($value): void
{
if (is_array($value)) {
throw new NonStringCastableTypeException($value);
}

if (is_object($value) && !method_exists($value, '__toString')) {
throw new NonStringCastableTypeException($value);
}
}

/**
* logic according to intval https://www.php.net/manual/en/function.intval.php
* "intval() should not be used on objects, as doing so will emit an E_NOTICE level error and return 1."
*
* @param mixed $value
*/
protected function assertValueCanBeCastToInt($value): void
{
if (is_object($value) && !$value instanceof \SimpleXMLElement) {
throw new NonIntCastableTypeException($value);
}
}

/**
* logic according to floatval https://www.php.net/manual/en/function.floatval.php
* "floatval() should not be used on objects, as doing so will emit an E_NOTICE level error and return 1."
*
* @param mixed $value
*/
protected function assertValueCanCastToFloat($value): void
{
if (is_object($value) && !$value instanceof \SimpleXMLElement) {
throw new NonFloatCastableTypeException($value);
}
}
}
37 changes: 37 additions & 0 deletions src/Exception/NonCastableTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Exception;

abstract class NonCastableTypeException extends RuntimeException
{
/**
* @var mixed
*/
private $value;

/**
* @param mixed $value
*/
public function __construct(string $expectedType, $value)
{
$this->value = $value;

parent::__construct(
sprintf(
'Cannot convert value of type "%s" to %s',
gettype($value),
$expectedType
)
);
}

/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
}
16 changes: 16 additions & 0 deletions src/Exception/NonFloatCastableTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Exception;

final class NonFloatCastableTypeException extends NonCastableTypeException
{
/**
* @param mixed $value
*/
public function __construct($value)
{
parent::__construct('float', $value);
}
}
16 changes: 16 additions & 0 deletions src/Exception/NonIntCastableTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Exception;

final class NonIntCastableTypeException extends NonCastableTypeException
{
/**
* @param mixed $value
*/
public function __construct($value)
{
parent::__construct('int', $value);
}
}
16 changes: 16 additions & 0 deletions src/Exception/NonStringCastableTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace JMS\Serializer\Exception;

final class NonStringCastableTypeException extends NonCastableTypeException
{
/**
* @param mixed $value
*/
public function __construct($value)
{
parent::__construct('string', $value);
}
}
6 changes: 6 additions & 0 deletions src/JsonDeserializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ public function visitNull($data, array $type)
*/
public function visitString($data, array $type): string
{
$this->assertValueCanBeCastToString($data);

return (string) $data;
}

Expand All @@ -71,6 +73,8 @@ public function visitBoolean($data, array $type): bool
*/
public function visitInteger($data, array $type): int
{
$this->assertValueCanBeCastToInt($data);

return (int) $data;
}

Expand All @@ -79,6 +83,8 @@ public function visitInteger($data, array $type): int
*/
public function visitDouble($data, array $type): float
{
$this->assertValueCanCastToFloat($data);

return (float) $data;
}

Expand Down
8 changes: 8 additions & 0 deletions src/XmlDeserializationVisitor.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ public function visitNull($data, array $type)
*/
public function visitString($data, array $type): string
{
$this->assertValueCanBeCastToString($data);

return (string) $data;
}

Expand All @@ -136,6 +138,8 @@ public function visitString($data, array $type): string
*/
public function visitBoolean($data, array $type): bool
{
$this->assertValueCanBeCastToString($data);

$data = (string) $data;

if ('true' === $data || '1' === $data) {
Expand All @@ -152,6 +156,8 @@ public function visitBoolean($data, array $type): bool
*/
public function visitInteger($data, array $type): int
{
$this->assertValueCanBeCastToInt($data);

return (int) $data;
}

Expand All @@ -160,6 +166,8 @@ public function visitInteger($data, array $type): int
*/
public function visitDouble($data, array $type): float
{
$this->assertValueCanCastToFloat($data);

return (float) $data;
}

Expand Down
50 changes: 42 additions & 8 deletions tests/Deserializer/BaseDeserializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,46 @@
namespace JMS\Serializer\Tests\Deserializer;

use JMS\Serializer\DeserializationContext;
use JMS\Serializer\Exception\NonCastableTypeException;
use JMS\Serializer\SerializerBuilder;
use JMS\Serializer\Tests\Fixtures\Discriminator\Car;
use JMS\Serializer\Tests\Fixtures\GroupsObject;
use JMS\Serializer\Tests\Fixtures\Price;
use JMS\Serializer\Tests\Fixtures\Publisher;
use PHPUnit\Framework\TestCase;

class BaseDeserializationTest extends TestCase
{
/**
* @dataProvider dataTypeCannotBeCasted
*/
public function testDeserializationInvalidDataCausesException($data, string $type): void
{
$serializer = SerializerBuilder::create()->build();

$this->expectException(NonCastableTypeException::class);

$serializer->fromArray($data, $type);
}

public function dataTypeCannotBeCasted(): iterable
{
yield 'array to string' => [
['pub_name' => ['bla', 'bla']],
Publisher::class,
];

yield 'object to float' => [
['price' => (object) ['bla' => 'bla']],
Price::class,
];

yield 'object to int' => [
['km' => (object) ['bla' => 'bla']],
Car::class,
];
}

/**
* @dataProvider dataDeserializerGroupExclusion
*/
Expand All @@ -25,17 +59,17 @@ public function testDeserializerGroupExclusion(array $data, array $groups, array
public function dataDeserializerGroupExclusion(): iterable
{
$data = [
'foo' => 'foo',
'foo' => 'foo',
'foobar' => 'foobar',
'bar' => 'bar',
'none' => 'none',
'bar' => 'bar',
'none' => 'none',
];

yield [
$data,
['Default'],
[
'bar' => 'bar',
'bar' => 'bar',
'none' => 'none',
],
];
Expand All @@ -44,7 +78,7 @@ public function dataDeserializerGroupExclusion(): iterable
$data,
['foo'],
[
'foo' => 'foo',
'foo' => 'foo',
'foobar' => 'foobar',
],
];
Expand All @@ -54,17 +88,17 @@ public function dataDeserializerGroupExclusion(): iterable
['bar'],
[
'foobar' => 'foobar',
'bar' => 'bar',
'bar' => 'bar',
],
];

yield [
$data,
['foo', 'bar'],
[
'foo' => 'foo',
'foo' => 'foo',
'foobar' => 'foobar',
'bar' => 'bar',
'bar' => 'bar',
],
];

Expand Down

0 comments on commit bd50175

Please sign in to comment.