Laravel 5.5 Resource Controller and Dependecy Injection - php

I am working on a Laravel 5.5 application. When I use php artisan make:model SomeModel -mr it creates the model, migration and resource controller.
I've been noticed that some methods have by default only one parameter: the model:
public function show(SomeModel $someModel)
{
...
}
If you look into the $someModel variable it has an empty SomeModel object.
I was reading on Laravel Documentation that it looks like the Containers or Facades but I am not sure how to use this. Do you?
Edit 1:
I had my routes defined in routes/web.php as: Route::resource('users', 'UserController');
Now I had to define all the routes manually since automatic binding was not working:
Route::get('users', 'UserController#index');
Route::get('users/create', 'UserController#create');
Route::post('users', 'UserController#store');
Route::get('users/{user}/edit', 'UserController#edit', function(App\User $user) {});
Route::post('users/{user}', 'UserController#update', function(App\User $user) {});
Route::post('users/{user}/delete', 'UserController#destroy', function(App\User $user) {});
So, should I replace every resource controller route to manual routing like this?

The resource controller is expecting you to use route model binding. In your routes file, each route that corresponds to a controller action with an injected model will need to have a matching parameter.
For example:
Route::get('user/{user}', 'UserController#show');
Using the above route, the following controller action would receive a user instances that corresponds to the user ID passed as a URL parameter.
class UserController extends Controller
{
public function show(User $user)
{
...
}
}
The reason you're seeing an empty model now is that Laravel will just pass and fresh model to the controller if it is not bound to a route parameter. In other words, if you forget to bind the model in your routes file automatic injection will just give you a new instance.
Note that if you are using a route resource the resulting routes should already have the correct parameters
Route::resource('users', 'UserController');
You can run php artisan route:list to confirm that your actual routes are correct.

Your problem is your controller is expecting two parameters like below:
public function show($id, User $user)
if you try:
public function show(User $user)
it should work correctly.
In your route you are passing only a single param like:
user/{user}
So if you dd the first param it will display the number 1 but if you pass that
to the model it will return the corresponding user as per what id you pass in the route.
The reason your User model was returning an empty object was because there was no value passed to it.
Also make sure your route placeholder: /{user} matches the variable name in
the controller: public function show(User $user).
Hope this helps.

I too came across with the same problem.
If your model having two or more words, you have to use only small letters like $modeModel as $somemodel.
public function show(SomeModel $somemodel)
{
...
}

Related

Declare same route twice but expect different behaviour according to a middleware

