mironsoft.deBlogDesign Patterns
Magento 2 · Design Patterns
Service Locator &
ObjectManager in Magento 2

Der ObjectManager ist das Service Locator Pattern in Magento 2 — und gleichzeitig das bekannteste Anti-Pattern der Plattform. Dieser Artikel erklärt, warum sein direkter Einsatz in Produktionscode verboten ist, welche Ausnahmen legitim sind und wie korrektes Dependency Injection aussieht.

13 Min. Lesezeit PHP 8.4 Magento 2.4.8

Was ist das Service Locator Pattern?

Das Service Locator Pattern ist ein Entwurfsmuster zur Auflösung von Abhängigkeiten zur Laufzeit. Ein zentrales Service-Locator-Objekt kennt alle registrierten Services und gibt sie auf Anfrage zurück. Anstatt Abhängigkeiten im Konstruktor zu deklarieren, ruft die Klasse den Locator in ihren Methoden auf: $service = ServiceLocator::getInstance()->get('mailer'). Die Klasse erhält den Service, ohne ihn selbst zu instanziieren.

Auf den ersten Blick erscheint das praktisch: Man muss keine langen Konstruktor-Parameterlisten pflegen und kann Services flexibel austauschen. In der Praxis jedoch überwiegen die Nachteile massiv. Der erste und schwerwiegendste: Wer eine Klasse von außen betrachtet — etwa im Code-Review oder beim Schreiben von Tests — sieht nicht, welche Abhängigkeiten die Klasse benötigt. Sie erscheinen nicht im Konstruktor. Man muss den gesamten Methodenrumpf lesen, um alle Service-Locator-Aufrufe zu finden.

Das Service Locator Pattern taucht in vielen Formen auf: als globales Registry-Objekt, als statische Utility-Klasse oder als Dependency Injection Container der direkt aufgerufen wird. In Magento 2 ist die bekannteste Ausprägung der direkte Aufruf von ObjectManager::getInstance(). Dieser Aufruf ist der Magento-spezifische Service Locator — und er ist in Produktionscode strikte verboten.

Es ist wichtig, das Service Locator Pattern vom Dependency Injection Container zu unterscheiden. Beide sind Container, die Abhängigkeiten verwalten. Der entscheidende Unterschied liegt in der Nutzungsweise: Ein DI-Container injiziert Abhängigkeiten proaktiv in den Konstruktor — die Klasse muss nichts tun. Ein Service Locator stellt Services auf explizite Anfrage bereit — die Klasse muss aktiv nach ihnen fragen und dabei eine Abhängigkeit zum Locator selbst eingehen. Magento's DI-Container ist ein valider DI-Container; der direkte ObjectManager-Aufruf verwandelt ihn in einen Service Locator.

Die Rolle des ObjectManagers in Magento 2

Der Magento\Framework\ObjectManager ist das Herzstück des Magento 2 Dependency Injection Systems. Er ist für die Instanziierung aller Objekte im System verantwortlich, löst Konstruktor-Abhängigkeiten automatisch auf, verwaltet Shared Instances (das Object Pool Pattern) und verarbeitet die DI-Konfiguration aus di.xml-Dateien. Intern ist der ObjectManager das Fundament, auf dem das gesamte Framework aufbaut.

Magento selbst nutzt den ObjectManager intern intensiv. Der Bootstrap-Prozess, das Request-Routing, das Plugin-System und die Factory-Erzeugung laufen alle über den ObjectManager. Wenn Magento setup:di:compile ausführt, analysiert es alle Klassen, löst Abhängigkeitsketten auf und generiert optimierten PHP-Code, der den ObjectManager beim regulären Betrieb weitgehend umgeht — das ist der Grund, warum Magento im Produktionsmodus schneller läuft als im Entwicklermodus.

Die öffentliche Klasse Magento\Framework\App\ObjectManager bietet über getInstance() einen globalen Zugriffspunkt. Dieser globale Zugriffspunkt ist das Problem. Er gibt externem Code die Möglichkeit, den DI-Container als Service Locator zu missbrauchen — und dieser Missbrauch ist in der Magento-Community weit verbreitet, insbesondere in älterem Code und schnell geschriebenen Erweiterungen.

