Skip to main content

Service Definition Loader

Overview

The Service Definition Loader is a powerful feature in DomainFlow Core that automates the process of binding services into your application. By loading service definitions from configuration files, it allows you to register services using a consistent structure—supporting PHP, JSON, and YAML formats. This mechanism simplifies managing dependencies, centralizes configuration, and enables advanced features like factory methods and service tagging.


Key Concepts

  • Supported File Formats: Service definitions can be provided in PHP (returning an array), JSON, or YAML (via the Symfony YAML component). This flexibility lets you choose the format that best fits your project.

  • Definition Structure: Each service definition typically includes:

    • concrete: The fully qualified class name or alias to be bound.
    • factory: A callable for instantiating the service (takes precedence over a concrete definition). The value must already be callable in PHP; textual JSON/YAML method references are not converted into callables.
    • shared: A boolean indicating whether the service should be a singleton.
    • tags: Optional grouping information for later retrieval or processing.
  • Error Handling: The loader validates each definition while processing it. It raises descriptive exceptions for file-reading/parsing errors and invalid top-level structures, but those failures occur before the per-definition service_definition.error event wrapper. Processing failures for an individual definition fire service_definition.error (see System Events) before throwing.

  • Custom File Reading: You can inject a custom file reader for testing or to modify file access behavior, ensuring flexibility when dealing with different file systems or formats.


Method Descriptions

setFileReader($fileReader)

Signature

public function setFileReader(FileReader $fileReader): void

Purpose Allows you to specify a custom file reader. This is particularly useful for testing or when you need to override the default file reading behavior.

ParameterTypeRequired?Description
$fileReaderFileReaderYesAn instance of a file reader to use.

Usage Example

$customReader = new CustomFileReader();
$app->setFileReader($customReader);

loadServiceDefinitions($file)

Signature

public function loadServiceDefinitions(string $file): void

Purpose Loads service definitions from the specified file. The method reads the file, parses its content based on its extension (PHP, JSON, or YAML), and binds each service into the container.

ParameterTypeRequired?Description
$filestringYesPath to the configuration file containing service definitions.

Usage Example

$app->loadServiceDefinitions(__DIR__ . '/config/services.php');
  • Internal Flow:
  1. Checks if the file exists.
  2. If the application's external container cache is a FileContainerCache, tracks $file as a resource for the declarative-definitions cache entry (see below).
  3. Uses a file reader to get the content.
  4. Parses the file according to its extension.
  5. Iterates over each service definition and processes it (binding the service, setting shared flags, and tagging if specified).
Cache freshness for edited definition files

When the application's external cache is a FileContainerCache, loadServiceDefinitions() automatically calls trackResource() for $file against the container's definitions cache key. A later cache read self-invalidates when the file disappears, its modification time changes in either direction, or its SHA-256 content hash changes. This catches same-timestamp edits and restore workflows across process boundaries. This is opt-in and automatic: nothing changes if you aren't using a FileContainerCache as the external cache.


Internal Methods (for Reference)

While not directly invoked by users, these methods handle the heavy lifting:

  • parseDefinitionsFile($file): Parses the configuration file based on its extension. Returns the service definitions as an array.

  • processServiceDefinition($abstract, $definition): Processes each individual service definition by binding it to the application container. It takes into account factory functions, shared flags, and tags.


Comparative Descriptions

  • Concrete vs. Factory Definitions: When both a concrete and a factory key are provided in a service definition, the loader will prioritize the factory callable. The factory key must contain a callable value; a non-callable value is rejected rather than falling back to concrete. PHP definition files can return closures or callable arrays, while JSON and YAML cannot create a live PHP closure or convert a textual method reference automatically.

  • Shared Services: Setting the shared flag to true ensures that the service is instantiated only once and the same instance is returned on subsequent requests, promoting efficient resource usage.

  • Tags: Adding tags to a service definition provides a way to group services, which can be useful for batch processing or retrieving services by category. The value must be an array of strings; malformed tag values are rejected.


Practical Examples

Imagine you have a configuration file (e.g., services.php) with the following structure:

<?php
return [
'Mailer' => [
'concrete' => '\\App\\Mailer\\SmtpMailer',
'shared' => true,
'tags' => ['communication', 'email'],
],
'ReportGenerator' => [
'factory' => function ($container) {
return new \\App\\Reports\\Generator($container->get('Mailer'));
},
'shared' => false,
],
];

You would load these definitions into your application like this:

$app->loadServiceDefinitions(__DIR__ . '/config/services.php');

// Later, you can resolve the services:
$mailer = $app->get('Mailer');
$reportGenerator = $app->get('ReportGenerator');

Extensibility

  • Custom File Reader: Override the default file reader to support alternative storage systems or custom file access strategies.

  • Supporting Additional Formats: While the loader currently supports PHP, JSON, and YAML, you could extend it to handle other configuration formats by enhancing the parsing logic.

  • Dynamic Definition Updates: Consider integrating the loader with a caching mechanism to minimize file reads on every boot, or enabling runtime updates to service definitions for more dynamic environments.

This Service Definition Loader makes it easy to centralize and automate service registration, leading to a more modular and maintainable application architecture.