advanced reports dependency injection development environment frontend editing git javascript meetup php pixlr porthole purify queuedjobs rage silverstripe tidy ubuntu webservices wiki
I've been playing around adding some dependency injection to SilverStripe off and on recently. One of the problems is where to actually put the configuration you need, and how to ensure the loading of the services occurs in the expected order.
The default mechanism in SilverStripe is to put things in the _config.php file for a given module. Rather than come up with something new, I decided to stick with the same methodology. However, that presents a new issue - if you specify a service configuration there that is meant to override a service configuration defined in another module, the other module might actually load AFTER yours, meaning it will take that specification. One way to enforce a module to appear first is to provide a 'priority' alongside the service spec.
$config = array(
array(
'src' => TEST_SERVICES.'/SampleService.php',
'priority' => 10,
)
);
$injector->load($config);
$config = array(
array(
'src' => TEST_SERVICES.'/AnotherService.php',
'id' => 'SampleService',
'priority' => 1,
)
);
$injector->load($config);
In this example, previously the injector would have used the AnotherService class whenever SampleService was referred to. However, because we've explicitly stated that a priority of > 10 is needed to replace it, it will NOT have its definition changed.
I've been toying with a simple dependency injector for the last few days, and have started to think about ways in which it (or another dependency injector) could benefit SilverStripe. Some ideas I've been toying with
Along the lines of the first point, I put some code together to see how it works
// injector configuration
Injector::inst(array(
array(
'class' => 'RequestProcessor',
'properties' => array(
'preFilters' => array(
'#$AuthenticationFilter',
'#$AuthorisationFilter',
)
),
),
'AuthenticationFilter',
'AuthorisationFilter',
array(
'class' => 'AuthenticationService',
'properties' => array(
'authenticators' => array(
'#$DbAuthenticationProvider',
)
),
),
'DbAuthenticationProvider',
);
And then adding a hook in before the Director dispatches the request
Injector::inst()->get('RequestProcessor')->preFilter($req, $session); // $session required to get around a SS quirk
The 'grunt' work comes in the AuthneticationFilter
class AuthenticationFilter implements RequestFilter {
/**
* Automatically injected based on convention
*/
public $authenticationService;
public function preRequest(SS_HTTPRequest $request, Session $session) {
// lets try authenticating
if (isset($_REQUEST['auth']) || isset($_REQUEST['action_dologin'])) {
$email = $request->postVar('Email');
$pass = $request->postVar('Password');
$member = $this->authenticationService->authenticate($email, $pass);
if ($member) {
$member->logIn($request->postVar('Remember'));
// dirty hack for now...
$session->inst_set('loggedInAs', $member->ID);
}
// because we have the request here, we can do a bunch of redirects or
// whatever we like - but this is just a poc for now so we won't bother with
// anything else.
}
}
public function postRequest(SS_HTTPRequest $request, SS_HTTPResponse $response) {
}
}
So the actual 'authenticate' work is done via an authentication service, which in turn has registered within it a bunch of AuthenticationProvider implementors - in this case I only have one (DbAuthenticationProvider) which simply calls MemberAuthenticator::authenticate(). I could quite easily add on any number of other providers (LDAP, OpenID, etc etc) in a chain, and let the user login via the first that succeeds. Makes for an elegant way to have a single login form.
The next step is to add some logic to the AuthorisationFilter to prevent requests to certain URLs continuing if the user is not authorized (I'm looking at you /dev requests). This stops unauthorised requests very early in the request cycle, and help centralise the logic for this for a single maintainable area for developers to refer to. But that's for another day...