Core: Invalid IRI instead of validation error

Created on 23 Mar 2020  路  2Comments  路  Source: api-platform/core

API Platform version(s) affected: v2.5.4

Description
Hi, I have 2 entities Booking and Department that has many to one relationship.

Booking entity

/**
 * @ApiResource(
 *      itemOperations={
 *          "get"={
 *              "normalization_context"={
 *                  "groups"={"read-item"}
 *              }
 *          },
 *          "put"={
 *              "denormalization_context"={
 *                  "groups"={"put"}
 *              }
 *          }
 *    }
 * @ORM\Entity(repositoryClass="App\Repository\BookingRepository")
class Booking
{
    /**
     * @ORM\ManyToOne(targetEntity="App\Entity\Department", inversedBy="bookings")
     * @ORM\JoinColumn(nullable=false)
     * @Assert\NotBlank
     * @Groups({"read-item", "put"})
     */
    private $department;
}

Department entity

/**
 * @ApiResource(
 *     collectionOperations={"get"},
 *      itemOperations={"get"}
 * )
 * @ORM\Entity(repositoryClass="App\Repository\DepartmentRepository")
 */
class Department
{
    /**
     * @ORM\OneToMany(targetEntity="App\Entity\Booking", mappedBy="department")
     */
    private $bookings;
}

When I send a post request (to create a new booking ) with a request body

{
  "department": ""
}

I was expecting a validation error but I got the below error

{
    "@context": "/api/contexts/Error",
    "@type": "hydra:Error",
    "hydra:title": "An error occurred",
    "hydra:description": "Invalid IRI \"\".",
    "trace": [
        {
            "namespace": "",
            "short_class": "",
            "class": "",
            "type": "",
            "function": "",
            "file": "/var/www/html/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
            "line": 418,
            "args": []
        },
        {
            "namespace": "ApiPlatform\\Core\\Serializer",
            "short_class": "AbstractItemNormalizer",
            "class": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer",
            "type": "->",
            "function": "denormalizeRelation",
            "file": "/var/www/html/vendor/api-platform/core/src/Serializer/AbstractItemNormalizer.php",
            "line": 685,
            "args": [
                [
                    "string",
                    "department"
                ],
                [
                    "object",
                    "ApiPlatform\\Core\\Metadata\\Property\\PropertyMetadata"
                ],
                [
                    "string",
                    "App\\Entity\\Department"
                ],
                [
                    "string",
                    ""
                ],
                [
                    "string",
                    "json"
                ],
                [
                    "array",
                    {
                        "operation_type": [
                            "string",
                            "collection"
                        ],
                        "collection_operation_name": [
                            "string",
                            "post"
                        ],
                        "api_allow_update": [
                            "boolean",
                            false
                        ],
                        "resource_class": [
                            "string",
                            "App\\Entity\\Department"
                        ],
                        "input": [
                            "null",
                            null
                        ],
                        "output": [
                            "null",
                            null
                        ],
                        "request_uri": [
                            "string",
                            "/api/bookings"
                        ],
                        "uri": [
                            "string",
                            "http://localhost/api/bookings"
                        ],
                        "skip_null_values": [
                            "boolean",
                            true
                        ],
                        "api_denormalize": [
                            "boolean",
                            true
                        ],
                        "cache_key": [
                            "string",
                            "db505854694e53aec6831eb427a03028"
                        ]
                    }
                ]
            ]
        }, 
}

Please may i ask as why it's not throwing a validation error like

```
{
"propertyPath": "department",
"message": "This value should not be blank."
}

Most helpful comment

Because validation occurs after deserialization. The serializer takes your empty value when denormalizing relation, but as it is invalid iri so it throws.

All 2 comments

Because validation occurs after deserialization. The serializer takes your empty value when denormalizing relation, but as it is invalid iri so it throws.

@norkunas Many Thanks. I've now added a custom denormaliser that removes the empty values and this fixed the issue :)

services.yml

    App\Serializer\EmptyValueDenormalizer:
        arguments: [ '@api_platform.serializer.normalizer.item' ]
        tags:
            - { name: serializer.normalizer, priority: 18 }

custom denormaliser

    namespace App\Serializer;
    use ApiPlatform\Core\Serializer\ItemNormalizer;
    use App\Entity\Booking;
    use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
    /**
     * Class EmptyValueDenormalizer
     *
     * @package App\Serializer
     */
    class EmptyValueDenormalizer implements DenormalizerInterface
    {
    /**
     * @var ItemNormalizer
     */
    private $decorated;

    /**
     * EmptyValuesDenormalizer constructor.
     *
     * @param ItemNormalizer $decorated
     */
    public function __construct(ItemNormalizer $decorated)
    {
        $this->decorated = $decorated;
    }

    /**
     * @param mixed $data
     * @param string $type
     * @param null $format
     * @param array $context
     * @return array|object|null
     *
     * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
     */
    public function denormalize($data, $type, $format = null, array $context = [])
    {
        $data = $this->removeEmptyValues($data, $type);

        return $this->decorated->denormalize($data, $type, $format, $context);
    }

    /**
     * @param $data
     * @param $type
     * @return mixed
     */
    private function removeEmptyValues($data, $type)
    {
        if ($type == Booking::class) {
            if (array_key_exists('pickupTime', $data) && ($data['pickupTime'] == ''|| is_null($data['pickupTime']))) {
                unset($data['pickupTime']);
            }

            if (array_key_exists('department', $data) && ($data['department'] == ''|| is_null($data['department']))) {
                unset($data['department']);
            }
        }

        return $data;
    }

    /**
     * @inheritDoc
     */
    public function supportsDenormalization($data, $type, $format = null)
    {
        return $this->decorated->supportsDenormalization($data, $type, $format);
    }
    } 
Was this page helpful?
0 / 5 - 0 ratings