> APIs >

Course Overview

API Platform 3 Parte 3: Recursos personalizados

  • 974 students
  • EN/ES Captions
  • EN/ES Script
  • Certificate of Completion

Your Guides

About this course

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "3.1.x-dev", // 3.1.x-dev
        "doctrine/annotations": "^2.0", // 2.0.1
        "doctrine/doctrine-bundle": "^2.8", // 2.10.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.4
        "doctrine/orm": "^2.14", // 2.16.1
        "nelmio/cors-bundle": "^2.2", // 2.3.1
        "nesbot/carbon": "^2.64", // 2.69.0
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.23.1
        "symfony/asset": "6.3.*", // v6.3.0
        "symfony/console": "6.3.*", // v6.3.2
        "symfony/dotenv": "6.3.*", // v6.3.0
        "symfony/expression-language": "6.3.*", // v6.3.0
        "symfony/flex": "^2", // v2.3.3
        "symfony/framework-bundle": "6.3.*", // v6.3.2
        "symfony/property-access": "6.3.*", // v6.3.2
        "symfony/property-info": "6.3.*", // v6.3.0
        "symfony/runtime": "6.3.*", // v6.3.2
        "symfony/security-bundle": "6.3.*", // v6.3.3
        "symfony/serializer": "6.3.*", // v6.3.3
        "symfony/stimulus-bundle": "^2.9", // v2.10.0
        "symfony/string": "6.3.*", // v6.3.2
        "symfony/twig-bundle": "6.3.*", // v6.3.0
        "symfony/ux-react": "^2.6", // v2.10.0
        "symfony/ux-vue": "^2.7", // v2.10.0
        "symfony/validator": "6.3.*", // v6.3.2
        "symfony/webpack-encore-bundle": "^2.0", // v2.0.1
        "symfony/yaml": "6.3.*", // v6.3.3
        "symfonycasts/micro-mapper": "^0.1.0" // v0.1.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
        "mtdowling/jmespath.php": "^2.6", // 2.6.1
        "phpunit/phpunit": "^9.5", // 9.6.11
        "symfony/browser-kit": "6.3.*", // v6.3.2
        "symfony/css-selector": "6.3.*", // v6.3.2
        "symfony/debug-bundle": "6.3.*", // v6.3.2
        "symfony/maker-bundle": "^1.48", // v1.50.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/phpunit-bridge": "^6.2", // v6.3.2
        "symfony/stopwatch": "6.3.*", // v6.3.0
        "symfony/web-profiler-bundle": "6.3.*", // v6.3.2
        "zenstruck/browser": "^1.2", // v1.4.0
        "zenstruck/foundry": "^1.26" // v1.35.0
    }
}

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "3.1.x-dev", // 3.1.x-dev
        "doctrine/annotations": "^2.0", // 2.0.1
        "doctrine/doctrine-bundle": "^2.8", // 2.10.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.4
        "doctrine/orm": "^2.14", // 2.16.1
        "nelmio/cors-bundle": "^2.2", // 2.3.1
        "nesbot/carbon": "^2.64", // 2.69.0
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.23.1
        "symfony/asset": "6.3.*", // v6.3.0
        "symfony/console": "6.3.*", // v6.3.2
        "symfony/dotenv": "6.3.*", // v6.3.0
        "symfony/expression-language": "6.3.*", // v6.3.0
        "symfony/flex": "^2", // v2.3.3
        "symfony/framework-bundle": "6.3.*", // v6.3.2
        "symfony/property-access": "6.3.*", // v6.3.2
        "symfony/property-info": "6.3.*", // v6.3.0
        "symfony/runtime": "6.3.*", // v6.3.2
        "symfony/security-bundle": "6.3.*", // v6.3.3
        "symfony/serializer": "6.3.*", // v6.3.3
        "symfony/stimulus-bundle": "^2.9", // v2.10.0
        "symfony/string": "6.3.*", // v6.3.2
        "symfony/twig-bundle": "6.3.*", // v6.3.0
        "symfony/ux-react": "^2.6", // v2.10.0
        "symfony/ux-vue": "^2.7", // v2.10.0
        "symfony/validator": "6.3.*", // v6.3.2
        "symfony/webpack-encore-bundle": "^2.0", // v2.0.1
        "symfony/yaml": "6.3.*", // v6.3.3
        "symfonycasts/micro-mapper": "^0.1.0" // v0.1.1
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.4
        "mtdowling/jmespath.php": "^2.6", // 2.6.1
        "phpunit/phpunit": "^9.5", // 9.6.11
        "symfony/browser-kit": "6.3.*", // v6.3.2
        "symfony/css-selector": "6.3.*", // v6.3.2
        "symfony/debug-bundle": "6.3.*", // v6.3.2
        "symfony/maker-bundle": "^1.48", // v1.50.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/phpunit-bridge": "^6.2", // v6.3.2
        "symfony/stopwatch": "6.3.*", // v6.3.0
        "symfony/web-profiler-bundle": "6.3.*", // v6.3.2
        "zenstruck/browser": "^1.2", // v1.4.0
        "zenstruck/foundry": "^1.26" // v1.35.0
    }
}

