Skip to content

Commit

Permalink
Remove DocBlockParser::$whitelist
Browse files Browse the repository at this point in the history
This makes the `Generator` class the authority about whitelisted annotation
namespaces. A value of `null` is interpreted as wildcard.

Registers a new loader for each `Generator` instance. A new method
`withContext()` was also added to allow to run custom code in the context
of a configured `Generator`.
  • Loading branch information
DerManoMann committed Sep 27, 2021
1 parent 9b3b713 commit 53add4a
Show file tree
Hide file tree
Showing 17 changed files with 132 additions and 130 deletions.
40 changes: 1 addition & 39 deletions src/Analysers/DocBlockParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,57 +6,19 @@

namespace OpenApi\Analysers;

use Doctrine\Common\Annotations\AnnotationRegistry;
use Doctrine\Common\Annotations\DocParser;
use OpenApi\Context;
use OpenApi\Generator;

if (class_exists(AnnotationRegistry::class, true)) {
AnnotationRegistry::registerLoader(
function (string $class): bool {
if (DocBlockParser::$whitelist === false) {
$whitelist = ['OpenApi\\Annotations\\'];
} else {
$whitelist = DocBlockParser::$whitelist;
}
foreach ($whitelist as $namespace) {
if (strtolower(substr($class, 0, strlen($namespace))) === strtolower($namespace)) {
$loaded = class_exists($class);
if (!$loaded && $namespace === 'OpenApi\\Annotations\\') {
if (in_array(strtolower(substr($class, 20)), ['definition', 'path'])) {
// Detected an 2.x annotation?
throw new \Exception('The annotation @SWG\\' . substr($class, 20) . '() is deprecated. Found in ' . Generator::$context . "\nFor more information read the migration guide: https://github.com/zircote/swagger-php/blob/master/docs/Migrating-to-v3.md");
}
}

return $loaded;
}
}

return false;
}
);
}

