• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

luttje / filament-user-attributes / 16229874913

11 Jul 2025 09:14PM UTC coverage: 81.138% (+6.6%) from 74.489%
16229874913

push

github

luttje
Fix tests related to attribute configuring

2168 of 2672 relevant lines covered (81.14%)

33.15 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

39.02
/src/Filament/Factories/BaseComponentFactory.php
1
<?php
2

3
namespace Luttje\FilamentUserAttributes\Filament\Factories;
4

5
use Closure;
6
use Filament\Forms\Components\Checkbox;
7
use Filament\Forms\Components\Component;
8
use Filament\Forms\Components\Field;
9
use Filament\Forms\Components\TextInput;
10
use Filament\Forms\Get;
11
use Filament\Forms\Set;
12
use Filament\Tables\Columns\Column;
13
use Illuminate\Database\Eloquent\Builder;
14
use Illuminate\Database\Eloquent\Model;
15
use Luttje\FilamentUserAttributes\Contracts\HasUserAttributesContract;
16
use Luttje\FilamentUserAttributes\EloquentHelper;
17
use Luttje\FilamentUserAttributes\Filament\UserAttributeComponentFactoryInterface;
18

19
abstract class BaseComponentFactory implements UserAttributeComponentFactoryInterface
20
{
21
    public function __construct(
56✔
22
        protected string $modelType
23
    ) {
24
        //
25
    }
56✔
26

27
    private function makeInheritedDefault(array $userAttribute, mixed $default = null): Closure
×
28
    {
29
        return function (?Model $record, Get $get) use ($userAttribute, $default) {
×
30
            if ($userAttribute['inherit_relation'] === '__self') {
×
31
                $relatedField = $get($userAttribute['inherit_attribute']);
×
32

33
                if ($relatedField !== null) {
×
34
                    return $relatedField;
×
35
                }
36

37
                return data_get($record ?? [], $userAttribute['inherit_attribute']);
×
38
            }
39

40
            if ($record !== null) {
×
41
                $default = data_get($record, $userAttribute['inherit_relation'] . '.' . $userAttribute['inherit_attribute']);
×
42
            } else {
43
                // TODO: Should we support situations where a relation exists, but no form field is shown for it.
44
                // TODO: We currently only support relations that have a form field e.g: relation customer needs customer(_id) field
45
                $relatedField = $get($userAttribute['inherit_relation']);
×
46

47
                if (!$relatedField) {
×
48
                    $relatedField = $get($userAttribute['inherit_relation'] . '_id');
×
49
                }
50

51
                if ($relatedField !== null) {
×
52
                    $record = $this->modelType;
×
53
                    $inheritRelationInfo = EloquentHelper::getRelationInfo($record, $userAttribute['inherit_relation']);
×
54
                    $related = ($inheritRelationInfo->relatedType)::find($relatedField);
×
55
                    $default = data_get($related, $userAttribute['inherit_attribute']);
×
56
                }
57
            }
58

59
            return $default;
×
60
        };
×
61
    }
62

63
    protected function setUpColumn(Column $column, array $userAttribute): Column
12✔
64
    {
65
        $customizations = $userAttribute['customizations'] ?? [];
12✔
66
        $default = $customizations['default'] ?? false;
12✔
67

68
        if (isset($userAttribute['inherit']) && $userAttribute['inherit'] === true) {
12✔
69
            $default = $this->makeInheritedDefault($userAttribute, $default);
×
70
        }
71

72
        if (isset($customizations['is_limited']) && $customizations['is_limited'] === true) {
12✔
73
            $column->limit($customizations['limit'] ?? 50);
×
74
        }
75

76
        if (isset($customizations['wraps_text']) && $customizations['wraps_text'] === true) {
12✔
77
            $column->wrap();
×
78
        }
79

80
        $userAttributeName = $userAttribute['name'];
12✔
81

82
        if (isset($customizations['is_searchable']) && $customizations['is_searchable'] === true) {
12✔
83
            $column->searchable(query: function (Builder $query, string $search) use ($userAttributeName): Builder {
×
84
                $jsonPath = '$."' . str_replace('"', '\"', $userAttributeName) . '"';
×
85

86
                $query->whereHas('userAttribute', function (Builder $query) use ($jsonPath, $search) {
×
87
                    $query->where(function (Builder $subQuery) use ($jsonPath, $search) {
×
88
                        $subQuery->whereRaw(
×
89
                            "JSON_UNQUOTE(JSON_EXTRACT(`values`, ?)) LIKE ?",
×
90
                            [$jsonPath, '%' . $search . '%']
×
91
                        );
×
92

93
                        // If search contains comma, also search with dot, because the database only stores JSON numbers with dots
94
                        if (strpos($search, ',') !== false) {
×
95
                            $searchWithDot = str_replace(',', '.', $search);
×
96
                            $subQuery->orWhereRaw(
×
97
                                "JSON_UNQUOTE(JSON_EXTRACT(`values`, ?)) LIKE ?",
×
98
                                [$jsonPath, '%' . $searchWithDot . '%']
×
99
                            );
×
100
                        }
101
                    });
×
102
                });
×
103
                return $query;
×
104
            });
×
105
        }
106

107
        if (isset($customizations['is_sortable']) && $customizations['is_sortable'] === true) {
12✔
108
            $column->sortable(
×
109
                query: function (Builder $query, string $direction) use ($userAttributeName): Builder {
×
110
                    $jsonPath = '$."' . str_replace('"', '\"', $userAttributeName) . '"';
×
111
                    $model = $query->getModel();
×
112
                    $modelClass = get_class($model);
×
113
                    $modelTable = $model->getTable();
×
114
                    $modelKey = $model->getKeyName();
×
115
                    $nullsFirst = $direction === 'desc';
×
116

117
                    return $query
×
118
                        ->select($modelTable . '.*') // Ensure we're selecting all columns from main table (or we get a `Missing required parameter` error)
×
119
                        ->leftJoin('user_attributes', function ($join) use ($modelClass, $modelTable, $modelKey) {
×
120
                            $join->on('user_attributes.model_id', '=', $modelTable . '.' . $modelKey)
×
121
                                 ->where('user_attributes.model_type', '=', $modelClass);
×
122
                        })
×
123
                        ->orderByRaw("
×
124
                            CASE
125
                                WHEN user_attributes.id IS NULL THEN ?
126
                                ELSE ?
127
                            END,
128
                            COALESCE(JSON_UNQUOTE(JSON_EXTRACT(user_attributes.values, ?)), '') {$direction}
×
129
                        ", [
×
130
                            $nullsFirst ? 0 : 1,  // NULL records priority
×
131
                            $nullsFirst ? 1 : 0,  // Non-NULL records priority
×
132
                            $jsonPath
×
133
                        ]);
×
134
                }
×
135
            );
×
136
        }
137

138
        $column->toggleable();
12✔
139

140
        return $column
12✔
141
            ->label($userAttribute['label'])
12✔
142
            ->default($default);
12✔
143
    }