Gracias a la parte 1 y parte 2, ya hemos creado una API muy potente, con seguridad, campos personalizados y muchas más cosas. En este curso, llevaremos las cosas aún más lejos:

  • Proveedores de estado y campos personalizados "adecuados"
  • Ejecuta código personalizado en un cambio de "estado" (e.g. publicación)
  • [ApiResource] Clases DTO personalizadas (sin entidad)

  • El nuevo atajo stateOptions para DTO's
  • DTO's y proveedores de estado (make:state-provider)
  • Procesadores de estado con DTO's
  • Transformación de datos con symfonycasts/micro-mapper
  • IGNORED_ATTRIBUTES security y otros trucos para evitar los grupos de serialización
  • Objetos embebidos (incluidos los objetos que no son ApiResource)
  • Paginación para recursos DTO

Woh. Si pensabas que antes eras peligroso con API Platform, sólo espera...

Next courses in the APIs: API Platform 3 section of the APIs Track!

17 Comments

Sort By
Login or Register to join the conversation
Peter-P avatar Peter-P 5 months ago

HI, could you add an extra chapter about filters? I don't see how they work with custom resources. Is there an easy way to make it work?

2 | Reply |

Hey @Peter-P!

I'm hoping to do an episode 4 at some point, which will include filters. No timeline however! Until then, we do talk about custom filters in the API Platform v2 version of this tutorial - https://symfonycasts.com/screencast/api-platform2-extending - as far as I know, not so much has changed in this area.

I don't see how they work with custom resources. Is there an easy way to make it work?

But to answer this, yes, when you have a custom resource, you probably need to create custom filters. If you're using our stateOptions + entityClass trick, then you can reuse the normal "entity" filters, which we showed.

If you have any questions along the way, I'd be happy to do my best to answer them.

Cheers!

| Reply |
Evgeny avatar Evgeny 5 months ago

What about API Platform 3 Part 4: Using graphql to the fullest?

2 | Reply |

Thank you for your suggestion @Evgeny we love to know what other topics you'd like to learn, and we consider them when choosing our next tutorial.
Cheers!

2 | Reply |
Alessandro-M avatar Alessandro-M 8 months ago

Yes!!

This is the course I've been waiting.

Looking forward to it! :)

1 | Reply |

It's coming soon!

| Reply |
fGuix avatar fGuix 9 days ago

Hello,
Thanks for the Course.
I think there might be a bug in the chapter 36 of this part 3.
In the src/Validator/TreasuresAllowedOwnerChangeValidator.php file, there is a return statement in the loop :

foreach ($value->dragonTreasures as $dragonTreasureApi) {
    assert($dragonTreasureApi instanceof DragonTreasureApi);
    $originalOwnerId = $dragonTreasureApi->owner?->id;
    $newOwnerId = $value->id;
    if (!$originalOwnerId || $originalOwnerId === $newOwnerId) {
        return; // <-- HERE !
    }
    // the owner is being changed
    $this->context->buildViolation($constraint->message)
        ->addViolation();
}

I think this will be problematic when configuring multiple IRIs. Would't that check only the first DragonTreasureApi instance ? then if the first is owned by the user, the others won't be checked ?
As it is far in the course, the problem might come in earlier chapters.

In a completely other subject, I saw you talked about releasing a new episode. It might be good to see how to handle file upload, I followed the official documentation and I had a really difficult time to set it up ;)

| Reply |

Hey @fGuix