Interessanterweise ist es gerade die Macht des ObjectManagers, die seinen direkten Einsatz so verführerisch macht. Mit einer Zeile Code erhält man Zugriff auf jeden Service im System, ohne Konstruktoren anpassen zu müssen. In Magento 1 war die globale Mage::getModel()-Funktion das Äquivalent — und viele Magento-1-Entwickler brachten dieses Muster nach Magento 2 mit, wo es als Anti-Pattern gilt.

<?php
// VERBOTEN: Service Locator Pattern via ObjectManager (Anti-Pattern)
use Magento\Framework\App\ObjectManager;

class BadCheckoutHelper
{
    public function getCartTotal(): float
    {
        // Abhängigkeiten sind hier versteckt — weder im Konstruktor noch sichtbar
        $om = ObjectManager::getInstance();
        $cart = $om->get(\Magento\Checkout\Model\Cart::class);
        $session = $om->get(\Magento\Checkout\Model\Session::class);
        $logger = $om->get(\Psr\Log\LoggerInterface::class);

        // Diese Klasse kann nicht ohne laufenden ObjectManager getestet werden.
        // PHPStan-ECG-Regeln markieren diese Zeilen als Fehler.
        $quote = $session->getQuote();
        return (float) $quote->getGrandTotal();
    }
}

Warum ObjectManager::getInstance() ein Anti-Pattern ist

Der erste und kritischste Einwand gegen den direkten ObjectManager-Einsatz ist die Unsichtbarkeit der Abhängigkeiten. In einer korrekt implementierten Klasse beschreibt der Konstruktor alle Abhängigkeiten — er ist ein Vertrag: "Diese Klasse braucht genau diese Services, um zu funktionieren." Wer einen ObjectManager-Aufruf in einer Methode versteckt, bricht diesen Vertrag. Die Klasse hat eine versteckte Abhängigkeit, die nach außen nicht kommuniziert wird.

Das zweite, für das tägliche Entwicklerleben besonders schmerzhafte Problem: Klassen mit direkten ObjectManager-Aufrufen können in Unit Tests nicht isoliert getestet werden. Unit Tests erfordern das Mocken von Abhängigkeiten — man ersetzt echte Implementierungen durch Test-Doubles. Das ist mit Constructor Injection trivial. Bei ObjectManager-Aufrufen in Methodenrümpfen muss man einen globalen Zustand manipulieren, was Tests instabil, langsam und abhängig von der Ausführungsreihenfolge macht.

Das dritte Problem betrifft das Build-System: bin/magento setup:di:compile ist das mächtigste Werkzeug zur Fehlerprüfung in Magento 2. Es analysiert alle Klassen, prüft Konstruktor-Abhängigkeiten gegen registrierte Typen und Interface-Preferences und generiert Factory-Klassen. Diese Analyse findet Tippfehler in Klassennamen, fehlende Preferences und zirkuläre Abhängigkeiten — aber nur in Konstruktoren. ObjectManager-Aufrufe in Methoden sind für diesen Mechanismus unsichtbar. Fehler treten erst zur Laufzeit auf, oft in Produktionsumgebungen.

Das vierte Problem ist die Verletzung des Open/Closed Principle: Klassen mit ObjectManager-Aufrufen sind schwer erweiterbar, weil Plugins und Preferences nur auf Konstruktor-Injektionen wirken. Wenn eine Abhängigkeit direkt über den ObjectManager aufgerufen wird, können keine alternativen Implementierungen über preference-Einträge in di.xml eingespielt werden, ohne den Aufruf selbst zu ändern.

SOLID-Prinzipien und wie ObjectManager sie verletzt

Das Dependency Inversion Principle (DIP) — das "D" in SOLID — besagt, dass High-Level-Module nicht von Low-Level-Modulen abhängen sollen, sondern beide von Abstraktionen. In der Praxis bedeutet das: Klassen sollen gegen Interfaces programmiert werden, nicht gegen konkrete Implementierungen. Der DI-Container hilft dabei, indem er automatisch die richtige Implementierung für ein Interface injiziert.

