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!
36.
Input DTO Class
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
}
}
8 Comments
Hello guys, I'm reaching out because I'm in need of some help here. Im dealing with this project on which I have two resources, ResourceA lives in my DB and I have full access but then I have ResourceB that lives in another DB and I can only access it through another API the tricky part is that A and B are related so when I create A I must store an ID related to B.
So far I managed to handle B as is a part of the native API using a DataProvider and a DataPersister, but now when I try to POST A, A should have an IRI pointing to B and if I define a ManyToOne in A pointing to B I'm having the error that B "is not a valid entity or mapped super class"..
To sum up, I must have a relation between A and B beeing B a complete custom resource because it lives in another API (and DB).
Hope someone understand what I tried to explain, and can shed some light on wich is the best way to achieve this or if an impossible task.
Thanks in advance.
Hi Don!
Yea... that does some complex. I don't have any direct experience with non-entity classes and relating them. However, I "think" that, on an API Platform level, all API Platform cares about is that class A contains an instance of class B on a property: it does not need to be (and shouldn't be) using a ManyToOne attribute from Doctrine. So, for example:
And, of course,
Bis also an#[ApiResource]. With just this setup, I believe you should be able to do something like this:I "think" (but I am doing some guessing), that API Plaform will see that the
bfield should be an instance ofBand thatBis an#[ApiResource]and so it will then use the IRI (/api/b/5) to "find" theBobject with id5using yourDataProviderforB. It would then call->setB($b)on classA. I'm guessing you also have some sort ofprivate int $bIdproperty onAwhere you store theid. So, to handle setting that, you could do something like:Additionally, IF you want to get fancy, you could use a post-hydration (I think this is the hook you want) listener on Doctrine that could read the
bIdfield and use the API to find theBobject and set it onto theAclass every time you query forA. The downside is that you'll be hitting the API every time you query for anA- so it's up to you :).Let me know if that helps!
Ryan! Hi there!,
Thank you so much for your thorough reply!. Yes! :) for the POSTing part I was half way on doing your proposal, but very uncertain if I was on the right path, the best or the only one. (like most of the time on API Platform :P).
So, POST worked perfect and as expected.
Now for the GET part, post-hydration was the exact word that I was looking for that. I used a Doctrine Listener on PostLoad and worked!, of course I endup duplicating part of the code that lives in BDataProvider::getItem() so next step would be re organize (try) the code to not repeat myself.
I'd like to ask you couple of followup questions:
Thanks again, and keep up the great job you are doing with these tutorials, I'm very happy that you are addressing more advanced topics, that really helps on getting us to the next level.
Hi Don!
Yay! I'm really happy to hear about all your success - and thanks for following up to tell me!
You could also do it manually in your app code. For API Platform, that would mean doing it in the "data provider". For example, you could decorate the "Doctrine" data provider - https://symfonycasts.com/screencast/api-platform-extending/decorate-provider - you would call the Doctrine data provider so it can do its normal logic for fetching the
Aentity. Then you would manually grab theAobject, read thebIdoff of it, fetch over the API forB, then set it ontoA. That is, I think a perfectly acceptable alternative to the Doctrine Listener.I think I answered this above - but let me know :). Yes, the idea is that YOU need to have a custom data provider for
A. But of course, you don't want to do ALL of the work - so you decorate the Doctrine data provider so it can do most of the work... then you do the little tweak withBafter.It depends on who is using your API. If only YOU are using it... then who cares! :) But, it IS a bit weird that all of your other relations would use an IRI, and it would be different here.
However, you did give me an idea. In theory, if you KNOW that you will only return the IRI - and not the actual data - in the custom data provider for
Athat we're discussing, instead of fetchingBfrom the external API, you could create a fake, emptyBobject that ONLY has itsidset (which you already have). In theory, the serializer would use that to create the IRI... never caring that the object is otherwise empty.Cheers!
Hi Ryan!.. awesome!..
I have learned a lot in this couple of posts.. Thanks a lot for taking the time and give such a complete answers, they were very clarifying.
So, these are the results:
Begining with your last paragraph, don't konw why I did not think about that!, I'm doing exactly the same when a
Bresource is created so Api Platform will return the IRI for the newly created resource. (that works!)Using that idea, placed in the DoctrineListener and worked beatifully.
Then moved on creating ADataProvider and test the same thing and also worked, of course.
Lastly, in case
Bdetails needed to be shown, inject BDataProvider in ADataProvider::constructor() and instead of doing anew B(), simple call to BDataProvider::getItem() and worked extremely well.Now, let's go celebrate (as you would say).
Cheers!
Woohoo! This is a really cool situation - it's awesome to hear that it's now all working nicely! Yup, celebration time :D
Hi
I have a question. Let I have a Resource, Book Entity, a standard ApiResource, with a REST operation POST for adding new Entity.
Let I have a new partner that want to add an Entity using API, BUT ... this partner can send a JSON that is totally different from standard POST Api.
Which is the best option to handle this situation? I think that DTO could be useful , I can declare a PartnerXBookDTO, but should I create a new custom operation? If I create a custom operation, should I create the entire ControllerAction or I can use some kind of Listener?
Hey Gianluca!
Sorry for the slow reply - the team left this tougher question for me :). Hmm... I'm not really sure what the "best" way to handle this would be. So yes, I'd probably try this with a DTO. In theory, if you create a DTO that looks like the JSON, then the JSON will deserializer into the DTO, then you can transform it back into a Book. Should you create a custom operation? That depends on whether you need to keep the "original" POST action or if having this new POST action is the only you need. If it's the only you need, then skip the custom operation and make the original POST operation use your DTO. If you DO need a custom operation... I'm not sure whether you would need a custom controller or not... it might be enough to create the custom operation and configure it with the DTO (and no controller is needed).
I'm going some guessing here about the way to handle things, but let me know if this helps or if you found a nice solution :).
Cheers!
"Houston: no signs of life"
Start the conversation!