Git Product home page Git Product logo

elie29 / zend-di-config Goto Github PK

View Code? Open in Web Editor NEW
19.0 4.0 4.0 1.87 MB

PSR-11 PHP-DI container configurator for Laminas, Mezzio, ZF, Expressive applications or any framework that needs a PSR-11 container

License: MIT License

PHP 100.00%
php-di zend-framework zend-expressive psr-11 zend-servicemanager container php-di-container zend-expressive-skeleton expressive-composer-installer laminas

zend-di-config's Introduction

zend-phpdi-config

Build Status Coverage Status

Introduction

zend-phpdi-config acts as a bridge to configure a PSR-11 compatible PHP-DI container using service manager configuration. It can be used with Laminas and Mezzio starting from v6.0.0

This library uses autowiring technique, cache compilation and cache definitions as defined in PHP-DI.

Configuration

Service Manager Configuration

To get a configured PSR-11 PHP-DI container, do the following:

<?php

use Elie\PHPDI\Config\Config;
use Elie\PHPDI\Config\ContainerFactory;

$factory = new ContainerFactory();

$container = $factory(
    new Config([
        'dependencies' => [
            'services'   => [],
            'invokables' => [],
            'autowires'  => [], // A new key added to support PHP-DI autowire technique
            'factories'  => [],
            'aliases'    => [],
            'delegators' => [],
        ],
        // ... other configuration

        // Enable compilation
        Config::DI_CACHE_PATH => __DIR__, // Folder path

        // Write proxies to file : cf. https://php-di.org/doc/lazy-injection.html
        Config::DI_PROXY_PATH => __DIR__, // Folder path

        // Disable autowire (enabled by default)
        Config::USE_AUTOWIRE => false

        // Enable cache
        Config::ENABLE_CACHE_DEFINITION => false, // boolean, true if APCu is activated
    ])
);

The dependencies sub associative array can contain the following keys:

  • services: an associative array that maps a key to a specific service instance or service name.
  • invokables: an associative array that map a key to a constructor-less service; i.e., for services that do not require arguments to the constructor. The key and service name usually are the same; if they are not, the key is treated as an alias. It could also be an array of services.
  • autowires: an array of service with or without a constructor; PHP-DI offers an autowire technique that will scan the code and see what are the parameters needed in the constructors. Any aliases needed should be created in the aliases configuration.
  • factories: an associative array that maps a service name to a factory class name, or any callable. Factory classes must be instantiable without arguments, and callable once instantiated (i.e., implement the __invoke() method).
  • aliases: an associative array that maps an alias to a service name (or another alias).
  • delegators: an associative array that maps service names to lists of delegator factory keys, see the Expressive delegators documentation for more details.

N.B.: The whole configuration -- unless dependencies -- is merged in a config key within the $container:

$config = $container->get('config');

CLI command to add a new autowire entry

Configuration image

The cli command add-autowires-entry creates the configuration file if it doesn't exist otherwise it adds the entry to the autowires key.

Example of adding ConsoleHelper to a config.php:

./vendor/bin/add-autowires-entry config.php "Laminas\\Stdlib\\ConsoleHelper"
[DONE] Changes written to config.php

Using with Expressive

Replace contents of config/container.php with the following:

<?php

declare(strict_types = 1);

use Elie\PHPDI\Config\Config;
use Elie\PHPDI\Config\ContainerFactory;

// Protect variables from global scope
return call_user_func(function () {

    $config = require __DIR__ . '/config.php';

    $factory = new ContainerFactory();

    // Container
    return $factory(new Config($config));
});

Example of a ConfigProvider class

<?php

class ConfigProvider
{

    /**
     * Returns the configuration array
     */
    public function __invoke(): array
    {
        return [
            'dependencies' => $this->getDependencies()
        ];
    }

    /**
     * Returns the container dependencies
     */
    public function getDependencies(): array
    {
        return [
            'autowires' => [
                UserManager::class
            ]
        ];
    }
}

Where UserManager depends on Mailer as follow:

class UserManager
{
    private $mailer;

    public function __construct(Mailer $mailer)
    {
        $this->mailer = $mailer;
    }

    public function register($email, $password)
    {
        $this->mailer->mail($email, 'Hello and welcome!');
    }
}

class Mailer
{
    public function mail($recipient, $content)
    {
    }
}

Switching back to another container

To switch back to another container is very easy:

  1. Create your factories with __invoke function
  2. Replace autowires key in ConfigProvider by factories key, then for each class name attach its correspondent factory.

