How can I generate a link to a nested resource? - php

The Laravel application I am working on has two resources.
The routes for the second resource are given below:
$ php artisan route:list | grep -i activity
POST | admin/procedure/{id}/activity | admin.procedure.{id}.activity.store | (...)\ProcedureActivityController#store
GET|HEAD | admin/procedure/{id}/activity | admin.procedure.{id}.activity.index | (...)\ProcedureActivityController#index
GET|HEAD | admin/procedure/{id}/activity/create | admin.procedure.{id}.activity.create | (...)\ProcedureActivityController#create
GET|HEAD | admin/procedure/{id}/activity/{activity} | admin.procedure.{id}.activity.show | (...)\ProcedureActivityController#show
PUT|PATCH | admin/procedure/{id}/activity/{activity} | admin.procedure.{id}.activity.update | (...)\ProcedureActivityController#update
DELETE | admin/procedure/{id}/activity/{activity} | admin.procedure.{id}.activity.destroy | (...)\ProcedureActivityController#destroy
GET|HEAD | admin/procedure/{id}/activity/{activity}/edit | admin.procedure.{id}.activity.edit | (...)\ProcedureActivityController#edit
I call this setup a nested resource because activities are defined under a procedure. The definition or the routes looks like this:
Route::resource('procedure', 'ProcedureController');
Route::resource('procedure/{id}/activity', 'Admin\ProcedureActivityController');
I would like to generate a link to the POST action for a new activity that belongs to procedure 3 as I would with the list-all-procedures route;
$ php artisan tinker
>>> route('admin.procedure.index')
=> "http://localhost/admin/procedure"
>>> route('admin.procedure.{id}.activity')
InvalidArgumentException with message
'Route [admin.procedure.{id}.activity] not defined.'
Is there a way to generate a link to a nested resource using the standard helpers and facades?

Your route definition for the nested resource is not quite right.
Route::resource('procedure/{id}/activity', 'Admin\ProcedureActivityController');
Should be:
Route::resource('procedure.activity', 'Admin\ProcedureActivityController');
Also I am not sure how you are getting {id} in the URI as the ResourceRegistrar will create parameters based on the resource name. Based on the definition that should be {procedure} for your first resource definition.
You should end up with a route name like admin.procedure.activity.index for the index route.
route('admin.procedure.activity.index', ['procedure' => $id]);
Laravel 5.1 - Controllers - Restful - Nested Resources
Route::resource('photos.comments', 'PhotoCommentController');
This route will register a "nested" resource that may be accessed with URLs like the following: photos/{photos}/comments/{comments}.

You should use route() with a parameter to make it work:
route('admin.procedure.{id}.activity.index', $id);

Related

route() helper function does not work with resource controller in laravel, but url() do work. Is it normal?

I have a standard resource controller registered like this:
Route::resource('/movies', \App\Http\Controllers\MovieController::class );
I checked php artisan route:list and I have the get 'movies' route.
But in blade template
Movies
does not work anymore.
And only
Movies
works.
Before that when I had:
Route::get('/movies', function () {
return view('movies');
})->name('movies');
The route method worked.
So url() method works everywhere and route() only in specific cases.
Could anyone tell me what is the main difference between those two methods?
which one should be used when?
And the main question - is it OK that the route() method does not work with resource controller, or is it something wrong with my code?
And since url() works everywhere should I just forget about route() method as unreliable?
If you look at the result of php artisan route:list you will see several routes e.g.
| | POST | movies | movies.store | App\Http\Controllers\MovieController#store | web |
| | GET|HEAD | movies | movies.index | App\Http\Controllers\MovieController#index | web |
| | DELETE | movies/{movie} | movies.destroy | App\Http\Controllers\MovieController#destroy | web |
| | PUT|PATCH | movies/{movie} | movies.update | App\Http\Controllers\MovieController#update |
route() and url() accept different values.
route() will accept route names e.g. movies.index, movies.destroy, movies.update and
url() will accept movies, movies/23.
route() will select the right methods (GET, POST, PATCH, DELETE) for you but
url() will only provide the path.

Laravel API Routes Return 404

