Browse Source

Scribe config v2

Shalvah 1 year ago
parent
commit
fcd078e2c9

+ 104 - 0
config/scribe_new.php

@@ -0,0 +1,104 @@
+<?php
+
+use Knuckles\Scribe\Extracting\Strategies;
+use Knuckles\Scribe;
+
+/**
+ * For documentation, use your IDE's features, or see https://scribe.knuckles.wtf/laravel/reference/config
+ */
+
+return Scribe\Config\Factory::make(
+    extracting: Scribe\Config\Extracting::with(
+        routes: Scribe\Config\Routes::match(
+            prefixes: ['api/*'],
+            domains: ['*'],
+            alwaysInclude: [],
+            alwaysExclude: [],
+        ),
+        defaultGroup: 'Endpoints',
+        databaseConnectionsToTransact: [config('database.default')],
+        fakerSeedForExamples: null,
+        dataSourcesForExampleModels: ['factoryCreate', 'factoryMake', 'databaseFirst'],
+        auth: Scribe\Config\Extracting::auth(
+            enabled: false,
+            default: false,
+            in: 'bearer',
+            useValue: env('SCRIBE_AUTH_KEY'),
+            placeholder: '{YOUR_AUTH_KEY}',
+            extraInfo: <<<AUTH
+        You can retrieve your token by visiting your dashboard and clicking <b>Generate API token</b>.
+        AUTH
+        ),
+        strategies: Scribe\Config\Extracting::strategies(
+            metadata: Scribe\Config\Defaults::metadataStrategies(),
+            urlParameters: Scribe\Config\Defaults::urlParametersStrategies(),
+            queryParameters: Scribe\Config\Defaults::queryParametersStrategies(),
+            headers: Scribe\Config\Defaults::headersStrategies()
+                ->override([
+                    'Content-Type' => 'application/json',
+                    'Accept' => 'application/json',
+                ]),
+            bodyParameters: Scribe\Config\Defaults::bodyParametersStrategies(),
+            responses: Scribe\Config\Defaults::responsesStrategies()
+                ->configure(Strategies\Responses\ResponseCalls::withSettings(
+                    only: ['GET *'],
+                    config: [
+                        'app.env' => 'documentation',
+                        // 'app.debug' => false,
+                    ],
+                    queryParams: [],
+                    bodyParams: [],
+                    fileParams: [],
+                    cookies: [],
+                )),
+            responseFields: Scribe\Config\Defaults::responseFieldsStrategies(),
+        )
+    ),
+    output: Scribe\Config\Output::with(
+        theme: 'default',
+        title: null,
+        description: '',
+        baseUrls: [
+            "production" => config("app.base_url"),
+        ],
+        exampleLanguages: ['bash', 'javascript'],
+        logo: false,
+        lastUpdated: 'Last updated: {date:F j, Y}',
+        introText: <<<INTRO
+    This documentation aims to provide all the information you need to work with our API.
+
+    <aside>As you scroll, you'll see code examples for working with the API in different programming languages in the dark area to the right (or as part of the content on mobile).
+    You can switch the language used with the tabs at the top right (or from the nav menu at the top left on mobile).</aside>
+    INTRO,
+        groupsOrder: [
+            // 'This group will come first',
+            // 'This group will come next' => [
+            //     'POST /this-endpoint-will-come-first',
+            //     'GET /this-endpoint-will-come-next',
+            // ],
+            // 'This group will come third' => [
+            //     'This subgroup will come first' => [
+            //         'GET /this-other-endpoint-will-come-first',
+            //     ]
+            // ]
+        ],
+        type: Scribe\Config\Output::staticType(
+            outputPath: 'public/docs'
+        ),
+        postman: Scribe\Config\Output::postman(
+            enabled: true,
+            overrides: [
+                // 'info.version' => '2.0.0',
+            ]
+        ),
+        openApi: Scribe\Config\Output::openApi(
+            enabled: true,
+            overrides: [
+                // 'info.version' => '2.0.0',
+            ]
+        ),
+        tryItOut: Scribe\Config\Output::tryItOut(
+            enabled: true,
+        )
+    )
+);

+ 2 - 2
src/Commands/GenerateDocumentation.php

@@ -105,10 +105,10 @@ class GenerateDocumentation extends Command
             throw new \InvalidArgumentException("The specified config (config/{$configName}.php) doesn't exist.");
         }
 
-        $this->paths = new PathConfig(configName: $configName);
+        $this->paths = new PathConfig($configName);
         if ($this->hasOption('scribe-dir') && !empty($this->option('scribe-dir'))) {
             $this->paths = new PathConfig(
-                configName: $configName, scribeDir: $this->option('scribe-dir')
+                $configName, scribeDir: $this->option('scribe-dir')
             );
         }
 

