Route::resource binding creates plural attribute routes (i.e. projects/{projects}) - laravel

I am trying to set up a Route:resource for Series.
When I create individual get, post, patch and delete Routes it works as expected (e.g. GET series/${serie}).
However when I use Route::resource it creates plural attributes (e.g. GET series/${series}).
In the laracast that I am following it creates the singular (e.g. GET projects/${project}).
I can't figure out what I am missing.
This works:
Route::get('/series', 'SeriesController#index');
Route::get('/series/create', 'SeriesController#create');
Route::get('/series/{serie}', 'SeriesController#show');
Route::post('/series', 'SeriesController#store');
Route::get('/series/{serie}/edit', 'SeriesController#edit');
Route::patch('/series/{serie}', 'SeriesController#update');
Route::delete('/series/{serie}', 'SeriesController#destroy');
The route list is:
GET|HEAD | series | App\Http\Controllers\SeriesController#index
POST | series | App\Http\Controllers\SeriesController#store
GET|HEAD | series/create | App\Http\Controllers\SeriesController#create
GET|HEAD | series/{serie} | App\Http\Controllers\SeriesController#show
PATCH | series/{serie} | App\Http\Controllers\SeriesController#update
DELETE | series/{serie} | App\Http\Controllers\SeriesController#destroy
GET|HEAD | series/{serie}/edit | App\Http\Controllers\SeriesController#edit
This doesn't work:
Route::resource('series', 'SeriesController');
and produces this route list:
GET|HEAD | series | App\Http\Controllers\SeriesController#index
POST | series | App\Http\Controllers\SeriesController#store
GET|HEAD | series/create | App\Http\Controllers\SeriesController#create
GET|HEAD | series/{series} | App\Http\Controllers\SeriesController#show
PATCH | series/{series} | App\Http\Controllers\SeriesController#update
DELETE | series/{series} | App\Http\Controllers\SeriesController#destroy
GET|HEAD | series/{series}/edit | App\Http\Controllers\SeriesController#edit
Notice the plural form {series}. Why is this happening?

"Series" is the plural as well as the singular of the word, therefore the routes Laravel is generating are perfectly fine.
If you have a look into Laravels pluralizer helper, you can even see that "series" is explicitely listed as uncountable: https://github.com/laravel/framework/blob/5.5/src/Illuminate/Support/Pluralizer.php#L49

Related

API RESTful Laravel 6.x Best Practice for Many to Many Relatioship

