Zend DI 3.0 is here!

Zend DI is a ZendFramework component, that provides auto-wiring capabilities for IoC containers. But the version 2.x had some mayor performance-drawbacks.
For version 3.0 we thought it would be a nice idea to get rid of these drawbacks…

Why we did it

Defining services in ZendServicemanager requires you to implement a factory for every instance you want to consume. For classes without dependencies, this is no problem at all, the ServiceManager provides setInvokableClass() for this case, but if classes consume dependencies, this requires writing simple factories that pass $container->get(InterfaceName::class) to the constructor most of the time.

A solution that provides automatic wiring for such simple cases would be great. Indeed there is one: Zend DI. But the version 2.x of this component adds performance penalties, inconsistencies and error-prone instantiation to your code.
Also, it is not recommend to use this in production…

Therefore one of our senior developers – who is also one of the ZendFramework experts in our team – decided to re-factor this component to provide a solid and production-ready solution.
In the true spirit of Open Source we decided to give it back to the community, which was accepted and published as the next major release of the component.
(*Yeah!*)

Why you should use it

So what’s the fuss about and why should you bother using it?
This is easy to answer: it is designed to assist you when implementing a ZendFramework Application (whether MVC or Expressive).

Let me give you an example: Assuming you are going to develop an expressive application that will use Doctrine as persistence framework, you will most likely consume the object manager in some classes (e.g. a Middleware to handle authentication).

So you would have something like this:


use Doctrine\Common\Persistence\ObjectManager;

class MyMiddleware
{
    public function __construct(ObjectManager $om)
    { /* ... */ }
}

class MyAction
{
    public function __construct(ObjectManager $om, SomeOtherService $foo)
    { /* ... */ }
}

Let’s assume you already have configured a service called „Doctrine\Common\Persistence\ObjectManager“ (e.g. an instance of doctrine ORM’s EntityManager) and another service called „SomeOtherService“.

So you now have to write a Service factory for each class to provide it via Zend ServiceManager:


use Doctrine\Common\Persistence\ObjectManager;
use Psr\Container\ContainerInterface;

class MyMiddlewareFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new MyMiddleware($container->get(ObjectManager::class));
    }
}

class MyActionFactory
{
    public function __invoke(ContainerInterface $container)
    {
        return new MyAction(
            $container->get(ObjectManager::class),
            $container->get(SomeOtherService::class)
        );
    }
}

You also have to create new factories for each new class you add. Even though the fact you can recycle factories like this, it will require another factory as soon as dependencies on the class change:


use Doctrine\Common\Persistence\ObjectManager;
use Psr\Container\ContainerInterface;

class ProvideObjectManagerFactory
{
    public function __invoke(ContainerInterface $container, $classname)
    {
        return new $classname($container->get(ObjectManager::class));
    }
}

And don’t forget as for best practices you should test your factories …

Now doing this rather trivial stuff seems like a waste of time!

It’s relatively clear that in general the ObjectManager (as shown in the example) should be the configured service with the class or interface name from the service manager – so let’s automate it with Zend DI 3!

At first, we will install the component with composer:

composer install zendframework/zend-di

When you are using ZendFramework’s component installer, it will take care of configuring the integration into MVC or Expressive for you. If not – and we’re assuming expressive in this example – there is a config provider that contains the required configuration: Zend\Di\ConfigProvider. For MVC there is a module available: Zend\Di\Module

Now after Zend DI is in place, you can get rid of the factories mentioned above and directly consume your class.

Zend DI will take care of injecting the ObjectManager instance:


use Doctrine\Common\Persistence\ObjectManager;

class MyMiddleware
{
    public function __construct(ObjectManager $om)
    { /* ... */ }
}

class MyAction
{
    public function __construct(ObjectManager $om, SomeServiceInterface $foo)
    { /* ... */ }
}

// No need for a factory now
$serviceManager->get(MyMiddleware::class);

But what if my class consumes an Interface as a dependency instead of a concrete class? The answer is pretty simple: Alias the interface in the service manager configuration. Adding the alias is enough.

The Concrete class will be instantiated by zend-di automatically:

/* part of the zend-servicemanager config: */
return [
    'aliases' => [
        SomeServiceInterface::class => SomeServiceImplementation::class,
        // But you can also alias to services provided by a factory as usual:
        ObjectManager::class => 'doctrine.orm.entity_manager.default',
    ],
];


So it’s simple math: without Zend DI 3 you need one factory per service, with Zend DI 3 you need none (best case) or just a small set of factories for complex cases.

How to get it in production

But what about performance?
…all dependencies are auto-wired at runtime at the cost of performance!

And this is why it comes with a code generator that allows you to resolve dependencies and generate factories ahead of time (AoT).

The official documentation shows how to implement this in detail.

By adding this feature to our project, we improve the performance drastically. The service manager now uses pre-generated factories without lookups to the abstract AutowireFactory or runtime wiring.

Before finally deploying the code to production we can improve performance even further by updating our autoloader with the -o option (Optimize by building class maps):

composer dump-autoload -o

How to use it with older PHP versions

Since newer ZendFramework component releases will drop support for PHP versions before 7.1, you may not be able to use Zend DI 3.x with these PHP versions. But we’ve got you covered. We created a PHP 5.6+ compatible backport on Github that you can use to benefit from these changes and make your code future-proof. When you are ready to upgrade to PHP 7.1 or later, you can then seamlessly switch to the official zend-di.

Please be aware that this backport is not an official part of ZendFramework.

