> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Package service resolution hooks

> Examine Laravel 13's callAfterResolving() and learn how to extend services whether or not they have been resolved yet, along with caveats for singletons, regular bindings, and repeated registration.

When a package adds functionality to a Laravel service, you often want to avoid creating the instance until it is actually used, while still applying your configuration if it has already been created. The service provider's `callAfterResolving()` is a protected method that combines these two behaviors.

This page examines the implementation in Laravel Framework `v13.35.0` and digs deeper into extending services from `boot()`, as introduced in [Laravel package development](/en/advanced/package-development). Note that this is not a hook that waits for the entire application to finish booting; it is a hook on the resolution of a specific service.

## Wait if unresolved, run now if already resolved

`ServiceProvider::callAfterResolving()` works in two steps:

1. It registers the callback with the container's `afterResolving()`.
2. If `resolved()` returns true, it retrieves the service with `make()` and also invokes the callback immediately.

If the service has not been resolved yet, the method itself does not call `make()` on the target. During normal resolution, the container builds the object, applies extenders, calls the `resolving` callbacks, and then calls the `afterResolving` callbacks.

```mermaid theme={null}
flowchart TD
    A["callAfterResolving() in boot()"] --> B["Register with afterResolving"]
    B --> C{"Is the target resolved()?"}
    C -->|No| D["Do not create the target now"]
    D --> E["Target is resolved later"]
    E --> F["Run the registered callback"]
    C -->|Yes| G["Retrieve the target with make()"]
    G --> H["Also run the callback immediately"]
```

| Approach | Unresolved at registration | Singleton already resolved at registration |
| - | - | - |
| Call `$this->app->make()` and configure directly | Creates the instance immediately | Configures the existing instance |
| `$this->app->afterResolving()` | Waits for future resolution | Registration alone does not configure the existing instance |
| `$this->callAfterResolving()` | Waits for future resolution | Also configures the existing instance immediately |

When retrieving a singleton normally, the container returns the stored instance early, so `afterResolving` does not fire on every retrieval. The key point is that simply registering `afterResolving()` misses configuration for singletons created before the registration.

## Add a rule to the validation factory

As an example of providing a package-specific string rule, register the rule after `validator` is resolved. The official `ValidationServiceProvider` registers this key as a singleton, and the provider itself supports deferred loading.

```php theme={null}
<?php

namespace Acme\Courier;

use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Factory;

class CourierServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->callAfterResolving('validator', function (Factory $factory): void {
            $factory->extend(
                'courier_tracking_code',
                static function ($attribute, $value, $parameters, $validator): bool {
                    return is_string($value)
                        && preg_match('/\ACR-[0-9]{8}\z/', $value) === 1;
                },
                'The :attribute must be a valid courier tracking code.',
            );
        });
    }
}
```

The consuming application uses it alongside regular rules.

```php theme={null}
$request->validate([
    'tracking_code' => ['required', 'string', 'courier_tracking_code'],
]);
```

The `extend()` in this example is the rule registration API of `Illuminate\Validation\Factory`. It is a different method from the container's `extend()` described later. Regular custom rules may not run for empty values, so specify that the field is required with `required`. For details on designing the rule itself, see [Custom validation rules](/en/advanced/custom-validation-rules).

`Factory::extend()` overwrites the array entry with the same rule name, so registering the same logic again, as in this example, does not add more entries. However, to avoid overwriting rules from other packages, prefix the rule names you expose with a package-specific prefix.

<Info>
  The callback runs when the factory is resolved. The rule's validation closure runs later, when the Validator validates the target value. Input values are not validated when the hook is registered.
</Info>

## Match the target key with the resolved check

For the container's normal resolution events, callbacks are selected not only when they match the registered key but also when they match the type of the retrieved object. On the other hand, `resolved($name)`, which `callAfterResolving()` uses for the immediate check, looks at the alias-normalized key to see whether it has a resolved flag or a stored instance. It does not search through all objects that have been created.

Therefore, just because an interface or class has been resolved does not mean that `resolved()` will also return true for a different key. Check the actual binding and aliases of the target service, and test the already-resolved case with the same key. The example above specifies `validator`, which is registered by the official provider.

