phpdependency-injectioninversion-of-controlphpstormpimple

PHPStorm Auto-complete Array Keys (dynamically inserted)


I'm using Pimple dependency injector, and every time I use a dependency from the container, I can't help but to double check the spelling of the key used to get the dependency:

$ioc = new Pimple();

// 1. Define some object
$ioc["some-key"] = $ioc->share(function($c){ /* ... */});

// 2. Use it
$ioc["som... // Open config file and check spelling...

Does PHPStorm have some way of looking up those properties and providing auto-completion? I have considered defining all those keys using something like

define('SOME_KEY', 'some-key');

// ...

$ioc[SOME_KEY] = $ioc->share(/* ... */);

but I wonder if there's a better way.

Edit

Here's some sample code:

// project_root/library/App/Injector/Ioc.php
require_once "Pimple.php";

/** @var array|Pimple $ioc */
$ioc = new Pimple();

$ioc["version"] = "1.0.1650.63";

$ioc["location-service"] = $ioc->share(function ($c) {
     return new Application_Service_Location();
   }
);

It turns out that string auto-completion works fine whether or not I include /** @var array|Pimple $ioc */ before the $ioc declaration in the same file as $ioc is declared. However, since I'm using Zend Framework, I'm usually using $ioc thusly:

// project_root/Application/Bootstrap.php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap {
   protected function _initInjector() {
     $ioc = null;
     require_once LIBRARY_PATH . "/MFM/Injector/ioc.php";
     Zend_Registry::set("ioc", $ioc);
   }
}

// project_root/Application/Controllers/SomeController.php
class Application_Controller_SomeController extends Zend_Controller_Action {
   public function IndexAction() {
      /** @var Pimple $ioc */
      $ioc = Zend_Registry::get("ioc");

      // No IDE assistance for the string "location-service"
      $service = $ioc["location-service"];
   }
}

Solution

  • I currently use the DynamicReturnTypePlugin for PhpStorm and have the following config of it in my dynamicReturnTypeMeta.json:

    {
        "methodCalls": [
            {
                "class": "\\MyOwnWrapperOfDIContainer",
                "method": "get",
                "position": 0
            },
            {
                "class": "\\Pimple\\Container",
                "method": "offsetGet",
                "position": 0
            }
        ]
    }
    

    This configurations means the following: whenever in code I'm using any variable of type Pimple\Container as an array (this will call its ArrayAccess::offsetGet method), then PhpStorm should consider the return value is of type that was used as a key when accessing that array. i. e.:

    $c = new Pimple\Container;
    $c['MyClass']->/*here autocompletion works*/methodOfMyClass();
    

    I also use it in that way:

    $myClassInstance = MyOwnWrapperOfDIContainer::get(MyClass::class);
    $myClassInstance->methodOfMyClass(); // autocompletion works here
    

    The only problem is when you register some dependencies in Pimple container not by their class name, but using other names which you want. E. g., autocompletion won't work in the following case:

    $c = new Pimple\Container;
    $c['my-favourite-var'] = new MyClass(1);
    $c[MyClass::class] = new MyClass(2);
    $c['my-favourite-var']->/*here autocompletion doesn't work*/methodOfMyClass();
    $c['MyClass']->/*here autocompletion works*/methodOfMyClass();
    

    Still you can workaround it like this:

    class MyFavouriteVar extends MyClass;
    $c[MyFavouriteVar::class] = new MyFavouriteVar(2);
    // or even like this:
    $c[MyFavouriteVar::class] = new MyClass(2);
    $c[MyFavouriteVar::class]->/*now autocompletion works fine*/methodOfMyClass();
    

    Or you have to find some other solution of your problem...

    edit 1

    also consider this article: http://confluence.jetbrains.com/display/PhpStorm/PhpStorm+Advanced+Metadata

    edit 2

    and this plugin https://github.com/Sorien/silex-idea-plugin

    edit 3

    Also it's possible to workaround the problem mentioned above like this:

    class MyPimple extends Pimple\Container
    {
        public function get($type, $desc = null) {
            return $this[$type . (isset($desc) ? ':' . $desc : '')];
        }
    }
    $c = new MyPimple;
    $c[MyClass::class] = new MyClass('default');
    $c[MyClass::class . ':' . 'my-special-value'] = new MyClass('special');
    $c->get(MyClass::class, 'my-special-value')->/*autocompletion should work*/methodOfMyClass();
    

    The dynamicReturnTypeMeta.json should then contain:

    {
        "methodCalls": [
            {
                "class": "\\MyPimple",
                "method": "get",
                "position": 0
            }
        ]
    }