Laravel resource: How define the name of the key parameter? - laravel

When you define a resource with Route::resource('recipe', 'RecipeController');, among others, the following route is defined: /photo/{photo}/edit, and once you define all your resources you have something like this:
/recipes/{recipes}/edit
/allergens/{allergens}/edit
/ingredients/{ingredients}/edit
Because all my records use id as primary key (MongoDB), I'd like to have {id} instead, like so:
/recipes/{id}/edit
/allergens/{id}/edit
/ingredients/{id}/edit
I dug in the Router class but I don't see how to specify this.
More over when I create a form with Form::model($record) I get actions like /recipes/{recipes} because recipes is a property of $record.
How can I define the name of the key parameter to id instead of recipes, allergens, ingredients?

In order to change the param name for Route::resource, you need custom ResourceRegistrar implementation.
Here's how you can achieve that in a shortest possible way:
// AppServiceProvider (or anywhere you like)
public function register()
{
$this->app->bind('Illuminate\Routing\ResourceRegistrar', function ($app) {
// *php7* anonymous class for brevity,
// feel free to create ordinary `ResourceRegistrar` class instead
return new class($app['router']) extends \Illuminate\Routing\ResourceRegistrar
{
public function register($name, $controller, array $options = [])
{
if (str_contains($name, '/')) {
return parent::register($name, $controller, $options);
}
// ---------------------------------
// this is the part that we override
$base = array_get($options, 'param', $this->getResourceWildcard(last(explode('.', $name))));
// ---------------------------------
$defaults = $this->resourceDefaults;
foreach ($this->getResourceMethods($defaults, $options) as $m) {
$this->{'addResource'.ucfirst($m)}($name, $base, $controller, $options);
}
}
};
});
}
Now your routes will look like:
Route::resource('users', 'UsersController', ['param' => 'some_param'])
/users/{some_param}
// default as fallback
Route::resource('users', 'UsersController')
/users/{users}
Mind that this way can't work for nested resources and thus they will be a mix of default and custom behaviour, like this:
Route::resource('users.posts', 'SomeController', ['param' => 'id'])
/users/{users}/posts/{id}

I know this is 4 year old question but for anyone who is googling; you can pass a third argument to override key naming:
Route::resource('ingredients', 'IngredientController', ['parameters' => ['ingredients' => 'id']]);
Or
Route::resource('ingredients', 'IngredientController')->parameters(['ingredients' => 'id']);

You can pass your ids to the routes you don't necessary need to change the parameters {recipes} to {id} since the parameters are just a placeholder.
So
public function edit($recipes){
// code goes hr
}
is the same as this
public function edit($id){
// code goes hr
}
for this route /recipes/{recipes}/edit

Related

Laravel 8: Storing custom route attributes

What is the most simple way to store custom attributes such as page titles or other key value pairs that may be attached to a route?
For example, say I want to add my own metadata data to:
Route::get('/themetest', [MyController::class, 'list'])->name('themetest');
I thought I could add a route macro to save metadata to be retrieved later using an addMetadata method like
Route::get('/themetest', [MyController::class, 'list'])->name('themetest')->addMetadata('title' => 'Page Title');
Is that possible? Doesn't seem like it is.
Is there a standard way to store this type of info? Or, any practical way? I thought maybe I could store them using default(), but that could change the default parameters for a controller function.
You could use the 'action' array of the Route to store this information if you had to:
// In a Service Provider # boot
Illuminate\Routing\Route::macro('addMetaData', function ($key, $value) {
$this->action['meta'][$key] = $value;
return $this;
});
Illuminate\Routing\Route::macro('getMetaData', function ($key = null) {
return is_null($key)
? $this->getAction('meta')
: $this->getAction('meta.'. $key);
});
// Route definition
Route::get('/themetest', [MyController::class, 'list'])
->name('themetest')
->addMetaData('title', 'Page Title');
// Controller method (Route action)
public function list(Request $request)
{
dump($request->route()->getMetaData('title'));
}

Set $pageName globally in pagination Laravel

We can use a custom $customPageName in laravel's paginate() function's 3rd parameter.By that we can use site.com/url?$customPageName=n . Is there any way we can set the custom page name globally ? In AppServiceProvider or somewhere like that?So we don't need to define every time?
There are many possible solution. I give you two that IMO are the best:
1: Create a trait and use it in every component
I love to use traits, they avoid a lot of redundancy and can be easily invoked everywhere:
trait PaginationTrait
{
const PAGE_NAME = 'your_global_value'
public function paginate($query, $perPage, $columns = [''], $page_name = null)
{
return $query->paginate($perPage, $columns, $page_name ?: self::PAGE_NAME);
}
}
This solution requires anyway that you pass as input a Builder instance, and may cause a logic problem. So let's move to the next solution
2: Config files
Simply set a variable in an existing config file or in a new config file:
// Example: app.php
return [
// [...]
'page_name' => 'your_custom_value' // or env('APP_PAGE_NAME', 'your_custom_value'),
// [...]
];
And in your controller you can retrive the value as follows:
public function index() {
// [...]
$result = MyModel::paginate($per_page, $columns, config('app.page_name'));
}
3: App\Http\Controllers\Controller
The third solution is the easiest. Set a constant in your App\Http\Controllers\Controller class (as I wrote for the trait part) and by the OOP rules, it will be inherited to all your controllers:
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
const PAGE_NAME = 'your_global_value';
}
And in your controller:
public function index() {
// [...]
$result = MyModel::paginate($per_page, $columns, self::PAGE_NAME);
}
In my opinion, if you simply have to set this global variable, solution 2 and 3 are the best... If you have to create a custom pagination logic, then I think that creating a specific Trait or Class is a good choice