Also note that `resolved()` includes the condition "has been resolved in the past." It does not mean that the current instance is guaranteed to still exist.

## Not an API that guarantees "run only once"

`callAfterResolving()` leaves the callback registered. Even after it runs immediately, the registered callback runs again whenever the object is newly resolved in the future. In addition, calling the method again adds another callback.

Take particular care when a regular non-shared binding has already been resolved. In the implementation we examined, the following happens:

1. A new callback is registered.
2. Because `resolved()` is true, `make()` is called.
3. `make()` creates a new object, and the callback runs during its resolution event.
4. The immediate callback also runs on the same object returned from `make()`.

On this path, the newly registered callback is applied to the same object twice. Do not reuse a design that assumes only singleton retrieval for regular bindings as-is.

<Warning>
  Do not send emails, call external APIs, charge payments, or unconditionally add listeners inside the callback. Limit its use to applying configuration to the service idempotently, and design it so that side effects are not duplicated by repeated registration or resolution.
</Warning>

## Use a different API to replace objects

The return value of an `afterResolving` callback is not used to replace the object returned by the container. If you want to return a decorator and swap out the service itself, consider the container's `extend()`. The closure for this API is contractually required to return the modified service.

Similarly, `callAfterResolving()` is not a mechanism for updating every dependency that has already been stored in other objects. If your goal is to update dependencies when a binding is rebound, check `rebinding()` in the official documentation and the design of the target class.

For the requirements of providers that truly defer loading services, see [DeferrableProvider](/en/advanced/deferred-provider). Using a hook and making the provider that registers the hook itself deferred are two separate things.

## Combinations to check when updating

Before releasing a package, verify not only the normal boot order but also the case where another provider uses the service first.

| Case | What to verify |
| - | - |
| Register the hook while the target is unresolved | Registration alone does not create the target, and the configuration is applied on first resolution |
| Resolve the singleton first, then register | The configuration is applied immediately to the same instance |
| Retrieve a configured singleton again with a normal `make()` | The same instance is returned, and resolution callbacks do not run again |
| Register on a regular binding that was resolved in the past | Nothing breaks even if it is applied twice, by the `make()` at registration and by the explicit call |
| Register the hook multiple times or recreate the instance | The same rules, listeners, paths, and so on do not accumulate unintentionally |
| Update the supported Laravel version | Recheck the binding key, whether it is shared, and the hook execution path |

If you record "used once across the entire application" with a static flag, configuration may be missed for services rebuilt in a new container or in tests. Ensure idempotency at the level of the target object or the registered key whenever possible.

## Related pages

<Columns cols={2}>
  <Card title="Overriding and updating package views" icon="eye" href="/en/advanced/package-views">
    See an example of how loadViewsFrom() uses a resolution hook to register a view namespace.
  </Card>

  <Card title="Package translation overrides and updates" icon="language" href="/en/advanced/package-translations">
    Review how Translator namespaces are registered and how already loaded translations are handled.
  </Card>
</Columns>

## Primary sources consulted

* [Laravel documentation: Package development](https://github.com/laravel/docs/blob/13.x/packages.md)
* [Laravel documentation: Container events, extending bindings, and rebinding](https://github.com/laravel/docs/blob/13.x/container.md#container-events)
* [Laravel documentation: Custom validation rules](https://github.com/laravel/docs/blob/13.x/validation.md#custom-validation-rules)
* [ServiceProvider: callAfterResolving() and its use in resource registration](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Support/ServiceProvider.php)
* [Container: resolved(), resolve(), and afterResolving()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Container/Container.php)
* [ValidationServiceProvider: singleton registration of validator](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Validation/ValidationServiceProvider.php)
* [Validation Factory: rule registration with extend()](https://github.com/laravel/framework/blob/v13.35.0/src/Illuminate/Validation/Factory.php)


## Related topics

- [Laravel Package Development](/en/advanced/package-development.md)
- [Advanced Topics](/en/advanced/index.md)
- [BlueskyManager and HasShortHand](/en/packages/laravel-bluesky/bluesky-manager.md)
- [Service container](/en/service-container.md)
- [Session hooks](/en/packages/laravel-copilot-sdk/hooks.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.