Git Product home page Git Product logo

rest-client-sdk's Introduction

Rest Client Sdk StyleCI Scrutinizer

PHP Rest Client SDK for JSON API.

This client tries to avoid the complexity of implementing a custom SDK for every API you have. You just have to implements your model and a little configuration and it will hide the complexity for you.

installation

composer require mapado/rest-client-sdk

Usage

Imagine you have those API endpoints:

  • /v2/carts
  • /v2/carts/{id}
  • /v2/cart_items
  • /v2/cart_items/{id}

You will need to have two entities, let's say:

  • Foo\Bar\Model\Cart
  • Foo\Bar\Model\CartItem

You will need to declare one Mapping containing your two ClassMetadata

Entity declarations

Configure an entity

Imagine the following entities:

namespace Acme\Foo\Bar;

use Mapado\RestClientSdk\Mapping\Annotations as Rest;

/**
 * @Rest\Entity(key="carts")
 */
class Cart
{
  /**
   * @Rest\Id
   * @Rest\Attribute(name="@id", type="string")
   */
  private $iri;

  /**
   * @Rest\Attribute(name="status", type="string")
   */
  private $status;

  /**
   * @Rest\Attribute(name="created_at", type="datetime")
   */
  private $createdAt;

  /**
   * @Rest\OneToMany(name="cart_items", targetEntity="CartItem")
   */
  private $cartItemList;

  // getters & setters ...
}

/**
 * @Rest\Entity(key="cart_items")
 */
class CartItem
{
  /**
   * @Rest\Id
   * @Rest\Attribute(name="@id", type="string")
   */
  private $iri;

  /**
   * @Rest\Attribute(name="number", type="integer")
   */
  private $number;

  /**
   * @Rest\ManyToOne(name="cart", targetEntity="Cart")
   */
  private $cart;
}

Explanations

Entity definitions:

  • key must be the key of your API endpoint

Attributes definition:

  • name the name of the key in the API return format
  • type type of the attribute

Relations definition:

  • name the name of the key in the API return format
  • targetEntity class name of the target entity

UnitOfWork

EntityRepository has a UnitofWork used for PUT and POST requests. It sends only changed data not the full model by comparing with the entity stored on GET request.

Configuration

Using Symfony ?

There is a bundle to easily integrate this component: mapado/rest-client-sdk-bundle.

Once configured, you can get a client like this:

$sdkClient = $this->get('mapado.rest_client_sdk.foo');
// or $sdkClient = $this->get('mapado.rest_client_sdk')->getSdkClient('foo');

Not using Symfony

You need to configure client this way:

use Mapado\RestClientSdk\Mapping;
use Mapado\RestClientSdk\RestClient;
use Mapado\RestClientSdk\SdkClient;
use Mapado\RestClientSdk\Mapping\Driver\AnnotationDriver;

$restClient = new RestClient(
  new GuzzleHttp\Client(),
  'http://path-to-your-api.root'
);

// if you need to pass some headers to the client, you can do something like this:
// $restClient = new RestClient(
//   new GuzzleHttp\Client(['headers' => [ 'Authorization' => 'Bearer foobar' ]]),
//   'http://path-to-your-api.root'
// );
// See guzzle documentation for more informations

$annotationDriver = new AnnotationDriver($cachePath, ($debug = true));

$mapping = new Mapping('/v2'); // /v2 is the prefix of your routes
$mapping->setMapping($annotationDriver->loadDirectory($pathToEntityDirectory));

$sdkClient = new SdkClient($restClient, $mapping);

Optionnal: create a SdkClientRegistry

use Mapado\RestClientSdk\SdkClientRegistry;

$registry = new Registry(['my-api' => $sdkClient]);

It can be easier to manager multiple clients. You can then call:

$sdkClient = $registry->getSdkClient('my-api');

// or

$sdkClient = $registry->getSdkClientForClass('\App\Entity\SomeEntity');

Accessing data

Fetching an entity / a list of entities

$repository = $sdkClient->getRepository('Acme\Foo\Bar\Cart');

