Symfony2 Dependency Injection to custom/business classes - php

I didn't find similar questions/solutions Googleing, or on Stackoverflow, so here I am, in need for help.
I'm porting a PHP project from a custom framework to the Symfony framework (v2.5) which had it's custom dependency injection (based on class reflection). Many of the business logic classes have injected properties and I'm interested if there's a way to do something similar in Symfony without declaring those classes as services, because some of the classes are used for temporary holding/manipulating content coming from 3rd party endpoints and I need different instances of them.
More precisely is there a way to use Dependency Injection in combination with instantiating objects using the new keyword. So if I needed a new class that's dependent on logger or a validator, how could I do something like this.
Content class that I want to solve dependency injection for:
class Content extends SomeObject
{
private $pictureUrl;
...
public __construct(array $array = null)
{
parent::__construct($array);
}
public setPicture(...\Picture $picture)
{
// what's the best way to inject/enable this into my class
$errors = $this->get('validator')->validate($picture->getUrl());
...
$this->pictureUrl = $picture->getUrl();
}
...
}
The class that instantiates the previous class (in this particular case this could be a service, but there are other cases where this is another business logic related class):
class EndpointService extends Service
{
...
public fetchContent()
{
$dataArray = $this->endpoint->fetch();
$contentArray = array();
foreach ($dataArray as $key => $value) {
$content = new Content(array());
$content->setPicture($value->picture);
...
$contentArray[$key] = $content;
}
}
...
}
Note: If there are logical/syntactical errors please ignore them, this is an example through which I demonstrate my problem, and is a distant abstraction from my code base.

