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 Data
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 General
.
Good Examples
-
\TYPO3\
has been refactored in TYPO3 v13 to be stateless:CMS\ Core\ Configuration\ Flex Form\ Flex Form Tools - A shared and readonly service with a few dependencies
- A clear scope with reasonable API methods
- No data properties
Bad Examples
-
\TYPO3\
CMS\ Core\ Data Handling\ Data Handler - Far too complex
- Heavily stateful
Service aliases
TYPO3 core provides several service aliases, but
it does not add additional aliases arbitrarily. Injecting state, as in the
extension-
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.
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.