How to used Tucker-Eric/EloquentFilter Laravel - laravel

good day, I am using Tucker-Eric/EloquentFilter Laravel.
I want to filter it by relationship using Models
I want to automate it, instead of using the following:
public function users($users)
{
// dd($users);
return $this->r('users', $users);
}
public function user($user)
{
// dd($user);
return $this->r('user', $user);
}
public function owner($owner)
{
// dd($owner);
return $this->r('owner', $owner);
}
I want to make it one function that based on the relationship
so that I want to add another relationship on the model I don't need anymore to add another function.
Thanks!

We specifically stayed away from the type of implicit functionality you're looking for and opted for explicit filter methods to avoid security issues if/when new relations/properties were added to a model they wouldn't implicitly be available to filter against.
With that, what you're looking for isn't recommended because of the security concerns above but it can still exist if you implement it.
It sounds like the setup method would be the best place to implement it since it would be called first every time ->filter() is called.
public function setup()
{
foreach($this->input() as $key => $val) {
if($this->getModel()->$key() instanceof \Illuminate\Database\Eloquent\Relations\Relation) {
// Your logic here
}
}
}

Related

Define fields based on resource's model attributes in Laravel Nova

I have a (relatively) basic need in Nova that I can't seem to figure out and I slowly start to feel that I'm approaching things the wrong way.
So, I've got a User, Company, Device and Transfer models and respectively resources, everything pretty default regarding the resource setup.
The schema is the following:
users: id, company_id
companies: id, type_id, name where type_id is pointing to one of three pre-populated types (manufacturer, dealer, client)
devices: id, imei
transfers: id, from_company_id, to_company_id, accepted_at
and Transfer is in a Many-to-Many with Device.
The idea behind the transfers being that Manufacturers transfer to Dealers, Dealers transfer to Clients, so it's really only a one-way thing.
Now the problem occurs at the following crucial point in the logic:
In my Transfer resource pages, I want to show different fields depending on the type of the company the currently authenticated user belongs to. Basically, if the company is:
Manufacturer, then display a DEALER column populated with the transfers' toCompany relation;
Dealer, then display a CONTRAGENT column populated with the transfers' fromCompany or toCompany relations (depending on which mathces the current auth() company)
Client, then display a DEALER column populated with the transfers' fromCompany
All of the described logic works fine with the following code (App\Nova\Transfer.php as is) UNTIL I wanted to finally display the transfer's devices on the details page:
<?php
namespace App\Nova;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\ID;
use Laravel\Nova\Fields\BelongsTo;
use Laravel\Nova\Fields\BelongsToMany;
use Laravel\Nova\Http\Requests\NovaRequest;
class Transfer extends Resource
{
public static $model = \App\Models\Transfer::class;
public static $title = 'id';
public static $search = [
'id',
];
public static $with = [
'fromCompany',
'toCompany'
];
public function fields(Request $request)
{
$company = auth()->company();
if($company->hasType('manufacturer'))
{
$contragentTitle = 'Dealer';
$contragent = 'toCompany';
}
else if($company->hasType('dealer'))
{
//\Debugbar::info($this); //showing empty resource when populating the devices
$contragentTitle = 'Contragent';
$contragent = $this->fromCompany->is($company) ? 'toCompany' : 'fromCompany'; //exception here, since the resource is empty and fromCompany is null
}
else
{
$contragentTitle = 'Dealer';
$contragent = 'fromCompany';
}
$contragentCompanyField = BelongsTo::make("$contragentTitle company", $contragent, Company::class);
if($company->hasType('dealer'))
{
$contragentCompanyField->displayUsing(function ($contragentCompany) use ($contragent){
return $contragentCompany->title() . " (".($contragent == 'toCompany' ? 'Outgoing' : "Incoming").')';
});
}
return [
ID::make(__('ID'), 'id')->sortable(),
$contragentCompanyField,
BelongsToMany::make('Devices') //problematic field, when removed, everything is fine...
];
}
public static function indexQuery(NovaRequest $request, $query)
{
if(auth()->check())
{
return $query->where(function($subQuery){
return $subQuery->where('from_company_id', auth()->company()->id)->orWhere('to_company_id', auth()->company()->id);
});
}
}
public function cards(Request $request)
{
return [];
}
public function filters(Request $request)
{
return [];
}
public function lenses(Request $request)
{
return [];
}
//action is working fine (additional canRun added to avoid policy conflicts)
public function actions(Request $request)
{
return [
(new Actions\AcceptTransfer())->showOnTableRow()->canSee(function ($request) {
if ($request instanceof \Laravel\Nova\Http\Requests\ActionRequest) {
return true;
}
return $this->resource->exists
&& $this->resource->toCompany->is(auth()->company())
&& $this->resource->accepted_at === null;
})->canRun(function ($request) {
return true;
})
];
}
}
Now the strange thing that is happening is that the fields() method gets called multiple times on multiple ajax requests behind the scenes with Nova and when populating the devices relationship table, it gets called without a resource, although a call is never actually needed (as far as I can grasp the mechanics behind Nova) or at least when fetching relationships, you must still have the model information (at least the ID) somewhere to fetch by... So basically, if I'm a user of a dealer company, I can't see the devices that are being transferred (currently throwing a calling is() on null exception).
Now, this happens to be a big problem, since it hinders most of the stuff I need for my transfers, but also generally I don't like my approach so far, so... What would be the right way to achieve this multi-layer resource? Ideally I'd like to define three different transfer resource classes and somehow tell nova which one to use based on the user's company's type (since branching will most probably just grow more complex and therefore uglier as of the current aproach), but I can't figure out the way to do so.
I've also considered moving this entire logic to a separate Nova tool, but I really don't know much about them yet and whether that would be the right option... The only thing stopping me is that I still won't be able to elegantly solve the multi-layer problem and will have to write much of the otherwise useful Nova CRUD logic and views myself...
Any explanations (regarding the multiple calls of fields() and why resource is empty) or general structural recommendations to solve this case would be greatly appreciated! Many thanks in advance!
EDIT:
I was able to circumvent the error by taking advantage of viaResourceId, so instaed of $this I ended up using:
$transfer = $this->id ? $this->resource : \App\Models\Transfer::find($request->viaResourceId);
but the messy code and the unneeded calls still remain an open question. Thanks again in advance!
Here is an example of how I handled this:
public function fields(NovaRequest $request)
{
/** #var \App\Models\User $user */
$user = $this->id ? $this->resource : \App\Models\User::find($request->viaResourceId);
if ($user && $user->whatEver()) {
// display special fields in preview/detail view
return [...];
}
// display for index and if no model is found
return [...];
}

