Closed Ragazzo closed 10 years ago
I won't. I'm splitting it into RestController and ModelSerializer. Will ask for another review later.
good, i also think that when it will be in core it should be under yii\api\rest
ns and not just yii\api
.
We already have support on how to map a HTTP method to an action via URL rules
I remember that. But if I want to setup the rules to map HTTP request type with actual controller action, how many rules I will need to add? And most important: why should I do it? It should be something “out of the box”. Also, what should I do, if I do not use “friendly” URLs at all?
I don't think actions should be named like actionGet, actionPut, etc. This is because a single method may be mapped to different actions for different purposes.
Indeed, it could be (and probably will be) several actions in the same controller, which will have same HTTP request type. I have never told this should be restricted. But in this case their names should be explicitly passed to URL or mapped via URL rule. “actionGet”, “actionPut” and so on are just default ones, which will be applied magically, if defined, and no explicit action id is passed with URL. Same thing happens for “actionIndex” in normal web controller: just define such method and you have default page for controller, remove it and you will have 404 if no action specified.
Define base rest controller with a default set of action methods. Child classes should use filter or override the action methods to disable some of them.
Well, I don’t get it. Compare: there is a common set of operation for web controller named CRUD. It is a common start for most admin panels. However none is talking about creating base CrudController with a “default set of action methods”. Instead we have a Gii generator, which creates some basic code, which should be modified later. REST is not so strict as SOAP, I do not think we should framework should provide its implementation like “just add your model” style. REST support should be flexible.
the typical CRUD actions works well with me..... it's just up to the users how to map the Verbs to those actions
But if I want to setup the rules to map HTTP request type with actual controller action, how many rules I will need to add? And most important: why should I do it? It should be something “out of the box”. Also, what should I do, if I do not use “friendly” URLs at all?
URL rules are only part of the story. You need them if you want friendly URLs. I don't think we should do hidden magic here (how would you do this anyway?). What we can do is to provide something like a macro rule which expands into smaller ones mapping to each REST method and resource.
Indeed, it could be (and probably will be) several actions in the same controller, which will have same HTTP request type. I have never told this should be restricted. But in this case their names should be explicitly passed to URL or mapped via URL rule. “actionGet”, “actionPut” and so on are just default ones, which will be applied magically, if defined, and no explicit action id is passed with URL. Same thing happens for “actionIndex” in normal web controller: just define such method and you have default page for controller, remove it and you will have 404 if no action specified.
The other part of the story is about verb filtering. So you are suggesting using action names to imply which verb an action can accept. Again, I think we shouldn't do such magic because it's trivial to declare the verb filtering, which is more explicit and has less constraint. Another point is that the same verb can support multiple actions and the same action can support multiple verbs (e.g. actionUpdate
can support GET
, PUT
and PATCH
where GET
when responding to HTML format will display a form).
Define base rest controller with a default set of action methods. Child classes should use filter or override the action methods to disable some of them.
I was mainly proposing some alternatives. So you are proposing we rely on Gii to generate the skeleton rest controller code, right? That's certainly fine too. While we never promote CrudController, in practice I believe many people still develop and use it because it avoids a lot of duplicated code, even if they are generated by Gii. Personally, I'm not against either approach.
What we can do is to provide something like a macro rule which expands into smaller ones mapping to each REST method and resource.
Good idea.
(e.g. actionUpdate can support GET, PUT and PATCH where GET when responding to HTML format will display a form).
Example should sayactionComment
, not actionUpdate
i.e. action should be a resource endpoint.
"Native" rest support would be amazing. I think it would be best handled by a new REST application extending \yii\web\Application.
@tjconcept makes a lot of sense to me. Did you pursue this at all?
What is still left problematic from my point of view - is authorisation and sessions handling, that is still require good thinking to make it really customizable.
Why is this the hard part? HTTP Basic over SSL or OAuth2 using a library. Authentication filter comes first and throws 401, authorization comes pre-action and throws 403. REST is stateless so there shouldn't be a session - the client authenticates on every request. API-specific RBAC component handles specific authorizations.
App-wide management of error codes is an issue, however. Maybe Yii could provide direction on that? (App-specific codes, not HTTP codes.)
good, i also think that when it will be in core it should be under yii\api\rest ns and not just yii\api.
@Ragazzo Strongly agree that the namespace should be rest
. But what else is in api
?
Define base rest controller with a default set of action methods. Child classes should use filter or override the action methods to disable some of them.
What does the RestController do that ordinary controllers don't?
What is the scope of this component? I'd like to contribute but it's not clear to me what the goal of this library is.
Request/Response
do 90% of the job alreadyResources/Linking Resources and linking is by far more important and difficult than routing and controllers, so I think that should be our focus.
I strongly suggest we define a resource interface, e.g. rest\ResourceInterface
to clarify our direction. With a good interface it should be pretty straightforward for developers to start contributing to this issue.
Minimum support:
The Api
example is a start but IMO has too many responsibilities. (Expansion, property mapping, collection handling, error handling, ...) It also doesn't seem to address links.
Routing Put and Patch mean different things, so I would prefer splitting them up. (You can, for example, put a new record to a particular ID, but you can't Patch a new record.) HTML forms don't support either, so I don't see the harm in doing things this way.
GET /resource => list
POST /resource => create
GET /resource/:id => show
PUT /resource/:id => replace
PATCH /resource/:id => update
DELETE /resource/:id => delete
@danschmidt5189 Thank you for your insightful comments and suggestions. You mentioned ResourceInterface
- who should implement this interface? And how should a resource link be created?
Below is the rough structure of my latest design:
/rest
folder.Action
and Controller
, and a set of commonly used action classes (e.g. ViewAction
, CreateAction
). They come with out-of-box REST support and with the following features:
User
class for stateless authentication (register it as apiUser
application component)Resource serialization is probably the most complicated one (which might include resource links as you suggested).
Dont think that we should support PUT
as we are not "RESTiarians".
@Ragazzo why not?
@qiangxue great ! So you have decided to go with "extended" api support out of the box (but not with a very bacis as you have described some time ago) ?
Also I vote for "resource/collections links", I think it is very important to have to make APi and client more separated to make less assumptions on how one or another resource should be called.
Automatic API doc generation
Do you mean just docs accroding to PHPDoc ? Or based on real request params and validation to them ?
Btw - request params validation should be also considered. As for example for CRUD actions corresponding Model rules could be used for validation. But if it will be not a CRUD, but Verb action like search
or confrimFriendship
- it would be good to have ability for similar to Model declarative validation rules specification (and it appears that such validation should be made on Action level, as there could be no Model for Verb action).
@Ragazzo it will be very strange to support everything, but not PUT
:)
@samdark its senseless if we use PATCH
. POST - create new instance, PATCH - update. Sometimes users make PUT work like POST, but as you see there is no good difference here.
"In a PUT request, the enclosed entity is considered to be a modified version of the resource stored on the origin server, and the client is requesting that the stored version be replaced. With PATCH, however, the enclosed entity contains a set of instructions describing how a resource currently residing on the origin server should be modified to produce a new version."
So these are basically different.
@samdark yes, but its in theory, in practice its like: PUT or POST, PATCH or PUT.
@Ragazzo PUT
like POST
can make an object but:
We use PUT
if ID of object generate on a client.
We use POST
if we expect it from a server.
We use PUT if ID of object generate on a client.
true, so put is like total substituation of object.
We use POST if we expect it from a server.
what does that mean?
Anyway there will be 4 verbs (and three already known: GET, POST, DELETE), not 5 :) I think we should go the rails way here.
I assume the main goal allowing usage of any request method no matter PUT, PATCH or anything else. Nowadays people are talking about OPTIONS request type, which should return the help information about REST resource usage. There should not be any restrictions in this case.
Agree. Restricting it doesn't make sense. We may put together a doc with recommended way though.
Fine by me since rails support also both PUT/PATCH
in this way.
We may put together a doc with recommended way though.
yes, this should be done i think, so we will avoid mess in the routes.
We use
POST
if we expect it from a server.what does that mean?
if we want make an object with specific id we use PUT
if we don't want we use POST
which generate id on a server.
I read it from http://restcookbook.com/HTTP%20Methods/put-vs-post/
Yes, both are valid.
@dizews yeah, right. thanks for the docs :)
@qiangxue Some feedback:
Automatic API doc generation
Existing libraries like Swagger can handle doc generation, so maybe this is a low high priority? Could just mention them in the guide as a heads-up for developers.
Versioning (this probably will be done with subdirectory support for controllers)
Hm... I will need to think about this. I'm worried this encourages too-rapid API changes and makes it harder to version control. Good post by Phil Sturgeon on the subject.
Resource serialization, including selective property mapping and object embedding/expansion, collection handling.
ModelSerializer
is close to what I'd written and seems to handle all our requirements. I'll submit a pr with changes (mainly configuring individual serializers) and improved handling for data providers (for paging/collections).
Authentication / Authorization
Authentication should remain more or less the same. I like RBAC for authorization - is there a reason not to use it? Might need to improve performance, e.g. rbac\CacheManager
that can use a Redis backend.
@Ragazzo
Dont think that we should support PUT as we are not "RESTiarians".
I think you're recommending that we not write actionReplace()
, not that we ignore PUT
? That sounds reasonable to me - replace is hard and specific to the dev's app and database. (E.g., handling a request like PUT /users/99999999999 {...}
- how to allocate the pk? Handle future inserts? etc.)
Mapping both PUT
and PATCH
to actionUpdate()
is a good default that many will recognize from Rails.
@danschmidt5189 Thank you for your comments. Do not submit PR for ModelSerializer
because I'm considering dropping it and introducing an interface that may be implemented by models.
Authentication should remain more or less the same. I like RBAC for authorization - is there a reason not to use it? Might need to improve performance, e.g. rbac\CacheManager that can use a Redis backend.
RBAC will remain. It is for authorization. I was talking about authentication. Currently User
component requires starting a session, while for REST api, people prefer it to be stateless without starting session. That's why I'm thinking to develop a separate User
component.
it is an article about versioning which consider cons and pros of 3 ways.
@danschmidt5189
(E.g., handling a request like PUT /users/99999999999 {...} - how to allocate the pk? Handle future inserts? etc.) Mapping both PUT and PATCH to actionUpdate() is a good default that many will recognize from Rails.
yeah, thats what i was trying to say, thank you.
Two comments on "406 Not Acceptable" exceptions (as I write the NotAcceptableHttpException
doc comment for #2106):
It would be pretty cool if throwing a NotAcceptableHttpException
automatically added the acceptable content types to the response body. Just a thought.
Here's the spec.
I was looking at the yii\web\User
class and I've come to a connclusion that the loginByCookie()
method could be "demoted" to a behavior, so as to make the User component more agnostic about the form of authentication used. Then, some other behaviors could be added to accomodate for the needs of RESTful APIs. For example: loginByCookie()
=> CookieAuthBehavior
, and new OA2AuthBehavior
could be created to login users by tokens they provide.
Of course I am open to any other approaches, as long they make the User component more universal.
Is there any news about REST support in Yii2? (although not a argument, but this is only thing stopping develop new project with yii2..).
(although not a argument, but this is only thing stopping develop new project with yii2..
you can take already developed extensions for Yii1 and use it, or rewrite them for Yii2. See in this article how you can use Yii1 components and extensions in Yii2.
@qiangxue Any updates?
I have a few ideas regarding an interface-based implementation. The basic concept is to define a ResourceInterface
, the core of which defines:
/**
* Returns the string representation of the object in a given MIME type
*
* @param string $mimeType A MIME type, e.g. "application/json"
* @param array $options Conversion options, e.g. "embed xyz resources"
*
* @return string String representation of the object
*/
public function convertTo($mimeType, $options = array());
/**
* Returns whether the object can be converted to the given MIME type
*
* @param string $mimeType A MIME type, e.g. "application/json"
* @param array $options Conversion options, e.g. "embed xyz resources"
*
* @return bool Whether the object is convertible to the given type
*/
public function isConvertibleTo($mimeType, $options = array());
(It adds other features too, e.g. concepts of "freshness" and id.)
A ResourceResponseFormatter
would handle content-type negotiation as well as setting headers and content based on the resource passed into Response::$data.
public function actionCreate(array $Customer)
{
// Load the resource
$customer = new Customer();
// Something like this is possible because the resource knows what it can
// be converted to. Probably better in a filter.
if (!$this->isAcceptable($customer)) {
throw new NotAcceptableHttpException();
}
// Do your thing
$customer->setAttributes($Customer);
$customer->save();
// Response inspects resource state and the request to determine how to respond, eg:
// 1. JSON representation with correct status code (201/422/500) if request wants JSON
// 2. Redirect to "new" if there are errors
// 3. Redirect to "view/id" if there are no errors
Yii::$app->response->format = 'resource';
Yii::$app->response->data = $customer;
return Yii::$app->response;
}
This is all strongly inspired by Responders / respond_with in Ruby on Rails 4, btw.
@loloroboros Thank you for your suggestions. I'm still working on this (about 60-70% done). It has taken quite a long time because I spent a lot of time evaluating existing solutions and many articles.
I thought about introducing ResourceInterface
like you suggested but gave it up for various reasons. Currently, my design is using arrays as the common format, converting models to arrays, and converting arrays further to various formats (e.g. json, xml). Custom fields and embeds are both supported. HATEOAS is also supported.
Content type negotiation, result serialization, user authentication, allow check, etc. happen in action filters or before/afterAction() calls. As a result, you may find the following code very familiar:
public function actionCreate()
{
$model = new Post;
$model->load(Yii::$app->getRequest()->getBodyParams(), '');
$model->save();
return $model;
}
Wow, that sounds fantastic. I'm really curious to see and comment on your implementation.
Do you plan to release a preview soon?
Also, is there the same/similar magic to Rails with partials/views and redirects when the request wants text/html? That's a great time-saving feature.
This is my top priority for now. Will certainly release it for preview before merging and when ready.
@qiangxue well that is good, but please dont forget about filtering, pagination, custom fields in response (/api/v1/users?fields=name,email&limit=100&offset=10&filter[name]=%some_name%
) and other things) Currently i dont like the approach where api is going because it will be more like some controller-helpers-useful-methods and not a separated API solution, but will see ;)
@qiangxue can you point us to these other solutions you are referring to? for academic reasons.
@Ragazzo pagination and custom fields is one thing but filtering .... nah ... filtering can be rather implementation specific. Anyway, my two cents here. Driven by the need for a more versatile filtering scheme, in one of my own implementations I supported multiple types of filters such as:
so your filter from the example above would become something like /api/v1/users?fields=name,email&limit=100&offset=10&filter[name:contains]=some_name
@jfragoulis the list will be huge. Check this one for the start: http://pages.apigee.com/web-api-design-ebook.html
@jfragoulis does not matter, if web api does not provide filtering than it is bad api.
An initial implementation is done:
Your comments are welcome.
Remaining work:
@qiangxue That's really elegant code. I look forward to adding APIs in easily in the future, I've been experimenting with Ember.js and this looks like it'd fit together quite nicely. Thanks for all the hard work fit in around your work and home life.
so, we once again put everything in one model with huge number of scenarios (e.g. api-create
)? not a good thing as for me)) will review other later.
I don't like the idea of separating the html interface of an app from other formats. If I'd like to add an API to an existing application it would require to redefine all controllers and actions to return data passed to render instead of render result.
I think that API should be integral part of an application and the html view is just a degraded version to be used by a dumb client (human/browser) that can't use other http methods than GET/POST. If the html view contains navigation items like a menu and data items like a grid with item numbers and currently active filters then the same information should be available in other response formats like JSON or XML. The controller actions would remain the same, that is it would call render and return the result.
Format would be selected when rendering the output, not when sending it back, which the Response class is responsible for.
I recently did such implementation in Yii 1 and it looks really simple. I got a full html CRUD with an option to fetch JSON data utilizing all my filtering, pagination etc. It's also hyperlinked.
I only had to:
One more example, the API response for index action should return:
To sum this up, what I ask is is it possible to move format related login from Response to Controller::render()?
so, we once again put everything in one model with huge number of scenarios (e.g. api-create)? not a good thing as for me)) will review other later.
@Ragazzo: you are encouraged to subclass your base model classes. That is, you keep a set of model classes under common
, and then subclass them in each tier. Creating specific resource classes is just another way of splitting the code, which you need to duplicate some logic available through AR.
I don't like the idea of separating the html interface of an app from other formats. If I'd like to add an API to an existing application it would require to redefine all controllers and actions to return data passed to render instead of render result.
@nineinchnick Thank you for your feedback. The idea of serving HTML together with JSON/XML using a common set of controllers sounds fascinating. However, in practice this will end up with very messy code in the end for the following reasons:
Those are valid points but I don't think it's a response to my question. I don't say we have to make all apps have a web frontend bundled with an API. I just want it to be possible, probably for some more generic CRUD backends, to have a HTML format available in the API so a human could use it as a normal application instead of building a second one. I could provide my own formatter for HTML output to achieve this but that's not my point.
It seems like going away from the views concept and I don't see why. Even if preparing a specific format like JSON involves just a call to some serializer method it doesn't mean it can't be called rendering. I mean I'd like to keep the current flow of executing an action that prepares data, passes it to a renderer to format it and then returns it.
In Yii 1 an action sent the response contents, either through calling render or directly (echo). Now an action must return response contents. In the current API solution this flow is distrupted, the action returns raw data. A new concept of formatting is introduced that could be a bit confusing.
I ask my question again, why not move formatting from the Response class to a renderer and use it in API?
@nineinchnick You may treat yii\rest\Serializer
as the view if you want. The point is that HTML views are mainly used to format complex outputs (call it free-form formatting if you like), while API data formatting is very regular and thus a single serializer is enough without the need to use different views to format different data. If you still insist in using a view, the current implementation doesn't prevent you from doing so.
Are you considering using OAuth2 as an alternative authentication mechanism or do we have to write/use 3rd party extensions for this?
Just improved the authentication by supporting different auth types, including HTTP Bearer which is supposed to be used by OAuth2. @subdee The implementation of oauth authorization server is beyond the scope of this rest api feature. We may or may not address it in a separate feature.
i think the simplest way to implement those hypermedia links is to have some kind of map of models to resources that can be used by the behavior attached to the model... also this could provide a mapping of resource attributes to model attributes i implement it like this:
$model->attachBehavior('hypermedia', new Hypermedia(function($model) use (&$controller){
return $controller->createAbsoluteUrl($controller->resourceName. '/view', array('id'=>$model->id}));
});
@qiangxue Initial thoughts (could be off base - I've spent 12+ hours working ;) -
api-
scenario prefix? IMO defaulting to the standard scenarios is less surprising.$.parseJSON(data)._links.next
support)And... that's enough for now. I'd like to hear what others think and take a deeper look myself.
Edit - Just saw Linkable/Link: a8c7d36c02e45fe490b16bf2f49de11262589c70. I like this - why not implement defaults for AR? Magically doing it would require checking for available API controllers.
I think it can be useful if Yii will provide some web-api support, for example REST, with catching event when request starts, also if Yii will provide some
CRestAction < CAction
, maybe other workaround can be here to fit REST, what do u think? Situation is common enough so if Yii will give developers some basis it can be good.Features to implement
File upload support (RC)moved to #4477Batch queries with transactions support and error handling. (GA or post GA)moved to #4478Searching and filtering (GA or post GA)moved to #4479Automatic API Documentation Generation (e.g. via https://developers.helloreverb.com/swagger/) (GA or post GA)moved to #2684