thephpleague / container Goto Github PK
View Code? Open in Web Editor NEWSmall but powerful dependency injection container
Home Page: http://container.thephpleague.com
License: MIT License
Small but powerful dependency injection container
Home Page: http://container.thephpleague.com
License: MIT License
Similarities between CallableDefinition
and ClosureDefinition
mean they can be combined and the functionality surrounding Container::invokable
and Container::call
can be proxied through. This also has the benefit of allowing any callable to be used as as a factory for building objects.
I'm not sure if this is expected behavior or not, but here goes:
When you add an instance of a class which has an __invoke method, the DefinitionFactory sees this as a callable, so it gets added as a CallableDefinition. As a consequence of this, when you try to get the $concrete it does not return the instance, but whatever the __invoke method returns.
Here's an example to make it a bit clearer
I think this might be a bug, because changing the innards of a class should probably not change what the container returns. If my thinking is correct, the fix would probably be handling objects which are not instances of Callable like any other undefineable item and just add them as an arbitrary value/instance (= like here.
I'm more than willing to PR this, but I'd rather make sure this is indeed a bug and not by design, because I can imagine being able to use __invoke has its uses too.
Hi, the PSR-11 (ContainerInterface) has been accepted. bit.ly/2lGHGz8
Currently this package (thephpleague/container) provides the "container-interop/container-interop-implementation" v.1.1. So it should be "easy" to migrate to v.1.2 (PSR-11) right?
Starting Feb. 13th 2017, container-interop is officially deprecated in favor of PSR-11. Container-interop has been the test-bed of PSR-11. From v1.2, container-interop directly extends PSR-11 interfaces. Therefore, all containers implementing container-interop are now de-facto compatible with PSR-11.
When are you planing to "update" from "container-interop/container-interop" v.1.1 to v1.2 (PSR-11)?
Is it the intended behavior? What do I do if I want to return false from the closure?
I think you have a fantastic project here. It is lightweight and has everything that is needed for an IoC container. One problem I had when trying to learn the container was in how to add singletons to the container. The documentation has no reference to the container's singleton method. I feel as though it should be. I will gladly write it myself and submit a pull request if there are no objections to adding this.
Hello, I use [https://github.com/jenssegers/lean] for integrate with League\Container and Slim. I wanted to override foundHandler
and request
without edit SlimServiceProvider
class. If I use the same name, there is no effect. How to override named items in $provides
array in SlimServiceProvider
.
I'm not sure if this is a bug or intended behavior so bear with me. If I share a value in the container, I am properly able to overwrite it:
$container = new League\Container\Container();
$container->share('foo', 'bar');
dump($container->get('foo')); // bar
$container->share('foo', 'baz');
dump($container->get('foo')); // baz
If however I share a factory, I can't overwrite it:
$container = new League\Container\Container();
$container->share('foo', function() {
return 'bar';
});
dump($container->get('foo'));// bar
$container->share('foo', function() {
return 'baz';
});
dump($container->get('foo')); // bar
Reason seems to be the container looks in $this->shared
the second time even though the definition has changed, and thus it returns the resolved value. Shouldn't the resolved value be cleared when the definition changes?
Example:
$c = new League\Container\Container(['di' => [
'a_value' => 123,
]]);
PHP Fatal error: Call to a member function withArguments() on integer in /Users/chris/Code/league-container/src/Container.php on line 283
I'm having some issues with the ReflectionContainer
and autowiring. I tried digging into the code to do a PR, but I could not really figure out the problem yet.
In my example, I share a specific instance of a class first:
$container = new League\Container\Container;
$container->delegate(
new League\Container\ReflectionContainer
);
$container->share('App\Client', function()
{
return new App\Client('test', 'test');
});
And I have another class called App\SDK
which requires a App\Client
instance from the constructor. Here comes the bad part. When I try to create an instance of App\SDK
using reflection, I'm getting a lot of errors:
$container->get('App\SDK');
PHP Fatal error: Uncaught exception 'League\Container\Exception\NotFoundException' with message 'Unable to resolve a value for parameter (username) in the function/method (__construct)' in .../vendor/league/container/src/Argument/ArgumentResolverTrait.php:66
It seems like it tries to create the App\Client
even though I have already bound an instance of it to the container. While digging into the code, I have noticed that $this->getContainer()
returns NULL
in the ArgumentResolverTrait
. Shouldn't this contain the original container instance?
So I may have overlooked something but I can't figure this out.
I am creating a bot that has a main object $this->bot and I want to add all the modules/services to that object.
So I setup the container etc. and I created a ServiceProvider for my module called "helloworld".
class ServiceProvider extends AbstractServiceProvider
{
protected $provides = [
'helloworld'
];
public function register()
{
$this->getContainer()->add('helloworld', 'Bot\HelloWorld\HelloCommands');
}
}
I then run the __construct of the main Bot class to register and load every single service I have:
class Bot {
/**
* Construct the bot and read the configurations.
*/
public function __construct()
{
// Create the main DependencyInjection
$bot = new Container;
// Read the config into the main bot object
$this->bot->config = $this->config();
// Add services to the container from the config
foreach ($this->bot->config['services'] as $service) {
$bot->addServiceProvider($service);
}
$this->bot = $bot;
}
public function config()
{
return [
"services" => [
"Bot\HelloWorld\ServiceProvider"
],
];
}
}
This is where I get stuck. How do I access the key "helloworld" when I have registered the service as "Bot\HelloWorld\ServiceProvider" so I can create this object:
$this->bot->helloworld
# So I can use the commands in HelloCommands class:
$this->bot->helloworld->sayHello();
Basically automate the following without knowing the namespace/key:
$this->bot->helloworld = $bot->get('helloworld');
Thanks.
If you try to store a string in the container and the string happens to be a valid classname then it will be converted to a service. For example
$database_host = 'db'; // lets assume there is a top level class named db
$container->add('database_host', $database_host);
var_dump($container->get('database_host')); // this is an instance of db and not the string we provided
I poked around in the code and there doesnt seem to be a way to change this behavior. It also seems a little implicit that you cant store a scalar/array value in the container except by using the ->add method which has this guessing behavior.
Is this container not meant to allow storing of simple values in addition to instances or am I missing something?
Take the following code:
class Foo {}
class Bar {
public function __construct(Foo $foo) {}
}
$container = new Container();
$container->add('some.alias', 'Bar');
dump($container->get('Bar')); // Will work
dump($container->get('some.alias')); // Argument 1 passed to Bar::__construct() must be an instance of Foo, none given
Is there no way to have aliased services be auto-resolved, without having to specify all their constructor arguments manually like this:
$container->add('some.alias', 'Bar')->withArgument('Foo');
Do this package allow recursive dependencies?
class Recursive
{
public function __construct(Recursive $recursive) { }
}
Coming from Zend\ServiceManager\ServiceManager
, I wondered if anyone besides me would find it useful if the Container
could be configured to throw exceptions when it is attempted to retrieve a service which
What do you think?
/cc @alucic
Should I use a leading backslash in class name when register new class:
$container->add('\SomeNS\ClassName');
instead of:
$container->add('SomeNS\ClassName');
What's difference?
It would appear that \League\Container\Container::isRegistered
only checks the items array so for \League\Container\Definition/ClassDefinition.php::invokeMethods
line 81, where you run the code
foreach ($method['arguments'] as $arg) {
$args[] = ($this->container->isRegistered($arg)) ? $this->container->get($arg) : $arg;
}
The problem is if you try to inject a singleton using withMethodCall
then it returns false however $this->container->get($arg)
would actually return the correct singleton.
My use case is as follows:
$container
->add('command.tool', 'Namespace\Command\Tool')
->withMethodCall('setContainer', ['League\Container\Container']);
( Note in this case the class Namespace\Command\Tool
implements the trait \League\Container\ContainerAwareTrait
)
And since the container is a singleton it is not resolved.
Is this intended behaviour? ( If not i can throw up a pull request to either change the behaviour of isRegistered
or change this if statement to run OR $this->container->isSingleton($arg)
)
Is there a reason why the function addItemsFromConfig is protected? I want to use it inside a ServiceProvider, is it a bad idea?
The commented line throws an error: Argument 1 passed to App\Users\UserMapper::__construct() must be an instance of Spot\Locator, none given. And the second line works properly, but defeats the purpose of lazy loading.
// $container->add('App\Authentication\UserMapperInterface', 'App\Users\UserMapper');
$container->add('App\Authentication\UserMapperInterface', $container->get('App\Users\UserMapper'));
The problem seems to be with Spot\Locator being registered in a service provider. Using the commented line, the container does not look into the providers to find the service, like it does when using the get method
It would be awesome to be able to use withMethodCall with CallableDefinition or at least have a method that would accept any callable and put the container value as the first argument. I need to be able to extend definitions from one provider to another:
//First provider
public function register()
{
$this->getContainer()->add('SomeClass', function() {return new SomeClass;});
}
//Second provider
public function boot()
{
$this->getContainer()->extend('SomeClass')->withCallback(function($classObj, $otherArg) {
$classObj->add($otherArg);
return $classObj;
}, ['otherArgFromContainer]);
}
What are your thoughts on this? Could it be added fast as a non breakable new feature? I can do a pull request if you want.
I use named constructors (static methods which return an instance of the class) fairly often. I'd really like to be able to do this with container.
It is currently possible using call and invokable but it has a different meaning semantically. And also using factory closures, but these are not possible through the config array.
I'm happy to create a PR for this but just wanted to see whether it's something which might be accepted before starting work on it.
So, what I propose is the following:
Given:
<?php
final class Foo
{
public static function fromString($string)
{
return new self(/* ... */);
}
// ...
}
I'd like to be able to use container like so:
$container->add('Foo', ['Foo', 'fromString')->withArgument('some_value');
$foo = $container->get('Foo');
Also, double colon notation may be worth considering but has a danger of a BC break of anyone is using double colons in their names:
$container->add('Foo', 'Foo::fromString')->withArgument('some_value');
$foo = $container->get('Foo');
Finally, I'd like to see it so this is possible via the config array:
$config = [
'Foo' = [
'class' => 'Foo', // 'Foo::fromString' could be possible also
'constructor' => 'fromString',
'arguments' => [ 'some_value' ]
]
];
So thoughts? If I'd created a PR for this would it be considered?
I am thinking about this for a long time now, and I think that from the outside invokables and aliases have only one difference: different resolving method. But I think letting invokables to be passed as an argument would be a good feature.
Hi,
It would be great to allow a way to have the inflector
use a factory closure to create classes that implement an interface or extend a base class x.
For instance:
use My\ClassInterface;
$container->inflector(ClassInterface::class, function ($container, $concrete) {
return ClassFactory::create($concrete);
});
What is your thought on that?
See #28 (comment)
When the definition of a reflected class is created, it is not possible to pass arguments to it. At least currently the definition returned from the reflect
method is not called with the arguments passed to the get
method. Why is that?
Considering the new Container::call
functionality, I feel there should be a way to pass concrete runtime arguments to the callable
separately to the dependencies that are defined.
It doesn't necessarily need to be >5.6
only, the callable
would simply need to define the correct amount of arguments.
$container->invokable('some_callable', function (SomeDependency $somedependency, $variadic1, $variadic2) {
// ...
})->withArgument('SomeDependency');
$container->call('some_callable', ['variadic1 value', 'variadic2 value']);
$container->invokable('some_callable', function (SomeDependency $somedependency, ...$params) {
// ...
})->withArgument('SomeDependency');
$container->call('some_callable', ['variadic1 value', 'variadic2 value']);
The main motivation behind this is to be able to use Container::call
within a strategy for League\Route, allowing for dependencies to be defined per controller action but still pass in values from wildcard segments of the URI.
Currently every service provider, which is added to the container is instantiated by the container every time it checks a service provider. Furthermore, setContainer
is called every time as well which is unnecessary if the service provider is already instantiated.
I created two ServiceProviders with their own namespace.
But if I register the same key (hellocommands) in both (notice HelloWorld and HelloWorld2).
This code exists in their respective ServiceProvider.php class, I just type both here to illustrate.
$this->getContainer()->add('hellocommands', 'Bot\HelloWorld\HelloCommands');
$this->getContainer()->add('hellocommands', 'Bot\HelloWorld2\HelloCommands');
Only the latter exists in the container.
When I do this:
$this->getContainer()->add('1_hellocommands', 'Bot\HelloWorld\HelloCommands');
$this->getContainer()->add('2_hellocommands', 'Bot\HelloWorld2\HelloCommands');
Both shows up.
Is this normal?
How would I go about forcing modules of my application to have unique key names to avoid this since it seems it does not use namespace to separate them?
Currently you can only call a method once since method names are stored as keys of an associative array. Would be awesome if we could call a method multiple times. Use case: adding "extensions" to an object.
I can't see any difference other than a few lines of different formatting a different type hint. Does it make any sense?
For example, when an object implements ContainerAwareInterface
the container could be aware that when this is resolved, it should call a defined setter (setContainer
) to inject something.
$container = new League\Container\Container();
$container->context(League\Container\ContainerAwareInterface::class)
->withMethodCall('setContainer', [$container]);
Or something. The context
method is just a temporary name. There must be a better suited name.
When an object which has this interface implemented, the setContainer
method will be called automatically on the resolved object, right before the end of $container->get(Vendor\MyClass::class)
.
Currently the only way to pass a configuration array ['di' => $services]
is to pass it in the container's constructor. It would be nice if the method was made public to allow service providers to register their services via an array too.
I couldn't find a definition about the param $args
to Container::get()
in the docs.
I am using Auto Dependency Resolution.
The param seems to be ignored after line 202 in src/Container.php
, which handles the auto-resolution.
What i would expect:
Using the $args
param, it should be possible to inject further parameters into the constructor of the class.
Possibly even overriding defined parameters..
I'd be happy to do a pull-request, but i want to see whether this needs to be discussed first.
Because I branched #94 off of #93 instead of master, GitHub marked #93 as merged once #94 goes in. Per #93 (comment), opening this issue for tracking purposes.
I have a KernelInterface
class which is implemented by various different user definable Kernel
classes with the default being a DefaultKernel
class; this is handled by the following code:
if (file_exists($kernelPath)){
include $kernelPath;
$container->share(KernelInterface::class, $configuration->get('kernel', DefaultKernel::class));
}else{
$container->share(KernelInterface::class, DefaultKernel::class);
}
/** @var KernelInterface $kernel */
$kernel = $container->get(KernelInterface::class);
$kernel->boot();
The KernelInterface is as follows:
interface KernelInterface
{
public function boot();
}
The DefaultKernel is as follows:
class DefaultKernel implements KernelInterface
{
/**
* @var Project
*/
private $project;
/**
* DefaultKernel constructor.
* @param Project $project
*/
public function __construct(Project $project)
{
$this->project= $project;
}
public function boot()
{
// ...
}
}
If the implementation of KernelInterface doesn't have a __construct
it works fine and the kernel's boot method will be executed; however with the __construct
I get the following error message:
Catchable fatal error: Argument 1 passed to App\Modules\Kernel\DefaultKernel::__construct() must be an instance of App\Project, none given
The only way I can get Project
to be autowired is if I use the class name directly $kernel = $container->get(DefaultKernel::class);
instead of via the interface.
I have auto-wiring enabled via the use of the League\Container\ReflectionContainer
delegate.
Is this a bug or am I doing something wrong?
IMO Callable and Closure also return an object. Why can't addMethodCall(s) be bart of the whole interface? In some cases it can also be useful (for example when extending a definition).
is it possible to keep the old docs?
Like http://glide.thephpleague.com/ or http://event.thephpleague.com/?
In some cases an argument can be a class name (eg. a doctrine entity name). In this case I don't want the container to instantiate the class, but pass it as is. To achieve this, there could be a string escaping/expression function/wrapper object (which then gets removed during argument resolution).
Hi, not quite sure if it's just me being stupid or this just not being possible:
use League\Container\Container;
use League\Container\ServiceProvider;
class MyServiceProvider extends ServiceProvider
{
public function register()
{
$this->getContainer()['key'] = 'value';
}
}
$container = new Container();
$container->add(new MyServiceProvider);
var_dump($container->get('key'));
It throws:
Fatal error: Uncaught exception 'League\Container\Exception\ReflectionException' with message 'Unable to reflect on the class [key], does the class exist and is it properly autoloaded?' in /Volumes/Data HD/Dropbox/Projects/snap/vendor/league/container/src/Container.php on line 401
I've used Pimple in the past and this seems to work just fine ๐
When the InflectorAggregate inflects on an object, it doesn't look at traits, only interfaces.
I was wondering if it would be worth the effort of adding support for traits?
Arguments against such a thing could be that traits shouldn't be used for inflection or that adding extra code (and thus extra complexity) isn't desired.
So before opening a PR to that effect, I thought it might be wise to post this question first.
I see you are working on version 2, which probably would have option described in title properly implemented, but since it will probably take a while to release I would like to propose quick hotfix for version 1 to also has this option implemented.
Registering factory in the container could be done as registering some FactoryProxy object that would store given callable in one of its fields. During creation of object, this callable would be taken from this wrapper and call() would execute it as normal function. Thanks to that you can have Factories with param resolution. Easy, powerful, quite dirty but good enough for version 1.
What do you think?
League\Container\Container
allows a definition factory to be injected. This now needs to be provided as an interface to allow for custom definitions.
Reference: orno/di#32
I had an issue where I had put
$this->container->inflector(ContainerAwareInterface::class)
->inflect('setContainer', [ContainerInterface::class]);
inside the register
method of a ServiceProvider
. The register method wasn't called because the resolved object wasn't referenced in the container as a ContainerAwareInterface
implementation object.
Expected behaviour:
$container->share(Client::class)
->withRawArgument([
'base_uri' => $config->get('base_uri'),
'handler' => $container->get(HandlerStack::class),
]);
Current behaviour:
$container->share(Client::class)
->withArgument(new RawArgument([
'base_uri' => $config->get('base_uri'),
'handler' => $container->get(HandlerStack::class),
]));
Would this be acceptable? I can make the PR, it just saves having to import a RawArgument
class ๐
Singletons make sense in a normal application context. However in some cases you want to create a completely new instance of a singleton.
Use case: default DB connection, new DB connection used elsewhere.
Just set up a project with autowiring when I noticed that when the container has to fetch it from a delegate for autowiring it isn't stored in the container as share entry even though I marked it as shared in the first place. Is this intended behavior?
Current behavior:
$container = new Container();
$container->delegate(new ReflectionContainer());
$container->share('Acme\Foo', new Acme\Foo());
var_dump(
$container->get('Acme\Foo') === $container->get('Acme\Foo')
); // false, two different instances are returned
Expected behavior:
$container = new Container();
$container->delegate(new ReflectionContainer());
$container->share('Acme\Foo', new Acme\Foo());
var_dump(
$container->get('Acme\Foo') === $container->get('Acme\Foo')
); // true, both return the same instance
When the system looks up an alias during a get that is not defined & has to reflect it, then the args to not get passed in the get request do not get passed. I am about to submit a test & a patch to show the issue.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.