0

I am writing end2end tests for a rest api in a Symfony 4 project.

I use php 7.4, Swagger annotation, nelmio/api-doc-bundle 3.6.1, nelmio/cors-bundle 1.5.6

This is my controller code for the method.

/**
 * @Route(path="", name="create", methods={"POST"})
 * @ValidationGroups({"create"})
 *
 * @SWG\Post(
 *     path="/api/professions",
 *     tags={"Endpoint: Professions"},
 *     summary="save a profession",
 *     @SWG\Parameter(
 *         name="ProfessionDto",
 *         required=true,
 *         in="body",
 *         @Model(type=ProfessionDto::class)
 *     ),
 *     @SWG\Response(
 *         response=201,
 *         description="created",
 *     )
 * )
 */
public function add(ProfessionDto $professionDto): CreatedView
{
    $professionDto = $this->professionService->insert($professionDto);

    return $this->created(['profession' => $professionDto]);
}

ProfessionDto is the object defining the exchanged data. It contains as property some more objects, since I want to have some structure in the returned data and not just a heap of key value pairs.

In the class ProfessionDto I defined the properties, which relate to other objects e.g.:


namespace App\Api\Dto;

use Nelmio\ApiDocBundle\Annotation\Model;
use Swagger\Annotations as SWG;
use Symfony\Component\Serializer\Annotation\Groups as SerializerGroups;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @SWG\Definition()
 */
class ProfessionDto
{
    use HistoryDto;

    /**
     * @SWG\Property(description="Id", type="integer")
     *
     * @SerializerGroups({"view", "update", "collection"})
     */
    public ?int $id = null;

    /**
     * @Assert\NotBlank(groups={"create"})
     * @Assert\Type("string")
     * @SWG\Property(description="...", type="string")
     */
    public string $name;

    /**
     * @Assert\Type("boolean")
     * @SWG\Property(description="ist aktiviert/deaktiviert", type="boolean")
     */
    public bool $active;

    /**
     * @SWG\Property(
     *     description="...",
     *     type="object",
     *     ref=@Model(type=ProfessionAreaDto::class)
     * )
     *
     * @SerializerGroups({"view", "create", "update"})
     */
    public ?ProfessionAreaDto $professionArea = null;

    /**
     * @SWG\Property(
     *     description="...",
     *     type="object",
     *     ref=@Model(type=ProfessionalActivityDto::class)
     * )
     *
     * @SerializerGroups({"view", "create", "update"})
     */
    public ?ProfessionalActivityDto $professionalActivity = null;

    /**
     * @SWG\Property(
     *     description="...",
     *     type="object",
     *     ref=@Model(type=IntroductionDto::class)
     * )
     *
     * @SerializerGroups({"view", "create", "update"})
     */
    public ?IntroductionDto $introduction = null;

    /**
     * @SWG\Property(
     *     description="...",
     *     type="object",
     *     ref=@Model(type=PerformanceBehaviourDto::class)
     * )
     *
     * @SerializerGroups({"view", "create", "update"})
     */
    public ?PerformanceBehaviourDto $performanceBehaviour = null;
}

If I call the api with postman or via my test and pass in my data as json e.g.

{
  "id":null,
  "name":"fancy new data",
  "active":true,
  "professionArea":{
    "id":48,
    "name":"Vitae nulla aperiam aut enim.",
    "active":true,
    "signature":null,
    "signatureTypeId":null,
    "transition":null,
    "infotextCheck":null
  },
  "professionalActivity":{
    "id":null,
    "textMResignationTestimonial":null,
    ...
    "changedBy":null
  },
  "introduction":{
    "id":null,
    "textMResignationTestimonial":null,
    "textMInterimTestimonial":null,
    ...
    "changedOn":null,
    "changedBy":null
  },
  "performanceBehaviour":{
    "id":null,
    "textMResignationGrade1":null,
    "textMInterimGrade1":null,
    ...
    "changedOn":null,
    "changedBy":null
  },
  "createdOn":null,
  "createdBy":null,
  "changedOn":null,
  "changedBy":null
}

I get the error message: TypeError: Typed property App\Api\Dto\ProfessionDto::$professionArea must be an instance of App\Api\Dto\ProfessionAreaDto or null, array used

What did I do wrong? Do I expect too much? are objects within objects not possible?

2
  • Please show ProfessionDto Commented Jun 30, 2020 at 14:30
  • Added the ProfessionDto class code Commented Jun 30, 2020 at 15:34

1 Answer 1

0

Since this project didn't use the friendsofsymfony/rest-bundle and the Controller didn't extend the AbstractFOSRestController.

So the solution to my problem was to manually write a Denormalizer class, which maps my array back to the proper objects.
This is how I did it:


namespace App\Serializer;


use App\Api\Dto\IntroductionDto;
use App\Api\Dto\PerformanceBehaviourDto;
use App\Api\Dto\ProfessionalActivityDto;
use App\Api\Dto\ProfessionAreaDto;
use App\Api\Dto\ProfessionDto;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;

class ProfessionDeNormalizer implements DenormalizerInterface
{
    public function denormalize($data, $type, $format = null, array $context = [])
    {
        $dto = new ProfessionDto();
        $areaDto = new ProfessionAreaDto();
        $introDto = new IntroductionDto();
        $activityDto = new ProfessionalActivityDto();
        $performanceDto = new PerformanceBehaviourDto();

        foreach($data['professionArea'] as $key => $value) {
            $areaDto->$key = $value;
        }
        unset($data['professionArea']);

        foreach($data['introduction'] as $key => $value) {
            $introDto->$key = $value;
        }
        unset($data['introduction']);

        foreach($data['professionalActivity'] as $key => $value) {
            $areaDto->$key = $value;
        }
        unset($data['professionalActivity']);

        foreach($data['performanceBehaviour'] as $key => $value) {
            $areaDto->$key = $value;
        }
        unset($data['performanceBehaviour']);

        foreach($data as $key => $value) {
            $dto->$key = $value;
        }

        $dto->professionArea = $areaDto;
        $dto->professionalActivity = $activityDto;
        $dto->performanceBehaviour = $performanceDto;
        $dto->introduction = $introDto;

        return $dto;
    }

    public function supportsDenormalization($data, $type, $format = null)
    {
        return ProfessionDto::class === $type;
    }

}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.