Alles Definitionssache – besonders in der Projektkonzeption

Amerikanische Wissenschaftler haben herausgefunden, dass über 70% aller Softwareprojekte in der Planungsphase zu wenig Zeit/Ressourcen in die Projektkonzeption investierten – und wenn amerikanische Wissenschaftler das noch nicht rausgefunden haben, dann sollten sie das schleunigst tun.

Was der Kunde beschrieb vs. Was der Kunde wirklich brauchte - klassisches Missverständnis in der Projektkonzeption

Aber ernsthaft: Wer schon mehr als eine Handvoll Projekte mitgemacht hat, kann mit ziemlicher Sicherheit eine ganze Reihe von Anekdoten erzählen. Was damals für schlaflose Nächte und zerraufte Haare gesorgt hat, wird mit der nötigen zeitlichen Distanz oft mit einem Lächeln erzählt werden und hatte hoffentlich einen Lerneffekt bei allen Beteiligten.

Was der Kunde sagt

Können Sie das nicht definieren? Sie haben da doch mehr Erfahrung…

Wer einer Feierlichkeit plant würde auf die Frage des Dienstleisters „Für wie viele Gäste müssen wir denn die Räumlichkeiten herrichten?“ wahrscheinlich nie die oben zitierte Antwort geben.
Woher sollte der Dienstleiter wissen, wie viele Personen eingeladen sind und wie viele dann im Endeffekt kommen?

In der IT hingegen sind solche Antworten nicht selten – z.B. in Bezug auf Serverdimensionierung. Das führt dann dazu, dass eine unterdimensionierte Infrastruktur von Anfragen überrannt wird, Benutzer verärgert werden und Kunden zornig bei Dienstleistern anrufen. Und dann muss „mit heißer Nadel“ in nächtlichen Sitzungen die Applikationssoftware optimiert oder die Hardware aufgestockt werden.

Gut – in Zeiten von Cloud-Lösungen und skalierenden Systemen kann recht dynamisch auf veränderliche Anforderungen reagiert werden, aber auch hier gibt es Grenzen. Denn was in der Programmierung nicht berücksichtigt wurde, kann auch durch mehr Hardware nur bedingt kompensiert werden. Und wenn im Vorfeld – wahrscheinlich aus Kostengründen – keine dynamische Lösung geplant wurde, nutzt auch die ganze Magie der Cloud nichts…

Natürlich kann man nicht erwarten, dass ein Auftraggeber über umfangreiche Erfahrung verfügt, welche IT-Lösung für die Anforderungen angemessen ist. Aber zumindest sollte er die eigenen Anforderungen kennen oder das zu erwartende Mengengerüst definieren können. Mitunter reicht es schon aus, wenn die notwendigen Hinweise gegeben werden, so dass man im Dialog eine belastbare Lösung planen und umsetzen kann.

Was die Projektkonzeption braucht

Mit der Aktion werden acht Millionen Personen angesprochen

Dann werden daraus zwar nicht zwangsläufig acht Millionen Nutzer, aber es gibt eine maximale Obergrenze. Und damit kann dann anhand von Erfahrungswerten die reale Nutzerzahl genauer eingegrenzt werden.

Die Kampagne wird von massiver Printwerbung flankiert

Das wird die Zahl der realen Nutzer mit großer Wahrscheinlichkeit erhöhen.

Am Wochenende sind TV- und Radiowerbung geschaltet und für Donnerstag ist Bannerwerbung bei einem großen Webportal gebucht

Das sind Zeiträume, in denen mit Besucherspitzen zu rechnen ist!

Aber was ist die Ursache, dass Kunden mit solchen wichtigen Informationen hinter dem Berg halten? Eine Vermutung ist – Achtung: Unterstellung! – dass Budgetverantwortliche beim Kunden primär auf die Initialkosten eines Projekts schauen. Dadurch werden sie dazu verleitet, bei den Projektkennzahlen eher zu untertreiben. Keiner möchte am Ende dafür verantwortlich sein, dass eine überdimensionierte Lösung in Auftrag gegeben wurde. Wer etwas Erfahrung hat, weiß, dass das eine Milchmädchenrechnung ist, denn nachträgliche Anpassungen an Applikationssoftware oder Infrastruktur generieren zusätzliche Kosten.
Kosten, die vom Kunden getragen werden müssen und so im Endeffekt das Projekt teurer machen.

Und mit Infrastructure as a Service und einem Content Delivery Network können sogar sehr schwer abzuschätzende Projekte oder Projekte mit stark schwankenden Nutzerzahlen umgesetzt werden, ohne dass Unsummen in eine ausreichend dimensionierte Infrastruktur gesteckt werden, die sich dann vielleicht zu 80% der Zeit „langweilt“.
Man bezahlt bei diesen Modellen – abgesehen von den Grundkosten – ja nur das, was auch wirklich an Ressourcen abgerufen wird.

Lerneffekt

Also: den Kunden in die Pflicht nehmen, ihn nicht mit ausweichenden Antworten oder Schulterzucken durchkommen lassen, auf belastbare Zahlen bestehen bzw. genauere Definitionen gemeinsam erarbeiten.
Eigentlich sollte die Aussicht auf ein gelungenes Projekt genug intrinsische Motivation für den Kunden sein um zielführend bei der Projektkonzeption mitzuarbeiten und „mit offenen Karten zu spielen“.
Aber manchmal braucht es da eine kleine Erinnerung oder ein „Anstubsen“.

(Bildquelle: Paragon Innovations, Inc.)

Merken