+ 76 - 0
src/Config/Defaults.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace Knuckles\Scribe\Config;
+
+use Knuckles\Scribe\Extracting\Strategies;
+
+class Defaults
+{
+    public static function metadataStrategies(): StrategyListWrapper
+    {
+        return new StrategyListWrapper([
+            Strategies\Metadata\GetFromDocBlocks::class,
+            Strategies\Metadata\GetFromMetadataAttributes::class,
+        ]);
+    }
+
+    public static function urlParametersStrategies(): StrategyListWrapper
+    {
+        return new StrategyListWrapper([
+            Strategies\UrlParameters\GetFromLaravelAPI::class,
+            Strategies\UrlParameters\GetFromLumenAPI::class,
+            Strategies\UrlParameters\GetFromUrlParamAttribute::class,
+            Strategies\UrlParameters\GetFromUrlParamTag::class,
+        ]);
+    }
+
+    public static function queryParametersStrategies(): StrategyListWrapper
+    {
+        return new StrategyListWrapper([
+            Strategies\QueryParameters\GetFromFormRequest::class,
+            Strategies\QueryParameters\GetFromInlineValidator::class,
+            Strategies\QueryParameters\GetFromQueryParamAttribute::class,
+            Strategies\QueryParameters\GetFromQueryParamTag::class,
+        ]);
+    }
+
+    public static function headersStrategies(): StrategyListWrapper
+    {
+        return new StrategyListWrapper([
+            Strategies\Headers\GetFromRouteRules::class,
+            Strategies\Headers\GetFromHeaderAttribute::class,
+            Strategies\Headers\GetFromHeaderTag::class,
+        ]);
+    }
+
+    public static function bodyParametersStrategies(): StrategyListWrapper
+    {
+        return new StrategyListWrapper([
+            Strategies\BodyParameters\GetFromFormRequest::class,
+            Strategies\BodyParameters\GetFromInlineValidator::class,
+            Strategies\BodyParameters\GetFromBodyParamAttribute::class,
+            Strategies\BodyParameters\GetFromBodyParamTag::class,
+        ]);
+    }
+
+    public static function responsesStrategies(): StrategyListWrapper
+    {
+        return new StrategyListWrapper([
+            Strategies\Responses\UseResponseAttributes::class,
+            Strategies\Responses\UseTransformerTags::class,
+            Strategies\Responses\UseApiResourceTags::class,
+            Strategies\Responses\UseResponseTag::class,
+            Strategies\Responses\UseResponseFileTag::class,
+            Strategies\Responses\ResponseCalls::class,
+        ]);
+    }
+
+    public static function responseFieldsStrategies(): StrategyListWrapper
+    {
+        return new StrategyListWrapper([
+            Strategies\ResponseFields\GetFromResponseFieldAttribute::class,
+            Strategies\ResponseFields\GetFromResponseFieldTag::class,
+        ]);
+    }
+
+}

+ 63 - 0
src/Config/Extracting.php

@@ -0,0 +1,63 @@
+<?php
+
+namespace Knuckles\Scribe\Config;
+
+use Illuminate\Support\Arr;
+
+class Extracting
+{
+    public function __construct(
+        public Routes  $routes,
+        public string  $defaultGroup = 'Endpoints',
+        public array   $databaseConnectionsToTransact = [],
+        public ?int    $fakerSeedForExamples = null,
+        public array   $dataSourcesForExampleModels = ['factoryCreate', 'factoryMake', 'databaseFirst'],
+        public ?string $routeMatcher = null,
+        public ?string $fractalSerializer = null,
+        public array   $auth = [],
+        public array   $strategies = [],
+    )
+    {
+    }
+
+    public static function auth(
+        bool   $enabled = true,
+        bool   $default = false,
+        string $in = 'bearer',
+        string $name = 'key',
+        string $useValue = null,
+        string $placeholder = '{YOUR_AUTH_KEY}',
+        string $extraInfo = ''
+    ): array
+    {
+        return get_defined_vars();
+    }
+
+    public static function strategies(
+        StrategyListWrapper $metadata,
+        StrategyListWrapper $urlParameters,
+        StrategyListWrapper $queryParameters,
+        StrategyListWrapper $headers,
+        StrategyListWrapper $bodyParameters,
+        StrategyListWrapper $responses,
+        StrategyListWrapper $responseFields,
+    ): array
+    {
+        return Arr::map(get_defined_vars(), fn($listWrapper) => $listWrapper->toArray());
+    }
+
+    public static function with(
+        Routes $routes,
+        string $defaultGroup = 'Endpoints',
+        array $databaseConnectionsToTransact = [],
+        int $fakerSeedForExamples = null,
+        array $dataSourcesForExampleModels = ['factoryCreate', 'factoryMake', 'databaseFirst'],
+        string $routeMatcher = null,
+        string $fractalSerializer = null,
+        array   $auth = [],
+        array   $strategies = [],
+    ): Extracting
+    {
+        return new self(...get_defined_vars());
+    }
+}

+ 11 - 0
src/Config/Factory.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace Knuckles\Scribe\Config;
+
+class Factory
+{
+    public static function make(Extracting $extracting, Output $output): array
+    {
+        return Serializer::toOldConfig($extracting, $output);
+    }
+}

+ 87 - 0
src/Config/Output.php