// you can also access the repository by model key:
// $repository = $sdkClient->getRepository('cart');

// Find entity based on ID as defined in the entity by @Rest\Id
$cart = $repository->find(1);

// If you need to pass extra query parameters:
$cart = $repository->find(1, ['foo' => 'bar']); // will call /v2/carts/1?foo=bar

// Find all entities in the database
$cart = $repository->findAll();

// Find one entity based on the fielddefined in the function name (in this case <Name>)
$cart = $repository->findOneByName('username'); // will call /v2/carts?name=username

// Find one entity based on the criteria defined in the array
$cart = $repository->findOneBy(array(
  'name' => 'username',
  'date' => '1-1-2016',
)); // will call /v2/carts?name=username&date=1-1-2016

// To find all matches for the two examples above replace findOneByName() with findByName() and findOneBy() with findBy()

Creating a new instance

$cart = new \Acme\Foo\Bar\Cart();
$cart->setStatus('awaiting_payment');
$cart->setCreatedAt(new \DateTime());
$repository->persist($cart);

The persist operation will send a POST request with the serialized object to the API endpoint and return the newly created object

Updating an instance

$cart = $repository->find(13);
$cart->setStatus('payed');
$repository->update($cart);

The update operation will send a PUT request with the serialized object to the API endpoint (using the object Id) and return the updated object.

Deleting an instance

$cart = $repository->find(13);
$repository->remove($cart);

The remove operation will send a DELETE request to the API endpoint using the object Id.

Custom serialization

By default, when serializing, the SDK return only the Id to existing relations (like one-to-many).

You may want to serialize OneToMany relations though (imagine you have a Cart and you want to update linked cartItems)

to do so, you can specify a $serializationContext in the persist and update method:

$repostory->update($cart, ['serializeRelations' => ['cartItems']]);

Extending the repository

If you need to extend the EntityRepository, you can just do something like that:

namespace Acme\Foo\Bar\Repository;

use Mapado\RestClientSdk\EntityRepository;

class CartRepository extends EntityRepository
{
  public function findOneByFoo($bar)
  {
    // generate the path to call
    $path = $data = $this->restClient->get($path); // ...
    return $this->sdk->getModelHydrator()->hydrate($data, $this->entityName); // hydrate for an entity, hydrateList for a list
  }
}

Update your entity @Rest annotation to let the entity be aware of it's repository:

namespace Acme\Foo\Bar;

use Mapado\RestClientSdk\Mapping\Annotations as Rest;

/**
 * @Rest\Entity(key="carts", repository="Acme\Foo\Bar\Repository\CartRepository")
 */
