Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Custom Resource Item Provider

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.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Let's try to get a single item. I'll change the date, hit "Execute", and... 200 status code. Hold your horses... this is returning a collection: the exact same data as our collection endpoint!

Collection vs Item Operations

Ok, each operation can have its own provider. But when we put provider directly under #[ApiResource], this becomes the provider for every operation. That's peachy... given you don't forget that some operations fetch a collection of resources while other fetch a single item.

Inside our provider, the $operation helps us know the difference. dd() that...

... lines 1 - 9
class DailyQuestStateProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
dd($operation);
... line 15
}
... lines 17 - 32
}

Then, over here, copy the URL, paste it in a new tab and add .jsonld to the end. There we go! This is a Get operation. If we try to fetch the collection, it's GetCollection.

Back in the provider, if ($operation instanceof CollectionOperationInterface), return $this->createQuests().

... lines 1 - 4
use ApiPlatform\Metadata\CollectionOperationInterface;
... lines 6 - 10
class DailyQuestStateProvider implements ProviderInterface
{
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if ($operation instanceof CollectionOperationInterface) {
return $this->createQuests();
}
... lines 18 - 19
}
... lines 21 - 36
}

Below, we know this is an "item" operation.

URI Variables

So this does keep the collection operation working. Now, we need a way to extract the date string from the URL so we can find the one quest that matches. How can we get that? dd($uriVariables).

... lines 1 - 12
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
... lines 15 - 18
dd($uriVariables);
}
... lines 21 - 38

When we refresh... behold: there's a dayString inside! Notice that, in DailyQuest, we never configure what the URL should look like. You can do that, but by default, API Platform automatically figures out what the route and URL should look like. Run:

php bin/console debug:router

For the item endpoints, it's /api/quests/{dayString}: the dayString is a wildcard in the route. In the provider, $uriVariables will contain every variable part of the URI - so dayString in our case. That makes us dangerous.

Returning a Single Items

Down here, we need to return a single DailyQuest or null. Say $quests = $this->createQuests(), then return $quests[$uriVariables['dayString']] or null if it's not set.

... lines 1 - 12
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
... lines 15 - 18
$quests = $this->createQuests();
return $quests[$uriVariables['dayString']] ?? null;
}
... lines 23 - 40

Remember: this works because the array uses dayString for each key. In a real app, we would want to do this more efficiently: it doesn't make sense to load every quest... just to return one. But for our test app, this will work fine.

Ok, try that endpoint. Got it! One result. And if we try a random date that doesn't exist... like "2013"... we get a 404. API Platform sees that we returned null and it handled the 404 for us.

We are now the proud parents of a fully functional state provider! Though we'll talk about this more soon - including topics like pagination. But next: let's shift our focus to creating a state processor for our custom resource.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

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

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
    }
}
userVoice