144

145
    public function setUpField(Field $field, array $userAttribute): Field
8✔
146
    {
147
        $default = $this->makeDefaultValue($userAttribute) ?? null;
8✔
148

149
        if (isset($userAttribute['inherit']) && $userAttribute['inherit'] === true) {
8✔
150
            $default = $this->makeInheritedDefault($userAttribute, $default);
×
151

152
            // Default won't work when user_attributes does not yet contain the value, therefor we also set it here
153
            $field->formatStateUsing(function (Component $component, ?Model $record, $state) use ($userAttribute, $default) {
×
154
                if ($record === null) {
×
155
                    return $state;
×
156
                }
157

158
                if (!in_array(HasUserAttributesContract::class, class_implements($record))) {
×
159
                    throw new \Exception('Record must implement HasUserAttributesContract');
×
160
                }
161

162
                /** @var HasUserAttributesContract $record */
163
                if (!$record->hasUserAttribute($userAttribute['name'])) {
×
164
                    // Force the default value
165
                    return $component->evaluate($default);
×
166
                }
167

168
                return $state;
×
169
            });
×
170
        }
171

172
        $field->required($userAttribute['required'] ?? false)
8✔
173
            ->statePath('user_attributes.' . $userAttribute['name'])
8✔
174
            ->label($userAttribute['label'])
8✔
175
            ->default($default);
8✔
176

177
        return $field;
8✔
178
    }
179

180
    public function makeConfigurationSchema(): array
40✔
181
    {
182
        return [
40✔
183
            Checkbox::make('is_searchable')
40✔
184
                ->label(ucfirst(__('filament-user-attributes::user-attributes.attributes.is_searchable')))
40✔
185
                ->default(true),
40✔
186

187
            Checkbox::make('is_sortable')
40✔
188
                ->label(ucfirst(__('filament-user-attributes::user-attributes.attributes.is_sortable')))
40✔
189
                ->default(true),
40✔
190

191
            Checkbox::make('wraps_text')
40✔
192
                ->label(ucfirst(__('filament-user-attributes::user-attributes.attributes.wrap_text')))
40✔
193
                ->default(true),
40✔
194

195
            Checkbox::make('is_limited')
40✔
196
                ->label(ucfirst(__('filament-user-attributes::user-attributes.attributes.is_limited')))
40✔
197
                ->default(true)
40✔
198
                ->live()
40✔
199
                ->afterStateUpdated(function (Set $set, ?bool $state) {
40✔
200
                    if ($state) {
×
201
                        $set('limit', 50);
×
202
                    }
203
                }),
40✔
204

205
            TextInput::make('limit')
40✔
206
                ->numeric()
40✔
207
                ->label(ucfirst(__('filament-user-attributes::user-attributes.attributes.limit')))
40✔
208
                ->step(1)
40✔
209
                ->required()
40✔
210
                ->visible(fn (Get $get) => $get('is_limited'))
40✔
211
                ->default(50),
40✔
212
        ];
40✔
213
    }
214
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc