rdlowrey / auryn Goto Github PK
View Code? Open in Web Editor NEWIoC Dependency Injector
License: MIT License
IoC Dependency Injector
License: MIT License
Now that Injector::execute
is able to provision dependencies it may be worth using this functionality to simply the use of delegate/factory instantiators. Currently, Provider::delegate
requires a valid PHP callable. Consider the following scenario:
<?php
interface Auto {}
class Carerra911 implements Auto {}
class FordPrefect implements Auto {}
class CarSelector {
function select() {
return (extension_loaded('porsche'))
? new Carerra911
: new FordPrefect;
}
}
Because I use the Auto
interface all over my application I want to delegate its
instantiation to my Injector
. Currently I'd need to do this:
<?php
$injector->delegate('Auto', [new CarSelector, 'select']);
It would be a nice feature to have the delegate automatically detect callable classes (those with __invoke
) and class methods for (as yet) uninstantiated delegate classes.
So in the above scenario, instead of manually instantiating the CarSelector
and passing a callable to Injector::delegate
I'd be able to do this:
<?php
$injector->delegate('Auto', ['CarSelector', 'select']);
Or, if CarSelector
used __invoke
instead of select
:
<?php
$injector->delegate('Auto', 'CarSelector');
All that's needed is to check if the delegate passed is callable. If not, we attempt to use Injector::execute
to create a callable. If that fails, we error out like normal. The obvious advantage here is that you get the full power of configured dependency provision for your delegates instead of being forced to supply them in a callable state.
Support the specification of dependencies in a text configuration file.
For example:
<?php
$injector = new Auryn\Injector;
$injector->define('PhpAmqpLib\Connection\AMQPStreamConnection', ['localhost', 5672, 'guest', 'guest']);
$obj = $injector->make('ClassDependingOnAMQPStreamConnection');
You could define this in yaml/XML/whatever:
# wiring.yaml
wiring:
- { class: PhpAmqpLib\Connection\AMQPStreamConnection, params: [localhost, '5672', guest, guest] }
<?php
$injector = new Auryn\Injector;
$injector->parseWiring(file_get_contents('wiring.yaml'));
$obj = $injector->make('ClassDependingOnAMQPStreamConnection');
Is the behaviour for the below defined aka can you tell me what should happen without inspecting the code:
$provider = new Auryn\Provider();
$testClass = new TestSharingClass();
$provider->alias('TestSharingClass', 'AliasedTestSharingClass');
$provider->share($testClass);
$instance = $provider->make('TestSharingClass');
Is $instance
meant to be of type TestSharingClass or AliasedTestSharingClass?
I'm guessing that it might be better just to throw an error somewhere if you try to share something that is aliased or vice-versa, to avoid the result of the provider being dependent on the order in which it was sent commands.
If that's the case I can have a look at implementing it once it's agreed.
Travis also offers HHVM: http://docs.travis-ci.com/user/languages/php/
I'm using Auryn together with XStatic as a way to get something similar to Laravel Facades.
The problem is XStatic requires an instance of a Service Container that implements this Container Interface already being used by other Dependency Injector Containers.
It only requires implementing these two methods in the Provider (which you already has, but with other names).
public function get($id)
{
return $this->make($id);
}
public function has($id)
{
return $this->isDefined($id);
}
I'm currently extending Auryn\Provider so it is not much of a hassle, just wondering if it is wort the consideration.
When you call Injector->execute on a function, and it requires a non-typehinted param which has not been defined, the error message is generated with the code:
$declaringClass = $param->getDeclaringClass()->getName();
throw new InjectionException(
sprintf($this->errorMessages[self::E_UNDEFINED_PARAM], $declaringClass,$param->name),
self::E_UNDEFINED_PARAM
);
This can be replicated with the code:
$provider = new \Auryn\Provider();
$provider->execute('functionMissingParamDefine');
exit(0);
$param->getDeclaringClass() does not always work as you would expect. If execute is called from within another class it returns that classes name. e.g. if you put the above code in a test case inside ProviderTest, it returns ProviderTest.
I encountered this by as it returns null when the execute is called from outside a class.
I will fix this when I've re-merged my code with Levi's ninja reorganisation.
Several examples show passing a new instance of Auryn\ReflectionPool into Injector constructor. However, that class does not seem to be defined anywhere in the library.
The Injector constructor itself creates a new CachingReflector if no dependency is passed -- is the documentation out of date in this respect?
Any chance for a new tag soon. It seems the last stable release was back in Oct 2014 (unless I'm missing something). I've had some longer projects (months) which are going to be going stable soon and been using dev, but I don't want to push them to prod on dev-master, is there a roadmap for a 1.0?
Hi guys,
As I mentioned in the other issue, I'm not a fan of re-throwing errors. I've refactored it in my fork as that already had some changes that made the refactor easier.
Before I make the same changes based on rdlowrey's main branch please can you give me feedback on whether you think it's acceptable or whether you hate it, as there's no point me doing it on the main fork if it's not going to be accepted.
The short version of the changes are:
The result is that the cyclic dependency exception message looks like:
Detected a cyclic dependency while provisioning class RecursiveClassA. Constructor chain is DependsOnCyclic->RecursiveClassA->RecursiveClassB->RecursiveClassC->RecursiveClassA
i.e. it tells you which class causing the actual problem, as well as how that class was reached including the original class that you called make on.
The reason I prefer it like this, is so that you can see the whole trace of calls in one go without having to call $e->getPrevious().
Thoughts?
cheers
Dan
Hi,
Is there any reason why the code for buildArgumentFromTypeHint returns null rather than throwing an exception when a required parameter is missing?
https://github.com/rdlowrey/Auryn/blob/master/src/Auryn/Provider.php#L575
Unless there's a cunning reason behind it, it would seem better to throw a InjectionException to fail fast rather than allowing something to break later.
AKA I just forgot to set the defines for something and had to debug through the code to figure out the error, rather than having a nice Exception tell me straight away.
cheers
Dan
This is something I've been meaning to do for awhile ...
Currently you're required to to use the raw injection prefix when defining parameters for any argument type even if it's not a string.
<?php
class MyClass {
function __construct(StdClass $obj){}
}
$injector->define('MyClass', [':obj' => new StdClass]);
However, the :
prefix should only be necessary if the passed argument is a string (so the injector can differentiate between a class name and an actual raw argument). The injector should be smart enough to know that if a non-string array value was passed in the class's injection definition that it's meant to be a raw parameter. This is a small change that would eliminate one of the major sources of confusion for new users.
Could you show an example of how best to cache the instances across request response cycles? So that there's less bootstrapping required during production. I know that Orno/Di has a caching feature. https://github.com/orno/di#caching
It hides the constructor and then does magic stuff:
I'll look into what can be done to make it easier for Doctrinated people to use Auryn.
Currently the Auryn\Provider
will error out for non-concrete typehints without a definition even if the argument in question has a default parameters. Instead, injectors should pass in the default parameter if no implementation or alias exists for the parameter.
Hi,
The function buildNonConcreteImplementation appears to be unreachable.
It is only called in one place, and the functions that call it are all called from make($className, array $customDefinition = array())
but right at that top level, the classname is converted to either an alias, shared or delegated value, and so it doesn't appear to be possible for buildNonConcreteImplementation to be called.
Removing it, and removing the else block that it's called from doesn't affect the tests, which is not surprising as that line is not covered byt the tests.
cheers
Dan
The exception which gets thrown when an implementation is needed is lying about the parameter name.
interface Foo {}
class Bar
{
public function __construct(Foo $foo) {}
}
$injector = new Provider();
$injector->make('Bar');
results in:
Injection definition/implementation required for non-concrete parameter $interface of type Foo' in
where I would expect it to say the parameter is called $foo
.
The test classes and test method probably explain this adequately:
class BaseExecutableClass {
function foo() {
return 'This is the BaseExecutableClass';
}
}
class ExtendsExecutableClass extends BaseExecutableClass {
function foo() {
return 'This is the ExtendsExecutableClass';
}
}
public function testExecutableAliasing() {
$provider = new Provider();
$provider->alias('BaseExecutableClass', 'ExtendsExecutableClass');
$result = $provider->execute(['BaseExecutableClass', 'foo']);
$this->assertEquals('This is the ExtendsExecutableClass', $result);
}
Fixed in Danack/Auryn@e14db9a but that is post PR-64
Delegates cannot currently do much in terms of dynamic instantiation -- they're essentially specified once and will always return the same instances. By passing them information about the context in which they are being invoked delegates will be better equipped to return instances dynamically based on what's needed at that specific time and by the context requesting an object.
These two blocks of code should produce the same object shouldn't they?
$injector->defineParam('em', $entityManager);
$testClass = $injector->make("InjectTest");
$testClass = $injector->make("InjectTest", [':em' => $entityManager]);
The second one doesn't act the same. The custom definition doesn't get passed through when making the dependencies of InjectTest, and so the error:
'No definition available while attempting to provision typeless non-concrete parameter em'
is generated.
I don't like the title, but that was my 3rd try, so we'll go with it.
I have an interface, with an abstract class that implements the interface. Elsewhere, there is a concrete class which extends the abstract.
If I establish a delegate for the interface, then try to provision the concrete, make() will not call that delegate, but instead directly attempt to provision the concrete class.
include('../vendor/autoload.php');
$injector = new Auryn\Injector();
// given an interface
interface iFoo {}
// with an abstract implementation
abstract class Foo implements iFoo {
public function __construct(
$argumentFactoryWouldProvide
) {
}
abstract public function someMethod();
}
// and a concrete implementation
class Bar extends Foo {
public function someMethod() {}
}
// this factory can make any iFoo's
class iFooFactory {
public function makeMe() {
return new Bar('factory-provided-argument');
}
}
//$injector->alias('iFoo', 'Foo'); // this (and variations thereof) doesn't make a difference
$injector->delegate('iFoo', 'iFooFactory::makeMe');
$bar = $injector->make('Bar'); // expected: talk to my iFooFactory
// actual: exception about non-class Foo param
var_dump($bar); // nope
This may be a bug, or a feature request. When using a delegate function, either as a closure or a named function, I expected the delegate to be provisioned with hinted dependencies as though it were called by execute(). In actuality, it receives a string as an argument -- the name of the class being delegated.
A change to this behavior may have BC issues, if someone is using that string for a centralized provider.
Here is a repo using a closure: https://gist.github.com/GRMule/addd42fe35263b3f518a
And a similar repo, using a named function: https://gist.github.com/Danack/e54e9d9fa84914facb24
My real use-case, I have a database wrapper, and I want to pass in the host, user, password, etc. Those items are coming from a Configuration object. So, I set up a delegate like this for my database class:
$injector->delegate('DataAccessMysql', function (Configuration $config) {
return new DataAccessMySql(
$config->get('database.host'),
$config->get('database.user'),
$config->get('database.password'),
$config->get('database.database')
);
});
I expected my delegate to get the dependency injected, as though I had called it with execute(), but instead, it gets a string with the name of the class. Which, of course, throws an error.
I was able to work around the problem using this, which gives a fair idea of the expected behavior:
$injector->delegate('DataAccessMysql', function () use ($injector) {
return $injector->execute(function (Configuration $config) {
return new DataAccessMySql(
$config->get('database.host'),
$config->get('database.user'),
$config->get('database.password'),
$config->get('database.database')
);
});
});
Sometimes you need to have two different instances of the same type of object injected into an object e.g.
class RedisSyncCheck {
function __construct(RedisClient $sourceClient, RedisClient $targetClient) {
//....
}
function summariseMissingKeys() {
//A function that analyses stuff in the sourceClient compared to the targetClient
}
}
Currently there is no way for Auryn to support injecting different type-hinted parameters into the same class.
This is probably related to #35 - which is also about allowing more customisation of what gets injected based on the details of what is asking for the parameter.
@rdlowrey The other day you asked what features I think are still needed - here is one, which we've discussed this before, but I can't find a link.
Sometimes you need to be able to use different implementations for the same interface, based on external factors.
interface Logger{}
class UtilityClass {
function __construct(Logger $logger){}
}
class ClassThatIsWorkingCorrectly{
function __construct(UtilityClass $utilityClass) {}
}
class ClassRelatedToReportedBugs{
function __construct(UtilityClass $utilityClass) {}
}
Most of our code is working fine and so we want the 'Logger' to be inserted to be one that only reports notices at the 'error' level.
However some of our users have reported bugs that we think is being caused by the code in ClassRelatedToReportedBugs, or code that it calls. We want to be able to configure our system so that any 'Logger' created by 'ClassRelatedToReportedBugs' or any of it's dependencies uses a Logger that reports notices at the 'info' level.
I think this does need to be configurable through configuration rather than modifying the code, as that the changes need to be done on a production server without downtime.
For the above case, my fork with it's (marginally crazy) ability to alias/share different classes based on the chain of class constructors solves that problem.
However there are probably other cases where some other solution would be needed e.g.
// Copies data from one server to another.
function archiveData(DBConnection $liveServer, DBConnection $backupServer){
}
Wouldn't currently be possible would it?
Obviously not every possible way of configuring which classes are instantiated needs to be supported, or should even if it is possible, however I think the first case above is something that most people would expect to be able to configure.
Hey, I get that error when I had this code:
$injector->alias('Http\Response', 'Http\HttpResponse');
$injector->share('Http\HttpRequest');
$injector->define('Http\HttpRequest', [
':get' => $_GET,
':post' => $_POST,
':cookies' => $_COOKIE,
':files' => $_FILES,
':server' => $_SERVER,
]);
$injector->alias('Http\Request', 'Http\HttpRequest');
$injector->share('Http\HttpResponse');
However, when I use an old version of Auryn, it works perfectly fine! I bet this is a bug.
I used Auryn for the first time in a project. Since I have already used the DI/IoC concept in other langues (e.g. java) the question came up if Auryn supports something like lifecycles for the objects it provides.
What I found so far is share
which makes a object behave like a singleton.
In java you can have things like a session-scoped object, so the DI-Container will return always the same object based on the http session. So no matter when you make
a object, you get the very same object for the current user, even across severall http requests.
There are even more scopes, so for example you can define that the DI-Container will return the very same object as long as you stay within a certain wizzard. As soon as you leave the wizzard and later on come back to it, you get a fresh copy of your required 'wizzard-scoped' object. this object is automatically destroyed when you leave the wizzard.
This are only some samples, but maybe it is something a future version of Auryn could provide?
Something I'd like to add is the ability to use a one-stop master configuration file allowing users and applications to eliminate all the manual calls to define()
, alias()
, share()
, etc. This should be a format-agnostic option. By that I mean you should be able to specify the configuration in JSON/YAML/native PHP or whatever.
You'd have a configuration parser that would, based on the filename extension, parse the contents into a PHP array that looks something like this:
<?php
$injectorConfig = array(
'aliases' => array(
'SomeInterface' => 'SomeImplementationClass',
'SomeAbstract' => 'OtherImplementationClass',
),
'definitions' => array(
'SomeImplementationClass' => array(
':scalarArg' => 42
),
'MyUniversalConfigClass' => array(
':configFile' => '/path/to/myappconfig.php'
)
),
'shares' => array(
'MyUniversalConfigClass',
'MyDatabaseConnectionPoolClass'
)
);
With this array in hand the configurator could then generate and pre-populate an injector for you and obviate the need to manually define/alias/share everything in your application.
When you use an delegated instantiator: https://github.com/rdlowrey/Auryn#instantiation-delegates
Is it possible to reuse the $injector inside the function? So something like this:
$complexClassFactory = function() {
$obj = new MyComplexClass($injector->make('AnotherDifferentClass'));
$obj->doSomethingAfterInstantiation();
return $obj;
};
For example, Pimple allows the container to be reused inside the function since it injects the container inside the function.
Forgive me if this has been asked before, and I know there's a way to specify them, but I'm trying to address the more "automatic" behavior as much as possible with this question.
As it stands, and as far as I can tell, Auryn will not try to instantiate dependencies which are typehinted, but which have been made optional, i.e it would not provide __construct(Foo $foo = NULL)
with an instance of Foo
.
It is possible to call make and directly say ':foo' => new $foo
, use a factory, etc... but I'm wondering if there's a particular reason that the default behavior is not really the opposite, i.e. unless explicitly told to give it NULL
, try to construct those dependencies.
I realize these are not "dependencies" in a proper sense, as being optional implies the code is not dependent on them, but that they might provide some additional benefit (like a caching class that, when passed might make things faster), vs. if not passed everything is regenerated all the time.
That said, if my caching class can be built and passed automatically, why wouldn't I care to have it?
In the following example, if the options provision is removed it will result in a exception.
According to the docs the options argument should be optional.
anyFile.php, global scope
$injector = new Auryn\Provider;
$injector->define('PDO', [
':dsn' => '...',
':username' => '...',
':passwd' => '...',
':options' => null // removed this line for exception
]);
SomeRepo.php
namespace Example\SomeRepo;
class SomeRepository {
protected $pdo;
public function __construct(\PDO $pdo){}
}
Exception from not definining 'options'
Uncaught exception 'Auryn\InjectionException' with message
'No definition available while attempting to provision typeless
non-concrete parameter PDO(options)' in rdlowrey/auryn/lib/Provider.php:599
`
I think the tests below should pass - two don't. Hopefully it's an easy fix but there is a workaround to share instance via the first instance created rather than via the interface name.
interface SharedAliasedInterface {
function foo();
}
class SharedClass implements SharedAliasedInterface {
function foo(){}
}
class ClassWithAliasAsParameter {
private $sharedClass;
function __construct(SharedClass $sharedClass) {
$this->sharedClass = $sharedClass;
}
function getSharedClass() {
return $this->sharedClass;
}
}
public function testSharedByAliasedInterfaceName() {
$provider = new Auryn\Provider();
$provider->alias('SharedAliasedInterface', 'SharedClass');
$provider->share('SharedAliasedInterface');
$class = $provider->make('SharedAliasedInterface');
$class2 = $provider->make('SharedAliasedInterface');
$this->assertSame($class, $class2);
}
public function testSharedByAliasedInterfaceNameWithParameter() {
$provider = new Auryn\Provider();
$provider->alias('SharedAliasedInterface', 'SharedClass');
$provider->share('SharedAliasedInterface');
$sharedClass = $provider->make('SharedAliasedInterface');
$childClass = $provider->make('ClassWithAliasAsParameter');
$this->assertSame($sharedClass, $childClass->getSharedClass());
}
public function testSharedByAliasedInstance() {
$provider = new Auryn\Provider();
$provider->alias('SharedAliasedInterface', 'SharedClass');
$sharedClass = $provider->make('SharedAliasedInterface');
$provider->share($sharedClass);
$childClass = $provider->make('ClassWithAliasAsParameter');
$this->assertSame($sharedClass, $childClass->getSharedClass());
}
cheers
Dan
As the title explains, would it be possible to include this capability in here, so that when Auryn performs it's reflection to read constructor signatures, could the reflection also be performed for methods within the class?
Obviously this would increase overheard significantly, but if everything's cached - that shouldn't be an issue. I'm going to take a look at the source over the course of this week but, what are your opinions on this @rdlowrey?
I have a factory class that can instantiate two differing objects, each with different constructor parameters.
I have abstracted out an event (action) to be one of a few types. This list could be added to in the future.
- Torrent
- Notification
These inherit common methods from an Action
abstract class, which implements ActionInterface
. Hierarchy:
interface ActionInterface { }
abstract class Action implements ActionInterface { }
class Torrent extends Action
{
public function __construct(TorrentDataProvider $provider, $var)
{
$this->provider = $provider;
$this->var = $var;
}
}
class Notification extends Action
{
public function __construct(NotificationRepository $repo, $var2, $var3)
{
$this->repo = $repo;
$this->var2 = $var2;
$this->var3 = $var3;
}
}
The relevant action object should only be instantiated when needed (after verification of some other stuff - userId, unique token etc). Therefore, it shall be created via an ActionProvider
(a factory class).
class ActionProvider implements ActionProviderInterface
{
public function build($type)
{
switch ($type)
{
// Is it a torrent?
case (Action::TYPE_TORRENT):
$action = new Torrent;
$action->setSomeValue("a value");
break;
// Is it a notification?
case (Action::TYPE_NOTIFICATION):
$action = new Notification;
$action->setAnotherValueDifferently("a value")->andAnother("-.-");
break;
// Or has some other bollocks been passed in?
default:
throw new Exception\AreYouRetardedException("Yes");
break;
}
}
}
As you can see, the Torrent
action object and Notification
action object have different constructor parameters. They still extend the Action
abstract class, because in the context I am using them, this makes sense.
How can I use Auryn to say:
Notification
ActionFactory::build($type)
, please instantiate the right object with all it's relevant dependenciesI have uploaded the basic code for which I'm hitting the following exception:
'Auryn\InjectionException' with message 'Injection definition/implementation required for non-concrete parameter $em of type Doctrine\ORM\EntityManager'
After forking / using composer update, the only things this application is doing is:
execute()
The execute hits the App\Controllers\IndexController::indexAction()
, the constructor of which looks like the following:
public function indexAction(Doctrine\ORM\EntityManager $em) { ... }
CTRL + Clicking this in my IDE does in fact take me to the final class that is the EntityManager.
Please could you advise? I'm hoping this is what you meant when you wanted it cut down to it's bare minimum :)
The code
$injector->alias('SomeClass', 'ExtendClass');
$injector->execute('SomeClass', 'renderImage']);
and
$injector->alias('SomeClass', 'ExtendClass');
$object = $injector->make('SomeClass');
$injector->execute([$object, 'renderImage']);
Don't result in the same object being instantiated and called, instead the first version always ignores the class aliasing and always instantiates the base class.
I'll almost certainly fix this in my refactored branch. Just recording this here so when done I can reference the relevant changes.
class BaseExecutableClass {
function foo() {
return 'This is the BaseExecutableClass';
}
}
class ExtendsExecutableClass extends BaseExecutableClass {
function foo() {
return 'This is the ExtendsExecutableClass';
}
}
public function testExecutableAliasing() {
$provider = new Provider();
$provider->alias('BaseExecutableClass', 'ExtendsExecutableClass');
$result = $provider->execute(['BaseExecutableClass', 'foo']);
$this->assertEquals('This is the ExtendsExecutableClass', $result);
}
Currently it doesn't tell you what is wrong. It needs to give appropriate information about the callable that is failing e.g. in generateExecutableFromString the exception message should include the string that was attempted to be executed.
With the new file-based configuration options becoming available, it might be nice to add a feature for development that uses the standard reflection-based auto-wiring and then writes out the equivalent configuration for increased performance in production (and for actual-dependency discovery purposes).
Currently when a class has a constructor with a typehint on one of the params, and that param also has a default value Auryn will never create the dependency.
class DependencyClass {}
class DependencyClassHasDefault {
public $dependencyClass;
function __construct(DependencyClass $dependencyClass = null) {
$this->dependencyClass = $dependencyClass;
}
}
interface DependencyInterface {}
class DependencyInterfaceHasDefault {
public $dependencyInterface;
function __construct(DependencyInterface $dependencyInterface = null) {
$this->dependencyInterface = $dependencyInterface;
}
}
e.g. calling $obj = $injector->make('DependencyClassHasDefault');
will always result in $obj->dependencyClass
being null, even though the dependency is a concrete class and so should be instantiable.
I believe the correct behaviour should be:
public function testClassDependencyWithTypeHintAndDefault() {
$provider = new Provider();
$obj = $provider->make('DependencyClassHasDefault');
$this->assertInstanceOf('DependencyClass', $obj->dependencyClass);
}
public function testInterfaceDependencyWithDefault() {
$provider = new Provider();
$obj = $provider->make('DependencyInterfaceHasDefault');
$this->assertNull($obj->dependencyInterface);
}
This is fixed in Danack/Auryn@0f59695 However the code is very ugly, and there may be a better way.
Or, we can just point and laugh at people who have 'optional' dependencies.
@rdlowrey The other day you asked what features I think are still needed - here is a second one:
I have some code that without DI looks like this:
class FetchImageTask implements BackgroundTask {}
class TaskRunner {
function __construct(BackgroundTask $taskToRun, Logger $logger, $taskName) {}
}
class ControllerClass {
function __construct() {
$fetchImageTask = new FetchImageTask();
$taskRunner = new TaskRunner($fetchImageTask, new Logger(), 'fetchImage');
}
}
Converting it to use DI with Auryn, I apparently have to create an extra class to make it possible e.g.
class FetchImageTaskRunner extends TaskRunner {
function __construct(FetchImageTask $fetchImageTask, Logger $logger, $taskName) {
parent::__construct($fetchImageTask, $logger, $taskName);
}
}
class ControllerClass {
functon __construct(FetchImageTaskRunner $fetchImageTaskRunner) {
}
}
This is a totally feasible solution, however it smells quite a bit as we're creating a class that does nothing, just due to a limitation on the language/DI tool being used.
It could also be solved by passing in a factory:
class TaskRunnerFactory {
function create(BackgroundTask $taskToRun, Logger $logger, $taskName) {
return new TaskRunner($taskToRun, $logger, $taskName)
}
}
class ControllerClass {
function __construct(TaskRunnerFactory $taskRunnerFactory, FetchImageTask $fetchImageTask, Logger $logger, $taskName) {
$taskRunner = $taskRunnerFactory->create($fetchImageTask, $logger, $taskName);
}
}
However that also smells. Again, it's creating an extra class, just because we the tool can't do the DI directly as we'd like it. It also means that parameters that need to be passed into the class 'TaskRunner' have to be passed into the 'ControllerClass' which shouldn't have to know about them.
Can you think of a better way to do this?
I modified a test class to test something else and discovered that argument definitions are not being passed to delegated functions.
interface DelegatableInterface {
function foo();
}
class ImplementsInterface implements DelegatableInterface{
function foo(){
}
}
class ImplementsInterfaceFactory{
public function __invoke($arg1) {
return new ImplementsInterface();
}
}
public function testInterfaceFactoryDelegation() {
$injector = new Auryn\Provider(new Auryn\ReflectionPool);
$injector->define('ImplementsInterfaceFactory', array(':arg1' => 'First argument'));
$injector->delegate('DelegatableInterface', 'ImplementsInterfaceFactory');
$requiresDelegatedInterface = $injector->make('RequiresDelegatedInterface');
$requiresDelegatedInterface->foo();
}
I think this should work, but it doesn't because the selectDefinition()
function isn't called for delegated creation. https://github.com/rdlowrey/Auryn/blob/master/src/Auryn/Provider.php#L105
Unit tests are fine, but what about comments? When i e.g. run phpDocumentor2 there is not much to read. Your readme.md examples are nice, but a good API reference is what i would prefer in the long run.
In addition the directory structure seems weak. No separate directories for e.g. exceptions or reflection cache drivers?
Besides that i can't find any information on the coding rules and standards used for Auryn. That makes it hard to run tools like CodeSniffer on it.
Don't get me wrong, Auryn seems powerful and covers the right way to manage (static OOP language) dependencies in my opinion, but when i use external libraries for my private projects they have to meet some quality measures. I won't force a certain coding rule for example. I just make sure that something like that is used at all.
Keep up the good work :)
For some reason
$injector->share('PDO');
$injector->define('PDO', [
':dsn' => 'mysql:dbname=' . getenv('DB_DATABASE') . ';host=' . getenv('DB_HOST'),
':username' => getenv('DB_USERNAME'),
':passwd' => 'root',
':options' => []
]);
does work, but not:
$injector->share('PDO');
$injector->define('PDO', [
':dsn' => 'mysql:dbname=' . getenv('DB_DATABASE') . ';host=' . getenv('DB_HOST'),
':username' => getenv('DB_USERNAME'),
':password' => 'root',
':options' => []
]);
Why?
Actually ignore it, I figured it out. My own stupid fault.
I think InjectorBuilder
would be a more appropriate name than ProviderBuilder
because the builder uses the public Injector
API and nothing specific to the Provider
itself. Unless anyone raises valid concerns in the interim, the next tagged release will contain this change.
/cc @ascii-soup
The documentation for delegates says:
NOTE: Auryn\Provider will pass the class name to be instantiated to the specified callable as its lone argument. This doesn't seem to be working.
Test is as follows.
class DelegationInvokeTestFactory {
function __invoke($class){
$numberOfArgs = func_num_args();
if ($numberOfArgs == 0) {
throw new \Exception("Error in delegation, __invoke is being called with no parameters, so factory can't see class requested.");
}
if ($class === null) {
throw new \Exception("Error in delegation, __invoke called with null parameter instead of class name.");
}
return new DelegationInvokeTest();
}
}
function testInvokeDelegationParameters() {
$injector = new Auryn\Provider(new Auryn\ReflectionPool);
$injector->delegate('DelegationInvokeTest', 'DelegationInvokeTestFactory');
$testObject = $injector->make('DelegationInvokeTest');
}
No exception
'Error in delegation, __invoke called with null parameter instead of class name.'
Currently you can only define scalar values for injection on a per class basis. That is fine until you need to start defining the scalar values for dependencies, where:
i) That may require many 'defines' to be setup for each class that needs that define e.g. if many classes need to have the same value set for a $tmpDirectory parameter.
ii) You have to setup the values for every possible class that may require that scalar value, unless you can know which dependencies can be created.
So, should it be possible to call $provider->defineParam('thumbnailSize', 128);
and then whenever a parameter called 'thumbnailSize' needs to be injected, and is not otherwise defined, used the defined value.
class DBConnection {
function __construct($username, $password, $host, $port, $socket) {...}
}
this is better:
abstract class DBConfig { }
class DevDBConfig extends DBConfig {
function __construct($username, $password, $host, $port, $socket) { ...}
}
class DBConnection {
function __construct(DBConfig $DBConfig){...}
}
$provider->alias('DBConfig', 'DevDBConfig');
However that is annoying to do when it's a single scalar value that is required.
btw I don't think this should apply to type-hinted variables that lack an alias or shared implementation - it should only be for un-typehinted values to limit the 'surprise' factor.
Is at - https://github.com/Danack/Auryn/compare/typelessDefines?expand=1
It's pretty trivial, and would have been less code, except 'buildArgumentFromTypeHint' already had some stuff not related to TypeHinting in it which I've moved out.
When you use something like this inside a class
use Monolog\Logger as Monolog;
class Something{
public function __construct(Monolog $logger){}
}
How does Auryn inject the Monolog instance? Does it instantiate Monolog\Logger? Or can I delegate the instantiation of "Monolog"?
Therefore should I be using "define" parameters with "Monolog\Logger" or "Monolog"?
$injector->delegate('Monolog\Logger')
//or
$injector->delegate('Monolog')
It would be ideal if we could register post-instantiation callbacks based on interface or trait name. If my understanding is correct, the current methodology is essentially to fix interfaces to implementations, but one always needs a concrete implementation.
Could we get an API addition to add something similar to delegate... but for the interfaces/traits?
I can think of one really good example of this where I'd have a shared EventManager and would like any objects that use the emitter trait to receive the emitter based on a trait method (not their constructor) like:
class EmittableObject {
use EmitterTrait;
...
}
Then when I instantiate those badboys, something like:
$injector->prepare('EmitterTrait', function($object, $injector) {
$object->addEventManager($injector->make('EventManager'));
});
Does this make sense?
Or even using execute internally without having the explicitly make it.
I'm not sure if aliasing a concrete class to another concrete class is meant to work or not:
class ConcreteClass1{}
class ConcreteClass2{}
function testAliasingConcreteClasses(){
$provider = new Auryn\Provider();
$provider->alias('ConcreteClass1', 'ConcreteClass2');
$class = $provider->make('ConcreteClass1');
$this->assertEquals('ConcreteClass2', get_class($class));
}
Expected :ConcreteClass2
Actual :ConcreteClass1
Docs describe it is possible to define an injection by specifingy a pre-existing instance instead of the string class name:
$injector->define('MyClass', [':dependency' => $dependencyInstance]);
However, make
only allows to override definition by using a different class name and not a given instance:
$myObj = $injector->make('MyClass', ['dependency' => 'SomeImplementation']);
If I try to pass it an instance it throws:
Warning: strtolower() expects parameter 1 to be string, object given in /xxxxx/vendor/rdlowrey/auryn/lib/Provider.php on line 752
Is this allowed or I'm just doing it wrong?
In some IDE's (in my case, Netbeans), after downloading Auryn via compser, my IDE shows errors for a file - specifically with config_with_unparsable.json.
I can't specifically choose to ignore this file, so it's constantly throwing up a nasty red exclamation mark on my file.
Any chance of an alternative test to check for invalid json? Perhaps creating a file in memory using SplTempFileObject or similar?
Note: This is of course an extremely tiny issue - so at the bottom of the to-do list, if at all.
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.