PSR 11 and Interop\Container\ContainerInterface

V4.x supports as well Interop\Container\ContainerInterface

Migration guides

zend-di-config's People

Contributors

elie29 avatar holtkamp avatar nednisw avatar yethee avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

zend-di-config's Issues

Psalm (static analysis tool) integration instead of phpstan

Implementing psalm is quite easy.

Required

  • Create a psalm.xml in the project root
  • Adapt the contents from this psalm.xml.dist
  • Run $ composer require --dev vimeo/psalm
  • Run $ vendor/bin/psalm --set-baseline=psalm-baseline.xml
  • Add a composer script static-analysis with the command psalm --shepherd --stats
  • Add a new line to script: in .travis.yml: - if [[ $TEST_COVERAGE == 'true' ]]; then composer static-analysis ; fi
  • Remove phpstan from the project (phpstan.neon.dist, .travis.yml entry, composer.json require-dev and scripts)

Optional
Report in description errors as checklist and fix them

PHP8.1 Compatibility

The bridge is not compatible with the new PHP8.1 version. A rework is required on some librairies and classes.

The correction would be on a new release V9.0.0

Circular dependency detected

Occurs error while resolving lazy injection:

Circular dependency detected while trying to resolve entry 'config'

Seems, in this case PHP-DI tries resolve each entry of config, because it is ArrayDefinition.

Simple code to reproduce:

use DI\ContainerBuilder;
use Elie\PHPDI\Config\Config;
use Psr\Container\ContainerInterface;

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

class Foo
{
    public function __construct(string $key)
    {
    }
}

class Bar
{
    public function __construct(Foo $foo)
    {
    }

    public function compute(): void
    {
    }
}

$config = new Config([
    'key' => 'null',
    'dependencies' => [
        'factories' => [
            Foo::class => function (ContainerInterface $c) {
                return new Foo($c->get('config')['key']);
            },
        ]
    ],
]);

$containerBuilder = new ContainerBuilder();
$config->configureContainer($containerBuilder);

$containerBuilder->addDefinitions([
    Bar::class => DI\create()
        ->constructor(DI\get(Foo::class))
        ->lazy(),
]);

$container = $containerBuilder->build();

$bar = $container->get(Bar::class);
$bar->compute();
php-di/php-di:^6.0
elie29/zend-phpdi-config:^4.0
ocramius/proxy-manager:^2.1

If delete config.dependencies from the definitions this solves the issue. But, I do not know whether this can lead to issues in other cases?

    private function setDependencies(): void
    {
        $this->dependencies = $this->definitions[$this::CONFIG]['dependencies'] ?? [];
+       unset($this->definitions[$this::CONFIG]['dependencies']);
    }

Adding new autowire key support to ConfigProvider

Instead of changing invokables behaviour, it is more interesintong to introduce a new key to ConfigProvider.

new Config([
        'dependencies' => [
            'services'   => [],
            'invokables' => [],
            'autowires'  => [], // A new key added to support PHP-DI autowire technique
            'factories'  => [],
            'aliases'    => [],
            'delegators' => [],
        ],
        // ... other configuration

        // Enable compilation
        Config::DI_CACHE_PATH => __DIR__, // Folder path

       // Enable cache
        Config::ENABLE_CACHE_DEFINITION => false, // boolean, true if APCu is activated
])

Makes invokables an array of services

Actually, invokables key accepts only an associative array that map a key to a constructor-less service. this should be improved by accepting an array of services as well :

'invokables' => [
    'service-1' => MyService1::class,
    MyService2::class
]

Moreover, through the container we should be able to get the service by its name or by its alias as follow:
$container->get('service-1') === $container->get(MyService1::class);

Zend\Di namespace

Hi, I've been merging a related PR for zend-expressive today. I noticed you use the Zend\DI namespace for this package. It's a bit confusing since there is actually a zend-di package. There are no plans yet to add Zend\Di to the zend-expressive skeleton yet, but that might change in the future and which would introduce a namespace collision.

I'm wondering if you could change the namespace in a next major release to prevent confusion and a possible future namespace collision. Something like Zend\PHPDI\, DI\ or Ellie\DI\ would work.

Allow to disable use autowire

In dev mode, sometimes we need to test the ability to disable autowire. Add this possibility to master and branch 3.0

Compile with Delegators

