Download the PHP package sbuerk/typo3-symfony-di-test without Composer

On this page you can find all versions of the php package sbuerk/typo3-symfony-di-test. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.

FAQ

After the download, you have to make one include require_once('vendor/autoload.php');. After that you have to import the classes with use statements.

Example:
If you use only one package a project is not needed. But if you use more then one package, without a project it is not possible to import the classes with use statements.

In general, it is recommended to use always a project to download your libraries. In an application normally there is more than one library needed.
Some PHP packages are not free to download and because of that hosted in private repositories. In this case some credentials are needed to access such packages. Please use the auth.json textarea to insert credentials, if a package is coming from a private repository. You can look here for more information.

  • Some hosting areas are not accessible by a terminal or SSH. Then it is not possible to use Composer.
  • To use Composer is sometimes complicated. Especially for beginners.
  • Composer needs much resources. Sometimes they are not available on a simple webspace.
  • If you are using private repositories you don't need to share your credentials. You can set up everything on our site and then you provide a simple download link to your team member.
  • Simplify your Composer build process. Use our own command line tool to download the vendor folder as binary. This makes your build process faster and you don't need to expose your credentials for private repositories.
Please rate this library. Is it a good library?

Informations about the package typo3-symfony-di-test

sbuerk/typo3-symfony-di-test

Preamble

This repository and package is just a proof-of-concept implementation and not meant to be a longtime example or demonstration. At lest not yet. Thus, this package lacks for now CGL, code analyzers and test and is not maintained in any case.

Take this into account if you want to contribute, as literally this may be a waste of time because it is not meant to be maintained.

However, while talking about this contribution may be added (pull-request) to co-work on this to have something testable at hand to talk and decide if the included pattern(s) may be used and documented within the TYPO3 core and/or extensions.

Note that this TYPO3 extension will not be released in the TYPO3 Extension repository - either download it/check it out for non-composer installations or monorepo. However, it is registered in public packagist and can be required in composer based installations with:

Introduction

This package contains a TYPO3 extension which demonstrates different Symfony DI techniques.

Using Symfony Service decoration for extendable service factories

It's a common way to use the service factory pattern to create a instance based on different configuration parameters. In these cases the ServiceFactory is used within consumer classes (injected service factory) to retrieve the matching service.

The usual TYPO3 Core way to use service factory forces extension authors to replace the DI definition with the extended service factory which is only possible if the service factory is not final or the factory implements a factory interface and the interface is used as type annotation in core code. Extension authors adopted the same technique for their own extensions.

In cases multiple extension wants to influence the factory to retrieve adjusted (custom) services based on some context data but still keep prior service factory in place this was not possible.

Symfony DI supports service decoration, which can be used to provide a more flexible way for extension authors to influence service creation based on context data. This package contains a demonstration for that technique.

Scenario introduction

For the demonstration scenario we assume, that a way is needed to create specialized service based on a string value as context data with a default service.

That means, that we need a ServiceInterface which can be used for PHP type declarations in methods and return types and ensure that required methods exists. The service itself should be able to retrieve autowired dependencies.

To be able to create service instance based on that context value, a service factory is a good way to archieve that:

If the service factory class is final like in the example above, extension authors are not able to extend the service factory and replace (alias) them in the Symfony DI configuration. Using a registry (injected) or tagged services array could be used to replace the match/context determination by calling a method on all retrieved tagged service to determine the service to use and return.

For the sake of this scenario, it would be nice that extension authors may be able to simple add custom service factories retrieving the prior service factory, so it can fallback to the parent factory:

This is possible in general, but nearly impossible for extension when multiple extension want to do that because the type hint in the constructor.

Given that we introduce a ServiceFactoryInterface it is still a configuration nightmare and instances (developer/maintainer) needs to adjust the chain through multiple extensions which is quite a horrible way.

Combining the use of an interface with the Decorator Pattern this would be more suitable:

we could provide a chain. How to use the decorator pattern with Symfony DI is the background for this example.

Symfony DI service decorator pattern - what is needed ?

From the TYPO3 Core side (or a extension providing this) following basic parts are needed:

Implementation of the aforementioned requirement are explained in detail in next sections.

Based on that, extension authors would

The example implementation for extension authors are also explained later.

Note that default extension Configuration/Services.yaml snippet is assumed for explained explanation using Symfony PHP attributes:

Readup:

BASE: ServiceInterface

The service interface defines the required public facing methods all services requires to implement, for the demonstration purpose a simple ping() method:

BASE: ServiceFactoryInterface

We need to ensure a fixed factory method and additional prepare for decorating services, providing an interface for it is the way to go:

Note that we do not make the constructor part of the interface to allow Symfony DI autowiring for factory implementations.

BASE: DefaultServiceInterface