Route model binding with multiple wildcards

How to explicitly say to route model binding to fetch only related categories? I have my web.php file as follows:
Route::get('/catalog/{category}', [CategoryController::class, 'index'])->name('category.index');
Route::get('/catalog/{category}/{subcategory}', [SubcategoryController::class, 'index'])->name('subcategory.index');
Route::get('/catalog/{category}/{subcategory}/{subsubcategory}', [SubsubcategoryController::class, 'index'])->name('subsubcategory.index');
Subsubcategory controller:
public function index(Category $category, Subcategory $subcategory, Subsubcategory $subsubcategory)
{
$subsubcategory->load('product')->loadCount('product');
$products = Product::where('subsubcategory_id', $subsubcategory->id)->orderByRaw('product_order = 0, product_order')->get();
return view('subsubcategory.index', compact('subsubcategory', 'products'));
}
And model in question:
public function subcategory()
{
return $this->belongsTo(Subcategory::class);
}
public function category()
{
return $this->belongsTo(Category::class);
}
public function getRouteKeyName()
{
return 'slug';
}
It works partially ok. It loads all the slugs, but the problem is, let's say I have Samsung Subsubcategory with it's parent categories like:
catalog/mobile-phones/android/samsung
Whenever I modify url from catalog/mobile-phones/android/samsung to catalog/mobile-phones/ios/samsung it works, where in fact it should not. How to handle this second scenario?
PS: it also applies if I open subcategory and change category slug. But, obviously, if upper level category does not exists, it's going to throw 404.
You may want to explore the docs a bit in regard to explicit route model binding and customizing the resolution logic to get some ideas.
https://laravel.com/docs/8.x/routing#customizing-the-resolution-logic
The following is untested and I'm making some guesses about your table structures, but I think this should give you a basic concept of how you can alter route model binding to fit your needs. The same concept could also be applied to the {subcategory} binding, but with one less relationship check.
App/Providers/RouteServiceProvider.php
public function boot()
{
// ...default code...
// add custom resolution for binding 'subsubcategory'
Route::bind('subsubcategory', function($slug, $route) {
// check to see if category exists
if ($category = Category::where('slug',$route->parameter('category'))->first()) {
// check to see if subcategory exists under category
if ($subcategory = $category->subcategories()->where('slug',$route->parameter('subcategory'))->first()) {
// check to see if subsubcategory exists under subcategory
if ($subsubcategory = $subcategory->subsubcategories()->where('slug',$slug)->first()) {
// success, proper relationship exists
return $subsubcategory;
}
}
}
// fail (404) if we get here
throw new ModelNotFoundException();
});
}
I will note, however, that this makes a number of separate database calls. There may be more efficient ways to achieve the same goal through other methods if optimization is a concern.