class Cart {

Handling exceptions

The SDK will throw an Exception if your API return something different than a 2xx or a 404 status code.

You can see all the exceptions in this folder.

If you need to access the body of the response from your API you can do something like this:

use Mapado\RestClientSdk\Exception\RestClientException;

try {
  // do something that will fail
} catch (\RestClientException $e) {
  $response = $e->getResponse(); // $response should be a Psr\Http\Message\ResponseInterface
  $body = $response->getBody();
  var_dump($body); // will dump your response body
}

PHPStan

rest-client-sdk does work well with PHPStan. See the related documentation.

Hacking

This library is tested on multiple Symfony and PHP version thanks to composer-test-scenarios.

But there is a conflict between ocramius/proxy-manager, composer 1&2, and php version.

In order to sort this issue, it may be usefull to update scenarios with this command for scenario with php 7.4:

docker run --rm --interactive --tty --volume $PWD:/app -w "/app" roadiz/php74-runner composer scenario:update

This should be resolved with ocramius/proxy-manager#2.9.0 but it requires composer 2 absolutely, and we (Mapado) are not ready for this.

The path to fix this is to bump a major version with "ocramius/proxy-manager": "^2.9.0" that requires composer^2

rest-client-sdk's People

Contributors

ambroisemaupate avatar badaz avatar bjulien avatar dallegoet avatar dependabot[bot] avatar didier2l avatar fe4nn0r avatar jdeniau avatar jmfeurprier avatar julienravia avatar leroy0211 avatar nickinthebox avatar scrutinizer-auto-fixer avatar steit avatar thomasdiluccio avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rest-client-sdk's Issues

Proxy initializer function bug

Hi dude, how are you?
i found this really weird bug.
When i call a method on a Proxy, (proxy because Oficina its a subresource), in the controller like this:
$agenteRepository->find(20)->getOficina()

object(ProxyManagerGeneratedProxy_PM_\App\Entity\Rest\Oficina\Generated183fa80ff3d071671b476b73d35b700e)[2419]
private 'initializer9b768' =>
object(Closure)[2371]
public 'static' =>
array (size=4)
'sdk' =>
object(Mapado\RestClientSdk\SdkClient)[532]
...
'classMetadata' =>
object(Mapado\RestClientSdk\Mapping\ClassMetadata)[562]
...
'id' => string '/api/oficinas/1' (length=15)
'proxyModelName' => string 'App\Entity\Rest\Oficina' (length=23)
public 'this' =>
object(Mapado\RestClientSdk\SdkClient)[532]
protected 'restClient' =>
object(Mapado\RestClientSdkBundle\RequestAwareRestClient)[440]
...
protected 'cacheItemPool' =>
object(Symfony\Component\Cache\Adapter\ArrayAdapter)[493]
...
protected 'cachePrefix' => string 'mapado_rest_client_' (length=19)
private 'mapping' =>
object(Mapado\RestClientSdk\Mapping)[537]
...
private 'serializer' =>
object(Mapado\RestClientSdk\Model\Serializer)[531]
...
private 'modelHydrator' =>
object(Mapado\RestClientSdk\Model\ModelHydrator)[536]
...
private 'repositoryList' =>
array (size=1)
...
private 'proxyManagerConfig' =>
object(ProxyManager\Configuration)[534]
...
private 'unitOfWork' =>
object(Mapado\RestClientSdk\UnitOfWork)[526]
...
public 'parameter' =>
array (size=5)
'$proxy' => string '' (length=10)
'$method' => string '' (length=10)
'$parameters' => string '' (length=10)
'&$initializer' => string '' (length=10)
'$properties' => string '' (length=10)
private 'initializationTracker65ef4' => boolean false
private 'iri' (App\Entity\Rest\Oficina) => string '/api/oficinas/1' (length=15)
private 'numero' (App\Entity\Rest\Oficina) => null
private 'descripcion' (App\Entity\Rest\Oficina) => null
private 'finalidad' (App\Entity\Rest\Oficina) => null
private 'ofiPrgCodigo' (App\Entity\Rest\Oficina) => null
private 'baja' (App\Entity\Rest\Oficina) => null
private 'ofiUejCodigo' (App\Entity\Rest\Oficina) => null
private 'ofiModCodigo' (App\Entity\Rest\Oficina) => null
private 'codigo' (App\Entity\Rest\Oficina) => null

All the attributes of the entity are in null.
BUT! if you pass this same object to a template like this:
{{ agente.oficina.numero }}
this works.......

SO.....I investigated this and i found this:
vendor/mapado/rest-client-sdk/src/SdkClient.php:245
if (!$isAllowedMethod) {

this condition on the initializer function always return false when you call it from a controller....but i don't know why...

DateTime : allow customer format

Today, the datetime serializer format date like this : ->format('c'), but sometimes, the API needs a custom format (SIBIL requires a Java Instant that doesn't accept anything else than Y-m-d\TH:i:s\Z).

Allow the Attribute to have an options parameters, and the datetime should use options.format value if provided

Do not `PUT` if Unit of work returns an empty diff

If you have this entity:

{
  "@id": "/foo/1",
  "title": "first foo"
}

and you send the same, the UnitOfWork will try to put this:

{}

Which is useless. If the unit of work returns an empty diff, we should not send the request

Issue with `UnitOfWork` and query parameters

The EntityRepository find method does registerClean in the unit of work but every other call to UnitOfWork is made with the entity id, which is probably better.

There might be a problem when having two different instances of an entity sharing the same id though, but the problem is already here as the use the id getter in the findBy / findAll methods

support of partial entities

Hi,

In an attempt to optimize a CMS using the mapado REST client SDK, I tried implementing HTTP-based normalization/denormalization groups on the API I'm using, i.e. :

GET /articles/1234

would return a complete entity like this:

{
  	"id": 1234,
  	"body": "<p>Article body</p>",
  	"template": "article",
  	"section": 2345,
  	"title": "Article title",
  	"sponsor": null,
  	"authors": [
  		456,
  		567
  	],
  	"tags": [
  		567,
  		678
  	],
  	"status": "PUBLISHED",
  	"createdAt": "2017-09-21T15:28:10-04:00",
  	"modifiedAt": "2017-10-23T15:40:04-04:00"
  }

While

GET /articles/1234?_groups[]=bare

would return only a partial entity like this:

{
  	"id": 1234,
  	"title": "Article title"
  }

Unfortunately, the REST client calls every setter on the entity, and for values which were not returned by the API, attempts to set NULL values with setters which don't allow them, causing fatal errors.

A possible "fix" / solution would be to skip setter calls for which the corresponding property was not provided in the API response.

Does it make sense?

Do not allow two `@Rest\Id` fields

We do accept that two fields contains @Rest\Id, we should throw an error on that as the id is the url we will call for update / delete and only one is possible

Persisting an entity with a ManyToOne relation does not convert related entity as IRI

We have the following entity classes:

<?php

namespace AppBundle\Entity;

use Mapado\RestClientSdk\Mapping\Annotations as Rest;

/**
 * @Rest\Entity(key="articles")
 */
class Article
{
    /**
     * @Rest\Id
     * @Rest\Attribute(name="id", type="string")
     */
    private $id;

    /**
     * @Rest\ManyToOne(name="section", targetEntity="Section")
     */
    private $section;

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getId()
    {
        return $this->id;
    }

    public function setSection(Section $section)
    {
        $this->section = $section;
    }

    public function getSection()
    {
        return $this->section;
    }
}

and

<?php

namespace AppBundle\Entity;

use Mapado\RestClientSdk\Mapping\Annotations as Rest;

/**
 * @Rest\Entity(key="sections")
 */
class Section
{
    /**
     * @Rest\Id
     * @Rest\Attribute(name="id", type="string")
     */
    private $id;

    /**
     * @var string
     *
     * @Rest\Attribute(name="title", type="string")
     */
    private $title;

    public function setId($id)
    {
        $this->id = $id;
    }

    public function getId()
    {
        return $this->id;
    }

    public function setTitle($title)
    {
        $this->title = $title;
    }

    public function getTitle()
    {
        return $this->title;
    }
}

When we persist an entity like this:

// $section is a valid and existing entity fetched from the API.

$article = new Article();
$article->setSection($section);

$articleRepository = $sdk->getRepository('articles');
$articleRepository->persist($article);

Then the HTTP call to the API is like this:

Path: POST http://domain.tld/articles

Parameters: { "json": { "section": "946c3a2a-e36a-11e6-b8b0-0242ac110002", "title": "test" }, "version": "1.0", "headers": { "Accept-Language": "en-US,en;q=0.5" } } 

Notice how the "section" key is "946c3a2a-e36a-11e6-b8b0-0242ac110002" instead of "/sections/946c3a2a-e36a-11e6-b8b0-0242ac110002"

I think the fix could be to modify the Serializer::recursiveSerialize method, around line 189, by replacing:

if ($data->getId()) {
    $data = $data->getId();
} elseif (...) {

with:

if ($data->getId()) {
    $hydrator = $this->sdk->getModelHydrator();
    $data = $hydrator->convertId($data->getId(), $relation->getTargetEntity());
} elseif (...) {

paginated hydra collections support

Not sure if this is an issue on the SDK side or the API side.

I'm querying an API endpoint which should return a collection of items (CGET), with a page (1) and a number of items per page (10), and I would expect to access the real total item count (5000), to build a pagination system. This information seem to be available through the HydraPaginatedCollection class and getTotalItems() method, but I always get a HydraCollection object, which only shows the number of returned items (10).

I'm making an API call with these parameters:

GET domain.tld/items?foo=bar&order[name]=ASC&itemsPerPage=10&page=1

And here is a sample of the response :

{
    "@context": "\/contexts\/Item",
    "@id": "\/items",
    "@type": "hydra:Collection",
    "hydra:member": [
        {
            "@id": "\/items\/bd87b2df-000b-11e7-a37a-0242ac150003",
            "@type": "Item",
            "id": "bd87b2df-000b-11e7-a37a-0242ac150003",
            "name": "foo"
        },
( more items here )
    ],
    "hydra:totalItems": 5000,
    "hydra:view": {
        "@id": "\/items?foo=bar&order%5Bname%5D=ASC&itemsPerPage=10&page=1",
        "@type": "hydra:PartialCollectionView",
        "hydra:first": "\/items?foo=bar&order%5Bname%5D=ASC&itemsPerPage=10&page=1",
        "hydra:last": "\/items?foo=bar&order%5Bname%5D=ASC&itemsPerPage=10&page=500",
        "hydra:next": "\/items?foo=bar&order%5Bname%5D=ASC&itemsPerPage=10&page=2"
    },
    "hydra:search": {
        ( ... irrelevant stuff here ... )
    }
}

The ModelHydrator::deserializeAll() method seems to rely on the value of the @type node to build a HydraPaginatedCollection instead of a HydraCollection :

        $hydratedList = new HydraCollection($data);

        if (!empty($data['@type'])) {
            if ($data['@type'] === 'hydra:PagedCollection') {
                $hydratedList = new HydraPaginatedCollection($data);
            }
        }

        return $hydratedList;

But in my response, the value of @type is 'hydra:Collection'.

Do you think the condition is too strict, or the API response too "loose" ? (API is driven by api-platform).

Allow an ID instead of an IRI

Today, the ID needs to be an IRI with the full path.

It works fine with JSON-LD API but does not on other cases.

See #46 for more details

@Attribute name annotation giving NULL when the class parameter name is different

First of all let me define my resouces.

I use this Hydra API: http://www.markus-lanthaler.com/hydra/event-api/events/41
I created a class Event like:

/**
 * Class Event
 *
 * @package Acme\DemoBundle\Entity
 * @Rest\Entity(key="events", client="Acme\DemoBundle\Command\BasicClient")
 */
class Event
{
    /**
     * @var int
     * @Rest\Id()
     */
    protected $id;

    /**
     * @var string
     * @Rest\Attribute(name="name", type="string")
     */
    protected $firstName;

    /**
     * @return mixed
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * @param mixed $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

    /**
     * @return string
     */
    public function getFirstName()
    {
        return $this->firstName;
    }

    /**
     * @param string $firstName
     */
    public function setFirstName($firstName)
    {
        $this->firstName = $firstName;
    }

}

Now when I try to get the Event Entity the Id is correct. But the $firstName attribute gives me back NULL.

When I change the $firstName attribute to $name, everything works like a charm.

It looks like the @Attribute name annotation doesn't seem to work as it should.

Possibility to configure API return keys

->findAll() on the AbstractClient results in an empty array.

I think this has to do with the hydra: prefix on the member key.
As far as I know the key for members on a collection is members instead of hydra:member

see: convertList method in the AbstractClient class.

improve performance of related entities retrieval

Hi,

we encounter performance issues with related entities.

Given we make this API call:

GET https://api/articles/12345678-4792-11e7-88db-f23c9124c2b0

Which returns :

{
    "@context": "\/contexts\/Article",
    "@id": "\/articles\/12345678-4792-11e7-88db-f23c9124c2b0",
    "@type": "Article",
    "id": "12345678-4792-11e7-88db-f23c9124c2b0",
    "title": "foo bar",
    "tags": [
        "\/tags\/331a2c47-164a-11e7-8a8a-f23c9124c2b0",
        "\/tags\/331a658c-164a-11e7-8a8a-f23c9124c2b0",
        "\/tags\/331a71cc-164a-11e7-8a8a-f23c9124c2b0",
        "\/tags\/9c828580-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9c82e2b1-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9c834c02-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9c850c8b-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9c86ca17-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9cc9f244-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9cd4d60e-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d186137-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d194643-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d1f1245-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d1fe224-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d33f4f4-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d37d44d-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d4353fe-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9d5d71fd-75f3-11e7-8820-f23c9124c2b0",
        "\/tags\/9da0e8d6-75f3-11e7-8820-f23c9124c2b0"
    ]
}

When we iterate over the entity tags collection ($article->getTags()), each iteration makes an API call to retrieve the tag entity:

GET https://api/tags/331a2c47-164a-11e7-8a8a-f23c9124c2b0
GET https://api/tags/331a658c-164a-11e7-8a8a-f23c9124c2b0
GET https://api/tags/331a71cc-164a-11e7-8a8a-f23c9124c2b0
GET https://api/tags/9c828580-164a-11e7-8a8a-f23c9124c2b0
...

Which kills the performances of our application.

Embedding the tags in the article API response is not an option in our case (for a lot of reasons).

So my question is: is there a way to tell the API client to retrieve all the tags at once instead of retrieving them one by one ?

Creating relation entities even when they aren't resources

When the serializer deserialize the entity with:
$agente = $this->get('mapado.rest_client_sdk.agentes')->getRepository('agentes')->find(20);
if that entity, in this case "Agentes", have a relation with other entity but that entity doesn't have an iri identifier the app throws an exception like this:
image
That error means that the entity is handled like an resourse but it isn't.
But maybe something like other annotation or maybe an if condition on the desiarialization can make that a non resourse entity relation be handled as a rest relation.

i made something like that with the controller scope:

$serializer = $this->get('mapado.rest_client_sdk.agentes')->getSerializer();

$agente = $this->get('mapado.rest_client_sdk.agentes')->getRepository('agentes')->find(20);

$agente->setTipoCuil($serializer->deserialize($agente->getTipoCuil(), 'App\Entity\TipoCuil'));

This only works if you put the rest\entity annotation on the relation entity:

/**
 * TipoCuil
 * @Rest\Entity(key="tipoCuil")
 */
class TipoCuil`

and in Agentes:

`/**
 * @var array
 *
 * @Rest\Attribute(name="tipoCuil", type="array")
 */
 private $tipoCuil;

this would make all relationships be objects and no array like now when the relation is an nono iri relation.

sorry for my bad english and for the latest issues. This is a really good and useful project and i want to help with everything ahah

DateTime : changing timezone

This follows #95, it could be usefull to change datetime timezone before formating.

This operation should not alter original value, so we need to convert DateTime instances to DateTimeImmutable (with DateTimeImmutable::fromMutable($d)) to do the job.

retrieving a list of "abstracted" entities

Hi there,

We have an entity hierarchy as such (simplified) :

  • abstract Document { id, title, author }
  • Article extends Document { content }
  • Recipe extends Document { ingredients, calories }

We have an API end-point for documents which allows to make queries based on shared properties (id, title, author) from the Document base class. Here is a sample response for GET /documents :

{
  "@context": "/contexts/Document",
  "@id": "/documents",
  "@type": "hydra:Collection",
  "hydra:member": [
    {
      "@id": "/articles/320d346d-0c8f-11e7-8f02-0242ac150002",
      "@type": "Article",
      "id": "320d346d-0c8f-11e7-8f02-0242ac150002",
      "title": "I am an article",
      "author": "Me",
      "content": "article content"
    },
    {
      "@id": "/recipes/320d45f8-0c8f-11e7-8f02-0242ac150002",
      "@type": "Recipe",
      "id": "320d45f8-0c8f-11e7-8f02-0242ac150002",
      "title": "I am a recipe",
      "author": "You",
      "ingredients": "food and stuff",
      "calories": 5000
    }
  ],
  "hydra:totalItems": 2,
  "hydra:search": {
    // ...
  }
}

And then, when we try to call this end-point with the SDK client, the model hydrator explodes:

Cannot instantiate abstract class AppBundle\Entity\Document

Stack Trace
    in vendor/mapado/rest-client-sdk/src/Model/Serializer.php at line 84  -
                $identifierAttribute = $classMetadata->getIdentifierAttribute();
                $identifierAttrKey = $identifierAttribute ? $identifierAttribute->getSerializedKey() : null;
                $instance = new $className();
                foreach ($data as $key => $value) {
                    $attribute = $classMetadata->getAttribute($key);
                }

Of course, the Document is not to be instantiated as it is abstract. The model hydrator receives the "entityName" from an entity repository (DocumentRepository in our case), and then asks to hydrate the list of entities with this entityName, but in fact it should use the "@type" of the collection items to instantiate each entity, and maybe just use the entity name to validate the instantiated entities?

What do you think, is there a way to make it work?

Relation serialization bug

When we try to update entity with relationships, sdk does not serialize relationships properly. In this example walletList is serialized as an array of strings (proper way) although minisiteList is serialized as an array of Minisite objects (thus not serialized?).

In this example, the contract we get has ids for wallets, and json objects for minisites, but if we get ids for minisites too the problem remains the same.

/**
 * Contract Model
 *
 * @Rest\Entity(key="contracts", repository="Mapado\Component\Ticketing\Model\Repository\ContractRepository")
 */
class Contract extends HasShortId
{
    /**
     * @var integer
     * @Rest\Attribute(name="id", type="string")
     * @Groups({"ticketing"})
     */
    private $id;

    /**
     * walletList
     *
     * @var array
     * @access private
     *
     * @Rest\OneToMany(name="walletList", targetEntity="Wallet")
     * @Groups({"wallet"})
     */
    private $walletList;

    /**
     * minisiteList
     *
     * @var array
     * @access private
     *
     * @Rest\OneToMany(name="minisiteList", targetEntity="Minsite")
     */
    private $minisiteList;
}
/**
 * Minisite Model
 *
 * @Rest\Entity(key="minisites", repository="Mapado\Component\Ticketing\Model\Repository\MinisiteRepository")
 */
class Minisite extends HasShortId
{
    /**
     * id
     *
     * @Rest\Id
     * @Rest\Attribute(name="id", type="string")
     * @Groups({"contract", "ticketing"})
     *
     * @var string
     * @access private
     */
    private $id;

    /**
     * contract
     *
     * @var Contract
     * @access private
     *
     * @Rest\ManyToOne(name="contract", targetEntity="Contract")
     * @Groups({"ticketing"})
     */
    private $contract;
}
/**
 * Wallet Model
 *
 * @Rest\Entity(key="wallets")
 *
 */
class Wallet extends HasShortId
{
    /**
     * @var integer
     *
     * @Rest\Id
     * @Rest\Attribute(name="id", type="string")
     */
    private $id;

    /**
     * contract
     *
     * @var Contract
     * @access private
     *
     * @Rest\ManyToOne(name="contract", targetEntity="Contract")
     * @Groups({"wallet"})
     */
    private $contract;
}

GET http://mon-ticketing-de-dev.com:81/v1/contracts/197
=>

{
  "@context": "/v1/contexts/Contract",
  "@id": "/v1/contracts/197",
  "walletList": [
    "/v1/wallets/165"
  ],
  "minisiteList": [
    {
      "@id": "/v1/minisites/183",
      "@type": "Minisite",
      "slug": "toto",
      "contract": "/v1/contracts/197",
    }
  ],
}

Type hinting (PHPDoc and method signatures)

Type hinting is a bit off on most classes of this project, which makes it difficult to use in recent IDEs.

class EntityRepository
{
// ...
    /**
     * @object REST Client
     */
    protected $restClient;

    /**
     * @var SDK object
     */
    protected $sdk;
// ...
    /**
     * EntityRepository constructor
     *
     * @param object $sdkClient - the client to connect to the datasource with
     * @param object $restClient - client to process the http requests
     * @param string $entityName The entiy to work with
     */
    public function __construct($sdkClient, $restClient, $entityName)
    {
        $this->sdk = $sdkClient;
        $this->restClient = $restClient;
        $this->entityName = $entityName;
    }
// ...
}

I could take care of this by adding/fixing PHPDoc blocks and methods signatures as such (for example):

class EntityRepository
{
// ...
    /**
     * @var RestClient
     */
    protected $restClient;

    /**
     * @var SdkClient
     */
    protected $sdk;
// ...
    /**
     * EntityRepository constructor
     *
     * @param SdkClient  $sdkClient  The client to connect to the datasource with
     * @param RestClient $restClient The client to process the http requests
     * @param string     $entityName The entity to work with
     */
    public function __construct(SdkClient $sdkClient, RestClient $restClient, $entityName)
    {
        $this->sdk = $sdkClient;
        $this->restClient = $restClient;
        $this->entityName = $entityName;
    }
// ...
}

Just let me know if it's ok with you :)

Split the Client in a Client and an Entity Repository

Wouldn't it be nicer to have a default Client which handles the Conversion and persisting, and a separate class like the Doctrine EntityRepository to handle lookups?

If we want to load a specific filter, we can create an EntityRepository and add the specific methods to load something specific. Next add the EntityRepository to the Entity.

If we would want to persist or convert in a different way, we extend the Client and add it to the Entity tell the sdkClient to use a different client

Support for php 5.4

At the moment your only lib which requires a minimum of php 5.5 is the guzzle client.
Could you run the tests with guzzle ~5.3.0 also, to check if the SDK still works on PHP 5.4?

Handle exceptions from API

Hello,

I have a problem handling 400 response from API.

My problem is that I need to show a particular message when an admin tries to create a new user with an already used email.

The API returns this when it happens:

Response status code: 400
Response body: - Toggle response

{
    "@context": "\/contexts\/ConstraintViolationList",
    "@type": "ConstraintViolationList",
    "hydra:title": "An error occurred",
    "hydra:description": "email: This value is already used.",
    "violations": [
        {
            "propertyPath": "email",
            "message": "This value is already used."
        }
    ]
}

In my controller however, if I Try/Catch the call to the repository, I only have access to the ClientException (thrown in Mapado\RestClientSdk\RestClient::post), so I can't distinguish the particular "dupplicate email" error from other ones, even if the API sent the information in it's 400 response.

Please help, Merci beaucoup

Using the SDK without a prefix

Hi,

we are using the SDK (with the Symfony bundle) with a REST API which has no URL prefix, ie, we have end-points like these:

http://domain.tld/articles
http://domain.tld/articles/434856bf-e37e-11e6-b8b0-0242ac110002

In the YML configs, we put these values:

            server_url: 'http://domain.tld'
            mappings:
                prefix: ~
                dir: '%kernel.root_dir%/../src/AppBundle/Entity/'

On our entities:

/**
 * @Rest\Entity(key="articles")
 */
class Article
{
[...]
}

But calls made by the SDK look like this (notice the missing slash /):
http://domain.tldarticles/434856bf-e37e-11e6-b8b0-0242ac110002

We then changed the YML property "prefix" from NULL (~) to '/' but then we get this:
http://domain.tld//articles/434856bf-e37e-11e6-b8b0-0242ac110002

We sort of hacked by putting 'http://domain.tld//' (2 slashes) in the server_url and a NULL prefix, but we get many other problems after (entities are converted to identifiers as "articles/...." instead of "/articles/...." for example).

Is the absence of URL prefix supported? If so, how should we configure the SDK?

Thanks in advance.

Symfony 3.0

Hi, I would like to know if you plan to make a version of rest-client-sdk compatible with Symfony 3.0.

Thanks for the good work :-)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.