As we want to provide a default implementation or the service, we implement a generic one. We also uses PHP Attrobites to tell Symfony DI to use this class as implementation for the ServiceInterface (default service):

Note that we need to mark this service public, as we later retrieve it from the DI container within the DefaultServiceFactory - without marking it public the DefaultService would be removed from the DI container leading to an error.

Additionally, we add an alias to the service factory so it is possible to retrieve the DefaultService for the interface, for example in custom service implementations (see later for second service implementation).

BASE: DefaultServiceFactory

Now, we implement a default service factory to retrieve the default service and also being the first class to autowire if ServiceFactoryInterface is requested to be autowired into classes:

The #[AsAlias] attribute is here used to define this class as default factory for the ServiceFactoryInterface.

Note that it is possible for extension loaded later to also use this attribute and override the DefaultServiceFactory if required. The original default can still be autowired by using DefaultServiceFactory type within the constructor. Replacing the default service factory is not part of this demonstration.

EXTENSION: Provide custom service - CustomService

Extension may want to provide custom services for specific context values. For that, they implement the custom service based on the ServiceInterface:

Note that public: true is required here to avoid the removal from the DI container - otherwise the factory could not retrieve it from the DI container.

In some cases it may be useful to retrieve the original default service, which can be achieved by adding it to the service constructor by name and not the interface:

EXTENSION: Provide custom service factory SecondServiceFactory

Usually and mostly known in the TYPO3 community, we would now implement a custom service factory retrieving the DefaultFactory to be able to call it:

which replaces the DefaultFactory for ServiceFactoryInterface when the extension is loaded after the extension providing the base implementation, controlled by require/suggest within composer.json OR depends/suggest in ext_emconf.php in legacy mode.

However, with multiple extension in need to do this it is impossible with this technique to have all in the chain. Some would start to check for other extension and do the implementation casual based in some way which is still a continues maintainance burden and communication/coordination affort for extension authors.

To mitigate these issues, extension authors can use the decorator pattern to decorate the prior service factory which simply build a decorator chain:

We use the default service factory as decoration basis, but note that the retrieved $serviceFactory may already be a decorated service factory from another extension which allows to call the chain within the create method.

#[AsDecorator] attribute is used to tell Symfony DI to decorate the current DefaultServiceFactory instance with the new one, either the initial or the already decorated instance. Using an Interface for type declarations allows us to make all decorated service factories final, because extending is not needed.

The #[AutowireDecorated] flags the constructor argument which retrieves the prior service factory instance, which should be decorated by this implementation. Depending on options (onInvalid) it may be required to make the property nullable and check for execution.

Note that it is possible to use a AbstractServiceFactory instead of an interface in type hints and configuration which all custom service factories needs to implement which may be also a valid approach. Using an interface does not make an abstract implementation impossible, but extension authors are free to use the abstract or not as long as they implement all required interface methods.

Other extension may add additional service factory/factories:

BASE: Demo cli command with injected servicefactory

Let's now implement a cli command which expects autowired ServiceFactoryInterface as constructor argument and call the ServiceFactoryInterface->create() with multiple context values and returning the output of the ServiceInterface->ping() method result:

If we execute this command, we would retrieve following output:

Combining with other techniques

This pattern can be mixed with the Symfony DI service Lazy instantiating feature.

For example, the SecondService could be injected in the SecondServiceFactory and using the lazy option for the autowiring attribute. The service factory would then look similar to following:

which is compatible to the prior implementation, but will change the output of the prior described test command:

The minor but important hint is the service class for second, which is now an automatic created service proxy, here named ServiceInterfaceProxyD7aab2d, returning the SecondService instance when request by calling methods on it by dispatching. The lazy proxy is a intelligent and hidden decorator variant.

The benefit is, that the service must not be marked as public anymore and it is not needed to hand over the container to the factory.

Conclusion / Fazit

From my point of view, using the decorator service (factory) pattern along with lazy service injection even for the default service factory would be a good way for new implementation, allowing the TYPO3 Core to mark factories and services final while keeping extensibility and testability up.

Still open to consider/verify

The [AsDecorator()] attribute allows to define priorities. The pro/cons to use the priority is open to investigate. That should be done before using the Decorator pattern for services or service factories to have proper material and argumentation for the TYPO3 Documentation at hand.


All versions of typo3-symfony-di-test with dependencies

PHP Build Version
Package Version
Requires typo3/cms-core Version ~13.3@dev
typo3/cms-frontend Version ~13.3@dev
typo3/cms-backend Version ~13.3@dev
Composer command for our command line client (download client) This client runs in each environment. You don't need a specific PHP version etc. The first 20 API calls are free. Standard composer command

The package sbuerk/typo3-symfony-di-test contains the following files

Loading the files please wait ....