Skip to content

Instantly share code, notes, and snippets.

@AmyStephen
Last active December 18, 2015 00:38
Show Gist options
  • Save AmyStephen/5697696 to your computer and use it in GitHub Desktop.
Save AmyStephen/5697696 to your computer and use it in GitHub Desktop.
Are there better approaches for accessing the container to request the creation of a dependent class when creating that class?
<?php
//Level 1
class Container implements ContainerInterface
{
public function getService($service, $options = array())
{
// returns class instance if it has been so configured and is available
// creates new instance uses an adapter pattern
}
// also available: getServices, setService, cloneService, removeService...
}
// Level 2 - Adapter - container drives the class construction process via an adapter, interface:
interface InjectorInterface
{
public function get($key, $default = null);
public function set($key, $value = null);
public function setDependencies();
public function instantiateService($create_static = false);
public function performAfterInstantiationLogic();
public function getServiceInstance();
public function destructService();
}
// Level 3 - Handler - implements the Interface above providing
// 1 - "standard" (automatic convention and reflection approach for simple class construction)
// 2 - per-class custom DI injectors (where gathering dependencies is more complex/custom)
@AmyStephen
Copy link
Author

Assumptions/goals:

  1. One entry point to the Container, via the front controller.
  2. All three levels are the IoCC, not just the first class. (Valid? or, Good Try, Amy ;-))
  3. Never inject the Container into application classes outside of the above structure (Or, you'll have a Service Locator and go to developer hell).

Challenge

Using the container to create dependent classes discovered within the Handler so that those classes can be constructed using the container and the instance used to satisfy the class construction.

Approach

Created a Closure to the getService method that is used within the Handlers to request construction of dependent classes in order to inject that instance into the handler's subject class via the constructor.

Questions

I now understand Closures are statics and that this approach is injecting the Container into the Adapter/Handler layer.

Are the handlers really part of the container? Or, have I created a Service Locator approach? (Even though I do not use the closure outside of the Handlers.)

What other methods could be used that would not create a SL - but enable class construction of dependent classes via the container -- using only one entry point to the container -- without using statics?

@AmyStephen
Copy link
Author

@Crell
Copy link

Crell commented Jun 3, 2013

I'd generally agree with Anthony on this one. Container Aware object => it's using the container as a Service Locator. That's OK as long as all it's really doing with the Service Locator is applying runtime logic for what to pull out of the SL. That is, factories in some form, which at that point are just runtime extensions of the container.

Think of it like this: Anything that is ContainerAware is too hard to unit test to bother unit testing. So only put code in ContainerAware objects that isn't worth unit testing. (Note: Yes, such code absolutely exists.)

@AmyStephen
Copy link
Author

Ok @Crell - thanks for that, that gives me something to think on. All the adapter/handler pairs do is create the class. The only "custom" code relates to gathering dependency data and storing it in an area where it can be automatically injected by the abstract class method into the class via the constructor.

In the container itself, I create this Closure and then pass it into the Adapter.

$connect = $this;
$this->getService    = function ($service, $options = array()) use ($connect) {
    return $connect->getService($service, $options);
};

The adapter uses it to request another Service from the IoCC (1st 2 lines.) That it uses to build the dependencies.

In this case, it's getting configuration data from the Application object for email options.

$getService = $this->getService;
$application = $getService('Application');

$options = array();
$options['mailer_transport'] = $application->get('mailer_transport');
$options['site_name'] = $application->get('application_name');
$options['field_handler'] = $getService('FieldHandler');

In this example, it's preparing the MVC structure for processing data, which means it needs an instance of the User object, the Field Handler, the Event Scheduler, etc.

$ifexists             = array();
$options                   = array();
$ifexists['if_exists'] = true;
$options['user']           = $getService('User', $ifexists);
$options['language']       = $getService('Language', $ifexists);
$options['permissions']    = $getService('Permissions', $ifexists);
$options['field_handler']  = $getService('FieldHandler');
$options['model']          = $model_instance;
$options['model_registry'] = $model_registry;
$options['parameters']     = $parameters;
$options['event']          = $getService('Dispatcher', $options);

The approach has had a good effect in that it has separated out construction code from the application.

I'm trying to keep that code very simple, which speaks to your comment on testing, although I wouldn't want to avoid testing here, it's key processing.

I just can't think of any better way to isolate the class creation (one entry point hooked to the front controller) without using some kind of static (to keep the door open, as it were), but that essentially means you are passing that container into a class, which is what I had been trying to avoid.

I never use the $getService closure outside of the Adapter/Handler pairs. I might even add some code to check that entry comes from a class of that type to discourage broader use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment