Chapter 5: Disco with Design Patterns: A Fresh Look at Dependency Injection

by Reza Lavaryan

<?php

class Test {

    protected $dbh;

    public function __construct(PDO $dbh)
    {
        $this->dbh = $dbh;
    }

}

$dbh  = new PDO('mysql:host=localhost;dbname=test', 'username', 'password');
$test = new Test($dbh) 

Installation

composer require bitexpert/disco
php -S localhost:8000 -t web

Getting Started

container_interop's Interfaces

<?php
/**
 * @Configuration
 */
 class Services {
    // ...
 }
<?php
/**
 * @Configuration
 */
class Configuration {

    /**
     * @Bean
     * @return SampleService
     */
    public function getSampleService()
    {
        // Instantiation
        $service  = new SampleService();

        // Configuration
        $service->setParameter('key', 'value');
        return $service; 
    }
}
<?php

// ...

/**
 * @Bean({"singleton"=true, "lazy"=true})
 * @return AcmeSampleService
 */
 public function getSampleService()
 {
     return new SampleService();
 }

// ...
<?php

// ...

use itExpertDiscoAnnotationBeanFactory;
use itExpertDiscoBeanFactoryRegistry;

// ...

// Setting up the container
$container = new AnnotationBeanFactory(Services::class, $config);
BeanFactoryRegistry::register($container);

How to Get a Service from the Container

// ...

$sampleService = $container->get('sampleService');
$sampleService->callSomeMethod();

Service Scope

<?php

class sample {

    public $counter = 0;

    public function add()
    {
        $this->counter++;
        return $this;
    } 
}
// ...
/**
 * @Bean({"scope"="session"})
 * @return Sample
 */
public function getSample()
{
    return new Sample();
}
// ...
// ...
$sample = $container->get('getSample');
$sample->add()
       ->add()
       ->add();

echo $sample->counter; // output: 3
// ...

Container Parameters

// ...
$parameters = [

    // Database configuration
    'database' => [        
        'dbms' => 'mysql',
        'host' => 'localhost',
        'user' => 'username',
        'pass' => 'password',
    ],
];

// Setting up the container
$container = new AnnotationBeanFactory(Services::class, $parameters);
BeanFactoryRegistry::register($container);
<?php
// ...

/**
 * @Bean
 * @Parameters({
 *    @parameter({"name"= "database"})
 * })
 *
*/
public function sampleService($database = null)
{
    // ...
}

Disco in Action

  • HTTP Kernel.The heart of our framework. Provides the request / response basics.
  • Http Foundation.A nice object-oriented layer around PHP's HTTP super globals.
  • Router. According to the official website: maps an HTTP request to a set of configuration variables - more on this below.
  • Event Dispatcher. This library provides a way to hook into different phases of a request / response lifecycle, using listeners and subscribers.
composer require symfony/http-foundation symfony/routing symfony/http-kernel symfony/event-dispatcher
// ...
 "autoload": {
        "psr-4": {
            "": "src/"
        }
    }
// ...

The Kernel

<?php
// src/Framework/Kernel.php

namespace Framework;

use SymfonyComponentHttpKernelHttpKernel;
use SymfonyComponentHttpKernelHttpKernelInterface;

class Kernel extends HttpKernel implements HttpKernelInterface {
}

Routing

<?php

// ...

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;

$routes = new RouteCollection();

$routes->add('route_alias', new Route('path/to/match', ['_controller' => function(){
    // Do something here...
}]
));
<?php
// src/Framework/RouteBuilder.php

namespace Framework;

use SymfonyComponentRoutingRouteCollection;
use SymfonyComponentRoutingRoute;

class RouteBuilder {

    protected $routes;

    public function __construct(RouteCollection $routes)
    {
        $this->routes = $routes;
    }

    public function get($name, $path, $controller)
    {
        return $this->add($name, $path, $controller, 'GET');
    }

    public function post($name, $path, $controller)
    {
        return $this->add($name, $path, $controller, 'POST');
    }

    public function put($name, $path, $controller)
    {
        return $this->add($name, $path, $controller, 'PUT');
    }

    public function delete($name, $path, $controller)
    {
        return $this->add($name, $path, $controller, 'DELETE');
    }

    protected function add($name, $path, $controller, $method)
    {
        $this->routes->add($name, new Route($path, ['_controller' => $controller], ['_method' => $method]));

        return $this;
    }

}
<?php
// src/routes.php

use SymfonyComponentRoutingRouteCollection;
use FrameworkRouteBuilder;

$routeBuilder = new RouteBuilder(new RouteCollection());

$routeBuilder

->get('home', '/', function() {
            return new Response('It Works!');
})

->get('welcome', '/welcome', function() {
            return new Response('Welcome!');
});

The Front Controller

<?php
//web/index.php

require_once __DIR__ . '/../vendor/autoload.php';

use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationRequestStack;
use SymfonyComponentHttpKernelEventListenerRouterListener;
use SymfonyComponentHttpKernelControllerControllerResolver;

// Create a request object from PHP's global variables
$request = Request::createFromGlobals();

$routes = include __DIR__.'/../src/routes.php';
$UrlMatcher = new RoutingMatcherUrlMatcher($routes, new RoutingRequestContext());

// Event dispatcher & subscribers
$dispatcher = new EventDispatcher();
// Add a subscriber for matching the correct route. We pass UrlMatcher to this class
$dispatcher->addSubscriber(new RouterListener($UrlMatcher, new RequestStack()));


$kernel = new FrameworkKernel($dispatcher, new ControllerResolver());
$response  = $kernel->handle($request);

// Sending the response
$response->send();
<?php
// ...
$request = Request::createFromGlobals();
// ...
<?php
// ...
$routes = include __DIR__.'/../src/routes.php';
$UrlMatcher = new RoutingMatcherUrlMatcher($routes, new RoutingRequestContext());
// ...
<?php
// ...
// Event dispatcher & subscribers
$dispatcher = new EventDispatcher();
// Add a subscriber for matching the correct route. We pass UrlMatcher to this class
$dispatcher->addSubscriber(new RouterListener($UrlMatcher, new RequestStack()));
// ...
<?php
// ...

$kernel    = new FrameworkKernel($dispatcher, new ControllerResolver());
$response  = $kernel->handle($request);

// Sending the response
$response->send();
// ...

Disco Time

composer require bitexpert/Disco;
<?php
// src/Framework/Services.php

use bitExpertDiscoAnnotationsBean;
use bitExpertDiscoAnnotationsConfiguration;
use bitExpertDiscoAnnotationsParameters;
use bitExpertDiscoAnnotationsParameter;

/**
 * @Configuration
 */
class Services {

    /**
     * @Bean
     * @return SymfonyComponentRoutingRequestContext 
     */
    public function context()
    {
        return new SymfonyComponentRoutingRequestContext();
    }

    /**
     * @Bean
     *
     * @return SymfonyComponentRoutingMatcherUrlMatcher
     */
    public function matcher()
    {
        return new SymfonyComponentRoutingMatcherUrlMatcher($this->routeCollection(), $this->context());
    }

    /**
     * @Bean
     * @return SymfonyComponentHttpFoundationRequestStack
     */
    public function requestStack()
    {
        return new SymfonyComponentHttpFoundationRequestStack();
    }

    /**
     * @Bean
     * @return SymfonyComponentRoutingRouteCollection
     */
    public function routeCollection()
    {
        return new SymfonyComponentRoutingRouteCollection();
    }

    /**
     * @Bean
     * @return FrameworkRouteBuilder
     */
    public function routeBuilder()
    {
        return new FrameworkRouteBuilder($this->routeCollection());
    }

    /**
     * @Bean
     * @return SymfonyComponentHttpKernelControllerControllerResolver
     */
    public function resolver()
    {
        return new SymfonyComponentHttpKernelControllerControllerResolver();
    }


    /**
     * @Bean
     * @return SymfonyComponentHttpKernelEventListenerRouterListener
     */
    protected function listenerRouter()
    {
        return new SymfonyComponentHttpKernelEventListenerRouterListener(
            $this->matcher(),
            $this->requestStack()
        );
    }

    /**
     * @Bean
     * @return SymfonyComponentEventDispatcherEventDispatcher
     */
    public function dispatcher()
    {
        $dispatcher = new SymfonyComponentEventDispatcherEventDispatcher();

        $dispatcher->addSubscriber($this->listenerRouter());

        return $dispatcher;
    }

    /**
     * @Bean
     * @return Kernel
     */
    public function framework()
    {
        return new Kernel($this->dispatcher(), $this->resolver());
    }

}
// ...
 "autoload": {
        "psr-4": {
            "": "src/"
        },
        "files": [
            "src/Services.php"
        ]
    }
// ...
<?php

//web/index.php

require_once __DIR__ . '/../vendor/autoload.php';

use SymfonyComponentHttpFoundationRequest;

$request   = Request::createFromGlobals();

$container = new itExpertDiscoAnnotationBeanFactory(Services::class);
itExpertDiscoBeanFactoryRegistry::register($container);

$routes   = include __DIR__.'/../src/routes.php';

$kernel   = $container->get('framework')
$response = $kernel->handle($request);
$response->send();

But How About the Configuration?

// Config/dev.php

return [
    'debug' => true;
];
// Config/prod.php

return [
    'debug' => false;
];
ENV=dev
composer require vlucas/phpdotenv
ENV=dev
<?php
//web/index.php

// ...

// Loading environment variables stored .env into $_ENV
$dotenv = new DotenvDotenv(__DIR__ . '/../Config');
$dotenv->load();

// Load the proper configuration file based on the environment
$parameters = require __DIR__ . '/../config/' . getenv('ENV') . '.php';

$container = new itExpertDiscoAnnotationBeanFactory(Services::class, $parameters);       itExpertDiscoBeanFactoryRegistry::register($container);

// ...

Creating a Container Builder

<?php
// src/Factory.php

namespace Framework;

class Factory {

    /**
     * Create an instance of Disco container
     *
     * @param  array $parameters
     * @return itExpertDiscoAnnotationBeanFactory
     */
    public static function buildContainer($parameters = [])
    {
        $container = new itExpertDiscoAnnotationBeanFactory(Services::class, $parameters);
        itExpertDiscoBeanFactoryRegistry::register($container);

        return $container;
    }

}
<?php
//web/index.php

require_once __DIR__ . '/../vendor/autoload.php';

use SymfonyComponentHttpFoundationRequest;

// Getting the environment
$dotenv = new DotenvDotenv(__DIR__ . '/../config');
$dotenv->load();

// Load the proper configuration file based on the environment
$parameters = require __DIR__ . '/../config/' . getenv('ENV') . '.php';

$request   = Request::createFromGlobals();
$container = FrameworkFactory::buildContainer($parameters);
$routes    = include __DIR__.'/../src/routes.php';

$kernel   = $container->get('framework')
$response = $kernel->handle($request);
$response->send();

Application Class

<?php

namespace Framework;

use SymfonyComponentHttpKernelHttpKernelInterface;
use SymfonyComponentHttpFoundationRequest;

class Application {

    protected $kernel;

    public function __construct(HttpKernelInterface $kernel)
    {
        $this->kernel = $kernel;
    }

    public function run()
    {
        $request = Request::createFromGlobals();

        $response = $this->kernel->handle($request);
        $response->send();
    }
}
<?php

// src/Framework/Services.php

// ...

    /**
     * @Bean
     * @return FrameworkApplication
     */
    public function application()
    {
        return new FrameworkApplication($this->kernel());
    }

// ...
<?php

require_once __DIR__ . '/../vendor/autoload.php';

// Getting the environment
$dotenv = new DotenvDotenv(__DIR__ . '/../config');
$dotenv->load();

// Load the proper configuration file based on the environment
$parameters = require __DIR__ . '/../config/' . getenv('ENV') . '.php';

// Build a Disco container using the Factory class
$container = FrameworkFactory::buildContainer($parameters);

// Including the routes
require __DIR__ . '/../src/routes.php';

// Running the application to handle the response
$app = $container->get('application')
          ->run();

Creating a Response Listener

<?php

// ...

$routeBuilder

->get('home', '/', function() {
            return new Response('It Works!');
});

->get('welcome', '/welcome', function() {
            return new Response('Welcome!');
});

// ...
<?php
// src/Framework/StringResponseListener
namespace Framework;

use SymfonyComponentEventDispatcherEventSubscriberInterface;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentHttpKernelEventGetResponseForControllerResultEvent;
use SymfonyComponentHttpKernelKernelEvents;

class StringResponseListener implements EventSubscriberInterface
{
    public function onView(GetResponseForControllerResultEvent $event)
    {
        $response = $event->getControllerResult();

        if (is_string($response)) {
            $event->setResponse(new Response($response));
        }
    }

    public static function getSubscribedEvents()
    {
        return array(KernelEvents::VIEW => 'onView');
    }
}
<?php

// ...

/**
 * @Bean
 * @return FrameworkStringResponseListener
 */
protected function ListenerStringResponse()
{
    return new FrameworkStringResponseListener();
}

// ...
<?php

// ...

/**
 * @Bean
 * @return SymfonyComponentEventDispatcherEventDispatcher
 */
public function dispatcher()
{
    $dispatcher = new SymfonyComponentEventDispatcherEventDispatcher();

    $dispatcher->addSubscriber($this->listenerRouter());
    $dispatcher->addSubscriber($this->ListenerStringResponse());

    return $dispatcher;
}

// ...
<?php

// ...

$routeBuilder

->get('home', '/', function() {
            return 'It Works!';
})

->get('welcome', '/welcome', function() {
            return 'Welcome!';
});

// ...

Conclusion

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.25.34