@@ -0,0 +1,87 @@
+<?php
+
+namespace Knuckles\Scribe\Config;
+
+class Output
+{
+    public static function with(
+        string $theme = 'default',
+        string $title = null,
+        string $description = '',
+        array  $baseUrls = [],
+        array  $exampleLanguages = ['bash', 'javascript'],
+        bool   $logo = false,
+        string $lastUpdated = 'Last updated: {date:F j, Y}',
+        string $introText = "",
+        array  $groupsOrder = [],
+        ?array $type = null, /* tuple */
+        array  $postman = ['enabled' => true],
+        array  $openApi = ['enabled' => true],
+        array  $tryItOut = ['enabled' => true],
+    ): static
+    {
+        return new static(...get_defined_vars());
+    }
+
+    public function __construct(
+        public string  $theme = 'default',
+        public ?string $title = null,
+        public string  $description = '',
+        public array   $baseUrls = [], /* If empty, Scribe will use config('app.url') */
+        public array   $groupsOrder = [],
+        public string  $introText = "",
+        public array   $exampleLanguages = ['bash', 'javascript'],
+        public bool    $logo = false,
+        public string  $lastUpdated = 'Last updated: {date:F j, Y}',
+
+        public ?array  $type = null, /* tuple */
+        public array   $postman = ['enabled' => true],
+        public array   $openApi = ['enabled' => true],
+        public array   $tryItOut = ['enabled' => true],
+    )
+    {
+    }
+
+    public static function laravelType(
+        bool   $addRoutes = true,
+        string $docsUrl = '/docs',
+        string $assetsDirectory = null,
+        array  $middleware = [],
+    ): array
+    {
+        return ['laravel', get_defined_vars()];
+    }
+
+    public static function staticType(
+        string $outputPath = 'public/docs',
+    ): array
+    {
+        return ['static', get_defined_vars()];
+    }
+
+    public static function postman(
+        bool  $enabled = true,
+        array $overrides = [],
+    ): array
+    {
+        return get_defined_vars();
+    }
+
+    public static function openApi(
+        bool  $enabled = true,
+        array $overrides = [],
+    ): array
+    {
+        return get_defined_vars();
+    }
+
+    public static function tryItOut(
+        bool   $enabled = true,
+        string $baseUrl = null,
+        bool   $useCsrf = false,
+        string $csrfUrl = '/sanctum/csrf-cookie',
+    ): array
+    {
+        return get_defined_vars();
+    }
+}

+ 27 - 0
src/Config/Routes.php

@@ -0,0 +1,27 @@
+<?php
+
+namespace Knuckles\Scribe\Config;
+
+class Routes
+{
+    public static function match(
+        array $prefixes = ['api/*'],
+        array $domains = ['*'],
+        array $dingoVersions = ['v1'],
+        array $alwaysInclude = [],
+        array $alwaysExclude = [],
+    ): static
+    {
+        return new static(...get_defined_vars());
+    }
+
+    public function __construct(
+        public array $prefixes = [],
+        public array $domains = [],
+        public array $dingoVersions = [],
+        public array $alwaysInclude = [],
+        public array $alwaysExclude = []
+    )
+    {
+    }
+}

+ 78 - 0
src/Config/Serializer.php

@@ -0,0 +1,78 @@
+<?php
+
+namespace Knuckles\Scribe\Config;
+
+use Illuminate\Support\Arr;
+use Illuminate\Support\Str;
+
+class Serializer
+{
+    // todo new features & breaking changes:
+    // - strategy config tuples - responseCalls support
+    // - support route method + path in route matching
+    // - no more route groups, header apply rules move to override
+    // - no more apply rules for response_calls, use strategy settings. methods is replaced by only/except
+    // todo design beta/migration story
+    // todo design upgrade story
+    // todo design continuous upgrade story
+    // todo document parameters
+    // todo support multiple base URLs
+    public static function toOldConfig(Extracting $extractingConfig, Output $outputConfig): array
+    {
+        return [
+            '__configVersion' => 'v2',
+            'theme' => $outputConfig->theme,
+            'title' => $outputConfig->title,
+            'description' => $outputConfig->description,
+            'base_url' => $outputConfig->baseUrls[0] ?? null,
+            'type' => $outputConfig->type[0],
+            $outputConfig->type[0] => self::translateKeys($outputConfig->type[1]),
+            'try_it_out' => self::translateKeys($outputConfig->tryItOut),
+            'postman' => self::translateKeys($outputConfig->postman),
+            'openapi' => self::translateKeys($outputConfig->openApi),
+            'intro_text' => $outputConfig->introText,
+            'example_languages' => $outputConfig->exampleLanguages,
+            'logo' => $outputConfig->logo,
+            'last_updated' => $outputConfig->lastUpdated,
+            'groups' => [
+                'order' => $outputConfig->groupsOrder,
+                'default' => $extractingConfig->defaultGroup,
+            ],
+
+            'examples' => [
+                'faker_seed' => $extractingConfig->fakerSeedForExamples,
+                'models_source' => $extractingConfig->dataSourcesForExampleModels,
+            ],
+            'routeMatcher' => $extractingConfig->routeMatcher,
+            'database_connections_to_transact' => $extractingConfig->databaseConnectionsToTransact,
+            'fractal' => [
+                'serializer' => $extractingConfig->fractalSerializer,
+            ],
+            'auth' => self::translateKeys($extractingConfig->auth),
+            'strategies' => $extractingConfig->strategies,
+            'routes' => static::generateRoutesConfig($extractingConfig->routes),
+        ];
+    }
+
+    protected static function generateRoutesConfig(Routes $routesConfig): array
+    {
+        return [
+            [
+                'match' => [
+                    'prefixes' => $routesConfig->prefixes,
+                    'domains' => $routesConfig->domains,
+                    'versions' => $routesConfig->dingoVersions,
+                ],
+                'include' => $routesConfig->alwaysInclude,
+                'exclude' => $routesConfig->alwaysExclude,
+            ]
+        ];
+    }
+
+    protected static function translateKeys($array)
+    {
+        return Arr::mapWithKeys($array, function ($value, $key) {
+            return [Str::snake($key) => $value];
+        });
+    }
+}

+ 64 - 0
src/Config/StrategyListWrapper.php

