agentsclimarketplace

Bitrix service locator

Skill bxmaximum/bitrix-framework-skills/skills/bitrix-service-locator

Covers DI container Bitrix\Main\DI\ServiceLocator (PSR-11) — registration of services in the services section of a module's .settings.php file, autowire, retrieving dependencies via has()/get(), constructor injection in application services, action-parameter injection in controllers, binding interfaces to implementations. Applied when moving logic to services, avoiding static calls, and wiring dependencies into services and controller actions (not console/event constructors). Key terms — ServiceLocator, DI, services, autowire, PSR-11, dependency injection, container.From its SKILL.md

Install
npx -y skills add bxmaximum/bitrix-framework-skills --skill bitrix-service-locator

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 19 stars19 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

SKILL.md

8.1 KB, ~1.7k tokens by cl100k_base, as published. Nobody here has run it

ServiceLocator (DI) in Bitrix

Bitrix\Main\DI\ServiceLocator is the kernel's PSR-11 container. It should be retrieved via ServiceLocator::getInstance(), but directly in application code only where dependencies cannot be injected the usual way (factories, legacy, static context, console commands, event handlers).

Layer Rules

ContextConstructor DI via ServiceLocator?How to get services
Application / Infrastructure servicesYesRegister in services, autowire constructors
Controller action parametersYes (autowire)Type-hint service in the action method
Controller constructorNoEngine builds controller with Request only — use action params or init() + ServiceLocator::get()
Console commandsNoCLI does new $commandClass() — call ServiceLocator::get() in execute()
Event handlersNocall_user_func_array — resolve inside the handler method
Messenger receiversYes (must be registered)Handler FQCN must exist in services
  • Domain does not know about the container.
  • Services from Application/ / Infrastructure/ receive dependencies via constructor.
  • Controllers receive services via action parameters only (not constructor).
  • Console commands and event handlers are not created by the container.

Service Registration

File /local/modules/vendor.module/.settings.php:

<?php
return [
    'services' => [
        'value' => [
            // 1. By string name
            'vendor.module.postService' => [
                'className' => \Vendor\Module\Application\Service\PostService::class,
            ],

            // 2. By FQCN (preferred — less magic, IDE support)
            \Vendor\Module\Application\Service\PostService::class => [
                'className' => \Vendor\Module\Application\Service\PostService::class,
            ],

            // 3. Interface → Implementation
            \Vendor\Module\Domain\Repository\PostRepositoryInterface::class => [
                'className' => \Vendor\Module\Infrastructure\Repository\PostRepository::class,
            ],

            // 4. With constructor parameters
            \Vendor\Module\Infrastructure\Http\TelegramClient::class => [
                'className' => \Vendor\Module\Infrastructure\Http\TelegramClient::class,
                'constructorParams' => static fn () => [
                    'token' => getenv('TELEGRAM_BOT_TOKEN'),
                ],
            ],

            // 5. Closure factory (full control over creation)
            \Psr\Log\LoggerInterface::class => [
                'constructor' => static function (): \Psr\Log\LoggerInterface {
                    return \Vendor\Module\Infrastructure\Logger\LoggerFactory::create();
                },
            ],
        ],
        'readonly' => true,
    ],
];

Modes

  • className — simple registration; the container resolves dependencies via autowire (by FQCN from constructor).
  • className + constructorParams — pass scalar parameters.
  • constructor — full control, returns a finished object.

Global Services

The services section can also be used in /local/.settings.php — registration does not require a module:

'services' => [
    'value' => [
        'project.featureFlags' => [
            'className' => \App\FeatureFlags::class,
        ],
    ],
    'readonly' => true,
],

Global services are registered first (registerByGlobalSettings). On Loader::includeModule, module services are registered; if has($code) is already true, the module entry is skipped.

Retrieving a Service

Autowire via Constructor (services only)

final class PostService
{
    public function __construct(
        private readonly \Vendor\Module\Domain\Repository\PostRepositoryInterface $posts,
        private readonly \Psr\Log\LoggerInterface $logger,
    ) {}
}

Simply registering PostService itself is enough — its dependencies will be retrieved from the container by type.

In a Controller (action parameters)

final class Post extends \Bitrix\Main\Engine\Controller
{
    public function getAction(
        int $id,
        \Vendor\Module\Application\Service\PostService $postService,
    ): array {
        return ['post' => $postService->find($id)];
    }
}

Do not type-hint custom services in the controller constructor — ControllerBuilder passes Request only.

In a Console Command / Event Handler

Not created by the container. Resolve explicitly:

$service = \Bitrix\Main\DI\ServiceLocator::getInstance()
    ->get(\Vendor\Module\Application\Service\PostService::class);

Explicit Container Access

$sl = \Bitrix\Main\DI\ServiceLocator::getInstance();

if ($sl->has(PostService::class))
{
    /** @var PostService $posts */
    $posts = $sl->get(PostService::class);
}

Use only where DI is impossible (init.php, global functions, console execute(), event handlers, old callbacks).

Service Overriding

First registration wins. When a module calls registerByModuleSettings, existing codes are skipped (has() → continue). Another module cannot override a service by registering the same key later with readonly: false.

Override via:

  1. /local/.settings.php or /local/.settings_extra.php — global services (loaded before modules), or
  2. ServiceLocator::getInstance()->addInstance($code, $object) / addInstanceLazy() at runtime (e.g. in init.php).
// /local/.settings_extra.php — wins over later module registration for the same key
'services' => [
    'value' => [
        \Vendor\Blog\Domain\Repository\PostRepositoryInterface::class => [
            'className' => \Vendor\Override\Repository\CachedPostRepository::class,
        ],
    ],
    'readonly' => true,
],

Lifecycle

  • Services are singletons per process/request. Do not store per-request state in them; use request scope via method parameters.
  • In long-running CLI processes (messenger-consumer), avoid global state and memory leaks.

Antipatterns

  • Constructor DI on Controller / console command / event handler.
  • $service = new PostService(...); in a controller/command when the service is registered — use action-param DI or ServiceLocator::get().
  • ServiceLocator::getInstance()->get(...) in domain classes — they should not know about the container.
  • Expecting a second module's services entry to override the first registration.
  • Registering "config" as a service without a wrapper — pass config as an object/DTO rather than an array.
  • Mixing global \Bitrix\Main\Application::getInstance()->... via statics instead of injection.

Checklist

  • All application services are in module services or global.
  • The key matches FQCN where possible (autocompletion + clarity).
  • Domain interfaces look at infrastructure implementations only via ServiceLocator.
  • Controllers use action-parameter injection; commands/handlers use ServiceLocator::get().
  • Overrides go through global settings / addInstance, not a later module registration.
  • No circular dependencies (the container throws an Exception in this case).

Persistent Storage (Since main 25.1100)

PersistentStorageInterface is registered in kernel services. Retrieve via:

$storage = ServiceLocator::getInstance()
    ->get(\Bitrix\Main\Data\Storage\PersistentStorageInterface::class);
$storage->set('vendor.module.key', $data, 3600);

See skill bitrix-storage for DeferredStorageDecorator and TTL rules.

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 325,949. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.