I am using Laravel 5.6 and I'm getting HTTP404 responses on existing routes in routes/api.php which I define as follows:
Route::middleware('auth:api')->post('/account/plan', 'Account\BillingController#updatePlan');
Route::middleware('auth:api')->put('/account/plan', 'Account\BillingController#unsubscribe');
Route::middleware('auth:api')->patch('/account/plan', 'Account\BillingController#resubscribe');
When I use axios.post() on these routes and include the _method parameter I get a 404 response on the PUT and PATCH routes. I have also tested axios.put()/axios.patch() in place of using post() with and without the inclsion of the _method parameter. I have also confirmed these are being correctly represented by artisan route:list:
| | POST | api/account/plan | | App\Http\Controllers\Account\BillingController#updatePlan | api,auth:api |
| | PUT | api/account/plan | | App\Http\Controllers\Account\BillingController#unsubscribe | api,auth:api |
| | PATCH | api/account/plan | | App\Http\Controllers\Account\BillingController#resubscribe | api,auth:api |
Example of the Axios Call:
axios.post(url,{_method:"PUT",confirm:"unsubscribe"})
.then(response => callback(response.data))
.catch(error => console.log(error))
When I define these same routes as follows they all work as intended:
Route::middleware('auth:api')->post('/account/plan', 'Account\BillingController#updatePlan');
Route::middleware('auth:api')->post('/account/unsubscribe', 'Account\BillingController#unsubscribe');
Route::middleware('auth:api')->post('/account/resubscribe', 'Account\BillingController#resubscribe');
I am able to separate endpoints by the request method on other routes I am unsure why these are creating a problem. Can someone explain why I get the 404 responses and how I can avoid them?
Since it seems you are doing evrything fine maybe by following laravel more strict conventions on defining routes you won't encounter the problem? Try like this:
Route::middleware(['auth:api'])->group(function () {
Route::post('/account/plan', 'Account\BillingController#updatePlan');
Route::put('/account/plan', 'Account\BillingController#unsubscribe');
Route::patch('/account/plan', 'Account\BillingController#resubscribe');
});

Laravel Controller Dependency Injection

