Laravel 4 RESTful API - resource controller - deeper request hierarchy - php

I'm building my frontend part of the API system.
Using default Resource Controller's by Laravel you can achieve this type of requests:
http://superapi.com/v1/soccer/10
Which will call the:
/app/controllers/SoccerController.php # show($id) method
I need at least one more level of depth, to be able to do it like this:
http://superapi.com/v1/soccer/player/10
So it will resolve to:
/app/controllers/SoccerController.php # show() method having both, "player" and "10" arguments.
public function show($model)
{
return Response::json(array('success' => true, 'message' => 'Test ' . $model));
}
Laravel passes only 1 parameter to method, and I cannot find anything about how to pass more.
I'm doing this basically because my 1 controller is responsible for talking to a few Models, not just one. Either SoccerPlayer or SoccerTeam or SoccerSchedule, it all should be nicely put under
/v1/<controller>/<type>/<id>
Advices?
Thanks!

You can have the best of both worlds anyway, by placing a custom route before the resource route:
Route::get('soccer', 'SoccerController#show');
Route::resource('soccer', 'SoccerController', array('except' => array('show')));
Now the show() method will be escluded from the resource controller control (thus calling soccer/10 or soccer/player will lead to a NotFoundHttpException), and placed into your custom route.
You'll need to edit the show() method to accept the second parameter though:
public function show($type, $id)
{
}

you can use route prefixing
Route::group(array('prefix' => 'soccer'), function()
{
Route::resource('player', 'PlayerController');
});
then, you can put all soccer relavant controllers in the same namespace (and same folder) like Soccer. in this case, change the above route to
Route::resource('player', 'Soccer\PlayerController');

Related

Laravel 4 Resource perform action after default store

I have taken over a project which uses Laravel 4, and the previous developer created a RESTful resource in the routes file like so:
Route::resource('home/information', 'HomeInformationController',
array('except' => array('create', 'edit')));
Now, when I look in HomeInformationController.php I see some other methods the user created, but I do not see a store method, but when I make a post request to the resource, I get a successful 201 CREATED response. My question is, is there a way to perform another action without disturbing what it is already doing by default? I know I could implement a store method myself and do it that way, but I was wondering if there was a way to keep it the way it is and simply perform a default action every time a record is created.
Also, How is laravel even creating the record, since there is no store method? Does laravel create one by default when you create a resource? And how does it know which model use? There is indeed a model called HomeInformation.php. I have searched Laravel documentation but have not found anything.
Thank you.
of course You can.
let's say You know the route:
Route::resource('home/information', 'HomeInformationController',
array('except' => array('create', 'edit')));
and You know that there are methods (in HomeInformationController) of REST excepting create and edit.
so You can create custom routes and use same controller or another controller.
for example:
Route::get('some/custom/route', array('uses' => 'HomeInformationController#existingMethodOrCustomMethod'));
Route::post('some/custom/route/that/handles/post', array('uses' => 'HomeInformationController#existingMethodOrCustomMethod2'));
and add to Your controller:
public function existingMethodOrCustomMethod(){/*implementation here*/}
public function existingMethodOrCustomMethod2(){/*implementation here*/}
with arguments:
Route::get('some/custom/route/{id}', array('uses' => 'HomeInformationController#existingMethodOrCustomMethod'));
Route::post('some/custom/route/that/handles/post/{id}', array('uses' => 'HomeInformationController#existingMethodOrCustomMethod2'));
and add to Your controller:
public function existingMethodOrCustomMethod($id){/*implementation here*/}
public function existingMethodOrCustomMethod2($id){/*implementation here*/}
read about routing here: http://laravel.com/docs/4.2/routing

How can I use "where" in a Laravel controller