Is your feature request related to a problem? Please describe.
When a delegator is in use, the container can not be compiled:
PHP Fatal error: Uncaught DI\\Definition\\Exception\ \InvalidDefinition: Cannot compile closures which import variables using the use keyword in.... When disabling the used delegator, it works.

Describe the solution you'd like
Even when using delegators, it would be fantastic for performance to be able to compile/cache it, the closure with use crashes the caching.
Hints on what can be used otherwise would help also a lot! (Or if i'm again using something in the wrong part)
Moving the routes in a central definition is not wanted in the architecture.

Additional context
I'm using the ApplicationConfigInjectionDelegator to be able to have the route definitions in the corresponding module ConfigProvider, like explained here.

public function getDependencies(): array {
    return [
        'delegators' => [
            \Mezzio\Application::class => [
                \Mezzio\Container\ApplicationConfigInjectionDelegator::class,
            ],
        ],
    ];
}

Thanks!
michael

Consider to change namespace

Would you consider to change the namespace of the project in something like Elie29\Zend\Config\Di or something more strictly to you?

The library could conflict with Zend\Di in the future.

Allow to declare custom definitions

It would be useful a config key which allows to set custom service definitions.

Something like:

return [
    'dependencies' => [
        'definitions' => [
            MyClass::class => DI\create(MyClass::class)->lazy(),
        ],
    ],
];

This will allow to use a configuration to define services in different modules when you use a modular structure.

Use Expressive Skeleton installer with PHP-DI results in error?

I have some experience with PHP-DI and now try to use it with Zend Expressive.

When using composer create-project zendframework/zend-expressive-skeleton expressive-project and selecting PHP-DI as DependencyInjectionContainer, the default installation seems to result in an error:

composer serve => then open gives:

Fatal error: Uncaught Zend\ServiceManager\Exception\InvalidArgumentException: Zend\ServiceManager\AbstractPluginManager expects a ConfigInterface or ContainerInterface instance as the first argument; received DI\Container in /user/project/vendor/zendframework/zend-servicemanager/src/AbstractPluginManager.php:59 Stack trace: #0 /user/project/vendor/zendframework/zend-view/src/HelperPluginManager.php(253): Zend\ServiceManager\AbstractPluginManager->__construct(Object(DI\Container), Array) #1 /user/project/vendor/zendframework/zend-expressive-zendviewrenderer/src/HelperPluginManagerFactory.php(20): Zend\View\HelperPluginManager->__construct(Object(DI\Container)) #2 [internal function]: Zend\Expressive\ZendView\HelperPluginManagerFactory->__invoke(Object(DI\Container)) #3 /user/project/ in /user/project/vendor/zendframework/zend-servicemanager/src/AbstractPluginManager.php on line 59

I tried the instructions at https://github.com/elie29/zend-di-config#using-with-expressive

apparently Interop\Container\ContainerInterface is required by Zend\ServiceManager\AbstractPluginManager::__construct(), while the PHP-DI container implements Psr\Container\ContainerInterface.

but am kind of stuck here... Any suggestions? Are the involved libraries still compatible?

Should Zend Expressive switch the the PSR-11 approach?

https://github.com/container-interop/container-interop

Projects consuming container-interop interfaces are very strongly encouraged to directly type-hint on PSR-11 interfaces, in order to be compatible with PSR-11 containers that are not compatible with container-interop.

migration to php 8

  1. create a new release v8.0.0 to support php 8
  2. update all classes to php 8 syntaxe
  3. modify and update composer.json
  4. update readme
  5. update changelog

Container compilation and delegators

I use several delegators in my project and if I define Config::DI_CACHE_PATH I get the error

Cannot compile closures which import variables using the use keyword
as mentioned in issue #45. The solution mentioned there isn't practicable in my case. I'd like to propose the following change to the Config class which eliminates the problem with the use keyword (and allows for type hinting the parameters):

                $current                      =
                    factory(function (ContainerInterface $container, RequestedEntry $requestedEntry, string $delegator, string $previous, string $name) {
                        $factory  = new $delegator();
                        $callable = function () use ($previous, $container) {
                            return $container->get($previous);
                        };
                        return $factory($container, $name, $callable);
                    })->parameter('delegator', $delegator)
                      ->parameter('previous', $previous)
                      ->parameter('name', $name);

scalar constructor / property injection etc.

Used PHP-DI and liked the way of configuring services, e.g. constructor injection for scalar values.

return [
    'Logger' => DI\create()
        ->constructor('/tmp/app.log'),
];

How can stuff like this be done when using this config lib?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.