How to define policy for a list or array in laravel?

I have the following policy which determines if a user is able to view a contract.
public function view(User $user, Contract $contract)
{
if ($user->user_type->id == 2) { // If user is a vecino
if ($user->id == $contract->customer_id) {
return true;
}
} else if ($user->user_type->is_admin == true) { // If user is an admin
return true;
}
return false;
}
Which is then checked for authorization with
$this->authorize('view', $contract);
How do I check authorization for a list/array/collection? Like if I get a list of contracts via Contract::all()
I haven't found any way to do this. I could do a loop and call $this->authorize for every iteration to check for authorization but that might impact performance.
Is there a better way of doing this?
One solution I am currently using is a hybrid approach where you define your rules within a scope and then reference that scope from the policy allowing you to reuse your authorization logic.
// Contract model
public function scopeViewable($query)
{
// If the user is admin, just return the query unfiltered.
if (Auth::user()->user_type->is_admin) {
return $query;
}
// Check the contract belongs to the logged in user.
return $query->where('customer_id', Auth::id());
}
And then in your policy, reference that scope but restrict it to the current model. Make sure to return a boolean using exists(). This essentially checks that your model is viewable.
// Contract Policy
public function view(User $user, Contract $contract)
{
return Contract::viewable()
->where('id', $contract->id)
->exists()
;
}
Importantly, you should use the scope when retrieving a collection of models and not the policy which would run the scope query for each model in the collection. Policies should be used on individual model instances.
Contract::viewable()->paginate(10);
// Or
Contract::viewable()->get();
But, when you want to check an individual contract you can use your policy directly.
$this->authorize('view', $contract);
// Or
Auth::user()->can('view', [Contract::class, $contract]);
The design i often sees in this case, is to check if all elements in the query is allowed to be viewed through the policy. This does not scale well and works bad with pagination.
Instead of filtering out the contracts with policies, the better solution is to filter the contracts already in the query. This mainly because if you want to do pagination down the line, you want to do all filtering before the query is executed to avoid having weird pagination meta data. While also having to run n operations for each element, which would already be a problem at 1000 elements.
There for doing the following query clause, can obtain the same result as your policy.
Contract::where('user_id', $user->id)->get();
A version of this i usually do to make things easier for my self is creating a scope in the user model.
public function scopeOwned($query, User $user)
{
return $this->query->where('user_id', $user->id);
}
Contract::owned($user)->get();
You have to loop, one way to another. There is no difference between looping over Contract object in your controller, or on your policy, but policies are made to check a single resource so I would do that in your controller.

How to Override Update Method in Laravel 5.8