If I have the next route:
Route::get('/user/{id}', function($id) {
return View::make(users.profile, array('id' => $id));
})->where(array('id' => '[0-9]+'));`
How could I do the same in a Restful controller?
Route::controller('/user', 'UserController');
My UserController:
class UserController extends BaseController {
public function getProfile($id) {
return View::make('users.profile', array('id' => $id));
}
}
Thanks for your attention.
where doesn't appear to work when chained onto a Route::controller call, but you can achieve the same functionality with the Route::pattern declaration. So, for example, this code for a Route::controller (called "implicit routing") would work, limiting id to numeric:
Route::pattern('id', '\d+');
Route::controller('/user/{id}', 'UserController');
Then, in UserController, the getIndex method would be called from a GET request:
class UserController extends BaseController {
public function getIndex($id) {
return View::make('users.profile', array('id' => $id));
}
}
Note, however, that this only works for the index method, i.e. for calls to http:://example.com/user/99. If you want to use other controller methods using "implicit routing", for example http:://example.com/user/profile/99 and the controller method getProfile($id), you need to declare your route without the {id} parameter, like so:
Route::controller('/user', 'UserController');
...in which case, you aren't able to use ->where or Route::pattern to constrain the {id}, since there is no {id} parameter to constrain.
In the end, you're better off going with "explicit routing," as you do at the beginning of your answer, or using RESTful Resource Controllers (see the docs) and specifying your route as:
Route::resource('user', 'UserController');.
If you subscribe to Laracasts, Jeffrey Way has a great, clear tutorial about some of the perils of "implicit routing" here.
To make sure about the methods and URL run the php artisan routes from your terminal so you'll get the list of all routes you have access to with their URL. In this case for the following route and controller you may find a URL like user/profile/10
// Route
Route::controller('/user', 'UserController');
// Controller
class UserController extends BaseController {
public function getProfile($id) {
return View::make('users.profile', array('id' => $id));
}
}
So use http://domain.com/user/profile/10, here 10 will be passed to the $id variable in your profile method. Also remember that, in RESTfull controller each method should be prefixed with the HTTP verb they responds to so in this case this method will respond to a GET request.
In order to do it you want to wrap your Route::controller statements in a group and apply where pattern for the group, since setting it globally might not be accurate for other routes:
Route::group('where' => ['id' => '\d+'], function () {
Route::controller('users', 'UsersController');
// other restful controller definitions with this pattern go here
}

Laravel beforeFilter in controller on POST request throws non-object exception

I try to set some specific filter on all controller methods with:
public function __construct() {
$this->beforeFilter(function(){
//whathever
});
}
and it's working well on normal GET methods, problem occures when there is some POST method:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => function($direction) {
$controller = new CMSSettingsController();
return $controller->doMoveMenu($direction);
}));
after click in a button which send POST with $direction, I'v got
Call to a member function filter() on a non-object
in vendor/laravel/framework/src/Illuminate/Routing/Controller.php
protected function registerClosureFilter(Closure $filter)
{
$this->getFilterer()->filter($name = spl_object_hash($filter), $filter);
return $name;
}
If I use already registred filter it's working, so what's going on?
I have few controllers which need specific function todo before running controller methods, so I can't make global universal filter. Is there any other good solution?
The problem could be that you are calling the controller action directly instead of letting the Router do it for you. When the router tries to apply the filters, instead of applying them on the controller, it ends up attempting to apply them on the output of the doMoveMenu action - which, of course, is not a Controller object and has no method filter.
Instead, your route should look like this:
Route::post('settings/menu-order/{direction}', array(
'as' => 'setting.menu-order.move',
'uses' => 'CMSSettingsController#doMoveMenu'));
The reason you don't need to do the method call manually is that since your Route has a parameter in it and your method accepts a parameter, the Router will automatically pass the parameter into the action method. Additionally, since you are providing a method name as the uses value, Laravel knows that it has to instantiate the Controller and run the filters.

What does the 'uses' keyword mean in regards to laravel routing?

I'm new to laravel and trying my best to RTM but having difficulty understanding a few things. I think there is an expected level on context that I am not aware of when it comes to routing. In reviewing the documentation for routing I see that the uses keyword allows one to Attach(ing) A Filter To A Controller Action, but what does that mean? I have an existing site that is using the uses keyword but I am at a loss at to what it is actually doing. Can someone explain (a tab more thoroughly than the laravel documentation does) and show a very simple example what this actually does?
The keyword uses of routing is where you define which action (controller method or anonymous function) will be used to process that particular route. Take this controller method example:
Route::get('user', array('uses' => 'UserController#showProfile'));
It says that uses will call the method showProfile in your UserController class, this would be the class:
class UserController extends Controller {
public function showProfile
{
return "Hi! I'm the showProfile method!";
}
}
So, if you hit the
http://localhost/user
You should see the message
Hi! I'm the showProfile method!
Because your route execute that action you defined in the uses.
An anonymous function (closure) example would be:
Route::get('user', array('uses' => function() {
return "Hi, I'm a closure!";
}));
Antonio's answer answers your (exact) question pretty perfectly, but I figured I'd add my comments to the wider context of the question, as I feel you don't actually want to know what uses is for, but instead are generally confused by the way routing works (you mention uses is for filters, which it isn't, but its use is a side-effect of adding filter configuration). So allow me to explain:
The router methods are actually kinda simple, but you just need to know what syntax they expect. There are two main forms, the unconfigured and the configured:
Unconfigured:
// syntax:
Route::get($route, $action);
// controller method
Route::get('some/route', 'Controller#action');
// closure
Route::get('some/route', function () {
return View::make(/* etc. */);
});
But as soon as you need to add configuration to a given route (including filters, naming, etc.) you need to use a slightly different syntax:
// syntax:
Route::get($route, $config); // where $config is an array
// controller method
Route::get('some/route', array(
'as' => 'my.route.name',
'uses' => 'Controller#action',
));
// closure
Route::get('some/route', array(
'before' => 'some.filter',
'uses' => function () {
return View::make(/* etc. */);
}
));
// closure alternative
Route::get('some/route', array(
'before' => 'some.filter',
function () {
return View::make(/* etc. */);
}
));
So, as soon as you need to name your route (using as) or add a filter (using before and after) you need to turn that more simple syntax into the slightly more complex array syntax. And when you make it an array, you still need a way to tell Laravel what to actually use for the execution - that's what uses is for.

Laravel 4 authentication. Restrict access to some functions of a resource but not all

I have this blog resource which has the usual CRUD methods.(index, create, store, show, edit, update, destroy).
I have the following route in my routes.php:
Route::resource('blog', 'PostsController');
but I want to restrict all but index and show.
so I have
Route::get('blog', 'PostsController#index');
Route::group(array('before' => 'auth'), function()
{
Route::resource('blog', 'PostsController');
});
which is fine for index but I don't know how to route the show method ? Or is there another way? Instead of routing the resource should I route every URI individually and put the ones I want restricted in my restricted access route?
Cheers
Laravel has a feature that lets you specify filters in the controllers' __construct method using $this->beforeFilter. This function takes a second argument that lets your provide exceptions (or enable the filter only for certain methods). Try using your original routes file and set up your controller like this:
class PostsController extends BaseController {
function __construct() {
// ...
$this->beforeFilter('auth', array('except' => array('index', 'show')));
// ...
}
// ...
See Controller Filters in the Laravel documentation. It's not entirely well-documented, but you can also start a deeper journey into the guts of Laravel from here.
In Laravel 5 you use middleware function instead like this:
$this->middleware('auth', array('except' => array('index', 'show')));

Categories