I have just started tinkering with Laravel (PHP newbie alert) and I have a doubt on how to implement my REST JSON APIs on this framework.
The desired URL path should be:
http://api.mysite.com/APIKEY/FUNCTION/(ARGUMENTS)*
But what is the best way to do it?
I did not find any enough explanatory guide, because it is assumed that each feature (authentication, search, and so on) are on different routes.
At the moment I managed to create migrations and models for the needed data and I also set a route this way:
Route::get('/{apikey}/{function}/{arg1}/{arg2}/{arg3?}',
//The first two arguments are mandatory, the 3rd optional
function($apikey,$function,$arg1,$arg2)
{
return Response::json(array(
'status'=>'200'),
200);
})
->where(array('function'=>'[A-Za-z]+'));
This should be the correct action flow, but I have some doubts.
Check that apikey is valid, if not return a 404 json response. How do I call such function, where do I define it?
If key check is successful, understand which function is needed (should I use a switch construct or is there a better way to implement this, like a route group? In all examples with Route::group there is a static prefix, but here the prefix is apikey, a variable)
Return data - if available - getting it from the database. I suppose that for each function i should code a specific controller that gets data from the database using the Models.
#1. I think I'd probably use a route filter for this, like so:
Route::filter('apikey', function(){
if (Shared\API::checkIfKeyIsValid(Input::get('key') != true){
return Shared\Errors::handleError("bad-api-key");
}
});
You can see this filter checks to make sure some session variables are set and match, and if it returns ANYTHING, that's failing, and it won't send the user where the route normally goes. You'd call it in the route like so:
Route::get('play/{id}', array('before' => 'loggedin', 'uses' => 'ThingController#doThing'));
#2. I think a route group is how I'd structure this (if I understand what you're asking).
Edit: You've changed the question since I answered, regarding the prefix being a variable. I'd reorder your arguments so the API key is at the end, or you could take a look at this, which might do what you want: https://github.com/jasonlewis/enhanced-router
Route::group(array('prefix' => 'api'), function()
{
//the routes for all API calls
Route::group(array('prefix' => '/v1'), function()
{
//for version 1
Route::group(array('prefix' => '/thing', 'before' => 'loggedin'), function()
{
//for things only
Route::get('/getThing/{id}', 'APIController#getThing');
Route::get('/getDifferentThing/{id}/{aux}/{optional?}', 'APIController#getDifferentThing');
});
});
});
#3. The returning of the data should be done via your controller with the data coming from the model. Either return it as a view, or just return it as JSON like so:
return Response::json($data);
Related
I have a middleware that detects if a user owns a tournament.
So, if user want to edit a tournament he doesn't own, he will get a 403.
Thing is I can't make difference between laravel.dev/tournament/1/edit, and laravel.devl/tournament/1
Off course, I could check the "edit" word in URL, but I would prefer other better param...
I tried method param in Request Object, but it is giving me GET for both, so I can't make difference...
Any Idea???
In your case, you can do like this:
$request->route()->getName();
Now you can do your logic based on this.
What about using a different HTTP method for edit, e.g PATCH or PUT and declaring two different routes, something like:
Route::get('laravel.devl/tournament/1', 'TournamentController#showTournament');
Route::put('laravel.dev/tournament/1/edit', 'TournamentController#editTournament');
Then in the TournamentController you can check if the user has rights to edit.
It sounds like you should just use route specific middleware instead of global middleware. See https://laravel.com/docs/master/middleware#assigning-middleware-to-routes. Then you can just do:
Route::get('/tournament/{id}/edit', ['middleware' => ['tournamentOwner'], function () {
//
}]);
Route::get('/tournament/{id}', ['middleware' => [], function () {
//
}]);
If it's a RESTful route, you can just do:
public function __construct()
{
$this->middleware('tournamentOwner', ['only' => ['edit']]);
}
I'm using Laravel 5.1 and am building a service that can be seen as JSON or HTML. This approach is already done by sites like reddit.
Example
Normal view: http://www.reddit.com/r/soccer
JSON view: http://www.reddit.com/r/soccer.json
As you can see, they simply add .json to an URL and the user is able to see the exact same content either as HTML or as JSON.
I now wanted to reproduce the same in Laravel, however I'm having multiple issues.
Approach 1 - Optional parameter
The first thing I tried was adding optional parameter to all my routes
Route::get('/{type?}', 'HomeController#index');
Route::get('pages/{type?}', 'PageController#index');
However, the problem I was facing here, is that all routes were caught by the HomeController, meaning /pages/?type=json as well as /pages?type=json were redirected to the HomeController.
Approach 2 - Route Grouping with Namespaces
Next I tried to add route groupings with namespaces, to seperate backend and frontend
Route::get('pages', 'PageController#index');
Route::group(['prefix' => 'json', 'namespace' => 'Backend'], function(){
Route::get('pages', 'PageController#index');
});
However, this doesn't work either. It does work, when using api as prefix, but what I want, is that I can add .json to every URL and get the results as json. How can I achieve that in Laravel?
You can apply regular expressions on your parameters to avoud such catch-all situation as you have for HomeController#index:
Route::get('/pages{type?}', 'PageController#index'->where('type', '\.json'));
This way it type will only match, if it is equal to .json.
Then, to access it in your controller:
class PageController {
public function index($type = null) {
dd($type);
}
}
and go to /pages.json
So we have a load of content within the database, let's call these Articles. The paths for the Articles start from the application root and can contain slashes. So we want to search the DB to see if we match an Article, and if not skip the route and give other routes the opportunity to respond.
In Sinatra (which I believe has inspired the routing within Laravel) you have the option to pass to the next route. It might be this that's leading me astray.
Route::get( '{uri}', function( URI $uri ) {
// check database, if we find a record we'll need to pass it to the appropriate controller
// we'll have a class that handles this responsiblity
// pass if we don't find anything
} )->where('uri', '.*');
Route::get( 'other/page', 'OtherController#sayHello' );
The issue Allow skip the route based on conditions #1899 talks about this, although I can't see how filters cater for this, they will simply intercept and give you an opportunity to stop route execution (throw exception, redirect to route specifically etc.), you can't return FALSE; without error. There is an example chaining a condition method to a route, although this method doesn't seem to exist (in 4.2).
Any ideas?
In addition to this, we're also are thinking about containing this logic within a package so it can be shared across applications, and wonder if you can influence the order of execution of routes provided by package?
You can have a group of routes, which you contain within all the routes that match it. If it fails that group then it skips to the next section.
Route::group(array('before' => 'auth'), function()
{
Route::get('/', function()
{
// Has Auth Filter
});
Route::get('user/profile', function()
{
// Has Auth Filter
});
});
http://laravel.com/docs/4.2/routing
In a Laravel 4 app, i'm using subdomain routing around a bunch of Route::resource's like this:
Route::group(['domain' => '{account}.my.app'], function()
{
Route::group(['before' => 'auth'], function($account)
{
Route::resource('organisations', 'OrganisationsController');
Route::resource('clients', 'ClientsController');
Route::resource('domains', 'DomainsController');
});
});
In my auth filter i'm doing the following:
Route::filter('auth', function($route)
{
// you could now access $account in a controller if it was passed as an argument to the method
$account = $route->getParameter('account');
// share account variable with all views
View::share('account', $account);
// Auth::guest Returns true if the current user is not logged in (a guest).
if (Auth::guest()) return Redirect::guest('login');
});
Within my views I can now access $account, but if I want a call to URL::route() to be correct I have to manually pass the account variable, like URL::route('clients.show',['account' => $account]) otherwise it generates URLs like %7Baccount%7D.my.app.
This is a bit of a pain and doesn't seem that elegant, is there any other or better way to achieve this? I guess I could create my own route helper to use instead of the built-in one.
However, I also do redirects with Redirect::route() within controllers so I would also need to make updates here.
EDIT
As suggested in the comments it may be that extending the Route API is the best approach here. Does anyone have any suggestions how this should be done?
Thanks.
To force HTTPS on a named route, the Laravel docs say do the following:
Route::get('foo', array('https', function()
{
return 'Must be over HTTPS';
}));
Now, on my first Laravel app, I have been using resource controllers. I don't think I will be using them for my second app, going on what I have since read, but for now they sit happily in my router.php file.
I wanted to force the back office part of my app to use HTTPS. So, my opening gambit was as follows:
Route::resource('backoffice', array('https','BackofficeController'));
Laravel didn't like the array.
So, instead I thought I would try putting at the next parameter:
Route::resource('backoffice', 'BackofficeController', 'https'));
But the next parameter needs to be an array. I could find no documentation on this, but I converted it to array. It still didn't work.
Route::resource('backoffice', 'BackofficeController', array('https')));
I even tried:
Route::resource('backoffice', 'BackofficeController', array('https'=>true)));
However, that failed too. So, how do I force a resource to use https?
Route::filter('forceHttps', function($req){
if (! Request::secure()) {
return Redirect::secure(Request::getRequestUri());
}
});
Route::group(['before' => 'forceHttps'], function(){
Route::resource('backoffice', 'BackofficeController');
});
Assuming you have a filter function like the one Andreyco suggested, which seems fine, you could do something similar to this:
//Andreyco's filter
Route::filter('forceHttps', function($req){
if (! Request::secure()) {
return Redirect::secure(Request::getRequestUri());
}
});
//backoffice group routing
Route::group(array('prefix' => 'backoffice', 'before' => 'forceHttps'), function()
{
Route::any('/', 'App\Controllers\BOindexController#index');
Route::resource('otherBackOfficeURI', 'App\Controllers\OtherBOController');
//other routes & controllers here...
});
This way, everything starting with site.tld/backoffice will go through the https filter (and most probably through a isAdmin filter) and then check inner function route rules. I think this will be more convenient.