How to Make My PHP Ide Understand Dependency Injection Containers

How do I make my PHP IDE understand Dependency Injection Containers?

You can define class of the variable 'manually':

/** @var YourClassType $mailer */
$mailer = $container['mailer'];

In PhpStorm (and by standards), use two asterisks and write the data type before the name of the variable.

You can write the data type without the name of the variable (but not the name without the data type).

Dependency Injection Container PHP

You may be better off using one of the existing Dependency Containers out there, such as PHP-DI or Pimple. However, if you are looking for a simpler solution, then I've implemented a Dependency Container as part of an article that I wrote here: http://software-architecture-php.blogspot.com/

Here is the code for the container

    class Container implements \DecoupledApp\Interfaces\Container\ContainerInterface 
{
/**
* This function resolves the constructor arguments and creates an object
* @param string $dataType
* @return mixed An object
*/
private function createObject($dataType)
{
if(!class_exists($dataType)) {
throw new \Exception("$dataType class does not exist");
}
$reflectionClass = new \ReflectionClass($dataType);
$constructor = $reflectionClass->getConstructor();
$args = null;
$obj = null;
if($constructor !== null)
{
$block = new \phpDocumentor\Reflection\DocBlock($constructor);

$tags = $block->getTagsByName("param");
if(count($tags) > 0)
{
$args = array();
}
foreach($tags as $tag)
{
//resolve constructor parameters
$args[] = $this->resolve($tag->getType());
}
}
if($args !== null)
{
$obj = $reflectionClass->newInstanceArgs($args);
}
else
{
$obj = $reflectionClass->newInstanceArgs();
}

return $obj;
}

/**
* Resolves the properities that have a type that is registered with the Container.
* @param mixed $obj
*/
private function resolveProperties(&$obj)
{
$reflectionClass = new \ReflectionClass(get_class($obj));
$props = $reflectionClass->getProperties();
foreach($props as $prop)
{
$block = new \phpDocumentor\Reflection\DocBlock($prop);

//This assumes that there is only one "var" tag.
//If there are more than one, then only the first one will be considered.
$tags = $block->getTagsByName("var");
if(isset($tags[0]))
{
$value = $this->resolve($tags[0]->getType());

if($value !== null)
{
if($prop->isPublic()) {
$prop->setValue($obj, $value);
} else {
$setter = "set".ucfirst($prop->name);
if($reflectionClass->hasMethod($setter)) {
$rmeth = $reflectionClass->getMethod($setter);
if($rmeth->isPublic()){
$rmeth->invoke($obj, $value);
}
}
}
}
}
}
}

/**
*
* @param string $dataType
* @return object|NULL If the $dataType is registered, the this function creates the corresponding object and returns it;
* otherwise, this function returns null
*/
public function resolve($dataType)
{
$dataType = trim($dataType, "\\");
$obj = null;
if(isset($this->singletonRegistry[$dataType]))
{
//TODO: check if the class exists
$className = $this->singletonRegistry[$dataType];
$obj = $className::getInstance();
}
else if(isset($this->closureRegistry[$dataType]))
{
$obj = $this->closureRegistry[$dataType]();
}
else if(isset($this->typeRegistry[$dataType]))
{
$obj = $this->createObject($this->typeRegistry[$dataType]);
}

if($obj !== null)
{
//Now we need to resolve the object properties
$this->resolveProperties($obj);
}
return $obj;
}

/**
* @see \DecoupledApp\Interfaces\Container\ContainerInterface::make()
*/
public function make($dataType)
{
$obj = $this->createObject($dataType);
$this->resolveProperties($obj);
return $obj;
}

/**
*
* @param Array $singletonRegistry
* @param Array $typeRegistry
* @param Array $closureRegistry
*/
public function __construct($singletonRegistry, $typeRegistry, $closureRegistry)
{
$this->singletonRegistry = $singletonRegistry;
$this->typeRegistry = $typeRegistry;
$this->closureRegistry = $closureRegistry;
}

/**
* An array that stores the mappings of an interface to a concrete singleton class.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* @var Array
*/
private $singletonRegistry;

/**
* An array that stores the mappings of an interface to a concrete class.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* @var Array
*/
private $typeRegistry;

/**
* An array that stores the mappings of an interface to a closure that is used to create and return the concrete object.
* The key/value pair corresond to the interface name/class name pair.
* The interface and class names are all fully qualified (i.e., include the namespaces).
* @var Array
*/
private $closureRegistry;

}

The above code can be found here: https://github.com/abdulla16/decoupled-app (under the /Container folder)

You can register your dependencies as a singleton, as a type (every time a new object will be instantiated), or as a closure (the container will call the function that you register and that function is expected to return the instance).

For example,

$singletonRegistry = array();
$singletonRegistry["DecoupledApp\\Interfaces\\UnitOfWork\\UnitOfWorkInterface"] =
"\\DecoupledApp\\UnitOfWork\\UnitOfWork";

$typeRegistry = array();
$typeRegistry["DecoupledApp\\Interfaces\\DataModel\\Entities\\UserInterface"] =
"\\DecoupledApp\\DataModel\\Entities\\User";

$closureRegistry = array();
$closureRegistry["DecoupledApp\\Interfaces\\DataModel\\Repositories\\UserRepositoryInterface"] =
function() {
global $entityManager;
return $entityManager->getRepository("\\DecoupledApp\\DataModel\\Entities\\User");
};

$container = new \DecoupledApp\Container\Container($singletonRegistry, $typeRegistry, $closureRegistry);

This Container resolves properties of a class as well as the constructor parameters.

When can Dependency Injection Containers become too big, and what can I do about it?

I'd like to answer this myself now.

Dependency Injection Containers do not contain every object in your application. The fact that they are labelled containers is the main problem here. "DiC's" like Symfony's Pimple are not dependency injectors. They are glorified associative arrays used to hold objects and primarily advocate the Service Locator anti-pattern.

See $this->get('something') in your code, like in Symfony? That's a service locator. Use that and you are hiding class dependencies and making a liar of your object API. This includes your controllers!

Object requirements should be readable from the constructor or method signatures of an object only, and a provided to this object via Dependency Injection providing Inversion of Control through your codebase helping testability, maintainability and flexibility with greatly simplified polymorphism being made available through it.

Dependency injection containers should be renamed to injectors, because that is what they should be doing - injecting dependencies for you. You should not be pulling objects out of them when you require. How are you going to use DI and inversion of control if you just pull them out when you need them using a service locator?

In real life you wouldn't build a house by transporting the entire hardware store (hopefully) to
the construction site so you can access any parts you need. Instead, the foreman (__construct())
asks for the specific parts that will be needed (Door and Window) and goes about procuring them.
Your objects should function in the same way; they should ask only for the specific dependencies
required to do their jobs. Giving the House access to the entire hardware store is at best poor
OOP style and at worst a maintainability nightmare. rdlowrey - auryn

You could use reflection to read object requirements then instantiate and inject dependencies automatically. Auryn is a fantastic injector for this. You set it up in your bootstrap and controller resolver and then you can write SOLID code for conrete auto-injection based on object typehints throughout your codebase. Any abstract classes / interfaces? No problem, you alias concretes to these either directly in-code or by reading them from a config file (preferable) and aliasing them in a loop that way. Using a configuration file means you can have config-based polymorphism at your disposal - the ability to switch out one concrete implementation with another simply by creating the new object and changing one string in a config file is fantastic.

In conclusion, Dependency Injection Containers never become "too big" unless you're doing it wrong and using them as a service locator or a glorified associative array of objects! You don't shove all your objects in there in order to pull them out when you need them. I don't care if they're lazy-loaded. Use an actual injector. And don't even mention Laravel or "facades".

Can I use Dependency Injection for All Dependencies?

Well, yes.

The whole point of dependency injection is abstraction, give the function/method/object/class what it needs, and have it work it out inside of it. A factory for generating objects and variables to pass for DI is fine.

