scribe: Url parameter access on formRequest does not work

Hello,

In a FormRequest, access directly to url parameter does not work (null). Here is a code example:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\Route;

class UserRequest extends FormRequest
{

    public function rules(): array
    {
        $this->user; // null

        $this->route('user'); // null

        Route::current()->parameters()['user']; // not null

    }
}

Only the third one works but i prefer to avoid this.

I ran php artisan scribe:generate

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 18 (8 by maintainers)

Most upvoted comments

Hi,

Same problem here, i have a basic formRequest with the following rule :

Rule::unique('users', 'login')->ignore($this->user->login, 'login')

And scribe throw exception cause $this->user is null

I have dived into your package and started to write a solution. My solution work but only if i specify an @urlParam

For example : route PUT /api/users/{user} and controller :

/**
* @urlParam user int required L'identifiant de l'utilisateur. Exemple: 300
*/
public function update(UserRequest $request, UserModel $user){ // ... }

And here the following update that i have wrote into scribe Knuckles\Scribe\Extracting\Strategies\GetFromFormRequestBase.php

    public function __invoke(ExtractedEndpointData $endpointData, array $routeRules = []): ?array
    {
        return $this->getParametersFromFormRequest($endpointData, $endpointData->method, $endpointData->route);
    }

    public function getParametersFromFormRequest(ExtractedEndpointData $endpointData, ReflectionFunctionAbstract $method, Route $route): array
    {
        if (!$formRequestReflectionClass = $this->getFormRequestReflectionClass($method)) {
            return [];
        }
        
        if (!$this->isFormRequestMeantForThisStrategy($formRequestReflectionClass)) {
            return [];
        }
        
        $className = $formRequestReflectionClass->getName();
        
        /*
         * Get the params name/example value 
         */
        $params = [];
        foreach($endpointData->urlParameters as $param) {
            $params[$param->name] = $param->example;
        }
    
        /**
         * instanciate a new request with the route parameters
         */
        $request = \Illuminate\Http\Request::create($route->uri(), $route->methods()[0], $params);
        
        if (Globals::$__instantiateFormRequestUsing) {
            $formRequest = call_user_func_array(Globals::$__instantiateFormRequestUsing, [$className, $route, $method]);
        } else {
            /**
             * instanciate a new form request
             */
            $formRequest = \Illuminate\Foundation\Http\FormRequest::createFrom($request, new $className);
        }
    
        /**
         * bind the route parameters to the form request
         */
        $route->bind($formRequest);
    
        /**
         * set the parameters to the route
         */
        foreach($endpointData->urlParameters as $param) {
            $route->setParameter($param->name, $param->example);
        }
    
        /**
         * convert params to model instances
         */
        app('router')->substituteBindings($route);
        app('router')->substituteImplicitBindings($route);
        
        $formRequest->server->set('REQUEST_METHOD', $route->methods()[0]);
        
        $parametersFromFormRequest = $this->getParametersFromValidationRules(
            $this->getRouteValidationRules($formRequest),
            $this->getCustomParameterData($formRequest)
        );
        
        return $this->normaliseArrayAndObjectParameters($parametersFromFormRequest);
    }

But i have found 2 problems and thats why i don’t PR your my solution. First : $endpointData->urlParameters have 2 keys, the one added by Strategies\UrlParameters\GetFromLaravelAPI::class and the second my @urlParams added by Strategies\UrlParameters\GetFromUrlParamTag::class, that don’t cause problem but it’s little strange that my @urlParams doesn’t override the first one ?

Second : in the case i don’t specify the @urlParams i have an not found exception throw by model binding cause GetFromLaravelAPI create a tag user_id and substitution doesn’t work.

Hope it help. 😃

I had a similar issue, which I was able to solve by using instantiateFormRequestUsing.

Here’s a simple example where the rules depend on a route param:

class SomeFormRequest extends FormRequest
{
    public Customer $customer;

    protected function prepareForValidation()
    {
        $this->customer = $this->route('customer');
    }

    public function rules()
    {
        return [
            'user_id' => ['required', Rule::exists('users', 'id')->where('customer_id', $this->customer->id)],
        ];
    }
}

And in AppServiceProvider’s boot method:

if (class_exists(Scribe::class)) {
    Scribe::instantiateFormRequestUsing(function (string $formRequestClassName) {
        if ($formRequestClassName === SomeFormRequest::class) {
            $formRequest = new $formRequestClassName();
            $formRequest->customer = new Customer();
        }

        return $formRequest;
    });
}

Note that the Scribe documentation’s example uses app()->makeWith() to instantiate the formrequest, which doesn’t work in my experience. When a FormRequest is resolved this way, it automatically triggers the ValidatesWhenResolved trait, meaning that a ValidationException is thrown if the rules don’t pass (which they probably won’t since there are no request params). But you can instantiate the FormRequest using new, and then add “mock” dependencies, before passing it back to Scribe.

So basically this workaround works by having the dependencies defined as class attributes, which are normally populated in prepareForValidation(), but can also be set manually.