Reusable sections

Hi, I was wondering how you usually create reusable parts for example e.g. infolists or forms? For example I create my own AddressEntry that I am using on multiple resources. The problem comes when I need to access the Address model. This is my demo custom entry:
<?php

namespace App\Filament\Components\Infolists\Entries;

class AddressEntry
{
public static function make(string $name = 'address'): Entry
{
return TextEntry::make($name)
->label(__('Address'))
->hintAction(
Action::make('openMaps')
->url(fn (Model $record) => match (true) {
$record instanceof User => 'https://google.com/maps/place/'.$record->address->street,
$record instanceof Client => 'https://google.com/maps/place/'.$record->user->address->street,
default => 'https://google.com/maps/place/'.$record->street,
})
->openUrlInNewTab(),
);
}
}
<?php

namespace App\Filament\Components\Infolists\Entries;

class AddressEntry
{
public static function make(string $name = 'address'): Entry
{
return TextEntry::make($name)
->label(__('Address'))
->hintAction(
Action::make('openMaps')
->url(fn (Model $record) => match (true) {
$record instanceof User => 'https://google.com/maps/place/'.$record->address->street,
$record instanceof Client => 'https://google.com/maps/place/'.$record->user->address->street,
default => 'https://google.com/maps/place/'.$record->street,
})
->openUrlInNewTab(),
);
}
}
Model in my closure in url method is taken from resource, when I use it on UserResource, it is User etc. It kinda works, but I don't like this implementation. Do you know better way?
14 Replies
bernhard
bernhard9mo ago
What exactly is your problem with your implementation? Why don't you like it? One point for me is, that the wording doesn't make sense in a laravel context. In most cases in laravel, a static makemethod would create an instance of the current class (in this case AddressEntry). All Filament classes behave like that, the Model::make does it etc. Your code created not an instance of AddressEntry but an instance of TextEntry. So I think its a bad idea to use the word make. I would suggest to be more generic and use this class for other common fields/entries, otherwise you would have to create separete classes for every szenario. Just my two cents 😉
Trauma Zombie
Trauma ZombieOP9mo ago
I really appreciate your comments. But could you be more specific?
bernhard
bernhard9mo ago
I don't know your exact scenario so it is a bit hard to be very specific, but I will try to explain it with FormFields which I centralize in my own projects. For this, I either create a class CommonField where I put static methods in for common used fields, even if there is not that much logic on the field. For example:
class CommonFields
{
public static function cols(): Select
{
return Select::make("cols")
->label("Spalten")
->options([
1 => "1",
2 => "2",
3 => "3",
4 => "4",
]);
}


public static function money(string $name): TextInput
{
return TextInput::make($name)
->numeric()
->postfix("€");
}


public static function priority(): TextInput
{
return TextInput::make("priority")
->label("Priorität")
->numeric()
->minValue(-100)
->maxValue(100);
}

public static function parent(string $name = "parent_id", string $relationName = "parent", string $relationNameAttribute = "name"): Select
{
return Select::make($name)
->relationship($relationName, $relationNameAttribute)
->searchable()
->preload()
->label("Eltern-Element");
}
}
class CommonFields
{
public static function cols(): Select
{
return Select::make("cols")
->label("Spalten")
->options([
1 => "1",
2 => "2",
3 => "3",
4 => "4",
]);
}


public static function money(string $name): TextInput
{
return TextInput::make($name)
->numeric()
->postfix("€");
}


public static function priority(): TextInput
{
return TextInput::make("priority")
->label("Priorität")
->numeric()
->minValue(-100)
->maxValue(100);
}

public static function parent(string $name = "parent_id", string $relationName = "parent", string $relationNameAttribute = "name"): Select
{
return Select::make($name)
->relationship($relationName, $relationNameAttribute)
->searchable()
->preload()
->label("Eltern-Element");
}
}
This is of course a matter of taste, but I like it this way, because (for example) all fields with currency values in my project to have the "€" postfix and be numeric, but I don't wanna create for a 3-liner an extra class/file. Of course there are more complex ones, with callbacks 😉 I hope that helps. But as I said, this is a matter of taste
Trauma Zombie
Trauma ZombieOP9mo ago
Thanks, now I understand what you mean. My example was simplified to make it more clear. I understand your comments, but the problem was more related to the ->url section. I will give you another example where I have extracted multiple fields into one array. My question is about the ->url section. I don't like what I created in closure, but it works.
class CompanyInfolist
{
public static function get(): array
{
return [
Infolists\Components\TextEntry::make('company')
->label(__('Company')),

Infolists\Components\TextEntry::make('business_no')
->label(__('Business No.'))
->hintActions([
Infolists\Components\Actions\Action::make('openBusinessRegister')
->hiddenLabel()
->url(
fn (Model $record) => match (true) {
$record instanceof Contract => $record->client->entity->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->client->entity->business_no : null,
$record instanceof Entity => $record->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->business_no : null,
default => $record->entity->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->entity->business_no : null,
}
)
->openUrlInNewTab()
->tooltip(__('Visit business register')),

]),

Infolists\Components\TextEntry::make('tax_no')
->label(__('Tax No.')),

Infolists\Components\TextEntry::make('vat_no')
->label(__('VAT No.')),

Infolists\Components\TextEntry::make('iban')
->label(__('IBAN')),
];
}
}
class CompanyInfolist
{
public static function get(): array
{
return [
Infolists\Components\TextEntry::make('company')
->label(__('Company')),

Infolists\Components\TextEntry::make('business_no')
->label(__('Business No.'))
->hintActions([
Infolists\Components\Actions\Action::make('openBusinessRegister')
->hiddenLabel()
->url(
fn (Model $record) => match (true) {
$record instanceof Contract => $record->client->entity->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->client->entity->business_no : null,
$record instanceof Entity => $record->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->business_no : null,
default => $record->entity->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->entity->business_no : null,
}
)
->openUrlInNewTab()
->tooltip(__('Visit business register')),

]),

Infolists\Components\TextEntry::make('tax_no')
->label(__('Tax No.')),

Infolists\Components\TextEntry::make('vat_no')
->label(__('VAT No.')),

Infolists\Components\TextEntry::make('iban')
->label(__('IBAN')),
];
}
}
I use this array inside my ViewContract and ViewEntity pages.
bernhard
bernhard9mo ago
Ok, but why don't you like it? I don't get the point? That your argument $record is a Model instead of User? If you would be lazy, you wouldn't even need a type at all 😄
Trauma Zombie
Trauma ZombieOP9mo ago
I use it like this:
// UserResource.php

