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

Support resource types that can behave as a proxy (e.x. Doctrine Proxies etc.) #40

Closed
koemeet opened this issue Jun 29, 2015 · 7 comments
Assignees
Milestone

Comments

@koemeet
Copy link

koemeet commented Jun 29, 2015

Hi,

There are some use cases where the resource type that is defined with Encoder::instance() will not match the one that another schema is returning, but instead is a subclass. One of them is when you return a proxy object in the getRelationships method of a schema.

I will give a code example. Let's say we have a PostSchema that has the following relationships:

public function getRelationships($post)
{
    return [
        'author' => [
            self::DATA => $post->getAuthor() // will not return an Author object, but an AuthorProxy which extends Author
        ]
    ];
}

Now when you want to encode a Post, it will give you an error, saying that there is no provider for AuthorProxy. You see the problem? AuthorProxy IS an Author, but the encoder cannot handle this.

This is caused by Container::getSchema($resource):

public function getSchema($resource)
{
    $resourceType = get_class($resource);
    // ...
}

I can propose two solutions. One is that we check with instanceof if it can provide a schema for $resource. The second solution is that it supports Doctrine natively, we can do this by extending the Container with:

public function getSchema($resource)
{
    $resourceType = get_class($resource);

    // support for Doctrine Proxies
    if (class_exists('Doctrine\Common\Util\ClassUtils')) {
        $resourceType = ClassUtils::getRealClass($resourceType);
    }

    // ...
}

Also, it could have it's own ClassUtils class that will get the correct class name if there is __CG__ (common marker name for proxy classes) in the name. Then it will support Doctrine too and probably more libraries that implement the Proxy pattern.

@neomerx
Copy link
Owner

neomerx commented Jun 29, 2015

@steffenbrem

$encoder = Encoder::instance([
    Author::class       => AuthorSchema::class,
    AuthorProxy::class  => AuthorSchema::class,
    Post::class         => PostSchema::class,
    PostProxy::class    => PostSchema::class,
]);

Will it work for you?

@neomerx neomerx added this to the 1.0 milestone Jun 29, 2015
@neomerx neomerx self-assigned this Jun 29, 2015
@neomerx
Copy link
Owner

neomerx commented Jun 29, 2015

This package is frameworks agnostic so it can't use Doctrine specific implementation details. However it can be refactored for easier extension if it worth doing.
Why ClassUtils::getRealClass is better or XxxProxy::class => XxxSchema::class is fine?

@koemeet
Copy link
Author

koemeet commented Jun 29, 2015

@neomerx And how about using instanceof? Then you would iterate over the provided mappings and check if one is an instance or direct subclass of $resource and then use that schema. How do you feel about that approach?

@neomerx
Copy link
Owner

neomerx commented Jun 29, 2015

@steffenbrem generally speaking I do not like cycles because they slow down apps. I'll post some code in a minute.

neomerx added a commit that referenced this issue Jun 29, 2015
- Inherit `Container` and override `getResourceType` method
- Inherit `SchemaFactory` and override `createContainer` method
- Inherit `Encoder` and override `getFactories` method where `$schemaFactory` should return the new schema factory instance
@neomerx
Copy link
Owner

neomerx commented Jun 29, 2015

@steffenbrem plz try this branch

@koemeet
Copy link
Author

koemeet commented Jun 29, 2015

👍 I solved it by extending the Encoder, SchemaFactory and Container. My classes look like this:

class Encoder extends BaseEncoder
{
    /**
     * {@inheritdoc}
     */
    protected static function getFactories()
    {
        $factories = parent::getFactories();
        $factories['0'] = new SchemaFactory();
        return $factories;
    }
}
class SchemaFactory extends BaseSchemaFactory
{
    public function createContainer(array $providers = [])
    {
        return new Container($this, $providers); // Container is my own class that extends the one from this library
    }
}
use Doctrine\Common\Util\ClassUtils;

class Container extends BaseContainer
{
    /**
     * {@inheritdoc}
     */
    protected function getResourceType($resource)
    {
        return ClassUtils::getRealClass(get_class($resource));
    }
}

@neomerx
Copy link
Owner

neomerx commented Jul 10, 2015

@steffenbrem I've reduced number of factories and it has become even easier to extend the encoder.
Instead of extending multiple factories you should

  • extend \Neomerx\JsonApi\Factories\Factory
  • method Encoder::getFactories was replaced with Encoder::getFactory and should return new factory

Here is an example

Don't hesitate to ask questions if any 😉

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

2 participants