How to condition route model bindings by actions in RouteServiceProvider - laravel

I have an application which needs to accomplish this in RouteServiceProvider.php:
//in \App\Providers\RouteServiceProvider.php
public function boot(Router $router)
{
//
parent::boot($router);
//somehow can get the current action
$action = $router->getCurrentAction();
if($action == 'edit'){
$router->model('articles','App\Article');
} else{
$router->bind('articles', function($id){
return Article::published()->findOrFail($id);
});
}
}
Here is my route:
Route::resource('articles', 'ArticlesController');
Here is my Controller:
public function show(Article $article){
return view('articles.show',compact('article'));
}
public function edit(Article $article){
return view('articles.edit',compact(['article','tags']));
}
The reason I want to do it because I want show action only shows published articles while edit action can change both published and unpublished articles.
if there is any better solution, please teach me. Thank you !

Why don't you make a middleware, that will work only for 'published' routes, where you will check if your Article is published?

Based in your route, I assume that you have the following paths:
example.com/edit
example.com/3 - article ID
So, you could use a pattern to bind the article:
public function boot(Router $router)
{
parent::boot($router);
// bind
$router->bind('article', function($id){
return Article::published()->findOrFail($id);
});
// id pattern
$router->pattern('id', '[0-9]+');
}
Now, just declare the respective routes:
Route::get('articles', 'ArticlesController#showAll');
Route::get('{id}', 'ArticlesController#edit');
Finally, your controller could looks like this:
class ArticlesController{
public funcion showAll(){
// show a list of articles
}
public function edit(Article $article){
// selected article
}
}
Try to get the best of Laravel using class injection and keeping cleaning code.

Related

Laravel authorization policy not working on Show page

I have a laravel app using Policies to assign roles and permissions, i cant seem to access the show page and im not sure what im doing wrong?
If i set return true it still shows a 403 error as well, so im unsure where im going wrong here. The index page is accessable but the show page is not?
UserPolicy
public function viewAny(User $user)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
public function view(User $user, User $model)
{
if ($user->isSuperAdmin() || $user->hasPermissionTo(44, 'web')) {
return true;
}
return false;
}
UserController
public function __construct()
{
$this->authorizeResource(User::class, 'user');
}
public function index()
{
$page_title = 'Users';
$page_description = 'User Profiles';
$users = User::all();
return view('pages.users.users.index', compact('page_title', 'page_description', 'users'));
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show($id)
{
$user = User::findOrFail($id);
$user_roles = $user->getRoleNames()->toArray();
return view('pages.users.users.show', compact('user', 'user_roles'));
}
Base on Authorize Resource and Resource Controller documentation.
You should run php artisan make:policy UserPolicy --model=User. This allows the policy to navigate within the model.
When you use the authorizeResource() function you should implement your condition in the middleware like:
// For Index
Route::get('/users', [UserController::class, 'index'])->middleware('can:viewAny,user');
// For View
Route::get('/users/{user}', [UserController::class, 'view'])->middleware('can:view,user');
or you can also use one policy for both view and index on your controller.
I had an issue with authorizeResource function.
I stuck on failed auth policy error:
This action is unauthorized.
The problem was that I named controller resource/request param with different name than its model class name.
F. ex. my model class name is Acknowledge , but I named param as timelineAcknowledge
Laravel writes in its documentation that
The authorizeResource method accepts the model's class name as its first argument, and the name of the route / request parameter that will contain the model's ID as its second argument
So the second argument had to be request parameter name.
// Here request param name is timelineAcknowledge
public function show(Acknowledge $timelineAcknowledge)
{
return $timelineAcknowledge->toArray();
}
// So I used this naming here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'timelineAcknowledge');
}
Solution was to name request param to the same name as its model class name.
Fixed code example
// I changed param name to the same as its model name
public function show(Acknowledge $acknowledge)
{
return $acknowledge->toArray();
}
// Changed here also
public function __construct()
{
$this->authorizeResource(Acknowledge::class, 'acknowledge');
}
I looked over Laravel policy auth code and I saw that the code actually expects the name to be as the model class name, but I couldn't find it anywhere mentioned in Laravel docs.
Of course in most of the cases request param name is the same as model class name, but I had a different case.
Hope it might help for someone.

Different view / method based on middleware using the same route in Laravel

I want to redirect logged in users to the '/' route (like example.com, without anything behind it)
This works:
Route::get('/', Home::class)->name('home');
class Home extends Controller
{
public function __invoke()
{
if(Auth::check()) {
return view('dashboard');
}
else {
return view('welcome');
}
}
}
But now I need to add middleware to this route / controller.
Documentation suggests adding $this->middleware(['auth', 'verified']) to the __constructor in my Home controller.
This doesn't work because it also affects the view for guests (return view('welcome');)
I also tried:
Route::get('/', [Home::class, 'index'])->name('home');
class Home extends Controller
{
public function __construct()
{
$this->middleware(['auth', 'verified'])->only('dashboard');
}
public function index()
{
if(Auth::check()) {
$this->dashboard();
}
else {
$this->welcome();
}
}
public function welcome()
{
return view('welcome');
}
public function dashboard()
{
return view('dashboard');
}
}
But this doesn't work either. Any ideas?
I found this question when Googling for something similar, so I'm writing this for such people, sorry.
This looks like a very old version of Laravel that I'm not familiar with (and it's probably no longer supported), so I'll give my answer for Laravel 9.
To return different views, depending on whether they're logged in, use
// routes/web.php
Route::get('/home', function() {
if (Auth::check()) {
return view('dashboard');
} else {
return view('welcome');
}
});
If you have to use the result of some other policy and call controller methods, use
// routes/web.php
use App\Http\Controllers;
use Illuminate\Support\Facades\Gate;
Route::get('/settings', function () {
if (Gate::allows('manageSystem')) {
return (new Controllers\SettingsController)->index();
} else {
return (new Controllers\UserController)->settings();
}
});
I haven't had a change to test this, but I hope this helps someone

How to get route parameter on difference function on laravel?

I have a problem in my project laravel, I want to get route parameter 'id_project' on route 'project' to use in route 'modulproject'. This route in one view.
This is my Route:
Route::get('project/{id_project}','ProjectDetailController#project');
Route::get('modulproject','ProjectDetailController#modulproject');
This is my Controller:
public function project($id_project)
{
$project=Project::where('id','=',$id_project);
return($project);
}
public function modulproject($id_project)
{
$modulproject=Modul::where('id_project','=',$id_project);
return($modulproject);
}
You can used Session to get that id_project.
use Session;
public function project($id_project)
{
Session::put('id_project',$id_project);
$project=Project::where('id','=',$id_project);
return($project);
}
public function modulproject()
{
if(Session::has('id_project')){
$modulproject=Modul::where('id_project','=',Session::get('id_project'));
Session::forget('id_project');
return($modulproject);
}
else{
return 'redirect to other page.. (custom)';
}
}

Laravel domain group without having to pass domain parameter to controllers

I have all my routes in a domain group but I would like to avoid having the domain as a parameter in each controller method.
So I would like to avoid having this everywhere:
public function show($domain, $id) {}
and would like to just keep it as
public function show($id) {}
I was able to partially make it work with $request->route()->forgetParameter('subdomain') placed in a middleware but it doesn't work in the case of calling redirect()->action('SomeController#show') from a controller method.
Here are some more details:
First, all routes are in a domain group.
Route::middleware(['some_middleware'])->domain('{subdomain}' .website.com)->group(function () {
// .. All routes
} );
Then, in some_middleware I have
public function handle($request, Closure $next) {
// ..
$request->route()->forgetParameter('subdomain');
return $next($request);
}
Then where it doesn't work:
class SomeController {
public function process()
{
// ...
redirect()->action('SimpleController#show', ['simple' => $id]);
}
}
The error I'm getting is:
Missing required parameters for [Route: ] [URI: simples/{simple}].
This only works if I explicitly pass in the subdomain variable.
class SomeController {
public function process()
{
// ...
redirect()->action('SimpleController#show', ['subdomain'=>'some_subdomain', 'simple' => $id]);
}
}
Can anyone suggest a "fix" for this? Thanks in advance :)
With Laravel 5.5+, you can use URL::defaults to set request-wide values for things like the route helper.
https://laravel.com/docs/5.6/urls#default-values
public function handle($request, Closure $next) {
// ..
$subdomain = $request->route('subdomain');
URL::defaults(['subdomain' => $subdomain]);
$request->route()->forgetParameter('subdomain');
return $next($request);
}
You could create a wrapper helper for the action() helper:
if (! function_exists('actionSub')) {
function actionSub($name, $parameters)
{
return action($name, $parameters + ['subdomain' => request()->route('subdomain')]);
}
}
Then use it:
redirect(actionSub('SimpleController#show', ['simple' => $id]));
If someone has a more elegant solution for this, it will be great to see it.

Table Bring all comments for one article using yajra/laravel-datatables

when try to get all comment for one Article by Article::first() but first()
Bring just the first article
i try use find() like
$comments = Article::find()-> commentsArticle()->with('articles');
return Datatables::of($comments)
i get error so how i can Pass a value to view all comments for one article
or
my be there is way without using find()
Article model
class Article extends Model{
public $table = 'articles';
public function commentsArticle() {
return $this->hasMany('App\Comment');
}
}
controller
enter code here
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Yajra\Datatables\Datatables;
use App\Article;
use App\Comment;
class CommentController extends Controller{
public function commentsForOne Article()
{
$comments = Article::all()->commentsArticle->with('articles');
return Datatables::of($comments)->make(true);
}
}
last error i get
ErrorException (E_DEPRECATED)
Non-static method Yajra\Datatables\Datatables::collection() should
not be called statically
I hope find any idea or example like that will help me to learn
You are trying to get the first articles with its comments.
public function commentsForOneArticle($id)
{
$article = Article::fine($id);
//check if article exists to avoid errors
if ( $article ) {
return Datatables::of(Comment::where('article_id', $article->id))->make(true);
}
return "no article with id" . $id;
}
This was just an illustration. But it seems you need to understand first how Eloquent works. Watch this free Laracast https://laracasts.com/series/laravel-from-scratch-2017/episodes/7
For routes, you can define the route like this:
Route::get('comments/{article_id}', 'ArticleController#commentsForOneArticle');
And call it in Ajax like
$.ajax({url: "/comments/1",
success: function(result){
//do stuff here
},
error: function(error) {
console.log(error)
}
});
All this is just a guide and not THE solution.
Edit
To take data with the user in one go
$article = Article::with('user')->find($id);
//will include all the fields from user and article
Comments & author
To get the name of the comment author, you need to define the relationship in comment model
public function user() {
return $this->belongsTo(User::class);
}
Then get like this
if ( $article ) {
return Datatables::of(Comment::with('users')->where('article_id', $article->id))->make(true);
}

Resources