Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BoundMethod::call() sends parameters in wrong order #22687

Closed
tssge opened this issue Jan 8, 2018 · 7 comments
Closed

BoundMethod::call() sends parameters in wrong order #22687

tssge opened this issue Jan 8, 2018 · 7 comments

Comments

@tssge
Copy link

tssge commented Jan 8, 2018

  • Laravel Version: 5.5
  • PHP Version: 7.1
  • Database Driver & Version: MySQL 5.7 / PDO

Description:

BoundMethod::call() will send parameters to the function in wrong order in some cases. If a function parameter has a default value set and one precedes it with no default value, the default value from the second parameter will be applied to the first parameter.

Steps To Reproduce:

//... some class ...

public function list(string $url, string $type = 'all'): void
{
    var_dump($url);
    var_dump($type);
}

//... some container ...
$this->app->call("SomeClass@list", ['testparam']);

This will result in output:

string 'all' (length=3)
string 'testparam' (length=24)

So the default value for $type was sent to $url parameter instead.

Explanation:

This is because in BoundMethod on line 119

      return array_merge($dependencies, $parameters);

the results from depedency resolution are merged with no order at all. The dependencies are resolved first and after that the parameters so no order is taken into account.

How to fix:

In function addDepdencyForCallParameter take given parameters into account and fill dependecies in such a way that the order is preserved.

tssge added a commit to tssge/container that referenced this issue Jan 8, 2018
@tssge
Copy link
Author

tssge commented Jan 8, 2018

I have created a patch and can create pull request if needed. Just waiting for some input.

@sisve
Copy link
Contributor

sisve commented Jan 8, 2018

Uhm. Let there be input! And there was input!

What do you want input on? Submit the PR with the tests, and see if your solutions is approved.

tssge added a commit to tssge/framework that referenced this issue Jan 8, 2018
tssge added a commit to tssge/framework that referenced this issue Jan 9, 2018
tssge added a commit to tssge/framework that referenced this issue Jan 9, 2018
@tssge
Copy link
Author

tssge commented Jan 9, 2018

No, it does not pass the tests. I am currently trying to figure out why this test

    public function testCallWithCallableArray()
    {
        $container = new Container;
        $stub = new ContainerTestCallStub;
        $result = $container->call([$stub, 'work'], ['foo', 'bar']);
        $this->assertEquals(['foo', 'bar'], $result);
    }

expects that this function

    public function work()
    {
        return func_get_args();
    }

returns 2 arguments even though it accepts none. To me this seems like really strange behavior: a function that accepts 0 arguments is passed 2 arguments and is expected to return correct values for those 2 arguments it never accepts.

Can you shine some light on why such a test exists and why a function that doesn't accept arguments should be tricked to accept arguments by Laravel framework?

@sisve
Copy link
Contributor

sisve commented Jan 9, 2018

Php allows you to send an infinite amount of parameters to a function. (Or at least more than those that are declared.) The code can then use func_get_args to retrieve all arguments sent.

Perhaps it's easier understood as a variadic function; public function work(...$params) { return $params; }.

The test verifies that 1) a callable array [$stub, 'work'] can be used and, 2) the parameters you passed to $container->call is forwarded to the method.

@tssge
Copy link
Author

tssge commented Jan 9, 2018

I was about to use addDependencyForCallParameter function to handle the parameters and dependencies, as this function already handles parameter default values. But this particular function checks the argument count instead of blindly merging dependencies with parameters and then passing this array to the function as arguments.

This is why the test doesn't pass, because the arguments given don't match the count of arguments taken.

Should such check be removed, or should we never check the argument count at all instead?

By the way, should we throw BindingResolutionException if there are parameters that cannot be resolved? Currently BoundMethod does not do so and instead let's PHP do whatever error it does when mandatory arguments are missing. This behvaior is inconsistent with the behavior of Container, which throws BindingResolutionException if an argument is mandatory and cannot be resolved.

@sisve
Copy link
Contributor

sisve commented Jan 9, 2018

I cannot answer you about specifics on the BoundMethod class, I do not know enough about it. Try #internals on Slack.