Ein direkter ObjectManager::get(ConcreteClass::class)-Aufruf verletzt DIP auf doppelte Weise: Erstens referenziert er die konkrete Implementierung statt eines Interfaces. Zweitens ist die Klasse selbst nun direkt von ObjectManager abhängig — einer konkreten Implementierung eines Framework-Services. Das Single Responsibility Principle (SRP) wird verletzt, weil die Klasse nun zwei Verantwortlichkeiten hat: ihre eigentliche Aufgabe und die Verwaltung ihrer Abhängigkeiten.

Das Interface Segregation Principle leidet ebenfalls: Da Abhängigkeiten nicht im Konstruktor deklariert sind, gibt es keinen Anreiz, schlanke, spezifische Interfaces zu definieren. Man neigt dazu, breite, universelle Klassen über den ObjectManager abzurufen, anstatt nur die benötigten Methoden über schmale Interfaces zu exponieren.

Das Liskov Substitution Principle wird verletzt, wenn konkrete Klassen statt Interfaces abgerufen werden: Eine Erweiterung dieser Klasse durch eine Subklasse oder eine Preference ist zwar möglich, wird aber nicht sauber durchgesetzt, weil der ObjectManager-Aufruf die konkrete Klasse direkt referenziert. In Tests kann man die Abhängigkeit nicht durch einen Test-Double ersetzen, weil der ObjectManager-Aufruf mitten im Methodenrumpf sitzt — er ist vom Rest des Codes nicht trennbar.

Legitime Ausnahmen: wann ObjectManager erlaubt ist

Die Regel "kein ObjectManager in Produktionscode" hat wenige, gut definierte Ausnahmen. Diese Ausnahmen sind keine Schlupflöcher — sie sind spezifische Szenarien, in denen kein DI-Kontext existiert oder in denen der ObjectManager explizit als Bootstrapping-Tool benötigt wird. Jede Ausnahme sollte im Code kommentiert und begründet werden.

Der erste legitime Einsatzort sind Bootstrap Entry Points: pub/index.php, pub/cron.php und das bin/magento-CLI-Script. Diese Dateien initialisieren den ObjectManager selbst — sie müssen das tun, weil es keinen anderen Weg gibt, den DI-Container zu starten. Danach übergeben sie die Kontrolle an das Framework, das Constructor Injection verwendet.

Der zweite legitime Einsatzort sind Integrationstests: Magento\TestFramework\Helper\Bootstrap::getObjectManager() ermöglicht den direkten ObjectManager-Zugriff in Integrations-Test-Fixtures. Das ist bewusst so designt: Integrationstests laufen in einem vollständigen Magento-Kontext und müssen Klassen instanziieren, ohne vollständige Klassen-Konstruktoren schreiben zu müssen. Unit Tests dagegen sollen keinerlei ObjectManager-Abhängigkeit haben.

Der dritte Sonderfall betrifft Factory-Klassen, die selbst nicht als Injectable registriert sind. Ein seltenes, aber gültiges Szenario: eine statische Helper-Methode, die außerhalb des DI-Kontexts aufgerufen wird. Hier kann der ObjectManager als Brücke dienen — aber auch das sollte nach Möglichkeit vermieden und durch sauberes DI-Design ersetzt werden. In Magento 2.4.8 gibt es für nahezu jeden dieser Fälle einen saubereren Weg.

<?php
declare(strict_types=1);

// LEGITIM: Bootstrap Entry Point — pub/index.php
// Der ObjectManager wird hier initialisiert, nicht missbraucht
$bootstrap = \Magento\Framework\App\Bootstrap::create(BP, $_SERVER);
$app = $bootstrap->createApplication(\Magento\Framework\App\Http::class);
$bootstrap->run($app);

// LEGITIM: Magento Integrationstest-Fixture
namespace Mironsoft\Blog\Test\Integration;

use Magento\TestFramework\Helper\Bootstrap;

class PostRepositoryTest extends \PHPUnit\Framework\TestCase
{
    private \Magento\Framework\ObjectManagerInterface $objectManager;

