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

Support auto generete with laravel-data #872

Open
1 task done
tanmnt opened this issue Jul 18, 2024 · 3 comments
Open
1 task done

Support auto generete with laravel-data #872

tanmnt opened this issue Jul 18, 2024 · 3 comments
Labels
enhancement New feature or request feature-request

Comments

@tanmnt
Copy link

tanmnt commented Jul 18, 2024

Scribe version

4.37.1

Your question

Is there a way I can auto-generate requests use laravel-data like using FormRequest?

  • UserController.php
    /**
     * Create user
     *
     * @apiResourcedResource status=201
     *
     * @apiResourceModel App\Models\User
     *
     * @apiResourceAdditional message="Create successfully!"
     */
    public function store(CreateUserDto $createUserDto): JsonResponse
    {
        // Create user
    }
  • CreateUserDto.php
class CreateUserDto extends Data
{
    public function __construct(public string $name, public string $email, public string $password, public int $role_id)
    {
    }

    public static function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:191'],
            'email' => ['required', 'string', 'email', 'max:191', Rule::unique('users')->withoutTrashed()],
            'role_id' => ['required', 'exists:roles,id'],
            'password' => [
                'required',
                'confirmed',
                'max:191',
                Password::min(8)->letters()->mixedCase()->symbols()->numbers(),
            ],
        ];
    }
}

Docs

@tanmnt tanmnt added question Further information is requested triage labels Jul 18, 2024
@shalvah
Copy link
Contributor

shalvah commented Jul 19, 2024

I think someone mentioned it once, but no, there are currently no plans. Feel free to add it!

@shalvah shalvah added enhancement New feature or request feature-request and removed question Further information is requested triage labels Jul 19, 2024
@kiboko-dev
Copy link

Very necessary functionality, because there are no developments supporting the current version of the Laravel Data package. And there is no desire to leave Scribe.

@ravibpatel
Copy link

You can add support for Laravel Data using custom strategies for Query Parameters and Body Parameters.

App\Scribe\Extracting\Strategies\BodyParameters\GetFromLaravelData.php

<?php

namespace App\Scribe\Extracting\Strategies\BodyParameters;

use App\Scribe\Extracting\Strategies\GetFromLaravelDataBase;
use ReflectionClass;

class GetFromLaravelData extends GetFromLaravelDataBase
{
    protected string $customParameterDataMethodName = 'bodyParameters';

    protected function isLaravelDataMeantForThisStrategy(ReflectionClass $laravelDataReflectionClass): bool
    {
        // Only use this FormRequest for body params if there's no "Query parameters" in the docblock
        // Or there's a bodyParameters() method
        $formRequestDocBlock = $laravelDataReflectionClass->getDocComment();
        if (str_contains(strtolower($formRequestDocBlock), "query parameters")
            || $laravelDataReflectionClass->hasMethod('queryParameters')) {
            return false;
        }

        return true;
    }
}

App\Scribe\Extracting\Strategies\QueryParameters\GetFromLaravelData.php

<?php

namespace App\Scribe\Extracting\Strategies\QueryParameters;

use App\Scribe\Extracting\Strategies\GetFromLaravelDataBase;
use ReflectionClass;

class GetFromLaravelData extends GetFromLaravelDataBase
{
    protected string $customParameterDataMethodName = 'queryParameters';

    protected function isLaravelDataMeantForThisStrategy(ReflectionClass $laravelDataReflectionClass): bool
    {
        // Only use this FormRequest for query params if there's "Query parameters" in the docblock
        // Or there's a queryParameters() method
        $formRequestDocBlock = $laravelDataReflectionClass->getDocComment();
        if (str_contains(strtolower($formRequestDocBlock), "query parameters")) {
            return true;
        }

        return parent::isLaravelDataMeantForThisStrategy($laravelDataReflectionClass);
    }
}

App\Scribe\Extracting\Strategies\GetFromLaravelDataBase.php

<?php

namespace App\Scribe\Extracting\Strategies;

use Illuminate\Routing\Route;
use Knuckles\Camel\Extraction\ExtractedEndpointData;
use Knuckles\Scribe\Extracting\ParsesValidationRules;
use Knuckles\Scribe\Extracting\Strategies\Strategy;
use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
use ReflectionClass;
use ReflectionException;
use ReflectionFunctionAbstract;
use ReflectionUnionType;
use Spatie\LaravelData\Data;

class GetFromLaravelDataBase extends Strategy
{
    use ParsesValidationRules;

    protected string $customParameterDataMethodName = '';

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

    private function getParametersFromLaravelData(ReflectionFunctionAbstract $method, Route $route): array
    {
        if (!$laravelDataReflectionClass = $this->getLaravelDataReflectionClass($method)) {
            return [];
        }

        if (!$this->isLaravelDataMeantForThisStrategy($laravelDataReflectionClass)) {
            return [];
        }

        $className = $laravelDataReflectionClass->getName();

        $laravelData = new $className;

        $parametersFromLaravelData = $this->getParametersFromValidationRules(
            $this->getRouteValidationRules($laravelData),
            $this->getCustomParameterData($laravelData)
        );

        return $this->normaliseArrayAndObjectParameters($parametersFromLaravelData);
    }

    protected function getRouteValidationRules(Data $data)
    {
        if (method_exists($data, 'getValidationRules')) {
            $properties = get_object_vars($data);
            return app()->call([$data, 'getValidationRules'], ['payload' => $properties]);
        }

        return [];
    }

    protected function getCustomParameterData(Data $data)
    {
        if (method_exists($data, $this->customParameterDataMethodName)) {
            return call_user_func_array([$data, $this->customParameterDataMethodName], []);
        }

        c::warn("No {$this->customParameterDataMethodName}() method found in " . get_class($data) . ". Scribe will only be able to extract basic information from the rules() method.");

        return [];
    }

    protected function getMissingCustomDataMessage($parameterName): string
    {
        return "No data found for parameter '$parameterName' in your {$this->customParameterDataMethodName}() method. Add an entry for '$parameterName' so you can add a description and example.";
    }

    protected function getLaravelDataReflectionClass(ReflectionFunctionAbstract $method): ?ReflectionClass
    {
        foreach ($method->getParameters() as $argument) {
            $argType = $argument->getType();
            if ($argType === null || $argType instanceof ReflectionUnionType) continue;

            $argumentClassName = $argType->getName();

            if (!class_exists($argumentClassName)) continue;

            try {
                $argumentClass = new ReflectionClass($argumentClassName);
            } catch (ReflectionException $e) {
                continue;
            }

            if (
                (class_exists(Data::class) && $argumentClass->isSubclassOf(Data::class))) {
                return $argumentClass;
            }
        }

        return null;
    }

    protected function isLaravelDataMeantForThisStrategy(ReflectionClass $laravelDataReflectionClass): bool
    {
        return $laravelDataReflectionClass->hasMethod($this->customParameterDataMethodName);
    }
}

App\Data\TestData.php

<?php

namespace App\Data;

use Spatie\LaravelData\Attributes\MapName;
use Spatie\LaravelData\Attributes\Validation\Min;
use Spatie\LaravelData\Data;
use Spatie\LaravelData\Mappers\SnakeCaseMapper;

#[MapName(SnakeCaseMapper::class)]
class TestData extends Data
{
    public function __construct(
        #[Min(1)]
        public string $name,
    ) {}

    public function bodyParameters()
    {
        return [
            'name' => [
                'description' => 'The name of the test data',
                'example' => 'Test Name',
            ],
        ];
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request feature-request
Projects
None yet
Development

No branches or pull requests

4 participants