For instance:

public function pdo_select(PDO $pdo_connection, $table, $where_condition)

Here, we pass in everything we need, the connection object, the table name and the WHERE MySQL clause. We can further abstract this function by doing:

public function pdo_query(PDO $pdo_connection, $query)

Now, we aren't restricting our function to just selecting, we can do all sorts of queries.

But you see that you definitely can pass objects to different functions to work with.

Passing objects also gives you the nice touch of having type hinting, which would throw an error if a different kind of parameter is entered, helping you to debug.

How do I best use an object factory inside another class using Pimple for Dependency Injection?

So, in your DI container setup, you can pass around container references. So if you have the need to use the Block class a lot in your page class, you could do this:

$container = new Container();

$container['page'] = $container->factory(function ($c) {
return new Page( $c['block'] );
});

$container['block'] = $container->factory(function ($c) {
return new Block();
});

But this means you must add a parameter to your Page class's constructor:

class Page {

private $block = NULL;

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

private function newBlock() {
//do something here to get $filepath
$this->block->setFilePath($filepath);
return $this->block;
}

}

So, we're not actually passing the entire container into each class, but rather allowing the DI container to pass objects as needed.

Using dependency injection in a PHP web application

Without looking at specific implementations, it's hard to give specific advice, so instead I'll provide the general advice that I can (I'm not very good, actually). It sounds like you could use a "dependency injection container" (google it .. find one you like or roll your own for your purposes) that manages the large interlocking dependencies of classes.

One thing you should keep in mind is that your code should gently fall around the design, which will hold it up very strongly. You shouldn't have to work hard to get your code to follow the design you've created. If what you are trying to do isn't working according to your understanding of the design, it's probably time for a rewrite.

You also only use examples of constructor injection. I'm sure you are aware that it's possible to use other kinds of injection such as setter injection when appropriate.

As I said, I haven't seen your code, but the problem with your design is that it's not the Page that should know about what widgets it needs, it's the service. If you are tying specific widget implementations to a page, this violates dependency inversion (and makes dependency injection harder). Instead your code should be more along the lines of:

interface Widget {
function widgetize();
}

class ConcretePage {
private $widgets = array();

public function addWidget(Widget $widget) {
$this->widgets[] = $widget;
}

public function run() {
foreach ($this->widgets as $widget) {
$widget->widgetize();
}
}
}

The service will create a page and add the widgets it requires. The page shouldn't care about those widgets. You may also have to implement a bridge between the page and the widgets. The service can handle that too.

As the very article writer you mention states here, you should have code that is devoted to building your entire object graph (this is where all of your new operators go). Imagine how difficult it would be to test your Page that expects five specific widgets without the class definitions of those widgets! You need to depend on abstractions.

This might not be in the spirit of DI, but a simpler solution would be to have a WidgetFactory:

class Page {
private $wf;
private $widgets = array('whiz', 'bang');
public function __construct(WidgetFactory $wf) {
$this->wf = $wf;
}
public function widgetize() {
foreach ($this->widgets as $widget) {
$widget = $this->wf::build($widget);
}
}
}

The WidgetFactory interface can be separate from application to application and even in testing. This still gives you some level of abstraction for the widgets Page expects.

PHP Lazy loading with Pimple Dependency Injection Container?

In most cases this is not a problem. If initializing your dependencies become an actual performance problem, you should either split your service into two separate services, or, create a proxy that lazyloads the dependency on the first call.

There is a library for PHP that provides automatic proxy generation, called ProxyManager. Without knowing your requirements, my first guess is that it's probably overkill for you. Don't worry about this until you are sure that there is an actual performance bottleneck that you can solve this way.

Is it good practice to have DI container replace a global $registry object?

Q2 :
If you were passing around all over your $registry object.... then your Registry was not really what is called a Registry (as Fowler described it).

A Registry is more or less a global object (a "well-known") with get/set methods.
In PHP, two common prototypes for the implementations of a Registry would be

As a singleton

class RegistryAsSingleton
{
public static function getInstance (){
//the singleton part
}

public function getStuff ()
{
//some stuff accessed thanks to the registry
}
}

With static methods all over the place

class RegistryAsStatic
{
public static function getStuff()
{
}
}

Passing your Registry all over the place makes it, well, just an object: a container with no greater purpose than providing references to other objects.

Your DI container (using Pimple as you suggested in your OP) is kind of a Registry itself: It IS well known and enables you to get components from anywhere.

So yes, we can say that your DI container will remove the requirement and necessity of a registry by performing the same functionality.

BUT (there's always a but)

Registry are always guilty until
proven innocent
(Martin Fowler)

If you're using your DI Container to replace your Registry, this is probably wrong.

eg:

//probably a Wrong usage of Registry
class NeedsRegistry
{
public function asAParameter(Registry $pRegistry)
{
//Wrong dependency on registry where dependency is on Connection
$ct = $pRegistry->getConnection();
}

public function asDirectAccess ()
{
//same mistake, more obvious as we can't use another component
$ct = Registry::getInstance()->getConnection();
}
}

//probably a wrong replacement for Registry using DI Container
class NeedsContainer
{
public function asAParameter(Container $pRegistry)
{
//We are dependent to the container with no needs,
//this code should be dependent on Connection
$ct = $pContainer->getConnection();
}

public function asDirectAccess ()
{
//should not be dependent on container
$ct = Container::getInstance()->getConnection();
}
}

Why is this bad? Because your code is not less dependent than before, it still depends on a component (either a registry or a container) which does not provides a clear goal (we may think of interface here)

The Registry-pattern be useful in some cases because it's a simple and fairly inexpensive way to define components or data (e.g. global configuration).

A way to refactor the above example without relying on DI by removing the dependency would be:

class WasNeedingARegistry
{
public function asAParameter (Connection $pConnection)
{
$pConnection->doStuff();//The real dependency here, we don't care for
//a global registry
}
}

//the client code would be like
$wasNeedingARegistry = new WasNeedingARegistry();
$wasNeedingARegistry->setConnection($connection);

Of course, this may not be possible if the client code isn't aware of the connection, which is probably the reason why you probably ended using a Registry in the first place.

Now DI comes into play

Using DI makes our life better because it will handle the dependency and enables us to access the dependency in a ready-to-use state.

Somewhere in your code, you'll configure your components:

$container['connection'] = function ($container) {
return new Connection('configuration');
};
$container['neededARegistry'] = function ($container) {
$neededARegistry = new NeededARegistry();
$neededARegistry->setConnection($container['connection']);
return $neededARegistry;
};

Now you have everything you need to refactor your code:

// probably a better design pattern for using a Registry 
class NeededARegistry
{
public function setConnection(Connection $pConnection)
{
$this->connection = $pConnection;
return $this;
}

public function previouslyAsDirectAccess ()
{
$this->connection->doStuff();
}
}

//and the client code just needs to know about the DI container
$container['neededARegistry']->previouslyAsDirectAccess();

The "client" code should be as isolated as possible. The client should be responsible for and inject its own dependencies (via set- methods). The client should not be responsible for handling its dependencies's dependencies.

class WrongClientCode
{
private $connection;
public function setConnection(Connection $pConnection)
{
$this->connection = $pConnection;
}

public function callService ()
{
//for the demo we use a factory here
ServiceFactory::create('SomeId')
->setConnection($this->connection)
->call();
//here, connection was propagated on the solely
// purpose of being passed to the Service
}
}

class GoodClientCode
{
private $service;
public function setService(Service $pService)
{
//the only dependency is on Service, no more connection
$this->service = $pService;
}

public function callService ()
{
$this->service->setConnection($this->connection)
->call();
}
}

The DI container will configure GoodClientCode with the Service that has already been properly configured with its Connection

As for the Singleton aspect, yes, it will enable you to get rid of them.
Hope this helps



Related Topics



Leave a reply



Submit