"New" is Not Your Enemy!

Stephan Hochdörfer // October 13, 2016

Hi, I am Stephan Hochdörfer

@shochdoerfer

I work for bitExpert AG

Technical consultant

Knowledge transfer

unKonf host & organiser

I speak at conferences (a lot)

Co-Org. of PHP UG FFM

Co-Org. of PHP UG MRN

10 years ago we did this...

<?php

class ProductController extends Controller
{
    public function index()
    {
        $service = new ProductService();

        $this->render(
            'index',
            array(
                'products' => $service->readAllProducts()
            )
        );
    }
}

Actually it looked more like...

<?php

class ProductController extends Controller
{
    public function index()
    {
        global $CONFIG;

        $db = new Database($CONFIG['dsn']);
        $db->connect();
        $products = $db->query('SELECT * 
from products;');

        $this->render(
            'index',
            array(
                'products' => $products
            )
        );
    }
}

Tightly coupled code

No component re-use

No isolation, not testable!

...and then came this guy

And today?

<?php

class ProductController extends Controller
{
    public function __construct(
        ProductService $productService,
        ProductCommentsService $productCommentsService,
        ProductRatingService $productRatingService,
        UserService $userService,
        NotificationService $notificationService,
        DiscountService $discountService,
        EmailService $emailService,
        PaymentFactory $paymentFactory,
        Order $order,
        Comment $comment,
        Rating $rating,
        PriceFormatterHelper $formatter
    ){
        // [...]
    }
}

Constructor Injection FTW!

<?php

class ProductController extends Controller
{
    public function __construct(
        ProductService $productService,
        UserService $userService
    ){
        // [...]
    }
}

Property Injection is evil!




« Repeat after me: field injection is a
dumb idea... *sigh* » - @olivergierke

Property Injection is evil!

<?php

class ProductController extends Controller
{
    /**
     * @Inject
     * @var ProductService
     */
    private $productService;
    /**
     * @Inject
     * @var UserService
     */
    private $userService;

    // [...]
}

But public properties...

<?php

class ProductController extends Controller
{
    /**
     * @Inject
     * @var ProductService
     */
    public $productService;
    /**
     * @Inject
     * @var UserService
     */
    public $userService;

    // [...]
}

Avoid Setter Injection...

<?php

class ProductController extends Controller
{
    public function setProductService(ProductService $service)
    {
        // [...]
    }

    public function setUserService(UserService $service)
    {
        // [...]
    }
}

...but optional dependencies?




The concept of
optional dependencies is a lie!

Also avoid Interface Injection

<?php

interface ProductServiceAware
{
    public function setProductService(ProductService $service);
}
<?php

class ProductController extends Controller
    implements ProductServiceAware
{
    public function setProductService(ProductService $service)
    {
        // [...]
    }
}

ContainerAware is a lie, too!

<?php

interface ContainerAware
{
    public function setContainer(ContainerInterface $container);
}
<?php

class ProductController extends Controller
    implements ContainerAware
{
    protected $productService;

    public function setContainer(ContainerInterface $container)
    {
        $this->productService = 
$container->get('productService');
    }
}

But I need lazy-loading...

To new or not to new...

<?php

class OrderController extends Controller
{
    public function __construct(
        BasketService $basketService,
        OrderService $orderService,
        Order $order
    ){
        $products = $basketService->getProducts();
        foreach($products as $product) {
            $order->addProduct($product);
        }

        // [...]

        $orderService->order($order);
    }
}

To new or not to new...

<?php

class OrderController extends Controller
{
    public function __construct(
        BasketService $basketService,
        OrderService $orderService
    ){
        $order = new Order();

        $products = $basketService->getProducts();
        foreach($products as $product) {
            $order->addProduct($product);
        }

        // [...]

        $orderService->order($order);
    }
}

To new or not to new...

<?php

class FileTemplateStore
{
    public function parseTemplate($file, array $variables = [])
    {
        $content = file_get_contents($file);

        // replace placeholders with content from $variables array...
        // [...]

        return SimpleMessage::parseFromString($content);
    }
}

What is a dependency?

What is a dependency?




Simply ask yourself: Do I really need to replace this dependency with something different?

What is a dependency?




Spoiler alert: Sometimes dependencies are not what they seem to be.

Is a logger a dependency?

Is a logger a dependency?

<?php

class OrderController extends Controller
{
    protected $logger;

    public function __construct()
    {
        $this->logger = new Logger('/tmp/application.log');
        // [...]
    }
}
{
    "require": {
        "psr/log": "^1.0",
        "monolog/monolog": "^1.21"
    }
}

What is this PSR-3 thing?




« The main goal is to allow libraries to receive
a Psr\Log\LoggerInterface object and write logs
to it in a simple and universal way. » - PHP-FIG

No vendor lock-in for loggers

How to solve this problem?

<?php

namespace Psr\Log;

/**
 * Describes a logger-aware instance.
 */
interface LoggerAwareInterface
{
    /**
     * Sets a logger instance on the object.
     *
     * @param LoggerInterface $logger
     * @return null
     */
    public function setLogger(LoggerInterface $logger);
}

How to solve this problem?

<?php

$controller = new OrderController();
$controller->processOrder();
PHP Fatal error: Uncaught Error: Call to a member function debug()
on null in /home/shochdoerfer/Workspace/demo/src/Demo/Http/Controller/
OrderController.php:29

Fix: Rely on NullLogger

<?php

class OrderController extends Controller
    implements \Psr\Log\LoggerAwareInterface
{
    protected $logger;

    public function __construct()
    {
        $this->logger = new \Psr\Log\NullLogger();
        // [...]
    }
}

Fix: Constructor injection

<?php

class OrderController extends Controller
{
    protected $logger;

