Can route model binding be used with RESTful controllers? - php

I have been using RESTful controllers in my Laravel project. By including:
Route::controller('things', 'ThingController')
in my routes.php, I can define functions in the ThingController like:
public function getDisplay($id) {
$thing = Thing::find($id)
...
}
so that GETting the URL "...things/display/1" would automatically be directed to the controller function. This seems pretty handy and has been working great for me so far.
I noticed many of my controller functions start with getting a model by id from the url, and I thought it would be nice to be able to use route model binding to do this for me instead. So I updated my routes.php to
Route::model('thing', 'Thing');
Route::controller('things', 'ThingController')
and changed the ThingController functions to
public function getDisplay($thing) {
...
}
I assumed this would magically work the way I wanted it to (like everything else I've tried so far in Laravel has) but unfortunately I get "Trying to get property of non-object" when I attempt to use $thing in the function. Is this something that should be able to work and I have just done it wrong, or can route model binding only work with routes explicitly named in routes.php?

If you don't mind with URI path, method name and just work only show, edit and update method, you can use Resource Controller to generate URI string which can define model binding.
In routes.php change to
Route::model('things', 'Thing');
Route::resource('things', 'ThingController');
You can use php artisan routes command to see all URIs
$ artisan routes | grep ThingController
GET|HEAD things | things.index | ThingController#index
GET|HEAD things/create | things.create | ThingController#create
POST things | things.store | ThingController#store
GET|HEAD things/{things} | things.show | ThingController#show
GET|HEAD things/{things}/edit | things.edit | ThingController#edit
PUT things/{things} | things.update | ThingController#update
PATCH things/{things} | | ThingController#update
After that you can threat parameter as Thing object without explicitly name route.
/**
* Display the specified thing.
*
* #param Thing $thing
* #return mixed
*/
public function show(Thing $thing)
{
return $thing->toJson();
}
If you want to access ThingController#show, pass your model ID and Laravel will retrieve it automatically.
http://example.com/things/1
{"id":1,"type":"Yo!"}

You can use Route:resource and still provide other methods. Place the route you need just before that particular Route::resource line.
Eg:
Route::model('things', 'Thing');
Route::get('things/{things}/owner', 'ThingController#getOwner');
Route::resource('things', 'ThingController');
Then create the corresponding method in your controller.
public function getOwner($things) {
return Response::json($things->owner()->get());
}
Here is the official documentation from the Laravel 4.2 docs:
Source: http://laravel.com/docs/controllers#resource-controllers
Adding Additional Routes To Resource Controllers
If it becomes necessary for you to add additional routes to a resource controller beyond the default resource routes, you should define those routes before your call to Route::resource:
Route::get('photos/popular');
Route::resource('photos', 'PhotoController');

Related

Laravel Route Model Binding - Resource Controller [duplicate]

I have been using RESTful controllers in my Laravel project. By including:
Route::controller('things', 'ThingController')
in my routes.php, I can define functions in the ThingController like:
public function getDisplay($id) {
$thing = Thing::find($id)
...
}
so that GETting the URL "...things/display/1" would automatically be directed to the controller function. This seems pretty handy and has been working great for me so far.
I noticed many of my controller functions start with getting a model by id from the url, and I thought it would be nice to be able to use route model binding to do this for me instead. So I updated my routes.php to
Route::model('thing', 'Thing');
Route::controller('things', 'ThingController')
and changed the ThingController functions to
public function getDisplay($thing) {
...
}
I assumed this would magically work the way I wanted it to (like everything else I've tried so far in Laravel has) but unfortunately I get "Trying to get property of non-object" when I attempt to use $thing in the function. Is this something that should be able to work and I have just done it wrong, or can route model binding only work with routes explicitly named in routes.php?
If you don't mind with URI path, method name and just work only show, edit and update method, you can use Resource Controller to generate URI string which can define model binding.
In routes.php change to
Route::model('things', 'Thing');
Route::resource('things', 'ThingController');
You can use php artisan routes command to see all URIs
$ artisan routes | grep ThingController
GET|HEAD things | things.index | ThingController#index
GET|HEAD things/create | things.create | ThingController#create
POST things | things.store | ThingController#store
GET|HEAD things/{things} | things.show | ThingController#show
GET|HEAD things/{things}/edit | things.edit | ThingController#edit
PUT things/{things} | things.update | ThingController#update
PATCH things/{things} | | ThingController#update
After that you can threat parameter as Thing object without explicitly name route.
/**
* Display the specified thing.
*
* #param Thing $thing
* #return mixed
*/
public function show(Thing $thing)
{
return $thing->toJson();
}
If you want to access ThingController#show, pass your model ID and Laravel will retrieve it automatically.
http://example.com/things/1
{"id":1,"type":"Yo!"}
You can use Route:resource and still provide other methods. Place the route you need just before that particular Route::resource line.
Eg:
Route::model('things', 'Thing');
Route::get('things/{things}/owner', 'ThingController#getOwner');
Route::resource('things', 'ThingController');
Then create the corresponding method in your controller.
public function getOwner($things) {
return Response::json($things->owner()->get());
}
Here is the official documentation from the Laravel 4.2 docs:
Source: http://laravel.com/docs/controllers#resource-controllers
Adding Additional Routes To Resource Controllers
If it becomes necessary for you to add additional routes to a resource controller beyond the default resource routes, you should define those routes before your call to Route::resource:
Route::get('photos/popular');
Route::resource('photos', 'PhotoController');

laravel 5.7 how to pass variable id of one controller to other

I have two controllers and I try pass id of variable form method one controller to method to second controller and I got an error like this >MethodNotAllowedHttpException . I will add that my url after the action looks like this >http://localhost/comment?12 . How is the best way of solving this problem ?
You are most probably getting MethodNotAllowedException, because you are opening a route that is defined as a POST route via GET or the other way around.
To avoid that you can use php artisan route:list and get a list of all defined routes and see how you should "access" them:
+--------+-----------+----------------------------------------------------+------------------------+------------------------------------------------------------------------+--------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+----------------------------------------------------+------------------------+------------------------------------------------------------------------+--------------+
| | GET|HEAD | /a/show/{id} | | App\Http\Controllers\AController#show | web |
| | GET|HEAD | /b/show/{id} | | App\Http\Controllers\BController#show | web |
So let's say you have 2 controllers: AController and BController. Each of the controllers have a show() method declared in them.
class AController extends Controller {
//... other AController related code
public function show($id) {
dd($id);
}
}
class BController extends Controller {
//... other BController related code
public function show($id) {
dd($id);
}
}
Then you can define your routes like this:
Route::get('/a/show/{id}', 'AController#show');
Links like: example.com/a/show/10 will "load" AController's show() method. All we have in our AController::show() method's body is dump and die on $id, we will get 10 printed if we visit that link.
We can replace that dd($id); with:
redirect()->action('BController#show', ['id' => $id]);
And define another route:
Route::get('/b/show/{id}', 'BController#show');
This way if we open the previous link: example.com/a/show/10, we will be redirected to: example.com/b/show/10 and BController::show() method will be executed and it prints the variable using dump and die.
Key points:
Route Parameters
Controller Parameters
redirect()-ing

Laravel Route resource named functions

I currently have routes like this
//Settings
Route::prefix('settings')->group(function(){
//Get all users settings
Route::resource('user', 'SettingsController');
});
Which will produce a list of routes like so
| POST | settings/user |user.store | App\Http\Controllers\SettingsController#store | web,auth,GlobalAdmin |
| GET|HEAD | settings/user |user.index | App\Http\Controllers\SettingsController#index | web,auth,GlobalAdmin |
| GET|HEAD | settings/user/create |user.create | App\Http\Controllers\SettingsController#create | web,auth,GlobalAdmin |
And so on.
My issue is that I want the settings controller to be able to control a list of different settings in 1 controller, not just 'users'.
How would I name the resource so that it names the functions at the end?
For example, the above code generates function names like SettingsController#store, how would i get it so that it auto builds the function name with a prefix like SettingsController#userstore?
You can't. Unless you go behind the scenes and actually hack into how Laravel handles the generation. What you can do however is use except on the resource route or use partial resource routes.
Once you have done one of the above, you can just add your routes manually such as
POST settings/user
POST settings/other
POST settings/general
And point them that way.
If you really want to have such behavior you can have it. You can extend the Illuminate\Routing\ResourceRegistrar class and bind your extended version to the container.
There is only one method that needs to be adjusted, which is the method that sets up the action for each of the routes of the resource, getResourceAction. This can be adjusted to check for a key passed in the options array the the ResourceRegistrar already uses. If a key is present you can enable the behavior you need, prefixing the method name with the resource name and uppercasing the first letter of the actual method.
class YourRegistrar extends \Illuminate\Routing\ResourceRegistrar
{
protected function getResourceAction($resource, $controller, $method, $options)
{
$name = $this->getResourceRouteName($resource, $method, $options);
// check if 'pre' option was set
$method = isset($options['pre']) ? $resource . ucfirst($method) : $method;
$action = ['as' => $name, 'uses' => $controller.'#'.$method];
if (isset($options['middleware'])) {
$action['middleware'] = $options['middleware'];
}
return $action;
}
}
In a Service Provider #register (binding your new class to the current ResourceRegistrar):
$this->app->bind(
\Illuminate\Routing\ResourceRegistrar::class,
\Where\Ever\YourResourceRegistrar::class
);
In a routes file:
Route::resource('user', 'SettingsController', ['pre' => true]);
// SettingsController#userIndex
// SettingsController#userShow
// ...
Route::resource('user', 'SettingsController');
// SettingsController#index
// SettingsController#show
// ... normal
The router checks to see if there is something bound to the name of the ResourceRegistrar on the container before newing up one. If there is a binding it asks the container to resolve one. This is how you can extend the ResourceRegistrar and the router uses your version.
In our version we are checking if the options key pre was set or not. If it was we adjust the method names for the routes accordingly. ($method = isset($options['pre']) ? $resource . ucfirst($method) : $method;)
You can read more on the ResourceRegistrar and more detail of what happened above in my blog article on the subject:
asklagbox blog - Resource Registrar - lets extend

Laravel - Overriding a resource route into a different route filter group

Laravel's routing doesn't seem to be working as expected? From what I understand, if I intend to override a route, all I need to do is to put the expected route before the other one.
What I have is something like this:
Route::group(array('before'=>'defaultLoads'), function(){
Route::post('newsletter', 'NewsletterController#store');
Route::group(array('before'=>'login'), function(){
Route::resource('newsletter','NewsletterController');
}
});
Which I assumed that if i post to this route http://domain.com/newsletter it should only run the defaultLoads route filter.
However, when I run php artisan routes, I get this:
| | POST newsletter |
newsletter.store | NewsletterController#store |
defaultLoads, login | |
Although it reads the route correctly (php artisan loads that correct route in the correct place) but the resource route's filter affected the route even when it's not in that filter group.
So my question:
Is this how Laravel works?
If so, is it possible for me to override that POST->newsletter route without actually doing the following?
Route::group(array('before'=>'defaultLoads'), function(){
Route::post('newsletter', 'NewsletterController#store');
Route::group(array('before'=>'login'), function(){
Route::get('newsletter','NewsletterController#get');
Route::get('newsletter/{id}', 'NewsletterController#show');
//etc all the rest of the routes except post
}});
Actually overriding in that way works in cases where you need to override the actual route that is being matched. For example when you need to override a route with a parameter, with something hardcoded:
Route::get('newsletter/custom', ...);
Route::get('newsletter/{param}', ...); // this is overriden by the first route
In your case however, your route definitions are identical (both must match newsletter for a post request). That means that the last one will override the first one (and any filters applied to it in the current context). So you should be overrding it after the resource route definition:
Route::group(array('before'=>'defaultLoads'), function()
{
Route::group(array('before'=>'login'), function()
{
Route::resource('newsletter','NewsletterController');
}
Route::post('newsletter', 'NewsletterController#store');
});
Your artisan routes for it should look like this now:
POST newsletter | NewsletterController#store | | defaultLoads

How do I display information from a form?

I am trying to display information submitted from a form to create a message board. I am using php in the laravel framework. I am using a form, record, and repository. Whenever I try to display the content I receive the following error Symfony \ Component \ HttpKernel \ Exception \ MethodNotAllowedHttpException.
Here is the controller:
/**
* Display a listing of the resource.
* GET /messages
*
* #return Response
*/
public function create()
{
return View::make('comments.create');
}
public function show($comment)
{
$message_id = $this->messageRepository->find($comment);
return View::make('comments.show')->with('comment', $message_id);
}
/**
* Store a newly created resource in storage.
* POST /messaages
*
* #return Response
*/
public function store()
{
$data = Input::all() ;
$this->messageForm->validate($data);
$messageRecord = new MessageRecord;
$messageRecord->comment = $data['comment'];
Return "Comment created";
}
}
Here is the view causing the trouble:
<p>$comment</p>
I have not been using a route but I threw this together:
Route::resource('/message', 'MessageController');
[Edit:]
If your store() is already used by another form and you still have another form to send POST data then, you should declare another routes for that. Like
Route::post('message/display', ['as' => 'message.display', 'uses' => 'MessageController#display');
Sorry for my previous answer, show() is for GET request and since you are using form you probably you don't need it unless you decide to send data through url. I'm extremely sorry.
[Edit ends]
You are getting that error because you are not allowed to use display() in resourceful routing. Instead, you must use store(). Since you are using laravel resourceful routing, you should follow the convention of laravel. In your command line, run
php artisan routes
You will get something like this,
GET|HEAD message | message.index | MessageController#index
GET|HEAD message/create | message.create | MessageController#create
POST message | message.store | MessageController#store
GET|HEAD message/{message} | message.show | MessageController#show
GET|HEAD message/{message}/edit | message.edit | MessageController#edit
PUT message/{message} | message.update | MessageController#update
PATCH message/{message} | | MessageController#update
DELETE message/{message} | message.destroy | MessageController#destroy
These are the methods, their route names and their corresponding HTTP request that you should use.
Check Laravel documentation for resourceful controller
For quick look,
index : Display a listing of the resource,
create : Show the form for creating a new resource,
store : Store a newly created resource in storage,
show : Display the specified resource,
edit : Show the form for editing the specified resource,
update : Update the specified resource in storage,
destroy : Remove the specified resource from storage
It seems you use incorrect verb for route displaying this route. For display you should probably use Route::get and you probably use Route::post. If it's not the issue you edit your question and put there routes.php as you were asked in comment.

Categories