-
Notifications
You must be signed in to change notification settings - Fork 63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make ResourceBuilder and RichTextParser extendable #298
Conversation
Direct property access is now replaced by public methods. This is a backwards compatible change, since these methods were previously unused. This commit allows users to implement their own resource builders and rich text parser on top of existing functionality.
This issue should resolve #7 |
For what it's worth (which isn't much, but still) I still believe that making the delivery client extendable is still the wrong approach. What you're trying to achieve is to have the resource builder and rich text parser do something custom, but you're trying to get there by extending the client (and therefore, needing to extend the resource builder and rich text parser). The correct approach in my opinion would be to update the resource builder to somehow be able to define custom mappers (most likely by updating |
I see your point, and feel the same way. The proposed solution in the PR feels like a "quick fix" that can be integrated relatively quick without negatively impacting existing implementations. I felt this was a better approach, since this issue has been open for a pretty long while and I did not want to create a massive overhaul in the library. Still though, a future PR could implement on your proposed solution. |
Hold on, I think the base resource builder already supports this. Let me check. |
Yeah, there's already a working solution for this (which I had implemented but forgot about it). It is documented for the CMA SDK, but it's the same code underneath so it works for this too. $builder = $client->getResourceBuilder();
$builder->setDataMapperMatcher('Entry', function (array $data) {
switch ($data['sys']['contentType']['sys']['id']) {
case 'blogPost':
return \My\Custom\Mapper\BlogPostMapper::class;
case 'author':
return \My\Custom\Mapper\AuthorMapper::class;
}
}); Basically the method Basically, this PR is unnecessary as there already is a supported way of doing this operation 🙂 the problem is the lack of documentation, not the lack of the feature. I remember the 2016 issue and I remember that during the CDA SDK refactoring I did in early 2018 (which I had mentioned in the original issue) I made sure to fix some of the outstanding problems, including the lack of options for extending rhe resource builder. |
Hi @dborsatto , thank you for chiming in again! Your contribution is very much appreciated. I'll try to get your suggested method documented, which is necessary either way. @arondeparon does this work for you? |
Thanks. I have looked into this and it seems that although this should make it possible to create custom mappers. For my personal use case, this still seems unusable though. Instead of creating a mapper for each content type, I simply define value models in my application that extend a certain base class. I then want a generic mapper to map the Contentful fields to the public properties of my value object. I have already gotten this to work using a custom query builder that pipes everything through my formatter, but I don't think I can get this to work with with this mapper pattern. I could get this to work by defining some kind of map that tells the mapper which content types refer to a given class name, but I simply want it to work out of the box. I think that this could work by extending the Builder object itself, but at this point I am doubting whether that is worth it, because I have already gotten it to work in another way. |
A mapper is simply an object with a method that turns an unserialized response from the API into *anything* you want, you are 100% free to implement any logic you want, in both the mapper (take a look at those that the SDK ships with for a general idea) and in the matcher callable that you feed to the resource builder. There's no need to filter by content type, that was just a common use case.
You can do pretty much anything you want with this, I fail to understand how this mechanism is any different from your needs and how extending the client/resource builder would solve anything that this doesn't...
|
I understand. It's simply a matter of how I like to structure my code. The solution can definitely be implemented my creating mappers for all my content types, but I do not want an extra object for each value model. I want a generic mapper that maps Contentful data to my value models by introspecting the value model. To clarify: By default, the data flows somewhat like this: Example: class NewsItemModel extends ContentfulModel
{
/** @var string */
public $title;
/** @var string */
public $content;
/** DateTimeImmutable */
public $date;
} In this case $items = NewsItemModel::where('fields.pinned', true)->orderBy('fields.date', true)->get()` Which then results in a collection of model -> custom builder -> CF client -> CF builder -> CF (base) mapper -> custom builder -> generic mapper -> model As you can see, this results in an extra mapping step, but since this is abstracted away, I feel that this is not a very expensive tradeoff. The same end result is possible using the CF mappers, but like I mentioned before, these mappers are called from a different context, which makes is very hard to determine which value model a given entry should map to. |
Well yeah, but that implies that data from Contentful can take different forms depending on the context from which it is retrieved, which given that one of the benefits of using the CDA is that you get structured data, does not feel like a great solution to me. There should always be an entity object that offers a base representation of some data (regardless of where it is stored), and then you can create different value objects from this entity according to the rules of the specific subdomain you're working in. Creating these value objects straight from the source data to me feels like directly tying these objects to the raw data instead of what such data is actually supposed to represent in your application's domain. Anyway, going back to the main topic, it seems to me that you're trying to do something that kind of goes beyond what the intended use case for the SDK is/was. From the start it was always designed to fit more in a "data mapper-like" architecture (Rouven and then I both were/are more Symfony and Doctrine people), whereas your approach seems to me "active record-like" (given you use Eloquent as example of what you want to achieve), so I believe that's where the main issue lies with your use of this library 🤔 |
Goal
Allows users to implement their own resource builders and rich text parsers on top of existing functionality.
Blockers
The delivery client currently has a public
getResourceBuilder()
method that sits unused. By default, it returns the private$builder
property.All the internal code does not use
getResourceBuilder()
, but the$builder
property directly, which makes extending only possible when extending everything.Implemented solution
Stop using
$this->builder
directly, and usegetResourceBuilder()
.Direct property access is now replaced by public methods. This is a backwards compatible change, since these methods were previously unused.