    protected function setUp(): void
    {
        // In Integrationstests erlaubt — bewusst so designt
        $this->objectManager = Bootstrap::getObjectManager();
    }

    public function testSaveAndRetrievePost(): void
    {
        $repository = $this->objectManager->get(
            \Mironsoft\Blog\Api\PostRepositoryInterface::class
        );
        // Test-Logik...
    }
}

Der richtige Weg: Constructor Injection in PHP 8.4

Constructor Injection ist der korrekte, von Magento unterstützte und empfohlene Weg, Abhängigkeiten zu verwalten. Die Idee ist einfach: Jede Abhängigkeit, die eine Klasse benötigt, wird als Parameter im Konstruktor deklariert. Magento's DI-Container liest diese Parameter, löst sie automatisch auf und übergibt die passenden Instanzen beim Erstellen der Klasse. Der Entwickler muss nichts weiter tun als die Abhängigkeiten korrekt zu deklarieren.

In PHP 8.4 wird Constructor Injection durch Constructor Property Promotion erheblich vereinfacht. Statt eine Property zu deklarieren, einen Parameter entgegenzunehmen und die Zuweisung zu schreiben, geschieht alles in einer einzigen Zeile im Konstruktor-Parameter. Das Ergebnis ist prägnanter, lesbarer Code ohne Boilerplate. Die Kombination mit readonly stellt sicher, dass Abhängigkeiten nach der Injection nicht überschrieben werden können — Immutabilität auf Property-Ebene.

Ein wichtiges Detail: Magento's DI-Container versteht den Unterschied zwischen Interfaces und konkreten Klassen. Wenn man ein Interface als Konstruktor-Parameter deklariert, schaut der Container in der di.xml-Konfiguration nach der registrierten preference für dieses Interface und injiziert die konfigurierte Implementierung. Das macht den Code flexibel austauschbar: Eine andere Implementierung kann durch einen einfachen Preference-Eintrag in di.xml aktiviert werden, ohne die Klasse selbst anzufassen.

declare(strict_types=1) ist in Magento 2.4.8 Pflicht für alle neuen PHP-Dateien. Es stellt sicher, dass Typfehler bei der Injection sofort als Fehler erkannt werden und nicht durch implizite Typkonvertierungen verborgen bleiben. Zusammen mit PHPStan und den ECG-Coding-Standard-Regeln bildet das ein robustes Sicherheitsnetz gegen ObjectManager-Missbrauch und andere Code-Qualitätsprobleme.

<?php
declare(strict_types=1);

namespace Mironsoft\Order\Model;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Api\SearchCriteriaBuilder;
use Magento\Framework\Exception\NoSuchEntityException;
use Psr\Log\LoggerInterface;

/**
 * Order processor — all dependencies declared via constructor injection.
 * No ObjectManager usage: fully testable, mockable, compile-time-validated.
 */
class OrderProcessor
{
    public function __construct(
        private readonly ProductRepositoryInterface $productRepository,
        private readonly SearchCriteriaBuilder $searchCriteriaBuilder,
        private readonly LoggerInterface $logger,
        // Virtual types and custom arguments can be configured in di.xml
        private readonly string $defaultCurrency = 'EUR'
    ) {}

    /**
     * Process order for a product by ID.
     * All dependencies are available via $this — injected, mockable, typed.
     */
    public function processForProduct(int $productId): void
    {
        try {
            $product = $this->productRepository->getById($productId);
            $this->logger->info('Processing order', [
                'sku'      => $product->getSku(),
                'currency' => $this->defaultCurrency,
            ]);
        } catch (NoSuchEntityException $e) {
            $this->logger->error('Product not found', [
                'product_id' => $productId,
                'error'      => $e->getMessage(),
            ]);
        }
    }
}

Factories statt ObjectManager::create()

Constructor Injection funktioniert hervorragend für Services, Repositories, Logger und andere Injectable Objects, die als Shared Instances verwaltet werden. Für Non-Injectable Objects — also Entities wie Product, Quote, Order, sowie Collections und Value Objects — braucht man einen anderen Ansatz. Diese Objekte müssen für jeden Datensatz neu instanziiert werden und dürfen nicht als Shared Instance geteilt werden.

