Laravel Gate User parameter with eager loading relationship - laravel

As far as i know, the $user paramater on Laravel's Gate by default will returning instance of user credentials object which came from EloquentServiceProvider (not from User Model).
Gate::define('create-something', function ($user) {
// Code
}
But, how to add eager loading of relationship on it ? Lets say i have relationship called stuff on User Model, so i want to access it like so :
Gate::define('create-something', function ($user) {
dd($user->stuff);
}
Maybe easiest solution is by passing another parameters which came from Models\User , but it seems duplicating/redundant considering the $user parameter already there by default.
Is the another way to achieve this ?
Add notes : Stuff is many to many relationship

you can do something like this:
Gate::define('create-something', function ($user) {
$user->load('stuff');
dd($user->stuff);
}

Related

Laravel Automatic resource CRUD

What patterns can I use for 'automatic' resource CRUD operations for given Models in Laravel?
Say I have two models SomeModel and SomeRelatedModel where some_related_model.some_model_id is an FK to SomeModel.
The standard method on the SomeModelController for handling the create POST /api/someModel might look like this:
public function store(Request $request)
{
$user = Auth::guard('api')->user();
$data = $request->get('data');
$data['user_id'] = $user->id;
$someModel = SomeModel::create($data);
// has this request been made with the data for the
// related model? If so create this too.
if($data['relatedModel']){
SomeRelatedModel::create(array_merge(
['some_model_id' => $someModel->id]
$data['relatedModel']
));
}
// has this request been made expecting to get related
// models back in the response? If so load these
if($request->has('with')){
$someModel->load($request->get('with'));
}
return (new PostResource($post))
->toResponse($request)
->setStatusCode(201);
}
This works but is very verbose and for models with a sub-sub relation would need changing further. Similar work will need to be done for the other endpoints for all resources.
Is there a more versatile (or tidy) pattern using out-of-the box classes to get a similar effect?
Have a look at Laravel Orion. Fits your use case.

getting infinite loop - adding scope on user model with traits

after adding the following trait to the user model, I get a 500 error because of an infinite loop.
trait Multitenantable
{
public static function bootMultitenantable()
{
static::addGlobalScope('tenant_id', function (Builder $builder) {
$tenant_id = 1;
if ( auth()->check() )
{
$tenant_id = Auth::user()->tenant_id;
}
$builder->where('tenant_id', '=', $tenant_id);
});
}
}
when I remove either the trait from the user model or the if containing the auth() part (lines 7,8,9,10) from this trait, the infinite loop resolves.
I'm not familiar with the magic behind laravel, could someone explain why this happens?
and how I could add global scope for multitenancy to the user model like the others?
I've followed this instruction to add multitenancy to my laraval project.
I resolve this by creating trait and used the trait on user model
use Illuminate\Database\Eloquent\Builder;
trait FilterByClass
{
protected static function booted()
{
if( request()->session()->exists('auth')){
$user = auth()->user();
self::addGlobalScope(function(Builder $builder)use($user){
if($user->hasRole('Head Teacher')){
return;
}
$builder->where('branch_id',$user->class_id);
});
}
}
}
I figured the loop happens because you are trying to authenticate from the user model and at the same time the model is looking for an authenticated user hence use are deadlocked.
so by checking if an request()->session()->exist('auth') exist before applying the scope will make sure it check for an auth()->user() only if it exist.
Apparently when I add the auth condition to this trait, I cannot add the trait to user model.
If I do, when the user model is booting, It must check the Auth, wich requires booting the user model. And this cause the infinite loop.

How to use custom keys in laravel routes without scoping?

When using custom keys Laravel forces us with scoping, for example, I have a route to getting a country and a post
api/countries/{country:slug}/posts/{post:slug}
but I can't get that using slug key because it doesn't have a relation with country, and in this case, I want to handle scope myself and I don't need implicitly scope binding, but I get an error (Call to undefined method App\Country::posts() ).
so because of that I cant using this Laravel feature. is there a way to turn the implicitly scope binding off?
If the posts are not related to the countries, it may not make sense to nest them in the URI?
But, nonetheless, to answer your question, you need to do one of two things:
Instead of setting {country:slug}, just use {country} and then override getKeyRouteName() function on your Country and Post models.
Alternatively, especially if you want to use the ID elsewhere, use explicit model binding.
To use a slug without custom keys in the routes file
class Post
{
[...]
public function getRouteKeyName()
{
return 'slug';
}
}
To use explicit route model binding
Add the following to the boot() method of your RouteServiceProvider:
public function boot()
{
parent::boot();
Route::bind('post', function ($value) {
return App\Post::where('slug', $value)->firstOrFail();
});
}

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.

Models accessible only for authenticated user THAT CREATED THEM (Laravel)

I'm writing a software application to let the people have their own private archive of cooking recipes.
The RecipeController constructor contains:
$this->middleware('auth')
because only registered users can use recipes, but I need to protect also the access to the models.
The point is that users can view and modify only their own recipes.
Example: The user TortelliEngineer can create a recipe "Tortelli Secret Recipe" using the model Recipe; he can view, update and delete his recipe(s), but nobody else can see his precious "Tortelli Secret Recipe".
So, which is the cleanest way?
I added a user_id attribute to the model Recipe.
I must use this parameter every single time that I ask to the database for a Recipe (goodbye "findOrFail" by ID)
That means that every time I make a request I must access the Request object that contains User that contains User_id
using Auth::id() EVERY SINGLE TIME that I need one (or n) recipe
Like this:
class RecipeRepository{
public function all(){
return Recipe::where('user_id', Auth::id())
->orderBy('created_at', 'asc')
->get();
}
public function find($recipe_id){
return Recipe::where('user_id', Auth::id())
->where('id', $recipe_id)
->firstOrFail();
}
Is that correct? Do you hate me for this? Do you know better or more correct ways to do it?
Most of the time I make a method inside the model to check if someone is authorised, owner etc.. of something.
An example would be:
// User model
public function owns_recipe($recipe)
{
return ($recipe->user_id == $this->id);
}
You can call this at the very beginning in of the methods of your controller:
// Controller
public function index (Request $request)
{
$recipe = Recipe::find($request->id); // Get recipe
$user = ... // Get user somehow
if (!$recipe) App::abort(404); // Show 404 not found, or something
if (!$user->owns_recipe($recipe)) App::abort(403); // Show 403 permission denied, or something
... // Do whatever you want :)
}
While there are many ways of approaching this, Laravel does provide some built-in methods for handling general authentication of actions. In the first place I'd do something along the lines of what you intended (have a getRecipesByOwner method in RecipeRepository) and you can pass the user to it from the injected Request object:
// RecipeController
public function index(Request $request)
{
$recipes = $this->recipeRepo->findRecipesByOwner($request->user());
}
In addition though, I'd recommend creating policies to manage whether or not a user is capable of updating/deleting/viewing individual recipes. You can then authorize their actions in the controllers/blade templates/etc. via built-in methods like:
// Controller
public function update(Request $request, Recipe $recipe)
{
$this->authorize('update', $recipe);
}
// Blade template
#can('update', $recipe)
#endcan
The documentation is available at: https://laravel.com/docs/5.3/authorization#creating-policies

Resources