Skip to content

Container calls afterResolving callback twice when a contract gets resolved #25043

@ewstefan

Description

@ewstefan
  • Laravel Version: 5.6.29
  • PHP Version: 7.2.7-1
  • Database Driver & Version: MySQL / not relevant

Container calls afterResolving callback twice when a contract gets resolved

Demonstrated here by using FormRequests, which validate twice as a result of the problem.
(Also see ContainerTest below.)

A request that is built via reflection gets validated once - as expected.
A request that is built via resolving a registered contract gets validated twice.

Steps To Reproduce:

Create the following classes in their default directories
in a fresh Laravel installation.

(I installed via composer)

  1. Create a DemoRule
class DemoRule implements Rule
{
    public function passes($attribute, $value)
    {
        echo "Verified rule with $attribute: and value: $value";
        echo '<br/><br/>';
        return true;
    }

    public function message()
    {
        return [];
    }
}

  1. Create a ResolveViaServiceContract
interface ResolveViaServiceContract
{

}
  1. Create ResolveViaServiceContractRequest
class ResolveViaServiceContractRequest extends FormRequest implements ResolveViaServiceContract
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'demoAttribute' => [new DemoRule()]
        ];
    }
}
  1. To demonstrate that resolution via reflection validates once as expected
    create a ResolveViaReflectionRequest
class ResolveViaReflectionRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'demoAttribute' => [new DemoRule()]
        ];
    }
}
  1. Register the contract in your AppServiceProvider
class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton('App\Http\Requests\ResolveViaServiceContract', ResolveViaServiceContractRequest::class);
    }
}
  1. Add forms to your welcome.blade
<body>
<form action="/reflection" method="post">
        @csrf
        <input type="text" name="demoAttribute">
        <input type="submit" value="Via Reflection">
    </form>

    <form action="/service" method="post">
        @csrf
        <input type="text" name="demoAttribute">
        <input type="submit" value="Via ServiceContract">
    </form>

...
</body>
  1. Extend the web routes
Route::get('/', function () {return view('welcome');});

Route::post('/reflection', 'DemoController@resolveViaReflection');
Route::post('/service', 'DemoController@resolveViaServiceContract');
  1. Add a DemoController to handle the routes and to have the requests built
class DemoController extends Controller
{

    public function resolveViaReflection(ResolveViaReflectionRequest $request)
    {
        return "Done via reflection";
    }

    public function resolveViaServiceContract(ResolveViaServiceContract $request)
    {
        return "Done via service contract";
    }
}
  1. Start the app
    http://localhost

  2. Hit the "Via Reflection" button
    Observe "Verified rule with demoAttribute: and value:" is printed once - as expected.

  3. Hit the "Via Service Contract" button
    Observe "Verified rule with demoAttribute: and value:" is printed twice.
    Expected the request to also be validated only once.

Conclusion

While this validates the request more often than needed, it is also a problem when you have a validation rule that evaluates to true the first time, but cannot be executed a second time and evaluates to false. As in my case with Google reCaptcha.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions