# Service Map

Gacela resolves sibling pillars (Facade → Factory → Config → Provider) by convention. From `1.12.0` onwards, the **`#[ServiceMap]` attribute** is the preferred way to declare that a class should be able to resolve an additional Gacela service on demand. For example, accessing a Facade from inside a Symfony `Command` without constructor injection.

`#[ServiceMap]` replaces the older DocBlock `@method` dance. Both work; the attribute is more discoverable, survives PHPStan/Psalm out of the box (with [the bundled configs](https://gacela-project.com/docs/1.x/static-analysis.md)), and is friendlier to static tooling.

## Basic usage

```php
use Gacela\Framework\ServiceResolver\ServiceMap;
use Gacela\Framework\ServiceResolverAwareTrait;

#[ServiceMap(method: 'getFacade', className: UserFacade::class)]
final class UserController
{
    use ServiceResolverAwareTrait;

    public function show(int $id): array
    {
        return $this->getFacade()->findUser($id);
    }
}
```

The attribute is repeatable. Declare every resolvable service the class needs:

```php
#[ServiceMap(method: 'getFacade',     className: UserFacade::class)]
#[ServiceMap(method: 'getCatalog',    className: CatalogFacade::class)]
#[ServiceMap(method: 'getLogger',     className: LoggerInterface::class)]
final class DashboardController
{
    use ServiceResolverAwareTrait;

    public function index(): array
    {
        return [
            'user'    => $this->getFacade()->current(),
            'top'     => $this->getCatalog()->popular(),
        ];
    }
}
```

Each `__call()` dispatch is cached. The resolver pool is static across the process, so repeated calls are essentially free.

## DocBlock alternative

The `@method` form is still fully supported and produces identical runtime behaviour. It's useful for legacy code bases and for PHP versions without attribute support:

```php
/**
 * @method UserFacade getFacade()
 */
final class UserController
{
    use ServiceResolverAwareTrait;
}
```

The trait was renamed from `DocBlockResolverAwareTrait` to `ServiceResolverAwareTrait` in `1.12.0`. The old name still works, but new code should use the new trait.

## Relationship with the container

`#[ServiceMap]` is a thin sugar on top of the Locator. The service is ultimately resolved through the main container, respecting every binding, alias, contextual binding and `AnonymousGlobal` declaration registered in `gacela.php`.

If you are authoring a class managed by another container (Symfony, Laravel), prefer constructor injection with [`#[Inject]`](https://gacela-project.com/docs/1.x/inject.md). `#[ServiceMap]` is targeted at classes instantiated outside of Gacela where constructor injection is not practical.

## Limitations

- The `__call()` dispatch means IDEs need the attribute (or `@method`) to autocomplete. Both are read by PhpStorm's Symfony plugin out of the box.
- Protected services (`addProtected()`) cannot be resolved through `#[ServiceMap]`. They are stored as raw closures and the container will not instantiate them.
- Static analysis may still flag magic calls; use the [PHPStan / Psalm configs](https://gacela-project.com/docs/1.x/static-analysis.md) shipped with Gacela to suppress them.