That's a good observation. The validator will stop if the DragonTreasureApi object has no owner (a new object has been created), or if the owner changed. But, as you said, there's a tiny issue with this code, it won't check all objects. What I can say is Ryan assumed that all the modified objects belong to the same owner. It can be seen as a bug or not but it can be easily fixed by changing the return statement by continue

Cheers!

| Reply |

Hello @fGuix,

Nice catch, the case looks legit. The best thing to bypass this issue I think will be to change return; to continue;. We will discuss internally how to fix the course properly.

Cheers!

| Reply |
Jeremy avatar Jeremy 2 months ago

This is a really nice way to design APIs, I really love it! This look so much clearer than having tons of serialization groups everywhere, and having calculated field in dedicated classes is so nicer than having them in entities.

But whoa, if you're going to deal with several entities, you better have a nice AI companion to code with you or it'll be a real pain to write all you Api, EntityToApiMapper and *ApiToEntityMapper.
I'd probably gave up while creating these 3 files manually for each Entity, as it is so time consuming and off-putting.

| Reply |

Hey @Jeremy

Yea, I agree, it feels like a lot to do if you want to rely on DTOs. I hope ApiPlatform will add better support to DTOs in the future

Cheers!

| Reply |
Peter-Hoehne avatar Peter-Hoehne 2 months ago

Hi,
as response from POST and PATCH requests we get the serialized resource,
like when i do a GET request. i noticed that changes to the resource that not coming from my POST/PATCH requests are not updated and reflected in the response. an example is an automatically changed property like "updatedAt". instead there is the old value or nothing at all when the value was not set before.
In the follow up requests i see the changes.
Is there an elegant way to ensure that i get the actual state of the resource?
What i tried so far is:
a) in the Dto2Entity Mapper also setting the Dto properties. It works, but at this point i dont have all infos, for example updatedAt coming from an doctrine event.
b) in the processor where you set the id of the generated entity back to the dto. That works but there have to be some sort of callback/hook mechanism for a generic processor.
c) similar to b) not really tested, but is it a good idea to simply call the micromapper and overwrite the dto by mapping the entity back to it?

Thanks for your great tutorials.
Peter

| Reply |

Hey @Peter-Hoehne!

Yea, I think understand the issue and I've had this conversation in the comments on this tutorial a few times (I wish I could find those!).

The answer is (c). Others have suggested it, and I agree. It also removes the need to manually call $data->id = $entity->getId(); to set the entity id back onto the DTO. The micro mapper will take care of that.

So yea, go for C! There could be a small performance impact, but I bet it's small, and it will only happen on those write operations anway.

Cheers!

| Reply |
MMilev avatar MMilev 4 months ago edited

Hello, a great course :)

I just have one question. Maybe, I have missed it but we saw how to persist embedded objects with Groups but what about the same case with DTOs?

Is it possible to create nested objects via DTOs? For instance I am trying to persist a new User object with liked Profile object (1:1 in the particular case). I am using the microMapper but now I got an error "400 Nested documents for attribute \"profile\" are not allowed. Use IRIs instead.". If I use serialization Groups I do get to save both objects but it does not retrieve any fields, besides the LD+JSON ones @id, @type.

Everything in all the mappers seems OK.

Example Post Request body:

{
  "email": "test-email@example.com",
  "password": "123456",
  "profile": {
    "firstName": "FirstName",
    "middleName": "MiddleName",
    "lastName": "LastName"
  }
}
| Reply |
MMilev avatar MMilev MMilev 4 months ago edited

I have found the culprit. I use the suggested EntityClassDtoStateProcessor but it does not populate the ID field for all the embedded objects, thus generateIriFromResource throws an exception. In the code bellow the commented line actually makes it work for the discussed case.

EntityClassDtoStateProcessor#process
...

$this->persistProcessor->process($entity, $operation, $uriVariables, $context);
$data->id = $entity->getId();
// $data->profile->id = $entity->getProfile()->getId();

return $data;
| Reply |

Hey @MMilev!

Ah yea, that makes sense. Some people, which I think makes sense, have been using the micromapper after calling $this->persistProcessor->process() to map the $entity back into the DTO. Because the $entity (and in your case the Profile) now have ids, this maps the ids back to the DTO's.

Cheers!

2 | Reply |

Hello, thank you for your answer :) I will try that :)

Best regards :)

| Reply |

Delete comment?

Share this comment

astronaut with balloons in space

"Houston: no signs of life"
Start the conversation!