---
title: "Breaking: #110578 - SingletonInterface removed from core services"
manual: "TYPO3 Core Changelog"
version: "main"
permalink: "https://docs.typo3.org/permalink/changelog:breaking-110578-1788251919"
source: "Changelog/15.0/Breaking-110578-SingletonInterfaceRemovedFromCoreServices.rst"
modified: "2026-09-15T10:46:36+00:00"
---

# Breaking: #110578 - SingletonInterface removed from core services

See [forge#110578](https://forge.typo3.org/issues/110578)

## Description

`\TYPO3\CMS\Core\SingletonInterface` predates dependency injection.
Today it has exactly one effect: it turns a class into a *shared* and
*public* service in the dependency injection container, so that the legacy
`GeneralUtility::makeInstance()` can still reach it.

Both properties are already the default for injected services. Every
service in the container is shared, so every class that receives a service
through constructor injection gets the very same instance - that is what
the marker used to guarantee. A service only needs to be *public* on top
of that if it is pulled out of the container by hand.

The marker has therefore been removed from a number of Core services that
are resolved through dependency injection anyway. Their lifetime does not
change: they are still shared.

## Impact

These classes do not implement `SingletonInterface` anymore:

-   `\TYPO3\CMS\Adminpanel\Service\ConfigurationService`
-   `\TYPO3\CMS\Adminpanel\Service\ProcessedImageCollector`
-   `\TYPO3\CMS\Backend\View\BackendLayout\DataProviderCollection`
-   `\TYPO3\CMS\Core\Cache\CacheManager`
-   `\TYPO3\CMS\Core\Console\CommandRegistry`
-   `\TYPO3\CMS\Core\Error\AbstractExceptionHandler`
-   `\TYPO3\CMS\Core\Localization\Locales`
-   `\TYPO3\CMS\Core\Messaging\FlashMessageService`
-   `\TYPO3\CMS\Core\MetaTag\MetaTagManagerRegistry`
-   `\TYPO3\CMS\Core\PageTitle\PageTitleProviderManager`
-   `\TYPO3\CMS\Core\Resource\OnlineMedia\Helpers\OnlineMediaHelperRegistry`
-   `\TYPO3\CMS\Core\Resource\Processing\TaskTypeRegistry`
-   `\TYPO3\CMS\Core\Resource\Rendering\RendererRegistry`
-   `\TYPO3\CMS\Core\Resource\TextExtraction\TextExtractorRegistry`
-   `\TYPO3\CMS\Extbase\Persistence\Generic\Qom\QueryObjectModelFactory`
-   `\TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter`
-   `\TYPO3\CMS\Extbase\Service\ExtensionService`
-   `\TYPO3\CMS\Extbase\Validation\ValidatorResolver`
-   `\TYPO3\CMS\Install\Service\SessionService`
-   `\TYPO3\CMS\Scheduler\Scheduler`
-   `\TYPO3\CMS\Workspaces\Service\Dependency\CollectionService`

`GeneralUtility::setSingletonInstance()` and
`GeneralUtility::removeSingletonInstance()` only accept
`SingletonInterface` instances and raise a `TypeError` for these
classes. This mostly affects tests that substitute one of them.

Every type converter extending `AbstractTypeConverter` is affected as
well, since the marker was inherited. Converters registered with the
`extbase.type_converter` tag stay public, because the tag makes them
public on its own.

Some of these services are not public anymore either, so fetching them
with `GeneralUtility::makeInstance()` or `$container->get()`
fails with an `ArgumentCountError` or a
`ServiceNotFoundException`. Whether a Core service is public is an
implementation detail and must not be relied upon.

## Affected installations

Installations with extensions that fetch one of the listed classes through
`GeneralUtility::makeInstance()` or the container, or that replace one
of them in tests via `GeneralUtility::setSingletonInstance()`.

## Migration

Inject the service instead of fetching it. Since services are shared, the
injected instance is the same one the rest of the request uses:

**EXT:my_extension/Classes/Service/MyService.php**

```php
use TYPO3\CMS\Core\Localization\Locales;

final readonly class MyService
{
    public function __construct(
        private Locales $locales,
    ) {}

    public function doSomething(): void
    {
        // instead of GeneralUtility::makeInstance(Locales::class)
        $this->locales->createLocale('de');
    }
}
```

The same applies to tests that replaced one of these classes globally with
`GeneralUtility::setSingletonInstance()`. Hand the dependency to the
subject instead: a stub or a mock if the test only needs the collaborator
to be there, or a functional test if it should exercise the real one.

This is cleaner and framework-agnostic: the subject is built with plain
PHP, and everything it works with is visible in the test itself, instead
of being smuggled in through a global registry that the code under test
happens to read from.

`SingletonInterface` itself is not deprecated and keeps working for
custom classes. If a class of your own cannot use dependency injection -
typically because it is instantiated with constructor arguments, which
bypasses the container - it can also be made public explicitly, without
the marker:

**EXT:my_extension/Classes/Service/MyLegacyService.php**

```php
use Symfony\Component\DependencyInjection\Attribute\Autoconfigure;

#[Autoconfigure(public: true)]
final class MyLegacyService
{
}
```
