Skip to content
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

Question: how to create dynamic or reusable Adapters and Schemas? #203

Closed
tekord opened this issue Jun 27, 2018 · 13 comments
Closed

Question: how to create dynamic or reusable Adapters and Schemas? #203

tekord opened this issue Jun 27, 2018 · 13 comments

Comments

@tekord
Copy link
Contributor

tekord commented Jun 27, 2018

I have 10 models with completelly the same structure (Dictionary Entries like SkillEntry, ExperienceEntry, etc, they use different tables, but models are extended from single class) and 1 model which referencing to some of these dictionary models. I don't want to create 10 folders with Adapter and Schema, too many duplicate code. Is there any way to make schema and adapter dynamically? Or use the same Adapter and Schema but change their resource types dynamically? For instance:

User model has SkillEntry and ExperienceEntry relations (and another 8 entry relations). SkillEntry's resource type is 'dictionaries_skill-entries', ExperienceEntry's resource type is 'dictionaries_experience-entries'. These relations are available as normal relation of User resource by URL like this: /api/v1/users/42/relationships/skill-entry.

UPD. DictionaryEntry models has NO any relations. But if they were I also interesting in how to deal with it.

Thanks.

@lindyhopchris
Copy link
Member

Hi,

All the classes are created via the Laravel container, so it is possible to set this up using bindings. E.g. when looking for an adapter this package will resolve App\JsonApi\Posts\Adapter from your service container. So you could use the same adapter class for multiple different resource types by registering bindings, for example in a service provider:

public function register()
{
  $this->app->bind('App\JsonApi\Posts\Adapter', MyAdapter::class);
  $this->app->bind('App\JsonApi\Comments\Adapter', MyAdapter::class);
}

Or you could use functions to customise your generic class:

public function register()
{
  $this->app->bind('App\JsonApi\Posts\Adapter', function () {
    return new MyAdapter('posts');
  });
}

The same works for schemas, validators etc. Note that the binding name will depend on the namespace you have configured the JSON API with - i.e. in the examples I'm assuming you're using the default App\JsonApi namespace and your by-resource setting is true.

Let me know if you have any other questions!

@lindyhopchris
Copy link
Member

Actually not sure this will work at the mo, although this is how I intended it to work, so I might need to make a code change.

Note for myself: currently only checking if the class exists, need to also check if there is a container binding here:
https://github.com/cloudcreativity/laravel-json-api/blob/master/src/Container.php#L445

@tekord
Copy link
Contributor Author

tekord commented Jun 28, 2018

Also, Adapter has constructor with 'new Model' statement. Maybe make some AdapterModelResolver or something like that.

@lindyhopchris
Copy link
Member

If I implement the above container bindings support, you can inject the model through the binding:

public function register()
{
  $this->app->bind('App\JsonApi\Posts\Adapter', function () {
    return new MyAdapter(new MyModel());
  });
}

@lindyhopchris
Copy link
Member

lindyhopchris commented Jun 30, 2018

@tekord could you give this a go to see if it works for your scenario before I tag? You can install by using:

$ composer require cloudcreativity/laravel-json-api:dev-master

@tekord
Copy link
Contributor Author

tekord commented Jul 1, 2018

It works! Thank you :)

@tekord
Copy link
Contributor Author

tekord commented Jul 1, 2018

Not critical, just for your information. If not set 'resourceType' in Schema class or set it to null then exception raised:

local.ERROR: Schema is not registered for a resource at path ''. {"exception":"[object] (InvalidArgumentException(code: 0): Schema is not registered for a resource at path ''. at ...\\vendor\
eomerx\\json-api\\src\\Encoder\\Parser\\Parser.php:282, InvalidArgumentException(code: 0): Resource type is not set for Schema 'App\\JsonApi\\DictionaryEntries\\Schema'. at G:\\xampp\\htdocs\\cwa\\acp-backend\\vendor\
eomerx\\json-api\\src\\Schema\\SchemaProvider.php:82)

In my case the resource type is setup dynamically in binding. As workaround I set some resource type, but it does not make sense in case of dynamic schema.

@lindyhopchris
Copy link
Member

@tekord you'll still need to add the PHP class to the list resources in your API config to prevent that error that you've mentioned. The encoder (which is provided by the neomerx/json-api package) expects each PHP class it encounters to have a schema registered for it. This package does that registration for you based on the resources config.

@lindyhopchris
Copy link
Member

In terms of setting the resourceType in the Schema class, if your schema is reusable again you'll need to set that in the constructor. So your constructor should have a $resourceType argument and then this can be set via the container binding.

@lindyhopchris
Copy link
Member

Released in v1.0.0-alpha.4

@mathieufrh
Copy link

In terms of setting the resourceType in the Schema class, if your schema is reusable again you'll need to set that in the constructor. So your constructor should have a $resourceType argument and then this can be set via the container binding.

But If I bind a schema to a generic Schema with

$this->app->bind('App\JsonApi\Posts\Schema', function () {
    return new GenericSchema(new \App\Post(), 'posts');
});

And I have the generic Schema like :

class Schema extends SchemaProvider
{
    /**
     * @var string
     */
    protected $resourceType;

    /**
     * Adapter constructor.
     *
     * @param StandardStrategy $paging
     */
    public function __construct(SchemaFactoryInterface $factory, string $resourceType)
    {
        parent::__construct($factory);
        $this->resourceType = $resourceType;
    }

    // ...
}

I get Argument 1 passed to App\JsonApi\Generic\Schema::__construct() must implement interface Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface, instance of App\Post given

I know I should not use new \App\Post() in my binding but what should I use then ?

@lindyhopchris
Copy link
Member

Your schema binding should be:

$this->app->bind('App\JsonApi\Posts\Schema', function ($app) {
    return new GenericSchema($app->make(Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface::class), 'posts');
});

@mathieufrh
Copy link

Your schema binding should be:

$this->app->bind('App\JsonApi\Posts\Schema', function ($app) {
    return new GenericSchema($app->make(Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface::class), 'posts');
});

Yes it works. Thank you very much !!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants