How to influence execution's priority of the custom Request class? - laravel

As you might already know from the docs, Laravel's custom Request class (make:request using artisan) provides a possibility to keep validation rules inside of it. So, I have the following code's examples to describe the problem of the prioritization better:
api route
Route::post('/suggest', [SuggestionController::class, 'store'])
->middleware(['auth:sanctum']);
Request class
class SuggestionRequest extends FormRequest
{
public function authorize()
{
return $this->user() && $this->user()->hasRole('sales manager');
// return true;
}
public function rules() {...}
}
Here, I want to force my vendor middleware auth:sanctum (which checks if the current user has a token) to be executed before the SuggestionRequest class calls its own authorize method, because it doesn't make sense to validate the request's data and then check if the user is authenticated. Also, there's no need to have a quite similar logic in both places. Which way should I refactor my code? Are there any good approaches for it?

Related

Laravel 8 - Global Object available throughout application (not just in view files)

In my application, users can belong to different accounts and have different roles on those accounts. To determine which account is "current" I am setting a session variable in the LoginController in the authenticated() method.
$request->session()->put('account_id', $user->accounts()->first()->id);
Then, throughout the application I am doing a simple Eloquent query to find an account by ID.
While this "works", I am basically repeating the same exact query in every single Controller, Middleware, etc. The maintainability is suffering and there are duplicate queries showing in Debugbar.
For example, in every controller I am doing:
protected $account;
public function __construct()
{
$this->middleware(function($req, $next){
$this->account = Account::find($req->session()->get('account_id'));
return $next($req);
});
}
In custom middleware and throughout the entire application, I am essentially doing the same thing - finding Account by ID stored in session.
I understand you can share variable with all views, but I need a way to share with the whole application.
I suppose much in the same way you can get the auth user with auth()->user.
What would be the way to do this in Laravel?
I would create a class to handle this logic. Making it a singleton, to ensure it is the same class you are accessing. So in a provider singleton the class you are gonna create in a second.
$this->app->singleton(AccountContext::class);
Create the class, where you can set the account in context and get it out.
class AccountContext
{
private $account;
public function getAccount()
{
return $this->account;
}
public function setAccount($account)
{
$this->account = $account;
}
}
Now set your account in the middleware.
$this->middleware(function($req, $next){
resolve(AccountContext::class)->setAccount(Account::find($req->session()->get('account_id')));
return $next($req);
});
Everywhere in your application you can now access the account, with this snippet.
resolve(AccountContext::class)->getAccount();

Can a Laravel POST route return a view instead of redirecting to another route?

I have seen/worked on lot of projects where a Laravel POST route looking like this:
Route::post('/some-url', [SomeController::class, 'someMethod']);
And the controller with its method which takes care of the route looking like this:
class SomeController extends Controller
{
public function someMethod(Request $request) {
// My Logic to do something with the post data - $request
return redirect("/some-other-url");
}
}
have always redirected to a specific URL after doing something with the POST data, by convention.
I would like to know, if after processing a POST request, if it's okay to just return a view like what we do with GET requests? I know it works, but is it just convention or are there any issues in doing so?
Eg:
class SomeController extends Controller
{
public function someMethod(Request $request) {
// My Logic to do something with the post data - $request
return view("somebladeview")->with(["result" => $result ]);
}
}
The pattern is called post-redirect-get, or PRG. It is convention because POST should be used for requests that change data, whereas GET should be used to simply display data.
If you return a view after a POST, the user can hit reload, and (probably unintentionally) change data again, eg buy something a second time. By using PRG, the user hitting reload simply reloads the last GET, which just re-displays something.
NOTE: There is some advice in the comments on your question to ignore this convention. I think that is unwise and dangerous, these conventions exist for good reasons.

Need Laravel Route::controller and Route::controllers

As we know in Laravel 5.2 Route::controller() and Route::controllers() method was deprecated but it was very handy for reducing the number of routes. I was able to write simple route like this Route::controller('admin/invoice','InvoiceController'). With this simple one route, I can manage all things related to making invoice related work by a controller.
class InvoiceController extends Controller{
public function getInvoices(){ }
public function getInvoiceDetails(){ }
public function postStoreInvoice(){ }
public function postUpdateInvoice(){ }
public function postStoreInvoiceDetails(){ }
public function postupdateInvoiceDetails(){ }
public function postDeleteInvoice(){ }
public function postDeleteInvoiceDetails(){ }
....
}
but unfortunately this Route::controller() and Route::controllers() no longer available laravel version > 5.1. An option available Route::resource() but it has a limited number of the route. The laravel route is Macroable, there is an option to extend the route features like
Illuminate\Routing\Router::macro('controller', function ($routes) {
// implementation
});
Is there anyone who implements Route::controller() and Route::controllers() method for Laravel 5.8, 6 ? or suggest any way.
You can use Route::resource() or Route::resources().
Example:
Route::resource('books', 'BookController');
this will assumes you have
class BookController extends Controller {
// to list resources.
public function index();
// to show create form.
public function create();
// to store resource in database.
public function store();
// to show single resource.
public function show();
// to show edit form.
public function edit();
// to edit and then store the modified resource in database.
public function update();
// to delete a resource from database.
public function destroy();
}
You should read https://laravel.com/docs/master/controllers#resource-controllers for more information.
Edit
Implicit controllers was removed in version 5.2 for some reason.
If you come from the CodeIgniter world, then you may have warm and fuzzy
feelings for implicit routing. You know, where the URI matches up to
the controller method that will be called. You might even want this
for your Laravel development (which Laravel can do).
Though it might seem useful at first to simply call
Route::controller('admin', 'AdminController') and then declare all of
your desired routes from the controller, there are a number of
setbacks to this. Think about how you would, when using implicit routing,
leverage named routes, or create nested resources, or even do
something as simple as rename your controller class without affecting
your URI design.
No, when it comes to implicit routing, just say no.
source: https://laracasts.com/lessons/say-no-to-implicit-routing
However if you want this functionality you can use this package:
Laravel Routes Publisher or Laravel Advanced Route

Different state for Eloquent model fields depending on current user in laravel

I have the model:
class Task extends Model {
}
with some fields
protected $fillable = ['message', 'due_time', 'status', 'etc...'];
I've added custom function:
public function getEditableStateFor{AttributeName}
In my helper function I check that if
method_exists($class, 'getEditableStateForField1')
than I allow to edit this field depending on boolean value returned from this function.
Example:
if( ! $class->getEditableStateForField1() ) {
return "You can not edit field field1";
}
Here is how looks like some functions in Task:
private function isCreator() {
$user = Auth::user();
if($user) {
return $user->id === $this->creator_id;
}
return false;
}
public function getEditableStateForMessage() {
return $this->isCreator();
}
public function getEditableStateForDueTime() {
return $this->isCreator();
}
Is this a good way to do it or it is very bad design because of hidden dependency on Auth::user()?
What is a better way?
I do not want to put this logic inside controllers because this logic propagates to another models and is universal across application.
I'm like you and like to have Models that contain as much of the business logic as possible while remaining totally free of depencies on the "web" part of the application, which I believe should stay in Controllers, Request objects, etc. Ideally, Models should be easily usable from command line interfaces to the application, from within the Tinker REPL, and elsewhere while still guaranteeing data integrity and that business rules are observed.
That said, it seems the Laravel creators had slightly different ideas, hence the Auth facade being easily available in the model.
What I would likely do is add a parameter of type User to the getEditableStateFor series functions, and then in turn pass that parameter to isCreator ($user) and elsewhere. That also frees you up to be able to allow associated users to edit each other's Tasks if that ever became a desired feature in the future.
Edit: another, perhaps better or perhaps worse, is to have an instance method like setCurrentUser ($user) then use setFieldNameAttribute methods so that the controller doesn't have to check the editability of fields, keeping that the model's responsibility. Then you could call the getEditableStateFor methods, which now check for the current user set by the above method (maybe falling back to Auth::user() or throwing a helpful error), inside the setter.

Laravel 5 route protection

Assume we´ve got a User and Conversation model with a many-to-many relation.
class User extends Model ... {
public function conversations()
{
return $this->belongsToMany('App\Conversation');
}
}
class Conversation extends Model {
public function users()
{
return $this->belongsToMany('App\User');
}
}
Besides authentication (logging in) which comes out of the box with laravel: How can I protect a specific conversation route for it´s related users?
Which would be the most maintainable way to achieve this? Middleware? Guard? Route model binding? ... right now I´m a bit lost ...
Good question. In this case you'd be best off using Laravel's authorization features. Here are the differences:
Middleware: used to run logic based on either routes or logged in / logged out state. So, if you want to block the conversations entirely from non-logged in users, use a middleware.
Authorization (policies): not to be confused with authentication, is intended for cases where the rules to block someone is not based on route but on some other, more specific reason. These reasons can be anything from roles, to teams, entity ownership, and so on. If you wanted to hide a conversation to only those in the conversation, you can create a policy that kicks the user back to their previous page if they were not in the conversation.
Here's a quick policy you might create:
class ConversationPolicy {
public function view(User $user, Conversation $conv) {
return in_array($user->id, $conv->users->pluck('id'));
}
}
You could check your policy in a controller like the following:
if($request->user()->can('view', $conversation))
{
return view('conversation', ['conversation' => $conversation]);
}
return back()->withError('You are not authorized to view this conversation');
Just be aware you'll have to bind this policy in the AuthServiceProvider before it can be used.

Resources