I'm trying to do dependency injection in Laravel to keep my controllers and models as slim as possible. The goal is to have repositories to handle the fetching of data attributed to certain models.
To this end I'm trying to follow the example from the documentation here and a popular Laravel boilerplate here
But I don't understand where the $user is coming from.
So looking at the boilerplate we have two files:
The ProfileController here
Excerpt below:
use App\Repositories\Frontend\Access\User\UserRepository;
/**
* Class ProfileController.
*/
class ProfileController extends Controller
{
/**
* #var UserRepository
*/
protected $user;
/**
* ProfileController constructor.
*
* #param UserRepository $user
*/
public function __construct(UserRepository $user)
{
$this->user = $user;
}
This looks a lot like the dependency injection mentioned in the docs, which is this:
class UserController extends Controller {
/**
* The user repository instance.
*/
protected $users;
/**
* Create a new controller instance.
*
* #param UserRepository $users
* #return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
My problem is I don't understand where the $user is coming from.
In the UserRepository there is no $user defined as a parameter of the class itself. No where in the code is there any Auth::user() so I'm confused as to where the user instance is coming from.
In Laravel dependency injection is handled by the Container. I'm simplifying, but you can think of the container as a source of objects. If there is a singleton, its stored in the container. Otherwise the container knows how to instantiate objects for you. Whenever Laravel calls a method (like in a controller) or instantiates an object for you it will inspect the constructor and look for type hinted dependencies. If it sees a dependency it knows how to retrieve or create it will do so and pass it in for you.
So when Laravel instantiates the controller it looks at the constructor
public function __construct(UserRepository $user)
{
$this->user = $user;
}
The container uses Type Hinting to see that it requires a UserRepository so it will instantiate a new one for you. It also does this recursively. So when it creates a new UserRepository it looks at that constructor and sees that it requires a RoleRepository so it will instantiate that as well.
TLDR: The service container inspects your dependencies and will instantiate them for you.
Welcome to the dubious magic of Laravel. The basic idea with these dependency injections is that, depending on how you define your routes & controllers, Laravel can perform some automagical parsing of urls, identification of ids in those urls, and database fetching of objects.
My problem is I don't understand where the $user is coming from.
You should probably read the docs on the service container. You can also get a better idea of how your route definitions translate into parameter-laden urls with this command:
php artisan route:list
On one of my projects, this results in this output:
+--------+-----------+----------------------------+--------------------+-------------------------------------------------+--------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+----------------------------+--------------------+-------------------------------------------------+--------------+
| | GET|HEAD | / | | Closure | web |
| | GET|HEAD | api/user | | Closure | api,auth:api |
| | GET|HEAD | categories | categories.index | App\Http\Controllers\CategoryController#index | web |
| | POST | categories | categories.store | App\Http\Controllers\CategoryController#store | web |
| | GET|HEAD | categories/create | categories.create | App\Http\Controllers\CategoryController#create | web |
| | GET|HEAD | categories/{category} | categories.show | App\Http\Controllers\CategoryController#show | web |
| | PUT|PATCH | categories/{category} | categories.update | App\Http\Controllers\CategoryController#update | web |
| | DELETE | categories/{category} | categories.destroy | App\Http\Controllers\CategoryController#destroy | web |
| | GET|HEAD | categories/{category}/edit | categories.edit | App\Http\Controllers\CategoryController#edit | web |
| | GET|HEAD | products | products.index | App\Http\Controllers\ProductController#index | web |
| | POST | products | products.store | App\Http\Controllers\ProductController#store | web |
| | GET|HEAD | products/create | products.create | App\Http\Controllers\ProductController#create | web |
| | GET|HEAD | products/{product} | products.show | App\Http\Controllers\ProductController#show | web |
| | PUT|PATCH | products/{product} | products.update | App\Http\Controllers\ProductController#update | web |
| | DELETE | products/{product} | products.destroy | App\Http\Controllers\ProductController#destroy | web |
| | GET|HEAD | products/{product}/edit | products.edit | App\Http\Controllers\ProductController#edit | web |
+--------+-----------+----------------------------+--------------------+-------------------------------------------------+--------------+
And all those routes and their uris and parameters are generated from only a couple of very simple routes definitions. Here's my routes file:
$ cat routes/web.php
<?php
Route::get('/', function () {
return view('master');
});
Route::resource('products', 'ProductController');
Route::resource('categories', 'CategoryController');
If you look at the list of URIs in the routes output above, you'll see parameters named in the URIs like {category} and {product}. These correspond to ids/keys in the URI which Laravel identifies. Laravel is "smart" enough to look at my Controller files, see the type-hinting in the various functions and detect that my function is expecting a depedency to be injected.
For instance, the Category controller's show method looks like this:
public function show(Tree $category)
{
var_dump($category);
}
My controller might seem a little unusual because I'm type hinting that I want an object of type Tree, but Laravel is smart enough to recognize that I do in fact want a Model of type Tree, so it parses out the url and finds the id in it and automatically fetches the record in my db table trees with id matching the {category} fragment of my url and injects that into my function.
Note that I had some trouble when I tried to name the input parameter $tree instead of $category. That other thread may help answer your question a bit too.
The bottom line is that Laravel does a lot of "magic" to hopefully free you up from the tedious of manually defining your own code and queries to retrieve the objects you want.

Cahephp 3.x : dashed rest api not working

I'm building a RESTful API System with CakePHP 3.1.13 ( i can't use 3.2.x because the Server PHP Version is 5.5.x ).
My controller name is CmsCouplesController.php and the url :
http://localhost/~emanuele/works/grai/html/api/v1/cms-couples.json
works correctly.
BUT the other call ( http://localhost/~emanuele/works/grai/html/api/v1/cms-couples/1.json ) return :
Action CmsCouplesController::1() could not be found, or is not accessible.
If i create a controller CouplesController.php all works fine.
So why?!
UPDATE : routes configuration
Router::scope('/', function ($routes) {
$routes->prefix('v1',function($routes) {
$routes->extensions(['json','xml']);
$routes->resources('Couples');
$routes->fallbacks('DashedRoute');
});
Resource routes require separate inflection configuration
You are missing the proper inflection configuration for your resource routes. By default resource routes are using underscore inflection, ie currently your resource routes will match cms_couples.
Note that you can easily check which/how routes are connected by using the routes shell
bin/cake routes
It will show you something like
| v1:cmscouples:index | /v1/cms_couples | {"controller":"CmsCouples","action":"index","_method":"GET","prefix":"v1","plugin":null} |
| v1:cmscouples:add | /v1/cms_couples | {"controller":"CmsCouples","action":"add","_method":"POST","prefix":"v1","plugin":null} |
| v1:cmscouples:view | /v1/cms_couples/:id | {"controller":"CmsCouples","action":"view","_method":"GET","prefix":"v1","plugin":null} |
| v1:cmscouples:edit | /v1/cms_couples/:id | {"controller":"CmsCouples","action":"edit","_method":["PUT","PATCH"],"prefix":"v1","plugin":null} |
| v1:cmscouples:delete | /v1/cms_couples/:id | {"controller":"CmsCouples","action":"delete","_method":"DELETE","prefix":"v1","plugin":null} |
Long story short, use dasherize inflection and you should be good.
$routes->resources('CmsCouples', [
'inflect' => 'dasherize'
]);
See also
Cookbook > Shells, Tasks & Console Tools > Routes Shell
Cookbook > Routing > URL Inflection for Resource Routes
API > \Cake\Routing\RouteBuilder::resources()
from my understanding...
http://localhost/~emanuele/works/grai/html/api/v1/cms-couples.json
must be pointing to index function of CmsCouplesController.php controller
then for what reason you want this kind of url
http://localhost/~emanuele/works/grai/html/api/v1/cms-couples/1.json
you can give url like
http://localhost/~emanuele/works/grai/html/api/v1/cms-couples/json-request
then you can put your code in jsonReuest function of the CmsCouplesController.php controller ...
If this does not help then explain your question with answer of my question of why you want URl like 1.json

Laravel controller action returns error 404

I'm getting a 404 error when trying to access a route linked to a controller action.
I have the route defined like this in my routes.php file.
Route::controller('error', 'ErrorsController');
The ErrorsController class looks as follows.
class ErrorsController extends BaseController {
public function __construct()
{
// vacio
}
public function getIndex()
{
return View::make('error.accessdenied');
}
public function getAccessDenied()
{
return View::make('error.accessdenied');
}
}
I have a view with a link to chek if it is working properly. The link is created as follows
{{ HTML::linkAction('ErrorsController#getAccessDenied', 'Error') }}
When I click on the link the page moves to the URL 'mytestdomain.com/error/access-denied' returning an 404 error, but when I access the URL 'mytestdomain.com/error' it works perfectly.
Any idea on what I'm doing wrong?
EDIT:
Running the command php artisan routes these are the routes pointing to ErrorsController:
+--------+------------------------------------------------------------------------------------------------+------+--------------------------------------+----------------+---------------+
| Domain | URI | Name | Action | Before Filters | After Filters |
+--------+------------------------------------------------------------------------------------------------+------+--------------------------------------+----------------+---------------+
| | GET|HEAD error/index/{one?}/{two?}/{three?}/{four?}/{five?} | | ErrorsController#getIndex | | |
| | GET|HEAD error | | ErrorsController#getIndex | | |
| | GET|HEAD error/access-denied/{one?}/{two?}/{three?}/{four?}/{five?} | | ErrorsController#getAccessDenied | | |
| | GET|HEAD|POST|PUT|PATCH|DELETE error/{_missing} | | ErrorsController#missingMethod | | |
+--------+------------------------------------------------------------------------------------------------+------+--------------------------------------+----------------+---------------+
Only the sencond and the fourth ones are working.
It looks as though specifying the route in the way you have won't work. This type of routing only works for RESTful requests. See >http://laravel.com/docs/4.2/controllers#restful-resource-controllers>.
You might have to explicitly specify the route using Route::get/post.
Somehow I found the problem.
For some reason, my apache server doesn't rewrite mytestdomain.com/error/ * route. Probably is something related with the word error and the apache module mod_rewrite.
Anyway, defining the route as follows solves the problem.
Route::controller('fail', 'ErrorsController');

Categories