@@ -0,0 +1,64 @@
+<?php
+
+namespace Knuckles\Scribe\Config;
+
+use Illuminate\Support\Arr;
+use Knuckles\Scribe\Extracting\Strategies\StrategyInterface;
+
+/**
+ * @internal
+ */
+class StrategyListWrapper
+{
+    // Strategies can be:
+    // 1. (Original) A class name, e.g. Strategies\Responses\ResponseCalls::class
+    // 2. (New) A tuple containing the class name as item 1, and its config array as item 2
+    // 3. (New) A tuple containing "override" as item 1, and the values to override array as item 2
+    public function __construct(
+        public array $strategies = []
+    ) {
+
+    }
+
+    public function override(array $valuesToOverride): self
+    {
+        return $this->addStrategies(['overrides', $valuesToOverride]);
+    }
+
+    public function addStrategies(array|string ...$newStrategies): self
+    {
+        foreach ($newStrategies as $newStrategy) {
+            $this->strategies[] = $newStrategy;
+        }
+        return $this;
+    }
+
+    public function removeStrategies(string ...$strategyNamesToRemove): self
+    {
+        $correspondingStrategies = Arr::where($this->strategies, function ($strategy) use ($strategyNamesToRemove) {
+            $strategyName = is_string($strategy) ? $strategy : $strategy[0];
+            return in_array($strategyName, $strategyNamesToRemove);
+        });
+
+        foreach ($correspondingStrategies as $key => $value) {
+            unset($this->strategies[$key]);
+        }
+
+        return $this;
+    }
+
+    public function configure(array $configurationTuple): self
+    {
+        $this->strategies = Arr::map($this->strategies, function ($strategy) use ($configurationTuple) {
+            $strategyName = is_string($strategy) ? $strategy : $strategy[0];
+            return $strategyName == $configurationTuple[0] ? $configurationTuple : $strategy;
+        });
+
+        return $this;
+    }
+
+    public function toArray(): array
+    {
+        return $this->strategies;
+    }
+}

+ 3 - 1
src/Extracting/Strategies/Metadata/GetFromMetadataAttributes.php

