I have a simple class which looks like this:
<?php
namespace App\Algorithm;
use App\Dao\MatchDao;
use App\Service\MatchService;
class Calculator {
private $users;
private $matchDao;
function __construct(MatchService $matchService, MatchDao $matchDao) {
$this->users = $matchService->users;
$this->matchDao = $matchDao;
}
public function hourlyRate() {
$query = $this->matchDao->getSingleColumn('Payment', 'hourly_rate', 32);
var_dump($query);
}
}
But I get the following error message:
Circular reference detected for service "App\Algorithm\Calculator",
path: "App\Algorithm\Calculator -> App\Service\MatchService ->
App\Algorithm\Calculator".
MatchService.php
<?php
namespace App\Service;
use App\Algorithm\Calculator;
use App\Algorithm\Collection;
class MatchService {
public $users;
private $collection;
private $calculator;
function __construct(Collection $collection, Calculator $calculator) {
$this->collection = $collection;
$this->calculator = $calculator;
}
public function getMatch($data) {
$this->users = $this->collection->getAllUsers($data);
$this->calculator->hourlyRate();
return 1;
}
}
The problem would be MatchService but what exactly am I doing wrong?
As several people have pointed out, the circular dependency comes from that fact that you are trying to inject the Calculator into MatchService and at the same time, injecting MatchService into the Calculator. No way to create one before creating the other.
Looking a bit more deeply, it appears that Calculator is using the MatchService to get list of users. As a second problem, Calculator is trying to get the users before MatchService has generated them.
Here is one possible refactoring:
class Calculator
{
private $matchDao;
public function __construct(MatchDao $matchDao)
{
$this->matchDao = $matchDao;
}
public function getHourlyRate($users) // Added argument
{
$query = $this->matchDao->getSingleColumn('Payment', 'hourly_rate', 32);
}
}
class MatchService
{
private $collection;
private $calculator;
public function __construct(Collection $collection, Calculator $calculator)
{
$this->calculator = $calculator;
$this->collection = $collection;
}
public function getMatch($data)
{
$users = $this->collection->getAllUsers($data);
$this->calculator->getHourlyRate($users);
}
}
Removing MatchService from the Calculator's constructor solves the circular dependency problem. Passing $users to getHourlyRate solves the problem of trying to get users before they are available.
This is course is just one possible solution. It's not clear from your posted code if Calculator really needs $users or not.
This usually occurs when classes are dependency injecting each other, hence the circular reference.
Given you above example, your class MatchService injects Collection and Calculator. One of these (would assume calculator as collection is probably a doctrine class) dependency injects your MatchService.
Here is how I imagine your classes are supt:
class MatchService
{
public $users;
private $collection;
private $calculator;
public function __construct(Collection $collection, Calculator $calculator) {
$this->collection = $collection;
$this->calculator = $calculator;
}
}
class Calculator
{
private $matchService;
public function __construct(MatchService $matchService)
{
$this->matchService = $matchService;
}
}
You have a couple of options:
More services with fewer dependencies
Using statics
It's hard for us to solve for you as it's dependent on how you architect your application.
It is kind of obvious that you are injecting service A into Service B, and, also, Service B into Service A .
Seems kind of not logical to do so, but sometimes is needed.
In my case, I have two services :
_MySesion -> Which prototypes Symfony Session
_MyClient -> Responsible for identify the client and get its DB Credentials
I use the MySession to store those credentials, as so, it will be available to the whole system, but, to get those credentials using MyClient, I need some info stored into MySession .... See, two services that need each other to work ...
I start to see this same
Circular reference detected for service
just after upgrade to Symfony 5. And, sfy5 itself suggested the solution :
composer require symfony/proxy-manager-bridge
Remember that the services may be set with
lazy : true
More info on Symfony Docs
Related
I have a class Receipt.php
<?php
namespace TDD;
class Receipt {
private $user_id = 1;
private $pending_amount;
public function total(array $items = []){
$items[] = $this->pending();
return array_sum($items);
}
public function tax($amount,$tax){
return $amount * $tax;
}
private function pending()
{
$sql = 'select pending_amount from Pending_transtions where user_id =' . $this->user_id . ' limit 1;';
//$pending_amt = $this->mainDb->get_sql_row($sql);
//$this->pending = $pending_amt['pending_amount'];
return $this->pending_amount = 45;
}
public function addTaxPending($tax){
return $this->pending_amount * $tax;
}
}?>
And in my PHPUnit file, ReceiptTest.php
<?php
namespace TDD\Test;
require(dirname(dirname(__FILE__))).DIRECTORY_SEPARATOR.'vendor'.DIRECTORY_SEPARATOR.'autoload.php';
use PHPUnit\Framework\TestCase;
use TDD\Receipt;
class ReceiptTest extends TestCase{
public function setUp(): void {
$this->Receipt = new Receipt();
}
public function tearDown(): void{
unset($this->Receipt);
}
public function testTotal(){
$input = [0,2,5,8];
$output = $this->Receipt->total($input);
$this->assertEquals(15,$output,"this is not valid");
}
public function testTax(){
$inputAmount = 10.00;
$inputTax = 0.10;
$output = $this->Receipt->tax($inputAmount,$inputTax);
$this->assertEquals(1.0,$output,"this tax expecting 1.0");
}
}?>
question:
How to ignore internal calling function pending() because it fetches data from DB. At the same time I want to access the property of $this->pending_amount.
Here Pending() must be private function.
How can I achieve that? I am looking for your valuable solutions
Proper solution is to replace your dependency (the one which is saved under $this->mainDb in your example) with a "mocked" one in your test case.
Here is an article from PHPUnit manual, which shows how to create mocks - https://phpunit.readthedocs.io/en/9.5/test-doubles.html#mock-objects
--
Speaking about ways of injection: your can either pass $this->mainDb instance via class constructor, or make so-called "seam" in form of public setMainDb method (which is not an elegant solution - I'd prefer to avoid it).
Another thing which I had to do sometimes, is to replace the value via Reflection: make private property accessible and set it inside of test to the value I need.
--
Update:
Based on given example, I think the easiest way to achieve desired result is:
Change test case's setUp to:
public function setUp(): void
{
$this->Receipt = new Receipt();
$mainDbMock = new class() {
public function get_sql_row() {
return [
"pending_amount" => 0
];
}
};
$this->Receipt->setMainDb($mainDbMock);
}
Add "seam"-method to your Receipt class:
public function setMainDb($mainDb)
{
$this->mainDb = $mainDb;
}
You need to mock the dependency. The dependency in your case is the database. You need to replace access to the database with a mock that you can configure with known return values.
Take the time to read the PHPUnit documentation on how to use mock objects.
A basic example
Given a DB access class with a get_sql_row method that accepts an SQL string and returns some data structure, we don't need to fill in any code here in our example because we're going to mock the get_sql_row method and configure it to return known values for our test.
class MainDb
{
public function get_sql_row(string $sql)
{
// ...
}
}
Our receipt class accepts a DB access object in the constructor and uses it to execute SQL e.g. the pending method executes SQL to get the pending amount (if any).
class Receipt
{
public function __construct($mainDb)
{
$this->mainDb = $mainDb;
}
public function total(array $items = [])
{
$items[] = $this->pending();
return array_sum($items);
}
private function pending()
{
$pendingAmount = $this->mainDb->get_sql_row('sql...');
return $pendingAmount['pending_amount'];
}
}
In the testcase setup method we create a mock of the DB access class and create the receipt object with the mocked database. The mock is then used in the tests to configure expectations, received arguments, and return values.
The testTotalWithNoPendingAmount test configures the DB mock to return 0 when get_sql_row is invoked. The get_sql_row is only expected to be called once and will fail otherwise. Configuring get_sql_row to return a known value allows us to test the behaviour of the receipt class without touching the DB.
Similarly, testTotalWithPendingAmount configures the DB mock get_sql_row to return a value other than 0 so that we can test behaviour of calculating the total when there is a pending amount.
final class ReceiptTest extends TestCase
{
protected Receipt $receipt;
protected function setUp(): void
{
// create a mock of the database access object
$this->dbMock = $this->createMock(MainDb::class);
// create receipt with mocked database
$this->receipt = new Receipt($this->dbMock);
}
public function testTotalWithNoPendingAmount(): void
{
$this->dbMock->expects($this->once())
->method('get_sql_row')
->willReturn(['pending_amount' => 0]);
$this->assertEquals(15, $this->receipt->total([0, 2, 5, 8]));
}
public function testTotalWithPendingAmount(): void
{
$this->dbMock->expects($this->once())
->method('get_sql_row')
->willReturn(['pending_amount' => 3]);
$this->assertEquals(18, $this->receipt->total([0, 2, 5, 8]));
}
}
Take the time to read the PHPUnit documentation on using mock objects and understand how they work and how to use them in your testing.
Note also that your handling of SQL is potentially open to SQL injection. Please read about SQL injection.
I have some classes that require dependencies injected into their constructors. This allows me to inject mocks (e.g. from prophecy) for testing.
I'm interested in using a container to help configure and access these objects, and I've looked at Pimple for this (I also looked at PHP-DI although I couldn't get that to resolve stuff on a quick attempt).
All good so far. BUT, the problem I have is that the application (Drupal 7) is built around thousands of functions which do not belong to an object that can have dependencies injected into.
So I need these functions to be able to access the services from the container. Further more, for testing purposes, I need to replace the services with mocks and new mocks.
So the pattern is like:
<?php
/**
* Some controller class that uses an injected mailing service.
*/
class Supporter
{
protected $mailer;
public function __construct(MailingServiceInterface $mailer) {
$this->mailer = $mailer;
}
public function signUpForMalings($supporter_id) {
$email = $this->getSupporterEmail($supporter_id);
$this->mailer->signup($email);
}
}
Then peppered in various functions I'd use:
<?php
/**
* A form submit handler called by the platform app,
* with a signature I can't touch.
*/
function my_form_submit($values) {
global $container;
if ($values['subscribe']) {
$supporter = $container->get('supporter');
$supporter->signUpForMailings($values['supporter_id']);
}
}
Elsewhere I may need to access the mailer directly...
<?php
/**
* example function requires mailer service.
*/
function is_signed_up($email) {
global $container;
return $container->get('mailer')->isSignedUp($email);
}
And elsewhere a function that calls those functions...
<?php
/**
* example function that uses both the above functions
*/
function sign_em_up($email, $supporter_id) {
if (!is_signed_up($email)) {
my_form_submit(['supporter_id'=>$supporter_id);
return TRUE;
}
}
Let's acknowledge that these functions are a mess - that's a deliberate representation of the problem. But let's say I want to test the sign_em_up function:
<?php
public testSignUpNewPerson() {
$mock_mailer = createAMockMailer()
->thatWill()
->return(FALSE)
->whenFunctionCalled('isSignedUp', 'wilma#example.com');
// Somehow install the mock malier in the container.
$result = sign_em_up('wilma#example.com', 123);
$this->assertTrue($result);
}
// ... imagine other tests which also need to inject mocks.
While I recognise that this is using the container as a Service Locator in the various global functions, I think this is unavoidable given the nature of the platform. If there's a cleaner way, please let me know.
However my main question is:
There's a problem with injecting mocks, because the mocks need to change for various tests. Lets say I swap out the mailer service (in Pimple: $container->offsetUnset('mailer'); $container['mailer'] = $mock_mailer;), but if Pimple had already instantiated the supporter service, then that service will have the old, unmocked mailer object. Is this a limitation of the containter software, or the general container pattern, or am I Doing It Wrong, or is it just a mess because of the old-school function-centred application?
Here's what I've gone for, in absence of any other suggestions!
Container uses Pimple\Psr11\ServiceLocator
I'm using Pimple, so the container's factories may look like this
<?php
use Pimple\Container;
use Pimple\Psr11\ServiceLocator;
$container = new Container();
$container['mailer'] = function ($c) { return new SomeMailer(); }
$container['supporters'] = function ($c) {
// Create a service locator for the 'Supporters' class.
$services = new ServiceLocator($c, ['mailer']);
return new Supporter($services);
}
Then the Supporter class now instead of storing references to the objects extracted from the container when it was created, now fetches them from the ServiceLocator:
<?php
use \Pimple\Psr11\ServiceLocator;
/**
* Some controller class that uses an injected mailing service.
*/
class Supporter
{
protected $services;
public function __construct(ServiceLocator $services) {
$this->services = $services;
}
// This is a convenience function.
public function __get($prop) {
if ($prop == 'mailer') {
return $this->services->get('mailer');
}
throw new \InvalidArgumentException("Unknown property '$prop'");
}
public function signUpForMalings($supporter_id) {
$email = $this->getSupporterEmail($supporter_id);
$this->mailer->signup($email);
}
}
In the various CMS functions I just use global $container; $mailer = $container['mailer'];, but it means that that in tests I can now mock any service and know that all code that needs that service will now have my mocked service. e.g.
<?php
class SomeTest extends \PHPUnit\Framework\TestCase
{
function testSupporterGetsMailed() {
global $container;
$supporter = $container['supporter'];
// e.g. mock the mailer component
$container->offsetUnset('mailer');
$container['mailer'] = $this->getMockedMailer();
// Do something with supporter.
$supporter->doSomething();
// ...
}
}
Lately I'm giving a try to phpspec. It works great, but I have got a problem with testing command handlers. For example in PHPUnit I test it that way:
/**
* #test
*/
public function it_should_change_an_email()
{
$this->repository->add($this->employee);
$this->handler->changeEmail(
new ChangeEmailCommand(
$this->employee->username()->username(),
'new#email.com'
)
);
Asserts::assertEquals(new Email('new#email.com'), $this->employee->email());
}
and setup:
protected function setUp()
{
$this->repository = new InMemoryEmployeeRepository();
$this->createEmployee();
$this->handler = new EmployeeCommandHandler($this->repository);
}
The main point is that this test make assertions on the Employee object to check if CommandHandler is working good. But in phpspec I can't make assertion on different object than the specifying one, in this case I can only make assertion on my CommandHandler. So how I can test a command handler in phpspec?
EDIT
Maybe spies are the way to go:
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
/** #var EmployeeRepository */
private $employeeRepository;
public function let(EmployeeRepository $employeeRepository)
{
$this->employeeRepository = $employeeRepository;
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(Employee $employee)
{
$this->givenEmployeeExists($employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new#email.com')
);
$employee->changeEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(Employee $employee)
{
$this->employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->shouldBeCalled()
->willReturn($employee);
}
}
Employee class I've already speced. So, maybe, in command handler it'll be enough to just check if the method of the Employee has been called. What do you think about it? Am I going in good direction?
Messaging
Indeed, you shouldn't verify the state, but expect certain interactions between objects. That's what OOP is about afterall - messaging.
The way you've done it in PHPUnit is state verification. It forces you to expose the state as you need to provide a "getter", which is not always desired. What you're interested in is that Employee's email was updated:
$employee->updateEmail(new Email('new#email.com'))->shouldBeCalled();
The same can be achieved with spies if you prefer:
$employee->updateEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
Command/Query Separation
We usually only need to state our expectations against methods that have side effects (command methods from Command/Query separation). We mock them.
Query methods do not need to be mocked, but stubbed. You don't really expect that EmployeeRepository::employeeWithUsername() should be called. Doing so we're making assumptions about implementation which in turn will make refactoring harder. All you need is stubbing it, so if a method is called it returns a result:
$employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
Full example
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
public function let(EmployeeRepository $employeeRepository)
{
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(
EmployeeRepository $employees, Employee $employee
) {
$this->givenEmployeeExists($employees, $employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new#email.com')
);
$employee->changeEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(
EmployeeRepository $employees, Employee $employee
) {
$employees->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
}
}
I've been reading / watching a lot of recommended material, most recently this - MVC for advanced PHP developers. One thing that comes up is Singletons are bad, they create dependency between classes, and Dependency Injection is good as it allows for unit testing and decoupling.
That's all well and good until I'm writing my program. Let's take a Product page in a eshop as an example. First of all I have my page:
class Page {
public $html;
public function __construct() {
}
public function createPage() {
// do something to generate the page
}
public function showPage() {
echo $this->html;
}
}
All fine so far, but the page needs a product, so let's pass one in:
class Page {
public $html;
private $product;
public function __construct(Product $product) {
$this->product = $product;
}
public function createPage() {
// do something to generate the page
}
public function showPage() {
echo $this->html;
}
}
I've used dependency injection to avoid making my page class dependent on a product. But what if page had several public variables and whilst debugging I wanted to see what was in those. No problem, I just var_dump() the page instance. It gives me all the variables in page, including the product object, so I also get all the variables in product.
But product doesn't just have all the variables containing all the details of the product instantiated, it also had a database connection to get those product details. So now my var_dump() also has the database object in it as well. Now it's starting to get a bit longer and more difficult to read, even in <pre> tags.
Also a product belongs to one or more categories. For arguments sake let's say it belongs to two categories. They are loaded in the constructor and stored in a class variable containing an array. So now not only do I have all the variables in product and the database connection, but also two instances of the category class. And of course the category information also had to be loaded in from the database, so each category instance also has a database private variable.
So now when I var_dump() my page I have all the page variables, all the product variables, multiples of the category variables in an array, and 3 copies of the database variables (one from the products instance and one from each of the category instances). My output is now huge and difficult to read.
Now how about with singletons? Let's look at my page class using singletons.
class Page {
public $html;
public function __construct() {
}
public function createPage() {
$prodId = Url::getProdId();
$productInfo = Product::instance($prodId)->info();
// do something to generate the page
}
public function showPage() {
echo $this->html;
}
}
And I use similar singletons inside the Product class as well. Now when I var_dump() my Page instance I only get the variables I wanted, those belonging to the page and nothing else.
But of course this has created dependencies between my classes. And in unit testing there's no way to not call the product class, making unit testing difficult.
How can I get all the benefits of dependency injection but still make it easy to debug my classes using var_dump()? How can I avoid storing all these instances as variables in my classes?
I'll try to write about several things here.
About the var_dump():
I'm using Symfony2 as a default framework, and sometimes, var_dump() is the best option for a quick debug. However, it can output so much information, that there is no way you're going to read all of it, right? Like, dumping Symfony's AppKernel.php, or, which is more close to your case, some service with an EntityManager dependency. IMHO, var_dump() is nice when you debugging small bits of code, but large and complex product make var_dump() ineffective. Alternative for me is to use a "real" debugger, integrated with your IDE. With xDebug under PhpStorm I have no real need of var_dump() anymore.
Useful link about "Why?" and "How-to?" is here.
About the DI Container:
Big fan of it. It's simple and makes code more stable; it's common in modern applications. But I agree with you, there is a real problem behind: nested dependencies. This is over-abstraction, and it will add complexity by adding sometimes unnecessary layers.
Masking the pain by using a dependency injection container is making
your application more complex.
If you want to remove DIC from your application, and you actually can do it, then you don't need DIC at all. If you want alternative to DIC, well... Singletons are considered bad practice for not testable code and a huge state space of you application. Service locator to me has no benefits at all. So looks like there is the only way, to learn using DI right.
About your examples:
I see one thing immediately - injecting via construct(). It's cool, but I prefer optional passing dependency to the method that requires it, for example via setters in services config.yml.
class Page
{
public $html;
protected $em;
protected $product;
public function __construct(EntityManager $em) {
$this->em = $em;
}
//I suppose it's not from DB, because in this case EM handles this for you
protected function setProduct(Product $product)
{
$this->product = $product;
}
public function createPage()
{
//$this->product can be used here ONLY when you really need it
// do something to generate the page
}
public function showPage()
{
echo $this->html;
}
}
I think it gives needed flexibility when you need only some objects during execution, and at the given moment you can see inside your class only properties you need.
Conclusion
Excuse me for my broad and somewhat shallow answer. I really think that there is no direct answer to your question, and any solution would be opinion based. I just hope that you might find that DIC is really the best solution with limited downside, as well as integrated debuggers instead of dumping the whole class (constructor, service, etc...).
I exactly know that it's possible to reach result what you wish, and don't use extreme solutions.
I am not sure that my example is good enough for you, but it has: di and it easy to cover by unit test and var_dump will be show exactly what you wish, and i think it encourage SRP.
<?php
class Url
{
public static function getProdId()
{
return 'Category1';
}
}
class Product
{
public static $name = 'Car';
public static function instance($prodId)
{
if ($prodId === 'Category1') {
return new Category1();
}
}
}
class Category1 extends Product
{
public $model = 'DB9';
public function info()
{
return 'Aston Martin DB9 v12';
}
}
class Page
{
public $html;
public function createPage(Product $product)
{
// Here you can do something more to generate the page.
$this->html = $product->info() . PHP_EOL;
}
public function showPage()
{
echo $this->html;
}
}
$page = new Page();
$page->createPage(Product::instance(Url::getProdId()));
$page->showPage();
var_export($page);
Result:
Aston Martin DB9 v12
Page::__set_state(array(
'html' => 'Aston Martin DB9 v12
',
))
Maybe this will help you:
class Potatoe {
public $skin;
protected $meat;
private $roots;
function __construct ( $s, $m, $r ) {
$this->skin = $s;
$this->meat = $m;
$this->roots = $r;
}
}
$Obj = new Potatoe ( 1, 2, 3 );
echo "<pre>\n";
echo "Using get_object_vars:\n";
$vars = get_object_vars ( $Obj );
print_r ( $vars );
echo "\n\nUsing array cast:\n";
$Arr = (array)$Obj;
print_r ( $Arr );
This will returns:
Using get_object_vars:
Array
(
[skin] => 1
)
Using array cast:
Array
(
[skin] => 1
[ * meat] => 2
[ Potatoe roots] => 3
)
See the rest here http://php.net/manual/en/function.get-object-vars.php
The short answer is, yes you can avoid many private variables and using dependency injection. But (and this is a big but) you have to use something like an ServiceContainer or the principle of it.
The short answer:
class A
{
protected $services = array();
public function setService($name, $instance)
{
$this->services[$name] = $instance;
}
public function getService($name)
{
if (array_key_exists($name, $this->services)) {
return $this->services[$name];
}
return null;
}
private function log($message, $logLevel)
{
if (null === $this->getService('logger')) {
// Default behaviour is to log to php error log if $logLevel is critical
if ('critical' === $logLevel) {
error_log($message);
}
return;
}
$this->getService('logger')->log($message, $logLevel);
}
public function actionOne()
{
echo 'Action on was called';
$this->log('Action on was called', 0);
}
}
$a = new A();
// Logs to error log
$a->actionOne();
$a->setService('logger', new Logger());
// using the logger service
$a->actionOne();
With that class, you have just one protected variable and you are able to add any functionality to the class just by adding a service.
A more complexer example with an ServiceContainer can be somthing like that
<?php
/**
* Class ServiceContainer
* Manage our services
*/
class ServiceContainer
{
private $serviceDefinition = array();
private $services = array();
public function addService($name, $class)
{
$this->serviceDefinition[$name] = $class;
}
public function getService($name)
{
if (!array_key_exists($name, $this->services)) {
if (!array_key_exists($name, $this->serviceDefinition)) {
throw new \RuntimeException(
sprintf(
'Unkown service "%s". Known services are %s.',
$name,
implode(', ', array_keys($this->serviceDefinition))
)
);
}
$this->services[$name] = new $this->serviceDefinition[$name];
}
return $this->services[$name];
}
}
/**
* Class Product
* Part of the Model. Nothing too complex
*/
class Product
{
public $id;
public $info;
/**
* Get info
*
* #return mixed
*/
public function getInfo()
{
return $this->info;
}
}
/**
* Class ProductManager
*
*/
class ProductManager
{
public function find($id)
{
$p = new Product();
$p->id = $id;
$p->info = 'Product info of product with id ' . $id;
return $p;
}
}
class UnusedBadService
{
public function _construct()
{
ThisWillProduceAnErrorOnExecution();
}
}
/**
* Class Page
* Handle this request.
*/
class Page
{
protected $container;
/**
* Set container
*
* #param ServiceContainer $container
*
* #return ContainerAware
*/
public function setContainer(ServiceContainer $container)
{
$this->container = $container;
return $this;
}
public function get($name)
{
return $this->container->getService($name);
}
public function createPage($productId)
{
$pm = $this->get('product_manager');
$productInfo = $pm->find($productId)->getInfo();
// do something to generate the page
return sprintf('<html><head></head><body><h1>%s</h1></body></html>', $productInfo);
}
}
$serviceContainer = new ServiceContainer();
// Add some services
$serviceContainer->addService('product_manager', 'ProductManager');
$serviceContainer->addService('unused_bad_service', 'UnusedBadService');
$page = new Page();
$page->setContainer($serviceContainer);
echo $page->createPage(1);
var_dump($page);
You can see, if you look at the var_dump output, that just the services, you called are in the output.
So this is small, fast and sexy ;)
I'm new to DI ,using Pimple. Using: php 5.3.5 (wamp), namespaces as well.
I'm refactoring code, using it, but came to a problem (s):
I have my Container that extends from Pimple, lets call it PContainer.php:
class ReuseableContainer extends Pimple{
private function initOutterClass(){
$this['special_location_class'] = '\SpecialLocation';
$this['special_location'] = function($c){return new $c['special_location_class']($c['location_details'],$c['location']);};
}
private function initGlobalFunctions(){
$this['getGeneralDataFromArray'] = function($c){
// returning a function
return function($arr){
foreach ($arr as $key => $value){
// do something
$new_data = $c['general_data_type'];
$new_data->id = $value['id'];
$new_data->name = $value['name'];
}
}
}
public function __construct(){
$this['location_class'] = '\Location';
$this['location_details_class'] = '\LocationDetails';
$this['general_data_type_class'] = '\GeneralDataType';
// define some objects
$this['location'] = function ($c) {
return new $c['location_class']();
};
$this['location_details'] = function ($c) {
return new $c['location_details_class']();
};
$this['general_data_type'] = function ($c) {
return new $c['general_data_type_class']();
};
$this->initOutterClass();
$this->initGlobalFunctions();
}
}
global $container ;
$container = new Pimple();
// embed the SomeContainer container
$container['embed'] = $container->share(function () { return new ReuseableContainer(); });
Ok. So i got a SpecialHelper.php which holds:
final class SpecialLocation{
public $name;
public $location;
public $picture;
public function __construct($location){
$this->location; // dependent on class: Location
}
}
final class SpecialUser{
private $id;
private $location;
public function __construct(\Location $location,$id=''){
$this->id = $id;
$this->location = $location; // $container['embed']['location'];
}
and we got our GeneralHelper.php which holds:
final class Location{
public $lat;
public $lng;
public function __construct($lat='',$lng=''){ $this->lat = $lat; $this->lng = $lng;}
}
final class LocationDetails{
public $id;
public $addresss;
public function __construct($id='',$address=''){$this->id = $id; $this->address = $address;}
}
class GeneralDataType{
public $id;
public $name;
public function getName(){ return $this->name;}
public function getId(){ return $this->id;}
}
and we have our "Special Class" controller, which looks something like this:
final class SpecialController{
public function foor($some_array){
$this->doSomething($some_array);
}
private function doSomething($ret_value){
// do something
$arr = array();
foreach($ret_value as $key => $value){
$something = $container['embed']['getGeneralDataFromArray']($value);
$special_location = $container['embed']['special_location'];
$arr[] = special_location;
}
return $arr;
}
}
Finally we have our main "driver", main.php
require('PContainer.php');
....
...
$some_array = array(....);
$special_controller = new SpecialController();
$special_controller->foor($some_array);
Problems:
1) I had to add initOutterClass function inside ReuseableContainer to decouple the "Special" classes, how could have i decoupled them in a better way? creating a new "special" 9container or something? as EVERYTHING now sitts inside the container.. same goes to the initGlobalFunctions()
2) regarding SpecialHelper.php: i have there SpecialLocation, which one of its properties is a \Location class, i've put it in the constructor , but if i have 20 object properties that are dependent, i must put them all as INPUT params for the constructor?? same goes to the SpecialUser class, it has a $location which if i could i would have made $this->location = $container['embed']['location']; instead of $this->location = $location; resulting in a dependent on the DI! :/
3) I've had to create SpecialHelper.php in a different file, despite wanting to put it in the "special class controller", just so there won't be any unknowns (due to require statement order)
4) MOST importantly: about the "Special class" controller, how do i solve the doSomething method? i must create "Special Location" object inside the loop but i get that $container is unrecognized (despite being global, as of scope probably) but more over it's really dependent! and it's a private function, i don't wish to pass the container to EVERY class i'll use from now on, it isn't IoC right?
Any help is appriciated... i'm trying to understand the best practices..
Thank you
4)Most important: IoC is correct. That an implementation is not correctly working does not reflect the principle of IoC itself.
If you want to use the global $container within a function, then should you use the global keyword within that function. That is how PHP works. Making it static is solving the problem of reference, but does not make a real difference.
An IoC container resolves the dependencies for the caller. The caller does not have to know anything about the internals of the callee - and he doesn't care either. So, there should be some kind of contract by which the exchange of data is regulated. If you have that situation, then you have IoC.
3)That problem is too vague to answer, but imo also not relevant from a practical perspective. Does it work? Ok, good to know. :-)
2)The clue of IoC is the use of contracts. The IoC container is there to connect the caller to the proper contract. The contract resolves to a concrete callee. The callee will return information inline with the contract. The caller understands the answer. Therefor will you need that the input and output in this process is independent of a certain implementation at a certain time. So don't use 20 object properties as input, but use an array or general object instead.
1) I get the idea that you are mixing functional flow (data flow) with technical flow (relationships between classes). An IoC container serves the purpose of the technical flow, it optimizes the dependency in the relationships between classes. For instance, if you want to connect to a database, then might you reuse an existing connection instead of creating new connections all the time. Or if you want to use a special functionality on several moments in your flow, then might you use IoC for that.