/**
* Extract swagger-php annotations from a [PHPDoc](http://en.wikipedia.org/wiki/PHPDoc) using Doctrine's DocParser.
*/
class DocBlockParser
{
/**
* List of namespaces that should be detected by the doctrine annotation parser.
* Set to false to load all detected classes.
*
* @var array|false
*
* @deprecated use \OpenApi\Generator::setAliases() instead
*/
public static $whitelist = ['OpenApi\\Annotations\\'];

/**
* Use @OA\* for OpenAPI annotations (unless overwritten by a use statement).
*
* @deprecated use \OpenApi\Generator::setNamespaces() instead
* @deprecated use \OpenApi\Generator::setAliases() instead
*/
public static $defaultImports = ['oa' => 'OpenApi\\Annotations'];

Expand Down
8 changes: 5 additions & 3 deletions src/Analysers/TokenAnalyser.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,12 @@ protected function fromTokens(array $tokens, Context $parseContext): Analysis
{
$docBlockParser = new DocBlockParser();
$analysis = new Analysis([], $parseContext);
$generator = $this->generator ?: new Generator();

reset($tokens);
$token = '';

$imports = DocBlockParser::$defaultImports;
$imports = $generator->getAliases();

$parseContext->uses = [];
// default to parse context to start with
Expand Down Expand Up @@ -368,10 +369,11 @@ protected function fromTokens(array $tokens, Context $parseContext): Analysis
// not a trait use
$parseContext->uses[$alias] = $target;

if (DocBlockParser::$whitelist === false) {
$namespaces = $generator->getNamespaces();
if (null === $namespaces) {
$imports[strtolower($alias)] = $target;
} else {
foreach (DocBlockParser::$whitelist as $namespace) {
foreach ($namespaces as $namespace) {
if (strcasecmp(substr($target . '\\', 0, strlen($namespace)), $namespace) === 0) {
$imports[strtolower($alias)] = $target;
break;
Expand Down
78 changes: 58 additions & 20 deletions src/Generator.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

namespace OpenApi;

use Doctrine\Common\Annotations\AnnotationRegistry;
use OpenApi\Analysers\AnalyserInterface;
use OpenApi\Analysers\AttributeAnnotationFactory;
use OpenApi\Analysers\DocBlockAnnotationFactory;
use OpenApi\Analysers\DocBlockParser;
use OpenApi\Analysers\ReflectionAnalyser;
use OpenApi\Analysers\TokenAnalyser;
use OpenApi\Annotations\OpenApi;
use OpenApi\Loggers\DefaultLogger;
use OpenApi\Processors\AugmentParameters;
Expand All @@ -37,8 +37,6 @@
*
* This is an object oriented alternative to using the now deprecated `\OpenApi\scan()` function and
* static class properties of the `Analyzer` and `Analysis` classes.
*
* The `aliases` property supersedes the `DocBlockParser::$defaultImports`; `namespaces` maps to `DocBlockParser::$whitelist`.
*/
class Generator
{
Expand All @@ -55,8 +53,8 @@ class Generator
/** @var array Map of namespace aliases to be supported by doctrine. */
protected $aliases = null;

/** @var array List of annotation namespaces to be autoloaded by doctrine. */
protected $namespaces = null;
/** @var array|null List of annotation namespaces to be autoloaded by doctrine. */
protected $namespaces = ['OpenApi\\Annotations\\'];

/** @var AnalyserInterface The configured analyzer. */
protected $analyser;
Expand All @@ -76,23 +74,50 @@ public function __construct(?LoggerInterface $logger = null)
// kinda config stack to stay BC...
$this->configStack = new class() {
private $defaultImports;
private $whitelist;

public function push(Generator $generator): void
{
$this->generator = $generator;

// save current state
$this->defaultImports = DocBlockParser::$defaultImports;
$this->whitelist = DocBlockParser::$whitelist;

// update state with generator config
DocBlockParser::$defaultImports = $generator->getAliases();
DocBlockParser::$whitelist = $generator->getNamespaces();

if (class_exists(AnnotationRegistry::class, true)) {
// keeping track of &this->generator allows to 'disable' the loader after we are done;
// no unload, unfortunately :/
$gref = &$this->generator;
AnnotationRegistry::registerLoader(
function (string $class) use (&$gref): bool {
if ($gref) {
foreach ($gref->getNamespaces() as $namespace) {
if (strtolower(substr($class, 0, strlen($namespace))) === strtolower($namespace)) {
$loaded = class_exists($class);
if (!$loaded && $namespace === 'OpenApi\\Annotations\\') {
if (in_array(strtolower(substr($class, 20)), ['definition', 'path'])) {
// Detected an 2.x annotation?
throw new \Exception('The annotation @SWG\\' . substr($class, 20) . '() is deprecated. Found in ' . Generator::$context . "\nFor more information read the migration guide: https://github.com/zircote/swagger-php/blob/master/docs/Migrating-to-v3.md");
}
}

return $loaded;
}
}
}

return false;
}
);
}
}

public function pop(): void
{
$this->generator = null;

DocBlockParser::$defaultImports = $this->defaultImports;
DocBlockParser::$whitelist = $this->whitelist;
}
};
}
Expand All @@ -112,22 +137,17 @@ public function setAliases(?array $aliases): Generator
return $this;
}

public function getNamespaces(): array
public function getNamespaces(): ?array
{
$namespaces = null !== $this->namespaces ? $this->namespaces : DocBlockParser::$whitelist;
$namespaces = false !== $namespaces ? $namespaces : [];
$namespaces[] = 'OpenApi\\Annotations\\';

return array_unique($namespaces);
return $this->namespaces;
}

public function addNamespace(string $namespace): Generator
{
$namespaces = $this->getNamespaces();
$namespaces = (array) $this->getNamespaces();
$namespaces[] = $namespace;
$this->setNamespaces($namespaces);

return $this;
return $this->setNamespaces(array_unique($namespaces));
}

public function setNamespaces(?array $namespaces): Generator
Expand Down Expand Up @@ -250,8 +270,8 @@ public function getLogger(): ?LoggerInterface
* * \SplFileInfo
* * \Symfony\Component\Finder\Finder
* @param array $options
* aliases: null|array Defaults to `DocBlockParser::$defaultImports`.
* namespaces: null|array Defaults to `DocBlockParser::$whitelist`.
* aliases: null|array Defaults to `['oa' => 'OpenApi\\Annotations']`.
* namespaces: null|array Defaults to `['OpenApi\\Annotations\\']`.
* analyser: null|StaticAnalyser Defaults to a new `StaticAnalyser`.
* analysis: null|Analysis Defaults to a new `Analysis`.
* processors: null|array Defaults to `Analysis::processors()`.
Expand Down Expand Up @@ -279,6 +299,24 @@ public static function scan(iterable $sources, array $options = []): ?OpenApi
->generate($sources, $config['analysis'], $config['validate']);
}

/**
* Run code in the context of this generator.
*
* @return mixed the result of the `callable`
*/
public function withContext(callable $callable)
{
$rootContext = new Context(['logger' => $this->getLogger()]);
$analysis = new Analysis([], $rootContext);

$this->configStack->push($this);
try {
return $callable($this, $analysis, $rootContext);
} finally {
$this->configStack->pop();
}
}

/**
* Generate OpenAPI spec by scanning the given source files.
*
Expand Down
4 changes: 2 additions & 2 deletions tests/Analysers/DocBlockParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ protected function tearDown(): void

public function testParseContents()
{
$annotations = $this->annotationsFromDocBlock('@OA\Parameter(description="This is my parameter")');
$annotations = $this->annotationsFromDocBlockParser('@OA\Parameter(description="This is my parameter")');
$this->assertIsArray($annotations);
$parameter = $annotations[0];
$this->assertInstanceOf('OpenApi\Annotations\Parameter', $parameter);
Expand All @@ -35,6 +35,6 @@ public function testParseContents()
public function testDeprecatedAnnotationWarning()
{
$this->assertOpenApiLogEntryContains('The annotation @SWG\Definition() is deprecated.');
$this->annotationsFromDocBlock('@SWG\Definition()');
$this->annotationsFromDocBlockParser('@SWG\Definition()');
}
}
29 changes: 16 additions & 13 deletions tests/Analysers/ReflectionAnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,14 @@ public function analysers()
*/
public function testApiDocBlockBasic(AnalyserInterface $analyser)
{
$fixture = $this->fixture('Apis/DocBlocks/basic.php');
//require_once $fixture;
$analysis = (new Generator())
->withContext(function (Generator $generator) use ($analyser) {
$analyser->setGenerator($generator);
$analysis = $analyser->fromFile($this->fixture('Apis/DocBlocks/basic.php'), $this->getContext());
$analysis->process($generator->getProcessors());

$analysis = $analyser->fromFile($fixture, $this->getContext());
$analysis->process((new Generator())->getProcessors());
return $analysis;
});

$operations = $analysis->getAnnotationsOfType(Operation::class);
$this->assertIsArray($operations);
Expand All @@ -100,10 +103,7 @@ public function testApiDocBlockBasic(AnalyserInterface $analyser)
*/
public function testApiAttributesBasic(AnalyserInterface $analyser)
{
$fixture = $this->fixture('Apis/Attributes/basic.php');
require_once $fixture;

$analysis = $analyser->fromFile($fixture, $this->getContext());
$analysis = $analyser->fromFile($this->fixture('Apis/Attributes/basic.php'), $this->getContext());
$analysis->process((new Generator())->getProcessors());

$operations = $analysis->getAnnotationsOfType(Operation::class);
Expand All @@ -121,11 +121,14 @@ public function testApiAttributesBasic(AnalyserInterface $analyser)
*/
public function testApiMixedBasic(AnalyserInterface $analyser)
{
$fixture = $this->fixture('Apis/Mixed/basic.php');
require_once $fixture;

$analysis = $analyser->fromFile($fixture, $this->getContext());
$analysis->process((new Generator())->getProcessors());
$analysis = (new Generator())
->withContext(function (Generator $generator) use ($analyser) {
$analyser->setGenerator($generator);
$analysis = $analyser->fromFile($this->fixture('Apis/Mixed/basic.php'), $this->getContext());
$analysis->process((new Generator())->getProcessors());

return $analysis;
});

$operations = $analysis->getAnnotationsOfType(Operation::class);
$this->assertIsArray($operations);
Expand Down
11 changes: 5 additions & 6 deletions tests/Analysers/TokenAnalyserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

namespace OpenApi\Tests\Analysers;

use OpenApi\Analysers\DocBlockParser;
use OpenApi\Annotations\Property;
use OpenApi\Annotations\Schema;
use OpenApi\Generator;
Expand Down Expand Up @@ -119,21 +118,21 @@ public function testWrongCommentType()

public function testThirdPartyAnnotations()
{
$backup = DocBlockParser::$whitelist;
DocBlockParser::$whitelist = ['OpenApi\\Annotations\\'];
$generator = new Generator();
$analyser = new TokenAnalyser();
$analyser->setGenerator($generator);
$defaultAnalysis = $analyser->fromFile(__DIR__ . '/../Fixtures/ThirdPartyAnnotations.php', $this->getContext());
$this->assertCount(3, $defaultAnalysis->annotations, 'Only read the @OA annotations, skip the others.');

// Allow the analyser to parse 3rd party annotations, which might
// contain useful info that could be extracted with a custom processor
DocBlockParser::$whitelist[] = 'AnotherNamespace\\Annotations\\';
$openapi = (new Generator())
$generator->addNamespace('AnotherNamespace\\Annotations\\');
$openapi = $generator
->setAnalyser(new TokenAnalyser())
->generate([__DIR__ . '/../Fixtures/ThirdPartyAnnotations.php']);
$this->assertSame('api/3rd-party', $openapi->paths[0]->path);
$this->assertCount(4, $openapi->_unmerged);
DocBlockParser::$whitelist = $backup;

$analysis = $openapi->_analysis;
$annotations = $analysis->getAnnotationsOfType('AnotherNamespace\Annotations\Unrelated');
$this->assertCount(4, $annotations);
Expand Down
Loading

0 comments on commit 53add4a

Please sign in to comment.