zendframework / zend-expressive

PSR-15 middleware in minutes!
BSD 3-Clause "New" or "Revised" License
711 stars 197 forks source link

Container Docs #109

Closed harikt closed 9 years ago

harikt commented 9 years ago

Hi,

I was trying to make use of Aura.Di v3 which satisfies container-interop .

Going through the docs of pimple https://github.com/zendframework/zend-expressive/blob/master/doc/book/container/pimple.md and zend-service manager I guess there is something wrong.

Eg : In Pimple.

$container['Zend\Expressive\Application'] = new Container\ApplicationFactory;
// ... more code
$app = $container->get('Zend\Expressive\Application');
$app->run();

See getting the service Zend\Expressive\Application it will actually gives you an object of Zend\Expressive\Container\ApplicationFactory , and inorder to get the object of Zend\Expressive\Application you need to pass the container itself.

$container['Zend\Expressive\Application'] = new Container\ApplicationFactory;
// ... more code
$app = $container->get('Zend\Expressive\Application');
$app = $app($container);
$app->run();

Interested to hear whether it was a typo or I am understanding it wrongly.

Thank you.

codeliner commented 9 years ago

@harikt just try it

$container['Zend\Expressive\Application'] = new Container\ApplicationFactory;
// ... more code
$app = $container->get('Zend\Expressive\Application');
$app->run();

You'll see that it works :) Pimple and ServiceManager use factories to create services. That means when invoking $container->get('Zend\Expressive\Application') the first time the container uses the registered factory and invokes the factory with itself as argument. This way the factory can get further dependencies from the container, create the requested service and return it back to the container. The container caches the service (at least by default) and returns it to the caller of get. That's it. No need for auto wiring but you need to register a factory for each service.

harikt commented 9 years ago

Thank you for explaining @codeliner .

Seems not a good idea when you are expecting the same object. But it works for people. So ok :-) .

Thanks.

codeliner commented 9 years ago

@harikt hehe, no one said that Pimple's interface is clean and understandable ;) But it is widely used. So IMO it is a good idea of zend-expressive to support it.

In zend-servicemanager you explicitly map a service name to a factory: https://github.com/zendframework/zend-expressive/blob/master/doc/book/container/zend-servicemanager.md

That's my preferred IoC container because of the advanced features like abstract factories and the possibility to configure it with a simple PHP array config.

weierophinney commented 9 years ago

@harikt It works because of these lines:

When retrieving the value by name, if the value is an object and implements __invoke (which is also true of Closure!), then the conditional in the first set of lines fails. This causes the next set of lines to trigger, which invokes the factory. This means that you can define invokable classes as factories, set pimple services to them, and they will be invoked in order to create the service. (This also means that you cannot directly assign invokable objects — such as middleware! — to Pimple unless they can act as factories!)

harikt commented 9 years ago

Thanks.

I have a few config stuffs that help to make use of Aura.Di. Don't know when I can send a PR / get time . In case if someone likes to write. Feel free to take this.

<?php
// PROJECT_PATH/config/Common.php
namespace Application\_Config;

use Aura\Di\Container;
use Aura\Di\ContainerConfig;

class Common extends ContainerConfig
{
    public function define(Container $di)
    {
        $di->params['Aura\Router\RouteCollection'] = array(
            'route_factory' => $di->lazyNew('Aura\Router\RouteFactory'),
        );
        $di->params['Aura\Router\Router'] = array(
            'routes' => $di->lazyNew('Aura\Router\RouteCollection'),
            'generator' => $di->lazyNew('Aura\Router\Generator'),
        );
        $di->params['Zend\Expressive\Router\Aura']['router'] = $di->lazyNew('Aura\Router\Router');
        $di->set('Zend\Expressive\Router\RouterInterface', $di->lazyNew('Zend\Expressive\Router\Aura'));
        $di->set('Zend\Expressive\Container\ApplicationFactory', $di->lazyNew('Zend\Expressive\Container\ApplicationFactory'));
        $di->set('Zend\Expressive\Application', $di->lazyGetCall('Zend\Expressive\Container\ApplicationFactory', '__invoke', $di));

        // Templating
        // In most cases, you can instantiate the template renderer you want to use
        // without using a factory:
        $di->set('Zend\Expressive\Template\TemplateInterface', $di->lazyNew('Zend\Expressive\Template\Plates'));

        // These next two can be added in any environment; they won't be used unless
        // you add the WhoopsErrorHandler as the FinalHandler implementation:
        $di->set('Zend\Expressive\Container\WhoopsFactory', $di->lazyNew('Zend\Expressive\Container\WhoopsFactory'));
        $di->set('Zend\Expressive\Whoops', $di->lazyGetCall('Zend\Expressive\Container\WhoopsFactory', '__invoke', $di));
        $di->set('Zend\Expressive\Container\WhoopsPageHandlerFactory', $di->lazyNew('Zend\Expressive\Container\WhoopsPageHandlerFactory'));
        $di->set('Zend\Expressive\WhoopsPageHandler', $di->lazyGetCall('Zend\Expressive\Container\WhoopsPageHandlerFactory', '__invoke', $di));

        // Error Handling

        // If in development:
        $di->set('Zend\Expressive\Container\WhoopsErrorHandlerFactory', $di->lazyNew('Zend\Expressive\Container\WhoopsErrorHandlerFactory'));
        $di->set('Zend\Expressive\FinalHandler', $di->lazyGetCall('Zend\Expressive\Container\WhoopsErrorHandlerFactory', '__invoke', $di));

        // If in production:
        // $di->set('Zend\Expressive\FinalHandler', $di->lazyGetCall('Zend\Expressive\Container\TemplatedErrorHandlerFactory', '__invoke', $di));
    }

    public function modify(Container $di)
    {
        $router = $di->get('Zend\Expressive\Router\RouterInterface');
        $router->addRoute(new \Zend\Expressive\Router\Route('/hello', function ($request, $response, $next) {
            $response->write('Trying out');
            return $response;
        }, \Zend\Expressive\Router\Route::HTTP_METHOD_ANY, 'hello'));
    }
}

and

// index.php
<?php
use Aura\Di\ContainerBuilder;

chdir(dirname(__DIR__));
require 'vendor/autoload.php';

$container_builder = new ContainerBuilder();

// use the builder to create and configure a container
// using an array of ContainerConfig classes
$di = $container_builder->newConfiguredInstance([
    'Application\_Config\Common',
]);

$app = $di->get('Zend\Expressive\Application');

$app->get('/', function ($request, $response, $next) {
    $response->write('Hello, world!');
    return $response;
});

$app->run();
weierophinney commented 9 years ago

@harikt Thanks for the write-up above; I'm going to re-open and mark as "easy fix" so that somebody can take that information and write docs.

One question: in the above, you have calls like this:

$di->set('Zend\Expressive\FinalHandler', $di->lazyNew('Zend\Expressive\Container\WhoopsErrorHandlerFactory'));

Does that actually work? When I was researching adding Aura.Di docs, my understanding is that the above would return an instance of the WhoopsErrorHandlerFactory, not invoke the factory; based on the fact that you defined Zend\Expressive\Application to invoke the composed ApplicationFactory service, I'm thinking that approach would need to be taken for all such factories?

harikt commented 9 years ago

Good catch @weierophinney . You are absolutely correct on the Factories. I missed to check those.

Thanks.

harikt commented 9 years ago

I have created a skelton repo https://github.com/harikt/expressive-aura-skelton if someone love to test quickly :) . I will try to find sometime myself to write.

weierophinney commented 9 years ago

Fixed with #112