Magento löst das elegant durch automatisch generierte Factory-Klassen. Wenn man bin/magento setup:di:compile ausführt, analysiert Magento alle Konstruktoren und generiert Factory-Klassen für alle Non-Injectable-Typen, die als Factory verwendet werden. Das Muster ist einfach: Statt MyClass in den Konstruktor zu injizieren, injiziert man MyClassFactory. Magento erkennt das Factory-Suffix und generiert die entsprechende Klasse automatisch.

Der Vorteil gegenüber ObjectManager::create() ist erheblich: Die Factory ist selbst eine Injectable Klasse, die per Constructor Injection empfangen und in Tests durch eine Mock-Factory ersetzt werden kann. Die generierte Factory selbst nutzt intern den ObjectManager — aber das ist legitimiert, weil Factory-Klassen explizit als Ausnahme von der Anti-Pattern-Regel gelten.

Factories können auch mit benutzerdefinierten Argumenten aufgerufen werden: $factory->create(['data' => [...]]). Diese Argumente werden dem Konstruktor des zu erstellenden Objekts übergeben, nachdem der DI-Container seine normalen Abhängigkeiten aufgelöst hat. Das ermöglicht die Übergabe von Request-spezifischen Daten an neue Objekt-Instanzen, ohne den Mechanismus des DI-Containers zu umgehen.

<?php
declare(strict_types=1);

namespace Mironsoft\Blog\Model;

use Mironsoft\Blog\Api\Data\PostInterface;
use Mironsoft\Blog\Api\Data\PostInterfaceFactory;
use Mironsoft\Blog\Api\PostRepositoryInterface;
use Magento\Framework\Exception\CouldNotSaveException;

/**
 * Blog post service — uses Factory instead of ObjectManager::create().
 * The generated PostInterfaceFactory is a fully injectable, mockable class.
 */
class PostService
{
    public function __construct(
        private readonly PostInterfaceFactory $postFactory,
        private readonly PostRepositoryInterface $postRepository
    ) {}

    /**
     * Create and persist a new blog post.
     *
     * @throws CouldNotSaveException
     */
    public function createPost(string $title, string $content, int $authorId): PostInterface
    {
        // Factory::create() instead of ObjectManager::create()
        // The factory is injected — fully mockable in unit tests
        $post = $this->postFactory->create();
        $post->setTitle($title);
        $post->setContent($content);
        $post->setAuthorId($authorId);
        $post->setStatus(PostInterface::STATUS_DRAFT);

        return $this->postRepository->save($post);
    }
}

ObjectManager in Unit Tests mocken und refaktorieren

Wenn man auf Legacy-Code trifft, der ObjectManager direkt verwendet, ist Refactoring die saubere Lösung. Der erste Schritt ist immer das Schreiben eines Unit Tests für die bestehende Klasse — auch wenn der Test zunächst scheitert. Der Test zeigt, welche Abhängigkeiten der ObjectManager auflöst und macht das Refactoring-Ziel klar.

Für Legacy-Code der noch nicht refaktoriert werden kann, bietet Magento ObjectManager::setInstance() als Test-Hilfsmittel. Damit kann man eine Mock-Implementierung des ObjectManagers setzen, die bei get()-Aufrufen die konfigurierten Mocks zurückgibt. Das ist keine elegante Lösung, aber sie ermöglicht das Schreiben von Tests für Legacy-Klassen als Übergangslösung bis zum Refactoring.

Das Refactoring selbst folgt einem klaren Muster: Alle $om->get()-Aufrufe in der Klasse werden identifiziert. Jeder aufgelöste Typ wird als Konstruktor-Parameter mit readonly hinzugefügt. Die Methodenrümpfe ersetzen $om->get(Type::class) durch $this->injectedProperty. Nach dem Refactoring werden alle neuen Abhängigkeiten in Unit Tests als PHPUnit-Mocks übergeben.

