NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Options for Laravel

DI and actions

The facade is the recommended default, not a requirement. There are three equivalent entry points:

  • The Options facade — shortest; recommended for most code.
  • The manager, RoundlyConsulting\Options\OptionsManager, injected through the constructor — it’s the facade root, so it has exactly the same API, as an explicit dependency with no static calls.
  • Actions — single-purpose classes with one execute() method, for composing into your own actions, jobs and commands. Options ships two: ExportOptionsAction and ImportOptionsAction.

Inject the manager

use RoundlyConsulting\Options\OptionsManager;

final class CopyTeamSettings
{
    public function __construct(private OptionsManager $options) {}

    public function __invoke(Team $from): string
    {
        return $this->options->exportJson($from);
    }
}

// The same API as the facade, handles included:
$this->options->for($team)->set(ThemeOption::class, 'dark');

Options::fake() swaps the container binding, so an OptionsManager resolved after the call is the fake too.

Call an action

use RoundlyConsulting\Options\Actions\ExportOptionsAction;
use RoundlyConsulting\Options\Actions\ImportOptionsAction;

// The raw actions — one execute() each:
$payloads = app(ExportOptionsAction::class)->execute($team, globalOnly: false); // list<OptionPayload>
$count = app(ImportOptionsAction::class)->execute($payloads);                    // int

Facade method → action

Facade methodActionexecute()
Options::export()ExportOptionsActionexecute(?Model $owner = null, bool $globalOnly = false): list<OptionPayload>
Options::exportJson()— (encodes export())—
Options::import()ImportOptionsActionexecute(list<OptionPayload> $payloads): int

Every other manager method — get(), set(), the handles, groups, observers, the config bridge — lives on the manager itself; there is no separate action to call. The manager’s resolveOptionInstance() is @internal (the config bridge’s resolver), so don’t build on it.

The OptionPayload DTO

Both actions speak RoundlyConsulting\Options\DataTransferObjects\OptionPayload — one stored row. Build payloads yourself, or parse rows and JSON into them:

use RoundlyConsulting\Options\DataTransferObjects\OptionPayload;

$payload = new OptionPayload('theme', 'dark', ownerType: User::class, ownerId: 5);

OptionPayload::fromArray(['key' => 'theme', 'value' => 'dark']); // global row
OptionPayload::list($rows);                                      // rows or payloads → list<OptionPayload>
OptionPayload::listFromJson($json);                              // a JSON export → list<OptionPayload>

$payload->toArray(); // ['key' => 'theme', 'value' => 'dark', 'owner_type' => User::class, 'owner_id' => 5]

owner_id follows options.key_type — an int for bigint owners, a string for UUID or ULID owners. fromArray() turns digit strings into ints, and a row with only one of owner_type / owner_id is treated as global.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.