@@ -28,7 +28,9 @@ class GetFromMetadataAttributes extends PhpAttributeStrategy
 
     protected function extractFromAttributes(
         ExtractedEndpointData $endpointData,
-        array $attributesOnMethod, array $attributesOnFormRequest = [], array $attributesOnController = []
+        array $attributesOnMethod,
+        array $attributesOnFormRequest = [],
+        array $attributesOnController = []
     ): ?array
     {
         $metadata = [

+ 13 - 4
src/Extracting/Strategies/Responses/ResponseCalls.php

@@ -315,13 +315,22 @@ class ResponseCalls extends Strategy
 
     protected function shouldMakeApiCall(ExtractedEndpointData $endpointData, array $rulesToApply): bool
     {
-        $allowedMethods = $rulesToApply['methods'] ?? [];
-        if (empty($allowedMethods)) {
+        // Don't attempt a response call if there are already successful responses
+        if ($endpointData->responses->hasSuccessResponse()) {
             return false;
         }
 
-        // Don't attempt a response call if there are already successful responses
-        if ($endpointData->responses->hasSuccessResponse()) {
+        if (array_key_exists('only', $rulesToApply) || array_key_exists('except', $rulesToApply)) {
+            // We're in the v2 config. The route filtering has already been done
+            return true;
+        }
+
+        /**
+         * @deprecated The rest of this method is now determined by the strategy-level settings (`only` and `exclude` keys),
+         *   which are supported globally by all strategies.
+         */
+        $allowedMethods = $rulesToApply['methods'] ?? [];
+        if (empty($allowedMethods)) {
             return false;
         }
 

+ 2 - 0
src/ScribeServiceProvider.php

@@ -92,6 +92,8 @@ class ScribeServiceProvider extends ServiceProvider
         ], 'scribe-config');
 
         $this->mergeConfigFrom(__DIR__ . '/../config/scribe.php', 'scribe');
+        // This is really only used in internal testing.
+        $this->mergeConfigFrom(__DIR__ . '/../config/scribe_new.php', 'scribe_new');
     }
 
     protected function registerCommands(): void

+ 10 - 8
tests/GenerateDocumentation/BehavioursTest.php

@@ -24,11 +24,13 @@ class BehavioursTest extends BaseLaravelTest
     {
         parent::setUp();
 
-        config(['scribe.database_connections_to_transact' => []]);
-        config(['scribe.routes.0.match.prefixes' => ['api/*']]);
-        // Skip these ones for faster tests
-        config(['scribe.openapi.enabled' => false]);
-        config(['scribe.postman.enabled' => false]);
+        $this->setConfig([
+            'database_connections_to_transact' => [],
+            'routes.0.match.prefixes' => ['api/*'],
+            // Skip these for faster tests
+            'openapi.enabled' => false,
+            'postman.enabled' => false,
+        ]);
 
         $factory = app(\Illuminate\Database\Eloquent\Factory::class);
         $factory->define(TestUser::class, function () {
@@ -92,8 +94,8 @@ class BehavioursTest extends BaseLaravelTest
             $api->get('/test', [TestController::class, 'withEndpointDescription']);
         });
 
-        config(['scribe.routes.0.match.prefixes' => ['*']]);
-        config(['scribe.routes.0.match.versions' => ['v1']]);
+        $this->setConfig(['routes.0.match.prefixes' => ['*']]);
+        $this->setConfig(['routes.0.match.versions' => ['v1']]);
 
         $this->generateAndExpectConsoleOutput(
             'Processed route: [GET] closure',
@@ -197,7 +199,7 @@ class BehavioursTest extends BaseLaravelTest
     {
         RouteFacade::get('/api/action1', TestGroupController::class . '@action1');
 
-        config(['scribe.static.output_path' => 'static/docs']);
+        $this->setConfig(['static.output_path' => 'static/docs']);
         $this->assertFileDoesNotExist('static/docs/index.html');
 
         $this->generate();

+ 79 - 97
tests/GenerateDocumentation/OutputTest.php

@@ -6,6 +6,7 @@ use Illuminate\Support\Facades\Route as RouteFacade;
 use Illuminate\Support\Facades\Storage;
 use Illuminate\Support\Facades\View;
 use Illuminate\Support\Str;
+use Knuckles\Scribe\Config\Defaults;
 use Knuckles\Scribe\Tests\BaseLaravelTest;
 use Knuckles\Scribe\Tests\Fixtures\TestController;
 use Knuckles\Scribe\Tests\Fixtures\TestGroupController;
@@ -30,55 +31,31 @@ class OutputTest extends BaseLaravelTest
     {
         parent::setUp();
 
-        config(['scribe.strategies' => [
-            'metadata' => [
-                Strategies\Metadata\GetFromDocBlocks::class,
-                Strategies\Metadata\GetFromMetadataAttributes::class,
-            ],
-            'urlParameters' => [
-                Strategies\UrlParameters\GetFromLaravelAPI::class,
-                Strategies\UrlParameters\GetFromLumenAPI::class,
-                Strategies\UrlParameters\GetFromUrlParamAttribute::class,
-                Strategies\UrlParameters\GetFromUrlParamTag::class,
-            ],
-            'queryParameters' => [
-                Strategies\QueryParameters\GetFromFormRequest::class,
-                Strategies\QueryParameters\GetFromInlineValidator::class,
-                Strategies\QueryParameters\GetFromQueryParamAttribute::class,
-                Strategies\QueryParameters\GetFromQueryParamTag::class,
-            ],
-            'headers' => [
-                Strategies\Headers\GetFromRouteRules::class,
-                Strategies\Headers\GetFromHeaderAttribute::class,
-                Strategies\Headers\GetFromHeaderTag::class,
-            ],
-            'bodyParameters' => [
-                Strategies\BodyParameters\GetFromFormRequest::class,
-                Strategies\BodyParameters\GetFromInlineValidator::class,
-                Strategies\BodyParameters\GetFromBodyParamAttribute::class,
-                Strategies\BodyParameters\GetFromBodyParamTag::class,
-            ],
-            'responses' => [
-                Strategies\Responses\UseResponseAttributes::class,
-                Strategies\Responses\UseTransformerTags::class,
-                Strategies\Responses\UseApiResourceTags::class,
-                Strategies\Responses\UseResponseTag::class,
-                Strategies\Responses\UseResponseFileTag::class,
-                Strategies\Responses\ResponseCalls::class,
-            ],
-            'responseFields' => [
-                Strategies\ResponseFields\GetFromResponseFieldAttribute::class,
-                Strategies\ResponseFields\GetFromResponseFieldTag::class,
-            ],
+        $this->setConfig(['strategies' => [
+            'metadata' => Defaults::metadataStrategies()->toArray(),
+            'urlParameters' => Defaults::urlParametersStrategies()->toArray(),
+            'queryParameters' => Defaults::queryParametersStrategies()->toArray(),
+            'headers' => Defaults::headersStrategies()->toArray(),
+            'bodyParameters' => Defaults::bodyParametersStrategies()->toArray(),
+            'responses' => Defaults::responsesStrategies()
+                ->configure(Strategies\Responses\ResponseCalls::withSettings(
+                    only: ['GET *'],
+                    config: [
+                        'app.env' => 'documentation',
+                        // 'app.debug' => false,
+                    ],
+                ))
+                ->toArray(),
+            'responseFields' => Defaults::responseFieldsStrategies()->toArray(),
         ],
         ]);
-        config(['scribe.database_connections_to_transact' => []]);
-        config(['scribe.routes.0.match.prefixes' => ['api/*']]);
+        $this->setConfig(['database_connections_to_transact' => []]);
+        $this->setConfig(['routes.0.match.prefixes' => ['api/*']]);
         // Skip these ones for faster tests
-        config(['scribe.openapi.enabled' => false]);
-        config(['scribe.postman.enabled' => false]);
+        $this->setConfig(['openapi.enabled' => false]);
+        $this->setConfig(['postman.enabled' => false]);
         // We want to have the same values for params each time
-        config(['scribe.examples.faker_seed' => 1234]);
+        $this->setConfig(['examples.faker_seed' => 1234]);
 
         $factory = app(\Illuminate\Database\Eloquent\Factory::class);
         $factory->define(TestUser::class, function () {
@@ -99,9 +76,11 @@ class OutputTest extends BaseLaravelTest
 
     protected function usingLaravelTypeDocs($app)
     {
-        $app['config']->set('scribe.type', 'laravel');
-        $app['config']->set('scribe.laravel.add_routes', true);
-        $app['config']->set('scribe.laravel.docs_url', '/apidocs');
+        $this->setConfig([
+            'type' => 'laravel',
+            'laravel.add_routes' => true,
+            'laravel.docs_url' => '/apidocs',
+        ]);
     }
 
     /**
@@ -111,8 +90,8 @@ class OutputTest extends BaseLaravelTest
     public function generates_laravel_type_output()
     {
         RouteFacade::post('/api/withQueryParameters', [TestController::class, 'withQueryParameters']);
-        config(['scribe.postman.enabled' => true]);
-        config(['scribe.openapi.enabled' => true]);
+        $this->setConfig(['postman.enabled' => true]);
+        $this->setConfig(['openapi.enabled' => true]);
 
         $this->generateAndExpectConsoleOutput(
             "Wrote Blade docs to: vendor/orchestra/testbench-core/laravel/resources/views/scribe",
@@ -198,17 +177,17 @@ class OutputTest extends BaseLaravelTest
         RouteFacade::get('/api/withQueryParameters', [TestController::class, 'withQueryParameters']);
         RouteFacade::get('/api/withAuthTag', [TestController::class, 'withAuthenticatedTag']);
         RouteFacade::get('/api/echoesUrlParameters/{param}/{param2}/{param3?}/{param4?}', [TestController::class, 'echoesUrlParameters']);
-        config(['scribe.title' => 'GREAT API!']);
-        config(['scribe.auth.enabled' => true]);
-        config(['scribe.postman.overrides' => [
-            'info.version' => '3.9.9',
-        ]]);
-        config([
-            'scribe.routes.0.apply.headers' => [
+        $this->setConfig([
+            'title' => 'GREAT API!',
+            'auth.enabled' => true,
+            'postman.enabled' => true,
+            'postman.overrides' => [
+                'info.version' => '3.9.9',
+            ],
+            'routes.0.apply.headers' => [
                 'Custom-Header' => 'NotSoCustom',
             ],
         ]);
-        config(['scribe.postman.enabled' => true]);
 
         $this->generateAndExpectConsoleOutput(
             "Wrote HTML docs and assets to: public/docs/",
@@ -233,12 +212,12 @@ class OutputTest extends BaseLaravelTest
         RouteFacade::get('/api/withAuthTag', [TestController::class, 'withAuthenticatedTag']);
         RouteFacade::get('/api/echoesUrlParameters/{param}/{param2}/{param3?}/{param4?}', [TestController::class, 'echoesUrlParameters']);
 
-        config(['scribe.openapi.enabled' => true]);
-        config(['scribe.openapi.overrides' => [
-            'info.version' => '3.9.9',
-        ]]);
-        config([
-            'scribe.routes.0.apply.headers' => [
+        $this->setConfig([
+            'openapi.enabled' => true,
+            'openapi.overrides' => [
+                'info.version' => '3.9.9',
+            ],
+            'routes.0.apply.headers' => [
                 'Custom-Header' => 'NotSoCustom',
             ],
         ]);
@@ -250,6 +229,7 @@ class OutputTest extends BaseLaravelTest
 
         $generatedSpec = Yaml::parseFile($this->openapiOutputPath());
         $fixtureSpec = Yaml::parseFile(__DIR__ . '/../Fixtures/openapi.yaml');
+        ray($fixtureSpec);
         $this->assertEquals($fixtureSpec, $generatedSpec);
     }
 
@@ -257,8 +237,8 @@ class OutputTest extends BaseLaravelTest
     public function can_append_custom_http_headers()
     {
         RouteFacade::get('/api/headers', [TestController::class, 'checkCustomHeaders']);
-        config([
-            'scribe.routes.0.apply.headers' => [
+        $this->setConfig([
+            'routes.0.apply.headers' => [
                 'Authorization' => 'customAuthToken',
                 'Custom-Header' => 'NotSoCustom',
             ],
@@ -305,7 +285,7 @@ class OutputTest extends BaseLaravelTest
     /** @test */
     public function sorts_groups_and_endpoints_in_the_specified_order()
     {
-        config(['scribe.groups.order' => [
+        $this->setConfig(['groups.order' => [
             '10. Group 10',
             '1. Group 1' => [
                 'GET /api/action1b',
@@ -343,16 +323,16 @@ class OutputTest extends BaseLaravelTest
         $this->assertEquals('13. Group 13', $thirdGroup->textContent);
         $this->assertEquals('2. Group 2', $fourthGroup->textContent);
 
-        $firstGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($firstGroup->textContent).'"]');
+        $firstGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($firstGroup->textContent) . '"]');
         $this->assertEquals(1, $firstGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET api/action10", $firstGroupEndpointsAndSubgroups->getNode(0)->textContent);
 
-        $secondGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($secondGroup->textContent).'"]');
+        $secondGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($secondGroup->textContent) . '"]');
         $this->assertEquals(2, $secondGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET api/action1b", $secondGroupEndpointsAndSubgroups->getNode(0)->textContent);
         $this->assertEquals("GET api/action1", $secondGroupEndpointsAndSubgroups->getNode(1)->textContent);
 
-        $thirdGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($thirdGroup->textContent).'"]');
+        $thirdGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($thirdGroup->textContent) . '"]');
         $this->assertEquals(8, $thirdGroupEndpointsAndSubgroups->count());
         $this->assertEquals("SG B", $thirdGroupEndpointsAndSubgroups->getNode(0)->textContent);
         $this->assertEquals("POST api/action13d", $thirdGroupEndpointsAndSubgroups->getNode(1)->textContent);
@@ -367,7 +347,7 @@ class OutputTest extends BaseLaravelTest
     /** @test */
     public function sorts_groups_and_endpoints_in_the_specified_order_with_wildcard()
     {
-        config(['scribe.groups.order' => [
+        $this->setConfig(['groups.order' => [
             '10. Group 10',
             '*',
             '13. Group 13' => [
@@ -402,16 +382,16 @@ class OutputTest extends BaseLaravelTest
         $this->assertEquals('2. Group 2', $thirdGroup->textContent);
         $this->assertEquals('13. Group 13', $fourthGroup->textContent);
 
-        $firstGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($firstGroup->textContent).'"]');
+        $firstGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($firstGroup->textContent) . '"]');
         $this->assertEquals(1, $firstGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET api/action10", $firstGroupEndpointsAndSubgroups->getNode(0)->textContent);
 
-        $secondGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($secondGroup->textContent).'"]');
+        $secondGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($secondGroup->textContent) . '"]');
         $this->assertEquals(2, $secondGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET api/action1", $secondGroupEndpointsAndSubgroups->getNode(0)->textContent);
         $this->assertEquals("GET api/action1b", $secondGroupEndpointsAndSubgroups->getNode(1)->textContent);
 
-        $fourthGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($fourthGroup->textContent).'"]');
+        $fourthGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($fourthGroup->textContent) . '"]');
         $this->assertEquals(8, $fourthGroupEndpointsAndSubgroups->count());
         $this->assertEquals("SG B", $fourthGroupEndpointsAndSubgroups->getNode(0)->textContent);
         $this->assertEquals("POST api/action13d", $fourthGroupEndpointsAndSubgroups->getNode(1)->textContent);
@@ -428,13 +408,15 @@ class OutputTest extends BaseLaravelTest
     {
         RouteFacade::get('/api/action1', [TestGroupController::class, 'action1']);
         RouteFacade::get('/api/action2', [TestGroupController::class, 'action2']);
-        config(['scribe.routes.0.apply.response_calls.methods' => []]);
-        config(['scribe.groups.order' => [
-            '1. Group 1',
-            '5. Group 5',
-            '4. Group 4',
-            '2. Group 2',
-        ]]);
+        $this->setConfig([
+            'routes.0.apply.response_calls.methods' => [],
+            'groups.order' => [
+                '1. Group 1',
+                '5. Group 5',
+                '4. Group 4',
+                '2. Group 2',
+            ]
+        ]);
 
         if (!is_dir('.scribe/endpoints')) mkdir('.scribe/endpoints', 0777, true);
         copy(__DIR__ . '/../Fixtures/custom.0.yaml', '.scribe/endpoints/custom.0.yaml');
@@ -451,21 +433,21 @@ class OutputTest extends BaseLaravelTest
         $this->assertEquals('4. Group 4', $thirdGroup->textContent);
         $this->assertEquals('2. Group 2', $fourthGroup->textContent);
 
-        $firstGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($firstGroup->textContent).'"]');
+        $firstGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($firstGroup->textContent) . '"]');
         $this->assertEquals(2, $firstGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET api/action1", $firstGroupEndpointsAndSubgroups->getNode(0)->textContent);
         $this->assertEquals("User defined", $firstGroupEndpointsAndSubgroups->getNode(1)->textContent);
 
-        $secondGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($secondGroup->textContent).'"]');
+        $secondGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($secondGroup->textContent) . '"]');
         $this->assertEquals(2, $secondGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET group5", $secondGroupEndpointsAndSubgroups->getNode(0)->textContent);
         $this->assertEquals("GET alsoGroup5", $secondGroupEndpointsAndSubgroups->getNode(1)->textContent);
 
-        $thirdGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($thirdGroup->textContent).'"]');
+        $thirdGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($thirdGroup->textContent) . '"]');
         $this->assertEquals(1, $thirdGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET group4", $thirdGroupEndpointsAndSubgroups->getNode(0)->textContent);
 
-        $fourthGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="'.Str::slug($fourthGroup->textContent).'"]');
+        $fourthGroupEndpointsAndSubgroups = $crawler->filter('h2[id^="' . Str::slug($fourthGroup->textContent) . '"]');
         $this->assertEquals(1, $fourthGroupEndpointsAndSubgroups->count());
         $this->assertEquals("GET api/action2", $fourthGroupEndpointsAndSubgroups->getNode(0)->textContent);
     }
@@ -475,7 +457,7 @@ class OutputTest extends BaseLaravelTest
     {
         RouteFacade::get('/api/action1', [TestGroupController::class, 'action1']);
         RouteFacade::get('/api/action1b', [TestGroupController::class, 'action1b']);
-        config(['scribe.routes.0.apply.response_calls.methods' => []]);
+        $this->setConfig(['routes.0.apply.response_calls.methods' => []]);
 
         $this->generate();
 
@@ -525,8 +507,8 @@ class OutputTest extends BaseLaravelTest
                 'addresses' => 'address:uuid',
             ]);
         });
-        config(['scribe.routes.0.match.prefixes' => ['*']]);
-        config(['scribe.routes.0.apply.response_calls.methods' => []]);
+        $this->setConfig(['routes.0.match.prefixes' => ['*']]);
+        $this->setConfig(['routes.0.apply.response_calls.methods' => []]);
 
         $this->generate();
 
@@ -542,8 +524,8 @@ class OutputTest extends BaseLaravelTest
         RouteFacade::resource('posts', TestPostController::class)->only('update');
         RouteFacade::resource('posts.users', TestPostUserController::class)->only('update');
 
-        config(['scribe.routes.0.match.prefixes' => ['*']]);
-        config(['scribe.routes.0.apply.response_calls.methods' => []]);
+        $this->setConfig(['routes.0.match.prefixes' => ['*']]);
+        $this->setConfig(['routes.0.apply.response_calls.methods' => []]);
 
         $this->generate();
 
@@ -559,8 +541,8 @@ class OutputTest extends BaseLaravelTest
 
         RouteFacade::resource('posts', TestPostBoundInterfaceController::class)->only('update');
 
-        config(['scribe.routes.0.match.prefixes' => ['*']]);
-        config(['scribe.routes.0.apply.response_calls.methods' => []]);
+        $this->setConfig(['routes.0.match.prefixes' => ['*']]);
+        $this->setConfig(['routes.0.apply.response_calls.methods' => []]);
 
         $this->generate();
 
@@ -574,8 +556,8 @@ class OutputTest extends BaseLaravelTest
         RouteFacade::get('posts/{post}/users', function (TestPost $post) {
         });
 
-        config(['scribe.routes.0.match.prefixes' => ['*']]);
-        config(['scribe.routes.0.apply.response_calls.methods' => []]);
+        $this->setConfig(['routes.0.match.prefixes' => ['*']]);
+        $this->setConfig(['routes.0.apply.response_calls.methods' => []]);
 
         $this->generate();
 
@@ -586,7 +568,7 @@ class OutputTest extends BaseLaravelTest
     /** @test */
     public function generates_from_camel_dir_if_noExtraction_flag_is_set()
     {
-        config(['scribe.routes.0.exclude' => ['*']]);
+        $this->setConfig(['routes.0.exclude' => ['*']]);
         Utils::copyDirectory(__DIR__ . '/../Fixtures/.scribe', '.scribe');
 
         $output = $this->generate(['--no-extraction' => true]);
@@ -621,8 +603,8 @@ class OutputTest extends BaseLaravelTest
          * @bodyParam data.a_file file
          */
         RouteFacade::post('nested-file', fn() => null);
-        config(['scribe.routes.0.match.prefixes' => ['*']]);
-        config(['scribe.routes.0.apply.response_calls.methods' => []]);
+        $this->setConfig(['routes.0.match.prefixes' => ['*']]);
+        $this->setConfig(['routes.0.apply.response_calls.methods' => []]);
 
         $this->generate();
 

+ 2 - 2
tests/Strategies/Responses/ResponseCallsTest.php

@@ -24,7 +24,7 @@ class ResponseCallsTest extends BaseLaravelTest
     protected function setUp(): void
     {
         parent::setUp();
-        config(['scribe.database_connections_to_transact' => []]);
+        $this->setConfig(['database_connections_to_transact' => []]);
     }
 
     /** @test */
@@ -60,7 +60,7 @@ class ResponseCallsTest extends BaseLaravelTest
     {
         $route = RouteFacade::post('/withFormDataParams', [TestController::class, 'withFormDataParams']);
 
-        config(['scribe.routes.0.apply.response_calls.methods' => ['POST']]);
+        $this->setConfig(['routes.0.apply.response_calls.methods' => ['POST']]);
         $parsed = (new Extractor())->processRoute($route, config('scribe.routes.0.apply'));
 
         $responses = $parsed->responses->toArray();

+ 1 - 4
tests/Strategies/Responses/UseApiResourceTagsTest.php

@@ -15,12 +15,9 @@ use Knuckles\Scribe\Tests\Fixtures\TestUser;
 use Knuckles\Scribe\Tools\DocumentationConfig;
 use Knuckles\Scribe\Tools\Utils;
 use Mpociot\Reflection\DocBlock\Tag;
-use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
 
 class UseApiResourceTagsTest extends BaseLaravelTest
 {
-    use ArraySubsetAsserts;
-
     protected function getPackageProviders($app)
     {
         $providers = parent::getPackageProviders($app);
@@ -34,7 +31,7 @@ class UseApiResourceTagsTest extends BaseLaravelTest
     {
         parent::setUp();
 
-        config(['scribe.database_connections_to_transact' => []]);
+        $this->setConfig(['database_connections_to_transact' => []]);
 
         $factory = app(\Illuminate\Database\Eloquent\Factory::class);
         $factory->define(TestUser::class, function () {

+ 1 - 4
tests/Strategies/Responses/UseResponseAttributesTest.php

@@ -19,13 +19,10 @@ use Knuckles\Scribe\Tests\Fixtures\TestUserApiResource;
 use Knuckles\Scribe\Tools\DocumentationConfig;
 use Knuckles\Scribe\Tools\Utils;
 use League\Fractal\Pagination\IlluminatePaginatorAdapter;
-use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
 use ReflectionClass;
 
 class UseResponseAttributesTest extends BaseLaravelTest
 {
-    use ArraySubsetAsserts;
-
     protected function getPackageProviders($app)
     {
         $providers = parent::getPackageProviders($app);
@@ -39,7 +36,7 @@ class UseResponseAttributesTest extends BaseLaravelTest
     {
         parent::setUp();
 
-        config(['scribe.database_connections_to_transact' => []]);
+        $this->setConfig(['database_connections_to_transact' => []]);
 
         $factory = app(\Illuminate\Database\Eloquent\Factory::class);
         $factory->define(TestUser::class, function () {

+ 1 - 4
tests/Strategies/Responses/UseTransformerTagsTest.php

@@ -7,16 +7,13 @@ use Knuckles\Scribe\Tests\BaseLaravelTest;
 use Knuckles\Scribe\Tests\Fixtures\TestUser;
 use Knuckles\Scribe\Tools\DocumentationConfig;
 use Mpociot\Reflection\DocBlock\Tag;
-use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
 
 class UseTransformerTagsTest extends BaseLaravelTest
 {
-    use ArraySubsetAsserts;
-
     protected function setUp(): void
     {
         parent::setUp();
-        config(['scribe.database_connections_to_transact' => []]);
+        $this->setConfig(['database_connections_to_transact' => []]);
     }
 
     /**