Nach dem Refactoring sollte immer bin/magento setup:di:compile ausgeführt werden. Es validiert alle neuen Konstruktor-Abhängigkeiten, erkennt Fehler in Klassennamen und fehlende Preferences und gibt eine vollständige Übersicht über alle Abhängigkeiten im Projekt. Es ist das verlässlichste Werkzeug zur Verifikation eines erfolgreichen DI-Refactorings.

Typische Code-Smells erkennen und beheben

Der häufigste ObjectManager-Code-Smell ist der direkte Aufruf in Methoden: ObjectManager::getInstance()->get() oder ObjectManager::getInstance()->create() irgendwo im Methodenrumpf. Mit grep oder PHPStan lassen sich diese Aufrufe schnell im gesamten Codebase identifizieren. PHPStan mit den ECG-Regeln aus dem magento/magento-coding-standard-Paket markiert sie automatisch als Fehler.

Ein zweiter Code-Smell: Die ObjectManager-Instanz als Konstruktor-Parameter. Manche Entwickler erkennen das Problem mit direkten getInstance()-Aufrufen und injizieren stattdessen ObjectManagerInterface per Constructor. Das ist besser als getInstance(), aber immer noch ein Anti-Pattern. Der ObjectManager sollte niemals als Abhängigkeit injiziert werden — er ist ein Framework-Internaltool, keine Anwendungs-Abhängigkeit.

Ein dritter Code-Smell findet sich in Observer-Klassen: Viele Legacy-Observer rufen in der execute()-Methode den ObjectManager auf, um Services zu holen. Observer-Klassen sind vollwertige Injectable Classes und können alle ihre Abhängigkeiten per Constructor Injection deklarieren. Es gibt keinen technischen Grund, in einem Observer den ObjectManager zu verwenden.

PHPStan mit Level 8 und aktivierten ECG-Regeln ist das effizienteste Werkzeug zur systematischen Erkennung aller ObjectManager-Smells. Es lässt sich in CI/CD-Pipelines integrieren und verhindert, dass neue Anti-Patterns eingeführt werden. Die Konfiguration erfolgt über eine phpstan.neon-Datei im Projektstamm.

Service Locator & ObjectManager — Das Wichtigste auf einen Blick

Verboten in Produktionscode

ObjectManager::getInstance()->get() in Klassen, Blöcken, ViewModels, Plugins, Beobachtern. PHPStan-ECG-Regeln markieren es als Fehler.

Constructor Injection = korrekt

Alle Abhängigkeiten im Konstruktor deklarieren. PHP 8.4 Constructor Property Promotion mit readonly. DI-Container löst automatisch auf.

Factory statt create()

Für Non-Injectables: generierte ProductFactory per Injection, nicht $om->create(). Mockbar, testbar, DI-validiert.

Legitime Ausnahmen

Bootstrap Entry Points, Integrationstests. Immer kommentieren. In normalen Klassen niemals.

Mironsoft

Magento 2 Code-Qualität & DI-Architektur

ObjectManager-Code durch DI ersetzen?

Wir analysieren und migrieren ObjectManager-Anti-Patterns in Magento 2 Projekten zu sauberer Dependency Injection. Code-Review, PHPStan-Integration und vollständige Testabdeckung inklusive.

Code-Audit

Alle ObjectManager-Aufrufe finden und nach Kritikalität priorisieren

DI-Migration

Constructor Injection, Factory-Pattern und Service Contracts einführen

PHPStan Setup

ECG-Regeln aktivieren um neue Anti-Patterns automatisch zu erkennen

Zusammenfassung

Das Service Locator Pattern und sein Magento-Ausdruck — der direkte ObjectManager-Aufruf — sind eines der häufigsten Anti-Patterns in Magento 2 Projekten. Die Verlockung ist verständlich: eine Zeile Code für Zugriff auf jeden Service. Der Preis ist hoch: unsichtbare Abhängigkeiten, untestbarer Code, keine Compile-Time-Validierung und Verletzung aller relevanten SOLID-Prinzipien.

Constructor Injection ist die saubere Alternative. Mit PHP 8.4 Constructor Property Promotion und readonly ist sie nicht mehr aufwendiger zu schreiben als ein ObjectManager-Aufruf — aber deutlich wartbarer, testbarer und zukunftssicherer. Generierte Factory-Klassen lösen das Problem der Non-Injectable Objects vollständig, ohne auf den ObjectManager zurückgreifen zu müssen.

