Giter VIP home page Giter VIP logo

zfr-rest's People

Contributors

bakura10 avatar danizord avatar gabrielsch avatar jmleroux avatar lku avatar macnibblet avatar mesaugat avatar ocramius avatar ojhaujjwal avatar orkin avatar sasezaki avatar thinkscape avatar zluiten avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  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  avatar  avatar  avatar  avatar  avatar  avatar  avatar

zfr-rest's Issues

HATEOAS links

Once #1 is implemented, we can build HATEOAS links in resources by simply re-using routing to allow the user agent to discover associations, canonical URIs and other details on metadata of our resources.

Implement serialization of controller output

A controller, so far, may return one of following values when dispatched:

  • Zend\View\ModelInterface
  • Array
  • Zend\Stdlib\ResponseInterface

It may be interesting to handle serialization of the response in cases where a more specialized response is returned, such as (examples):

  • UserRegistrationResponse
  • User (resource)
  • UserDeletionResponse
  • BlogPostSubmissionResponse
  • BlogPost (resource)

All these custom types are not handled by ZF2 itself, but we may match them against some information (specific to this module) to decide which response to build.

Relevant things here are:

  • The Accept header sent by the user agent
  • The type of the returned object

Standard filter for serialization controller output

EmberJS has strong conventions on how data should be returned, and I suppose this is the same for other MVC JS framework. Of course all of this is configurable, but for instance, EmberJS expects that:

  1. The keys are underscore_separated.
  2. Each identifier is appended with _id.
  3. All the result is wrapped around something whose name is the name of the model (for instance: {'post': {'key1': 'value1', ...}}.

We should do a simple extraction (through the hydrator), and then provides built-in filters that would allow that kind of thing.

Handle associations without association

ping @Ocramius ,

Currently, to expose an association in ZfrRest, the entity MUST contains a Doctrine association and therefore potentially maintain a bi-directional associations. For instance if I have a user entity associated with projects, I'd like to have a uni-directional association from project to entity, but not maintain it in user. This is not possible yet.

I'd suggest allowing new parameters for the Association annotation:

/**
 * @REST\Association(path="projects", targetEntity="Project", propertyName="user")
 */
class User

Actually, the only new property is targetEntity. path was already here, and propertyName was implicitly set to be the property name.

Now, when hitting "/users/5/projects", it will first check if there is an association "projects" (there is none), and then also try the inverse associations registered with the entity.

Default hydrator should skip associations.

Right now, the default hydrator will produce the following garbage in the resulting model:
image

I expect it to skip associations (*-to-many and -to-one) as it is not possible to infer sub-hydrator.

Hydration of association-properties should be handled by custom (user-made) hydrators for safety and control.

Silent failure in resource payload listener

Looks like when an exception is thrown in ZfrRest\Mvc\View\Http\CreateResourcePayloadListener#createPayload, then the dispatch process still continues without triggering an application error.

The cause is still unknown to me, I'm just writing it down to later write an integration test

Support to get multiple resources by ID

Currently, we can: get a single item (eg. /users/1) or a collection (/users), but there is no support to get multiple items by id.

JSONAPI (http://jsonapi.org) suggests this syntax: /users/1,2,3,4

What do you think? The syntax seems nice to me, and it should be quite easy to add support for it.

How to handle caching?

Hi,

@Ocramius , I thought about this this morning, but what would be the cleanest way in your opinion to cache DB results? Because the fetching is done by the Criteria API directly in the router, we have no simple way to cache results.

Do you have any idea?

More flexible query filtering

Currently, a very simple logic is executed at the end of routing to add filters based on the query string. However it's pretty limited because:

  • You cannot filter with fields that does not exist in your entity.
  • You cannot implement custom logic (ZfrRest always add strict equality).

A more elegant solution to this problem is to remove this part from the router, and trigger an event like "onQueryFiltering", that would pass the query string, the criteria object and the class metadata.

Some things to think about:

  • Maybe we should include the entity name into the event name (like "onPostQueryFiltering", "onUserQueryFiltering"...) in order to avoid a giant switch in a listener (or something else, but I'm not aware of any feature in event manager that would allow to filter listeners based on the same event name).

Automatically add Allow header

ZfrRest should be able to add Allow headers in the Response (surely through a listener). This may be possible to discover which actions are possible through the method defined in the controller.

Change namespace

I'm going to change the namespaces of all Zfr modules/library to Zfr\ModuleName (Zfr\Rest, Zfr\MailChimpModule and so on)...

SelectModelListener must work with REST requests only.

After setting this, zf2 application stops working as expected:

    'zfr_rest' => [
        'models' => [
            'invokables' => [
                '*/*'                    => 'Zend\View\Model\JsonModel',
                'text/html'              => 'Zend\View\Model\JsonModel',
                'application/xhtml+xml'  => 'Zend\View\Model\JsonModel',
            ]
        ]
    ]

This is because SelectModelListener will now force JsonModel on all controllers, all actions, from all modules in the application.

Expected: ZfrRest\*\SelectModelListener should only act on REST calls and leave other listeners to handle other types of requests (i.e. Zend\View\Strategy\*)

Remove cache factory

ZfrRest\Factory\ResourceMetadataCacheFactory should be removed - the cache instance should be provided by the user, not by ZfrRest

CORS probleme

If you use two different application un rest context :

you have CORS problem because the request will be send by an other domain than the response.

One solution might be to add a configurable header to the response of which is the authorized domain.

I have add this code to dispatch fonction :

$headers = new Headers();
        $header = new GenericHeader('Access-Control-Allow-Origin', 'http://client.local');
        $headers->addHeader($header);
        $response->setHeaders($headers);

This code works for GET request.

Support for RFC 6570-style URI templates

While trying to implement the Hypertext Application Language specification in one of my projects I stumbled upon the issue on how to construct a templated URI like this:

"find": { "href": "/orders{?id}", "templated": true }

As it seems there is currently no elegant way to retrieve a previously defined route and its variables from within a ZF2 controller, so maybe it would be nice to have a custom route that exposes some kind of API to get the variable URI parts or maybe even supports that proposed standard RFC 6570?

Handle standard exceptions within REST

There's a whole lot of standard exception codes for HTTP errors.
We may implement the most obvious ones first, but basically, we need to have the REST module hook into the application.error event and sniff for known error types. We may provide interfaces for the main error types (4xx, 5xx) and then go into details by adding more exceptions (401 implements 4xx).

There may be libraries out there already doing this already, let's try not to rewrite all the error messages on our own.

Anyway, on an unsuccessful request, the user agent should get the according error message serialized in a format compatible with the Accept header (see #5)

Implement basic routing with resource discovery

As of previous discussion, routing should be able to discover resources on its own.

Basically, we map an entry point, like /user to some logic that is able to fetch user objects (example).

Knowing that, the route will be able to match URLs like /user/123/posts/456 by just knowing that /user has to use the logic to fetch users, 123 should fetch a specific user within the retrieved collection of users. /posts is an association within the single user (collection-valued) and then /456 is again a filtering applied on the retrieved posts.

This basically allows us to compose routes based on generic resources associated between each other in some particular way that can be configured by the end user.

As suggested, I'd simply extend Doctrine\Common\Persistence\Mapping\ClassMetadata to achieve this.

Recursive extraction

What's the best way to support this? Let's create a RecursiveHydrator?

{
    "id": 1,
    "title": "ZfrRest is awesome",
    "author": {
        "id": 50,
        "name": "Michaël Gallego"
    }
}

Request validation

Assuming we know the requested resource type (see #2), we can implement validation of the parsed data by requiring input filters compatible with the requested resource.

This can again be done by mapping input filters to resources in my opinion.

A route may also provide us with relevant information about what the expected request type may be.

For example, POST /users would expect a UserRegistrationRequest, while PUT /users/123 may expect a UserUpdateRequest. Assuming data is validated correctly, we may bring this one step further and provide the controller with already populated instances of such specialized request objects.

Add support for X-HTTP-Method-Override

Some clients do not allow some HTTP operations like PUT and DELETE (Lotus...). Therefore, some clients send custom HTTP Headers to override the HTTP verb.

It should be good to have an optional listener (on MvcEvent::ROUTE or early on MvcEvent::DISPATCH) that would check if this header is here and change the method accordingly. Those headers does not seem to be standard, while widely accepted. Those are:

X-HTTP-Method (Microsoft)
X-HTTP-Method-Override (Google/GData)
X-METHOD-OVERRIDE (IBM)

More info: http://fandry.blogspot.fr/2012/03/x-http-header-method-override-and-rest.html

Wrong Installation documentation

I tried to install this module according installation documentation but I had the following error :

Loading composer repositories with package information
Updating dependencies (including require-dev)
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - The requested package zf-fr/zfr-rest could not be found in any version, there may be a typo in the package name.

Potential causes:
 - A typo in the package name
 - The package is not available in a stable-enough version according to your minimum-stability setting
   see <https://groups.google.com/d/topic/composer-dev/_g3ASeIFlrc/discussion> for more details.

Read <http://getcomposer.org/doc/articles/troubleshooting.md> for further common problems.

In packagist, the module is named "zfr/zfr-rest" instead of "zf-fr/zfr-rest". Installation documentation should be update.

Workflow of ZfrRest

Hi,

As it is pretty confused for me and, it looks like, for you a little bit too, here I attempted to summarise the various steps of ZfrRest:

  1. First step is to execute the Resource Route on MvcEvent::EVENT_ROUTE listener.
    1. If ResourceRoute returns null, an error will be triggered, end of process.
    2. Otherwise, returns a RouteMatch with the association resource and controller, fetched from the resource metadata.
  2. Then, the MvcEvent::EVENT_DISPATCH is triggered. We have some listeners:
    1. The optional HttpMethodOverrideListener, that optionally override the Http method.
    2. Next I'd want to introduce a new listener, called "ProcessResourceListener" or something like that, whose job is to perform the various tasks like hydrating, filtering, body processing.
      1. If method is GET/DELETE: just leave the resource as it is in the route match. Methods in the controller will simply be public function get(User $user) / delete(User $user).
      2. If method is PUT/POST:
        1. Use the Body Parser to extract data (once again the question here is do we need to use the Body Parser, that only use the DecoderPluginManager, or should we use the decoders included in the resource... your use case you told me yesterday really looked weird to me so I don't really understand... The goal is to avoid asking people to write 30 decoders, 30 encodes, 30 hydrators... otherwise this module becomes pretty useless if we ask people to write so much boilerplate code).
        2. Get the input filter from the metadata and run it through the data fetched in previous step. If data is invalid, throw an exception.
        3. If data is valid, get the hydrator from the resource metadata, and hydrate the entity. Update the resource in the RouteMatch with what is returned from the hydrator.
        4. This will then resume, and the controller will be simply put(User $user) / post(User $user).
    3. The controller is run and returns "something".
    4. The SelectModelListener is called, which takes the result and parses to Json using the the resource encoder of the resource.

Some things here:
Basically, we're not using the the DecoderPluginManager and EncoderPluginManager anymore, simply because after all, we are using the decoders/encoders specified in the resource metadata.

What's still very unclear for me is, if for PUT/POST request, we should do all the stuff (filtering data, hydrating...) or if we should simply let people do this manually. If we let people do this manually this means that we can remove inputFilter and hydrator from the resource metadata.

Provides basic listeners for Authentication

It would be cool if we provide some simple assertions for basic Http authentication.

Both Zend\Permissions\Rbac and Zend\Permissions\Acl has an AssertionInteface that we could implement (and then use to any other module you use like BjyAuthorize or ZfcRbac).

I know @Ocramius you already have a talk about this (with BinaryKitten iirc) that this should not be our stuff, but what we would provide is only assertions that implement the logic, we would not plug this into the code.

What do you think ?

NOTE : I don't know anything about HTTP authentication yet, so I don't know if this is feasible yet. I'll invest that today =).

Support versioning

Just an idea for myself for potential mapping for versioning:

Currently, mapping is defined using Resource and Collection annotations:

/**
 * @REST\Resource(controller="MyController")
 * @REST\Collection(controller="MyCollectionController")
 */
class Resource

The idea is to simply add a "mediaVersion" parameter that would be used during content negotiation:

/**
 * @REST\Resource(mediaVersion="application/vnd.v1+json", controller="MyController")
 * @REST\Resource(mediaVersion="application/vnd.v2+json", controller="MyControllerV2")
 */
class Resource

Attempting to follow the Quickstart but receiving the following error

Hi, this looks like a very promising project and I'd like to get a minimal configuration working so I could check it out, hopefully this is the correct forum for this.

I'm following the quick start guide and when I attempt to get my collection of objects with the url /api/v1.1/projects I get the following error:

Fatal error: Call to a member function getOutsideClassMetadata() on a non-object in /opt/ssp/apps/www/html/chris/ssp2/vendor/zfr/zfr-rest/src/ZfrRest/Mvc/Router/Http/ResourceGraphRoute.php on line 318

I checked into the ResourceGraphRoute.php and confirmed that $this->resource is in fact an object of type Doctrine\ORM\EntityRepository, and the $resource->getClassName() returns the correct class name, but it seems that $this->metadataFactory->getMetadataForClass($resource->getClassName()); is returning NULL.

Any ideas about where I could look to resolve this?

Allow to assemble URL with route

Currently the "assemble" method of the route is missing. This is absolutely needed to generate URLs, but this is not as easy to do, because only the root route is defined in config, and others are just "guessed", so that kind of things $this->url('/users/tweets', array('user_id' => 3, 'tweet_id' => 5) cannot work because only the /users route is defined.

Some syntax like $this->url('/users', array('user_id' => 4)) are tempting, and can be used to generate URL for /users/4, /users/4/tweets/5 (with $this->url('/users', array('user_id' => 4, 'tweet_id' => 5)), but URL for a collection like /users/4/tweets cannot be generated.

Another solution would be to programmatically add new route as we discover them, but I don't really like this solution.

The way that ZF 2 router is made make this more complicated than it seems, and I really want a simple syntax (as the URL helper is mainly used in view helpers... the syntax must not be super-complex).

Don't halt dispatch when an exception is thrown

Currently, when an exception is thrown, it halts the dispatch process by returning a Response object in HttpExceptionListener. The problem is that it makes an empty page, hard for debugging.

At the same time, we cannot create a ViewModel directly otherwise, the other listener that create the appropriate ModelInterface according to Accept header won't work.

Where to put services ?

For @Ocramius : currently we're putting all the services classes to ZfrRest\Service. Is there a convention about that ? For instance, JsonDecoderFactory should be in ZfrRest\Service\JsonDecoderFactory (like it is now) or in ZfrRest\Serializer\Service\JsonDecoderFactory ?

Use DoctrineORMModule paginator adapter

When I wrote the paginator stuff for ZfrRest, I used the Selectable adapter (https://github.com/zf-fr/zfr-rest/blob/master/src/ZfrRest/Mvc/Router/Http/ResourceGraphRoute.php#L253).

While this allow us to completely dissociate ZfrRest from any ORM or ODM, Doctrine ORM still has the issue for not returning lazy loaded collection : http://www.doctrine-project.org/jira/browse/DDC-2217

Basically, it makes ZfrRest nearly unusable for those cases, as any thing that deal with Paginator will fetch the whole table inside an array.

Until Doctrine fixes this issue, I suggest we make an ugly fix to the ResourceGraphRouter to make at least ZfrRest usable. The idea:

  1. here, we check if $data is an instance of Doctrine\ORM\EntityRepository AND if the class DoctrineORMModule\Paginator\Adapter\DoctrinePaginator (https://github.com/doctrine/DoctrineORMModule/blob/master/src/DoctrineORMModule/Paginator/Adapter/DoctrinePaginator.php) exists.
  2. If so, we set this adapter.
  3. Otherwise, we stick with the Selectable adapter.

I know this fix is ugly, but at least it makes it usuable until someone give some love to this VERY important feature in Doctrine.

Implement request parsing

Request body should be parsed by knowing its type (submitted as content type header).

We may implement basic parsing for XML/JSON/UrlEncoded now.

This will simply produce an array of request values to be put somewhere.

Paginated resources could be pre-configured with items per page/current page

As discussed in https://github.com/zf-fr/zfr-rest/pull/43/files#r4334128, the paginator may be pre-configured with current page/items per page.

This simplifies logic in controllers (pseudo code) from:

public function delete(Paginator $paginator)
{
    $paginator->setCurrentPageNumber($this->params()->fromQuery('page', 1));
    $paginator->setItemsPerPage($this->params()->fromQuery('items_per_page', 20));

    foreach ($paginator->getCurrentPage() as $item) {
        $this->service->delete($item);
    }

    return true;
}

to:

public function delete(Paginator $paginator)
{
    foreach ($paginator->getCurrentPage() as $item) {
        $this->service->delete($item);
    }

    return true;
}

Collection resources should be passed "raw" to the controller

Right now, we are converting collection resources into paginators during routing.

That is a lossy conversion that makes it very hard to operate on the original collection by keeping the controller or service layer persistence-agnostic.

What I'm suggesting is that the controller should be allowed to get passed in whatever the original collection was, so instead of:

public function get(Paginator $someResource) {...}

we'd have

public function get($someResource) {...}

Deciding what to do with the resource (paginate it, slice it, whatever) is up to either the controller, or a method handler, or the view layer.

Routing was absolutely a mistake for this.

Implement abstract controller for a single REST resource

Zend\Mvc\Controller\AbstractRestfulController mixes up handling of two resources. We may implement our own abstract controller that allows dispatching of any HTTP method (also custom ones) given a resource.

It may be interesting to have something like following:

class UserController
{
    public function dispatchGet(User $user);
    public function dispatchPut(User $user, UpdateUserRequest $userData);
    public function dispatchDelete(User $user);
    public function dispatchOptions(User $user);

    // etc
}

Not sure this can be done via interface, since as you see there may be either a generic request object (like in normal controllers) and also a more specialized one (for example the UpdateUserRequest in this example).
It may be a good idea to put the specialized object in the route match instead of modifying the method signature. Not sure yet, but method signature with strict type hinting would allow us to assume data is already valid.

Also, assuming that HTTP method are not just GET, POST, PUT, PATCH, OPTIONS and DELETE, but may include custom ones too, we have to probably be very flexible.

But basically, we need to dispatch a controller against a resource object, a (parsed/hydrated) specialized request type and a generic http request too.

Project current status ?

Hello,

This module looks like it has lots of potential, but I can't quite figure out whether I should start using it right now or wait more.

Current README in master clearly states that this is not yet production-ready, because tests are missing. But there are several tests in tests/ZfrRestTest/.

Also there are several open issues that seems to be resolved and/or out of date. At least this is what it seems when looking at milestone 0.0.1.

So may I humbly suggest to update those things to better reflect the current status of the project ? Especially close issue if appropriate and maybe (re)define a milestone ? I think it would attract more user (and more PR) if we could see that the project is close to a stable state.

Please forgive me for using the issue tracker for this topic, but I could not find any other way to contact you.

Keep dependency to Symfony\Serializer ?

I'm wondering if, in fact, we need the dependency to Symfony\Serializer. It looks like Zend\Serializer\Adapter\Json does the job quite well too.

Sure, Zend Serializer does not have support for Xml, but as ZF 2 does not have XmlModel, the Symfony Xml Serializer cannot be used anyway. So I'm asking if it's worth while to keep the dependency.

Could you have a look at both the JsonSerializer of ZF 2 and the one of Symfony and tell me ?

Idea for flexible rendering

Hi,

I had an idea today to solve a issue we had before: how to render the Paginator? Some people may want to expose values like this way: item_per_page, current_page ; some may want "limit", "offset"... some may want to not display any info at all...

In current version of ZfrRest, it's rather inflexible, as we have a custom PaginatorHydrator. I'm thinking about this two steps process:

  1. A createResourceModel is listener after the action has been dispatched (so that user may return a resource which is either a single element OR a paginator). The task of this listener is to create a ResourceModel, by giving him the data to render AND the hydrator to use (this listener therefore automatically choose either the hydrator for collection or for single item).
  2. In the view layer (which is in fact where it should be), two methods are defined: renderItem and renderCollection. This way, if you want to customize how things are rendered, you just provide your renderer.

ping @Thinkscape @Ocramius

Allow to add filter mapping

To exclude annotation, we should be able to specify a REST\Exclude at property level. Internally, it would add something to a hydrator filter.

This in case would allow to remove the NONE serialization strategy for associations.

Add command line tool to visualize routes

Contrary to "normal" routes, the REST route we are developing uses a lot of metadata information to automatically "discover" resource routes. It may be hard to know which routes are available then.

Therefore, it could be useful to add command-line commands to show all the routes available in REST context.

The following commands could be useful:

  • php index.php rest routes : display all the routes available from ZfrRest
  • php index.php rest routes users : display all the available routes for the given routes (for instance, it may display /users, /users/:id, as well as /users/:id/tweets.

Support of API versioning

API versioning is a requirement when working with API, it gives control over the Response representation and allow improvement of both backend and clients without breaking things.

However, it is a tricky question, and there are best practices as well as real-life use case.

Therefore, there may be no "correct solution" for this.

I may recommend a ApiVersionStrategy utility, implementing the Strategy pattern to decide how to work with version in an uniform way.

The ApiVersionStrategy are free to be implemented by end-developers but we may provide a basic subset of common case like uri versioning, Accept versioning, etc.

The ApiVersionStrategy would generate an ApiConstraint which would be available to in a uniform and consistent way to decide of the implementation.

Depending on the rest of the implementation it may takes place in the Routing module.

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.