peej / tonic

RESTful PHP library/framework
http://peej.github.com/tonic/
MIT License
624 stars 127 forks source link

Custom annotations #76

Closed galactoise closed 11 years ago

galactoise commented 12 years ago

Hi peej,

I really like what you've done here - I come from a Java background where this how APIs are handled, so the familiarity is very nice. In that vein, I was hoping to add my own custom annotations that I could then treat as filters. The specific use-case I'm thinking of is tagging resource/method combinations with accepts and produces annotations, so that when a request comes in I can compare the content-type and the accept headers and knowing whether to throw a 406 or 415 before actually invoking the method call on the resource.

How hard would it be to hook into what you're using for the current annotations, and where would I even start trying to apply a filter to all requests?

I was thinking it would look something like this:

/*

}

Thanks!

peej commented 12 years ago

Wow, this sounds like a great idea. It should be quite simple to hook in, the only problem occurs when you want to define two different POST responses for the same resource but with different @accepts or @produces annotations. This would then lead to doing something like this instead:

/  \ @uri /myUri  / class SomeResource extends Resource{   /

I'm not 100% sure that this actually makes things simpler to understand, but it might.

Paul.

peej commented 12 years ago

I've been thinking about this more. I'm liking it more and more. Here's a Gist showing how a resource class might look https://gist.github.com/2407437

galactoise commented 12 years ago

That example looks exactly like what I was imaging. I think the @method annotation you created is really slick - unlocking the resource's naming conventions instead of having the php function name be required to match the HTTP method.

As an aside, is the construct you used with the :name path param something that's already supported, or was that just part of the hypothetical example? If it's real, does it work with multiple path params - like @uri /users/:username/pets/:petname ?

peej commented 12 years ago

Yes, you can use a regular expression, URL template format or the Rails colon format. The name of the colon parts don't actually mean anything to PHP and are just converted into a ([^/]+) regex part if I remember correctly.

sarxos commented 12 years ago

Hi @peej,

First at all, I have to say that you did wonderful job!

I do not want to create new issue for that, since I just wanted to ask you if you ever considered to implement functionality known from Java's JAX-RS where you can route REST requests to various methods inside single class entity depending on different negotiations URIs, which have common root. For example:

/**
 * This is root of negotiation URIs available in this class. Methods will cover those requests:
 * - METHOD /user
 * - METHOD /user/:name
 * - METHOD /user/:name/account
 * - METHOD /user/:name/account/:accid
 *
 * @uri /user
 */
class UsersResourse extends Resource {

  /**
   * Default endpoint for class - this method will be called when user invoke POST /user
   * @method POST
   */
  function addUser() { }

  /**
   * This method will be called when user invoke GET /user/:name
   * @method GET
   * @uri /:name
   */
  function getUser($user) { }

  /**
   * Get list of accounts from specific user - GET /user/:name/account
   * @method GET
   * @uri /:name/account
   */
  function getUserAccounts($name) { }

  /**
   * Get one of user's account - GET /user/:name/account
   * @method GET
   * @uri /:name/account/:accid
   */
  function getUserAccount($name, $accid) { }

}

With conjunction of functionality mentioned by @galactoise this would be awesome! Why? Because currently I have ~100 files for different resources - it's very hard to manage them together. Many of them could be easily combined in one resource because each one of them represents some part of modular functionality. For example - to manage (add/read/modify/delete) users, I had to (I work on the code from before last month "revolution") create two resources - one for /user and second one for /user/:name.

peej commented 12 years ago

Hi @sarxos, thanks for your comments.

I understand how the JAX-RS standard might seem like a useful thing to implement. It defines a way to link arbitrary methods to arbitrary URLs. The things about Tonic is that it is not designed to do that, it is designed to model the RESTful principles of HTTP.

In your example for instance, according to REST, /user and /user/:name are two different resources, one modelling a user and the other modelling a collection of user resources, and since Tonic is designed to model resources as classes, that means writing two classes. I feel that removing this limitation would effect the way people would design their applications slipping away from the RESTful principles at the heart of Tonic. There are plenty of micro-frameworks out there that do that already (eg. look at Silex).

My solution to your organisation problem would be to group resource classes by namespace/directory, so have Users\UserCollection and Users\User classes.

That all said, it should be perfectly possible to add a @uri annotation to your own resource child class to add this.

sarxos commented 12 years ago

Thank you for your response @peej,

To be hones I was expecting such answer after I've post this comment and thought a little, since, as you said, resources are what they are not mapped by multiple URIs. However it was still worth to ask :)

The problem with so many resources in my case is the fact that I have many permission levels for various resources and I had to hack Tonic a little to implement some generic functionality able to handle all cases. But now when I think about the custom annotations you implemented lately in Tonic core, I'm under impression that this will allow me to very easily manage permissions (at least most of them) on the annotations level instead implementing them directly in the resource code.

Thank you @peej, Tonic you've create, is really a good stuff and piece of useful code :)

peej commented 12 years ago

Yes, you should be able to define a custom annotation to allow you to have different resource methods match the request based upon a user token.