From 3a40fb355789d25c6a5090f916fd032e813bf01d Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Sat, 30 Mar 2019 07:43:46 +0100 Subject: [PATCH 01/16] Project analyzer bootstrap --- composer.json | 19 +++++---- .../{ => get_exampe}/get_route_example.php | 0 examples/{ => hello_world}/hello_world.php | 2 + src/Analyzer/FileAnalyzer.php | 34 +++++++++++++++ src/Analyzer/ProjectAnalyzer.php | 41 +++++++++++++++++++ src/Annotation/Application.php | 1 - src/Exception/ProjectAnalyzerException.php | 24 +++++++++++ src/FileAnalyse.php | 26 ------------ src/Http/Response.php | 1 - src/ProjectAnalyser.php | 22 ---------- tests/Analyzer/ProjectAnalyzerTests.php | 15 +++++++ 11 files changed, 127 insertions(+), 58 deletions(-) rename examples/{ => get_exampe}/get_route_example.php (100%) rename examples/{ => hello_world}/hello_world.php (97%) create mode 100644 src/Analyzer/FileAnalyzer.php create mode 100644 src/Analyzer/ProjectAnalyzer.php create mode 100644 src/Exception/ProjectAnalyzerException.php delete mode 100644 src/FileAnalyse.php delete mode 100644 src/ProjectAnalyser.php create mode 100644 tests/Analyzer/ProjectAnalyzerTests.php diff --git a/composer.json b/composer.json index 9c61914..5f46165 100644 --- a/composer.json +++ b/composer.json @@ -22,11 +22,14 @@ "ext-mbstring" : "*", "psr/http-message": ">=1.0", "fatcode/annotations": ">=1.0", - "zendframework/zend-diactoros": "^2.1" + "zendframework/zend-diactoros": ">=2.1", + "nikic/php-parser": ">=4.2" }, "require-dev": { "phpunit/phpunit": ">=8.0", - "mockery/mockery": ">=1.2" + "mockery/mockery": ">=1.2", + "vimeo/psalm": ">=3.2", + "squizlabs/php_codesniffer": ">=3.0" }, "autoload": { "psr-4": { @@ -38,10 +41,10 @@ "FatCode\\Tests\\OpenApi\\": "tests/" } }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/fat-code/annotations" - } - ] + "scripts": { + "phpunit": "vendor/bin/phpunit --coverage-text", + "phpcs": "vendor/bin/phpcs --standard=PSR12 --warning-severity=0 src", + "phpcsf": "vendor/bin/phpcbf --standard=PSR12 --warning-severity=0 src", + "psalm": "vendor/bin/psalm --show-info=false" + } } diff --git a/examples/get_route_example.php b/examples/get_exampe/get_route_example.php similarity index 100% rename from examples/get_route_example.php rename to examples/get_exampe/get_route_example.php diff --git a/examples/hello_world.php b/examples/hello_world/hello_world.php similarity index 97% rename from examples/hello_world.php rename to examples/hello_world/hello_world.php index bf8e06c..d83cfcb 100644 --- a/examples/hello_world.php +++ b/examples/hello_world/hello_world.php @@ -1,5 +1,7 @@ fileName = $fileName; + } + + public function analyze() : void + { + $contents = file_get_contents($this->fileName); + $tokens = token_get_all($contents); + foreach ($tokens as $token) { + + } + } +} diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php new file mode 100644 index 0000000..260317c --- /dev/null +++ b/src/Analyzer/ProjectAnalyzer.php @@ -0,0 +1,41 @@ +directory = new RecursiveDirectoryIterator($directory); + } + + public function analyze() : void + { + $allFiles = new RecursiveIteratorIterator($this->directory); + $phpFiles = new RegexIterator($allFiles, '/.*\.php$/i'); + /** @var \SplFileInfo $file */ + foreach ($phpFiles as $file) { + $fileAnalyzer = new FileAnalyzer($file->getRealPath()); + $fileAnalyzer->analyze(); + } + } + + public function readFromFile(string $filename) : void + { + } +} diff --git a/src/Annotation/Application.php b/src/Annotation/Application.php index 48b1481..907cbe0 100644 --- a/src/Annotation/Application.php +++ b/src/Annotation/Application.php @@ -74,4 +74,3 @@ final class Application */ public $security; } - diff --git a/src/Exception/ProjectAnalyzerException.php b/src/Exception/ProjectAnalyzerException.php new file mode 100644 index 0000000..f84fcc2 --- /dev/null +++ b/src/Exception/ProjectAnalyzerException.php @@ -0,0 +1,24 @@ +analyze(); + } +} From 6863ab81d1be211e7a38407e7d280004f4c136f2 Mon Sep 17 00:00:00 2001 From: Dawid Date: Sun, 31 Mar 2019 17:07:20 +0200 Subject: [PATCH 02/16] ReflectionFile implementation --- examples/hello_world/hello_world.php | 60 ++++++++++-------- examples/multi_namespaces_example/index.php | 38 +++++++++++ src/Analyzer/FileAnalyzer.php | 63 ++++++++++++++++++- src/Annotation/SecurityScheme/OAuthFlow.php | 4 +- src/Annotation/SecurityScheme/OAuthFlows.php | 4 +- .../SecurityScheme/SecurityRequirement.php | 4 +- 6 files changed, 137 insertions(+), 36 deletions(-) create mode 100644 examples/multi_namespaces_example/index.php diff --git a/examples/hello_world/hello_world.php b/examples/hello_world/hello_world.php index d83cfcb..2ca5159 100644 --- a/examples/hello_world/hello_world.php +++ b/examples/hello_world/hello_world.php @@ -1,34 +1,42 @@ fileName); $tokens = token_get_all($contents); - foreach ($tokens as $token) { + for ($this->cursor = 0; $this->cursor < count($tokens); $this->cursor++) { + $token = $tokens[$this->cursor]; + if (!is_array($token)) { + continue; + } + switch ($token[0]) { + case T_NAMESPACE: + $this->parseNamespace(); + break; + case T_CLASS: + $this->parseClass(); + break; + case T_FUNCTION: + $this->parseFunction(); + break; + case T_USE: + $this->parseUse(); + break; + case T_INTERFACE: + $this->parseInterface(); + break; + case T_TRAIT: + $this->parseTrait(); + break; + } } } + + private function parseNamespace() : void + { + + } + + private function parseClass() : void + { + + } + + private function parseFunction() : void + { + + } + + private function parseUse() : void + { + + } + + private function parseTrait() : void + { + + } + + private function parseInterface() : void + { + + } } diff --git a/src/Annotation/SecurityScheme/OAuthFlow.php b/src/Annotation/SecurityScheme/OAuthFlow.php index 3185626..aae16f6 100644 --- a/src/Annotation/SecurityScheme/OAuthFlow.php +++ b/src/Annotation/SecurityScheme/OAuthFlow.php @@ -2,12 +2,10 @@ namespace FatCode\OpenApi\Annotation\SecurityScheme; -use FatCode\OpenApi\Annotation\Annotation; - /** * @Annotation */ -class OAuthFlow extends Annotation +class OAuthFlow { /** * The authorization URL to be used for this flow. This MUST be in the form of a URL. diff --git a/src/Annotation/SecurityScheme/OAuthFlows.php b/src/Annotation/SecurityScheme/OAuthFlows.php index 1a65f27..c9df94f 100644 --- a/src/Annotation/SecurityScheme/OAuthFlows.php +++ b/src/Annotation/SecurityScheme/OAuthFlows.php @@ -2,12 +2,10 @@ namespace FatCode\OpenApi\Annotation\SecurityScheme; -use FatCode\OpenApi\Annotation\Annotation; - /** * @Annotation */ -class OAuthFlows extends Annotation +class OAuthFlows { /** * @var OAuthFlow diff --git a/src/Annotation/SecurityScheme/SecurityRequirement.php b/src/Annotation/SecurityScheme/SecurityRequirement.php index f33bd63..740f707 100644 --- a/src/Annotation/SecurityScheme/SecurityRequirement.php +++ b/src/Annotation/SecurityScheme/SecurityRequirement.php @@ -2,14 +2,12 @@ namespace FatCode\OpenApi\Annotation\SecurityScheme; -use FatCode\OpenApi\Annotation\Annotation; - /** * Keeps the required security schemes to execute given operation. * @Annotation * @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#security-requirement-object */ -final class SecurityRequirement extends Annotation +final class SecurityRequirement { public $requirement; From 4dae996d870fbfc3813a7d0a3d85096484d49b25 Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Mon, 1 Apr 2019 21:34:53 +0200 Subject: [PATCH 03/16] FileAnalyzer bootstrap --- examples/hello_world/hello_world.php | 4 +- src/Analyzer/FileAnalyzer.php | 100 +++++++++++++++++++++------ src/Analyzer/ProjectAnalyzer.php | 1 + 3 files changed, 81 insertions(+), 24 deletions(-) diff --git a/examples/hello_world/hello_world.php b/examples/hello_world/hello_world.php index 2ca5159..acc101b 100644 --- a/examples/hello_world/hello_world.php +++ b/examples/hello_world/hello_world.php @@ -1,6 +1,6 @@ fileName = $fileName; + $this->tokens = token_get_all(file_get_contents($this->fileName)); + $this->eof = count($this->tokens); + } + + public function getFileName() : string + { + return $this->fileName; } public function analyze() : void { - $contents = file_get_contents($this->fileName); - $tokens = token_get_all($contents); - for ($this->cursor = 0; $this->cursor < count($tokens); $this->cursor++) { - $token = $tokens[$this->cursor]; + for ($this->cursor = 0; $this->cursor < $this->eof; $this->cursor++) { + $token = $this->getCurrentToken(); if (!is_array($token)) { continue; } @@ -50,27 +56,41 @@ public function analyze() : void case T_FUNCTION: $this->parseFunction(); break; - case T_USE: - $this->parseUse(); - break; - case T_INTERFACE: - $this->parseInterface(); - break; - case T_TRAIT: - $this->parseTrait(); - break; } } + + $a = 1; } - private function parseNamespace() : void + private function getCurrentToken() { + return $this->tokens[$this->cursor]; + } + private function parseNamespace() : void + { + $this->currentNamespace = ''; + while ($this->cursor++ < $this->eof) { + $token = $this->getCurrentToken(); + if (is_array($token) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { + $this->currentNamespace .= $token[1]; + } + // Namespace can end either with { or ; + if ($token === ';' || $token === '{') { + return; + } + } } private function parseClass() : void { + $this->seekToken(T_STRING); + $className = $this->getCurrentToken()[1]; + // Skip extend, implements and other keywords + $this->seekStartOfBlock(); + $this->seekEndOfBlock(); + $this->declaredClasses[] = $className; } private function parseFunction() : void @@ -78,18 +98,54 @@ private function parseFunction() : void } - private function parseUse() : void + private function seekToken(int $type) : void { - + while ($this->cursor++ < $this->eof) { + $token = $this->getCurrentToken(); + if (!is_array($token)) { + continue; + } + if ($token[0] === $type) { + break; + } + } } - private function parseTrait() : void + private function seekValue(string $value) : void { - + while ($this->cursor++ < $this->eof) { + $token = $this->getCurrentToken(); + if (!is_array($token)) { + if ($token === $value) { + break; + } + continue; + } + if ($token[1] === $value) { + break; + } + } } - private function parseInterface() : void + private function seekStartOfBlock() : void { + $this->seekValue('{'); + } + private function seekEndOfBlock() : void + { + $depth = 1; + while ($this->cursor++ < $this->eof & $depth > 0) { + $token = $this->getCurrentToken(); + if (is_array($token)) { + continue; + } + if ($token === '}') { + $depth--; + } + if ($depth === '{') { + $depth++; + } + } } } diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php index 260317c..4d4b459 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectAnalyzer.php @@ -2,6 +2,7 @@ namespace FatCode\OpenApi\Analyzer; +use function App\HelloWorld\dupa; use FatCode\OpenApi\Exception\ProjectAnalyzerException; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; From 9e1ec34c02c2f2e045dea2df2268e40d4e4648a3 Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Mon, 1 Apr 2019 21:47:46 +0200 Subject: [PATCH 04/16] Add use parsing --- src/Analyzer/FileAnalyzer.php | 8 ++++++++ src/Analyzer/ProjectAnalyzer.php | 1 - 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Analyzer/FileAnalyzer.php b/src/Analyzer/FileAnalyzer.php index 92e0ca0..bee912a 100644 --- a/src/Analyzer/FileAnalyzer.php +++ b/src/Analyzer/FileAnalyzer.php @@ -56,6 +56,9 @@ public function analyze() : void case T_FUNCTION: $this->parseFunction(); break; + case T_USE: + $this->parseUse(); + break; } } @@ -93,6 +96,11 @@ private function parseClass() : void $this->declaredClasses[] = $className; } + private function parseUse() : void + { + + } + private function parseFunction() : void { diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php index 4d4b459..260317c 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectAnalyzer.php @@ -2,7 +2,6 @@ namespace FatCode\OpenApi\Analyzer; -use function App\HelloWorld\dupa; use FatCode\OpenApi\Exception\ProjectAnalyzerException; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; From 6b91b822c40512b796582035480f3b481611703c Mon Sep 17 00:00:00 2001 From: Jakub Giminski Date: Thu, 11 Apr 2019 18:28:59 +0100 Subject: [PATCH 05/16] Introduce File value object --- src/File/FileException.php | 18 ++++++++++++++ src/File/PhpFile.php | 49 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/File/FileException.php create mode 100644 src/File/PhpFile.php diff --git a/src/File/FileException.php b/src/File/FileException.php new file mode 100644 index 0000000..319f43b --- /dev/null +++ b/src/File/FileException.php @@ -0,0 +1,18 @@ +name = $name; + } + + public function getTokens() : array + { + return token_get_all(file_get_contents($this->name)); + } + + public function getTokenAt(int $cursor) + { + return $this->getTokens()[$cursor]; + } + + public function endsAt(): int + { + return count($this->getTokens()); + } + + public function __toString() : string + { + return $this->name; + } + + private static function validateFile(string $name) : void + { + if (!is_file($name) || !is_readable($name)) { + throw FileException::notReadable($name); + } + + try { + require_once $name; + } catch (Throwable $throwable) { + throw FileException::invalidFile($name); + } + } +} \ No newline at end of file From 5d2799f20d94b6a5b02d9f6d22c4fa4d2ba76f37 Mon Sep 17 00:00:00 2001 From: Jakub Giminski Date: Thu, 11 Apr 2019 20:19:22 +0100 Subject: [PATCH 06/16] Prototype a new design --- src/Analyzer/FileAnalyzer.php | 161 +++--------------------- src/Analyzer/Parser/ClassParser.php | 48 +++++++ src/Analyzer/Parser/FunctionParser.php | 13 ++ src/Analyzer/Parser/NamespaceParser.php | 42 +++++++ src/Analyzer/Parser/PhpFileParser.php | 10 ++ src/Analyzer/PhpFileInfo.php | 44 +++++++ src/Analyzer/ProjectAnalyzer.php | 5 +- src/File/PhpFile.php | 2 +- tests/Analyzer/FileAnalyzerTests.php | 17 +++ 9 files changed, 196 insertions(+), 146 deletions(-) create mode 100644 src/Analyzer/Parser/ClassParser.php create mode 100644 src/Analyzer/Parser/FunctionParser.php create mode 100644 src/Analyzer/Parser/NamespaceParser.php create mode 100644 src/Analyzer/Parser/PhpFileParser.php create mode 100644 src/Analyzer/PhpFileInfo.php create mode 100644 tests/Analyzer/FileAnalyzerTests.php diff --git a/src/Analyzer/FileAnalyzer.php b/src/Analyzer/FileAnalyzer.php index bee912a..e3fed5c 100644 --- a/src/Analyzer/FileAnalyzer.php +++ b/src/Analyzer/FileAnalyzer.php @@ -2,158 +2,33 @@ namespace FatCode\OpenApi\Analyzer; -use FatCode\OpenApi\Exception\ProjectAnalyzerException; -use Throwable; - -use function count; +use FatCode\OpenApi\Analyzer\Parser\ClassParser; +use FatCode\OpenApi\Analyzer\Parser\FunctionParser; +use FatCode\OpenApi\Analyzer\Parser\NamespaceParser; +use FatCode\OpenApi\File\PhpFile; class FileAnalyzer { - private $fileName; - private $cursor; - private $tokens; - private $eof; - private $currentNamespace; - private $declaredClasses = []; - private $declaredFunctions = []; - - public function __construct(string $fileName) - { - if (!is_file($fileName) || !is_readable($fileName)) { - throw ProjectAnalyzerException::forUnreadableFile($fileName); - } - try { - require_once $fileName; - } catch (Throwable $throwable) { - throw ProjectAnalyzerException::forInvalidFile($fileName); - } - - $this->fileName = $fileName; - $this->tokens = token_get_all(file_get_contents($this->fileName)); - $this->eof = count($this->tokens); - } - - public function getFileName() : string - { - return $this->fileName; - } - - public function analyze() : void - { - for ($this->cursor = 0; $this->cursor < $this->eof; $this->cursor++) { - $token = $this->getCurrentToken(); - if (!is_array($token)) { - continue; - } - - switch ($token[0]) { - case T_NAMESPACE: - $this->parseNamespace(); - break; - case T_CLASS: - $this->parseClass(); - break; - case T_FUNCTION: - $this->parseFunction(); - break; - case T_USE: - $this->parseUse(); - break; - } - } + private $namespaceParser; - $a = 1; - } - - private function getCurrentToken() - { - return $this->tokens[$this->cursor]; - } - - private function parseNamespace() : void - { - $this->currentNamespace = ''; - while ($this->cursor++ < $this->eof) { - $token = $this->getCurrentToken(); - if (is_array($token) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { - $this->currentNamespace .= $token[1]; - } - // Namespace can end either with { or ; - if ($token === ';' || $token === '{') { - return; - } - } - } + private $classParser; - private function parseClass() : void - { - $this->seekToken(T_STRING); - $className = $this->getCurrentToken()[1]; - // Skip extend, implements and other keywords - $this->seekStartOfBlock(); - $this->seekEndOfBlock(); - - $this->declaredClasses[] = $className; - } - - private function parseUse() : void - { - - } - - private function parseFunction() : void - { - - } - - private function seekToken(int $type) : void - { - while ($this->cursor++ < $this->eof) { - $token = $this->getCurrentToken(); - if (!is_array($token)) { - continue; - } - if ($token[0] === $type) { - break; - } - } - } - - private function seekValue(string $value) : void - { - while ($this->cursor++ < $this->eof) { - $token = $this->getCurrentToken(); - if (!is_array($token)) { - if ($token === $value) { - break; - } - continue; - } - if ($token[1] === $value) { - break; - } - } - } + private $functionParser; - private function seekStartOfBlock() : void + public function __construct(NamespaceParser $namespaceParser, ClassParser $classParser, FunctionParser $functionParser) { - $this->seekValue('{'); + $this->namespaceParser = $namespaceParser; + $this->classParser = $classParser; + $this->functionParser = $functionParser; } - private function seekEndOfBlock() : void + public function analyze(PhpFile $file) : PhpFileInfo { - $depth = 1; - while ($this->cursor++ < $this->eof & $depth > 0) { - $token = $this->getCurrentToken(); - if (is_array($token)) { - continue; - } - if ($token === '}') { - $depth--; - } - if ($depth === '{') { - $depth++; - } - } + return new PhpFileInfo( + $file, + $this->namespaceParser->parse($file), + $this->classParser->parse($file), + $this->functionParser->parse($file) + ); } } diff --git a/src/Analyzer/Parser/ClassParser.php b/src/Analyzer/Parser/ClassParser.php new file mode 100644 index 0000000..d45687c --- /dev/null +++ b/src/Analyzer/Parser/ClassParser.php @@ -0,0 +1,48 @@ +countTokens(); $cursor++) { + $token = $file->getTokenAt($cursor); + + if (!is_array($token) || $token[0] !== T_CLASS) { + continue; + } + + $classes[] = $this->parseClassAt($cursor, $file); + } + + return $classes; + } + + private function parseClassAt(int $cursor, PhpFile $file): string + { + $cursor = $this->seekCursorForToken(T_STRING, $cursor, $file); + return $file->getTokenAt($cursor)[1]; + } + + private function seekCursorForToken(int $seekedToken, int $cursor, PhpFile $file) : int + { + while ($cursor++ < $file->countTokens()) { + $token = $file->getTokenAt($cursor); + + if (!is_array($token)) { + continue; + } + + if ($token[0] === $seekedToken) { + break; + } + } + + return $cursor; + } +} diff --git a/src/Analyzer/Parser/FunctionParser.php b/src/Analyzer/Parser/FunctionParser.php new file mode 100644 index 0000000..a6dd0ae --- /dev/null +++ b/src/Analyzer/Parser/FunctionParser.php @@ -0,0 +1,13 @@ +countTokens(); $cursor++) { + $token = $file->getTokenAt($cursor); + + if (!is_array($token) || $token[0] !== T_NAMESPACE) { + continue; + } + + $namespaces[] = $this->parseNamespaceAt($cursor, $file); + } + + return $namespaces; + } + + private function parseNamespaceAt(int $cursor, PhpFile $file): string + { + $namespace = ''; + + while ($cursor++ < $file->countTokens()) { + $token = $file->getTokenAt($cursor); + + if (is_array($token) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { + $namespace .= $token[1]; + } + + if ($token === ';' || $token === '{') { // End of namespace + return $namespace; + } + } + } +} diff --git a/src/Analyzer/Parser/PhpFileParser.php b/src/Analyzer/Parser/PhpFileParser.php new file mode 100644 index 0000000..07e162f --- /dev/null +++ b/src/Analyzer/Parser/PhpFileParser.php @@ -0,0 +1,10 @@ +file = $file; + $this->namespaces = $namespaces; + $this->classes = $classes; + $this->functions = $functions; + } + + public function getFile(): PhpFile + { + return $this->file; + } + + public function getNamespaces(): array + { + return $this->namespaces; + } + + public function getClasses(): array + { + return $this->classes; + } + + public function getFunctions(): array + { + return $this->functions; + } +} \ No newline at end of file diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php index 260317c..d3d459a 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectAnalyzer.php @@ -3,6 +3,7 @@ namespace FatCode\OpenApi\Analyzer; use FatCode\OpenApi\Exception\ProjectAnalyzerException; +use FatCode\OpenApi\File\PhpFile; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RegexIterator; @@ -30,8 +31,8 @@ public function analyze() : void $phpFiles = new RegexIterator($allFiles, '/.*\.php$/i'); /** @var \SplFileInfo $file */ foreach ($phpFiles as $file) { - $fileAnalyzer = new FileAnalyzer($file->getRealPath()); - $fileAnalyzer->analyze(); + $fileAnalyzer = new FileAnalyzer(); + $fileAnalyzer->analyze(new PhpFile($file->getRealPath())); } } diff --git a/src/File/PhpFile.php b/src/File/PhpFile.php index 802913d..40bcd1f 100644 --- a/src/File/PhpFile.php +++ b/src/File/PhpFile.php @@ -24,7 +24,7 @@ public function getTokenAt(int $cursor) return $this->getTokens()[$cursor]; } - public function endsAt(): int + public function countTokens(): int { return count($this->getTokens()); } diff --git a/tests/Analyzer/FileAnalyzerTests.php b/tests/Analyzer/FileAnalyzerTests.php new file mode 100644 index 0000000..69b1c6c --- /dev/null +++ b/tests/Analyzer/FileAnalyzerTests.php @@ -0,0 +1,17 @@ +analyze( + new PhpFile(__DIR__ . '/../../examples/hello_world/hello_world.php') + ); + } +} From a7a360cf3cf6cf9105c455354d1e84bae9110e31 Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Tue, 23 Apr 2019 10:26:59 +0200 Subject: [PATCH 07/16] Fixed class parser --- src/Analyzer/FileAnalyzer.php | 7 +- src/Analyzer/Parser/ClassParser.php | 39 +++--- src/Analyzer/Parser/NamespaceParser.php | 32 ++--- src/Analyzer/PhpFileInfo.php | 10 +- src/Analyzer/ProjectAnalyzer.php | 18 +-- src/Exception/ProjectAnalyzerException.php | 5 + src/File/PhpFile.php | 132 +++++++++++++++++++-- tests/Analyzer/Parser/ClassParserTest.php | 24 ++++ 8 files changed, 201 insertions(+), 66 deletions(-) create mode 100644 tests/Analyzer/Parser/ClassParserTest.php diff --git a/src/Analyzer/FileAnalyzer.php b/src/Analyzer/FileAnalyzer.php index e3fed5c..70d6c31 100644 --- a/src/Analyzer/FileAnalyzer.php +++ b/src/Analyzer/FileAnalyzer.php @@ -15,8 +15,11 @@ class FileAnalyzer private $functionParser; - public function __construct(NamespaceParser $namespaceParser, ClassParser $classParser, FunctionParser $functionParser) - { + public function __construct( + NamespaceParser $namespaceParser, + ClassParser $classParser, + FunctionParser $functionParser + ) { $this->namespaceParser = $namespaceParser; $this->classParser = $classParser; $this->functionParser = $functionParser; diff --git a/src/Analyzer/Parser/ClassParser.php b/src/Analyzer/Parser/ClassParser.php index d45687c..da61650 100644 --- a/src/Analyzer/Parser/ClassParser.php +++ b/src/Analyzer/Parser/ClassParser.php @@ -4,45 +4,40 @@ use FatCode\OpenApi\File\PhpFile; +use function is_array; + class ClassParser implements PhpFileParser { + use NamespaceParser; + + private $currentNamespace = ''; + public function parse(PhpFile $file) : array { $classes = []; - for ($cursor = 0; $cursor < $file->countTokens(); $cursor++) { - $token = $file->getTokenAt($cursor); + foreach ($file as $index => $token) { + if (is_array($token) && $token[0] === T_NAMESPACE) { + $this->currentNamespace = $this->parseNamespaceAt($file); + } if (!is_array($token) || $token[0] !== T_CLASS) { continue; } - $classes[] = $this->parseClassAt($cursor, $file); + $classes[] = $this->currentNamespace . '\\' . $this->parseClassAt($file); } return $classes; } - private function parseClassAt(int $cursor, PhpFile $file): string - { - $cursor = $this->seekCursorForToken(T_STRING, $cursor, $file); - return $file->getTokenAt($cursor)[1]; - } - - private function seekCursorForToken(int $seekedToken, int $cursor, PhpFile $file) : int + private function parseClassAt(PhpFile $file) : string { - while ($cursor++ < $file->countTokens()) { - $token = $file->getTokenAt($cursor); - - if (!is_array($token)) { - continue; - } - - if ($token[0] === $seekedToken) { - break; - } - } + $file->seekToken(T_STRING); + $className = $file->current()[1]; + $file->seekStartOfScope(); + $file->skipScope(); - return $cursor; + return $className; } } diff --git a/src/Analyzer/Parser/NamespaceParser.php b/src/Analyzer/Parser/NamespaceParser.php index 40d4925..dcb8397 100644 --- a/src/Analyzer/Parser/NamespaceParser.php +++ b/src/Analyzer/Parser/NamespaceParser.php @@ -2,33 +2,19 @@ namespace FatCode\OpenApi\Analyzer\Parser; +use FatCode\OpenApi\Exception\ProjectAnalyzerException; use FatCode\OpenApi\File\PhpFile; -class NamespaceParser implements PhpFileParser -{ - public function parse(PhpFile $file) : array - { - $namespaces = []; - - for ($cursor = 0; $cursor < $file->countTokens(); $cursor++) { - $token = $file->getTokenAt($cursor); - - if (!is_array($token) || $token[0] !== T_NAMESPACE) { - continue; - } - - $namespaces[] = $this->parseNamespaceAt($cursor, $file); - } - - return $namespaces; - } +use function is_array; - private function parseNamespaceAt(int $cursor, PhpFile $file): string +trait NamespaceParser +{ + protected function parseNamespaceAt(PhpFile $file) : string { $namespace = ''; - - while ($cursor++ < $file->countTokens()) { - $token = $file->getTokenAt($cursor); + while ($file->valid()) { + $file->next(); + $token = $file->current(); if (is_array($token) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { $namespace .= $token[1]; @@ -38,5 +24,7 @@ private function parseNamespaceAt(int $cursor, PhpFile $file): string return $namespace; } } + + throw ProjectAnalyzerException::forInvalidNamespace(); } } diff --git a/src/Analyzer/PhpFileInfo.php b/src/Analyzer/PhpFileInfo.php index 8054b18..b5c6d93 100644 --- a/src/Analyzer/PhpFileInfo.php +++ b/src/Analyzer/PhpFileInfo.php @@ -14,7 +14,7 @@ class PhpFileInfo private $functions; - public function __construct(PhpFile $file, array $namespaces, array $classes , array $functions) + public function __construct(PhpFile $file, array $namespaces, array $classes, array $functions) { $this->file = $file; $this->namespaces = $namespaces; @@ -27,18 +27,18 @@ public function getFile(): PhpFile return $this->file; } - public function getNamespaces(): array + public function getNamespaces() : array { return $this->namespaces; } - public function getClasses(): array + public function getClasses() : array { return $this->classes; } - public function getFunctions(): array + public function getFunctions() : array { return $this->functions; } -} \ No newline at end of file +} diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php index d3d459a..4c56488 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectAnalyzer.php @@ -2,6 +2,9 @@ namespace FatCode\OpenApi\Analyzer; +use FatCode\OpenApi\Analyzer\Parser\ClassParser; +use FatCode\OpenApi\Analyzer\Parser\FunctionParser; +use FatCode\OpenApi\Analyzer\Parser\NamespaceParser; use FatCode\OpenApi\Exception\ProjectAnalyzerException; use FatCode\OpenApi\File\PhpFile; use RecursiveDirectoryIterator; @@ -15,14 +18,20 @@ class ProjectAnalyzer { private $directory; + private $fileAnalyzer; - public function __construct(string $directory) + public function __construct(string $directory, FileAnalyzer $fileAnalyzer) { if (!is_dir($directory)) { throw ProjectAnalyzerException::forInvalidDirectory($directory); } $this->directory = new RecursiveDirectoryIterator($directory); + $this->fileAnalyzer = $fileAnalyzer ?? new FileAnalyzer( + new NamespaceParser(), + new ClassParser(), + new FunctionParser() + ); } public function analyze() : void @@ -31,12 +40,7 @@ public function analyze() : void $phpFiles = new RegexIterator($allFiles, '/.*\.php$/i'); /** @var \SplFileInfo $file */ foreach ($phpFiles as $file) { - $fileAnalyzer = new FileAnalyzer(); - $fileAnalyzer->analyze(new PhpFile($file->getRealPath())); + $this->fileAnalyzer->analyze(new PhpFile($file->getRealPath())); } } - - public function readFromFile(string $filename) : void - { - } } diff --git a/src/Exception/ProjectAnalyzerException.php b/src/Exception/ProjectAnalyzerException.php index f84fcc2..75ba022 100644 --- a/src/Exception/ProjectAnalyzerException.php +++ b/src/Exception/ProjectAnalyzerException.php @@ -21,4 +21,9 @@ public static function forInvalidFile(string $filename) : self { return new self("Passed file `{$filename}` is not valid php file."); } + + public static function forInvalidNamespace() : self + { + return new self('Could not parse namespace.'); + } } diff --git a/src/File/PhpFile.php b/src/File/PhpFile.php index 40bcd1f..04dca5f 100644 --- a/src/File/PhpFile.php +++ b/src/File/PhpFile.php @@ -2,31 +2,145 @@ namespace FatCode\OpenApi\File; +use Iterator; use Throwable; -class PhpFile +use function count; +use function file_get_contents; +use function is_file; +use function is_readable; +use function token_get_all; + +class PhpFile implements Iterator { private $name; + private $cursor; + private $tokens; + private $length; public function __construct(string $name) { - self::validateFile($name); + $this->validateFile($name); $this->name = $name; } + public function current() + { + return $this->tokens[$this->cursor]; + } + + public function prev() : void + { + $this->cursor--; + } + + public function next() : void + { + $this->cursor++; + } + + public function key() + { + return $this->cursor; + } + + public function valid() : bool + { + return $this->cursor >= 0 && $this->cursor < $this->length; + } + + public function rewind() : void + { + $this->cursor = 0; + } + + public function getCursor() : int + { + return $this->cursor; + } + public function getTokens() : array { - return token_get_all(file_get_contents($this->name)); + return $this->tokens; } public function getTokenAt(int $cursor) { - return $this->getTokens()[$cursor]; + return $this->tokens[$cursor]; + } + + public function seekToken(int $token) : bool + { + while ($this->valid()) { + $current = $this->current(); + + if (!is_array($current)) { + continue; + } + + if ($current[0] === $token) { + return true; + } + $this->next(); + } + + return false; + } + + public function seekStartOfScope() : bool + { + while ($this->valid()) { + $current = $this->current(); + + if (is_array($current)) { + $this->next(); + continue; + } + + if ($current === '{') { + return true; + } + $this->next(); + } + + return false; + } + + public function skipScope() : bool + { + if ($this->current() === '{') { + $this->next(); + } + $depth = 1; + while ($this->valid()) { + $current = $this->current(); + + if (is_array($current)) { + $this->next(); + continue; + } + + if ($current === '{') { + $depth++; + $this->next(); + continue; + } + + if ($current === '}') { + $depth--; + if ($depth === 0) { + return true; + } + } + $this->next(); + } + + return false; } - public function countTokens(): int + public function countTokens() : int { - return count($this->getTokens()); + return $this->length; } public function __toString() : string @@ -34,7 +148,7 @@ public function __toString() : string return $this->name; } - private static function validateFile(string $name) : void + private function validateFile(string $name) : void { if (!is_file($name) || !is_readable($name)) { throw FileException::notReadable($name); @@ -42,8 +156,10 @@ private static function validateFile(string $name) : void try { require_once $name; + $this->tokens = token_get_all(file_get_contents($name)); + $this->length = count($this->tokens); } catch (Throwable $throwable) { throw FileException::invalidFile($name); } } -} \ No newline at end of file +} diff --git a/tests/Analyzer/Parser/ClassParserTest.php b/tests/Analyzer/Parser/ClassParserTest.php new file mode 100644 index 0000000..680fb95 --- /dev/null +++ b/tests/Analyzer/Parser/ClassParserTest.php @@ -0,0 +1,24 @@ +parse(new PhpFile(__FILE__)); + + self::assertCount(1, $info); + self::assertSame(ClassParserTest::class, $info[0]); + } +} From 307246453c454ecd914461757ae984e3d0d9d222 Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Sat, 27 Apr 2019 08:40:46 +0200 Subject: [PATCH 08/16] Class and Function parser implementation. --- src/Analyzer/Parser/ClassParser.php | 10 +++-- src/Analyzer/Parser/FunctionParser.php | 42 +++++++++++++++++++- src/Analyzer/Parser/NamespaceParser.php | 2 +- tests/Analyzer/Parser/FunctionParserTest.php | 33 +++++++++++++++ 4 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 tests/Analyzer/Parser/FunctionParserTest.php diff --git a/src/Analyzer/Parser/ClassParser.php b/src/Analyzer/Parser/ClassParser.php index da61650..0162e3d 100644 --- a/src/Analyzer/Parser/ClassParser.php +++ b/src/Analyzer/Parser/ClassParser.php @@ -6,6 +6,10 @@ use function is_array; +use const T_CLASS; +use const T_NAMESPACE; +use const T_STRING; + class ClassParser implements PhpFileParser { use NamespaceParser; @@ -18,20 +22,20 @@ public function parse(PhpFile $file) : array foreach ($file as $index => $token) { if (is_array($token) && $token[0] === T_NAMESPACE) { - $this->currentNamespace = $this->parseNamespaceAt($file); + $this->currentNamespace = $this->parseNamespace($file); } if (!is_array($token) || $token[0] !== T_CLASS) { continue; } - $classes[] = $this->currentNamespace . '\\' . $this->parseClassAt($file); + $classes[] = $this->currentNamespace . '\\' . $this->parseClass($file); } return $classes; } - private function parseClassAt(PhpFile $file) : string + private function parseClass(PhpFile $file) : string { $file->seekToken(T_STRING); $className = $file->current()[1]; diff --git a/src/Analyzer/Parser/FunctionParser.php b/src/Analyzer/Parser/FunctionParser.php index a6dd0ae..a9b007d 100644 --- a/src/Analyzer/Parser/FunctionParser.php +++ b/src/Analyzer/Parser/FunctionParser.php @@ -4,10 +4,50 @@ use FatCode\OpenApi\File\PhpFile; +use function is_array; + +use const T_FUNCTION; +use const T_NAMESPACE; +use const T_STRING; + class FunctionParser implements PhpFileParser { + use NamespaceParser; + + private $currentNamespace = ''; + public function parse(PhpFile $file) : array { - return []; + $functions = []; + + foreach ($file as $index => $token) { + if (is_array($token) && $token[0] === T_NAMESPACE) { + $this->currentNamespace = $this->parseNamespace($file); + } + + if (is_array($token) && $token[0] === T_CLASS) { + $file->seekStartOfScope(); + $file->skipScope(); + continue; + } + + if (!is_array($token) || $token[0] !== T_FUNCTION) { + continue; + } + + $functions[] = $this->currentNamespace . '\\' . $this->parseFunction($file); + } + + return $functions; + } + + private function parseFunction(PhpFile $file) : string + { + $file->seekToken(T_STRING); + $className = $file->current()[1]; + $file->seekStartOfScope(); + $file->skipScope(); + + return $className; } } diff --git a/src/Analyzer/Parser/NamespaceParser.php b/src/Analyzer/Parser/NamespaceParser.php index dcb8397..68b9350 100644 --- a/src/Analyzer/Parser/NamespaceParser.php +++ b/src/Analyzer/Parser/NamespaceParser.php @@ -9,7 +9,7 @@ trait NamespaceParser { - protected function parseNamespaceAt(PhpFile $file) : string + protected function parseNamespace(PhpFile $file) : string { $namespace = ''; while ($file->valid()) { diff --git a/tests/Analyzer/Parser/FunctionParserTest.php b/tests/Analyzer/Parser/FunctionParserTest.php new file mode 100644 index 0000000..d6832ab --- /dev/null +++ b/tests/Analyzer/Parser/FunctionParserTest.php @@ -0,0 +1,33 @@ +parse(new PhpFile(__FILE__)); + + self::assertCount(2, $info); + self::assertSame(__NAMESPACE__ . '\test_a', $info[0]); + self::assertSame(__NAMESPACE__ . '\test_b', $info[1]); + } +} + +function test_a() +{ +} + +function test_b() +{ +} From 815730eddac230c3668f3071dd123e4956e859f4 Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Sat, 27 Apr 2019 08:46:00 +0200 Subject: [PATCH 09/16] Clean up --- src/Analyzer/FileAnalyzer.php | 6 ------ src/Analyzer/PhpFileInfo.php | 10 +--------- src/Analyzer/ProjectAnalyzer.php | 4 +--- 3 files changed, 2 insertions(+), 18 deletions(-) diff --git a/src/Analyzer/FileAnalyzer.php b/src/Analyzer/FileAnalyzer.php index 70d6c31..0a96102 100644 --- a/src/Analyzer/FileAnalyzer.php +++ b/src/Analyzer/FileAnalyzer.php @@ -4,23 +4,18 @@ use FatCode\OpenApi\Analyzer\Parser\ClassParser; use FatCode\OpenApi\Analyzer\Parser\FunctionParser; -use FatCode\OpenApi\Analyzer\Parser\NamespaceParser; use FatCode\OpenApi\File\PhpFile; class FileAnalyzer { - private $namespaceParser; - private $classParser; private $functionParser; public function __construct( - NamespaceParser $namespaceParser, ClassParser $classParser, FunctionParser $functionParser ) { - $this->namespaceParser = $namespaceParser; $this->classParser = $classParser; $this->functionParser = $functionParser; } @@ -29,7 +24,6 @@ public function analyze(PhpFile $file) : PhpFileInfo { return new PhpFileInfo( $file, - $this->namespaceParser->parse($file), $this->classParser->parse($file), $this->functionParser->parse($file) ); diff --git a/src/Analyzer/PhpFileInfo.php b/src/Analyzer/PhpFileInfo.php index b5c6d93..35877af 100644 --- a/src/Analyzer/PhpFileInfo.php +++ b/src/Analyzer/PhpFileInfo.php @@ -8,16 +8,13 @@ class PhpFileInfo { private $file; - private $namespaces; - private $classes; private $functions; - public function __construct(PhpFile $file, array $namespaces, array $classes, array $functions) + public function __construct(PhpFile $file, array $classes, array $functions) { $this->file = $file; - $this->namespaces = $namespaces; $this->classes = $classes; $this->functions = $functions; } @@ -27,11 +24,6 @@ public function getFile(): PhpFile return $this->file; } - public function getNamespaces() : array - { - return $this->namespaces; - } - public function getClasses() : array { return $this->classes; diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php index 4c56488..3b71226 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectAnalyzer.php @@ -4,7 +4,6 @@ use FatCode\OpenApi\Analyzer\Parser\ClassParser; use FatCode\OpenApi\Analyzer\Parser\FunctionParser; -use FatCode\OpenApi\Analyzer\Parser\NamespaceParser; use FatCode\OpenApi\Exception\ProjectAnalyzerException; use FatCode\OpenApi\File\PhpFile; use RecursiveDirectoryIterator; @@ -20,7 +19,7 @@ class ProjectAnalyzer private $directory; private $fileAnalyzer; - public function __construct(string $directory, FileAnalyzer $fileAnalyzer) + public function __construct(string $directory, FileAnalyzer $fileAnalyzer = null) { if (!is_dir($directory)) { throw ProjectAnalyzerException::forInvalidDirectory($directory); @@ -28,7 +27,6 @@ public function __construct(string $directory, FileAnalyzer $fileAnalyzer) $this->directory = new RecursiveDirectoryIterator($directory); $this->fileAnalyzer = $fileAnalyzer ?? new FileAnalyzer( - new NamespaceParser(), new ClassParser(), new FunctionParser() ); From eeb1cb7138c69221ffcf3ae618d652bccb2c93d0 Mon Sep 17 00:00:00 2001 From: Dawid Date: Sun, 28 Apr 2019 22:34:37 +0200 Subject: [PATCH 10/16] Renamed pathitem to operation --- src/Annotation/{PathItem => Operation}/CookieParameter.php | 2 +- src/Annotation/{PathItem => Operation}/Delete.php | 2 +- src/Annotation/{PathItem => Operation}/Get.php | 2 +- src/Annotation/{PathItem => Operation}/Head.php | 2 +- src/Annotation/{PathItem => Operation}/HeaderParameter.php | 2 +- src/Annotation/{PathItem => Operation}/Operation.php | 2 +- src/Annotation/{PathItem => Operation}/Options.php | 2 +- src/Annotation/{PathItem => Operation}/Patch.php | 2 +- src/Annotation/{PathItem => Operation}/PathParameter.php | 2 +- src/Annotation/{PathItem => Operation}/Post.php | 2 +- src/Annotation/{PathItem => Operation}/Put.php | 2 +- src/Annotation/{PathItem => Operation}/QueryParameter.php | 2 +- src/Annotation/{PathItem => Operation}/Trace.php | 2 +- tests/bootstrap.php | 0 14 files changed, 13 insertions(+), 13 deletions(-) rename src/Annotation/{PathItem => Operation}/CookieParameter.php (83%) rename src/Annotation/{PathItem => Operation}/Delete.php (86%) rename src/Annotation/{PathItem => Operation}/Get.php (86%) rename src/Annotation/{PathItem => Operation}/Head.php (86%) rename src/Annotation/{PathItem => Operation}/HeaderParameter.php (83%) rename src/Annotation/{PathItem => Operation}/Operation.php (97%) rename src/Annotation/{PathItem => Operation}/Options.php (86%) rename src/Annotation/{PathItem => Operation}/Patch.php (86%) rename src/Annotation/{PathItem => Operation}/PathParameter.php (83%) rename src/Annotation/{PathItem => Operation}/Post.php (86%) rename src/Annotation/{PathItem => Operation}/Put.php (86%) rename src/Annotation/{PathItem => Operation}/QueryParameter.php (83%) rename src/Annotation/{PathItem => Operation}/Trace.php (86%) create mode 100644 tests/bootstrap.php diff --git a/src/Annotation/PathItem/CookieParameter.php b/src/Annotation/Operation/CookieParameter.php similarity index 83% rename from src/Annotation/PathItem/CookieParameter.php rename to src/Annotation/Operation/CookieParameter.php index 6dacc6f..aeed56e 100644 --- a/src/Annotation/PathItem/CookieParameter.php +++ b/src/Annotation/Operation/CookieParameter.php @@ -1,6 +1,6 @@ Date: Sun, 28 Apr 2019 22:34:47 +0200 Subject: [PATCH 11/16] Renamed pathitem to operation --- coverage.clover | 546 +++++++++++++++++++ examples/hello_world/hello_world.php | 21 +- phpunit.xml | 2 +- src/Analyzer/FileAnalyzer.php | 8 +- src/Analyzer/ProjectAnalyzer.php | 3 +- src/Annotation/Operation/CookieParameter.php | 2 +- src/Annotation/Operation/Delete.php | 2 +- src/Annotation/Operation/Get.php | 2 +- src/Annotation/Operation/Head.php | 2 +- src/Annotation/Operation/HeaderParameter.php | 2 +- src/Annotation/Operation/Operation.php | 2 +- src/Annotation/Operation/Options.php | 2 +- src/Annotation/Operation/Patch.php | 2 +- src/Annotation/Operation/PathParameter.php | 2 +- src/Annotation/Operation/Post.php | 2 +- src/Annotation/Operation/Put.php | 2 +- src/Annotation/Operation/QueryParameter.php | 2 +- src/Annotation/Operation/Trace.php | 2 +- src/Annotation/PathItem.php | 2 +- tests/Analyzer/FileAnalyzerTests.php | 9 +- tests/bootstrap.php | 6 + 21 files changed, 597 insertions(+), 26 deletions(-) create mode 100644 coverage.clover diff --git a/coverage.clover b/coverage.clover new file mode 100644 index 0000000..41c0c7b --- /dev/null +++ b/coverage.clover @@ -0,0 +1,546 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/hello_world/hello_world.php b/examples/hello_world/hello_world.php index acc101b..cb122c0 100644 --- a/examples/hello_world/hello_world.php +++ b/examples/hello_world/hello_world.php @@ -31,12 +31,27 @@ public function onRequest(ServerRequestInterface $request) : ResponseInterface } } - function dupa() { + /** + * @Api\Schema( + * type="object" + * ) + */ + class Greeter + { } - function dupa2() { + /** + * @Api\Operation\Get( + * description="List all pets", + * route="/pets", + * responses=[ + * @Api\Response(schema=@Api\Reference(Greeter::class)) + * ] + * ) + */ + function sayHello() { } -# Run with `open-api run development` + # Run with `open-api run development` } diff --git a/phpunit.xml b/phpunit.xml index b8f8f3d..e7c9331 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -7,7 +7,7 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - bootstrap="vendor/autoload.php" + bootstrap="tests/bootstrap.php" > diff --git a/src/Analyzer/FileAnalyzer.php b/src/Analyzer/FileAnalyzer.php index 0a96102..cebc8c6 100644 --- a/src/Analyzer/FileAnalyzer.php +++ b/src/Analyzer/FileAnalyzer.php @@ -13,11 +13,11 @@ class FileAnalyzer private $functionParser; public function __construct( - ClassParser $classParser, - FunctionParser $functionParser + ClassParser $classParser = null, + FunctionParser $functionParser = null ) { - $this->classParser = $classParser; - $this->functionParser = $functionParser; + $this->classParser = $classParser ?? new ClassParser(); + $this->functionParser = $functionParser ?? new FunctionParser(); } public function analyze(PhpFile $file) : PhpFileInfo diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php index 3b71226..b07c279 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectAnalyzer.php @@ -9,6 +9,7 @@ use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RegexIterator; +use SplFileInfo; /** * Class ProjectAnalyser @@ -36,7 +37,7 @@ public function analyze() : void { $allFiles = new RecursiveIteratorIterator($this->directory); $phpFiles = new RegexIterator($allFiles, '/.*\.php$/i'); - /** @var \SplFileInfo $file */ + /** @var SplFileInfo $file */ foreach ($phpFiles as $file) { $this->fileAnalyzer->analyze(new PhpFile($file->getRealPath())); } diff --git a/src/Annotation/Operation/CookieParameter.php b/src/Annotation/Operation/CookieParameter.php index aeed56e..9f27667 100644 --- a/src/Annotation/Operation/CookieParameter.php +++ b/src/Annotation/Operation/CookieParameter.php @@ -1,6 +1,6 @@ analyze( - new PhpFile(__DIR__ . '/../../examples/hello_world/hello_world.php') - ); + $analyzer = new FileAnalyzer(); + $fileInfo = $analyzer->analyze(new PhpFile(EXAMPLES_DIR . '/hello_world/hello_world.php')); + + $a = 1; + } } diff --git a/tests/bootstrap.php b/tests/bootstrap.php index e69de29..a602eda 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -0,0 +1,6 @@ + Date: Mon, 29 Apr 2019 08:31:57 +0200 Subject: [PATCH 12/16] File Analyzer tests --- .gitignore | 1 + coverage.clover | 546 ---------------------- examples/hello_world/hello_world.php | 27 +- src/{File => Exception}/FileException.php | 4 +- src/File/PhpFile.php | 1 + tests/Analyzer/FileAnalyzerTests.php | 7 +- 6 files changed, 24 insertions(+), 562 deletions(-) delete mode 100644 coverage.clover rename src/{File => Exception}/FileException.php (91%) diff --git a/.gitignore b/.gitignore index 205e437..afc5f8e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ /vendor .phpunit.result.cache /composer.lock +/coverage.clover diff --git a/coverage.clover b/coverage.clover deleted file mode 100644 index 41c0c7b..0000000 --- a/coverage.clover +++ /dev/null @@ -1,546 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/hello_world/hello_world.php b/examples/hello_world/hello_world.php index cb122c0..9a5c3a0 100644 --- a/examples/hello_world/hello_world.php +++ b/examples/hello_world/hello_world.php @@ -5,8 +5,7 @@ use FatCode\OpenApi\Annotation as Api; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; - use FatCode\OpenApi\Application\OnRequestListener; - use Zend\Diactoros\Response; + use FatCode\OpenApi\Http\Response; /** * Your entry point/front controller. @@ -23,35 +22,41 @@ * ] * ) */ - class Application extends Response implements OnRequestListener + class Application { - public function onRequest(ServerRequestInterface $request) : ResponseInterface - { - return new Response('Hello world'); - } } /** * @Api\Schema( + * title="Greeter schema", * type="object" * ) */ class Greeter { + /** + * @Api\Property(type="string") + */ + public $name; + public function __construct(string $name) + { + $this->name = $name; + } } /** * @Api\Operation\Get( * description="List all pets", - * route="/pets", + * route="/welcome/{name}", * responses=[ - * @Api\Response(schema=@Api\Reference(Greeter::class)) + * @Api\Response(code=200, schema=@Api\Reference(Greeter::class)) * ] * ) */ - function sayHello() { - + function sayHello(ServerRequestInterface $request) : ResponseInterface + { + return new Response(200, new Greeter($request->getAttribute('name'))); } # Run with `open-api run development` } diff --git a/src/File/FileException.php b/src/Exception/FileException.php similarity index 91% rename from src/File/FileException.php rename to src/Exception/FileException.php index 319f43b..bbb999c 100644 --- a/src/File/FileException.php +++ b/src/Exception/FileException.php @@ -1,6 +1,6 @@ analyze(new PhpFile(EXAMPLES_DIR . '/hello_world/hello_world.php')); - $a = 1; - + self::assertSame(['App\HelloWorld\Application', 'App\HelloWorld\Greeter'], $fileInfo->getClasses()); + self::assertSame(['App\HelloWorld\sayHello'], $fileInfo->getFunctions()); } } From d58c2d2b1394416083aeb20a550b208c9e015b5d Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Sat, 4 May 2019 22:39:32 +0200 Subject: [PATCH 13/16] Docs --- examples/get_exampe/get_route_example.php | 4 ++-- examples/multi_directory_project/index.php | 25 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 examples/multi_directory_project/index.php diff --git a/examples/get_exampe/get_route_example.php b/examples/get_exampe/get_route_example.php index e28be8c..44482d9 100644 --- a/examples/get_exampe/get_route_example.php +++ b/examples/get_exampe/get_route_example.php @@ -18,8 +18,8 @@ class Application { /** - * @Api\PathItem\Get( - * route="/hello/{name<\w>}", + * @Api\Operation\Get( + * route="/hello/{name:\w}", * responses=[ * @Api\Response(code=200, schema=@Api\Reference(TextPlain::class)) * ] diff --git a/examples/multi_directory_project/index.php b/examples/multi_directory_project/index.php new file mode 100644 index 0000000..a6eebb2 --- /dev/null +++ b/examples/multi_directory_project/index.php @@ -0,0 +1,25 @@ + Date: Mon, 6 May 2019 11:40:46 +0200 Subject: [PATCH 14/16] Project analyzer refactor --- examples/hello_world/hello_world.php | 15 +++++++- src/Analyzer/AnnotationCollector.php | 19 ++++++++++ src/Analyzer/FileAnalyzer.php | 31 ---------------- src/Analyzer/Parser/ClassParser.php | 14 +++---- src/Analyzer/Parser/FunctionParser.php | 18 ++++----- src/Analyzer/Parser/NamespaceParser.php | 10 ++--- src/Analyzer/Parser/PhpFileParser.php | 10 ----- src/Analyzer/Parser/StreamAnalyzer.php | 10 +++++ src/Analyzer/PhpFileInfo.php | 36 ------------------ .../PhpFile.php => Analyzer/PhpStream.php} | 33 ++++++++++------- src/Analyzer/Project.php | 0 src/Analyzer/ProjectAnalyzer.php | 37 ++++++++++++++----- tests/Analyzer/FileAnalyzerTests.php | 21 ----------- tests/Analyzer/Parser/ClassParserTest.php | 4 +- tests/Analyzer/Parser/FunctionParserTest.php | 4 +- tests/Analyzer/ProjectAnalyzerTests.php | 4 +- 16 files changed, 117 insertions(+), 149 deletions(-) create mode 100644 src/Analyzer/AnnotationCollector.php delete mode 100644 src/Analyzer/FileAnalyzer.php delete mode 100644 src/Analyzer/Parser/PhpFileParser.php create mode 100644 src/Analyzer/Parser/StreamAnalyzer.php delete mode 100644 src/Analyzer/PhpFileInfo.php rename src/{File/PhpFile.php => Analyzer/PhpStream.php} (78%) create mode 100644 src/Analyzer/Project.php delete mode 100644 tests/Analyzer/FileAnalyzerTests.php diff --git a/examples/hello_world/hello_world.php b/examples/hello_world/hello_world.php index 9a5c3a0..3900b96 100644 --- a/examples/hello_world/hello_world.php +++ b/examples/hello_world/hello_world.php @@ -24,6 +24,19 @@ */ class Application { + /** + * @Api\Operation\Get( + * description="Farewell user", + * route="/goodbye/{name}", + * responses=[ + * @Api\Response(code=200, schema=@Api\Reference(Greeter::class)) + * ] + * ) + */ + public function sayGoodbye(ServerRequestInterface $request) : ResponseInterface + { + return new Response(200, new Greeter($request->getAttribute('name'))); + } } /** @@ -47,7 +60,7 @@ public function __construct(string $name) /** * @Api\Operation\Get( - * description="List all pets", + * description="Greet user", * route="/welcome/{name}", * responses=[ * @Api\Response(code=200, schema=@Api\Reference(Greeter::class)) diff --git a/src/Analyzer/AnnotationCollector.php b/src/Analyzer/AnnotationCollector.php new file mode 100644 index 0000000..534fed3 --- /dev/null +++ b/src/Analyzer/AnnotationCollector.php @@ -0,0 +1,19 @@ +classParser = $classParser ?? new ClassParser(); - $this->functionParser = $functionParser ?? new FunctionParser(); - } - - public function analyze(PhpFile $file) : PhpFileInfo - { - return new PhpFileInfo( - $file, - $this->classParser->parse($file), - $this->functionParser->parse($file) - ); - } -} diff --git a/src/Analyzer/Parser/ClassParser.php b/src/Analyzer/Parser/ClassParser.php index 0162e3d..eaa0d18 100644 --- a/src/Analyzer/Parser/ClassParser.php +++ b/src/Analyzer/Parser/ClassParser.php @@ -2,7 +2,7 @@ namespace FatCode\OpenApi\Analyzer\Parser; -use FatCode\OpenApi\File\PhpFile; +use FatCode\OpenApi\Analyzer\PhpStream; use function is_array; @@ -10,32 +10,32 @@ use const T_NAMESPACE; use const T_STRING; -class ClassParser implements PhpFileParser +class ClassParser implements StreamAnalyzer { use NamespaceParser; private $currentNamespace = ''; - public function parse(PhpFile $file) : array + public function analyze(PhpStream $stream) : array { $classes = []; - foreach ($file as $index => $token) { + foreach ($stream as $index => $token) { if (is_array($token) && $token[0] === T_NAMESPACE) { - $this->currentNamespace = $this->parseNamespace($file); + $this->currentNamespace = $this->parseNamespace($stream); } if (!is_array($token) || $token[0] !== T_CLASS) { continue; } - $classes[] = $this->currentNamespace . '\\' . $this->parseClass($file); + $classes[] = $this->currentNamespace . '\\' . $this->parseClass($stream); } return $classes; } - private function parseClass(PhpFile $file) : string + private function parseClass(PhpStream $file) : string { $file->seekToken(T_STRING); $className = $file->current()[1]; diff --git a/src/Analyzer/Parser/FunctionParser.php b/src/Analyzer/Parser/FunctionParser.php index a9b007d..3b0dd91 100644 --- a/src/Analyzer/Parser/FunctionParser.php +++ b/src/Analyzer/Parser/FunctionParser.php @@ -2,7 +2,7 @@ namespace FatCode\OpenApi\Analyzer\Parser; -use FatCode\OpenApi\File\PhpFile; +use FatCode\OpenApi\Analyzer\PhpStream; use function is_array; @@ -10,24 +10,24 @@ use const T_NAMESPACE; use const T_STRING; -class FunctionParser implements PhpFileParser +class FunctionParser implements StreamAnalyzer { use NamespaceParser; private $currentNamespace = ''; - public function parse(PhpFile $file) : array + public function analyze(PhpStream $stream) : array { $functions = []; - foreach ($file as $index => $token) { + foreach ($stream as $index => $token) { if (is_array($token) && $token[0] === T_NAMESPACE) { - $this->currentNamespace = $this->parseNamespace($file); + $this->currentNamespace = $this->parseNamespace($stream); } if (is_array($token) && $token[0] === T_CLASS) { - $file->seekStartOfScope(); - $file->skipScope(); + $stream->seekStartOfScope(); + $stream->skipScope(); continue; } @@ -35,13 +35,13 @@ public function parse(PhpFile $file) : array continue; } - $functions[] = $this->currentNamespace . '\\' . $this->parseFunction($file); + $functions[] = $this->currentNamespace . '\\' . $this->parseFunction($stream); } return $functions; } - private function parseFunction(PhpFile $file) : string + private function parseFunction(PhpStream $file) : string { $file->seekToken(T_STRING); $className = $file->current()[1]; diff --git a/src/Analyzer/Parser/NamespaceParser.php b/src/Analyzer/Parser/NamespaceParser.php index 68b9350..aac78d1 100644 --- a/src/Analyzer/Parser/NamespaceParser.php +++ b/src/Analyzer/Parser/NamespaceParser.php @@ -3,18 +3,18 @@ namespace FatCode\OpenApi\Analyzer\Parser; use FatCode\OpenApi\Exception\ProjectAnalyzerException; -use FatCode\OpenApi\File\PhpFile; +use FatCode\OpenApi\Analyzer\PhpStream; use function is_array; trait NamespaceParser { - protected function parseNamespace(PhpFile $file) : string + protected function parseNamespace(PhpStream $stream) : string { $namespace = ''; - while ($file->valid()) { - $file->next(); - $token = $file->current(); + while ($stream->valid()) { + $stream->next(); + $token = $stream->current(); if (is_array($token) && ($token[0] === T_STRING || $token[0] === T_NS_SEPARATOR)) { $namespace .= $token[1]; diff --git a/src/Analyzer/Parser/PhpFileParser.php b/src/Analyzer/Parser/PhpFileParser.php deleted file mode 100644 index 07e162f..0000000 --- a/src/Analyzer/Parser/PhpFileParser.php +++ /dev/null @@ -1,10 +0,0 @@ -file = $file; - $this->classes = $classes; - $this->functions = $functions; - } - - public function getFile(): PhpFile - { - return $this->file; - } - - public function getClasses() : array - { - return $this->classes; - } - - public function getFunctions() : array - { - return $this->functions; - } -} diff --git a/src/File/PhpFile.php b/src/Analyzer/PhpStream.php similarity index 78% rename from src/File/PhpFile.php rename to src/Analyzer/PhpStream.php index 575ab94..8aa5895 100644 --- a/src/File/PhpFile.php +++ b/src/Analyzer/PhpStream.php @@ -1,6 +1,6 @@ validateFile($name); - $this->name = $name; + $this->stream = $stream; + $this->tokens = token_get_all($stream); + $this->length = count($this->tokens); + } + + public static function fromFile(string $fileName) : PhpStream + { + self::validateFile($fileName); + return new self(file_get_contents($fileName)); } public function current() @@ -146,21 +153,19 @@ public function countTokens() : int public function __toString() : string { - return $this->name; + return $this->stream; } - private function validateFile(string $name) : void + private static function validateFile(string $fileName) : void { - if (!is_file($name) || !is_readable($name)) { - throw FileException::notReadable($name); + if (!is_file($fileName) || !is_readable($fileName)) { + throw FileException::notReadable($fileName); } try { - require_once $name; - $this->tokens = token_get_all(file_get_contents($name)); - $this->length = count($this->tokens); + require_once $fileName; } catch (Throwable $throwable) { - throw FileException::invalidFile($name); + throw FileException::invalidFile($fileName); } } } diff --git a/src/Analyzer/Project.php b/src/Analyzer/Project.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectAnalyzer.php index b07c279..6abfe06 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectAnalyzer.php @@ -2,10 +2,11 @@ namespace FatCode\OpenApi\Analyzer; +use function array_merge; use FatCode\OpenApi\Analyzer\Parser\ClassParser; use FatCode\OpenApi\Analyzer\Parser\FunctionParser; +use FatCode\OpenApi\Analyzer\Parser\StreamAnalyzer; use FatCode\OpenApi\Exception\ProjectAnalyzerException; -use FatCode\OpenApi\File\PhpFile; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; use RegexIterator; @@ -17,29 +18,45 @@ */ class ProjectAnalyzer { + /** + * @var string + */ private $directory; - private $fileAnalyzer; + /** + * @var StreamAnalyzer + */ + private $analyzers; - public function __construct(string $directory, FileAnalyzer $fileAnalyzer = null) - { + public function __construct( + string $directory + ) { if (!is_dir($directory)) { throw ProjectAnalyzerException::forInvalidDirectory($directory); } $this->directory = new RecursiveDirectoryIterator($directory); - $this->fileAnalyzer = $fileAnalyzer ?? new FileAnalyzer( - new ClassParser(), - new FunctionParser() - ); + $this->analyzers = [ + 'classes' => new ClassParser(), + 'functions' => new FunctionParser(), + ]; } - public function analyze() : void + public function analyze() : array { $allFiles = new RecursiveIteratorIterator($this->directory); $phpFiles = new RegexIterator($allFiles, '/.*\.php$/i'); + $result = []; /** @var SplFileInfo $file */ foreach ($phpFiles as $file) { - $this->fileAnalyzer->analyze(new PhpFile($file->getRealPath())); + /** @var StreamAnalyzer $analyzer */ + foreach ($this->analyzers as $type => $analyzer) { + $result[$type] = array_merge( + $result[$type] ?? [], + $analyzer->analyze(PhpStream::fromFile($file->getRealPath())) + ); + } } + + return $result; } } diff --git a/tests/Analyzer/FileAnalyzerTests.php b/tests/Analyzer/FileAnalyzerTests.php deleted file mode 100644 index 9df5a90..0000000 --- a/tests/Analyzer/FileAnalyzerTests.php +++ /dev/null @@ -1,21 +0,0 @@ -analyze(new PhpFile(EXAMPLES_DIR . '/hello_world/hello_world.php')); - - self::assertSame(['App\HelloWorld\Application', 'App\HelloWorld\Greeter'], $fileInfo->getClasses()); - self::assertSame(['App\HelloWorld\sayHello'], $fileInfo->getFunctions()); - } -} diff --git a/tests/Analyzer/Parser/ClassParserTest.php b/tests/Analyzer/Parser/ClassParserTest.php index 680fb95..f9e961d 100644 --- a/tests/Analyzer/Parser/ClassParserTest.php +++ b/tests/Analyzer/Parser/ClassParserTest.php @@ -3,7 +3,7 @@ namespace FatCode\Tests\OpenApi\Analyzer\Parser; use FatCode\OpenApi\Analyzer\Parser\ClassParser; -use FatCode\OpenApi\File\PhpFile; +use FatCode\OpenApi\Analyzer\PhpStream; use PHPUnit\Framework\TestCase; final class ClassParserTest extends TestCase @@ -16,7 +16,7 @@ public function testCanInstantiate() : void public function testParse() : void { $parser = new ClassParser(); - $info = $parser->parse(new PhpFile(__FILE__)); + $info = $parser->analyze(PhpStream::fromFile(__FILE__)); self::assertCount(1, $info); self::assertSame(ClassParserTest::class, $info[0]); diff --git a/tests/Analyzer/Parser/FunctionParserTest.php b/tests/Analyzer/Parser/FunctionParserTest.php index d6832ab..a4d52b4 100644 --- a/tests/Analyzer/Parser/FunctionParserTest.php +++ b/tests/Analyzer/Parser/FunctionParserTest.php @@ -3,7 +3,7 @@ namespace FatCode\Tests\OpenApi\Analyzer\Parser; use FatCode\OpenApi\Analyzer\Parser\FunctionParser; -use FatCode\OpenApi\File\PhpFile; +use FatCode\OpenApi\Analyzer\PhpStream; use PHPUnit\Framework\TestCase; final class FunctionParserTest extends TestCase @@ -16,7 +16,7 @@ public function testCanInstantiate() : void public function testParse() : void { $parser = new FunctionParser(); - $info = $parser->parse(new PhpFile(__FILE__)); + $info = $parser->analyze(PhpStream::fromFile(__FILE__)); self::assertCount(2, $info); self::assertSame(__NAMESPACE__ . '\test_a', $info[0]); diff --git a/tests/Analyzer/ProjectAnalyzerTests.php b/tests/Analyzer/ProjectAnalyzerTests.php index ac879a7..415fbd7 100644 --- a/tests/Analyzer/ProjectAnalyzerTests.php +++ b/tests/Analyzer/ProjectAnalyzerTests.php @@ -10,6 +10,8 @@ class ProjectAnalyzerTests extends TestCase public function testAnalyze() : void { $projectAnalyzer = new ProjectAnalyzer(__DIR__ . '/../../examples/hello_world'); - $projectAnalyzer->analyze(); + $analyze = $projectAnalyzer->analyze(); + + self::assertCount(1, $analyze); } } From 1f7c4c1545da8c20c62b706df1e71bdef92e9d21 Mon Sep 17 00:00:00 2001 From: Dawid Date: Sun, 12 May 2019 09:48:07 +0200 Subject: [PATCH 15/16] Project analyzer - symbols support --- src/Analyzer/AnnotationCollector.php | 2 +- src/Analyzer/Parser/ClassParser.php | 13 +++++++-- src/Analyzer/Parser/FunctionParser.php | 13 +++++++-- src/Analyzer/Parser/StreamAnalyzer.php | 4 +++ src/Analyzer/Parser/Symbol.php | 30 ++++++++++++++++++++ src/Analyzer/Parser/SymbolType.php | 26 +++++++++++++++++ src/Analyzer/PhpStream.php | 11 +++++-- src/Analyzer/Project.php | 8 ++++++ src/Analyzer/ProjectAnalyzer.php | 6 ++-- tests/Analyzer/Parser/ClassParserTest.php | 3 +- tests/Analyzer/Parser/FunctionParserTest.php | 4 +-- tests/Analyzer/ProjectAnalyzerTests.php | 2 +- 12 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 src/Analyzer/Parser/Symbol.php create mode 100644 src/Analyzer/Parser/SymbolType.php diff --git a/src/Analyzer/AnnotationCollector.php b/src/Analyzer/AnnotationCollector.php index 534fed3..d1e4e96 100644 --- a/src/Analyzer/AnnotationCollector.php +++ b/src/Analyzer/AnnotationCollector.php @@ -12,7 +12,7 @@ public function __construct() { } - public function collect(PhpStreamMeta ...$files) : void + public function collect(Project $project) : void { } diff --git a/src/Analyzer/Parser/ClassParser.php b/src/Analyzer/Parser/ClassParser.php index eaa0d18..5aee7d2 100644 --- a/src/Analyzer/Parser/ClassParser.php +++ b/src/Analyzer/Parser/ClassParser.php @@ -16,9 +16,13 @@ class ClassParser implements StreamAnalyzer private $currentNamespace = ''; + /** + * @param PhpStream $stream + * @return Symbol[] + */ public function analyze(PhpStream $stream) : array { - $classes = []; + $results = []; foreach ($stream as $index => $token) { if (is_array($token) && $token[0] === T_NAMESPACE) { @@ -29,10 +33,13 @@ public function analyze(PhpStream $stream) : array continue; } - $classes[] = $this->currentNamespace . '\\' . $this->parseClass($stream); + $results[] = new Symbol( + $this->currentNamespace . '\\' . $this->parseClass($stream), + SymbolType::TYPE_CLASS() + ); } - return $classes; + return $results; } private function parseClass(PhpStream $file) : string diff --git a/src/Analyzer/Parser/FunctionParser.php b/src/Analyzer/Parser/FunctionParser.php index 3b0dd91..464f5b0 100644 --- a/src/Analyzer/Parser/FunctionParser.php +++ b/src/Analyzer/Parser/FunctionParser.php @@ -16,9 +16,13 @@ class FunctionParser implements StreamAnalyzer private $currentNamespace = ''; + /** + * @param PhpStream $stream + * @return Symbol[] + */ public function analyze(PhpStream $stream) : array { - $functions = []; + $results = []; foreach ($stream as $index => $token) { if (is_array($token) && $token[0] === T_NAMESPACE) { @@ -35,10 +39,13 @@ public function analyze(PhpStream $stream) : array continue; } - $functions[] = $this->currentNamespace . '\\' . $this->parseFunction($stream); + $results[] = new Symbol( + $this->currentNamespace . '\\' . $this->parseFunction($stream), + SymbolType::TYPE_FUNCTION() + ); } - return $functions; + return $results; } private function parseFunction(PhpStream $file) : string diff --git a/src/Analyzer/Parser/StreamAnalyzer.php b/src/Analyzer/Parser/StreamAnalyzer.php index 4a2f175..81dce78 100644 --- a/src/Analyzer/Parser/StreamAnalyzer.php +++ b/src/Analyzer/Parser/StreamAnalyzer.php @@ -6,5 +6,9 @@ interface StreamAnalyzer { + /** + * @param PhpStream $stream + * @return Symbol[] + */ public function analyze(PhpStream $stream) : array; } diff --git a/src/Analyzer/Parser/Symbol.php b/src/Analyzer/Parser/Symbol.php new file mode 100644 index 0000000..dc51dac --- /dev/null +++ b/src/Analyzer/Parser/Symbol.php @@ -0,0 +1,30 @@ +name = $name; + $this->type = $type; + } + + public function getName() : string + { + return $this->name; + } + + public function getType() : SymbolType + { + return $this->type; + } + + public function __toString() : string + { + return "{$this->type}:{$this->name}"; + } +} diff --git a/src/Analyzer/Parser/SymbolType.php b/src/Analyzer/Parser/SymbolType.php new file mode 100644 index 0000000..82e5751 --- /dev/null +++ b/src/Analyzer/Parser/SymbolType.php @@ -0,0 +1,26 @@ +getValue(); + } +} diff --git a/src/Analyzer/PhpStream.php b/src/Analyzer/PhpStream.php index 8aa5895..c4069e7 100644 --- a/src/Analyzer/PhpStream.php +++ b/src/Analyzer/PhpStream.php @@ -18,18 +18,25 @@ class PhpStream implements Iterator private $tokens; private $length; private $stream; + private $context; - private function __construct(string $stream) + private function __construct(string $stream, string $context = '') { $this->stream = $stream; $this->tokens = token_get_all($stream); $this->length = count($this->tokens); + $this->context = $context; } public static function fromFile(string $fileName) : PhpStream { self::validateFile($fileName); - return new self(file_get_contents($fileName)); + return new self(file_get_contents($fileName), $fileName); + } + + public function getContext() : string + { + return $this->context; } public function current() diff --git a/src/Analyzer/Project.php b/src/Analyzer/Project.php index e69de29..c1157fd 100644 --- a/src/Analyzer/Project.php +++ b/src/Analyzer/Project.php @@ -0,0 +1,8 @@ +analyzers as $type => $analyzer) { - $result[$type] = array_merge( - $result[$type] ?? [], + foreach ($this->analyzers as $analyzer) { + $result = array_merge( + $result ?? [], $analyzer->analyze(PhpStream::fromFile($file->getRealPath())) ); } diff --git a/tests/Analyzer/Parser/ClassParserTest.php b/tests/Analyzer/Parser/ClassParserTest.php index f9e961d..779a96b 100644 --- a/tests/Analyzer/Parser/ClassParserTest.php +++ b/tests/Analyzer/Parser/ClassParserTest.php @@ -3,6 +3,7 @@ namespace FatCode\Tests\OpenApi\Analyzer\Parser; use FatCode\OpenApi\Analyzer\Parser\ClassParser; +use FatCode\OpenApi\Analyzer\Parser\Symbol; use FatCode\OpenApi\Analyzer\PhpStream; use PHPUnit\Framework\TestCase; @@ -19,6 +20,6 @@ public function testParse() : void $info = $parser->analyze(PhpStream::fromFile(__FILE__)); self::assertCount(1, $info); - self::assertSame(ClassParserTest::class, $info[0]); + self::assertSame(__CLASS__, $info[0]->getName()); } } diff --git a/tests/Analyzer/Parser/FunctionParserTest.php b/tests/Analyzer/Parser/FunctionParserTest.php index a4d52b4..490acf6 100644 --- a/tests/Analyzer/Parser/FunctionParserTest.php +++ b/tests/Analyzer/Parser/FunctionParserTest.php @@ -19,8 +19,8 @@ public function testParse() : void $info = $parser->analyze(PhpStream::fromFile(__FILE__)); self::assertCount(2, $info); - self::assertSame(__NAMESPACE__ . '\test_a', $info[0]); - self::assertSame(__NAMESPACE__ . '\test_b', $info[1]); + self::assertSame(__NAMESPACE__ . '\test_a', $info[0]->getName()); + self::assertSame(__NAMESPACE__ . '\test_b', $info[1]->getName()); } } diff --git a/tests/Analyzer/ProjectAnalyzerTests.php b/tests/Analyzer/ProjectAnalyzerTests.php index 415fbd7..2ec386e 100644 --- a/tests/Analyzer/ProjectAnalyzerTests.php +++ b/tests/Analyzer/ProjectAnalyzerTests.php @@ -12,6 +12,6 @@ public function testAnalyze() : void $projectAnalyzer = new ProjectAnalyzer(__DIR__ . '/../../examples/hello_world'); $analyze = $projectAnalyzer->analyze(); - self::assertCount(1, $analyze); + self::assertCount(3, $analyze); } } From b4fbd169a9882fa42a926c2422c59a124180ada0 Mon Sep 17 00:00:00 2001 From: Dawid Kraczkowski Date: Sun, 12 May 2019 14:32:43 +0200 Subject: [PATCH 16/16] Project analyzer renamed to project factory --- ...nCollector.php => AnnotationProcessor.php} | 4 +- src/Analyzer/Project.php | 46 ++++++++++++++++++- ...ProjectAnalyzer.php => ProjectFactory.php} | 41 ++++++----------- tests/Analyzer/Parser/ClassParserTest.php | 1 - tests/Analyzer/ProjectAnalyzerTests.php | 10 ++-- 5 files changed, 66 insertions(+), 36 deletions(-) rename src/Analyzer/{AnnotationCollector.php => AnnotationProcessor.php} (71%) rename src/Analyzer/{ProjectAnalyzer.php => ProjectFactory.php} (53%) diff --git a/src/Analyzer/AnnotationCollector.php b/src/Analyzer/AnnotationProcessor.php similarity index 71% rename from src/Analyzer/AnnotationCollector.php rename to src/Analyzer/AnnotationProcessor.php index d1e4e96..2ec5b0b 100644 --- a/src/Analyzer/AnnotationCollector.php +++ b/src/Analyzer/AnnotationProcessor.php @@ -6,13 +6,13 @@ * Class ProjectAnalyser * @package FatCode\OpenApi */ -class AnnotationCollector +class AnnotationProcessor { public function __construct() { } - public function collect(Project $project) : void + public function process(Project $project) : void { } diff --git a/src/Analyzer/Project.php b/src/Analyzer/Project.php index c1157fd..7f516dd 100644 --- a/src/Analyzer/Project.php +++ b/src/Analyzer/Project.php @@ -2,7 +2,51 @@ namespace FatCode\OpenApi\Analyzer; -class Project +use ArrayIterator; +use FatCode\OpenApi\Analyzer\Parser\Symbol; +use FatCode\OpenApi\Analyzer\Parser\SymbolType; +use Iterator; +use IteratorAggregate; + +class Project implements IteratorAggregate { + /** + * @var Symbol[] + */ + private $symbols; + + public function addSymbol(Symbol ...$symbols) : void + { + foreach ($symbols as $symbol) { + $this->symbols[] = $symbol; + } + } + + public function getSymbols() : array + { + return $this->symbols; + } + + public function getIterator() : Iterator + { + return new ArrayIterator($this->symbols); + } + + public function listDeclaredFunctions() : iterable + { + foreach ($this->symbols as $symbol) { + if ($symbol->getType() === SymbolType::TYPE_FUNCTION()) { + yield $symbol; + } + } + } + public function listDeclaredClasses() : iterable + { + foreach ($this->symbols as $symbol) { + if ($symbol->getType() === SymbolType::TYPE_CLASS()) { + yield $symbol; + } + } + } } diff --git a/src/Analyzer/ProjectAnalyzer.php b/src/Analyzer/ProjectFactory.php similarity index 53% rename from src/Analyzer/ProjectAnalyzer.php rename to src/Analyzer/ProjectFactory.php index 011ff91..d281bd9 100644 --- a/src/Analyzer/ProjectAnalyzer.php +++ b/src/Analyzer/ProjectFactory.php @@ -2,7 +2,6 @@ namespace FatCode\OpenApi\Analyzer; -use function array_merge; use FatCode\OpenApi\Analyzer\Parser\ClassParser; use FatCode\OpenApi\Analyzer\Parser\FunctionParser; use FatCode\OpenApi\Analyzer\Parser\StreamAnalyzer; @@ -12,51 +11,37 @@ use RegexIterator; use SplFileInfo; -/** - * Class ProjectAnalyser - * @package FatCode\OpenApi - */ -class ProjectAnalyzer +class ProjectFactory { /** - * @var string - */ - private $directory; - /** - * @var StreamAnalyzer + * @var StreamAnalyzer[] */ private $analyzers; - public function __construct( - string $directory - ) { - if (!is_dir($directory)) { - throw ProjectAnalyzerException::forInvalidDirectory($directory); - } - - $this->directory = new RecursiveDirectoryIterator($directory); + public function __construct() + { $this->analyzers = [ 'classes' => new ClassParser(), 'functions' => new FunctionParser(), ]; } - public function analyze() : array + public function create(string $dirname) : Project { - $allFiles = new RecursiveIteratorIterator($this->directory); + if (!is_dir($dirname)) { + throw ProjectAnalyzerException::forInvalidDirectory($dirname); + } + $directory = new RecursiveDirectoryIterator($dirname); + $allFiles = new RecursiveIteratorIterator($directory); $phpFiles = new RegexIterator($allFiles, '/.*\.php$/i'); - $result = []; + $project = new Project(); /** @var SplFileInfo $file */ foreach ($phpFiles as $file) { - /** @var StreamAnalyzer $analyzer */ foreach ($this->analyzers as $analyzer) { - $result = array_merge( - $result ?? [], - $analyzer->analyze(PhpStream::fromFile($file->getRealPath())) - ); + $project->addSymbol(...$analyzer->analyze(PhpStream::fromFile($file->getRealPath()))); } } - return $result; + return $project; } } diff --git a/tests/Analyzer/Parser/ClassParserTest.php b/tests/Analyzer/Parser/ClassParserTest.php index 779a96b..7b8a717 100644 --- a/tests/Analyzer/Parser/ClassParserTest.php +++ b/tests/Analyzer/Parser/ClassParserTest.php @@ -3,7 +3,6 @@ namespace FatCode\Tests\OpenApi\Analyzer\Parser; use FatCode\OpenApi\Analyzer\Parser\ClassParser; -use FatCode\OpenApi\Analyzer\Parser\Symbol; use FatCode\OpenApi\Analyzer\PhpStream; use PHPUnit\Framework\TestCase; diff --git a/tests/Analyzer/ProjectAnalyzerTests.php b/tests/Analyzer/ProjectAnalyzerTests.php index 2ec386e..270070d 100644 --- a/tests/Analyzer/ProjectAnalyzerTests.php +++ b/tests/Analyzer/ProjectAnalyzerTests.php @@ -2,16 +2,18 @@ namespace FatCode\Tests\OpenApi\Analyzer; -use FatCode\OpenApi\Analyzer\ProjectAnalyzer; +use FatCode\OpenApi\Analyzer\Project; +use FatCode\OpenApi\Analyzer\ProjectFactory; use PHPUnit\Framework\TestCase; class ProjectAnalyzerTests extends TestCase { public function testAnalyze() : void { - $projectAnalyzer = new ProjectAnalyzer(__DIR__ . '/../../examples/hello_world'); - $analyze = $projectAnalyzer->analyze(); + $projectAnalyzer = new ProjectFactory(__DIR__ . '/../../examples/hello_world'); + $project = $projectAnalyzer->create(); - self::assertCount(3, $analyze); + self::assertInstanceOf(Project::class, $project); + self::assertCount(3, $project->getSymbols()); } }