CiviCRM Community Forums (archive)

*

News:

Have a question about CiviCRM?
Get it answered quickly at the new
CiviCRM Stack Exchange Q+A site

This forum was archived on 25 November 2017. Learn more.
How to get involved.
What to do if you think you've found a bug.



  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion »
  • 5.0 Saloon »
  • Dependency injection and autowiring
Pages: [1]

Author Topic: Dependency injection and autowiring  (Read 1305 times)

totten

  • Administrator
  • Ask me questions
  • *****
  • Posts: 695
  • Karma: 64
Dependency injection and autowiring
March 03, 2014, 04:20:37 pm
In the past few years, the "dependency injection" pattern ( http://en.wikipedia.org/wiki/Dependency_injection ) has caught on in PHP. I think one can fairly summarize DI as the difference between these two snippets:

Code: [Select]
<?php
// FILE: Example/NewsWidget.php
namespace Example;
class 
NewsWidget {
  function 
render() {
    
$http = new HttpClient();
    
$response = $http->get('http://example.com/news.json');
    if (
$response->isOK()) {
      
$templateEngine = new SmartyTemplateEngine();
      echo 
$templateEngine->render('news', json_decode($response->getContent()));
    }
  }
}

// FILE: Example/HttpClient.php
namespace Example;
class 
HttpClient {
  function 
get($url) {
    
$resp = new Response(file_get_contents($url));
    
// ...check for errors; add to $resp ...
    
return $resp;
  }
}

and

Code: [Select]
<?php
// FILE: Example/NewsWidget.php
namespace Example;
class 
NewsWidget {
  protected 
$http;
  protected 
$templateEngine;

  function 
__construct(HttpInterface $http, TemplateInterface $templateEngine) {
    
$this->http = $http;
    
$this->templateEngine = $templateEngine;
  }

  function 
render() {
    
$response = $this->http->get('http://example.com/news.json');
    if (
$response->isOK()) {
      echo 
$this->templateEngine->render('news', json_decode($response->getContent()));
    }
  }
}

// FILE: Example/HttpClient.php
namespace Example;
class 
HttpClient implements HttpInterface {
  function 
get($url) {
    
$resp = new Response(file_get_contents($url));
    
// ...
    
return $resp;
  }
}

// FILE: services.yml
service:
  
httpClient:
    class: \
Example\HttpClient
  templateEngine
:
    class: \
Example\SmartyTemplateEngine
  newsWidget
:
    class: \
Example\NewsWidget
    arguments
: [ '@httpClient', '@templateEngine']

This has some trade-offs.
 + Pro: It's easier to swap and reconfigure the parts (like HttpClient and SmartyTemplateEngine) without changing NewsWidget. The new structure is great for testing with mock objects ( http://phpunit.de/manual/3.7/en/test-doubles.html ). For example, you can write test-cases with a mock HTTP client that returns HTTP 200 (with valid JSON), HTTP 200 (with malformed JSON), HTTP 404, etc (depending on the particular test-case). This is easier, faster, more portable than setting up a real HTTP server to simulate the various responses.
 + Con: We've added a new file and +12 lines of "wiring" code (which "wires together" the news widget, the http client, and the template-engine). That's a 50% increase -- more files, more SLOC, more boilerplate, more cross-referencing, less cohesion.

Of course, this changes a bit depending on which "dependency injection" implementation is used. Symfony's dependency-injection system gets it to this level -- its encourages flexible, testable code but requires more boilerplate. Other application frameworks use "auto-wiring" which makes the "services.yml" file optional. A configuration file can be used for advanced situations, but for typical situations one uses annotations instead:

Code: [Select]
<?php
// FILE: Example/NewsWidget.php
namespace Example;
class 
NewsWidget {
  
/**
   * @Inject("httpClient")
   */
  
protected $http;

  
/**
   * @Inject("templateEngine")
   */
  
protected $templateEngine;

  function 
render() {
    
$response = $this->http->get('http://example.com/news.json');
    if (
$response->isOK()) {
      echo 
$this->templateEngine->render('news', json_decode($response->getContent()));
    }
  }
}

// FILE: Example/HttpClient.php
namespace Example;

/**
 * @Service("httpClient")
 */
class HttpClient implements HttpInterface {
  function 
get($url) {
    
$resp = new Response(file_get_contents($url));
    
// ...
    
return $resp;
  }
}

The annotations specify how to "wire together" services in a normal situation. The @Service annotation tells the auto-wiring service to make an instance of \Example\HttpClient called "httpClient"; then the @Inject annotation tells the auto-wiring service to put a reference to "httpClient" in the NewsWidget::$http property. However, you don't have to use these defaults: for example, when writing a unit-test with a mock HTTP service, you can inject the mock HTTP service. So you get the same flexibility without fewer files and less boilerplate.

There appear to be some addons for Symfony which enable autowiring. I haven't used/tested them yet:
 - http://jmsyst.com/bundles/JMSDiExtraBundle/master/usage
 - https://github.com/kutny/autowiring-bundle

I think it would be great to introduce DI with autowiring to Civi 5.0.

jaapjansma

  • I post frequently
  • ***
  • Posts: 247
  • Karma: 9
    • CiviCoop
  • CiviCRM version: 4.4.2
  • CMS version: Drupal 7
  • MySQL version: 5
  • PHP version: 5.4
Re: Dependency injection and autowiring
March 11, 2014, 02:15:37 am
Well I would say as civi is  now, it consist of writing lots of duplicate code for a certain function. Also the power of civi is that is highly flexible.

Dependency injection is one of the tools to prevent writing a certain code bit twice.. So I would say we should opt for writing less code more at the same time staying flexible.
Developer at Edeveloper / CiviCoop

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Dependency injection and autowiring
March 12, 2014, 04:30:57 am
Hi,

Could extensions benefit from DI too?
Eg a mailchimp/sendgrid extension injecting itself as the mail delivery service or the bounce processing one?

relevant discussion on D8 about DI and services

https://drupal.org/node/2133171
-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

jaapjansma

  • I post frequently
  • ***
  • Posts: 247
  • Karma: 9
    • CiviCoop
  • CiviCRM version: 4.4.2
  • CMS version: Drupal 7
  • MySQL version: 5
  • PHP version: 5.4
Re: Dependency injection and autowiring
March 12, 2014, 08:26:26 am
Yes that is what dependency injection is for. Injecting a service container (e.g. for mail sending) or from another topic, injecting Doctrine as the entity manager
Developer at Edeveloper / CiviCoop

xavier

  • Forum Godess / God
  • I’m (like) Lobo ;)
  • *****
  • Posts: 4453
  • Karma: 161
    • Tech To The People
  • CiviCRM version: yes probably
  • CMS version: drupal
Re: Dependency injection and autowiring
March 12, 2014, 12:10:30 pm
Sorry, should have clarified, my point was that that pattern as implemented would be awesome if it could allow extensions to replace a core service.
-Hackathon and data journalism about the European parliament 24-26 jan. Watch out the result

JoeMurray

  • Administrator
  • Ask me questions
  • *****
  • Posts: 578
  • Karma: 24
    • JMA Consulting
  • CiviCRM version: 4.4 and 4.5 (as of Nov 2014)
  • CMS version: Drupal, WordPress, Joomla
  • MySQL version: MySQL 5.5, 5.6, MariaDB 10.0 (as of Nov 2014)
Re: Dependency injection and autowiring
May 18, 2014, 07:29:39 am
I'm excited about using DI, and I support the pattern of use that X+ suggests regarding the replacement of a core service. For example, replacing the core spooler used for emails.
Co-author of Using CiviCRM https://www.packtpub.com/using-civicrm/book

Pages: [1]
  • CiviCRM Community Forums (archive) »
  • Old sections (read-only, deprecated) »
  • Developer Discussion »
  • 5.0 Saloon »
  • Dependency injection and autowiring

This forum was archived on 2017-11-26.