Lucky you! You found an early release chapter - it will be fully polished and published shortly!
Rest assured, the gnomes are hard at work
completing this video!
Our UserAPI
is now a fully functional API resource class! We've got our
EntityToDtoStateProvider
, which calls the core state provider from Doctrine, and
that gives us all the good stuff, like querying, filtering, and pagination. Then,
down here, we leverage the MicroMapper system to convert the $entity
objects into
UserApi
objects.
And we do the same thing with the processor. We use MicroMapper to go from
UserApi
to our User
entity... then call the core Doctrine state processor to
let it do the saving or deleting. I love that!
At this point, our dream is to create a DragonTreasureApi
and repeat all of
this magic. And if we can make these processor and provider classes completely
generic... that's going to be super easy. So let's do it!
Start in the provider. If you search for "user", there's only one spot: where we
tell MicroMapper which class to convert our $entity
into. So... can we fetch this
dynamically? Up here, our provider receives the $operation
along with
$context
. Let's dump both of these.
And since this is in our provider... we can just go refresh the Collection endpoint
and... boom! This is a GetCollection
operation... check it out. The operation
object stores the ApiResource class that it's attached to!
So over here, it's simple: $resourceClass = $operation->getClass()
.
Now that we've got that, down here, make it an argument - string $resourceClass
-
and pass that instead. Finally, we need to add $resourceClass
as the argument
when we call mapEntityToDto()
there... and right there. Remove the use
statement
we don't need anymore and... just like that, it still work!
We're on a roll! Head to the processor and search for "user". Ah, we have the
same problem except, this time, we need the User
entity class.
Ok! Up on top, dd($operation)
. And for this, we'll need to run one of our tests:
symfony php bin/phpunit --filter=testPostToCreateUser
And... got it! We see the Post
operation.. and the class is, of course,
UserApi
. But, in this case, what we really need is the User
class. Remember:
in UserApi
, we used stateOptions
to say that UserApi
is tied to the User
entity. And now, we can read this info from the operation. If we scroll down a
bit... there it is: the stateOptions
property with the Options
object,
and entityClass
inside.
Cool! Back in the processor, towards the top... remove the dd()
and start
with $stateOptions = $operation->getStateOptions()
. Then, to help my editor (and
also in case of any misconfiguration), assert($stateOptions instanceof Options)
(the one from Doctrine ORM).
You can technically use different Options
classes for your $stateOptions
...
like if you're getting data from ElasticSearch, but we we know we're using this
one from Doctrine. Below that, say $entityClass = $stateOptions->getEntityClass()
.
And... we don't need this assert()
down here, thenpass $entityClass
to
mapDtoToEntity()
. Finally, use that with string $entityClass
... and also pass
it here.
When we search for "user" now... we can get rid of the two use
statements...
and... we're clean! It's generic! Try the test!
symfony php bin/phpunit --filter=testPostToCreateUser
And... that's it! We're ready! We have a reusable provider and processor! Next,
let's create a DragonTreasureApi
class, repeat this magic, and see how quickly
things fall into place.
"Houston: no signs of life"
Start the conversation!
// 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
}
}