recover the slug of a category linked to another category Laravel

I would like to recover the slug of 2 categories from my routes but can’t write the Controller.
My Route
Route::get('technicians/o/{occupation}/c/{city}', 'User\TechnicianController#viewoccupationcity');
My Controller
public function viewoccupationcity($slug)
{
$technicians = TechnicianResource::collection(occupation::where('slug',$slug)->firstOrFail()->technicians()
->with('city','occupation')
->latest()->get());
return $technicians;
}
Route::get('technicians/o/{occupation}/c/{city}', 'User\TechnicianController#viewoccupationcity');
Your controller will accept the parameters from your route as variables by order
public function viewoccupationcity($ocupation, $city)
{
...
}
Example:
URL: technicians/o/foo/c/bar
public function viewoccupationcity($ocupation, $city)
{
// $ocupation will be 'foo'
// $city will be 'bar
}
Ok, you would need to retrieve 2 variables as that is what you are passing
public function viewoccupationcity($occupation, $city)
If you want the whole slug to do another search then you would use the $request object. So like so
public function viewoccupationcity(Request $request, $occupation, $city){ // You also need to include the Request decleration
$slug = $request->path();
$technicians = TechnicianResource::collection(occupation::where('slug',$slug)->firstOrFail()->technicians()
->with('city','occupation')
->latest()->get());
return $technicians;
}
EDIT: We are having to do a lot of guesswork as your question isn't very clear. I think what you are trying to achieve is probably this
public function viewoccupationcity($occupation, $city){
$technicians = TechnicianResource::collection(occupation::where('city',$city)->where('occupation',$occupation)->firstOrFail()->technicians()
->with('city','occupation')
->latest()->get());
return $technicians;
}
If you need something more then you need to give more details

Laravel 5: How to create a router model binding on multiple parameters

So far I know how to create a router model binding on single parameters like so:
// RouteServiceProvider.php
$router->model('subject_slug', 'App\Subject', function($slug) {
return Subject::where('slug', $slug)->firstOrFail();
});
The above can then be used like this:
// routes.php
Route::get('/{subject_slug}', 'MenuController#showSubject');
And in the controller:
public function showSubject(Subject $subject) {
....
}
But sometimes I need to specify multiple parameters in order to get the right model.
For example consider the following route:
Route::get('/{subject_slug}/{topic_slug}/', 'MenuController#showTopic');
and the corresponding controller:
public function showTopic(Subject $subject, Topic $topic) {
....
}
However to get the correct model for Topic I need to know the Subject. For example:
// !!! Invalid laravel code !!!
$router->model('topic_slug', 'App\Topic, function($subject_slug, $topic_slug) {
// ERROR: $subject_slug is obviously not defined!
return Topic::where([
'subject_slug' => $subject_slug,
'slug' => $topic_slug,
])->firstOrFail();
});
How can I make a router model binding for Topic bearing in mind I need to know the Subject parameter before it in order to fetch the correct Topic.
Is there an alternative better way of doing this?
UPDATE
Currently my showTopic method in my controller is like this:
public function showTopic(Subject $subject, $topic_slug) {
$topic = Topic::where([
'subject_slug' => $subject_slug,
'slug' => $topic_slug,
])->firstOrFail();
// ...
}
and I have no router model binding for topic_slug.
This works as expected, but I would like to take advantage of router model bindings!
It turns out the way I was doing it was a bit flawed. I was unnessarily using model bindings when instead it would be better to have used a normal binding like so:
$router->bind('topic_slug', function($slug, Route $route) {
$subject = $route->parameter('subject_slug');
return Topic::where([
'subject_slug' => $subject->slug,
'slug' => $slug,
])->firstOrFail();
});
Also I was using model bindings completely wrong before as the 3rd function should be the "not found behaviour" (not for additional logic)!

Route resources set before auth not working in Laravel 4

I added this in routes.php, expected it will check the authentication session for the page, however it is not working.
Route::resource('ticket', 'TicketController', array('before' => 'auth') );
Then I go to the controller, work in another way. It's work.
class TicketController extends BaseController {
public function __construct()
{
$this->beforeFilter('auth');
}
May I know where can get more documentation regarding the Route::resource(), what type of argument it able to accept?
OK... I found the answer.
in
\vendor\laravel\framework\src\Illuminate\Routing\Router.php
public function resource($resource, $controller, array $options = array())
{
// If the resource name contains a slash, we will assume the developer wishes to
// register these resource routes with a prefix so we will set that up out of
// the box so they don't have to mess with it. Otherwise, we will continue.
if (str_contains($resource, '/'))
{
$this->prefixedResource($resource, $controller, $options);
return;
}
// We need to extract the base resource from the resource name. Nested resources
// are supported in the framework, but we need to know what name to use for a
// place-holder on the route wildcards, which should be the base resources.
$base = $this->getBaseResource($resource);
$defaults = $this->resourceDefaults;
foreach ($this->getResourceMethods($defaults, $options) as $method)
{
$this->{'addResource'.ucfirst($method)}($resource, $base, $controller);
}
}
protected function getResourceMethods($defaults, $options)
{
if (isset($options['only']))
{
return array_intersect($defaults, $options['only']);
}
elseif (isset($options['except']))
{
return array_diff($defaults, $options['except']);
}
return $defaults;
}
as you can see, it only accept only and except arguement only.
If you want to archive the same result in route.php, it can be done as below
Route::group(array('before'=>'auth'), function() {
Route::resource('ticket', 'TicketController');
});

Resources