Infolists\Components\Group::make()
->relationship('entity')
->schema([
...CompanyInfolist::get(),
])
// UserResource.php

Infolists\Components\Group::make()
->relationship('entity')
->schema([
...CompanyInfolist::get(),
])
bernhard
bernhard9mo ago
You could move the callback back to the Resource:
class CompanyInfolist
{
public static function get(callable $cb): array
{
return [
Infolists\Components\TextEntry::make('company')
->label(__('Company')),

Infolists\Components\TextEntry::make('business_no')
->label(__('Business No.'))
->hintActions([
Infolists\Components\Actions\Action::make('openBusinessRegister')
->hiddenLabel()
->url($cb)
->openUrlInNewTab()
->tooltip(__('Visit business register')),

]),

Infolists\Components\TextEntry::make('tax_no')
->label(__('Tax No.')),

Infolists\Components\TextEntry::make('vat_no')
->label(__('VAT No.')),

Infolists\Components\TextEntry::make('iban')
->label(__('IBAN')),
];
}
}
class CompanyInfolist
{
public static function get(callable $cb): array
{
return [
Infolists\Components\TextEntry::make('company')
->label(__('Company')),

Infolists\Components\TextEntry::make('business_no')
->label(__('Business No.'))
->hintActions([
Infolists\Components\Actions\Action::make('openBusinessRegister')
->hiddenLabel()
->url($cb)
->openUrlInNewTab()
->tooltip(__('Visit business register')),

]),

Infolists\Components\TextEntry::make('tax_no')
->label(__('Tax No.')),

Infolists\Components\TextEntry::make('vat_no')
->label(__('VAT No.')),

Infolists\Components\TextEntry::make('iban')
->label(__('IBAN')),
];
}
}
Trauma Zombie
Trauma ZombieOP9mo ago
It is not about types. It is about that how to access that business_no attribute. For example when I have this inside my UserResource closure is like this: fn ($record) => $record->entity->business_no, but when it is inside ContractResource it is like this: fn ($record) => $record->client->entity->business_no.
bernhard
bernhard9mo ago
And use it like:
CompanyInfolist::get(fn (Contract $record) => $record->client->entity->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->client->entity->business_no : null);
CompanyInfolist::get(fn (Contract $record) => $record->client->entity->business_no ? 'https://orsr.sk/hladaj_ico.asp?ICO='.$record->client->entity->business_no : null);
depending on the resource
Trauma Zombie
Trauma ZombieOP9mo ago
Or just accept that Entity model, that holds business_no, right? Maybe I was kind of hoping that there's some way to get to the Entity model even when I'm on a UserResource or ContractResource without having to push the model there directly.
bernhard
bernhard9mo ago
Well or another way would be to implement it on the model itself and just call it then
url(fn (Model $record) => $record->getBusinessNo())
url(fn (Model $record) => $record->getBusinessNo())
so you would implement the method getBusinessNo() on both the Contract and the Entity model
Trauma Zombie
Trauma ZombieOP9mo ago
I will probably do this:
public static function get(Entity $record): array
public static function get(Entity $record): array
Because I am doing more actions with this model inside that CompanyInfolist, not just one URL.
bernhard
bernhard9mo ago
ok, but this would still work, just without the callback:
url($record->getBusinessNo())
url($record->getBusinessNo())
or
url($record->getBusinessNoUrl())
url($record->getBusinessNoUrl())
Btw. if you keep the origional version, I would do something with dublication of the url-string. for example:
->url(function (Model $record) {
$no = match (true) {
$record instanceof Contract => $record->client->entity->business_no ?? null,
$record instanceof Entity => $record->business_no ?? null,
default => $record->entity->business_no ?? null,
};

return $no ? 'https://orsr.sk/hladaj_ico.asp?ICO='. $no : null;
})
->url(function (Model $record) {
$no = match (true) {
$record instanceof Contract => $record->client->entity->business_no ?? null,
$record instanceof Entity => $record->business_no ?? null,
default => $record->entity->business_no ?? null,
};

return $no ? 'https://orsr.sk/hladaj_ico.asp?ICO='. $no : null;
})
because in your example, you have trippled the 'https://orsr.sk/hladaj_ico.asp?ICO=' or the new cleaner version:
->url(function (Model $record) {
$no = $record->getBusinessNo();

return $no ? 'https://orsr.sk/hladaj_ico.asp?ICO='. $no : null;
})
->url(function (Model $record) {
$no = $record->getBusinessNo();

return $no ? 'https://orsr.sk/hladaj_ico.asp?ICO='. $no : null;
})
Trauma Zombie
Trauma ZombieOP9mo ago
Right, I understand. Thank you very much. If you still have a moment, you could take a look at my other problem I'm having. https://discord.com/channels/883083792112300104/1214858935425703958
Want results from more Discord servers?
Add your server