Showing relational data of a Filament resource - php

I've started using Filament PHP for creating a Laravel based intranet application but stumbled across a question I couldn't answer myself using the official documentation:
What's the easiest way to show relational data inside the view page of a resource?
I have two resources ClientResource and ProjectResource which results in two Laravel relationships:
Client model:
public function projects(): HasMany
{
return $this->hasMany(Project::class);
}
Project model:
public function client(): BelongsTo
{
return $this->belongsTo(Client::class);
}
I have implemented a BelongsToSelect field inside project resource to assign a client:
Components\BelongsToSelect::make('client')
->relationship('client', 'first_name')
->required(),
Everything works fine so far, but (obviously) all I can see on the project's view page is the disabled select field showing the customer's first name. I'd like to have all related fields listed. Have I missed something crucial in the documentation or what's the best way to approach this?
I've had a look into the RelationManager but it seems there is only a belongsToMany relationship (no belongsTo).

This can also be done with a select component by specifying a relation like this:
Select::make('client_id')
->relationship('client', 'first_name');
You also have access to the eloquent query builder instance to play with like so:
Select::make('client_id')
->relationship('client', 'first_name',
fn (Builder $query) => $query->where('status', 'actif'))
);
Document reference: https://filamentphp.com/docs/2.x/forms/fields#populating-automatically-from-a-relationship

Related

Setting up a kind of BelongsToThrough relationship in Laravel

Here is my current situation:
I have a Task model.
Tasks have owners (a belongsTo relationship)
Owners have accounts (yet another belongsTo relationship)
I'd like to set up a "belongsToThrough" relationship from Tasks to Accounts.
My first solution was to define a relationship in the Tasks model, like this:
public function account(): BelongsTo
{
return $this->owner->account();
}
With it I could call $task->account and retrieve a task's account easily. The problem is that this doesn't work with load/with, which in turn causes problems because I can't refresh() a task that has had the account loaded in (because refresh uses load). The error just states Trying to call account() on null which was honestly expected.
My second solution was to change the relationship method to:
public function account(): BelongsTo
{
return $this->owner()->first()->account();
}
With this, I can also simply call $task->account and retrieve the model, and when loading, it doesn't work (returns null), but also doesn't throw any errors. I don't need to load this relationship in, it just happens that sometimes I need to refresh models and having the load method throw an error is not ok.
In summary
What I'm looking for is kind of a BelongsToThrough, as a Task would BelongTo an Account through an Owner (User). Is there a way to do this that works using both $task->account and $task->load('account'). Before you tell me I can load it using owner.account, I know that, but refresh() will do it automatically with load('account') so I need it to work like that, not with the dot notation.
To get it working with load(), you'll need to define an account relationship on the owner model, if you haven't done so already. Like this:
public function account() :BelongsTo
{
return $this->belongsTo(AccountsTable);
}
Then use dot notation when calling load() on your task model like:
$task->load('owner.account');
You can do that using eager loading
public function account()
{
return $this->belongsTo('App\ParentModel', 'foreignkey', 'localkey');
}
After that you can easily fetch relation data with load/with.
Thanks,

Laravel: "sending" a model with relationship to Vue

I'm starting to learn how to use Vue and Laravel. So I'm writting some Vue components to replace some parts of the blades.
In my application I have a Sugerencia (suggest) model. Of course I have users models and many others, but I need those two for this question
My SugerenciaController#index is quite simple:
public function index()
{
$sugerencias = Sugerencia::orderBy('created_at', 'desc')
->with('user')
->paginate(10);
return view('sugerencias.index', compact('sugerencias'));
}
In my sugerencias/index.blade.php I "send" the $sugerencia to Vue with:
#foreach ($sugerencias as $sugerencia)
...
<user-info :sugerencia="{{ $sugerencia }}"></user-info>
...
#endforeach
And works as intended. user-info is a Vue component that formats and displays the user's info. In Vue, to access the user's name, I use {{ sugerencia.user.name }} in my template. So far, so good. But this works only in the collection.
Then I have my SugerenciaController#show method, which is as simple as
public function show(Sugerencia $sugerencia) {
return view('sugerencias.show', compact('sugerencia'));
}
I assumed that in my sugerencias\show.blade.php I could use the same call for the user-info Vue component as:
...
<user-info :sugerencia="{{ $sugerencia}}"></user-info>
...
...but I can't, since it's not "sending" the user relationship. I assume its because I'm not using the ->with('user') as I did in the collection.
In the mounted() method I placed a console.log(this.sugerencia) and I get all sugerencia's data, but not the user data (just the user_id foreign key)
So question is, how do I "append" the user relationship to the sugerencia model, kinda like eager loading. I want to do something that as trivial as $sugerencia->user->name in the ORM
Please forgive me if my English isn't clear enough and feel free to edit
Honestly, I would decouple the logic here and load the page and vue component with a null value and then do ajax requests to get the required data. Or implement a standardized way of getting data from the controller to the vue components without having specific variables in my view blade templates.
However, for your particular question, you could use Lazy eagar loading like this:
public function show(Sugerencia $sugerencia) {
$sugerencia->load('user');
return view('sugerencias.show', compact('sugerencia'));
}
From the docs:
Sometimes you may need to eager load a relationship after the parent model has already been retrieved. For example, this may be useful if you need to dynamically decide whether to load related models:

Laravel Eloquent HasMany / HasOne Additional On Clauses

