Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impossible to use messenger Input class with empty DTO (no attributes) #2773

Closed
Nightbr opened this issue May 3, 2019 · 14 comments
Closed

Comments

@Nightbr
Copy link
Contributor

Nightbr commented May 3, 2019

Hey folks,

I'm running into an issue where I want to use API entrypoint with messenger="input" and my input class (Command) is a DTO and it has no attributes.

A simple example of a User entity who needs to resend its validation email:

Entity/User.php

...
 *         "postResendValidationMail"={
 *             "method"="POST",
 *             "path"="/users/resend_validation_mail",
 *             "output"=false,
 *             "status"=202,
 *             "input"=ResendValidationMailCommand::class,
 *             "messenger"="input",
 *             "access_control"="is_granted('IS_AUTHENTICATED_FULLY')"
 *         },
...

And the input class (DTO) here we called them Command:
ResendValidationMailCommand.php

<?php

declare(strict_types=1);

namespace App\Domain\User\Command;

/**
 * Class ResendValidationMailCommand.
 */
class ResendValidationMailCommand implements UserCommandInterface
{
}

This will always throw an error because the body is empty when we call the POST entrypoint:

{
  "@context": "\/api\/contexts\/Error",
  "@type": "hydra:Error",
  "hydra:title": "An error occurred",
  "hydra:description": "Cannot validate values of type \"NULL\" automatically. Please provide a constraint.",
  "trace": [
    {
      "namespace": "",
      "short_class": "",
      "class": "",
      "type": "",
      "function": "",
      "file": "\/var\/www\/vendor\/symfony\/validator\/Validator\/RecursiveContextualValidator.php",
      "line": 166,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\Validator\\Validator",
      "short_class": "RecursiveContextualValidator",
      "class": "Symfony\\Component\\Validator\\Validator\\RecursiveContextualValidator",
      "type": "->",
      "function": "validate",
      "file": "\/var\/www\/vendor\/symfony\/validator\/Validator\/RecursiveValidator.php",
      "line": 100,
      "args": [
        [
          "null",
          null
        ],
        [
          "null",
          null
        ],
        [
          "array",
          [
            [
              "string",
              "Default"
            ]
          ]
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Validator\\Validator",
      "short_class": "RecursiveValidator",
      "class": "Symfony\\Component\\Validator\\Validator\\RecursiveValidator",
      "type": "->",
      "function": "validate",
      "file": "\/var\/www\/vendor\/symfony\/validator\/Validator\/TraceableValidator.php",
      "line": 66,
      "args": [
        [
          "null",
          null
        ],
        [
          "null",
          null
        ],
        [
          "null",
          null
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Validator\\Validator",
      "short_class": "TraceableValidator",
      "class": "Symfony\\Component\\Validator\\Validator\\TraceableValidator",
      "type": "->",
      "function": "validate",
      "file": "\/var\/www\/vendor\/liip\/functional-test-bundle\/src\/Validator\/DataCollectingValidator.php",
      "line": 66,
      "args": [
        [
          "null",
          null
        ],
        [
          "null",
          null
        ],
        [
          "null",
          null
        ]
      ]
    },
    {
      "namespace": "Liip\\FunctionalTestBundle\\Validator",
      "short_class": "DataCollectingValidator",
      "class": "Liip\\FunctionalTestBundle\\Validator\\DataCollectingValidator",
      "type": "->",
      "function": "validate",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Bridge\/Symfony\/Validator\/Validator.php",
      "line": 61,
      "args": [
        [
          "null",
          null
        ],
        [
          "null",
          null
        ],
        [
          "null",
          null
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Bridge\\Symfony\\Validator",
      "short_class": "Validator",
      "class": "ApiPlatform\\Core\\Bridge\\Symfony\\Validator\\Validator",
      "type": "->",
      "function": "validate",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Validator\/EventListener\/ValidateListener.php",
      "line": 62,
      "args": [
        [
          "null",
          null
        ],
        [
          "array",
          {
            "groups": [
              "null",
              null
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Validator\\EventListener",
      "short_class": "ValidateListener",
      "class": "ApiPlatform\\Core\\Validator\\EventListener\\ValidateListener",
      "type": "->",
      "function": "onKernelView",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/Debug\/WrappedListener.php",
      "line": 115,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent"
        ],
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "WrappedListener",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener",
      "type": "->",
      "function": "__invoke",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
      "line": 212,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent"
        ],
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "doDispatch",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
      "line": 44,
      "args": [
        [
          "array",
          [
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ]
          ]
        ],
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/Debug\/TraceableEventDispatcher.php",
      "line": 145,
      "args": [
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "TraceableEventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 155,
      "args": [
        [
          "string",
          "kernel.view"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handleRaw",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 67,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/Kernel.php",
      "line": 198,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ],
        [
          "boolean",
          true
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "Kernel",
      "class": "Symfony\\Component\\HttpKernel\\Kernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/public\/index.php",
      "line": 25,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ]
      ]
    }
  ]
}

Analysis

It seems the Symfony Validator is always called even if there is no attribute to be validated. It think it could be great to handle this case in https://github.com/api-platform/core/blob/master/src/Bridge/Symfony/Validator/EventListener/ValidateListener.php#L85
When the $validationGroup & $data are null/empty, don't call validate.

I think it's more in https://github.com/api-platform/core/blob/master/src/Validator/EventListener/ValidateListener.php#L60 we need to handle empty DTO input.

Further investigation

I tried to put "defaults"={"_api_receive"=false}, on the operation but it can't handle $data null for POST:

{
  "@context": "\/api\/contexts\/Error",
  "@type": "hydra:Error",
  "hydra:title": "An error occurred",
  "hydra:description": "Controller \"ApiPlatform\\Core\\Action\\PlaceholderAction\" requires that you provide a value for the \"$data\" argument. Either the argument is nullable and no null value has been provided, no default value has been provided or because there is a non optional argument after this one.",
  "trace": [
    {
      "namespace": "",
      "short_class": "",
      "class": "",
      "type": "",
      "function": "",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/Controller\/ArgumentResolver.php",
      "line": 78,
      "args": []
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel\\Controller",
      "short_class": "ArgumentResolver",
      "class": "Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver",
      "type": "->",
      "function": "getArguments",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/Controller\/TraceableArgumentResolver.php",
      "line": 38,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "object",
          "ApiPlatform\\Core\\Action\\PlaceholderAction"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel\\Controller",
      "short_class": "TraceableArgumentResolver",
      "class": "Symfony\\Component\\HttpKernel\\Controller\\TraceableArgumentResolver",
      "type": "->",
      "function": "getArguments",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 142,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "object",
          "ApiPlatform\\Core\\Action\\PlaceholderAction"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handleRaw",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 67,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/Kernel.php",
      "line": 198,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ],
        [
          "boolean",
          true
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "Kernel",
      "class": "Symfony\\Component\\HttpKernel\\Kernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/public\/index.php",
      "line": 25,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ]
      ]
    }
  ]
}

Workaround

The only workaround we found is to add a dummy attribute in the DTO but it's not ideal:

<?php

declare(strict_types=1);

namespace App\Domain\User\Command;

/**
 * Class ResendValidationMailCommand.
 */
class ResendValidationMailCommand implements UserCommandInterface
{
    /**
     * @var string
     */
    public $dummy = '';
}

ideas or other workaround?

Any idea on this? Or workaround because we have several entrypoint we want to manage with CQRS pattern which haven't data to transport (just the info that Command is called).

Thanks in advance ;)

@soyuka
Copy link
Member

soyuka commented May 3, 2019

#2757 might help you fix this
I'm also okay for a fix in the validation but it may be wrong to not validate on these simple criteria :|.

@Nightbr
Copy link
Contributor Author

Nightbr commented May 3, 2019

Hey, thanks for the quick answer!

Sure if we can control the listener it will handle our use case perfectly and be less intrusive in the Validation process ;)

I think we can say: No validation if No attribute & No data. But I'm not sure if there is some drawback to do this.

@teohhanhui
Copy link
Contributor

If your input DTO has no attributes, you must send an empty JSON object, not an empty body.

@Nightbr
Copy link
Contributor Author

Nightbr commented May 3, 2019

I tried with an empty DTO and send an empty JSON object {} but still an error:
ResendValidationMailCommand.php (UserCommandInterface is just an empty interface)

<?php

declare(strict_types=1);

namespace App\Domain\User\Command;

/**
 * Class ResendValidationMailCommand.
 */
class ResendValidationMailCommand implements UserCommandInterface
{
}

Response:

{
  "@context": "\/api\/contexts\/Error",
  "@type": "hydra:Error",
  "hydra:title": "An error `occurred",`
  "hydra:description": "There is no PropertyInfo extractor supporting the class \"App\\Domain\\User\\Command\\ResendValidationMailCommand\".",
  "trace": [
    {
      "namespace": "",```
      "short_class": "",
      "class": "",
      "type": "",
      "function": "",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Bridge\/Symfony\/PropertyInfo\/Metadata\/Property\/PropertyInfoPropertyNameCollectionFactory.php",
      "line": 46,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Core\\Bridge\\Symfony\\PropertyInfo\\Metadata\\Property",
      "short_class": "PropertyInfoPropertyNameCollectionFactory",
      "class": "ApiPlatform\\Core\\Bridge\\Symfony\\PropertyInfo\\Metadata\\Property\\PropertyInfoPropertyNameCollectionFactory",
      "type": "->",
      "function": "create",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/InheritedPropertyNameCollectionFactory.php",
      "line": 44,
      "args": [
        [
          "string",
          "App\\Domain\\User\\Command\\ResendValidationMailCommand"
        ],
        [
          "array",
          {
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
      "short_class": "InheritedPropertyNameCollectionFactory",
      "class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\InheritedPropertyNameCollectionFactory",
      "type": "->",
      "function": "create",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/ExtractorPropertyNameCollectionFactory.php",
      "line": 50,
      "args": [
        [
          "string",
          "App\\Domain\\User\\Command\\ResendValidationMailCommand"
        ],
        [
          "array",
          {
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
      "short_class": "ExtractorPropertyNameCollectionFactory",
      "class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\ExtractorPropertyNameCollectionFactory",
      "type": "->",
      "function": "create",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/ExtractorPropertyNameCollectionFactory.php",
      "line": 50,
      "args": [
        [
          "string",
          "App\\Domain\\User\\Command\\ResendValidationMailCommand"
        ],
        [
          "array",
          {
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
      "short_class": "ExtractorPropertyNameCollectionFactory",
      "class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\ExtractorPropertyNameCollectionFactory",
      "type": "->",
      "function": "create",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/CachedPropertyNameCollectionFactory.php",
      "line": 47,
      "args": [
        [
          "string",
          "App\\Domain\\User\\Command\\ResendValidationMailCommand"
        ],
        [
          "array",
          {
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
      "short_class": "CachedPropertyNameCollectionFactory",
      "class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\CachedPropertyNameCollectionFactory",
      "type": "->",
      "function": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\{closure}",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Cache\/CachedTrait.php",
      "line": 44,
      "args": []
    },
    {
      "namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
      "short_class": "CachedPropertyNameCollectionFactory",
      "class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\CachedPropertyNameCollectionFactory",
      "type": "->",
      "function": "getCached",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Metadata\/Property\/Factory\/CachedPropertyNameCollectionFactory.php",
      "line": 48,
      "args": [
        [
          "string",
          "property_name_collection_24a6f81e1e9443f5e1f3be6986ae3e8d"
        ],
        [
          "object",
          "Closure"
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Metadata\\Property\\Factory",
      "short_class": "CachedPropertyNameCollectionFactory",
      "class": "ApiPlatform\\Core\\Metadata\\Property\\Factory\\CachedPropertyNameCollectionFactory",
      "type": "->",
      "function": "create",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Serializer\/AbstractItemNormalizer.php",
      "line": 291,
      "args": [
        [
          "string",
          "App\\Domain\\User\\Command\\ResendValidationMailCommand"
        ],
        [
          "array",
          {
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Serializer",
      "short_class": "AbstractItemNormalizer",
      "class": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer",
      "type": "->",
      "function": "getAllowedAttributes",
      "file": "\/var\/www\/vendor\/symfony\/serializer\/Normalizer\/AbstractObjectNormalizer.php",
      "line": 278,
      "args": [
        [
          "string",
          "App\\Domain\\User\\Command\\ResendValidationMailCommand"
        ],
        [
          "array",
          {
            "resource_class": [
              "string",
              "App\\Domain\\User\\Command\\ResendValidationMailCommand"
            ],
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ],
            "api_allow_update": [
              "boolean",
              false
            ],
            "input": [
              "array",
              {
                "class": [
                  "string",
                  "App\\Domain\\User\\Command\\ResendValidationMailCommand"
                ],
                "name": [
                  "string",
                  "ResendValidationMailCommand"
                ]
              }
            ],
            "output": [
              "array",
              {
                "class": [
                  "null",
                  null
                ]
              }
            ],
            "request_uri": [
              "string",
              "\/api\/users\/resend_validation_mail"
            ],
            "uri": [
              "string",
              "http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
            ],
            "api_denormalize": [
              "boolean",
              true
            ],
            "cache_key": [
              "string",
              "be175295861aa5aa1e028b51fff958fe"
            ]
          }
        ],
        [
          "boolean",
          true
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Serializer\\Normalizer",
      "short_class": "AbstractObjectNormalizer",
      "class": "Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer",
      "type": "->",
      "function": "denormalize",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Serializer\/AbstractItemNormalizer.php",
      "line": 179,
      "args": [
        [
          "array",
          []
        ],
        [
          "string",
          "App\\Domain\\User\\Command\\ResendValidationMailCommand"
        ],
        [
          "string",
          "json"
        ],
        [
          "array",
          {
            "resource_class": [
              "string",
              "App\\Domain\\User\\Command\\ResendValidationMailCommand"
            ],
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ],
            "api_allow_update": [
              "boolean",
              false
            ],
            "input": [
              "array",
              {
                "class": [
                  "string",
                  "App\\Domain\\User\\Command\\ResendValidationMailCommand"
                ],
                "name": [
                  "string",
                  "ResendValidationMailCommand"
                ]
              }
            ],
            "output": [
              "array",
              {
                "class": [
                  "null",
                  null
                ]
              }
            ],
            "request_uri": [
              "string",
              "\/api\/users\/resend_validation_mail"
            ],
            "uri": [
              "string",
              "http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
            ],
            "api_denormalize": [
              "boolean",
              true
            ],
            "cache_key": [
              "string",
              "be175295861aa5aa1e028b51fff958fe"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Serializer",
      "short_class": "AbstractItemNormalizer",
      "class": "ApiPlatform\\Core\\Serializer\\AbstractItemNormalizer",
      "type": "->",
      "function": "denormalize",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/Serializer\/ItemNormalizer.php",
      "line": 71,
      "args": [
        [
          "array",
          []
        ],
        [
          "string",
          "App\\Infrastructure\\Doctrine\\Projection\\User"
        ],
        [
          "string",
          "json"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ],
            "api_allow_update": [
              "boolean",
              false
            ],
            "resource_class": [
              "string",
              "App\\Infrastructure\\Doctrine\\Projection\\User"
            ],
            "input": [
              "array",
              {
                "class": [
                  "string",
                  "App\\Domain\\User\\Command\\ResendValidationMailCommand"
                ],
                "name": [
                  "string",
                  "ResendValidationMailCommand"
                ]
              }
            ],
            "output": [
              "array",
              {
                "class": [
                  "null",
                  null
                ]
              }
            ],
            "request_uri": [
              "string",
              "\/api\/users\/resend_validation_mail"
            ],
            "uri": [
              "string",
              "http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
            ],
            "api_denormalize": [
              "boolean",
              true
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\Serializer",
      "short_class": "ItemNormalizer",
      "class": "ApiPlatform\\Core\\Serializer\\ItemNormalizer",
      "type": "->",
      "function": "denormalize",
      "file": "\/var\/www\/vendor\/symfony\/serializer\/Serializer.php",
      "line": 191,
      "args": [
        [
          "array",
          []
        ],
        [
          "string",
          "App\\Infrastructure\\Doctrine\\Projection\\User"
        ],
        [
          "string",
          "json"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ],
            "api_allow_update": [
              "boolean",
              false
            ],
            "resource_class": [
              "string",
              "App\\Infrastructure\\Doctrine\\Projection\\User"
            ],
            "input": [
              "array",
              {
                "class": [
                  "string",
                  "App\\Domain\\User\\Command\\ResendValidationMailCommand"
                ],
                "name": [
                  "string",
                  "ResendValidationMailCommand"
                ]
              }
            ],
            "output": [
              "array",
              {
                "class": [
                  "null",
                  null
                ]
              }
            ],
            "request_uri": [
              "string",
              "\/api\/users\/resend_validation_mail"
            ],
            "uri": [
              "string",
              "http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Serializer",
      "short_class": "Serializer",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->",
      "function": "denormalize",
      "file": "\/var\/www\/vendor\/symfony\/serializer\/Serializer.php",
      "line": 142,
      "args": [
        [
          "array",
          []
        ],
        [
          "string",
          "App\\Infrastructure\\Doctrine\\Projection\\User"
        ],
        [
          "string",
          "json"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ],
            "api_allow_update": [
              "boolean",
              false
            ],
            "resource_class": [
              "string",
              "App\\Infrastructure\\Doctrine\\Projection\\User"
            ],
            "input": [
              "array",
              {
                "class": [
                  "string",
                  "App\\Domain\\User\\Command\\ResendValidationMailCommand"
                ],
                "name": [
                  "string",
                  "ResendValidationMailCommand"
                ]
              }
            ],
            "output": [
              "array",
              {
                "class": [
                  "null",
                  null
                ]
              }
            ],
            "request_uri": [
              "string",
              "\/api\/users\/resend_validation_mail"
            ],
            "uri": [
              "string",
              "http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\Serializer",
      "short_class": "Serializer",
      "class": "Symfony\\Component\\Serializer\\Serializer",
      "type": "->",
      "function": "deserialize",
      "file": "\/var\/www\/vendor\/api-platform\/core\/src\/EventListener\/DeserializeListener.php",
      "line": 100,
      "args": [
        [
          "array",
          []
        ],
        [
          "string",
          "App\\Infrastructure\\Doctrine\\Projection\\User"
        ],
        [
          "string",
          "json"
        ],
        [
          "array",
          {
            "operation_type": [
              "string",
              "collection"
            ],
            "collection_operation_name": [
              "string",
              "postResendValidationMail"
            ],
            "api_allow_update": [
              "boolean",
              false
            ],
            "resource_class": [
              "string",
              "App\\Infrastructure\\Doctrine\\Projection\\User"
            ],
            "input": [
              "array",
              {
                "class": [
                  "string",
                  "App\\Domain\\User\\Command\\ResendValidationMailCommand"
                ],
                "name": [
                  "string",
                  "ResendValidationMailCommand"
                ]
              }
            ],
            "output": [
              "array",
              {
                "class": [
                  "null",
                  null
                ]
              }
            ],
            "request_uri": [
              "string",
              "\/api\/users\/resend_validation_mail"
            ],
            "uri": [
              "string",
              "http:\/\/api.renters.test\/api\/users\/resend_validation_mail"
            ]
          }
        ]
      ]
    },
    {
      "namespace": "ApiPlatform\\Core\\EventListener",
      "short_class": "DeserializeListener",
      "class": "ApiPlatform\\Core\\EventListener\\DeserializeListener",
      "type": "->",
      "function": "onKernelRequest",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/Debug\/WrappedListener.php",
      "line": 115,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
        ],
        [
          "string",
          "kernel.request"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "WrappedListener",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener",
      "type": "->",
      "function": "__invoke",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
      "line": 212,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
        ],
        [
          "string",
          "kernel.request"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Debug\\TraceableEventDispatcher"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "doDispatch",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/EventDispatcher.php",
      "line": 44,
      "args": [
        [
          "array",
          [
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ],
            [
              "object",
              "Symfony\\Component\\EventDispatcher\\Debug\\WrappedListener"
            ]
          ]
        ],
        [
          "string",
          "kernel.request"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher",
      "short_class": "EventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "\/var\/www\/vendor\/symfony\/event-dispatcher\/Debug\/TraceableEventDispatcher.php",
      "line": 145,
      "args": [
        [
          "string",
          "kernel.request"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\EventDispatcher\\Debug",
      "short_class": "TraceableEventDispatcher",
      "class": "Symfony\\Component\\EventDispatcher\\Debug\\TraceableEventDispatcher",
      "type": "->",
      "function": "dispatch",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 126,
      "args": [
        [
          "string",
          "kernel.request"
        ],
        [
          "object",
          "Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent"
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handleRaw",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/HttpKernel.php",
      "line": 67,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "HttpKernel",
      "class": "Symfony\\Component\\HttpKernel\\HttpKernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/vendor\/symfony\/http-kernel\/Kernel.php",
      "line": 198,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ],
        [
          "integer",
          1
        ],
        [
          "boolean",
          true
        ]
      ]
    },
    {
      "namespace": "Symfony\\Component\\HttpKernel",
      "short_class": "Kernel",
      "class": "Symfony\\Component\\HttpKernel\\Kernel",
      "type": "->",
      "function": "handle",
      "file": "\/var\/www\/public\/index.php",
      "line": 25,
      "args": [
        [
          "object",
          "Symfony\\Component\\HttpFoundation\\Request"
        ]
      ]
    }
  ]
}

@soyuka
Copy link
Member

soyuka commented May 3, 2019

I think this issue exists indeed because your class is empty.

@Nightbr
Copy link
Contributor Author

Nightbr commented May 3, 2019

Ok, so I think it could be a nice feature to be able to handle Empty DTO (Command) for some use case such as ResendUserPassword, POST workflow change state, ...

I think we could discuss on this implementation. Is it link to the possibility to toggle listeners or is it built in the different part of API platform (add condition on validator, propertyInfo, ...)?

@soyuka
Copy link
Member

soyuka commented May 3, 2019

IMO if you really want this behavior you should implement your own listener.

@Nightbr
Copy link
Contributor Author

Nightbr commented May 3, 2019

it's not a feature we want to handle in API Platform itself? I can make a PR if it's the subject here.

@soyuka
Copy link
Member

soyuka commented May 3, 2019

I think that we should fix this in the

throw new RuntimeException(sprintf('There is no PropertyInfo extractor supporting the class "%s".', $resourceClass));
instead. We should return new PropertyNameCollection([]) instead but I'm afraid it'd break some stuff :p.

@teohhanhui
Copy link
Contributor

No, it should be fixed in PropertyInfo. null should not be returned from getProperties when the class simply has no properties.

@teohhanhui
Copy link
Contributor

teohhanhui commented May 3, 2019

This is wrong: https://github.com/symfony/symfony/blob/v4.2.8/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php#L98

But perhaps it's done that way because PropertyInfo does not have a way to merge results from multiple extractors.

@soyuka
Copy link
Member

soyuka commented May 5, 2019

👍 indeed I also thought about this, we should try to fix this upstream then :/

@zalesak
Copy link

zalesak commented Jul 11, 2019

For me this workaround works:

  1. set "defaults"={"_api_receive"=false},
  2. set controller for operation which allow null value for $data, this disangeage PlaceholderAction
class NotificationTokensDelete
{
    public function __invoke($data = null)
    {
        return $data;
    }
}

It would by nice if PlaceholderAction allows null as value for $data - this problem raise everytime when you disable read listener and has empty body. For example, I have delete action for notification token for logged user, so there is no need to send id for entity. Read, validation listeners are disabled and everything I need is custom data persister - there is also POST endpoint for set tokens.

@soyuka
Copy link
Member

soyuka commented Feb 23, 2021

Greetings! We appreciate your concern but weren't able to reproduce this issue or it is more of a question. As described in the API Platform contributing guide, we use GitHub issues for bugs and feature requests only.

For support question ("How To", usage advice, or troubleshooting your own code), you have several options:

Feel free reach one of the support channels above. In the meantime we're closing this issue.

@soyuka soyuka closed this as completed Feb 23, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants