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).
Related
I'm currently working on an API project. I'm used to Laravel and now I need to work with Symfony. I want to use the request like Laravel does for validation.
So I extend the Symfony\Component\HttpFoundation\Request class. There I made some logic to check and sanitize the incoming request.
After that I add my newly created request to the store function in the controller. But that gives me an error:
Argument 1 passed to App\Controller\Ticket\TicketController::store() must be an instance of App\Validation\Ticket\TicketStoreRequest, instance of Symfony\Component\HttpFoundation\Request given,/vendor/symfony/http-kernel/HttpKernel.php on line 149 {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Argument 1 passed to App\\Controller\\Ticket\\TicketController::store() must be an instance of App\\Validation\\Ticket\\TicketStoreRequest, instance of Symfony\\Component\\HttpFoundation\\Request given
After some googling, I found a few options.
Add every controller action to the service.yaml
Create a listener and add the validation on correct route
But all options require extra information on a different place. I hope someone has a better idea.
I found a few hints that might point you in the right direction:
Symfony ships with five value resolvers in the HttpKernel component:
...
RequestValueResolver
Injects the current Request if type-hinted with Request or a class extending Request.
see https://symfony.com/doc/current/controller/argument_value_resolver.html
Then the page goes on to describe the implementation of a custom RequestAttributeValueResolver. This custom resolver can be registered in your
services.yml.
Though in this example a single Class is created for one single attribute type (User), there are ways to create a more dynamic implementation.
In this example the ArgumentMetadata parameter has a method $argument->getType() that should contain a string representation of the type that is checked against:
if (User::class !== $argument->getType()) {
return false;
}
Nothing is stopping you from checking against an array of supported Request-types. This array can be managed as a class member in your custom RequestValueResolver. The only requirement for your custom RequestValueResolver class, is that the supports() method returns true for supported Request types, and that the resolve() function returns an instance of this supported Request type. This should be straightforward because both methods are supplied the 'desired' class through the ArgumentMetaData parameter.
As an alternative, you can implement a custom RequestValueResolver for each custom Request type you want to support, but this does not feel very elegant.
I can not guarantee this will work and I'm also not sure about the differences between implementing the RequestAttributeValueResolver in the example and implementing a custom RequestValueResolver, but I have a feeling it might just work, with a bit of elbow grease.
For reference: https://api.symfony.com/4.1/Symfony/Component/HttpKernel/Controller/ArgumentResolver/RequestValueResolver.html
Here is a solution that elaborates on my other example. It's not as secure as my other example, but it satisfies your requirements.
public function supports(Request $request, ArgumentMetadata $argument)
{
$desiredRequestClass = $argument->getType();
return class_exists($desiredRequestClass);
}
public function resolve(Request $request, ArgumentMetadata $argument)
{
$desiredRequestClass = $argument->getType();
$customRequest = new $desiredRequestClass();
$customRequest->createFromGlobals();
// TODO: more initialization you might need.
yield $customRequest;
}
It might be advisable to check if $desiredRequestClass is a descendant of Request.
PHP is not a strict typing language right? I am working with Laravel 5.1 and PHP 7.2
I would make my route like this
Route::get('/getSizes', 'JobTickeController#getSizes')->name('getSizes');
in JobTicketController there I define my function as below
public function getSizes(Request $response){
}
My question is even if PHP is not a strict typing (by default) why should I define Request variable type before $request variable? This code throws an exception with out the type hinting Request.
Although others go into the fact that PHP is not a strictly typed language, there's no answer to why the type hinting is necessary.
What happens in your Laravel app is the following. Laravel will attempt to hit the getSizes method in your controller by resolving any typehinted arguments from "the (Service) Container" also known as IoC. In case it finds no bindings, it will attempt to instantiate the class. Any arguments that have no type hinting are assumed to be "route parameters".
As your route has no parameters, the $request cannot be given any value without being typehinted. When you type hint the $request argument with the Illuminate\Http\Request class Laravel will automatically resolve that binding from its container and make it available inside your method.
TLDR:
Your exception is thrown in case you don't typehint because there's no route parameter to use as value.
Using typehinting will have Laravel automatically resolve a binding from the container or instantiate the class if it has not been bound.
See Method Inject in the Documentation.
First there is nothing named Loose Type, but you can say PHP is not a strictly typed language like JAVA for example.
In your example, that is a Type Hinting, so here you force the function to accept parameters with type Request only.
I'm mocking a UserRepository class using prophecy to ensure that when a POST request to /user is sent, that the create() method on UserRepository is fired.
$repository = $this->prophesize(UserRepository::class);
$repository->create()->shouldBeCalled()
The only problem is that the create() method sends Request data as an argument to the repository for some serious tweaking of the inputs before doing anything. How do I mock the create() call without telling prophecy what the arguments will be?
Or is this just really bad practice on my end and the Request data should never be passed to the repository?
use Prophecy\Argument;
$repository->create(Argument::any())->shouldBeCalled()
use Prophecy\Argument;
$repository->create(Argument::cetera())->shouldBeCalled()
any() matches any single value where cetera matches all values to the rest of the signature.
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 !
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.