tssge added a commit to tssge/framework that referenced this issue Jan 9, 2018
For more information see
laravel#22687

We will first resolve the dependencies normal way, but in this commit we
will keep track on the order of the parameters.

Then we will loop through given parameters if all were not consumed and consume these if
we have some unfilled parameters left in our function call.

This way we prevent the default value of some parameter being applied to
the wrong position.

We will need to loop the parameters outside of
addDependencyForCallParameter, because calls to this function check for
parameter count of the function and send the ReflectionParameter as
argument. Because of according to test
ContainerTest::testCallWithCallableArary we must give parameters to
function even if the function parameter count mismatches, we cannot
perform this inside of addDependencyCallParameter.
tssge added a commit to tssge/framework that referenced this issue Jan 9, 2018
For more information see
laravel#22687

We will first resolve the dependencies normal way, but in this commit we
will keep track on the order of the parameters.

Then we will loop through given parameters if all were not consumed and consume these if
we have some unfilled parameters left in our function call.

This way we prevent the default value of some parameter being applied to
the wrong position.

We will need to loop the parameters outside of
addDependencyForCallParameter, because calls to this function check for
parameter count of the function and send the ReflectionParameter as
argument. Because of according to test
ContainerTest::testCallWithCallableArary we must give parameters to
function even if the function parameter count mismatches, we cannot
perform this inside of addDependencyCallParameter.
tssge added a commit to tssge/framework that referenced this issue Jan 9, 2018
For more information see
laravel#22687

We will first resolve the dependencies normal way, but in this commit we
will keep track on the order of the parameters.

Then we will loop through given parameters if all were not consumed and consume these if
we have some unfilled parameters left in our function call.

This way we prevent the default value of some parameter being applied to
the wrong position.

We will need to loop the parameters outside of
addDependencyForCallParameter, because calls to this function check for
parameter count of the function and send the ReflectionParameter as
argument. Because of according to test
ContainerTest::testCallWithCallableArary we must give parameters to
function even if the function parameter count mismatches, we cannot
perform this inside of addDependencyCallParameter.
tssge added a commit to tssge/framework that referenced this issue Jan 9, 2018
For more information see
laravel#22687

We will first resolve the dependencies normal way, but in this commit we
will keep track on the order of the parameters.

Then we will loop through given parameters if all were not consumed and consume these if
we have some unfilled parameters left in our function call.

This way we prevent the default value of some parameter being applied to
the wrong position.

We will need to loop the parameters outside of
addDependencyForCallParameter, because calls to this function check for
parameter count of the function and send the ReflectionParameter as
argument. Because of according to test
ContainerTest::testCallWithCallableArary we must give parameters to
function even if the function parameter count mismatches, we cannot
perform this inside of addDependencyCallParameter.
@themsaid
Copy link
Member

Closing since the PR was rejected, this usage case isn't documented anywhere.

taylorotwell pushed a commit that referenced this issue Dec 17, 2018
* Update BoundMethod.php

Bug fix for app()->call('class@method');

fixes:
#22687

* Update ContainerTest.php

Added tests for app()->call() with default values in the called method

* Update ContainerTest.php

fixes style-ci issues

* fixes the tests

Fixes the tests

* Fixes the bug caught by tests

* more tests to be more sure

* Fixes style-CI issue

* better tests + typo fix

* Passes the most recent test (defaultOnlyC)

* More tests covering more cases

* Adds more tests

Adds more tests

* Update ContainerTest.php

Fix Style-ci
taylorotwell pushed a commit to illuminate/container that referenced this issue Dec 17, 2018
* Update BoundMethod.php

Bug fix for app()->call('class@method');

fixes:
laravel/framework#22687

* Update ContainerTest.php

Added tests for app()->call() with default values in the called method

* Update ContainerTest.php

fixes style-ci issues

* fixes the tests

Fixes the tests

* Fixes the bug caught by tests

* more tests to be more sure

* Fixes style-CI issue

* better tests + typo fix

* Passes the most recent test (defaultOnlyC)

* More tests covering more cases

* Adds more tests

Adds more tests

* Update ContainerTest.php

Fix Style-ci
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants