dingo / api Goto Github PK
View Code? Open in Web Editor NEWA RESTful API package for the Laravel and Lumen frameworks.
License: BSD 3-Clause "New" or "Revised" License
A RESTful API package for the Laravel and Lumen frameworks.
License: BSD 3-Clause "New" or "Revised" License
Okay the plan is to overhaul the controller that the API ships with to provide a bunch of helper methods to easily return better responses.
Currently responses are kind of "magically" transformed. I'd like to keep that, but perhaps add a configuration option to disable it (perhaps by default?) so that you can return raw responses (in regards to #50) and also make use of the helper methods.
Possible helper methods:
withPaginator
withCollection
withModel
withArray
Usage would be:
return $this->withModel(User::find(1));
Or something to that affect. Just after some thoughts and other possible methods.
/cc @philsturgeon
Hey @jasonlewis - is the only reason this package required php 5.4 because you use array brackets, or is there another reason?
Hey Jason, any chance you wanted to build in support for the route() call vs clunky url patterns? :) I think it would be fantastic to do that, so in your internal API requests, they could be so clean.
Look at a way of implementing Fractal cursors.
The biggest issue with cursors is they require a little more configuration in user-land then something like pagination.
Ok, so I'm probably missing something, but I figured I might as well ask before I spent too much more time on this.
Here's my routes file.
Route::api( ['version' => 'v1', 'namespace' => 'Api\Controllers', 'protected' => true], function ()
{
// Route Patterns (All ids must be integers, etc...)
Route::pattern('id', '[0-9]+');
// Articles
Route::group( ['scopes' => 'articles.read'], function ()
{
Route::get( '/articles', 'ArticlesController@index' );
Route::get( '/articles/{id}', 'ArticlesController@show' );
Route::get( '/articles/{slug}', 'ArticlesController@showBySlug' );
} );
} );
So I have a very basic route setup here to be protected and tied to the articles.read scope.
I logged in and created an access token with this scope, and then I pass it to the route via an Authorization header in Postman. Doesn't work.
However, if I pass it as POST data with "access_token" it's working and it properly authenticates the request.
Is there something I'm doing incorrectly?
I'd like to be able to respond to the OPTIONS
and HEAD
verbs. This could probably be handled automatically, since this package already knows all the details needed to respond to those requests.
An OPTIONS
request to a given endpoint should respond with something like this:
HTTP/1.1 200 OK
Host: localhost:8000
Connection: close
Allow: GET,HEAD,POST
Where Allow:
lists the other HTTP verbs that are valid for that endpoint.
And a HEAD
request should be the same as a GET
request, but should return just the headers, without the response body.
I had a go at doing it myself but I'm fairly new to Laravel and I don't think I have the understanding of the internals of the framework to do it well.
Hi All,
Is there a current 'correct way' (in a controller) to determine if a call came in via Dingo or not? The current scenario is there is an API and standard Laravel routes served from the same server and I would like the under specific situations to return more relationships (or less) via specific API calls.
Hi,
I could be wrong but it seems that collections responses are not transformed by my Transformer. It do work for single Model returns :
Here is my code
API::transform('User', 'UserTransformer');
Route::api(['version' => 'v1','prefix'=>'api', 'protected' => true], function()
{
Route::get('me', function(){
return API::user();
});
Route::get('users', function()
{
return User::all();
});
Route::resource('room-types', 'Admin\Controllers\Api\RoomTypesController');
});
returns for /api/me is correct (name parameter is correct) :
{
"data": {
"id": 3,
"name": "Julien Moreau",
"email": "[email protected]",
"activated": true
}
}
meanwhile return for /api/users is not correct
{
"users": [
{
"id": 1,
"email": "[email protected]",
"last_login": "2014-04-30 14:28:03",
"first_name": "Admin",
"last_name": null,
"updated_at": "2014-04-30 14:28:03"
},
{
"id": 2,
"email": "[email protected]",
"last_login": null,
"first_name": null,
"last_name": null,
"updated_at": "2014-04-30 14:24:00"
},
{
"id": 3,
"email": "[email protected]",
"last_login": "2014-05-01 08:02:54",
"first_name": "Julien",
"last_name": "Moreau",
"updated_at": "2014-05-01 08:02:54"
}
]
}
The "testTransformingCollectionUsingTransformerClassName" test from "TransformerTest.php" seems to ensure that it works.
Other question, why is the key param for single models "data" and not "user" as it should be?
Not sure whether this is an issue, intended behaviour, an issue with Postman or my error.
I'm logged into my site as user 1 in Chrome.
Using Postman if I fetch /users/me with the Authorization for user 2 and then call $this->auth->users in the controller, it returns user 1. If I call ResourceServer::getOwnerId() (I'm using the League OAuth provider) it returns user 2. If I logout of the website, $this->auth->users then returns user 2. So it looks (looks) as if the logged in session is overriding Shield and $this->auth.
I'm not sure in practice whether this is an issue but thought I'd mention it.
Perhaps a config setting to disable the use of Fractal or at least a check inside the morph method (Response) or check inside transformableResponse method for example to see if getTransformers returns empty (to skip the rest of the transform actions.
I'm playing with Fractal for example but not transforming Eloquent models because the data being used can come from different sources, so I sort of normalise the result. Then I can't use the Dingo API::transform setters properly. I tried to hack some more generic setter but didn't like it so far (so for I sent already transformed results as response).
You know what I mean? (sorry bit in a hurry but can elaborate)
Sometimes it is needed to output a raw response (a string). For example for jsonp or sort of.
Is there a way to do this?
I'm trying to get transformers working and I keep getting an error exception that the delimiter is empty. I've tracked this down to the registration of the transformer in the service provider and when I dump the $app['config']['api::embeds']
I get an empty array instead of what's in the config file. (I did a fresh config:publish
just to be sure.) Any reason why the service provider wouldn't be able to grab those config values?
Okay so i have installed the package and all that jazz, looks like its working fine. Problem i have is thusly...
Route::api(['version' => 'v1', 'prefix' => 'api'], function () {
Route::get('test', function () {
return ['data' => ['a','b','c']];
});
});
So thats supposed to create a route on api/test that will return some json correct?
So anyway, i run 'php artisan routes' only to not see that route atall?
$ php artisan routes
+--------+-------------------------+----------------------+---------+----------------+---------------+
| Domain | URI | Name | Action | Before Filters | After Filters |
+--------+-------------------------+----------------------+---------+----------------+---------------+
| | GET|HEAD _debugbar/open | debugbar.openhandler | Closure | | |
+--------+-------------------------+----------------------+---------+----------------+---------------+
Am i doing something wrong or what? Any help is greatly appreciated... currently using laravel 4.1.25
I don't make a pull request because this enhancement should be thought over, but I think that this have sense to be implemented.
Here is my "peace of code" from controller, that normalizes Paginator object to json api format:
if ($response instanceof \Illuminate\Pagination\Paginator) {
$items = $response->getCollection();
$itemsKey = 'items';
$return = [];
if ($items->count() > 0 && ($firstItem = $items->get(0)) instanceof \Illuminate\Database\Eloquent\Model) {
$itemsKey = $firstItem->getTable();
}
$meta = [
'total' => $response->getTotal(),
'per_page' => $response->getPerPage(),
'current_page' => $response->getCurrentPage(),
'last_page' => $response->getLastPage(),
'from' => $response->getFrom(),
'to' => $response->getTo()
];
if ($items->count() > 0) {
$return[$itemsKey] = $items;
}
$return['meta'] = $meta;
return $return;
}
This will result in output:
{"users": [
{
"_id": "535fa16f6e6c631f936cb2a1",
"name": "Foo"
},
{
"_id": "535fa16f6e6c631f936cb2a2",
"name": "Bar"
}
], "meta": {
"total": 2,
"per_page": 10,
"current_page": 1,
"last_page": 1,
"from": 1,
"to": 2
}}
What do you think about implementation of this?
I currently have the backend API built using Teepluss' Restable package. I am now at the point of development that I need a package to make internal calls with. It looks like dingo can only be used for internal requests if the response is also coming from dingo, am I correct in thinking that?
Now the api wont return an array response... I just did a DD under the $class = and it prints out my array that was returned in the response.
protected function hasTransformer($class)
{
$class = is_object($class) ? get_class($class) : $class;
return isset($this->transformers[$class]);
}
When using embeds, straight from the Fractal docs :
class ResortTransformer extends \League\Fractal\TransformerAbstract
{
/**
* Embed if requested
*
* @var array
*/
protected $availableEmbeds = ['amenities'];
/**
* Embed amenities on the resort
*
* @return Collection
*/
public function embedAmenities(Resort $resort)
{
return $this->collection($resort->amenities, new AmenityTransformer);
}
}
Instead of getting this:
{
"name": "something",
"amenities": [
{
"an": "amenity"
},
{
"another": "amenity"
},
]
}
I'm getting this:
{
"name": "something",
"amenities": {
"data": [
{
"an": "amenity"
},
{
"another": "amenity"
},
]
}
}
Is this expected behavior ? And if yes is there a workaround? Or is it a Fractal issue?
If you provide invalid credentials it should 401, not simply fall back to unauthorized user and try to use up the rate limit for other people.
This is essentially a silent fail, and could lead to all sorts of crazy problems.
etc.
I'll try and have a look but I might not get around to it as I already have about 5-6 outstanding PRs to write for 3 different projects. If anyone can dive in that would be awesome.
Hey Jason,
If I wanted to :
how would you go about this?
Implement rate limiting.
I seem to be running into a problem of which I dont know if I'm doing something wrong or it is some sort of restriction.
I am currently testing your API to see if it fulfills the requirements for a project I will be working on. For this project I would like version numbers that use 3 digits (length), for example: 0.0.1, 1.0.3 etc.
When I create the API groups with simple digits v1, v2, v3 it works fine, but when I start to use v1.1.0 (also tried only 2 digits) or v110 it will always default back to v1.
So this works:
Route::api(['version' => 'v1', 'namespace' => 'v001'], function()
{ ... });
Route::api(['version' => 'v2', 'namespace' => 'v002'], function()
{ ... });
Route::api(['version' => 'v3', 'namespace' => 'v003'], function()
{ ... });
But when I change it to:
Route::api(['version' => 'v1', 'namespace' => 'v001'], function()
{ ... });
// v11 or v1.1 or v1.1.0 tried several combinations
Route::api(['version' => 'v11', 'namespace' => 'v011'], function()
{ ... });
Route::api(['version' => 'v3', 'namespace' => 'v003'], function()
{ ... });
It will always default back to v1 as if the version is not properly translated.
I used Postman to test the endpoints with the following Accept header:
application/vnd.test.v1.1+json
// or
application/vnd.test.v11+json
// or
application/vnd.test.v1.1.0+json
'test' is configured in the config.php
Could you tell me what I am doing wrong? If anything is unclear, please let me know!
Hi.
Why user instance is not corresponding with laravel user?
I mean, why when I protect the route, I get 401 even if user is logged in in laravel?
I think that API auth should use Laravel Auth and protected routes should utilize the Auth::check()
What do you think about this?
I have discovered that if you authenticate via OAuth using the client_credentials flow instead of the password flow, Shield basically explodes because it tries to do something like User::findOrFail('oauth_client_id')
, which if you're using the Dingo\Controllers, means bad business.
Not sure if this is intended or not, but thought I would bring it to your attention.
I'm add my own format in config.
How i can dynamically change format of response?
Hey, brand new to the package... and I cant get it to return a view file... the controller even extends the standard Laravel Controller... but as soon as I implement the API Classes, it automatically hijacks the response and returns as json. Any clues on this? I was under the impression that only controllers that extended the Api Controller would follow that logic.
Sorry for stupid question...
Can I implement dingo in my default Laravel app, with default routing etc..?
When I install dingo, I got error Route has no method "api".
I changed my app.php file, add Provider and Alias, but dont replace controller.
Sorry for bad english
I think we can't fork the wiki to send pr's, am I correct?
https://github.com/dingo/api/wiki/Internal-Requests
Here it says: Tthe api facade [...]
https://github.com/dingo/api/wiki/Returning-Errors
Here it says under Custom Exceptions:
[...]
return new Response($response, 401);
I think this should be:
return new Dingo\Api\Http\Response($response, 401);
yay?
Cheers
Using Angular to create a SPA, it works fine as long as my API isn't responding with an error. Take for example this code:
try {
return User::findOrFail($user_id);
} catch (ModelNotFoundException $e) {
throw new ResourceException("No user record found.");
}
If the user is found, Angular successfully retrieves the data. However if the record does not exist then this error is thrown.
XMLHttpRequest cannot load http://api.example.com/users/.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://my-angular-site.com' is therefore not allowed access.
Successful requests have these headers:
Access-Control-Allow-Origin →*
Cache-Control →no-cache, private
Connection →keep-alive
Content-Type →application/json
Server →nginx
Transfer-Encoding →chunked
X-Frame-Options →SAMEORIGIN
date →Fri, 23 May 2014 13:27:26 GMT
Responses provided by throwing an error have these headers:
Cache-Control →no-cache, private
Connection →keep-alive
Content-Type →application/json
Server →nginx
Transfer-Encoding →chunked
X-Frame-Options →SAMEORIGIN
date →Fri, 23 May 2014 13:31:29 GMT
If I use the following code then returning the errors works.
try {
return User::findOrFail($user_id);
} catch (ModelNotFoundException $e) {
header('Access-Control-Allow-Origin: *');
throw new ResourceException("No user record found.");
}
Are there any issues with the auth class if using the cartalyst/sentry package for users? I had to override the constructor to place the user into the auth shield to make that work.
I use filters in my query string, how is it best to put these on the pagination instance? Should I set it in my controller on the paginator or?
Thanks,
I had an issue where it looked like Laravel was throwing a NotFoundHttpException exception and Dingo was returning a JSON document like:
{
"message": "404 Not Found"
}
When accessing API routes over HTTPS. I took the default code from the Dingo documentation (and excluded the Post route as I didn't have that in my application):
Route::api(['version' => 'v1'], function()
{
Route::get('users', function()
{
return User::all();
});
});
and when I was calling https://myproject/users I was getting the above error.
What is somewhat more confusing is when I reconfigured the route to:
Route::group([ 'version' => 'v1' ], function() {
{
Route::get('users', function()
{
return User::all();
});
});
I didn't get any error. I did some more investigation and I found a similar issue on the Laravel IO forums:
http://laravel.io/forum/03-21-2014-https-definition-in-routesphp
this forum post indicated someone had had similar issues when accessing standard routes over HTTPS. My use case was slightly different as I was using Apache 2.2 rather than Nginx but when I followed their instructions and adjusted the Laravel Illuminate\Routing\Route::httpOnly method I could access my route via Route::api and standard Route::groups.
I'm really not sure if this is a Dingo or a Laravel issue.
Any suggestions would be appreciated.
The Input::file functions are not being parsed and always return as null values. The functions work as expected when outside of the Dingo API route but always return as null when inside it.
Hi
Just an idea: transformers currently have 2 ways to be injected:
API::transform
callDingo\Api\Transformer\TransformableInterface
in the modelIn my opinion the second option is more clean as I this seems to me like some sort of model definition similar to getAttribute()
.
However, how about defining the implementation as a trait? This avoids collision when a model needs multiple interfaces. (Actually I think packages never should define interfaces for models just for this reason)
Some code suggestions:
Change Dingo\API\Transformer\TransformableInterface
to Dingo\API\Transformer\TransformableTrait
:
trait TransformableTrait {
/**
* Get the transformer instance.
* Should be overridden in the implementing class.
*
* @return mixed
*/
public function getTransformer() {
return array();
}
}
In Dingo\API\Transformer\Factory
:
/**
* Determine if the class is bound by the transformable contract.
*
* @param string|object $class
* @return bool
*/
protected function boundByContract($class)
{
return is_object($class) and in_array('TransformableTrait', class_uses($class));
}
I'm using repositories to share logic between the API and the application, when I query for a model and it doesn't exist it uses findOrFail
which throws a Illuminate\Database\Eloquent\ModelNotFoundException
. The problem is, at its core this extension extends the core's RuntimeException
instead of a Symfony HTTP Exception so it's not caught by the API's exception handler. How would I go about to catch it globally without wrapping every single one of my methods in a try catch ?
I saw an API::error
method but I'm unsure as to how it's meant to be used, would that be it ?
When table is empty
Route::api(['version' => 'v1', 'prefix' => 'api'], function()
{
Route::get('users', function()
{
return User::all();
});
});
Results in:
Call to a member function getTable() on a non-object
/vendor/dingo/api/src/Http/Response.php:97
PS
What is the sense in setting a key in plural of table name? It is not the most convenient thing... It would more simple and transparent if collections would be wrapped in simple data
object.. This also would be compatible with laravel paginator
Hey Jason, i just did a composer update, and it broke the app.
Class Dingo\Api\Authentication does not exist
Currently to transform your resources you need to declare each one of them via API::transform
– could an alternative solution based on an interface be used, like @robclancy does with his presenters.
That would allow to implement the interface on a abstract or a trait per example to set the transformers in a more fluid manner.
I'd like to hear some feedback and discuss how the JSON should be formatted. I'd like to follow the JSON API spec.
There's a few things I'm not to sure about it.
Documents that represent singular resources are wrapped inside an array and keyed by the plural form of the resource type.
I don't particulary like this. Singular resources (IMO) should be keyed by the singular form of the resource type and not wrapped inside an array. The spec recommends this:
{
"posts": [{
"id": 1
// an individual post document
}]
}
I, personally, prefer this:
{
"post": {
"id": 1
// an individual post document
}
}
I'm thinking of allowing the way responses are handled configurable, so you can easily write your own adapter.
I faced a problem that after php artisan optimize
responses are breaking.
Details:
config/app
'providers' => array(
...
'Dingo\Api\ApiServiceProvider',
)
'aliases' => array(
...
'API' => 'Dingo\Api\Facades\API',
)
base class
use Dingo\Api\Routing\Controller;
abstract class ResourceControllerAbstract extends Controller {
public function index()
{
$args = $this->checkArgs(func_get_args(), $this->resources->count() - 1);
$data = $this->getResource($args)->paginate($this->listsPerPage);
return $data;
}
}
Now, if I run composer dump-autoload
— everything works like a charm.
But if I run php artisan optimize
...
use Dingo\Api\Routing\Controller;
abstract class ResourceControllerAbstract extends Controller {
public function index()
{
$args = $this->checkArgs(func_get_args(), $this->resources->count() - 1);
$data = $this->getResource($args)->paginate($this->listsPerPage);
dd($data); // object(Illuminate\Pagination\Paginator)
return $data;
}
}
abstract class ApiControllerAbstract extends ResourceControllerAbstract {
public function index()
{
try {
$response = call_user_func_array(array('parent', 'index'), func_get_args());
dd($response); // and here comes the magic! see below
if ($response instanceof \Illuminate\Pagination\Paginator) {
return ApiResponseFormatter::format($response);
} else {
return $response;
}
} catch (\Exception $e) {
throw new NotFoundHttpException;
}
}
}
dd result
object(Illuminate\Http\Response)[306]
public 'original' => string '{"total":2,"per_page":10,"current_page":1,"last_page":1,"from":1,"to":2,"data":[{"id":"535fb8d06e6c631f936cb2a3","name":"foo"},{"id":"535fb8d06e6c631f936cb2a4","name":"bar"}]}' (length=175)
...
protected 'content' => string '{"total":2,"per_page":10,"current_page":1,"last_page":1,"from":1,"to":2,"data":[{"id":"535fb8d06e6c631f936cb2a3","name":"foo"},{"id":"535fb8d06e6c631f936cb2a4","name":"bar"}]}' (length=175)
protected 'version' => string '1.0' (length=3)
protected 'statusCode' => int 200
protected 'statusText' => string 'OK' (length=2)
protected 'charset' => null
It is a response with a string!
But if i again run composer dump-autoload
— I get here an instanse of object(Illuminate\Pagination\Paginator)
as it supposed to be and everything works ok.
When doing simply this:
Route::get('users/{user}', function($user) {
return User::find($user);
});
I get this:
{
"user": {
}
}
However if I use a Transformer, it changes and I can't seem to find the option to change it back:
API::transform('Users' 'UserTransformer');
{
"data": {
}
}
Any way to achieve this?
I'm implementing a an API where I'll only need the client_credential
grant type. I've got it basically working, except I don't have laravel Auth setup, as I don't need it – I'm basically just using the client_id
and client_secret
to scope the request to the correct company in the system.
Am I mistaken that Auth needs to be up and running, or is there a way to make it optional?
I have got a temporary implementation for jsonp support.
Personally, i think it should fallback to a standard json response if the "callback" parameter is not supplied
https://gist.github.com/arnold-almeida/2f656202153b3e161bcb
If thats cool ill submit a PR.
Named routes doesn't seem works with the one defined in the Route::api() block. Is that the expected behavior ?
After our testing last night I had the following defined in my routes.php file:
Route::group([ 'version' => 'v1', 'prefix' => 'api' ], function() {
Route::any('foo', function () {
return array("Hello" => "World");
});
});
When I did a composer update to pull in the latest version of Dingo I got a RuntimeException thrown from dingo/api/src/Routing/Router.php line 417:
There is no API collection for the version "v1"
Once I removed the Route::group composer would update correctly. Should Dingo ignore any parameters passed to a Route that isn't Route::api ?
I love the include query string parameters but how can I do the same for columns?
Example:
?include=item,item.sub,item.othersub&columns=item.othersub.business_name&item.sub.contact_name
Is there a efficient way of doing this? My colleague is pestering me.
If returning Paginator with no items it bypasses the transformer and raw paginator is outputed
Not sure if I'm doing something wrong, but if I try to use a Route::resource()
, the __constructor()
method is called lots of times.
Code:
Route::api(['version' => 'v1', 'prefix' => 'api'], function()
{
Route::resource('user', 'App\Modules\User\Controllers\UserController');
});
use Dingo\Api\Dispatcher;
use Dingo\Api\Auth\Shield;
use Dingo\Api\Routing\Controller;
class UserController extends Controller {
public function __construct(Dispatcher $api, Shield $auth)
{
$this->api = $api;
$this->auth = $auth;
echo 'test';
}
//...
}
In this case 'test' is called exactly 9 times. Is it ok to use resource controllers?
For the delete requests of my API, I like to return 204 (no content) when it succeeds. Usually I would do Response::make('', 204);
, and that would work fine. But it seems that when the router goes to morph the response, it turns empty string into a null, which then gets json_encoded to "null"
, and winds up giving a Malformed JSON error for anything consuming the response.
The request still works, but I'd like to remove the Malformed JSON error. Anyone else ran into this and find a work around?
When I get time after work, I'll look into finding a nice way to check for empty responses, and send in a PR if necessary
It's not clear from the Wiki page on OAuth 2.0 or from the code itself if you support league/oauth2-server directly, or only via the lucadegasperi/oauth2-server-laravel
package. While the latter is great, the dependency is on 2.1 while 3.x is out and 4.0 is nearing.
If it is not yet decided, I would suggest that supporting league/oauth2-server
might help avoid some dependency problems in the future.
If it is decided, could the documentation and composer.json suggests section match up? And, could you add an example?
I'm going to try and get this going myself, but I dont want to be wasting my time chasing things that arent even close to working.
How can I implement easily in this package link headers for pagination objects?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.