Good morning,
Apologies if this has been ask before but we are unable to find any answer to an issue we are having.
We are working with a legacy database that is not owned by us (read-only) and are attempting to use Eloquent (Models) in Laravel to solve some common issues.
Is it possible to setup Eloquent's Eager-loading to create additional ON clauses to the HasMany / HasOne relationship builder?
Please see below of what we are trying to achieve without raw queries.
public function policy()
{
return $this->hasMany(Policy::class, 'Group', 'Group')
// This breaks as `on` isn't defined on Eloquent\Builder. Is this concept possible? Multiple on clauses
->on('Reference', 'Reference');
}
In our controller we have attempted the following which also fails.
Vehicle::with([
'policy' => function ($query) {
// Model isn't instantiated yet, but we need an additional on clause here
$query->on('Reference', 'Reference');
}
]);
Can the above be achieved or do we have to revert back to using raw queries?
Thank you in advance for any help.
You can use the Compoships package:
class Vehicle extends Model
{
use \Awobaz\Compoships\Compoships;
public function policy()
{
return $this->hasMany(Policy::class, ['Group', 'Reference'], ['Group', 'Reference']);
}
}
I am assuming the query already exists as raw SQL and what you are trying to achieve is to convert it to using eloquent. If that is the case you may find it quicker an deasier to use the built in raw query.

Eloquent - Call to undefined relationship

I have a users table and a permissions table. It's a many-to-many relationship so I also have a users_permissions table with a user_id & module_permission_id column.
The user model has the following relationship:
public function permissions()
{
return $this->belongsToMany(Permission::class, 'users_permissions', 'user_id', 'module_permission_id');
}
When I run my query, the result contains an empty permissions array.
User::with('permissions');
If I echo the query in the with, I get the error: Call to undefined relationship [] on model [App\Models\User]
User::with(['permissions', function($q) {
echo $q->toSql();
die();
}]);
The rest of the query works, it's just trying to get permissions which is failing.
In my case it was a coding convention issue related to camelCase vs. snake_case:
In the Model there was
public function getPermissions()
and in the query there was
User::with(['get_permissions'])
When I changed this to
User::with(['getPermissions'])
it started to work, although I'd say the Laravel way would be to use snake_case instead.
This confused me for a couple of days since frameworks like Symfony and AngularJs has a mixed conventions that somewhere you need to write parameters in snake_case and somewhere else in camelCase. I didn't find any documentation on Laravel site how they handle this, so I tried it the Straight Way and it seemed to be the case here :)
Maybe you just forgot the ->get() after User::with('permissions')->get() ?
https://laravel.com/docs/5.0/eloquent#eager-loading
Slapping this here for anyone who may be trying to refactor from an eager load that selects columns to an eager load with a scoped query.
You HAVE to move the selecting of columns into the scoped query, trying to select columns with the usual colon notation will fail and throw the undefined relationship error.
For example going from this eager load that selects a pivot table column, and a column from the permissions table User:with('permissions:permission_tag:tag_set_id,permission_name') to a scoped query that selects the same columns but also orders the results looks like this
User::with([
'permissions' => function ($query) {
$query->select('permission_tag:tag_set_id', 'permission_name');
$query->orderBy('permission_name');
},
]);
Notice I pulled out the : notation and it lives right in the scoped query.

Laravel belongsToMany to return specific columns only

So I have a many to many relationship between Users and Photos via the pivot table user_photo. I use belongsToMany('Photo') in my User model. However the trouble here is that I have a dozen columns in my Photo table most I don't need (especially during a json response). So an example would be:
//Grab user #987's photos:
User::with('photos')->find(987);
//json output:
{
id: 987,
name: "John Appleseed",
photos: {
id: 5435433,
date: ...,
name: 'feelsgoodman.jpg',
....// other columns that I don't need
}
}
Is it possible to modify this method such that Photos model will only return the accepted columns (say specified by an array ['name', 'date'])?
User.php
public function photos()
{
return $this->belongsToMany('Photo');
}
Note: I only want to select specific columns when doing a User->belongsToMany->Photo only. When doing something like Photo::all(), yes I would want all the columns as normal.
EDIT: I've tried Get specific columns using "with()" function in Laravel Eloquent but the columns are still being selected. Also https://github.com/laravel/laravel/issues/2306
You can use belongsToMany with select operation using laravel relationship.
public function photos()
{
return $this->belongsToMany('Photo')->select(array('name', 'date'));
}
Im assuming you have a column named user_id. Then you should be able to do the following:
public function photos()
{
return $this->belongsToMany('Photo')->select(['id', 'user_id', 'date', 'name']);
}
You have to select, the foreign key, else it will have no way of joining it.
Specifying the exact columns you want for the Photos relationship will likely end up biting you in the butt in the future, should your application's needs ever change. A better solution would be to only specify the data you want to return in that particular instance, i.e. the specific JSON response you're delivering.
Option 1: extend/overwrite the toArray() Eloquent function (which is called by toJson()) and change the information returned by it. This will affect every call to these methods, though, so it may end up giving you the same problems as doing select() in the original query.
Option 2: Create a specific method for your JSON response and call it instead of the general toJson(). That method could then do any data building / array modifications necessary to achieve the specific output you need.
Option 3: If you're working with an API or ajax calls in general that need a specific format, consider using a library such as League/Fractal, which is built for just such an occasion. (Phil is also working on a book on building APIs, and it doesn't suck.)

Categories