Skip to content

Instantly share code, notes, and snippets.

@smichaelsen
Last active March 11, 2016 16:17
Show Gist options
  • Save smichaelsen/b5b742ad66e89cbb8daf to your computer and use it in GitHub Desktop.
Save smichaelsen/b5b742ad66e89cbb8daf to your computer and use it in GitHub Desktop.
Idea: Injection Traits #typo3 #extbase
<?php
class A
{
/**
* In TYPO3 (Extbase) you can use a simple @inject annotation to get an instance of the class specified with
* the @var annotation ready-to-use when your class is instanciated.
* However you're discouraged to use that variant for performance reasons.
*
* @var \FirstFactory
* @inject
*/
protected $firstFactory;
// ----------------------------------------------------------------- //
/**
* @var \SecondFactory
*/
protected $secondFactory;
/**
* Nowadays this is the common way for dependency injection in TYPO3 even though it is a little bit longer.
* When your class is instanciated all public inject* methods will be called automagically and supplied with
* the desired objects.
*
* Disadvantage of both methods: Injections will always happen when you use this class. You might end up using a
* lot of service classes in your methods but you're not using every service in every method so you get classes
* injected which you're not using. That's bad for performance of course (consider these service classes may have
* injections as well). Also some heavy reflection crunching (and caching) is done to analyse which classes are needed.
*
* @param \SecondFactory $secondFactory
*/
public function injectSecondFactory(\SecondFactory $secondFactory)
{
$this->secondFactory = $secondFactory;
}
// ----------------------------------------------------------------- //
public function myMethod()
{
/**
* Instead of using dependency injection you could instanciate your services manually to make sure you only
* load what you need. From a performance point of view: Good choice!
*
* Disadvantage: Not only is it hazzly to write, it also is bad for the testability of your code.
* When using dependency injection you can pass mock objects as services to your class, but with this variant
* it's not really possible. To stay testable your service classes need to be properties of your class.
*/
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
$thirdFactory = $objectManager->get(\ThirdFactory::class);
}
// ----------------------------------------------------------------- //
use FourthFactoryInjection;
public function myOtherMethod()
{
/**
* @see FourthFactoryInjection.php
*/
$this->getFourthFactory();
}
}
<?php
/**
* Here's my idea:
* Using traits i'd like to avoid the disadvantages of all known injection variants.
* The trait provides a property $_fourthFactory to the class using it, so you can always
* inject a mock object in your tests. When you want to call your service always use the getter
* method and it will be instanciated on demand.
*
* What do you think? I'd be happy to hear your feedback.
*/
trait FourthFactoryInjection
{
/**
* @var \FourthFactory
*/
private $_fourthFactory;
/**
* @return \FourthFactory
*/
protected function getFourthFactory()
{
if (!$this->_fourthFactory instanceof \FourthFactory) {
$this->_fourthFactory = GeneralUtility::makeInstance(ObjectManager::class)->get(\FourthFactory::class);
}
return $this->_fourthFactory;
}
}
@NamelessCoder
Copy link

Two thoughts:

  • If your class requires it for operation, pass dependencies in the constructor. If you end up with a huge constructor, it's time to refactor.
  • A getter method is a reasonable alternative for ad-hoc dependencies that you don't want to inject for various reasons (primarily performance). However, because it is for ad-hoc use, I would personally never let it assign a property and return that (I would create new instances each time).

However, you do not need a Trait for that since there is minimal duplication with such methods.

Don't forget about the SF2 alternative (although I'm not a fan of that approach personally) with things like ContainerAwareTrait which adds $this->container where you can get various dependencies by a short name. That one is equally easy to mock but has the added benefit of being generic enough that one Trait fits all - but comes with other disadvantages to class composition and loosely declared return types when getting a dependency.

@afoeder
Copy link

afoeder commented Mar 11, 2016

Well, @NamelessCoder said it already: I am all and exclusively for constructor injection for the reasons the Symfony doc already state: http://symfony.com/doc/current/components/dependency_injection/types.html

Only constructor injection guarantees intrinsically that the dependency is there, everything other is just "hoping".

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