Skip to content

[2.x] config:cache fails because lunar-filament record URL resolvers contain closures #2564

Description

@RaBic
  • Lunar Core version: 2.x-dev (c0c6a81f4c9cc429fb99a8b9c4030828e1f29c18)
  • Lunar Filament version: 2.x-dev (1acfb414d026786b951471841891db4aa1e54b76)
  • Lunar Admin version: 2.x-dev (9da7134fc7b8b27cc2f73e6cdb447d96caaa7fa5)
  • Laravel version: 12.64.0
  • Filament version: 5.6.8
  • PHP version: 8.5
  • Database driver: MySQL, although the error occurs before database access

Expected Behaviour

Laravel's production optimization commands should complete successfully:

php artisan config:cache
# or
php artisan optimize

Actual Behaviour

config:cache fails because a closure is stored in the Laravel configuration repository:

LogicException

Your configuration files could not be serialized because the value at
"lunar-filament.record_urls.order" is non-serializable.

The underlying exception is:

Call to undefined method Closure::__set_state()

This also causes deployments that run php artisan optimize to fail before the application can become healthy.

Steps To Reproduce

  1. Install the Lunar v2 packages from 2.x-dev, including lunarphp/filament and lunarphp/lunar.

  2. Clear any existing configuration cache:

    php artisan config:clear
  3. Cache the configuration:

    php artisan config:cache
  4. Observe the serialization exception for:

    lunar-filament.record_urls.order
    

Root Cause

Lunar\Admin\LunarPanelProvider::registerBridgeRecordUrls() writes closures into Laravel's configuration repository:

$this->app['config']->set(
    'lunar-filament.record_urls.order',
    fn ($record, array $context = []) => ManageOrder::getUrl([
        ...$context,
        'record' => $record,
    ]),
);

The same method also registers closures for:

lunar-filament.record_urls.product_variant
lunar-filament.record_urls.collection_edit

Laravel's config:cache command exports the final configuration using var_export(). Closures cannot be reconstructed from that exported
representation, so the first resolver causes the command to fail.

Relevant source:

Suggested Fix

Use serializable callable arrays instead of closures, for example:

$this->app['config']->set(
    'lunar-filament.record_urls.order',
    [RecordUrlResolvers::class, 'order'],
);

Alternatively, the URL resolver callbacks could be stored in a dedicated runtime registry rather than Laravel's configuration repository.

Lunar\Filament\Support\RecordUrls already checks is_callable(), so static callable arrays work without changing its public behavior.

Application Workaround

We worked around the issue by replacing the three closures after Lunar's service providers have booted:

app()->booted(function (): void {
    config([
        'lunar-filament.record_urls.order' => [
            LunarRecordUrls::class,
            'order',
        ],
        'lunar-filament.record_urls.product_variant' => [
            LunarRecordUrls::class,
            'productVariant',
        ],
        'lunar-filament.record_urls.collection_edit' => [
            LunarRecordUrls::class,
            'collectionEdit',
        ],
    ]);
});

LunarRecordUrls contains equivalent public static methods that call the existing Lunar Admin pages and resources.

With this workaround:

  • php artisan config:cache succeeds.
  • All three values remain callable.
  • The generated order, product-variant, and collection URLs remain unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions