How can I pass an argument to the dependency injection container? - php

I have a class called GeneralReport, implementing HttpAccessibleDataGathererInterface, with a constructor and a method called calculate() that builds an array of plain PHP objects, containing the fields for a report. GeneralReport accepts two constructor arguments: one is an array of parameters, which contains the input used to construct the query. The second argument is another class, GeneralReportQueryBuilder, to build the query for the report based on the first argument.
Now, I would like the second argument to always be injected (I.E. the GeneralReportQueryBuilder). However, I want to somehow be able to pass in the first argument to the GeneralReport class in my controller. Keep in mind that the GeneralReport class implements an interface, so passing the array as an argument to the calculate() method is not an option. Also, since calling this class without an array of input would be pointless, it is not an optional dependency and creating a setter for it wouldn't make sense.
I would like to make the dependencies of GeneralReport clear and concise using its constructor, and don't want the DIC to get in the way.

There is a difference between :
creating a instance without X parameter
using an instance without X parameter
The first assertion is resolved by constructors parameters, the second by throwing an exception if during execution the parameter is not filled or valid!
Contextual parameters can be sent by setters !

Related

How does PHP function dependancy work?

I have an example here from the framework Laravel.
class UserController extends Controller
{
public function store(Request $request)
{
$name = $request->input("name");
}
}
What I don't understand is that Request is explicitly defined within the function signature of store().
Couldn't php's type inference figure out what is being passed into the function?
Would this even be considered dependency injection?
This is method level dependency injection. The Request is being passed into the store method, instead of the store method knowing how to create the Request object.
The type hint in the method signature does two things.
First, for plain PHP, it enforces the fact that the parameter passed to the store method must be a Request object, or an object that inherits from Request. If you attempt to call the store() method with anything other than a Request object (or descendant), you will get an error.
Because of this strict type enforcement, we know that the $request parameter must be a Request object (or descendant), and we can depend on its public interface being available for use (you know the input() method will exist).
Second, for Laravel, it tells the Laravel container what type of object this method needs, so that the correct object can be resolved out of the container and passed to the method. Laravel's controller methods are called through the container, which allows automatic dependency resolution of method calls.
If you were to remove the type hint, this would cause two issues. At an abstract level, it would allow you to pass any type of object, or even scalar values, to the store() method. If you attempt to call input() on a string, you're going to have problems. At a more concrete level, this will break Laravel's automatic dependency resolution of controller methods. Without the type hint, Laravel can't know what object the method requires, so it won't pass in anything, so in reality you'd get an error saying that you can't call input() on null (or an error saying the store method requires one parameter; not sure, didn't test).

How can I test this method call with PHPUnit?

Code to be tested:
// Add the activation provider argument to the factory definition
$factoryDefinition = $container->getDefinition('gremo_subscription_factory');
$factoryDefinition->addArgument(new Reference($providerId));
Test method should check the addArgument method, including $providerId argument. I'm just learining PHPUnit and right now I'm only able to call $this->anything():
$container->expects($this->at(3))
->method('getDefinition')
->with('gremo_subscription_factory')
->will($this->returnValue($factory));
$factory->expects($this->once())
->method('addArgument')
->with($this->anything());
$this->pass->process($container);
How can I check that argument type is Reference class, and (in turn) its argument is exactly the string $providerId?
This is pretty complicated, especially since the Reference class is not dependency injected and method call doesn't return anything. However, I think you can get around it using argument constraints. Here's how I would do that second clause:
$factory->expects($this->once())
->method('addArgument')
->with($this->logicalAnd(
$this->isInstanceOf('Reference'),
$this->attributeEqualTo('attribute', $providerId)
));
The second item in the logicalAnd() is basically just checking the Reference object that is created to see if $providerId gets assigned correctly (I'm not sure what happens to $providerId in the Reference constructor, but I'm assuming it gets saved to an instance variable or something).
This sort of thing, however, is moving into the territory of testing implementation details of the Reference class, so tests like this are not great for maintaining SRP. All of this would be better solved by refactoring your code. Generally speaking, if it's hard to test, it is probably not the test suite's fault. If you are able to, consider changing things on that end first, rather than writing overly-clever tests.

Passing object through constructor or method

So I have an object that could hold a string which contains some data, I have to validate the object or string but I cannot think why I would pass it into the method rather than the constructor, is there certain cases when I should pass it through the constructor rather than the method or through the method rather than the constructor ?
This is PHP code.
Constructor is also method differences is just in its usage. There are some things that we need to do at creation of an object of some class so for those things we pass object/setting arguments to constructor and when we need to process some data in context of that class we use method.
I think this was what u wanted to ask "difference b/w usage of method and constructor". If you got some coding issue tell me the scenario.
Pass an object into a constructor if you need it to be saved in the object for later use by one or more methods. Then, if you assign it, or a copy of it, to a member variable, it becomes part of the object's "state".
If the object to be passed is transient, and you only need it for the lifetime of the method, then you can pass it as an argument to the method.
Passing an arg through an object constructor has only one nominal benefit, and that is you are removing the extra step of calling setter(s) directly.
$foo = new Foo($dbo);
// Same thing as
$foo = new Foo();
$foo->setDbo($dbo);
Personally, I prefer to call setters explicitly rather than pass them through to the constructor because I don't want to end up with 15 signature arguments, and I can see how the calling code is using the object.
From time to time, I do use constructors for preparing related objects, but never for passing arguments.

Cannot pass the apiClient instance to another class with Codeigniter

I'm trying to load the Google calendar API which comes in PHP, but firstly to load the API, I need to pass the apiClient class instance to another class, I'm having no success at this since I'm using Codeigniter to load the apiClient library, but it fails to load the second class.
What I've done:
$this->load->library('google/apiClient', NULL, 'apiClient');
echo gettype($this->apiClient); // object
// This class needs the `apiClient` class to be passed to this, but fails
$this->load->library('google/contrib/apiCalendarService', $this->apiClient);
The error that gets thrown is this:
Argument 1 passed to apiCalendarService::__construct() must be an instance of apiClient, none given
However, if I do this:
$this->load->library('google/contrib/apiCalendarService', array($this->apiClient));
It will throw this error (passed the $this->apiClient in an array):
Argument 1 passed to apiCalendarService::__construct() must be an instance of apiClient, array given
What is the problem here? How can I specifically pass the apiClient to the apiCalendarService class?
The problem here is that CodeIgniter expects an array as the first parameter passed to all libraries, so not all classes fit in by default as CI libraries (particularly, ones that expect constructor parameters).
I see two options here:
Load the darned thing the "old way": include the class and instantiate new apiCalendarService($this->apiClient)
Create a wrapper class to "bridge the gap" between the two incompatible frameworks
Option #1 is two lines of code, by the way.

Check that mock's method is called without any parameters passed (in phpunit)

In phpunit we can specify the method was called with particular
->with($this->equalTo('foobar'))
or any
->with($this->anything())
parameter.
But is there a way to specify that the method has been called without parameters at all?
This is the test I expect to fail:
public function testZ()
{
$a = $this->getMock('q');
$a->expects($this->once())
->method('z')
->with(); // <--- what constraint to specify here?
$a->z(1);
}
UPD:
The question has theoretical nature, so I have no any real life example. Some case it could be useful I can think of right now is:
public function testMe($object)
{
$object->foo();
}
And let's assume that testMe should (by design and by requirements) always call the method without parameters (assuming foo() has default ones). Because any non-default parameter (more precise: any parameter != to default one, which we don't know yet and which probably could change independently) in this case causes fatal consequences.
While rdlowrey is correct that with() doesn't make provisions for checking for no arguments passed, the problem doesn't lie with PHPUnit but PHP itself.
First, if your method doesn't provide default values, the interpreter will raise a fatal error if you don't pass any parameters. This is expected and not entirely relevant to the question at hand, but it's important to state up front.
Second, if your method does provide default values, calling the method without arguments will cause PHP to alter the call before PHPUnit gets involved to pass the defaults instead. Here's a simple test that demonstrates PHP inserting itself before PHP can check the parameters. It's key to realize that the mock class that PHP creates has the same signature as the mocked class--including the defaults.
class MockTest extends PHPUnit_Framework_TestCase {
public function test() {
$mock = $this->getMock('Foo', array('bar'));
$mock->expects($this->once())
->method('bar')
->with() // does nothing, but it doesn't matter
->will($this->returnArgument(0));
self::assertEquals('foobar', $mock->bar()); // PHP inserts 1 and 2
// assertion fails because 1 != 'foobar'
}
}
class Foo {
public function bar($x = 1, $y = 2) {
return $x + $y;
}
}
This means you can verify that either nothing was passed or the default values were passed, but you cannot be more specific.
Can you get around this limitation? You can remove default values from arguments when overriding methods, so you should be able to create a subclass and mock it. Is it worth it? My initial gut reaction is that this is a huge code smell. Either your design or your tests are doing the Wrong Thing(tm).
If you can provide a real-world, concrete example where you actually need to do this kind of test, it's worth spending some time pondering a solution. Until then, I'm satisfied with the purely academic answer of "don't do that." :)
PHPUnit mock objects can only use the ->with() constraint method to verify that the count or respective values of passed parameters match those passed to the mocked method when invoked. You can't use it to require that no arguments are passed to the mock method.
The mock verification process specifically checks that the passed parameter count and the associated values are compatible with those passed to the with constraint. Also, as you've probably seen, specifying the with constraint without any values won't work either; if with receives no parameters it won't add any parameter constraints for verification.
You can see the actual PHPUnit_Framework_MockObject_Matcher_Parameters::verify method used to verify mock method parameters in the linked github source.
If validation of no passed arguments is a requirement you'll need to specify your own mock class to verify such a condition outside the PHPUnit mocking capabilities.

Categories