I'm developing an API with Laravel 6.
I've got 2 models:
card -> table cards with card_id ecc.
user -> table users with user_id ecc.
I've defined into models many to many relationships
User.php
public function cards()
{
return $this->belongsToMany('App\Models\v1\Card');
}
Card.php
public function users() {
return $this->belongsToMany('App\Models\v1\User');
}
The pivot table is called card_user .
Now I've created routes for single entities:
Route::resource('v1/users', 'v1\UsersController');
Route::resource('v1/cards', 'v1\CardsController');
and I need to develop routes and controller for insert and delete rows from pivot table.
What is the best practice for this issue?
I try to solve this with a special controller that respond to a specific endpoint:
Route::resource('v1/cards/{id}/users', 'v1\CardsUsersController')->only([
'index', 'store', 'destroy'
]);
But when I need to store information I need to pass the ids of card and user into the URL and as object in post body like so:
[
'user_id' => $userId,
'card_id' => $cardId
]
Exists a better way to do this?
Thanks a lot!
You can use Nested Resources as described here:
https://laravel.com/docs/6.x/controllers#restful-nested-resources
"Sometimes you may need to define routes to a "nested" resource. For example, a photo resource may have multiple "comments" that may be attached to the photo. To "nest" resource controllers, use "dot" notation in your route declaration:
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}."
If you must have separate routes and controller for them, then it would be better to do
Route::resource('v1/card_user', 'v1\CardsUsersController')->only(['index', 'store','destroy']);
Keep the route clean, and don't overcomplicate it. Either You or someone else in the future who views code should be able to understand what it is for.
I would combine both answers. As a relationship, it is technically a nested resource. Also, you really have 2 RESTful actions: store and destroy (which correspond to attach and detach in Laravel). You may also want an index to view all of the relationship. I believe the "create" action is optional, depending on your UI.
// Ability_Role pivot routes
Route::resource('v1/user.cards', 'UserCardController')
->only(['index', 'create', 'store','destroy']);
This will give the following routes:
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| | GET|HEAD | v1/user/{user}/cards | user.cards.index | App\Http\Controllers\UserCardController#index | web |
| | POST | v1/user/{user}/cards | user.cards.store | App\Http\Controllers\UserCardController#store | web |
| | GET|HEAD | v1/user/{user}/cards/create | user.cards.create | App\Http\Controllers\UserCardController#create | web |
| | DELETE | v1/user/{user}/cards/{card} | user.cards.destroy | App\Http\Controllers\UserCardController#destroy | web |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
I chose to label the routes as user.cards because I would think you would more often want to start with the user model and attached the cards.
For the store method, you can post an array of cards to attached to the user.
If you also want to start with cards, and store an array of users, you can also define the inverse relationships (though it would require a 2nd controller with just the create and store routes:
// Inverse create and store routes
Route::get('v1/cards/{card}/users/create', 'CardUserController#create')
->name('cards.users.create');
Route::post('v1/cards/{card}/users', 'CardUserController#store')
->name('cards.users.store');
now you will get 2 more routes added:
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+
| | GET|HEAD | api/user | api. | Closure | api |
| | | | | | auth:api |
| | POST | v1/cards/{card}/users | cards.users.store | App\Http\Controllers\CardUserController#store | web |
| | GET|HEAD | v1/cards/{card}/users/create | cards.users.create | App\Http\Controllers\CardUserController#create | web |
| | GET|HEAD | v1/user/{user}/cards | user.cards.index | App\Http\Controllers\UserCardController#index | web |
| | POST | v1/user/{user}/cards | user.cards.store | App\Http\Controllers\UserCardController#store | web |
| | GET|HEAD | v1/user/{user}/cards/create | user.cards.create | App\Http\Controllers\UserCardController#create | web |
| | DELETE | v1/user/{user}/cards/{card} | user.cards.destroy | App\Http\Controllers\UserCardController#destroy | web |
+--------+----------+------------------------------+--------------------+-------------------------------------------------+------------+

Laravel best naming convention for controller method and route

I'm creating an ajax request to get item details
Here's what my controller method looks like.
class SystemItemsController extends Controller
{
function getDetails(Request $request){
$response = SystemItems::where('item_name', 'like', '%' .$name . '%')->get();
return response()->json($response,200);
}
}
and my
routing name
Route::get("/system-items/item-details","SystemItemsController#getStockDetails");
question : what would be the best naming convention for my route(item-details) and method(getStockDetails)?
follow up Q : can i do this using laravel resource?
You can use kebab-case and plural in the URI pattern, but camelCase and singular for Controller name, as that is what Laravel will look for if trying to do route–model binding.
You can use it for resouce routes, but note, for this route
Route::resource('item-details', 'ItemDetailController');
the route parameter will result in snake_case and singular
/item-details/{item_detail}
For the controller methods the conventional names are index, show, create, store, edit, update and delete. And snakeCase for custom methods.
Also you can add a route group to prefix with some uri like /system-items
Route::group(['prefix' => 'system-items'], function () {
Route::resource('item-details', 'ItemDetailController');
});
run php artisan route:list to see the result
+--------+-----------+-----------------------------------------------------+-------------------------+---------------------------------------------------------------------------+--------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+-----------------------------------------------------+-------------------------
| | GET|HEAD | api/v1/system-items/item-details | item-details.index | App\Http\Controllers\Api\v1\ItemDetailController#index | api |
| | POST | api/v1/system-items/item-details | item-details.store | App\Http\Controllers\Api\v1\ItemDetailController#store | api |
| | GET|HEAD | api/v1/system-items/item-details/create | item-details.create | App\Http\Controllers\Api\v1\ItemDetailController#create | api |
| | GET|HEAD | api/v1/system-items/item-details/{item_detail} | item-details.show | App\Http\Controllers\Api\v1\ItemDetailController#show | api |
| | PUT|PATCH | api/v1/system-items/item-details/{item_detail} | item-details.update | App\Http\Controllers\Api\v1\ItemDetailController#update | api |
| | DELETE | api/v1/system-items/item-details/{item_detail} | item-details.destroy | App\Http\Controllers\Api\v1\ItemDetailController#destroy | api |
| | GET|HEAD | api/v1/system-items/item-details/{item_detail}/edit | item-details.edit | App\Http\Controllers\Api\v1\ItemDetailController#edit
Of course, all of these are conventions and you can customize everything by doing it by hand and using your own conventions.

Route::has note detecting route resource

I am trying to create links in the top nav menu using the code to check if the route name has a text pattern in it.
All is working except for one route which is also defined as a resource.
Below is my code in the web.php file
Route::get('/bookings', 'BookingController#index')->name('bookings');
Route::resource('/bookings', 'BookingController');
In the app.blade.php file I have the code:
#if (Route::has('bookings'))
<li class="nav-item">
<a class="nav-link" href="{{ route('bookings') }}">{{ __('Bookings') }}</a>
</li>
#endif
I checked the routes in php artisan and its listed as existing.
| api,auth:api |
| | POST | bookings | bookings.store | App\Http\Controllers\BookingController#store | web |
| | GET|HEAD | bookings | bookings.index | App\Http\Controllers\BookingController#index | web |
| | GET|HEAD | bookings/create | bookings.create | App\Http\Controllers\BookingController#create | web |
| | PUT|PATCH | bookings/{booking} | bookings.update | App\Http\Controllers\BookingController#update | web |
| | GET|HEAD | bookings/{booking} | bookings.show | App\Http\Controllers\BookingController#show | web |
| | DELETE | bookings/{booking} | bookings.destroy | App\Http\Controllers\BookingController#destroy | web |
| | GET|HEAD | bookings/{booking}/edit | bookings.edit | App\Http\Controllers\BookingController#edit
Route::has('bookings') checks the route list for a route named bookings. While you've got a URL of bookings, its name in the route list is bookings.index - you don't have any route named bookings.
(Per your php artisan route:list output, your route from the Route::resource call is wiping out the earlier Route::get call. Remove that redundant/ignored Route::get definition.)
Route::has('bookings.index') should do the trick (and changing route('bookings') to route('bookings.index').

Laravel 5.2 : MethodNotAllowedException

I've defined the following route in Laravel:
Route::group(['prefix' => 'api'], function() {
Route::post('login', [
'uses' => 'Auth\AuthController#login',
'as' => 'auth.login',
]);
});
And I'm using Postman to send a request like this (you can also see the results):
Why am I getting a MethodNotAllowed exception????
I also tried creating a form in an empty html file, with the method set to post. but got the same results.
EDIT
If i add a route::get that shows a login form, after the post request in Postman it shows that login form.
EDIT 2:
output of php artisan route:list for our route entries:
+--------+----------+--------------+---------------------+----------------------------------------------------+------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+----------+--------------+---------------------+----------------------------------------------------+------------+
| | GET|HEAD | / | guest.home | App\Http\Controllers\GuestController#index | |
| | GET|HEAD | a/dashboard | admin.dashboard | Closure | |
| | POST | api/login | auth.login | App\Http\Controllers\Auth\AuthController#login | |
| | GET|HEAD | api/login | auth.login | Closure | |
| | GET|HEAD | api/logout | auth.logout | App\Http\Controllers\Auth\AuthController#getLogout | jwt.auth |
| | POST | api/register | auth.register | App\Http\Controllers\Auth\AuthController#register | jwt.auth |
| | GET|HEAD | m/dashboard | moderator.dashboard | Closure | |
| | GET|HEAD | pu/dashboard | premium.dashboard | Closure | |
| | GET|HEAD | u/dashboard | user.dashboard | Closure | |
+--------+----------+--------------+---------------------+----------------------------------------------------+------------+
EDIT3
One more curious thing. If i set the method to Route::any, I get rid of the exception, but then I can't access the post parameters. i.e. I don't have any post parameters.
EDIT 4:
If I add a route::get and show the login view there and send the login credential, it works. But not in Postman.
Use x-www-form-urlencoded instead of form-data in postman, See the difference below.
form-data
multipart/form-data is the default encoding a web form uses to transfer data. This simulates filling a form on a website, and submitting it. The form-data editor lets you set key/value pairs (using the key-value editor) for your data. You can attach files to a key as well. Do note that due to restrictions of the HTML5 spec, files are not stored in history or collections. You would have to select the file again at the time of sending a request.
urlencoded
This encoding is the same as the one used in URL parameters. You just need to enter key/value pairs and Postman will encode the keys and values properly. Note that you can not upload files through this encoding mode. There might be some confusion between form-data and urlencoded so make sure to check with your API first.
Unfortunately the problem was with Postman3. I'm using Advanced REST Client now, and it works alright. Postman would send GET requests no matter what method I chose.

Laravel - Routes GET|HEAD

When I do php artisan routes, the GET request of my app has a |HEAD. What is the purpose of having |HEAD?
Routes.php
+--------+----------------------------------+------------------------------+--------------------------------------+----------------+---------------+
| Domain | URI | Name | Action | Before Filters | After Filters |
+--------+----------------------------------+------------------------------+--------------------------------------+----------------+---------------+
| | GET|HEAD / | home | HomeController#home | | |
| | GET|HEAD user/{username} | profile-user | ProfileController#user | | |
| | GET|HEAD account/change-password | account-change-password | AccountController#getChangePassword | auth | |
| | GET|HEAD asset/encode-file/{id} | encode-file | EncodeController#getEncode | auth | |
| | GET|HEAD asset/edit-file/{id} | edit-file | AssetController#getEdit | auth | |
| | GET|HEAD asset/delete-file/{id} | delete-file | AssetController#deleteDestroy | auth | |
| | GET|HEAD asset/upload-file-form | upload-file-form | AssetController#getUploadCreate | auth | |
| | GET|HEAD asset/library | asset-library | AssetController#getAssetLib | auth | |
| | GET|HEAD account/sign-out | account-sign-out | AccountController#getSignOut | auth | |
| | GET|HEAD account/activate/{code} | account-activate | AccountController#getActivate | guest | |
| | GET|HEAD account/forgot-password | account-forgot-password | AccountController#getForgotPassword | guest | |
| | GET|HEAD account/recover/{code} | account-recover | AccountController#getRecover | guest | |
| | GET|HEAD account/sign-in | account-sign-in | AccountController#getSignIn | guest | |
| | GET|HEAD account/create | account-create | AccountController#getCreate | guest | |
+--------+----------------------------------+------------------------------+--------------------------------------+----------------+---------------+
The HEAD request is almost identical to a GET request, they only differ by a single fundamental aspect: the HEAD response should not include a payload (the actual data).
This makes the HEAD HTTP verb fundamental for managing the validity of your current cached data.
The value for a header field in the response of your HEAD request will warn you if your data is not up-to-date. After that you can make a proper GET request retrieving the updated data.
This can be achieved observing the Content-Length field or the Last-Modified field for example.
When working with large payloads, caching your data and making HEAD requests before the actual GET to check the validity of you current data can save you big money on data consumption.
You will know precisely when to retrieve the full payload.
The big question is: why is Laravel combining HEAD and GET HTTP verbs when you use Route::get()?
You can use Route::match('HEAD') to register your HEAD request, but I find it weird that we don't have Route::head().
From the HTTP RFC:
The HEAD method is identical to GET except that the server MUST NOT return a message-body in the response. The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied by the request without transferring the entity-body itself. This method is often used for testing hypertext links for validity, accessibility, and recent modification.
The response to a HEAD request MAY be cacheable in the sense that the information contained in the response MAY be used to update a previously cached entity from that resource. If the new field values indicate that the cached entity differs from the current entity (as would be indicated by a change in Content-Length, Content-MD5, ETag or Last-Modified), then the cache MUST treat the cache entry as stale.
Following function is taken from the Laravel's Illuminate\Routing\Router.php class, when you use Route::get() method to add a route for your site/application, Laravel adds both methods for the url, it means that, these urls registered using getmethod could be accessed using both GET and HEAD HTTP method, and HEAD is just another HTTP verb/method, used for making a HEAD request.
/**
* Register a new GET route with the router.
*
* #param string $uri
* #param \Closure|array|string $action
* #return \Illuminate\Routing\Route
*/
public function get($uri, $action)
{
return $this->addRoute(array('GET', 'HEAD'), $uri, $action);
}

Resources