Chapters
36 Chapters
|
3:44:20
|
Login to bookmark this video
-
Course Code
Subscribe to download the code!
Subscribe to download the code!
-
This Video
Subscribe to download the video!
Subscribe to download the video!
-
Subtitles
Subscribe to download the subtitles!
Subscribe to download the subtitles!
-
Course Script
Subscribe to download the script!
Subscribe to download the script!
Scroll down to the script below, click on any sentence (including terminal blocks) to jump to that spot in the video!
Subscribe to jump to this part in the video!
Keep on Learning!
If you liked what you've learned so far, dive in! Subscribe to get access to this tutorial plus video, code and script downloads.
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
}
}
14 Comments
I tried to write some code in which I have one standard provider, one standard processor and one standard mapper which all rely on ApiPlatform functionality. It works and I'm happy with it. There is just one annoying thing. To each DTO I add the ApiResource annotation to help ApiPlatform generate correct IRI's for each resource, eg:
#[ApiResource(shortName: 'users',operations: [],stateOptions: new Options(entityClass: UserEntity::class),)]The goal is twofold; it's used to help ApiPlatform to generate IRI's and in the provider I use this stateOption to validate the correct entity.
However it comes with some issues because it does actually create a path (console debug:router) which is ofcourse necessary to generate IRI's:
_api_/users/{uuid}{._format}_get GET ANY ANY /api/users/{uuid}.{_format}And in some cases this route is above the actual GET path (probably because of the order of code):
_api_/users/{uuid}{._format}_get GET ANY ANY /api/users/{uuid}.{_format}_api_/users/{uuid}_get GET ANY ANY /api/users/{uuid}I have a solution, which is putting files which contains the actual operation on a higher folder level (parent folder
/Dto/User.phpinstead of/Dto/User/User.phpwhere this responseDTO is in/Dto/User/Response/UserResponseDto.php) in my code. I could also add priority to the operation but that's not an solution because there is no way to de-prioritize the path in my DTO while there is no GET operation. I tried priority -1 to 8 but because the other side does not have a priority it does not know how to relate these two.Any creative ideas on this?
Hey Arthur,
Ah, I think I see the problem. You want your DTOs to carry just enough
#[ApiResource]metadata so that ApiPlatform knows how to generate proper IRIs, but you don't actually want them to expose their own operations or generate their own routes, right?Yeah,
#[ApiResource]always implies tp expose that as a resource, which by default means routes get registered.I personally didn't try it but IIRC since ApiPlatform 3.3 you can set
uriTemplate: nullin#[ApiResource()]PHP attr. Then it should exist purely for metadata and no routes should be generated. Probably worth to try.Also, another soft option might be to use
extraPropertiesinstead ofstateOptions. Then in your provider, you can read it with$operation->getExtraProperties()['entityClass'] ?? null. That way you don’t need to rely on theOptionsstate option at all.As possible more advanced option - try to override the
ResourceMetadataCollectionFactoryif you want full control. You can decorate that and inject thestateOptionsfor your DTOs programmatically without having to sprinkle#[ApiResource]everywhere. That way, you can tell API Platform: "Here’s the DTO, link it to this entity,", but don’t create routes for it.That’s a bit more work, but it should keep your DTOs clean and avoid conflicts I think. But haven't tried it myself either.
I hope this helps!
Cheers!
Thanks @Victor . Some nice creative ideas!!
You're welcome! I hope that helps, not sure what else you can do in this case.
Cheers!
I am doing everything according to the course, but after returning $dtos in EntityToDtoStateProvider I get an error:
Unable to generate an IRI for the item of type \"App\\ApiResource\\UserApi\"Before returning $dtos I used die and dump, a valid array with
App\ApiResource\UserApiobjects was returned. Could I ask for an explanation of why this is happening?Oh, I noticed that when using
/api/users.json, everything generates correctly, even more so I don't know what the problem is. :(Hey @Albert-K!
This error:
This almost always means that you have a
UserApiobject with anullid property... but not always :p. You mentioned going to/api/users.jsonworks. So is the problem only when you go to/api/users/5.jsonto fetch a single User? If so, in this case, are you returning an array ofUserApiobjects or only a singleUserApiobject (or null)?Cheers!
Oh, I didn't add this in the first message, the error:
occurs with .jsonld, I tried the following endpoints in .jsonld and .json format:
/api/users.jsonld/api/users/5.jsonld/api/users.json/api/users/5.jsonThose in
.jsonldformat generate an errorUnable to generate an IRI for the item of type \"App\\ApiResource\\UserApi\", while those in.jsonformat work fine.As far as I can see the error is caused by the
IriConverterin thegenerateSymfonyRoutemethod in this code:I set
in the DTO class over
$idand/api/users.jsonldworks fine, just without IRI for an individual user.I had the same issue, identifier fixed it, thanks!
After that I got the same error when querying items, updating the api package fixed it
Hey @Albert-K!
Very strange issue - but good debugging. Everything about this feels simply like the id is missing on the DTO - I’m not sure why that would be the case. But I’m happy you have a workaround.
Cheers!
"Houston: no signs of life"
Start the conversation!