Middleware to convert IDs to slug - php

I've got 90% of my site finished, up until now it's been passing round model IDs from page to page as such:
http://website.domain/2/content/3
I'm using models bound within the web.php like so:
Route::get('{post}/content/{comment}', 'ContentController#index');
This is working fine.
I would like to change the URL so it's more user/SEO friendly so it displays as such:
http://website.domain/hello-world/content/this-is-more
I know I can do a look-up in the controller for every index, however I was wondering if there was a more automated way to convert the URL when an ID is used, such as a middleware perhaps, or is doing a look-up everytime I need to do it the only way forward?
Any help would be appreciated.

You dont need to do that through middleware...
The Eloquent's models has a method that indicates what column will be used by the router to lookup the binded model, you just need to override it.
Example for the post's model:
namespace App\Models\Post;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Route;
class Post extends Model
{
public function getRouteKeyName(): string {
$identifier = Route::current()->parameters()['post'];
if (!ctype_digit($identifier)) {
return 'your-slug-col-name';
}
return 'id';
}
}
This way your route will work with id or slug...

One easy way is use explicit model binding
in app/Providers/RouteServiceProvider.php in method boot you can define your bindings
For ex:
public function boot()
{
Route::bind('postSlug',function($value){
return Post::whereSlug($value)->firstOrFail();
});
Route::bind('commentSlug',function($value){
return Comment::whereSlug($value)->firstOrFail();
});
parent::boot();
}
and in your Route.php:
Route::get('{postSlug}/content/{commentSlug}', 'ContentController#index');
hope this is helpful

Related

How to pass a model instance to all controllers and views based on a route parameter

I am working on a Laravel control panel project where we should be able to toggle from one site to another and get the detail of the site based on the ID passed in the route.
In itself this is quiet easy to do but as I will have several controllers using this technique it means for each controller and each controller instance I will have collect the site instance and it does not look very user friendly due to the many repetitions.
Here is what I have:
Route:
Route::get(
'cp/site/{website}/modules/feeds',
'App\Http\Controllers\Modules_sites\Feeds\FeedController#index'
)->name('module_site.feeds.index');
Model:
class Website extends Model
{
use HasFactory;
protected $primaryKey ='site_id';
}
The database is simple with an id (site_id) and name
Controller:
public function index(Website $website)
{
dd($website -> name);
}
The above is working fine but I am going to end with dozens of methods across multiple controllers doing the same thing, and what if changes are required.
I have looked at the ID of using the AppServiceProvider to create the Website instance and then pass it to the controllers and views but I can't do this as the route is not defined at this stage and I only seem to be able to pass this to the view.
Essentially, I am looking to create something similar to the auth()->user() method that is available from controllers and routes without the needs to pass it to each controller.
Is this possible?
Perhaps you could use middleware to set this value? Something like this to put it in the session globally:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckWebsite
{
public function handle(Request $request, Closure $next): mixed
{
$request->session()->put("website", $request->route("website"));
return $next($request);
}
}
Or this on a per-controller basis:
<?php
namespace App\Http\Controllers\Modules_sites\Feeds;
use App\Http\Controllers\Controller;
use Closure;
use Illuminate\Http\Request;
class FeedController extends Controller
{
public function __construct()
{
$this->middleware(function (Request $request, Closure $next) {
$this->website = $request->route("website");
return $next($request);
});
}
public function index()
{
dd($this->website->name);
}
}
Also worth mentioning that routes are not defined like that in Laravel 8 any longer. It should look like this:
Route::get(
'cp/site/{website}/modules/feeds',
[FeedController::class, 'index']
)->name('module_site.feeds.index');
With an appropriate import for the controller class.
as you primary key is not id so it will not work automatically you need to tell laravel to search by column name
code will be
Route::get('cp/site/{website:site_id}/modules/feeds', 'App\Http\Controllers\Modules_sites\Feeds\FeedController#index')->name('module_site.feeds.index');
you need to use {website:site_id}
ref link https://laravel.com/docs/8.x/routing#customizing-the-default-key-name

Why does only some laravel routes return model attributes?