    public function __construct(\Psr\Log\LoggerInterface $logger)
    {
        $this->logger = $logger;
        // [...]
    }
}

Fix: Constructor injection

<?php

class FileTemplateStore
{
    public function parseTemplate($file, array $variables = [])
    {
        $content = file_get_contents($file);

        // replace placeholders with content from $variables array...
        // [...]

        return SimpleMessage::parseFromString($content, 
$this->logger);
    }
}

Logger is infrastructure




The Simple Logging Facade
for PSR-3 loggers

bitexpert/slf4psrlog

$> composer.phar require bitexpert/slf4psrlog
<?php

class FileTemplateStore
{
    protected $logger;

    public function __construct()
    {
        $this->logger = \bitExpert\Slf4PsrLog\LoggerFactory::getLogger(
            __CLASS__);
    }

    public function parseTemplate($file, array $variables = [])
    {
        $this->logger->debug('Parsing template...');
        // [...]
    }
}

The glue code...

<?php

\bitExpert\Slf4PsrLog\LoggerFactory::registerFactoryCallback(
    function($channel) {
        if (!\Monolog\Registry::hasLogger($channel)) 
{
            \Monolog\Registry::addLogger(
                new \Monolog\Logger($channel)
            );
        }

        return \Monolog\Registry::getInstance($channel);
    }
);

What is a dependency?




Remember: Sometimes dependencies are not what they seem to be.

Is a factory a dependency?

Injecting factory gone wrong

<?php

class OrderController extends Controller
{
    public function __construct(
        PaymentProcessorFactory $factory
    ){
        $processor = $factory->getInstance();

        // [...]
    }
}

Explicit dependencies

<?php

class OrderController extends Controller
{
    public function __construct(
        PaymentProcessor $paymentProcessor
    ){
        // [...]
    }
}

bitexpert/disco




« [...] a container-interop compatible, annotation-based dependency injection container. » - Disco

container-interop?




« container-interop tries to identify and standardize
features in container objects [...] to achieve
interoperability. » - container-interop

Annotations?

Why another DI container?





« [...] to make DI in PHP great again. » - Me

Problems Disco solves



  • Configuration as code
    (to improve refactoring)
  • Write clean configuration
    without much boilerplate code
  • Write readable
    configuration code

Configuration example

<?php

use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\Annotations\Configuration;

/** @Configuration */
class Config
{
    /** @Bean */
    public function productService() 
: ProductService
    {
        $db = new Database('mysql://user:secret@localhost/mydb');
        return new ProductService($db);
    }
}

Configuration example

<?php

use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\Annotations\Configuration;

/** @Configuration */
class Config
{
    /** @Bean */
    protected function database() : 
Database
    {
        return new Database('mysql://user:secret@localhost/mydb');
    }

    /** @Bean */
    public function productService() 
: ProductService
    {
        return new ProductService($this->database());
    }
}

Configuration example

<?php

use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\Annotations\Configuration;

/** @Configuration */
class Config
{
    /** @Bean({"singleton"=false, "lazy"=false, 
"scope"="request"}) */
    public function productService() 
: ProductService
    {
        return new ProductService();
    }
}

Configuration example

<?php

use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\Annotations\Configuration;

/** @Configuration */
class BeanConfiguration
{
    /** @Bean({"alias"="\My\Custom\Namespace\ProductService"}) */
    public function productService() 
: ProductService
    {
        return new ProductService();
    }
}

Disco in action

<?php

$beanFactory = new AnnotationBeanFactory(Config::class);
BeanFactoryRegistry::register($beanFactory);

$productService = $this->beanFactory->get('productService');
$productService = $this->beanFactory->get(
    '\My\Custom\Namespace\ProductService'
);

Using primitives

<?php

use bitExpert\Disco\Annotations\Bean;
use bitExpert\Disco\Annotations\Configuration;

/** @Configuration */
class BeanConfiguration
{
    /**
     * @Bean
     */
    public function disco() : string
    {
        return 'Disco rocks!';
    }
}

Structure the configuration

<?php
use bitExpert\Disco\Annotations\Bean;

trait ProductModule
{
    /** @Bean */
    public function productService() 
: ProductService
    {
        $db = new Database('mysql://user:secret@localhost/mydb');
        return new ProductService($db);
    }
}
<?php
use bitExpert\Disco\Annotations\Configuration;

/** @Configuration */
class BeanConfiguration
{
    use ProductModule;
}

...and some more stuff



  • Custom PostProcessors
  • Parameterized Beans
  • Sessionaware Beans
  • ...

Thank you! Questions?


  • Stephan Hochdörfer
  • Head of Technology, bitExpert AG
    (Mannheim, Germany)
  • S.Hochdoerfer@bitExpert.de
  • @shochdoerfer

  • #PHP, #DevOps, #Automation
  • #phpugffm, #phpugmrn, #unKonf






Do not forget to rate the talk:
https://joind.in/talk/706a6