Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Extracting/Extractor.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Knuckles\Scribe\Extracting\Strategies\StaticData;
use Knuckles\Scribe\Tools\ConsoleOutputUtils as c;
use Knuckles\Scribe\Tools\DocumentationConfig;
use Knuckles\Scribe\Tools\Globals;
use Knuckles\Scribe\Tools\RoutePatternMatcher;

class Extractor
Expand Down Expand Up @@ -96,6 +97,10 @@ public function processRoute(Route $route, array $routeRules = []): ExtractedEnd
$this->fetchResponseFields($endpointData, $routeRules);
$this->mergeInheritedMethodsData('responseFields', $endpointData, $inheritedDocsOverrides);

if (is_callable(Globals::$__afterExtracting)) {
call_user_func_array(Globals::$__afterExtracting, [$endpointData]);
}

self::$routeBeingProcessed = null;

return $endpointData;
Expand Down
11 changes: 11 additions & 0 deletions src/Scribe.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,15 @@ public static function normalizeEndpointUrlUsing(?callable $callable)
{
Globals::$__normalizeEndpointUrlUsing = $callable;
}

/**
* Specify a callback that will be executed after all extraction strategies have run for a route.
* This allows you to modify the extracted endpoint data before it is saved.
*
* @param callable(ExtractedEndpointData): void $callable
*/
public static function afterExtracting(callable $callable)
{
Globals::$__afterExtracting = $callable;
}
}
2 changes: 2 additions & 0 deletions src/Tools/Globals.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ class Globals
public static $__instantiateFormRequestUsing;

public static $__normalizeEndpointUrlUsing;

public static $__afterExtracting;
}
100 changes: 100 additions & 0 deletions tests/Unit/AfterExtractingHookTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<?php

namespace Knuckles\Scribe\Tests\Unit;

use Illuminate\Routing\Route;
use Knuckles\Camel\Extraction\ExtractedEndpointData;
use Knuckles\Scribe\Config\Defaults;
use Knuckles\Scribe\Extracting\Extractor;
use Knuckles\Scribe\Scribe;
use Knuckles\Scribe\Tests\BaseLaravelTest;
use Knuckles\Scribe\Tests\Fixtures\TestController;
use Knuckles\Scribe\Tools\DocumentationConfig;
use Knuckles\Scribe\Tools\Globals;

class AfterExtractingHookTest extends BaseLaravelTest
{
protected array $config = [
'strategies' => [
'metadata' => [
...Defaults::METADATA_STRATEGIES,
],
'headers' => [
...Defaults::HEADERS_STRATEGIES,
],
'urlParameters' => [
...Defaults::URL_PARAMETERS_STRATEGIES,
],
'queryParameters' => [
...Defaults::QUERY_PARAMETERS_STRATEGIES,
],
'bodyParameters' => [
...Defaults::BODY_PARAMETERS_STRATEGIES,
],
'responses' => [],
'responseFields' => [
...Defaults::RESPONSE_FIELDS_STRATEGIES,
],
],
];

protected function tearDown(): void
{
Globals::$__afterExtracting = null;
parent::tearDown();
}

/** @test */
public function can_use_after_extracting_hook()
{
$route = new Route(['GET'], 'api/test', ['uses' => [TestController::class, 'withEndpointDescription']]);

Scribe::afterExtracting(static function (ExtractedEndpointData $endpointData) {
$endpointData->metadata->title = 'Modified Title';
$endpointData->headers['X-Modified-By'] = 'Hook';
});

$extractor = new Extractor(new DocumentationConfig($this->config));
$parsed = $extractor->processRoute($route);

$this->assertSame('Modified Title', $parsed->metadata->title);
$this->assertSame('Hook', $parsed->headers['X-Modified-By']);
}

/** @test */
public function after_extracting_hook_is_called_only_once_per_route()
{
$route = new Route(['GET'], 'api/test', ['uses' => [TestController::class, 'withEndpointDescription']]);

$callCount = 0;
Scribe::afterExtracting(function (ExtractedEndpointData $endpointData) use (&$callCount) {
$callCount++;
});

$extractor = new Extractor(new DocumentationConfig($this->config));
$extractor->processRoute($route);

$this->assertSame(1, $callCount);
}

/** @test */
public function after_extracting_hook_can_access_route_middlewares()
{
$route = new Route(['GET'], 'api/test', ['uses' => [TestController::class, 'withEndpointDescription']]);
$route->middleware(['auth:agent_api', 'throttle:60,1']);

Scribe::afterExtracting(static function (ExtractedEndpointData $endpointData) {
$middlewares = $endpointData->route->gatherMiddleware();
if (in_array('auth:agent_api', $middlewares, true)) {
$endpointData->metadata->description .= 'Requires authentication: `Agent`';
} elseif (in_array('auth:api', $middlewares, true)) {
$endpointData->metadata->description .= 'Requires authentication: `User`';
}
});

$extractor = new Extractor(new DocumentationConfig($this->config));
$parsed = $extractor->processRoute($route);

$this->assertStringContainsString('Requires authentication: `Agent`', $parsed->metadata->description);
}
}