106 lines
3.9 KiB
Markdown
106 lines
3.9 KiB
Markdown
# Product Option Types
|
|||
|
|
|
||
|
|
Lunar's `ProductOption`/`ProductOptionValue` are generic by design — a "Color" option
|
||
|
|
and a "Size" option are both just a handle, a translated name, and a list of values.
|
||
|
|
Each `ProductOptionValue` carries a free-form `meta` jsonb column, but nothing in
|
||
|
|
Lunar's own admin UI exposes it — there's no way for an admin to, say, attach a hex
|
||
|
|
code to a "Red" value without editing the database directly.
|
||
|
|
|
||
|
|
`Modules\Core\Product\Contracts\ProductOptionTypeInterface` describes how a category
|
||
|
|
of option behaves — what structured data its values carry in `meta`, and how an
|
||
|
|
admin edits that data — without introducing a new model. `ProductOption`/
|
||
|
|
`ProductOptionValue` stay exactly as Lunar defines them.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Registering a type
|
||
|
|
|
||
|
|
A shop enables a type class in `config/core.php`:
|
||
|
|
|
||
|
|
```php
|
||
|
|
// config/core.php
|
||
|
|
'product_option_types' => [
|
||
|
|
\App\ProductOptions\ColorOptionType::class,
|
||
|
|
],
|
||
|
|
```
|
||
|
|
|
||
|
|
This is a plain list, **not** keyed by `ProductOption::handle` — a shop's own handle
|
||
|
|
naming (transliterated Greek, legacy import slugs, whatever an admin happened to type
|
||
|
|
when creating the option) shouldn't have to match a type's key. Instead, an admin
|
||
|
|
picks a type per-option from a dropdown on the `ProductOption` edit form itself (see
|
||
|
|
below); the choice is stored in `ProductOption::meta['option_type']`, not inferred
|
||
|
|
from anything else.
|
||
|
|
|
||
|
|
A `ProductOption` with no type selected behaves exactly as stock Lunar does — plain
|
||
|
|
name/position, no extra meta form.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Writing a type
|
||
|
|
|
||
|
|
```php
|
||
|
|
namespace App\ProductOptions;
|
||
|
|
|
||
|
|
use Filament\Forms\Components\ColorPicker;
|
||
|
|
use Modules\Core\Product\Contracts\ProductOptionTypeInterface;
|
||
|
|
|
||
|
|
class ColorOptionType implements ProductOptionTypeInterface
|
||
|
|
{
|
||
|
|
public static function getKey(): string
|
||
|
|
{
|
||
|
|
return 'color';
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getMetaForm(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
ColorPicker::make('meta.hex')
|
||
|
|
->label('Color')
|
||
|
|
->required(),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
`getMetaForm()` returns Filament form components, keyed under `meta.*` dot notation
|
||
|
|
— the path they save to on `ProductOptionValue::meta` (cast as `AsArrayObject`, a
|
||
|
|
plain jsonb column). `getKey()` is the identifier used in the admin's "Option Type"
|
||
|
|
dropdown and in `ProductOption::meta['option_type']` — it has no relationship to the
|
||
|
|
`ProductOption::handle`.
|
||
|
|
|
||
|
|
A reference implementation ships at `Modules\Core\Product\OptionTypes\ColorOptionType`
|
||
|
|
— not auto-registered, since registration is always an explicit shop decision.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## How it's wired into the admin UI
|
||
|
|
|
||
|
|
`Modules\Core\Product\Services\ProductOptionTypeManager`:
|
||
|
|
- `all(): Collection<string, ProductOptionTypeInterface>` — every enabled type,
|
||
|
|
keyed by `getKey()`.
|
||
|
|
- `resolve(?string $key): ?ProductOptionTypeInterface` — looks up one by key (or
|
||
|
|
`null` if no key / not found).
|
||
|
|
|
||
|
|
Two extensions hook into Lunar's admin via its extension system
|
||
|
|
(`LunarPanel::extensions([...])`, registered in `CorePlugin`) — no forking of Lunar's
|
||
|
|
classes needed:
|
||
|
|
|
||
|
|
- `Modules\Core\Product\Filament\Extensions\ProductOptionResourceExtension` extends
|
||
|
|
`Lunar\Admin\Filament\Resources\ProductOptionResource`'s own form with a `Select`
|
||
|
|
(`meta.option_type`) listing every enabled type's key. Shown only when at least one
|
||
|
|
type is enabled.
|
||
|
|
- `Modules\Core\Product\Filament\Extensions\ValuesRelationManagerExtension` extends
|
||
|
|
the "Values" tab's form. Its `extendForm()` reads
|
||
|
|
`$option->meta['option_type']` off the owning `ProductOption`, resolves it via
|
||
|
|
`ProductOptionTypeManager`, and appends `getMetaForm()`'s fields to the stock name
|
||
|
|
field. A `ProductOption` with no type selected gets the stock form unchanged.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Reading the value back
|
||
|
|
|
||
|
|
Storefront code reads `ProductOptionValue::meta` like any other jsonb column — e.g.
|
||
|
|
`$value->meta['hex']` for a color swatch. `ProductOptionTypeManager` is an admin-side
|
||
|
|
concern only (describing *how to edit* the meta); nothing requires the storefront to
|
||
|
|
go through it to *read* the meta.
|