It looks to me that your Content is a model, and that the validation should be done in the EndpointService class.
You might consider refactoring so that the validator can be injected to the Service parent class that EndpointService inherits from, if you don't want to inject it to the EndpointService directly.
class EndpointService extends Service
{
protected $validator;
public __construct(Validator $validator) {
$this->validator = $validator;
}
...
public fetchContent()
{
$dataArray = $this->endpoint->fetch();
$contentArray = array();
foreach ($dataArray as $key => $value) {
$content = new Content(array());
$errors = $this->validator->validate($value->picture->getUrl());
$content->setPicture($value->picture);
...
$contentArray[$key] = $content;
}
}
...
}
And in your config:
// config.yml
services:
app.endpoint_service:
class: Acme\DemoBundle\External\EndpointService
arguments: [#validator]

I see one way you could do it, by passing the validator depedency to your EndpointService through dependency injection. You could then pass it to the new Content.
class EndpointService extends Service
{
...
public function construct($validator)
{
$this->validator = $validator;
}
public fetchContent()
{
$dataArray = $this->endpoint->fetch();
$contentArray = array();
foreach ($dataArray as $key => $value) {
$content = new Content($this->validator, array());
$content->setPicture($value->picture);
...
$contentArray[$key] = $content;
}
}
...
}
class Content extends SomeObject
{
private $pictureUrl;
...
public __construct($validator, array $array = null)
{
$this->validator = $validator;
parent::__construct($array);
}
public setPicture(...\Picture $picture)
{
// what's the best way to inject/enable this into my class
$errors = $this->validator->validate($picture->getUrl());
...
$this->pictureUrl = $picture->getUrl();
}
...
}

Related

Can i define an object inside a Class constructor?

The clean code says, that is not recommended to initialize objects, use if statement or other things in the __constructor.
I have a class in which I used elements in the constructor which are not allowed.
How to rebuild it to conform to the rules?
I searched on google! But I do not really understand and I hope that I will succeed in understanding with this particular example.
The full code is also available on github: https://github.com/KoreLewi/21-blackjack-game
public function __construct(array $Cards = array())
{
if (empty($Cards)) {
$Cards = $this->initEnglishDeck();
}
parent::__construct($Cards);
}
public function initEnglishDeck()
{
$arrCards = array();
foreach ($this->suits as $suit) {
foreach ($this->cards as $card) {
$arrCards[] = new Card($suit, $card);
}
}
return $arrCards;
}
The full code is also available on github: https://github.com/KoreLewi/21-blackjack-game
The pattern is Dependency Injection rather than initialising your dependencies internaly.
One way to "fix" your code is to have a CardDeck Interface and multiple (or just in this case) a EnglishDeck class which implements CardDeck.
And in all the classes which require a card deck you inject it in the constructor like this:
class Game {
/**
* #var CardDeck
*/
private $cardDeck
public function __construct(CardDeck $cardDeck) {
$this->cardDeck = $cardDeck
}
}
In that case your Game class would still work even if you decide it to pass him another type of CardDeck, e.g. FrenchDeck which would also implement the CardDeck interface.
You could:
Move the if statement into the initEnglishDeck() function.
public function __construct(array $Cards = array()) {
$Cards = $this->initEnglishDeck($Cards);
parent::__construct($Cards);
}
public function initEnglishDeck($cards = array()) {
if(!empty($cards)) return $cards;
$arrCards = array();
foreach ($this->suits as $suit) {
foreach ($this->cards as $card) {
$arrCards[] = new Card($suit, $card);
}
}
return $arrCards;
}

How can I decouple instantiation from implementation for unit testing if DI is not feasible?

I have the following code (simplified and details changed for this question):
class model_to_be_tested {
// an array that holds a collection of thing A
public $array_of_thing_A;
// already doing constructor injection for the data object
public __construct($data_object) {
// details here
}
public function add_new_thing_A($has_relationship) {
$thing_A = new Thing_A();
$thing_A->is_thing = true;
$thing_A->has_relationship_with_thing_B = $has_relationship;
if ($has_relationship) {
$thing_B = new Thing_B();
$thing_A->relationship_with = $thing_B;
}
$this->array_of_thing_A[] = $thing_A;
}
}
In the above example, I have to decouple the instantiation of Thing_A and Thing_B from the add_new_thing method. However, a simple constructor injection will not do for these two classes. This is because I need fresh instances of Thing_A and Thing_B every time add_new_thing is called so that Thing_A can be added to the array_of_thing_A.
How can I make this function unit testable? And more specifically for me to use mocks of Thing_A and Thing_B in testing this function in PHPUnit?
Any suggestions with code example will be appreciated.
Additionally, I would like to mention that Thing_A and Thing_B are used elsewhere in the codebase that I am working with and the code using these classes will eventually need to be unit tested. Solutions that are too localized and would cause repeated code elsewhere will not be too ideal in my situation. Thank you.
As commenter xmike mentioned, you could use the factory pattern. You would inject a factory object through the ctor as well. Then you could have a factory that provides simplified instances of your Thing_A and Thing_B.
class ThingFactory {
public function buildThingA() {
return new Thing_A(); // or MockThing_A if you go the ducktyping route
}
public function buildThingB() {
return new Thing_B();
}
}
class model_to_be_tested {
// an array that holds a collection of thing A
public $array_of_thing_A;
// you could go the typed route and have an interface for this
private $factory;
// already doing constructor injection for the data object
public __construct($data_object, $factory) {
// details here
$this->factory = $factory;
}
public function add_new_thing_A($has_relationship) {
$thing_A = $this->factory->buildThingA();
$thing_A->is_thing = true;
$thing_A->has_relationship_with_thing_B = $has_relationship;
if ($has_relationship) {
$thing_B = $this->factory->buildThingB();
$thing_A->relationship_with = $thing_B;
}
$this->array_of_thing_A[] = $thing_A;
}
}
PHP is such a strange language, you can't assign a class to a variable. But you can do it as a string. Inject ThingA and ThingB on the constructor as strings. You can call new on the string member.
class ThingA {};
class ThingB{};
class model_to_be_tested {
// an array that holds a collection of thing A
public $array_of_thing_A;
private $_thingA;
private $_thingB;
public function __construct($data_object, $thingA, $thingB) {
$this->_thingA = $thingA;
$this->_thingB = $thingB;
}
public function add_new_thing_A($has_relationship) {
$thing_A = new $this->_thingA();
if ($has_relationship) {
$thing_B = new $this->_thingB();
}
$this->array_of_thing_A[] = $thing_A;
}
}
$model = new model_to_be_tested('foo', 'ThingA', 'ThingB');
$model->add_new_thing_A(true);
There's a live version here: https://repl.it/#rmoskal/InconsequentialAnotherGermanshorthairedpointer
Or provide a static constructor for the class.

How to access and manipulate data in Apigility before it is sent to the client?

I'm developing an Apigility driven application based on the Zend Framework 2.
Currently I'm sending the data retrieved in the database directly to the client: a request comes in, the MyResource#fetch(...) or MyResource#fetchAll(...) gets triggered and calls an appropriate method on MyService class, that calls MyMapper to retireve the data with its methods like findFooByBar(...).
Now I'd like to process the data, before the response is sent. How can I do that?
The Apigility ZF HAL documentation shows, how to access the entity data between it has been retrieved and sent to the client. Well I tried this out. It's ugly and to much code for such task. And... it doesn't work. I want however post here my attept:
namespace Portfolio;
...
class Module implements ApigilityProviderInterface {
private $serviceManager;
public function onBootstrap(MvcEvent $event) {
$application = $event->getTarget();
$this->serviceManager = $serviceManager = $application->getServiceManager();
$viewHelperManager = $serviceManager->get('ViewHelperManager');
$hal = $viewHelperManager->get('Hal');
$hal->getEventManager()->attach('renderEntity', array($this, 'onRenderEntity'));
$hal->getEventManager()->attach('renderCollection', array($this, 'onRenderCollection'));
}
public function onRenderEntity($event) {
$entity = $event->getParam('entity');
if ($entity->entity instanceof ProjectEntity) {
$projectEntity = $entity->entity;
$imageCollection = $this->tempCreateimagesForProject(
$event, $entity->entity->getId()
);
$projectEntity->setImages($imageCollection);
$event->setParam('entity', $projectEntity);
}
}
public function onRenderCollection($event) {
$collection = $event->getParam('collection');
$projectCollection = $collection->getCollection();
if ($projectCollection instanceof ProjectCollection) {
foreach ($projectCollection as $key => $projectItem) {
$tempProject = $projectCollection->getItem($key);
$tempProject->append(
['images' => $this->tempCreateimagesForProject($tempProject->offsetGet('id'))]
);
$projectCollection->getItem($key)->offsetSet($key, $tempProject);
}
}
}
private function tempCreateimagesForProject(Event $event, $projectId) {
$imageService = $this->serviceManager->get('Portfolio\V2\Rest\ImageService');
$imageCollection = $imageService->getImagesForProject($projectId);
return $imageCollection;
}
...
}
I think using the renderEntity and renderCollection events is not the correct spot to add this kind of resource specific logic. It is more suitable for more general changes or incidental customization.
You can add this logic to your resource listeners. So in your fetch and fetchAll methods in your MyResource class you can add the custom code you currently added in these onRenderEntity and onRenderCollection methods.
So something like this:
class MyResource extends AbstractResourceListener
{
/**
* Your image service dependency
*/
protected $imageService;
/* ... */
public function fetch($id)
{
$project = $this->projectMapper->fetch($id);
$imageCollection = $this->imageService->getImagesForProject($project);
$project->setImages($imageCollection);
return $project;
}
/* ... */
public function fetchAll($params = array())
{
$projects = $this->projectMapper->fetchAll();
foreach ($projects as $key => $project) {
$imageCollection = $this->imageService->getImagesForProject($project);
$project->setImages($imageCollection);
}
return $projects;
}
/* ... */
}
One possible solution is handling the data in the Hydrator. So we write a custom Hydrator class and enrich the items with nested objects and lists in it. It can look like this:
Portfolio\V2\Rest\Project\ProjectHydrator
...
class ProjectHydrator extends ClassMethods {
/**
* #var ImageService
*/
protected $imageService;
...
/*
* Doesn't need to be implemented:
* the ClassMethods#hydrate(...) handle the $data already as wished.
*/
/*
public function hydrate(array $data, $object) {
$object = parent::hydrate($data, $object);
if ($object->getId() !== null) {
$images = $this->imageService->getImagesForProject($object->getId());
$object->setImages($images);
}
return $object;
}
*/
/**
* #see \Zend\Stdlib\Hydrator\ClassMethods::extract()
*/
public function extract($object) {
$array = parent::extract($object);
if ($array['id'] !== null) {
$images = $this->imageService->getImagesForProject($array['id']);
$array['images'] = $images;
}
return $array;
}
}
It's not a nice solution, since then a part of the model / data retrieving logic gets moved to the hydrator. But it works. Here is shown an implementation of this approach and here is a discussion to this topic on GitHub.
If you are using the ClassMethods Hydrator and your Collection extends \Zend\Paginator\Paginator a good solution without losing the Collection's consistency and not changing anybody's code is to overwrite your getCurrentItems() method.
public class MyResourceCollection // most likely extends Paginator
{
public function getCurrentItems()
{
// getting the original items
$items = parent::getCurrentItems();
// as we work with objects $item will be an object
// when working with objects we use references to them not clones of objects
// so changes to $item are also present in the collection
foreach ($collection as $item) {
$oldSomething = $item->getSomething();
$item->setSomething('['.$oldSomething.']');
}
// $items are now changed, return them
return $items;
}
}
I have named the key something not to get confused with the getValue method from other places.
This makes the something value look like [something].

Modular design pattern

I'm trying to decide the design of a system which is meant to allow for a high amount of extensiblity. From what I can tell, a pattern such as the abstract factory would not allow for overriding of the base methods, apart from duplicating code (as demonstrated below).
I've done some preliminary research into aspect oriented programming and it seems to be along the lines of what I'm looking for but I'm having a difficult time wrapping my head around the specifics.
abstract class Object {
protected $object_id;
protected $name;
function LoadObjectData()
{
$file_contents = readfile('object'.$object_id.'.data');
$data = array();
// parse file contents into $data array...
return $data;
}
function Create()
{
$data = $this->LoadObjectData();
$name = $data['name'];
return $data;
}
}
class User extends Object {
protected $email_address;
function Create()
{
$data = parent::Create();
$this->email_address = $data['email_address'];
return $data;
}
}
//----------Module 1-MySQL Lookup-------------
/*
* Redefine Object::LoadObjectData() as follows:
*/
function LoadObjectData()
{
$data = array();
$result = mysql_query("SELECT...");
// construct array from result set
return $data;
}
//----------Module 2-Cache Machine-------------
/*
* Redefine Object::LoadObjectData() as follows:
*/
function LoadObjectData()
{
if (exists_in_cache($object_id)) {
return get_cached_object($object_id);
}
$data = parent::LoadObjectData();
cache_object($object_id, $data);
return $data;
}
(This is sort of a poor example, but hopefully it helps to get my point across)
The intended system would have a very large proportion of methods available to be extended and I would like to minimize the extra effort and learning necessary for developers.
Is AOP exactly what I'm looking for, or is there a better way to deal with this?
Thanks!
So, you want to use a decorator pattern without defining the decorator itself.
If yes, then it's a monkeypatching and can be done with aspect-oriented tools. This can be solved easily with following extensions and frameworks:
PHP Runkit Extension
Go! Aspect-Oriented framework for PHP
PHP-AOP Extension.
You don't have to declare the base class as an abstract class. You can make it a regular class and have it load and instantiate other classes based on passed construct parameters. The constructor can return an instance of a class, not just the class the constructor is in. To avoid duplicating code, you can mix static with instantiated functions and variables. Just remember that a static function or variable is the same for ALL instances. Change a static variable in one and it is changed for all instances. A rather basic example of a plugin architecture.
class BaseObject {
protected static $cache = array();
public function __construct($load_plugin) {
require_once($load_plugin.'.class.php');
$object = new $load_plugin();
return $object;
}
public static function cacheData($cache_key, $data) {
self::$cache[$cache_key] = $data;
}
}
class Plugin extends BaseObject {
public function __construct() {
}
public function loadData() {
// Check the cache first
if ( !isset(self::$cache[$cache_key]) ) {
// Load the data into cache
$data = 'data to cache';
self::cacheData($cache_key, $data);
}
return self::$cache[$cache_key];
}
}

How to inject mock classes into controllers (without having the controller aware of the tests)

This is a follow-on from a previous question I had: How to decouple my data layer better and restrict the scope of my unit tests?
I've read around on Zend and DI/IoC and came up with the following changes to my code:
Module Bootstrap
class Api_Bootstrap extends Zend_Application_Module_Bootstrap
{
protected function _initAllowedMethods()
{
$front = Zend_Controller_Front::getInstance();
$front->setParam('api_allowedMethods', array('POST'));
}
protected function _initResourceLoader()
{
$resourceLoader = $this->getResourceLoader();
$resourceLoader->addResourceType('actionhelper', 'controllers/helpers', 'Controller_Action_Helper');
}
protected function _initActionHelpers()
{
Zend_Controller_Action_HelperBroker::addHelper(new Api_Controller_Action_Helper_Model());
}
}
Action Helper
class Api_Controller_Action_Helper_Model extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
if ($this->_actionController->getRequest()->getModuleName() != 'api') {
return;
}
$this->_actionController->addMapper('account', new Application_Model_Mapper_Account());
$this->_actionController->addMapper('product', new Application_Model_Mapper_Product());
$this->_actionController->addMapper('subscription', new Application_Model_Mapper_Subscription());
}
}
Controller
class Api_AuthController extends AMH_Controller
{
protected $_mappers = array();
public function addMapper($name, $mapper)
{
$this->_mappers[$name] = $mapper;
}
public function validateUserAction()
{
// stuff
$accounts = $this->_mappers['account']->find(array('username' => $username, 'password' => $password));
// stuff
}
}
So, now, the controller doesn't care what specific classes the mappers are - so long as there is a mapper...
But how do I now replace those classes with mocks for unit-testing without making the application/controller aware that it is being tested? All I can think of is putting something in the action helper to detect the current application enviroment and load the mocks directly:
class Api_Controller_Action_Helper_Model extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
if ($this->_actionController->getRequest()->getModuleName() != 'api') {
return;
}
if (APPLICATION_ENV != 'testing') {
$this->_actionController->addMapper('account', new Application_Model_Mapper_Account());
$this->_actionController->addMapper('product', new Application_Model_Mapper_Product());
$this->_actionController->addMapper('subscription', new Application_Model_Mapper_Subscription());
} else {
$this->_actionController->addMapper('account', new Application_Model_Mapper_AccountMock());
$this->_actionController->addMapper('product', new Application_Model_Mapper_ProductMock());
$this->_actionController->addMapper('subscription', new Application_Model_Mapper_SubscriptionMock());
}
}
}
This just seems wrong...
It is wrong, your system under test shouldn't have any knowledge of mock objects at all.
Thankfully, because you have DI in place, it doesn't have to. Just instantiate your object in the test, and use addMapper() to replace the default mappers with mocked versions.
Your test case should look something like:
public function testBlah()
{
$helper_model = new Api_Controller_Action_Helper_Model;
$helper_model->_actionController->addMapper('account', new Application_Model_Mapper_AccountMock());
$helper_model->_actionController->addMapper('product', new Application_Model_Mapper_ProductMock());
$helper_model->_actionController->addMapper('subscription', new Application_Model_Mapper_SubscriptionMock());
// test code...
}
You could also put this code in your setUp() method so that you don't have to repeat it for every test.
So, after a few misses, I settled on rewriting the action helper:
class Api_Controller_Action_Helper_Model extends Zend_Controller_Action_Helper_Abstract
{
public function preDispatch()
{
if ($this->_actionController->getRequest()->getModuleName() != 'api') {
return;
}
$registry = Zend_Registry::getInstance();
$mappers = array();
if ($registry->offsetExists('mappers')) {
$mappers = $registry->get('mappers');
}
$this->_actionController->addMapper('account', (isset($mappers['account']) ? $mappers['account'] : new Application_Model_Mapper_Account()));
$this->_actionController->addMapper('product', (isset($mappers['product']) ? $mappers['product'] : new Application_Model_Mapper_Product()));
$this->_actionController->addMapper('subscription', (isset($mappers['subscription']) ? $mappers['subscription'] : new Application_Model_Mapper_Subscription()));
}
}
This means that I can inject any class I like via the registry, but have a default/fallback to the actual mapper.
My test case is:
public function testPostValidateAccount($message)
{
$request = $this->getRequest();
$request->setMethod('POST');
$request->setRawBody(file_get_contents($message));
$account = $this->getMock('Application_Model_Account');
$accountMapper = $this->getMock('Application_Model_Mapper_Account');
$accountMapper->expects($this->any())
->method('find')
->with($this->equalTo(array('username' => 'sjones', 'password' => 'test')))
->will($this->returnValue($accountMapper));
$accountMapper->expects($this->any())
->method('count')
->will($this->returnValue(1));
$accountMapper->expects($this->any())
->method('offsetGet')
->with($this->equalTo(0))
->will($this->returnValue($account));
Zend_Registry::set('mappers', array(
'account' => $accountMapper,
));
$this->dispatch('/api/auth/validate-user');
$this->assertModule('api');
$this->assertController('auth');
$this->assertAction('validate-user');
$this->assertResponseCode(200);
$expectedResponse = file_get_contents(dirname(__FILE__) . '/_testPostValidateAccount/response.xml');
$this->assertEquals($expectedResponse, $this->getResponse()->outputBody());
}
And I make sure that I clear the default Zend_Registry instance in my tearDown()
Below is my solution to inject a mocked timestamp for a ControllerTest unit test, which is similar to the question originally posted above.
In the ControllerTest class, a $mockDateTime is instantiated and added as a parameter to the FrontController before calling dispatch().
public function testControllerAction() {
....
$mockDateTime = new DateTime('2011-01-01T12:34:56+10:30');
$this->getFrontController()->setParam('datetime', $mockDateTime);
$this->dispatch('/module/controller/action');
...
}
In the Controller class, dispatch() will pass any parameters into _setInvokeArgs(), which we extend here:
protected function _setInvokeArgs(array $args = array())
{
$this->_datetime = isset($args['datetime']) ? $args['datetime'] : new DateTime();
return parent::_setInvokeArgs($args);
}
The major advantage of this solution is that it allows dependency injection while it does not require the unit tests to clean up global state.

Categories