app-zap / PHPFramework

A lightweight PHP framework as a composition of different good features and concepts of other frameworks intended for easy use and fast development
Other
4 stars 1 forks source link

Dependency Injection and AOP #38

Open smichaelsen opened 9 years ago

smichaelsen commented 9 years ago

We really don't want to sacrifice the new keyword. That's why we won't simply include a dependency injection container like Pimple. But maybe AOP can help us building a transparent dependency injection. Go! AOP - PHP looks promising.

smichaelsen commented 9 years ago

I have heard warnings and concerns by smart developers about that: https://twitter.com/s_michaelsen/status/541715828123926531 Keep that in mind.

smichaelsen commented 9 years ago

Also read and consider this: http://martinfowler.com/articles/injection.html (via @cedricziel)

helhum commented 9 years ago

AOP and DI are completely different topics. AOP will not help you to achieve DI.

Regarding GO! it is nice if you need AOP. It rewrites your class files that have Aspects defined and puts the rewritten variants into a file cache and transparently requires them during class loading.

Regarding DI:

Dependency Injection / Dependency Inversion are "just" a pattern which has the rule that instead of hard wiring Dependencies in your object, you should design the code in a way that it is possible to pass the dependency to your object.

E.g. instead of instantiating the dependency Bar in your class Foo, you should rather invert the dependency that lets you pass the dependency Bar to your Foo object. This can be made possible through the constructor or a setter or just pass it along as argument to a method:

<?php

class Bar {

}

interface BarInterface {

}

/**
 * Dependency inversion
 */
class Foo {

    /**
     * @var Bar
     */
    protected $bar;

    /**
     * Constructor injection
     *
     * @param Bar $bar
     */
    public function __construct(Bar $bar) {
        $this->bar = $bar;
    }

    /**
     * Setter injection
     *
     * @param Bar $bar
     */
    public function injectBar(Bar $bar) {
        $this->bar = $bar;
    }

    /**
     * Operate on or with Bar
     *
     * @param Bar $bar
     * @param $action
     */
    public function doSomethingWithBar(Bar $bar, $action) {
        // Do something with $bar and return the modified object or another object
    }
}

/**
 * Dependency inversion and
 * rather tying the class to a contract not to a concrete implementation of the contract
 */
class BetterFoo {

    /**
     * @var BarInterface
     */
    protected $bar;

    /**
     * Constructor injection
     *
     * @param BarInterface $bar
     */
    public function __construct(BarInterface $bar) {
        $this->bar = $bar;
    }

    /**
     * Setter injection
     *
     * @param BarInterface $bar
     */
    public function injectBar(BarInterface $bar) {
        $this->bar = $bar;
    }

    /**
     * Operate on or with BarInterface
     *
     * @param BarInterface $bar
     * @param $action
     */
    public function doSomethingWithBar(BarInterface $bar, $action) {
        // Do something with $bar and return the modified object or another object/ state
    }
}

You are talking (as I understood it) about Auto Wiring which means automatically resolve the dependencies (concrete implementations of your dependencies) of an object that uses the DI principle and pass them along during instantiation. For that you must have a DI container that is aware of what dependencies an object needs and which implementations should be passed during creation.

In Flow and Extbase the DI container is tied to the object manager, so you'll have to create objects with it so that it can resolve and inject the dependencies.

You also specifically wish to use the new keyword and still get an instance of that class with already resolved and injected dependencies. A few remarks on that:

  1. You can make this work to a certain extend in PHP, but due to language limitations, you must generate PHP code and make sure your class loader loads the generated class instead of your original class file. The generated class file will define a class with the same name and added code (in the constructor) which fetches your DI container, which again fetches the dependencies and passes them e.g. to the setters. This is more or less what Flow does.
  2. Given you implemented the above, in the places (classes) you write $foo = new Foo(), you'll never be able to achieve to have a different implementation of Foo assigned to $foo, unless you want to also re-write such during class generation as well.
  3. Given you accept these limitations or even implemented a solution for that, you will have re-built TYPO3 Flow in your own Framework, which isn't just a lightweight framework any more at this point.

I strongly suggest that you either give Flow as a full stack framework another try, or if you want to select specific features you need, have a deeper look into Symfony which has neatly separated small components which are easy to plug in and use or, if you still like to continue with your own framework, think about an object architecture following the SOLID principle. In any case I think it is better to live with the limitations in the language (and/or the framework) and decouple your code as good as possible to make it flexible and reusable, than trying to extend the language's capabilities by implementing some heavy lifting (magic) yourself.

I'll finish with two nice resources for further reading in case you don't know them yet:

A nice summary of modern PHP development https://www.airpair.com/php/posts/best-practices-for-modern-php-development

A nice post (pro Symfony) and the blog as a general recommendation for inspiration http://www.whitewashing.de/2014/10/26/symfony_all_the_things_web.html