|
| 1 | +--- |
| 2 | +title: Category providers |
| 3 | +sidebar_position: 3 |
| 4 | +--- |
| 5 | + |
| 6 | +A category provider maps the value stored in `getCategoryField()` to a label displayed in graphs, exports, and the category selector. |
| 7 | + |
| 8 | +Each item has an `id` and `label`: |
| 9 | + |
| 10 | +```php |
| 11 | +[ |
| 12 | + ['id' => 1, 'label' => 'Retail'], |
| 13 | + ['id' => 2, 'label' => 'Wholesale'], |
| 14 | +] |
| 15 | +``` |
| 16 | + |
| 17 | +Choose a provider based on where your categories are stored. |
| 18 | + |
| 19 | +## Export column titles |
| 20 | + |
| 21 | +Providers supply the default titles for the category ID and category name columns in CSV exports. A custom provider can override: |
| 22 | + |
| 23 | +```php |
| 24 | +public function getCategoricalStatsCategoryTitle(): string |
| 25 | +{ |
| 26 | + return __('Customer'); |
| 27 | +} |
| 28 | + |
| 29 | +public function getCategoricalStatsCategoryIdTitle(): string |
| 30 | +{ |
| 31 | + return __('Customer Number'); |
| 32 | +} |
| 33 | +``` |
| 34 | + |
| 35 | +Eloquent providers derive the category title from the model's morph class when available, otherwise from the model class name. Enum providers use the enum class name. Array, collection, and other in-memory providers default to `Category` and `Category ID`. |
| 36 | + |
| 37 | +The stat repository may override these titles for an individual metric with `getCategoryTitle()` and `getCategoryIdTitle()`. |
| 38 | + |
| 39 | +## Eloquent model providers |
| 40 | + |
| 41 | +Return a model class when the category ID is the model's primary key: |
| 42 | + |
| 43 | +```php |
| 44 | +use App\Models\Customer; |
| 45 | + |
| 46 | +public function categoryProvider(): mixed |
| 47 | +{ |
| 48 | + return Customer::class; |
| 49 | +} |
| 50 | +``` |
| 51 | + |
| 52 | +By default, the provider: |
| 53 | + |
| 54 | +- Uses the model's primary key as `id`. |
| 55 | +- Uses `admin_link_name` as the label when that accessor exists. |
| 56 | +- Otherwise uses the `name` attribute as the label. |
| 57 | +- Orders paginated searches by the primary key when no order is configured. |
| 58 | +- Derives export column titles from the model's morph class or class name. |
| 59 | + |
| 60 | +### Customize labels and search |
| 61 | + |
| 62 | +Add these methods to the category model when its fields differ from the defaults: |
| 63 | + |
| 64 | +```php |
| 65 | +public function getCategoricalStatsLabelField(): string |
| 66 | +{ |
| 67 | + return 'display_name'; |
| 68 | +} |
| 69 | + |
| 70 | +public function getCategoricalStatsSearchField(): string |
| 71 | +{ |
| 72 | + return 'name'; |
| 73 | +} |
| 74 | +``` |
| 75 | + |
| 76 | +For multi-column or relationship searches, define a `categoricalStatsSearch` scope: |
| 77 | + |
| 78 | +```php |
| 79 | +use Illuminate\Database\Eloquent\Builder; |
| 80 | + |
| 81 | +public function scopeCategoricalStatsSearch(Builder $query, string $search): Builder |
| 82 | +{ |
| 83 | + return $query->where(function (Builder $query) use ($search) { |
| 84 | + $query->where('first_name', 'like', "%{$search}%") |
| 85 | + ->orWhere('last_name', 'like', "%{$search}%") |
| 86 | + ->orWhere('email', 'like', "%{$search}%"); |
| 87 | + }); |
| 88 | +} |
| 89 | +``` |
| 90 | + |
| 91 | +If `categoricalStatsSearch` is not defined, the provider uses an existing `search` scope. If neither scope exists, it applies a `LIKE` condition to `getCategoricalStatsSearchField()`. |
| 92 | + |
| 93 | +### Restrict the available models |
| 94 | + |
| 95 | +Return an Eloquent query to limit the categories users can select: |
| 96 | + |
| 97 | +```php |
| 98 | +public function categoryProvider(): mixed |
| 99 | +{ |
| 100 | + return Customer::query() |
| 101 | + ->where('is_active', true) |
| 102 | + ->where('company_id', auth()->user()->company_id); |
| 103 | +} |
| 104 | +``` |
| 105 | + |
| 106 | +The same query is used when resolving labels and serving paginated search results. |
| 107 | + |
| 108 | +### Configure ordering |
| 109 | + |
| 110 | +For straightforward ordering, create an `EloquentCategoryProvider` and select a field and direction: |
| 111 | + |
| 112 | +```php |
| 113 | +use Javaabu\Stats\CategoryProviders\EloquentCategoryProvider; |
| 114 | + |
| 115 | +public function categoryProvider(): mixed |
| 116 | +{ |
| 117 | + return (new EloquentCategoryProvider(Customer::class)) |
| 118 | + ->sortCategoricalStatsItemsBy('name', 'asc'); |
| 119 | +} |
| 120 | +``` |
| 121 | + |
| 122 | +For multiple order clauses, return an ordered query: |
| 123 | + |
| 124 | +```php |
| 125 | +public function categoryProvider(): mixed |
| 126 | +{ |
| 127 | + return Customer::query() |
| 128 | + ->orderByDesc('priority') |
| 129 | + ->orderBy('name'); |
| 130 | +} |
| 131 | +``` |
| 132 | + |
| 133 | +You can also configure the query through a callback: |
| 134 | + |
| 135 | +```php |
| 136 | +return (new EloquentCategoryProvider(Customer::class)) |
| 137 | + ->sortCategoricalStatsItemsUsing( |
| 138 | + fn ($query) => $query->orderByRaw('priority is null')->orderBy('priority') |
| 139 | + ); |
| 140 | +``` |
| 141 | + |
| 142 | +### Use the provider trait on a model |
| 143 | + |
| 144 | +A model can implement `CategoryProvider` directly with `IsCategoricalStatsProvider`: |
| 145 | + |
| 146 | +```php |
| 147 | +use Javaabu\Stats\Concerns\IsCategoricalStatsProvider; |
| 148 | +use Javaabu\Stats\Contracts\CategoryProvider; |
| 149 | + |
| 150 | +class Customer extends Model implements CategoryProvider |
| 151 | +{ |
| 152 | + use IsCategoricalStatsProvider; |
| 153 | +} |
| 154 | +``` |
| 155 | + |
| 156 | +Return a configured model instance from the stat when you want to use the trait's sorting methods: |
| 157 | + |
| 158 | +```php |
| 159 | +return (new Customer) |
| 160 | + ->sortCategoricalStatsItemsBy('name'); |
| 161 | +``` |
| 162 | + |
| 163 | +## Enum providers |
| 164 | + |
| 165 | +Return a native enum class: |
| 166 | + |
| 167 | +```php |
| 168 | +public function categoryProvider(): mixed |
| 169 | +{ |
| 170 | + return PaymentStatus::class; |
| 171 | +} |
| 172 | +``` |
| 173 | + |
| 174 | +Backed enum values become IDs. The label is resolved from `getLabel()`, then `label()`, and finally the title-cased case name. |
| 175 | + |
| 176 | +To configure ordering, wrap the enum: |
| 177 | + |
| 178 | +```php |
| 179 | +use Javaabu\Stats\CategoryProviders\EnumCategoryProvider; |
| 180 | + |
| 181 | +return (new EnumCategoryProvider(PaymentStatus::class)) |
| 182 | + ->sortCategoricalStatsItemsBy('label', 'asc'); |
| 183 | +``` |
| 184 | + |
| 185 | +## Array and collection providers |
| 186 | + |
| 187 | +Return an associative array when categories are already available in memory: |
| 188 | + |
| 189 | +```php |
| 190 | +public function categoryProvider(): mixed |
| 191 | +{ |
| 192 | + return [ |
| 193 | + 'card' => 'Card', |
| 194 | + 'cash' => 'Cash', |
| 195 | + ]; |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +Arrays and collections can also contain explicit items: |
| 200 | + |
| 201 | +```php |
| 202 | +return collect([ |
| 203 | + ['id' => 'card', 'label' => 'Card'], |
| 204 | + ['id' => 'cash', 'label' => 'Cash'], |
| 205 | +]); |
| 206 | +``` |
| 207 | + |
| 208 | +Use `ArrayCategoryProvider` for field or callback sorting: |
| 209 | + |
| 210 | +```php |
| 211 | +use Javaabu\Stats\CategoryProviders\ArrayCategoryProvider; |
| 212 | + |
| 213 | +return (new ArrayCategoryProvider($categories)) |
| 214 | + ->sortCategoricalStatsItemsBy('label', 'desc'); |
| 215 | +``` |
| 216 | + |
| 217 | +A custom comparator receives two normalized items: |
| 218 | + |
| 219 | +```php |
| 220 | +return (new ArrayCategoryProvider($categories)) |
| 221 | + ->sortCategoricalStatsItemsUsing( |
| 222 | + fn (array $left, array $right) => |
| 223 | + strlen($left['label']) <=> strlen($right['label']) |
| 224 | + ); |
| 225 | +``` |
| 226 | + |
| 227 | +Array and collection providers search and paginate after loading their items into memory. Prefer an Eloquent or custom data-source provider for large category sets. |
| 228 | + |
| 229 | +## Custom providers |
| 230 | + |
| 231 | +Implement `CategoryProvider` when categories come from another data source. For an in-memory source, `HasCategoricalStatsItems` provides normalization, searching, sorting, and pagination: |
| 232 | + |
| 233 | +```php |
| 234 | +use Javaabu\Stats\Concerns\HasCategoricalStatsItems; |
| 235 | +use Javaabu\Stats\Contracts\CategoryProvider; |
| 236 | + |
| 237 | +class ChannelProvider implements CategoryProvider |
| 238 | +{ |
| 239 | + use HasCategoricalStatsItems; |
| 240 | + |
| 241 | + protected function categoricalStatsItems(): iterable |
| 242 | + { |
| 243 | + return config('channels'); |
| 244 | + } |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +Implement the contract methods yourself when the underlying service should perform searching or pagination. |
| 249 | + |
| 250 | +## Use the category search endpoint |
| 251 | + |
| 252 | +The Select2 component calls: |
| 253 | + |
| 254 | +```text |
| 255 | +GET /stats/categorical/categories |
| 256 | + ?filter[metric]=payments_by_customer |
| 257 | + &filter[search]=acme |
| 258 | + &page=2 |
| 259 | +``` |
| 260 | + |
| 261 | +Optional parameters are: |
| 262 | + |
| 263 | +- `per_page`, from 1 to 100. |
| 264 | +- `filters`, for model-specific stats. |
| 265 | + |
| 266 | +The response is a standard Laravel paginator whose `data` entries contain `id` and `label`. The requested metric must be registered, visible to the authenticated user, and compatible with the supplied filters. |
| 267 | + |
| 268 | + |
| 269 | + |
0 commit comments