Services

Characteristics

  • Services MUST be used as objects, they are never static.
  • Services SHOULD be stateless and shared.
  • Services MAY use their own configuration, but they SHOULD not.
  • Services MAY have dependencies to other services and SHOULD get them injected using TYPO3 Core dependency injection.

Rationale

Modern PHP programming primarily involves two types of classes: Services and data objects (DO).

This distinction has gained significance with the introduction of dependency injection in the TYPO3 core.

A well-designed service class comprise of one or more methods that process data, or just provide a data sink. For example, a mailer service might take a mail data object to send it. Conversely, service methods often return new or modified data based on input. A typical example is a repository service that accepts an identifier (e.g. the uid of a product) and returns a data object (the product).

Services may depend on other services and should use dependency injection to obtain these dependencies, typically using constructor injection for "leaf" classes and method injection for abstract classes.

In TYPO3, most classes are service classes unless they function as data objects to transport data.

Services should be stateless. The result of a service method call should only depend on the given input arguments and the service should not keep track of previous calls made. This is an important aspect of well crafted services. It reduces complexity significantly when a service does not hold data itself: It does not matter how often or in which context that service may have been called before. It also means that stateless services can be "shared" and declared readonly: They are instantiated only once and the same instance is injected to all dependent services.

TYPO3 core has historically stateful services that blend service class and data object characteristics. These stateful services pose issues in a service-oriented architecture: Injecting a stateful service into a stateless service make the latter stateful, potentially causing unpredictable behavior based on the prior state of the injected service. A clear example is the core DataHandler class which modifies numerous data properties when its primary API methods are called. Such instances become "tainted" after use, and should not be injected but created on-demand using GeneralUtility::makeInstance().

Good Examples

Bad Examples

Service aliases

TYPO3 core provides several service aliases, but it does not add additional aliases arbitrarily. Injecting state, as in the extension-configuration example, makes services stateful, which is undesirable unless the state does not change at runtime. Aliases for services that act as shortcuts for factories, like the cache.runtime example, will only be added for services that are used very frequently.

Service aliases also present backward compatibility challenges when modified. To avoid excessive clutter, TYPO3 core limits the number of service aliases. Developers needing aliases for core services can always add them in instance-specific extensions. The inclusion of such aliases in TYPO3 core will remain a case-by-case decision.

Further Reading