GithubHelp home page GithubHelp logo

auryn's People

Contributors

4d47 avatar alexssssss avatar aliselcuk avatar ascii-soup avatar bwoebi avatar danack avatar furgas avatar garrettw avatar gforsythe-godaddy avatar gigablah avatar hajo-p avatar j7mbo avatar jeichorn avatar kelunik avatar lencse avatar lt avatar m6w6 avatar morrisonlevi avatar orthographic-pedant avatar peter279k avatar rdlowrey avatar sam159 avatar shadowhand avatar staabm avatar stanangeloff avatar szepeviktor avatar vlakarados avatar zvax avatar

Stargazers

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

Watchers

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

auryn's Issues

Support text config files

Support the specification of dependencies in a text configuration file.

For example:

OLD

<?php
$injector = new Auryn\Injector;
$injector->define('PhpAmqpLib\Connection\AMQPStreamConnection', ['localhost', 5672, 'guest', 'guest']);
$obj = $injector->make('ClassDependingOnAMQPStreamConnection');

NEW

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');

Allow for one-stop text file and/or PHP array configuration

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.

Argument definitions are not passed to delegated factories

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

Automatic provisioning for delegates

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.

Reusing the injector inside delegated instantiators

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.

getDeclaringClass() not safe to use

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.

Simplifying raw parameter injection

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.

Using a factory that creates objects with different constructor parameter typehints

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:

  1. Here's a factory capable of creating objects from the ActionInterface (abstract class = "Action")
  2. I am now defining the Action to be created will be of type Notification
  3. When I call ActionFactory::build($type), please instantiate the right object with all it's relevant dependencies

Namespace aliases

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')

Aliasing concrete class

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

Edge case for sharing and alias.

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.

Configuration writer for production

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).

Ability to define different aliases for an interface?

@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.

Unexpected InjectionException

I 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:

  • Creating a new silex application
  • Registering a service provider in it
  • Retrieving the registered service provider and sharing it with Auryn
  • Running 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 :)

Injecting dynamic dependency for dependency.

@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?

Dependency with default is never instantiated

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.

Injection definition required for non-concrete parameter $interface of type Http\Request

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.

lifecycle support

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?

generateExecutableReflection needs to give more information

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.

Specifying existing instances on the fly

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?

New versions?

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?

Sharing aliased interfaces by class name fails.

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

Change / Remove unparsable json file

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.

Post Instantiation Callbacks

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.

Soft / optional dependencies

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?

buildNonConcreteImplementation is unreachable

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

ProviderBuilder -> InjectorBuilder name change

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

Add ability to define parameters by name for all classes.

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.

Reasons For

  • It's useful - I'm currently finding myself having to define things that should be a simple scalar as a full on class where the scalar value is just wrapped.
  • It will probably help people who are migrating to Auryn.

Reasons Against

  • It can probably be abused.
  • I'm not sure the names I've used for the implementation are very good, which usually implies that the idea is not very good.
  • It's probably best practice to wrap config settings into classes for all but the most simple ones e.g. this is bad:
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.

  • It will lead to surprising bugs when you rename parameter names.

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.

Example implementation

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.

Cyclic dependency exception handling.

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:

  • Provider::make() calls makeInternal() so that there is a single entry point.
  • On each call to makeInternal() the class name that is being constructed gets pushed onto the array chainClassConstructors.
  • No InjectionExceptions are caught within the Auryn code.
  • When InjectionException is thrown it can take the chainClassConstructors as a parameter and add that to the exception message.

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

Instantiating type-hinted parameters with different names

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.

api docs and code quality

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 :)

PDO optional argument $options must be provisioned

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

`

Provision function delegates with dependencies

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')
            );
        });
    });

buildArgumentFromTypeHint defaults to null rather than exception.

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

Any reason I cannot use :password when defining PDO?

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?

Use default param if available for non-concrete parameters

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.

Auto-wiring dependencies for setter injection

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?

Delegated factory not being passed class name parameter.

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');
}

Expected behaviour

No exception

Actual behaviour

'Error in delegation, __invoke called with null parameter instead of class name.'

Custom definitions not consistent with other defines.

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.

Implementing container-interop interface

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.

"Injection definition/implementation required for non-concrete parameter" exception is a liar

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.

Executing aliased classes, chooses wrong class

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);
}

Make not following inheritance chain to the delegated interface of abstract extended by concrete

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

Examples showing non-existent class

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?

Pass delegates call context information to allow dynamic instantiation in different scenarios

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.

Executing aliased class executes wrong class

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

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.