Die wenigen legitimen Ausnahmen — Bootstrap Entry Points und Integrationstests — sind klar definiert und sollten immer kommentiert werden. PHPStan mit ECG-Regeln und die Ausführung von setup:di:compile in CI/CD-Pipelines schützen vor dem schleichenden Einzug neuer ObjectManager-Anti-Patterns in bestehenden Code.

FAQ: Service Locator & ObjectManager in Magento 2

1 Was ist das Service Locator Pattern?
Ein Container der Services auf Anfrage bereitstellt. Statt Abhängigkeiten im Konstruktor zu deklarieren, fragt die Klasse den Locator zur Laufzeit. Macht Abhängigkeiten unsichtbar und untestbar — deshalb gilt er in modernem Anwendungscode als Anti-Pattern. In Magento 2 manifestiert er sich als direkter ObjectManager-Aufruf.
2 Warum ist ObjectManager::getInstance() ein Anti-Pattern?
Abhängigkeiten sind unsichtbar (nicht im Konstruktor), Unit Tests können nicht mocken, setup:di:compile findet keine Fehler, PHPStan/ECG-Regeln markieren es als Error. Es verletzt Dependency Inversion, Open/Closed und Single Responsibility Principle.
3 Wie ersetzt man ObjectManager::getInstance()->get()?
Durch Constructor Injection: die Klasse oder das Interface als Parameter im Konstruktor deklarieren. DI-Container injiziert automatisch. PHP 8.4 Constructor Property Promotion mit readonly: private readonly ProductRepositoryInterface $repo.
4 Wie ersetzt man ObjectManager::getInstance()->create()?
Durch Factory-Pattern: generierte ProductFactory per Constructor Injection injizieren, per $this->productFactory->create() aufrufen. Magento generiert Factory-Klassen automatisch beim Di:compile.
5 Wann darf man ObjectManager in Magento 2 legitim verwenden?
In Bootstrap Entry Points (pub/index.php), in Integrationstests-Fixtures und in benutzerdefinierten CLI-Entry-Points außerhalb des DI-Kontexts. In normalen Klassen, ViewModels, Plugins, Blocks und Beobachtern niemals.
6 Wie mockt man den ObjectManager in Unit Tests?
Mit korrekt strukturierten Klassen (Constructor Injection) braucht man keinen ObjectManager-Mock — alle Abhängigkeiten werden direkt als PHPUnit-Mocks übergeben. Für Legacy-Code: ObjectManager::setInstance() als Übergangslösung bis zum Refactoring.
7 Wie findet man ObjectManager-Aufrufe in einem Magento-Projekt?
Per grep: grep -rn 'ObjectManager::getInstance'. Oder PHPStan mit magento/magento-coding-standard-ECG-Regeln — markiert alle unerlaubten ObjectManager-Aufrufe automatisch als Fehler, integrierbar in CI/CD.
8 Was ist der Unterschied zwischen ObjectManager::get() und ObjectManager::create()?
get() gibt eine Shared Instance zurück — Singleton-Verhalten. create() erstellt immer eine neue Instanz — Factory-Verhalten. Korrekt: Shared Instances via Constructor Injection, neue Instanzen via generierte Factory-Klassen.
9 Erkennt setup:di:compile fehlerhafte ObjectManager-Aufrufe?
Nein. setup:di:compile validiert DI-Konfiguration und Konstruktoren, analysiert aber keinen Laufzeit-Code in Methodenrümpfen. Dafür ist PHPStan mit ECG-Regeln notwendig.
10 Wie refaktoriert man eine Klasse, die ObjectManager direkt verwendet?
Schritt 1: Alle $om->get()- und create()-Aufrufe identifizieren. Schritt 2: Typen als Konstruktor-Parameter deklarieren. Schritt 3: Methoden-Aufrufe durch $this->property ersetzen. Schritt 4: Unit Tests schreiben. Schritt 5: setup:di:compile ausführen.