Say I have a route like:
Route::get('users/{user}/posts/{post}', 'PostController#show')
And I've set up Route Model binding for an App\User to {user} and an App\Post to {post}. I've seen I'm able to call whatever existing post for any given user to get contents on the screen. Is there a generic place where I can assign constraints to the bound models?
you can use Route::bind and set a second variable for the function to access the current route and it parameters like this:
class RouteServiceProvider extends ServiceProvider{
public function boot(Router $router)
{
$router->bind('user', function($value) {
return App\User::findOrFail($value);
});
$router->bind('post', function($value, $route) {
return $route->parameter('user')->posts()->findOrFail($value);
});
}
}
You can use $router->bind() to control how a model is fetched:
Route::get('/user/{name}', 'PostController#show');
$router->bind('user', function($value) {
return App\User::where('name', $value)->first();
});
This certainly is a gap in laravel. I made a little package to help.
https://github.com/tarekadam/laravel-orm-binding-validation
Use middleware not validation.
Append the middleware so that binding already ran.
Define the expected relationships in the controller.
See README.md on the package.
Related
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
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
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
I have a model called Dbtable which isn't injected when used like this:
public function showEditDbTableForm(Request $request, DbTable $table)
{
}
it only works when I do this:
public function showEditDbTableForm(Request $request, $id)
{
$table = DbTable::find( $id );
}
Same thing happens even when I rename DbTable to DbTble
P.S.: please don't be rude with me as I'm new to Laravel framework
For Implicit Route Model Binding you need to make sure the parameter in the method signature has the same name as the route parameter you want to bind.
Route::get('widgets/{widget}', 'WidgetsController#show');
public function show(Widget $widget)
Laravel automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name.
Laravel 5.6 Docs - Routing - Implicit Model Binding
In RouteServiceProvider class add
public function boot()
{
parent::boot();
Route::model('db-table', App\DbTable::class);
// db-table correspond your rout parameter
}
see official documentation https://laravel.com/docs/5.5/routing Explicit Binding section
How can I make a router like this
Route::any("/{controller}/{method}/{param}", "$controller#$method");
So that instead of specifing every single method in the routes file, I would be able to define a route for most cases for the convention http://example.com/controller/method/param
In Laravel 4.2 you can use [implicit controller][1].
Laravel allows you to easily define a single route to handle every action in a controller. First, define the route using the Route::controller method:
Route::controller('users', 'UserController');
The controller method accepts two arguments. The first is the base URI the controller handles, while the second is the class name of the controller. Next, just add methods to your controller, prefixed with the HTTP verb they respond to:
class UserController extends BaseController {
public function getIndex()
{
//
}
public function postProfile()
{
//
}
}
https://laravel.com/docs/4.2/controllers#implicit-controllers
Like this:
Route::any('{controller}/{method}/{param}', function ($controller, $method, $param) {
return call_user_func_array($controller.'::'.$method, $param);
});