I'm trying to create a ticketing system that's linked to timesheets. Whenever someone updates a ticket, they have the option of submitting how much time has been spent on it, in a time_spent form object. Timesheets are polymorphically linked to many objects.
I want to create a trait, CreatesTimesheets, then apply that to relevant models so that:
Each of those models gets a timesheets() function.
It overrides the update() method of each model that it's a trait of, to check whether any time was submitted in time_spent.
It's the second bit that isn't working. My code is as below, and when I update the model (which works fine), this code doesn't fire at all, even testing it with a simple dd().
How do I fix this?
<?php
namespace App\Traits;
use App\Models\HR\Timesheet;
use Auth;
trait CreatesTimesheets
{
public function update(array $attributes = [], array $options = [])
{
dd('test');
if ($request->time_spent)
{
$timesheet = new Timesheet;
$timesheet->time_logged_in_mins = $request->time_spent;
$timesheet->appointment_id = Auth::user()->appointedJobIDToUse();
$this->timesheets()->save($timesheet);
}
parent::update($attributes, $options);
}
public function timesheets()
{
return $this->morphToMany('App\Models\HR\Timesheet', 'timesheetable');
}
}

using Observer pattern with a MVC/Codeigniter web site

I have a web site I'm converting to Codeigniter and I want to simplify and decouple. I like what I've read about the Observer pattern for things like "new survey created" (which triggers a new help ticket, which triggers an email, etc).
But how do I implement such a thing in Code Igniter? I see the Symfony component but at this point I'm not concerned about understanding the system as much as figuring out how to use it in controllers and models. I have extended both CI_Model and CI_Controller already for other reasons. Would putting Observer pattern code there be the best?
I imagine a point like this: someone hits the web site and spawns a request which gets routed to a controller/action: http://localhost/test/save_changes
// warning, pseudo-code!
class Test extends MY_Model
{
public function __construct ()
{
// do I put this here?!? - or maybe in MY_Model?
// Should it be a singleton?
$this->load->library('dispatcher');
// where do I attach what I want... here?
$this->load->library('emailer');
$this->dispatcher->attach($this->emailer);
// what if I have 50 possible things that might happen
// based on any given event, from adding a user to
// deleting a survey or document? There has got to be a
// way to attach a bunch of observers that trickle
// down to each object, right?
}
public function save_changes ()
{
$this->load->model('user');
$this->user->init($this->session->userdata('user.id'))->save();
}
}
class User extends MY_Model
{
public function __construct ()
{
parent::__construct ();
// do I put this here?!?
$this->load->library('dispatcher'); // just something to call it
}
public function init($id)
{
if($this->_loadUser ($id))
{
$this->dispatcher->notify($this, 'user.loaded');
}
}
public function save($id)
{
if(parent::save())
{
$this->dispatcher->notify($this, 'user.saved');
}
}
}
class Emailer
{
public function update ($caller,$msg)
{
switch ($msg)
{
case 'user.saved':
// send user an email
// re-cache some stuff
// other things that we might want to do, including more of these:
$this->dispatch->notify('user-saved-email-sent');
break;
}
}
}
class Dispatcher
{
public function notify ($caller, $msg) { ...foreach attached do $obj->update($caller,$msg) ...}
public function attach ($obj) { ... }
public function detach ($obj) { ... }
}
I can see how powerful that would be. But I'm not sure how to simplify the setup and attaching of all of these listener/observers.
Maybe I should have a factory to create them all? It just seems like yes, they would be decoupled from the way it currently works, but it seems managing all the different objects that I'd have to 'attached' in each controller or method would become coupled in a different way.
Thanks,
Hans
Your proposed structure would have to be something like:
$this->load->library('observer_factory', 'of'); // factory for creating observers
// Observer_factory would have knowledge/access to different classes which relate
// to the pattern.
$ync = $this->of->getNotifier( $some_variable ) );
$ync->attach( $this->of->getObserver( $some_other_variable ) );
$ync->attach( $this->of->getObserver( $some_final_variable ) );
$ync->someMethod(); // someMethod calls notify
But I wonder about it. You'd have a factory class that slowly becomes all-knowing. It starts usurping the functionality of the Loader. Why load a library when my Observer_factory can handle it by doing exactly the same thing?
I think you're better off with a library or a model that knows what it is supposed to do and is well designed, then adding this class structure. I do not see the gains outweighing the costs.

Resources