i´m studying laravel but having some doubts..
Controller
namespace App\Http\Controllers;
use App\ItemNfe;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class ItensNfeController extends Controller
{
public function edit($id,ItemNfe $itemNfe)
{
//i don´t want to have to make this select below
//$itemNfe = DB::table('itens_nfe')->where('id_itemnfe',$id)->get();
// dd($itemNfe); this dd() returns model attributes on few of my controllers only
return view...
}...
Model: (note i´m not using laravel convention but it´s informed)
namespace App;
use Illuminate\Database\Eloquent\Model;
class ItemNfe extends Model
{
protected $table = 'itens_nfe';
protected $primaryKey = 'id_itemnfe';
protected $fillable = [
'id_itemnfe','fk_venda', 'fk_produto'...
];
public function nfe()
{
return $this->belongsTo('App\Nfe'); //this is one diference among others models, but apparently doesn´t affects when i tested without this code.
}
}
The route i´m using is the same for everyone.. "resource routes"
At the first 2, i have the attributes returning, but not at the last one...
Route::resource('/usuarios', 'UsuariosController');
Route::resource('/nfes', 'NfesController');
Route::resource('/itensnfe', 'ItensNfeController');
The Url used is:
https://localhost/erpoverweb/public/itensnfe/1/edit
If needing more code please tell me... thanks!
If you don't want to manually search the database for the entry, you can use Laravel Container do perform a Dependency Injection. https://laravel.com/docs/7.x/container#introduction
public function edit(ItemNfe $itemNfe)
{
// Returns the model, and you didn't need to manually searched.
// Laravel automaticly injects this for you.
dd($itemNfe);
}
Sounds like you are looking for Route Model Binding (implicit at that). This requires that the route parameter name and the name of the parameter of the method signature for that route match.
public function edit(ItemNfe $itensnfe)
The resource route with resource name 'itensnfe' should make the parameter 'itensnfe'.
If you don't make these match you will just end up with Dependency Injection which would inject a new model instance.
Laravel 7.x Docs - Routing - Route Model Binding - Implicit Binding

Routes inside controllers with laravel?

I have been declaring all the routes for my application inside web.php , but it is now getting quite large. I find that I am losing a lot of time shifting between web.php and each controller and this is hurting productivity.
I feel like it would be better to define routes inside of the controller, perhaps ideally delegating some URL to a controller and then allowing the controller to handle the "sub routes" since this would allow me to use inheritance when I have two similar controllers with similar routes.
It is not possible given how laravel works. Every request is passed onto router to find its designated spot viz. the controller with the method. If it fails to find the route within the router, it just throws the exception. So the request never reaches any controller if the route is not found. It was possible in earlier versions on Symphony where you would configure the route in the comment of a particular controller method.
Sadly with laravel it works how it works.
But for me, I just like to have the routes in a separate file.
Alternate solution, easier way to sort all the routes.
You can move your route registration into controllers if you use static methods for this. The code below is checked in Laravel 7
In web.php
use App\Http\Controllers\MyController;
.....
MyController::registerRoutes('myprefix');
In MyController.php
(I use here additional static methods from the ancestor controller also posted below)
use Illuminate\Support\Facades\Route;
.....
class MyController extends Controller {
......
static public function registerRoutes($prefix)
{
Route::group(['prefix' => $prefix], function () {
Route::any("/foo/{$id}", self::selfRouteName("fooAction"));
Route::resource($prefix, self::selfQualifiedPath());
}
public function fooAction($id)
{
........
}
In Controller.php
class Controller extends BaseController {
....
protected static function selfAction($actionName, $parameters = [], $absolute = false)
{
return action([static::class, $actionName], $parameters, $absolute);
}
protected static function selfQualifiedPath()
{
return "\\".static::class;
}
protected static function selfRouteName($actionName)
{
//classic string syntax return "\\".static::class."#".$actionName;
// using tuple syntax for clarity
return [static::class, $actionName];
}
}
selfAction mentioned here is not related to your question, but mentioned just because it allows making correct urls for actions either by controller itself or any class using it. This approach helps making action-related activity closer to the controller and avoiding manual url-making. I even prefer making specific functions per action, so for example for fooAction
static public function fooActionUrl($id)
{
return self::selfAction('foo', ['id' => $id]);
}
Passing prefix into registerRoutes makes controller even portable in a sense, so allows inserting it into another site with a different prefix in case of conflict

Laravel, calling controller method from blade template file

Heey guys! I use Laravel 5.4, WAMP for localhost. I am struggling with the problem to call a Controller#methodName within my header.blade.php file, because I want to show in my header.blade.php file all notifications for the User. Normally I was getting all needed data with the help of routes in different pages. But for this case I need to call without using routes. Here is my code for my NotificationController:
class NotificationController extends Controller
{
public function getNotification(){
$notifications = Notification::where('user_id',Auth::user()->id)->get();
$unread=0;
foreach($notifications as $notify){
if($notify->seen==0)$unread++;
}
return ['notifications'=>$notifications, 'unread'=>$unread];
}
}
And I should receive all these data in my header file. I have used: {{App::make("NotificationController")->getNotification()}}
and {{NotificationController::getNotification() }} But it says Class NotificationController does not exist. Please heelp!
Instead of calling the controller method to get notifications, you can make a relationship method in your User model to retrieve all the notifications that belongs to the user and can use Auth::user()->notifications. For example:
// In User Model
public function notifications()
{
// Import Notification Model at the top, i.e:
// use App\Notification;
return $this->hasMany(Notification::class)
}
In your view you can now use something like this:
#foreach(auth()->user()->notifications as $notification)
// ...
#endforeach
Regarding your current problem, you need to use fully qualified namespace to make the controller instance, for example:
app(App\Http\Controllers\NotificationController::class)->getNotification()
Try using the full namespace:
For instance, App\Http\Controllers\NotificationController::getNotification
but of course, controllers aren't meant to be called the way you're using them. They're meant for routes. The better solution is to add a relationship in your user model like so:
public function notifications()
{
return $this->hasMany(Notification::class)
}
And then use this in your view like so:
#foreach(Auth::user()->notifications as $notification)

Laravel 5.1 - View variables(specifically current controller name) for a controller

What I wanna do is to know, inside a view, if I'm in a specific controller or not. From what I know, I've got two choices and I don't have the answer to either of them :-D
inject a view variable using the share method in my AppServiceProvider, which involves getting the current controller name(or at least the action name so that I can switch it) inside the service provider.
inject a variable to all the views returned in the controller. For example does controllers have a boot method? Or can I override the view() method in the following code snippet?
public function someAction(Request $request)
{
return view('someview', ['myvar' => $myvalue]);
}
well of course there's the easy (yet not easy :|) solution: add the variable in all methods of the controller. I don't like this one.
Thanks
You could use the controller's construct function.
Add this to the top of your controller:
public function __construct()
{
view()->share('key', 'value');
}

Categories