I started creating a REST API using the lumen framework and wanted to set up a particular behaviour for my GET /user route. Behaviour is the following:
If the request come from an authenticated user (using auth middleware), the method getAllFields from UserController is called and return all the data from the user
If it's not the case, the method get from UserController is called and return some of the data from the user
It seems logic to me to just write it like that in my web.php using a simple middleware:
<?php
$router->group(['middleware' => 'auth'], function () use ($router) {
$router->get('/user/{id}', [
'uses' => 'UserController#getAllFields'
]);
});
$router->get('/user/{id}', [
'uses' => 'UserController#get'
]);
But for some reason, even if the middleware is correct, I always get the response of the second route declaration (that call get()). I precise that if I remove the second route declaration, the one in the middleware work as expected.
Have someone an idea how I can achieve something similar that work?
Router will check if your request matches to any declared route. Middleware will run AFTER that match, so You cannot just return to router and try to find another match.
To fallow Laravel and Routes pattern - You should have single route that will point to method inside controller. Then inside that You can check if user is logged or not and execute getAllFields() from that controller. It will be not much to rewrite since You are currently using UserController in both routes anyway.
web.php
$router->get('/user/{id}', 'UserController#get');
UserController.php
public function get()
{
return auth()->check() ? YourMethodForLogged() : YourMethodForNotLogged();
}
Or if there is not much logic You can keep this in single method.
Also it is good idea to fallow Laravels REST standards (so use show instead of get, "users" instead of "user" etc - read more https://laravel.com/docs/7.x/controllers)
web.php
$router->get('/users/{user}', 'UserController#show');
UserController.php
public function show(User $user)
{
if (auth()->check()) {
//
} else {
//
}
}
To summary - for your needs use Auth inside controller instead of middleware.
To check if user is logged You can use Facade Auth::check() or helper auth()->check(), or opposite Auth::guest() or auth()->guest().
If you are actually using Lumen instead of full Laravel then there is not auth helper by default (You can make own or use package like lumen-helpers) or just keep it simple and use just Facades instead (if You have then enabled in Lumen).
Read more https://laravel.com/docs/7.x/authentication and https://lumen.laravel.com/docs/7.x/authentication
This pattern is against the idea of Laravel's routing. Each route should be defined once.
You can define your route without auth middleware enabled and then define your logic in the controller.

Laravel - Routes: Controller nested controller

My routes:
Route::apiResource('courses', 'CourseController');
Route::apiResource('courses.classrooms', 'ClassroomController');
List: php artisan route:list
api/v1/courses/{course}
api/v1/courses/{course}/classrooms/{classroom}
My question is: all my functions in classroom controller needs the course, something like that
public function index($course_id)
{
$classroom = Classroom::where('course_id', $course_id)->get();
return $classroom;
}
public function store($course_id, Request $request)
{
// ...
$classroom->course_id = $course_id;
// ...
}
public function show($course_id, $id)
{
$classroom = Classroom::where('course_id', $course_id)->find($id);
return $classroom;
}
// ...
Have some Policy/Helper in Laravel to accomplish this automatically?
I believe it's not necessary to add the property $course_id in all functions manually, what can I do?
You can use a group to enclose all your routes. Something like:
Route::group(['prefix' => '{course}'], function () {
// you can place your routes here
});
So all the routes that exist in that group will already have the course value in the url path and you don't have to "rewrite it" for every route.
If that field is set by you for example an env variable then inside your RouteServiceProvider you can put the prefix you want in the mapApiRoutes function.
protected function mapApiRoutes()
{
Route::prefix('/api/v1/courses/'.config('app.myVariable'))
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
}
That way ALL your api endpoints will start with that prefix and you can have it in all the endpoints.
If the routes are registered correctly like you posted, your methods in the ClassroomsController should receive an additional parameter that's the course id fragment from the url.
For example if you request /api/v1/courses/1/classrooms route, the controller will receive the correct {course} parameter set to 1 as the first parameter.
You could then implement the index method of the ClassroomsController to use implicit model binding and get the Course instance with the given url id for the course.
To do so you have to type-hint the Course model for the first function's parameter and name the variable as the url fragment you want to use to retrive your model.
In your code example, you should do:
public function index(Course $course)
{
return $course->classrooms;
}
Note: I assume you have a relationship between Course and Classroom models to retrive the classrooms from the course model instance
You can read more about that on the official documentation here

How can I redirect a Laravel route without a parameter to a controller method with a default parameter?

Assume the following routes in a Laravel 5.5 API:
// custom routes for THIS user (the user making the request)
Route::get('/user', 'UserController#show');
Route::get('/user/edit', 'UserController#edit');
// register CRUDdy resource routes for users
Route::resource('users', 'UserController');
Let's use edit as the example:
public function edit(User $user)
{
...
}
As you can see, the edit route contains a type-hinted $user parameter. This works just fine for the users/13/edit route, as expected. However, I'd to configure the route /user/edit to pass along the $request->user() user object to the function. I don't want to check for the user object in the actual edit method as that could interfere with other validation (for instance, I don't want to default to the current user if someone passes a non-existent user ID, I want to return an error in that case).
In short: how can I register a route to first create a parameter, then pass it to the given controller method? My first thought was to use a closure:
Route::get('/user/edit', function(Request $request){
$user = $request->user();
});
But once inside the closure, I'm not certain how to then carry the request forward to the appropriate controller method.
Instead of a closure, you could make a new controller method that calls edit with the current user.
Let's say your route is this:
Route::get('/user/edit', 'UserController#editSelf');
Then in your controller:
public function editSelf(Request $request)
{
$this->edit($request->user());
}

Laravel routing failing for controller

Its Laravel 5.
When the route.php contains this:
Route::get('/foo', function () {
return 'Hello World';
});
then the page shows with the text "Hello World".
However, as soon as I add this new line in route.php:
Route::get('/foo2', 'IndexController');
then the page show this error:
UnexpectedValueException in Route.php line 567: Invalid route action: [App\Http\Controllers\IndexController]
I previously created a controller with artisan which now looks like this:
class IndexController extends Controller
{
public function index()
{
echo 'test';
}
}
what am I doing wrong?
You have to specify wich method will be executed:
Route::get('/foo2', 'IndexController#index');
If you are using get method of Route. Normally first argument provided should be the url and second argument should be the method (there are other ways argument could be passed)
Route::get('/foo2', 'IndexController#index');
If you want to resourceful route . Normally first argument should be the resource name and the second argument should be RESTful controller name. (there are other ways argument could be passed).Example: photo is the resource name and PhotoController is the controller name.
Route::resource('photo', 'PhotoController');
in your case it should work this way
Route::resource('/foo2', 'IndexController');
or
Route::get('/foo2', 'IndexController#index');
so when you visit
yoursite.com/foo2
you will be displayed with IndexController index method
See reference more to learn laravel's restful resource controller
reference: https://laravel.com/docs/5.1/controllers#restful-resource-controllers
You need to specify the function inside the controller not just the controller:
Route::get('/foo2', 'IndexController#index');
You have to reference Controller#method as:
Route::get('/myroute', ['uses' => 'MyController#methodName']);

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
}

Categories