Chapters
48 Chapters
|
5:13:28
|
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!
12.
Custom Item Data Provider
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.
This tutorial also works great with API Platform 2.6.
What PHP libraries does this tutorial use?
// composer.json
{
"require": {
"php": ">=8.2",
"ext-ctype": "*",
"ext-iconv": "*",
"api-platform/core": "^2.1", // v2.5.10
"composer/package-versions-deprecated": "^1.11", // 1.11.99
"doctrine/annotations": "^1.0", // 1.12.1
"doctrine/doctrine-bundle": "^2.0", // 2.1.2
"doctrine/doctrine-migrations-bundle": "^3.0", // 3.0.2
"doctrine/orm": "^2.4.5", // 2.8.2
"nelmio/cors-bundle": "^2.1", // 2.1.0
"nesbot/carbon": "^2.17", // 2.39.1
"phpdocumentor/reflection-docblock": "^3.0 || ^4.0 || ^5.0", // 5.2.2
"ramsey/uuid-doctrine": "^1.6", // 1.6.0
"symfony/asset": "5.1.*", // v5.1.5
"symfony/console": "5.1.*", // v5.1.5
"symfony/debug-bundle": "5.1.*", // v5.1.5
"symfony/dotenv": "5.1.*", // v5.1.5
"symfony/expression-language": "5.1.*", // v5.1.5
"symfony/flex": "^1.1", // v1.21.6
"symfony/framework-bundle": "5.1.*", // v5.1.5
"symfony/http-client": "5.1.*", // v5.1.5
"symfony/monolog-bundle": "^3.4", // v3.5.0
"symfony/security-bundle": "5.1.*", // v5.1.5
"symfony/twig-bundle": "5.1.*", // v5.1.5
"symfony/validator": "5.1.*", // v5.1.5
"symfony/webpack-encore-bundle": "^1.6", // v1.8.0
"symfony/yaml": "5.1.*" // v5.1.5
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3", // 3.3.2
"symfony/browser-kit": "5.1.*", // v5.1.5
"symfony/css-selector": "5.1.*", // v5.1.5
"symfony/maker-bundle": "^1.11", // v1.23.0
"symfony/phpunit-bridge": "5.1.*", // v5.1.5
"symfony/stopwatch": "5.1.*", // v5.1.5
"symfony/twig-bundle": "5.1.*", // v5.1.5
"symfony/web-profiler-bundle": "5.1.*", // v5.1.5
"zenstruck/foundry": "^1.1" // v1.8.0
}
}
9 Comments
Hey there, you used bind instead of arguments when injecting
api_platform.doctrine.orm.default.item_data_provider service
, it was for a reason, is it a good practice ?
Hey Ahmedbhs,
Yeah, this is a good practice actually, we used this approach in our code as well. The exact variable name in the constructor args will requires Symfony to inject the specific service, that's how Symfony DI works behind the scene - it looks for the specific var name from bind :)
Another alternative solution would be to declare the service manually in services.yaml and specify passing the "@api_platform.doctrine.orm.default.item_data_provider" service manually.
Both should work, but usually devs using bind feature.
Cheers!
Hello, I've got error:
"@type": "hydra:Error",
"hydra:description": "$id must be array when "has_identifier_converter" key is set to true in the $context"
at: $item = $this->itemDataProvider->getItem($resourceClass, $id, $operationName, $context);
should it be: $item = $this->itemDataProvider->getItem($resourceClass, ['id' => $id], $operationName, $context);?
Where does has_identifier_converter comes from?
Hey @mwozniak!
Sorry for my slow reply - sometimes the deeper questions get left to me (and this is a good one!) and I was deep in tutorial land :). Now, let's see!
So this is not an area that I'm very familiar with. But here's what happens internally. Let's assume you hav this code:
When you call this, you almost immediately hit this code: https://github.com/api-platform/core/blob/d62e86e9cfa2a7a4eba3029053ff65f43a10e1ce/src/Bridge/Doctrine/Orm/ItemDataProvider.php#L75-L80
In most situations, where the
$idyou pass in is a string or int, thenormalizeIdentifiers()method is called. You can find that here: https://github.com/api-platform/core/blob/d62e86e9cfa2a7a4eba3029053ff65f43a10e1ce/src/Bridge/Doctrine/Common/Util/IdentifierManagerTrait.php#L43That method takes your string id and converts it into an array - for example
['id' => 4].Then, back in
getItem(), if$idis not an array at this point, you hit the exception you're seeing: https://github.com/api-platform/core/blob/d62e86e9cfa2a7a4eba3029053ff65f43a10e1ce/src/Bridge/Doctrine/Orm/ItemDataProvider.php#L78-L80The question is... why is this happening? But first, let me answer this:
If you're using an id in your system that is... somehow custom (e.g. maybe you use an id that is the combination of a "slug" field, a dash, then the id
$slug-$id), then you need to register a custom "identifier converter" that is capable of understanding this (i.e. splitting it into 2 pieces). This is not a context key that you would normally set yourself manually. It is a custom class you create, which API platform detects early in the request. It then sets this flag for you, so that it can handle your id conversion correctly.So. my guess is that this is NOT what you're trying to do. In your case, something is going wrong with the "id" normalization, and the error you're getting isn't the best one. A better error for you would be:
I know - still not that helpful - because we don't know what the problem here. So here is what I would do:
A) Add some debugging code right around here https://github.com/api-platform/core/blob/d62e86e9cfa2a7a4eba3029053ff65f43a10e1ce/src/Bridge/Doctrine/Orm/ItemDataProvider.php#L75-L77 - to make sure that you ARE getting inside that if statement.
B) Assuming you ARE getting into that if statement, add some debugging code to the
normalizeIdentifiers()method to see why this isn't returning an array.My guess is that you are in situation (A). That means that either:
1) You are passing an.
$idthat is not an int or string2) Somehow, that
has_identifier_converterIS being set to true by something.Let me know what you find out!
Cheers!
Hello There! If I write an custom DataProvider (maybe for User-Enities like in this example), than I will loose all the features like the QueryExtensions and OrmFilters. So this is the reason, why in this example the original DataProviders (Collection- and ItemProvider) was injected. But in this example these decorated DataProviders returns the query-result, but I see no way here to make joins, which will be often the case!. If I want to make a join, should I write an QueryExtension, which is used in the decorated DataProvider? This is very weird :-D. What is the recommendation here? :) Thanks!
Hi Szabolcs!
Sorry for my VERY slow reply - your question was left for me, and I've been lost in tutorial-creation land for a week or so :).
Ok, excellent question!
> If I write an custom DataProvider (maybe for User-Enities like in this example), than I will loose all the features like the QueryExtensions and OrmFilters. So this is the reason, why in this example the original DataProviders (Collection- and ItemProvider) was injected
Yes! Exactly! If we did NOT inject the original data providers (and instead, we simply queried for the object(s) ourself), we would be "avoiding" the query extension system, which also gives us things like pagination. So you are understanding this perfectly
> But in this example these decorated DataProviders returns the query-result, but I see no way here to make joins, which will be often the case!
Correct! The query has already been executed - so it's too late to change the query.
> If I want to make a join, should I write an QueryExtension, which is used in the decorated DataProvider
Yup! QueryExtension is your way to *modify* a query, which, in this example, will then be used by our decorated DataProvider. i know, it's complicated :). The QueryExtension and DataProvider have *slightly* different "superpowers". The QueryExtension can modify the query before it's executed, but it can't "modify" the entity objects that are returned, because the query hasn't actually be executed at that point. The DataProvider can't modify the query, but it can modify each entity object. That's why each serves a slightly different purpose.
Cheers!
Thank you for your answer! I have wrote an AbstractDataProvider which
allows access to the current query-builder. I believe, to be able to
make joins really belongs in a data-provider. QueryExtensions for me are
a place, where I put more "common things" like a "NotDeletedExtension"
and so on. Can I talk with Kevin Dunglas please? I just kidding :-p! I
really love your tutorial by the way, I'm watching it even when I'm on
vacation :-)! Best wishes!
Hello, shouldn't the injected collectionDataProvider be ContextAware also ?
Hey Ahmed,
if you're not going to work with the
$contextthen it's not needed to implement that interface but it doesn't cause any harm if you doCheers!
"Houston: no signs of life"
Start the conversation!