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
}
}
10 Comments
Hi,
Using
$entities = $this->collectionProvider->provide(...)causes an N+1 query problem: Doctrine lazily loads associations per entity, leading to many queries and slow performance.So, Icheck if the repository implements EagerLoadingRepositoryInterface and, if so, fetch paginated entities with relations eager-loaded. Map them to DTOs and return via TraversablePaginator. Otherwise, fall back to the default provider.
Repository
Is this a good solution, or can it be optimized further?
Thnks
Hey Anas,
I think you're on the right track! I have never do this myself but it sounds logical to me, and if it helped you with that N+1 problem - great job!
But if your goal is generic performance improvement, I would suggest you to take a look into API Platform’s built-in
EagerLoadingExtensionand only disable lazy joins where really needed. So yes, your solution works and is valid, but the optimized API Platform way is usually to let itsEagerLoadingExtensionhandle this, and only override with a customQueryCollectionExtensionif you need fine-grained control.I hope that helps!
Cheers!
Hi,
Apologies, I'm relatively new to Symfony/API Platform, so my terminology maybe slightly off. But I have been following this tutorial and creating my own API, and I am having an issue whereby when using
DTOs, aManyToManyrelationship is being returned as the fully hydrated model, not theIRIsas I would of expected.Broadly, this is an example what I have put in place (I have removed the mapper from the provider etc to make this as stripped back as possible, so what is being done is minimal):
The test provider just wraps the Doctrine Provider and maps the returned entity to the
BookDTO.The
ownerwill come back as expected, but theauthorswill come back fully hydrated, and not be converted to IRIs. I'm not sure why it works fine for the owner but not authors:Note that if I tag the related Doctrine Entities with
APIResourceit all works perfectly. Also note that theOneToManyon the model seems to be working as expected as well.I did originally have a
UserDTObut have stripped it out to make the example simpler (and to try and get this working with the Doctrine entity first).Any help would be greatly appreciated, I'm sure I'm doing something basic wrong.
Hey @jorganson!
This is almost certainly too late to help, but here we go anyway!
I'm not sure about this, but generally speaking: if you have a relation AND it doesn't contain any groups that your serializing the paren with AND the related class is an
#[ApiResource]you should get an IRI.Sorry for such a slow reply!
Being in this chapter of the course, my already tired head tells me that if I want to add custom data that comes from business logic operations, I would have to do it here in the created Mapper, injecting the service that performs the logic I need and then assign it in populate() as it is done for example with
$dto->isMine = $this->security->getUser() && $this->security->getUser() === $entity->getOwner();<br />Am I right ? or better somewhere else ?
Hey @Rodrypaladin!
I'd say that you're right. Technically, the place for this stuff a would be the data provider. But we've purposely made our provider generic & reusable: pushing all of the specific details into the mapper. So this makes the mapper, in a sense, the data provider code that's specific to just one ApiResource and thus the place I'd add the logic.
tl;dr yes, I agree with you!
Cheers!
Hello, how can we ensure that in the following example we at least have the entire nested object?
I can't seem to get more than an array of URIs, even when increasing MAX_DEPTH to 2 or more.
I would like to get the top level information for each dragon, including the road collection.
Unfortunately, I figured out that the annotation /** @var */ does not seems to work.
You might try with this, it's more verbose, but it definitely solves all my issues with serialization :
(readableLink allows to serialize relation as an entity, writeableLink allows to create new entities through relations in a Post, you might not need this one here)
Hope this helps :)
@Jeremy it works if you add the variable
$dragonTreasuresname to the@varannotation. Then you don't need the#[ApiProperty]directive.Hey @Billy!
The
MAX_DEPTHwill make sure that the embedded PHP objects have their data. But the determination of whether those embedded PHP objects will become an IRI or embedded data in the JSON is done by the serialization groups. What's annoying is that, with DTO's, you don't need serialization groups: your DTO only holds the fields that will be in your API. But... if you have an embedded object that is also an#[ApiResource]and you want it to be returned as embedded data in JSON, then you need to use serialization groups to opt into this: https://symfonycasts.com/screencast/api-platform/embedded#embedding-vs-iri-via-normalization-groupsBtw, another option that you might choose is to embed an object in your DTO that is... just a random object, and not an
#[ApiResource]. In this case, the data is embedded always - https://symfonycasts.com/screencast/api-platform-extending/embedded-objectCheers!
"Houston: no signs of life"
Start the conversation!