├── LICENSE
├── README.md
├── apigen.neon
├── bin
└── apigen
├── composer.json
├── composer.lock
└── src
├── Analyzer.php
├── Analyzer
├── AnalyzeResult.php
├── AnalyzeState.php
├── AnalyzeTask.php
├── AnalyzeTaskHandler.php
├── AnalyzeTaskHandlerFactory.php
├── BodySkippingLexer.php
├── Filter.php
├── IdentifierKind.php
├── NameContextFrame.php
└── NodeVisitors
│ └── PhpDocResolver.php
├── ApiGen.php
├── Bootstrap.php
├── Helpers.php
├── Index
├── FileIndex.php
├── Index.php
└── NamespaceIndex.php
├── Indexer.php
├── Info
├── AliasInfo.php
├── AliasReferenceInfo.php
├── ClassInfo.php
├── ClassLikeInfo.php
├── ClassLikeReferenceInfo.php
├── ConstantInfo.php
├── ConstantReferenceInfo.php
├── ElementInfo.php
├── EnumCaseInfo.php
├── EnumInfo.php
├── ErrorInfo.php
├── ErrorKind.php
├── Expr
│ ├── ArgExprInfo.php
│ ├── ArrayExprInfo.php
│ ├── ArrayItemExprInfo.php
│ ├── BinaryOpExprInfo.php
│ ├── BooleanExprInfo.php
│ ├── ClassConstantFetchExprInfo.php
│ ├── ConstantFetchExprInfo.php
│ ├── DimFetchExprInfo.php
│ ├── FloatExprInfo.php
│ ├── IntegerExprInfo.php
│ ├── NewExprInfo.php
│ ├── NullExprInfo.php
│ ├── NullSafePropertyFetchExprInfo.php
│ ├── PropertyFetchExprInfo.php
│ ├── StringExprInfo.php
│ ├── TernaryExprInfo.php
│ └── UnaryOpExprInfo.php
├── ExprInfo.php
├── FunctionInfo.php
├── FunctionReferenceInfo.php
├── GenericParameterInfo.php
├── GenericParameterVariance.php
├── InterfaceInfo.php
├── MemberInfo.php
├── MemberReferenceInfo.php
├── MethodInfo.php
├── MethodReferenceInfo.php
├── MissingInfo.php
├── NameInfo.php
├── ParameterInfo.php
├── PropertyInfo.php
├── PropertyReferenceInfo.php
├── TraitInfo.php
└── Traits
│ ├── HasGenericParameters.php
│ ├── HasLineLocation.php
│ ├── HasTags.php
│ └── HasVisibility.php
├── Locator.php
├── Renderer.php
├── Renderer
├── Filter.php
├── Latte
│ ├── LatteCascadingLoader.php
│ ├── LatteEngineFactory.php
│ ├── LatteExtension.php
│ ├── LatteFunctions.php
│ ├── LattePreNode.php
│ ├── LatteRenderTask.php
│ ├── LatteRenderTaskContext.php
│ ├── LatteRenderTaskHandler.php
│ ├── LatteRenderTaskHandlerFactory.php
│ ├── LatteRenderTaskType.php
│ ├── LatteRenderer.php
│ └── Template
│ │ ├── ClassLikeTemplate.php
│ │ ├── ConfigParameters.php
│ │ ├── FunctionTemplate.php
│ │ ├── IndexTemplate.php
│ │ ├── LayoutParameters.php
│ │ ├── NamespaceTemplate.php
│ │ ├── SitemapTemplate.php
│ │ ├── SourceTemplate.php
│ │ ├── TreeTemplate.php
│ │ ├── assets
│ │ ├── main.css
│ │ └── main.js
│ │ ├── blocks
│ │ ├── @index.latte
│ │ ├── alias.latte
│ │ ├── aliasLink.latte
│ │ ├── aliasSummary.latte
│ │ ├── autoBreakingLine.latte
│ │ ├── classLikeDescription.latte
│ │ ├── classLikeKind.latte
│ │ ├── classLikeLink.latte
│ │ ├── classLikeLinks.latte
│ │ ├── classLikeReference.latte
│ │ ├── classLikeSignature.latte
│ │ ├── classLikeSignatureTable.latte
│ │ ├── classTree.latte
│ │ ├── constant.latte
│ │ ├── constantInherited.latte
│ │ ├── constantInheritedSummary.latte
│ │ ├── constantSummary.latte
│ │ ├── description.latte
│ │ ├── elementSummary.latte
│ │ ├── elementSummaryGroup.latte
│ │ ├── enumCase.latte
│ │ ├── enumCaseSummary.latte
│ │ ├── expr.latte
│ │ ├── functionDescription.latte
│ │ ├── functionLinks.latte
│ │ ├── genericParameters.latte
│ │ ├── head.latte
│ │ ├── layout.latte
│ │ ├── member.latte
│ │ ├── memberDescription.latte
│ │ ├── memberLink.latte
│ │ ├── memberSourceLink.latte
│ │ ├── memberVisibility.latte
│ │ ├── menu.latte
│ │ ├── menuElements.latte
│ │ ├── menuGroup.latte
│ │ ├── method.latte
│ │ ├── methodInherited.latte
│ │ ├── methodInheritedSummary.latte
│ │ ├── methodRelation.latte
│ │ ├── methodSignature.latte
│ │ ├── methodSummary.latte
│ │ ├── methodUsed.latte
│ │ ├── methodUsedSummary.latte
│ │ ├── namespaceLinks.latte
│ │ ├── navbar.latte
│ │ ├── parameter.latte
│ │ ├── property.latte
│ │ ├── propertyInherited.latte
│ │ ├── propertyInheritedSummary.latte
│ │ ├── propertySummary.latte
│ │ ├── propertyUsed.latte
│ │ ├── propertyUsedSummary.latte
│ │ ├── search.latte
│ │ ├── source.latte
│ │ └── type.latte
│ │ └── pages
│ │ ├── classLike.latte
│ │ ├── function.latte
│ │ ├── index.latte
│ │ ├── namespace.latte
│ │ ├── sitemap.latte
│ │ ├── source.latte
│ │ └── tree.latte
├── SourceHighlighter.php
└── UrlGenerator.php
├── Scheduler.php
├── Scheduler
├── ExecScheduler.php
├── ForkScheduler.php
├── SchedulerFactory.php
├── SimpleScheduler.php
├── WorkerScheduler.php
└── worker.php
└── Task
├── Task.php
├── TaskHandler.php
└── TaskHandlerFactory.php
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2010 David Grudl <https://davidgrudl.com>
4 | & ApiGen Contributors <https://github.com/ApiGen/ApiGen/graphs/contributors>
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in all
14 | copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 | SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Smart and Readable Documentation for PHP projects
2 |
3 | ApiGen is easy to use and modern API doc generator **supporting all PHP 8.3 features**.
4 |
5 |
6 | ## Features
7 |
8 | - phpDoc
9 | - [all types supported by PHPStan](https://phpstan.org/writing-php-code/phpdoc-types)
10 | - [generic class declarations](https://phpstan.org/blog/generics-in-php-using-phpdocs)
11 | - [local type aliases](https://phpstan.org/writing-php-code/phpdoc-types#local-type-aliases)
12 | - PHP 8.3
13 | - [typed class constants](https://wiki.php.net/rfc/typed_class_constants)
14 | - PHP 8.2
15 | - [constants in traits](https://wiki.php.net/rfc/constants_in_traits)
16 | - [fetch enum properties in const expressions](https://wiki.php.net/rfc/fetch_property_in_const_expressions)
17 | - [disjunctive normal form types](https://wiki.php.net/rfc/dnf_types)
18 | - [readonly classes](https://wiki.php.net/rfc/readonly_classes)
19 | - [true](https://wiki.php.net/rfc/true-type), [false and null types](https://wiki.php.net/rfc/null-false-standalone-types)
20 | - PHP 8.1
21 | - [enums](https://wiki.php.net/rfc/enumerations)
22 | - [pure intersection types](https://wiki.php.net/rfc/pure-intersection-types)
23 | - [never type](https://wiki.php.net/rfc/noreturn_type)
24 | - [final class constants](https://wiki.php.net/rfc/final_class_const)
25 | - [new in initializers](https://wiki.php.net/rfc/new_in_initializers)
26 | - [readonly properties](https://wiki.php.net/rfc/readonly_properties_v2)
27 | - PHP 8.0
28 | - [constructor property promotion](https://wiki.php.net/rfc/constructor_promotion)
29 | - [union types](https://wiki.php.net/rfc/union_types_v2)
30 | - [mixed type](https://wiki.php.net/rfc/mixed_type_v2)
31 | - [static return type](https://wiki.php.net/rfc/static_return_type)
32 | - PHP 7.4
33 | - [typed properties](https://wiki.php.net/rfc/typed_properties_v2)
34 | - PHP 7.2
35 | - [object type](https://wiki.php.net/rfc/object-typehint)
36 | - PHP 7.1
37 | - [nullable types](https://wiki.php.net/rfc/nullable_types)
38 | - [iterable type](https://wiki.php.net/rfc/iterable)
39 | - [void type](https://wiki.php.net/rfc/void_return_type)
40 | - [class constant visibility](https://wiki.php.net/rfc/class_const_visibility)
41 | - PHP 7.0
42 | - [scalar types](https://wiki.php.net/rfc/scalar_type_hints_v5)
43 | - [return types](https://wiki.php.net/rfc/return_types)
44 | - PHP 5.6
45 | - [constant scalar expressions](https://wiki.php.net/rfc/const_scalar_exprs)
46 | - [variadic functions](https://wiki.php.net/rfc/variadics)
47 | - PHP 5.4
48 | - [traits](https://wiki.php.net/rfc/horizontalreuse)
49 | - [callable type](https://wiki.php.net/rfc/callable)
50 | - [binary integer notation](https://wiki.php.net/rfc/binnotation4ints)
51 |
52 |
53 | ## Built on Shoulders of Giants
54 |
55 | - [nikic/php-parser](https://github.com/nikic/PHP-Parser)
56 | - [phpstan/phpdoc-parser](https://github.com/phpstan/phpdoc-parser)
57 | - [latte/latte](https://github.com/nette/latte)
58 | - [league/commonmark](https://github.com/thephpleague/commonmark)
59 |
60 |
61 | ## Install
62 |
63 | ### With Docker
64 |
65 | ApiGen is available as [apigen/apigen](https://hub.docker.com/r/apigen/apigen) Docker image which you can directly use.
66 |
67 | ```bash
68 | docker run --rm --interactive --tty --volume "$PWD:$PWD" --workdir "$PWD" \
69 | apigen/apigen:edge \
70 | src --output docs
71 | ```
72 |
73 |
74 | ### With Phar
75 |
76 | This will install ApiGen phar binary to `tools/apigen`.
77 |
78 | ```bash
79 | mkdir -p tools
80 | curl -L https://github.com/ApiGen/ApiGen/releases/latest/download/apigen.phar -o tools/apigen
81 | chmod +x tools/apigen
82 | tools/apigen src --output docs
83 | ```
84 |
85 |
86 | ### With Composer
87 |
88 | This will install ApiGen to `tools/apigen` directory with executable entry point available in `tools/apigen/bin/apigen`.
89 |
90 | ```bash
91 | composer create-project --no-dev apigen/apigen:^7.0@alpha tools/apigen
92 | tools/apigen/bin/apigen src --output docs
93 | ```
94 |
95 |
96 | ## Usage
97 |
98 | Generate API docs by passing source directories and destination option:
99 |
100 | ```bash
101 | apigen src --output docs
102 | ```
103 |
104 |
105 | ## Configuration
106 |
107 | ApiGen can be configured with `apigen.neon` configuration file.
108 |
109 | ```neon
110 | parameters:
111 | # string[], passed as arguments in CLI, e.g. ['src']
112 | paths: []
113 |
114 | # string[], --include in CLI, included files mask, e.g. ['*.php']
115 | include: ['*.php']
116 |
117 | # string[], --exclude in CLI, excluded files mask, e.g. ['tests/**']
118 | exclude: []
119 |
120 | # bool, should protected members be excluded?
121 | excludeProtected: false
122 |
123 | # bool, should private members be excluded?
124 | excludePrivate: true
125 |
126 | # string[], list of tags used for excluding class-likes and members
127 | excludeTagged: ['internal']
128 |
129 | # string, --output in CLI
130 | outputDir: '%workingDir%/api'
131 |
132 | # string | null, --theme in CLI
133 | themeDir: null
134 |
135 | # string, --title in CLI
136 | title: 'API Documentation'
137 |
138 | # string, --base-url in CLI
139 | baseUrl: ''
140 |
141 | # int, --workers in CLI, number of processes that will be forked for parallel rendering
142 | workerCount: 8
143 |
144 | # string, --memory-limit in CLI
145 | memoryLimit: '512M'
146 | ```
147 |
--------------------------------------------------------------------------------
/apigen.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | # string[], passed as arguments in CLI, e.g. ['src']
3 | paths: []
4 |
5 | # string[], --include in CLI, included files mask, e.g. ['*.php']
6 | include: ['*.php']
7 |
8 | # string[], --exclude in CLI, excluded files mask, e.g. ['tests/**']
9 | exclude: []
10 |
11 | # bool, should protected members be excluded?
12 | excludeProtected: false
13 |
14 | # bool, should private members be excluded?
15 | excludePrivate: true
16 |
17 | # string[], list of tags used for excluding class-likes and members
18 | excludeTagged: ['internal']
19 |
20 | # string, --output in CLI
21 | outputDir: '%workingDir%/api'
22 |
23 | # string | null
24 | themeDir: null
25 |
26 | # string, --title in CLI
27 | title: ''
28 |
29 | # string, --base-url in CLI
30 | baseUrl: ''
31 |
32 | # int, --workers in CLI
33 | workerCount: 8
34 |
35 | # string, --memory-limit in CLI
36 | memoryLimit: '512M'
37 |
38 |
39 | services:
40 | # ApiGen
41 | apigen:
42 | factory: ApiGen\ApiGen(paths: %paths%, include: %include%, exclude: %exclude%)
43 |
44 | # Locator
45 | locator:
46 | factory: ApiGen\Locator::create(projectDir: %workingDir%)
47 |
48 | # Scheduler
49 | scheduler.factory:
50 | factory: ApiGen\Scheduler\SchedulerFactory(workerCount: %workerCount%)
51 |
52 | # Analyzer
53 | analyze.phpDocResolver:
54 | factory: ApiGen\Analyzer\NodeVisitors\PhpDocResolver
55 |
56 | analyzer.nodeTraverser:
57 | type: PhpParser\NodeTraverserInterface
58 | factory: PhpParser\NodeTraverser
59 | setup!:
60 | - addVisitor(@PhpParser\NodeVisitor\NameResolver)
61 | - addVisitor(@ApiGen\Analyzer\NodeVisitors\PhpDocResolver)
62 |
63 | analyzer.filter:
64 | factory: ApiGen\Analyzer\Filter(excludeProtected: %excludeProtected%, excludePrivate: %excludePrivate%, excludeTagged: %excludeTagged%)
65 |
66 | analyzer.taskHandlerFactory:
67 | implement: ApiGen\Analyzer\AnalyzeTaskHandlerFactory
68 |
69 | analyzer:
70 | factory: ApiGen\Analyzer
71 |
72 | # Indexer
73 | indexer:
74 | factory: ApiGen\Indexer
75 |
76 | # Renderer
77 | renderer.filter:
78 | factory: ApiGen\Renderer\Filter
79 |
80 | renderer.urlGenerator:
81 | factory: ApiGen\Renderer\UrlGenerator(ApiGen\Helpers::baseDir(%paths%), %baseUrl%)
82 |
83 | renderer.sourceHighlighter:
84 | factory: ApiGen\Renderer\SourceHighlighter
85 |
86 | renderer.latte.extension:
87 | factory: ApiGen\Renderer\Latte\LatteExtension
88 |
89 | renderer.latte.functions:
90 | factory: ApiGen\Renderer\Latte\LatteFunctions
91 |
92 | renderer.latte.engineFactory:
93 | factory: ApiGen\Renderer\Latte\LatteEngineFactory(tempDir: %tempDir%, themeDir: %themeDir%)
94 |
95 | renderer.latte.engine:
96 | factory: @ApiGen\Renderer\Latte\LatteEngineFactory::create()
97 |
98 | renderer.taskHandlerFactory:
99 | implement: ApiGen\Renderer\Latte\LatteRenderTaskHandlerFactory
100 | arguments:
101 | outputDir: %outputDir%
102 |
103 | renderer:
104 | type: ApiGen\Renderer
105 | factory: ApiGen\Renderer\Latte\LatteRenderer(outputDir: %outputDir%, title: %title%, version: %version%)
106 |
107 | # league/commonmark
108 | commonMark:
109 | type: League\CommonMark\ConverterInterface
110 | factory: League\CommonMark\GithubFlavoredMarkdownConverter
111 |
112 | # nikic/php-parser
113 | phpParser.nameResolver:
114 | factory: PhpParser\NodeVisitor\NameResolver
115 |
116 | phpParser.nameContext:
117 | factory: @PhpParser\NodeVisitor\NameResolver::getNameContext()
118 |
119 | phpParser.lexer:
120 | type: PhpParser\Lexer
121 | factory: ApiGen\Analyzer\BodySkippingLexer
122 |
123 | phpParser.phpVersion:
124 | type: PhpParser\PhpVersion
125 | factory: PhpParser\PhpVersion::getHostVersion()
126 |
127 | phpParser:
128 | type: PhpParser\Parser
129 | factory: PhpParser\Parser\Php8
130 |
131 | # phpstan/phpdoc-parser
132 | phpDocParser.typeParser:
133 | factory: PHPStan\PhpDocParser\Parser\TypeParser
134 |
135 | phpDocParser.constExprParser:
136 | factory: PHPStan\PhpDocParser\Parser\ConstExprParser
137 |
138 | phpDocParser.lexer:
139 | factory: PHPStan\PhpDocParser\Lexer\Lexer
140 |
141 | phpDocParser:
142 | factory: PHPStan\PhpDocParser\Parser\PhpDocParser(requireWhitespaceBeforeDescription: true)
143 |
144 | # symfony/console
145 | symfonyConsole.output:
146 | type: Symfony\Component\Console\Style\OutputStyle
147 | imported: true
148 |
--------------------------------------------------------------------------------
/bin/apigen:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | <?php declare(strict_types = 1);
3 |
4 | use ApiGen\Bootstrap;
5 | use Composer\InstalledVersions;
6 | use Symfony\Component\Console\Command\Command;
7 | use Symfony\Component\Console\Input\InputArgument;
8 | use Symfony\Component\Console\Input\InputInterface;
9 | use Symfony\Component\Console\Input\InputOption;
10 | use Symfony\Component\Console\Output\ConsoleOutputInterface;
11 | use Symfony\Component\Console\SingleCommandApplication;
12 | use Symfony\Component\Console\Style\SymfonyStyle;
13 |
14 |
15 | if (is_file(__DIR__ . '/../vendor/autoload.php')) {
16 | require __DIR__ . '/../vendor/autoload.php';
17 |
18 | } elseif (is_file(__DIR__ . '/../../../autoload.php')) {
19 | require __DIR__ . '/../../../autoload.php';
20 |
21 | } else {
22 | fwrite(STDERR, "ERROR: unable to find autoloader\n");
23 | exit(1);
24 | }
25 |
26 | Bootstrap::configureErrorHandling();
27 |
28 | $app = new SingleCommandApplication('ApiGen');
29 | $app->setVersion(InstalledVersions::getPrettyVersion('apigen/apigen'));
30 |
31 | $app->addArgument('path', InputArgument::IS_ARRAY, 'path directory to analyze');
32 | $app->addOption('config', 'c', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'path to config file');
33 | $app->addOption('include', 'i', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'includes files mask, e.g. *.php');
34 | $app->addOption('exclude', 'e', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'excluded files mask, e.g. tests/**');
35 | $app->addOption('working-dir', null, InputOption::VALUE_REQUIRED, 'project directory (containing composer.json), defaults to current working directory');
36 | $app->addOption('temp', null, InputOption::VALUE_REQUIRED, 'temp directory, defaults to sys_get_temp_dir() . \'/apigen\'');
37 | $app->addOption('workers', 'w', InputOption::VALUE_REQUIRED, 'worker count');
38 | $app->addOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'memory limit');
39 | $app->addOption('title', 't', InputOption::VALUE_REQUIRED, 'title');
40 | $app->addOption('base-url', null, InputOption::VALUE_REQUIRED, 'base URL');
41 | $app->addOption('output', 'o', InputOption::VALUE_REQUIRED, 'output directory');
42 | $app->addOption('theme', null, InputOption::VALUE_REQUIRED, 'theme directory');
43 |
44 | $app->setCode(function (InputInterface $input, ConsoleOutputInterface $output): int {
45 | $paths = $input->getArgument('path');
46 | $options = $input->getOptions();
47 |
48 | $optionsMapping = [
49 | 'include' => 'include',
50 | 'exclude' => 'exclude',
51 | 'working-dir' => 'workingDir',
52 | 'temp' => 'tempDir',
53 | 'workers' => 'workerCount',
54 | 'memory-limit' => 'memoryLimit',
55 | 'title' => 'title',
56 | 'base-url' => 'baseUrl',
57 | 'output' => 'outputDir',
58 | 'theme' => 'themeDir',
59 | ];
60 |
61 | $numericOptions = [
62 | 'workers' => true,
63 | ];
64 |
65 | $parameters = [];
66 | foreach ($optionsMapping as $optionKey => $parameterKey) {
67 | $optionValue = $options[$optionKey];
68 | if (is_string($optionValue)) {
69 | $parameters[$parameterKey] = isset($numericOptions[$optionKey]) ? (int) $optionValue : $optionValue;
70 |
71 | } elseif (is_array($optionValue) && count($optionValue) > 0) {
72 | $parameters[$parameterKey] = $optionValue;
73 | }
74 | }
75 |
76 | if (count($paths) > 0) {
77 | $parameters['paths'] = $paths;
78 | }
79 |
80 | $style = new SymfonyStyle($input, $output);
81 | $apiGen = Bootstrap::createApiGen($style, $parameters, $options['config']);
82 |
83 | return $apiGen->generate() ? Command::SUCCESS : Command::FAILURE;
84 | });
85 |
86 | $app->run();
87 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "apigen/apigen",
3 | "description": "PHP source code API generator.",
4 | "license": "MIT",
5 | "authors": [
6 | {"name": "ApiGen Contributors", "homepage": "https://github.com/apigen/apigen/graphs/contributors"},
7 | {"name": "Jaroslav Hanslík", "homepage": "https://github.com/kukulich"},
8 | {"name": "Ondřej Nešpor", "homepage": "https://github.com/andrewsville"},
9 | {"name": "David Grudl", "homepage": "https://davidgrudl.com"}
10 | ],
11 | "bin": ["bin/apigen"],
12 | "require": {
13 | "php": "^8.1",
14 | "ext-ctype": "*",
15 | "ext-json": "*",
16 | "ext-mbstring": "*",
17 | "ext-tokenizer": "*",
18 | "composer-runtime-api": "^2.0",
19 | "jetbrains/phpstorm-stubs": "^2024.2",
20 | "latte/latte": "^3.0",
21 | "league/commonmark": "^2.3",
22 | "nette/di": "^3.1",
23 | "nette/finder": "^3.0",
24 | "nette/schema": "^1.2",
25 | "nette/utils": "^4.0",
26 | "nikic/php-parser": "^5.3",
27 | "phpstan/php-8-stubs": "^0.4.0",
28 | "phpstan/phpdoc-parser": "^1.16",
29 | "symfony/console": "^6.4"
30 | },
31 | "require-dev": {
32 | "nette/neon": "^3.4",
33 | "nette/tester": "^2.4",
34 | "phpstan/phpstan": "^1.9",
35 | "tracy/tracy": "^2.9"
36 | },
37 | "replace": {
38 | "symfony/polyfill-ctype": "*",
39 | "symfony/polyfill-mbstring": "*",
40 | "symfony/polyfill-php80": "*"
41 | },
42 | "autoload": {
43 | "psr-4": {
44 | "ApiGen\\": "src"
45 | }
46 | },
47 | "autoload-dev": {
48 | "psr-4": {
49 | "ApiGenTests\\": "tests"
50 | }
51 | },
52 | "config": {
53 | "platform-check": true,
54 | "sort-packages": true,
55 | "platform": {
56 | "php": "8.1"
57 | }
58 | },
59 | "scripts": {
60 | "check": [
61 | "@check:phpstan",
62 | "@check:tests"
63 | ],
64 | "check:tests": "tester -C tests",
65 | "check:phpstan": "phpstan analyse -vvv"
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Analyzer.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen;
4 |
5 | use ApiGen\Analyzer\AnalyzeResult;
6 | use ApiGen\Analyzer\AnalyzeState;
7 | use ApiGen\Analyzer\AnalyzeTask;
8 | use ApiGen\Analyzer\AnalyzeTaskHandlerFactory;
9 | use ApiGen\Info\ClassLikeInfo;
10 | use ApiGen\Info\ClassLikeReferenceInfo;
11 | use ApiGen\Info\ErrorInfo;
12 | use ApiGen\Info\ErrorKind;
13 | use ApiGen\Info\FunctionInfo;
14 | use ApiGen\Info\MissingInfo;
15 | use ApiGen\Info\NameInfo;
16 | use ApiGen\Scheduler\SchedulerFactory;
17 | use Symfony\Component\Console\Helper\ProgressBar;
18 |
19 | use function count;
20 | use function implode;
21 |
22 |
23 | class Analyzer
24 | {
25 | public function __construct(
26 | protected SchedulerFactory $schedulerFactory,
27 | protected Locator $locator,
28 | ) {
29 | }
30 |
31 |
32 | /**
33 | * @param string[] $files indexed by []
34 | */
35 | public function analyze(ProgressBar $progressBar, array $files): AnalyzeResult
36 | {
37 | $scheduler = $this->schedulerFactory->create(AnalyzeTaskHandlerFactory::class, context: null);
38 | $state = new AnalyzeState($progressBar, $scheduler);
39 |
40 | foreach ($files as $file) {
41 | $this->scheduleFile($state, $file, primary: true);
42 | }
43 |
44 | /** @var AnalyzeTask $task */
45 | foreach ($scheduler->process() as $task => $result) {
46 | $this->processTaskResult($result, $state);
47 | $progressBar->setMessage($task->sourceFile);
48 | $progressBar->advance();
49 | }
50 |
51 | foreach ($state->missing as $missing) {
52 | $referencedBy = $state->classLikes[$missing->referencedBy->fullLower] ?? $state->functions[$missing->referencedBy->fullLower];
53 |
54 | if ($referencedBy->primary) {
55 | $state->errors[ErrorKind::MissingSymbol->name][] = $this->createMissingSymbolError($missing, $referencedBy);
56 | }
57 | }
58 |
59 | return new AnalyzeResult($state->classLikes + $state->missing, $state->functions, $state->errors);
60 | }
61 |
62 |
63 | protected function scheduleFile(AnalyzeState $state, string $file, bool $primary): void
64 | {
65 | $file = Helpers::realPath($file);
66 |
67 | if (isset($state->files[$file])) {
68 | return;
69 | }
70 |
71 | $state->files[$file] = true;
72 | $state->progressBar->setMaxSteps(count($state->files));
73 | $state->scheduler->schedule(new AnalyzeTask($file, $primary));
74 | }
75 |
76 |
77 | /**
78 | * @param array<ClassLikeInfo | FunctionInfo | ClassLikeReferenceInfo | ErrorInfo> $result
79 | */
80 | protected function processTaskResult(array $result, AnalyzeState $state): void
81 | {
82 | foreach ($result as $info) {
83 | match (true) {
84 | $info instanceof ClassLikeReferenceInfo => $this->processClassLikeReference($state, $info),
85 | $info instanceof ClassLikeInfo => $this->processClassLike($state, $info),
86 | $info instanceof FunctionInfo => $this->processFunction($state, $info),
87 | $info instanceof ErrorInfo => $this->processError($state, $info),
88 | };
89 | }
90 | }
91 |
92 |
93 | protected function processClassLikeReference(AnalyzeState $state, ClassLikeReferenceInfo $info): void
94 | {
95 | if ($state->prevName !== null && !isset($state->classLikes[$info->fullLower]) && !isset($state->missing[$info->fullLower])) {
96 | $name = new NameInfo($info->full, $info->fullLower);
97 | $state->missing[$info->fullLower] = new MissingInfo($name, $state->prevName);
98 |
99 | if (($file = $this->locator->locate($info)) !== null) {
100 | $this->scheduleFile($state, $file, primary: false);
101 | }
102 | }
103 | }
104 |
105 |
106 | protected function processClassLike(AnalyzeState $state, ClassLikeInfo $info): void
107 | {
108 | $existing = $state->classLikes[$info->name->fullLower] ?? null;
109 |
110 | if ($existing === null || ($info->primary && !$existing->primary)) {
111 | unset($state->missing[$info->name->fullLower]);
112 | $state->classLikes[$info->name->fullLower] = $info;
113 | $state->prevName = $info->name;
114 |
115 | } elseif ($info->primary) {
116 | $state->errors[ErrorKind::DuplicateSymbol->name][] = $this->createDuplicateSymbolError($info, $existing);
117 | $state->prevName = null;
118 |
119 | } else {
120 | $state->prevName = null;
121 | }
122 | }
123 |
124 |
125 | protected function processFunction(AnalyzeState $state, FunctionInfo $info): void
126 | {
127 | $existing = $state->functions[$info->name->fullLower] ?? null;
128 |
129 | if ($existing === null || ($info->primary && !$existing->primary)) {
130 | $state->functions[$info->name->fullLower] = $info;
131 | $state->prevName = $info->name;
132 |
133 | } elseif ($info->primary) {
134 | $state->errors[ErrorKind::DuplicateSymbol->name][] = $this->createDuplicateSymbolError($info, $existing);
135 | $state->prevName = null;
136 |
137 | } else {
138 | $state->prevName = null;
139 | }
140 | }
141 |
142 |
143 | protected function processError(AnalyzeState $state, ErrorInfo $info): void
144 | {
145 | $state->errors[$info->kind->name][] = $info;
146 | $state->prevName = null;
147 | }
148 |
149 |
150 | protected function createMissingSymbolError(MissingInfo $dependency, ClassLikeInfo | FunctionInfo $referencedBy): ErrorInfo
151 | {
152 | return new ErrorInfo(ErrorKind::MissingSymbol, implode("\n", [
153 | "Missing {$dependency->name->full}",
154 | "referenced by {$referencedBy->name->full}",
155 | ]));
156 | }
157 |
158 |
159 | protected function createDuplicateSymbolError(ClassLikeInfo | FunctionInfo $info, ClassLikeInfo | FunctionInfo $first): ErrorInfo
160 | {
161 | return new ErrorInfo(ErrorKind::DuplicateSymbol, implode("\n", [
162 | "Multiple definitions of {$info->name->full}.",
163 | "The first definition was found in {$first->file} on line {$first->startLine}",
164 | "and then another one was found in {$info->file} on line {$info->startLine}",
165 | ]));
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeResult.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 | use ApiGen\Info\ClassLikeInfo;
6 | use ApiGen\Info\ErrorInfo;
7 | use ApiGen\Info\FunctionInfo;
8 |
9 |
10 | class AnalyzeResult
11 | {
12 | /**
13 | * @param ClassLikeInfo[] $classLike indexed by [classLikeName]
14 | * @param FunctionInfo[] $function indexed by [functionName]
15 | * @param ErrorInfo[][] $error indexed by [errorKind][]
16 | */
17 | public function __construct(
18 | public array $classLike,
19 | public array $function,
20 | public array $error,
21 | ) {
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeState.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 | use ApiGen\Info\ClassLikeInfo;
6 | use ApiGen\Info\ClassLikeReferenceInfo;
7 | use ApiGen\Info\ErrorInfo;
8 | use ApiGen\Info\FunctionInfo;
9 | use ApiGen\Info\MissingInfo;
10 | use ApiGen\Info\NameInfo;
11 | use ApiGen\Scheduler;
12 | use Symfony\Component\Console\Helper\ProgressBar;
13 |
14 |
15 | class AnalyzeState
16 | {
17 | /** @var true[] indexed by [path] */
18 | public array $files = [];
19 |
20 | /** @var MissingInfo[] indexed by [classLikeName] */
21 | public array $missing = [];
22 |
23 | /** @var ClassLikeInfo[] indexed by [classLikeName] */
24 | public array $classLikes = [];
25 |
26 | /** @var FunctionInfo[] indexed by [functionName] */
27 | public array $functions = [];
28 |
29 | /** @var ErrorInfo[][] indexed by [errorKind][] */
30 | public array $errors = [];
31 |
32 | /** @var NameInfo|null */
33 | public ?NameInfo $prevName = null;
34 |
35 |
36 | /**
37 | * @param Scheduler<AnalyzeTask, array<ClassLikeInfo | FunctionInfo | ClassLikeReferenceInfo | ErrorInfo>> $scheduler
38 | */
39 | public function __construct(
40 | public ProgressBar $progressBar,
41 | public Scheduler $scheduler,
42 | ) {
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeTask.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 | use ApiGen\Task\Task;
6 |
7 |
8 | class AnalyzeTask implements Task
9 | {
10 | public function __construct(
11 | public string $sourceFile,
12 | public bool $primary,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Analyzer/AnalyzeTaskHandlerFactory.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 | use ApiGen\Task\TaskHandlerFactory;
6 |
7 |
8 | /**
9 | * @extends TaskHandlerFactory<null, AnalyzeTaskHandler>
10 | */
11 | interface AnalyzeTaskHandlerFactory extends TaskHandlerFactory
12 | {
13 | /**
14 | * @param null $context
15 | */
16 | public function create(mixed $context): AnalyzeTaskHandler;
17 | }
18 |
--------------------------------------------------------------------------------
/src/Analyzer/BodySkippingLexer.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 | use PhpParser\ErrorHandler;
6 | use PhpParser\Lexer;
7 | use PhpParser\Token;
8 |
9 | use function count;
10 |
11 | use const T_CURLY_OPEN;
12 | use const T_DOLLAR_OPEN_CURLY_BRACES;
13 | use const T_FUNCTION;
14 | use const T_USE;
15 | use const T_WHITESPACE;
16 |
17 |
18 | class BodySkippingLexer extends Lexer
19 | {
20 | private const CURLY_BRACE_OPEN = 0x7B;
21 | private const CURLY_BRACE_CLOSE = 0x7D;
22 | private const SEMICOLON = 0x3B;
23 |
24 |
25 | /**
26 | * @param list<Token> $tokens
27 | */
28 | protected function postprocessTokens(array &$tokens, ErrorHandler $errorHandler): void
29 | {
30 | parent::postprocessTokens($tokens, $errorHandler);
31 |
32 | $tokenCount = count($tokens);
33 | for ($i = 0; $i < $tokenCount; $i++) { // looking for function start
34 | if ($tokens[$i]->id === T_FUNCTION && $tokens[$i - 2]->id !== T_USE) {
35 | for ($i++; $i < $tokenCount; $i++) { // looking for opening curly brace of function body or semicolon
36 | switch ($tokens[$i]->id) {
37 | case self::SEMICOLON:
38 | continue 3; // look for next function
39 |
40 | case self::CURLY_BRACE_OPEN:
41 | break 2;
42 | }
43 | }
44 |
45 | for ($i++, $level = 0; $i < $tokenCount; $i++) { // looking for closing curly brace of function body
46 | switch ($tokens[$i]->id) {
47 | case T_WHITESPACE:
48 | continue 2;
49 |
50 | case self::CURLY_BRACE_OPEN:
51 | case T_CURLY_OPEN:
52 | case T_DOLLAR_OPEN_CURLY_BRACES:
53 | $level++;
54 | break;
55 |
56 | case self::CURLY_BRACE_CLOSE:
57 | if ($level === 0) {
58 | continue 3; // look for next function
59 | }
60 |
61 | $level--;
62 | break;
63 | }
64 |
65 | $tokens[$i] = new Token(T_WHITESPACE, ' '); // @phpstan-ignore parameterByRef.type
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Analyzer/Filter.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 | use ApiGen\Info\ClassLikeInfo;
6 | use ApiGen\Info\FunctionInfo;
7 | use ApiGen\Info\MemberInfo;
8 | use PhpParser\Node;
9 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
10 |
11 |
12 | class Filter
13 | {
14 | /** @var int */
15 | protected int $excludedVisibilityMask = 0;
16 |
17 |
18 | /**
19 | * @param string[] $excludeTagged indexed by []
20 | */
21 | public function __construct(
22 | bool $excludeProtected,
23 | bool $excludePrivate,
24 | protected array $excludeTagged,
25 | ) {
26 | $this->excludedVisibilityMask |= ($excludeProtected ? Node\Stmt\Class_::MODIFIER_PROTECTED : 0);
27 | $this->excludedVisibilityMask |= ($excludePrivate ? Node\Stmt\Class_::MODIFIER_PRIVATE : 0);
28 | }
29 |
30 |
31 | public function filterClassLikeNode(Node\Stmt\ClassLike $node): bool
32 | {
33 | return true;
34 | }
35 |
36 |
37 | /**
38 | * @param PhpDocTagValueNode[][] $tags indexed by [tagName][]
39 | */
40 | public function filterClassLikeTags(array $tags): bool
41 | {
42 | foreach ($this->excludeTagged as $tag) {
43 | if (isset($tags[$tag])) {
44 | return false;
45 | }
46 | }
47 |
48 | return true;
49 | }
50 |
51 |
52 | public function filterClassLikeInfo(ClassLikeInfo $info): bool
53 | {
54 | return true;
55 | }
56 |
57 |
58 | public function filterFunctionNode(Node\Stmt\Function_ $node): bool
59 | {
60 | return true;
61 | }
62 |
63 |
64 | /**
65 | * @param PhpDocTagValueNode[][] $tags indexed by [tagName][]
66 | */
67 | public function filterFunctionTags(array $tags): bool
68 | {
69 | foreach ($this->excludeTagged as $tag) {
70 | if (isset($tags[$tag])) {
71 | return false;
72 | }
73 | }
74 |
75 | return true;
76 | }
77 |
78 |
79 | public function filterFunctionInfo(FunctionInfo $info): bool
80 | {
81 | return true;
82 | }
83 |
84 |
85 | public function filterConstantNode(Node\Stmt\ClassConst $node): bool
86 | {
87 | return ($node->flags & $this->excludedVisibilityMask) === 0;
88 | }
89 |
90 |
91 | public function filterPropertyNode(Node\Stmt\Property $node): bool
92 | {
93 | return ($node->flags & $this->excludedVisibilityMask) === 0;
94 | }
95 |
96 |
97 | public function filterPromotedPropertyNode(Node\Param $node): bool
98 | {
99 | return ($node->flags & $this->excludedVisibilityMask) === 0;
100 | }
101 |
102 |
103 | public function filterMethodNode(Node\Stmt\ClassMethod $node): bool
104 | {
105 | return ($node->flags & $this->excludedVisibilityMask) === 0;
106 | }
107 |
108 |
109 | public function filterEnumCaseNode(Node\Stmt\EnumCase $node): bool
110 | {
111 | return true;
112 | }
113 |
114 |
115 | /**
116 | * @param PhpDocTagValueNode[][] $tags indexed by [tagName][]
117 | */
118 | public function filterMemberTags(array $tags): bool
119 | {
120 | foreach ($this->excludeTagged as $tag) {
121 | if (isset($tags[$tag])) {
122 | return false;
123 | }
124 | }
125 |
126 | return true;
127 | }
128 |
129 |
130 | public function filterMemberInfo(ClassLikeInfo $classLike, MemberInfo $member): bool
131 | {
132 | return true;
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/src/Analyzer/IdentifierKind.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 |
6 | enum IdentifierKind
7 | {
8 | case Keyword;
9 | case ClassLike;
10 | case Generic;
11 | case Alias;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Analyzer/NameContextFrame.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Analyzer;
4 |
5 | use ApiGen\Info\AliasReferenceInfo;
6 | use ApiGen\Info\ClassLikeReferenceInfo;
7 |
8 |
9 | class NameContextFrame
10 | {
11 | /** @var null|NameContextFrame */
12 | public ?NameContextFrame $parent = null;
13 |
14 | /** @var null|ClassLikeReferenceInfo */
15 | public ?ClassLikeReferenceInfo $scope = null;
16 |
17 | /** @var true[] indexed by [name] */
18 | public array $genericParameters = [];
19 |
20 | /** @var AliasReferenceInfo[] indexed by [name] */
21 | public array $aliases = [];
22 |
23 |
24 | public function __construct(?NameContextFrame $parent)
25 | {
26 | $this->parent = $parent;
27 | $this->scope = $parent?->scope;
28 | $this->genericParameters = $parent->genericParameters ?? [];
29 | $this->aliases = $parent->aliases ?? [];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/ApiGen.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen;
4 |
5 | use ApiGen\Analyzer\AnalyzeResult;
6 | use ApiGen\Index\Index;
7 | use ApiGen\Info\ErrorKind;
8 | use Nette\Utils\Finder;
9 | use Symfony\Component\Console\Helper\ProgressBar;
10 | use Symfony\Component\Console\Style\OutputStyle;
11 |
12 | use function array_column;
13 | use function array_slice;
14 | use function count;
15 | use function hrtime;
16 | use function implode;
17 | use function is_dir;
18 | use function is_file;
19 | use function memory_get_peak_usage;
20 | use function memory_reset_peak_usage;
21 | use function sprintf;
22 |
23 | use const PHP_VERSION_ID;
24 |
25 |
26 | class ApiGen
27 | {
28 | /**
29 | * @param string[] $paths indexed by []
30 | * @param string[] $include indexed by []
31 | * @param string[] $exclude indexed by []
32 | */
33 | public function __construct(
34 | protected OutputStyle $output,
35 | protected Analyzer $analyzer,
36 | protected Indexer $indexer,
37 | protected Renderer $renderer,
38 | protected array $paths,
39 | protected array $include,
40 | protected array $exclude,
41 | ) {
42 | }
43 |
44 |
45 | public function generate(): bool
46 | {
47 | $files = $this->findFiles();
48 |
49 | PHP_VERSION_ID >= 80200 && memory_reset_peak_usage();
50 | $analyzeTime = -hrtime(true);
51 | $analyzeResult = $this->analyze($files);
52 | $analyzeTime += hrtime(true);
53 | $analyzeMemory = memory_get_peak_usage();
54 |
55 | PHP_VERSION_ID >= 80200 && memory_reset_peak_usage();
56 | $indexTime = -hrtime(true);
57 | $index = $this->index($analyzeResult);
58 | $indexTime += hrtime(true);
59 | $indexMemory = memory_get_peak_usage();
60 |
61 | PHP_VERSION_ID >= 80200 && memory_reset_peak_usage();
62 | $renderTime = -hrtime(true);
63 | $this->render($index);
64 | $renderTime += hrtime(true);
65 | $renderMemory = memory_get_peak_usage();
66 |
67 | $this->performance($analyzeTime, $analyzeMemory, $indexTime, $indexMemory, $renderTime, $renderMemory);
68 | return $this->finish($analyzeResult);
69 | }
70 |
71 |
72 | /**
73 | * @return string[] list of files, indexed by []
74 | */
75 | protected function findFiles(): array
76 | {
77 | $files = [];
78 | $dirs = [];
79 |
80 | foreach ($this->paths as $path) {
81 | if (is_file($path)) {
82 | $files[] = $path;
83 |
84 | } elseif (is_dir($path)) {
85 | $dirs[] = $path;
86 |
87 | } else {
88 | $this->output->error(sprintf('Path "%s" does not exist.', $path));
89 | }
90 | }
91 |
92 | if (count($dirs) > 0) {
93 | $finder = Finder::findFiles($this->include)
94 | ->exclude($this->exclude)
95 | ->from($dirs);
96 |
97 | foreach ($finder as $file => $_) {
98 | $files[] = $file;
99 | }
100 | }
101 |
102 | if (count($files) === 0) {
103 | throw new \RuntimeException('No source files found.');
104 |
105 | } elseif ($this->output->isDebug()) {
106 | $this->output->text('<info>Matching source files:</info>');
107 | $this->output->newLine();
108 | $this->output->listing($files);
109 |
110 | } elseif ($this->output->isVerbose()) {
111 | $this->output->text(sprintf('Found %d source files.', count($files)));
112 | $this->output->newLine();
113 | }
114 |
115 | return $files;
116 | }
117 |
118 |
119 | /**
120 | * @param string[] $files indexed by []
121 | */
122 | protected function analyze(array $files): AnalyzeResult
123 | {
124 | $progressBar = $this->createProgressBar('Analyzing');
125 | $result = $this->analyzer->analyze($progressBar, $files);
126 |
127 | if ($progressBar->getMaxSteps() === $progressBar->getProgress()) {
128 | $progressBar->setMessage('done');
129 | $progressBar->finish();
130 | }
131 |
132 | $this->output->newLine(2);
133 |
134 | return $result;
135 | }
136 |
137 |
138 | protected function index(AnalyzeResult $analyzeResult): Index
139 | {
140 | $index = new Index();
141 |
142 | foreach ($analyzeResult->classLike as $info) {
143 | $this->indexer->indexFile($index, $info->file, $info->primary);
144 | $this->indexer->indexNamespace($index, $info->name->namespace, $info->name->namespaceLower, $info->primary, $info->isDeprecated());
145 | $this->indexer->indexClassLike($index, $info);
146 | }
147 |
148 | foreach ($analyzeResult->function as $info) {
149 | $this->indexer->indexFile($index, $info->file, $info->primary);
150 | $this->indexer->indexNamespace($index, $info->name->namespace, $info->name->namespaceLower, $info->primary, $info->isDeprecated());
151 | $this->indexer->indexFunction($index, $info);
152 | }
153 |
154 | $this->indexer->postProcess($index);
155 | return $index;
156 | }
157 |
158 |
159 | protected function render(Index $index): void
160 | {
161 | $progressBar = $this->createProgressBar('Rendering');
162 | $this->renderer->render($progressBar, $index);
163 |
164 | if ($progressBar->getMaxSteps() === $progressBar->getProgress()) {
165 | $progressBar->setMessage('done');
166 | $progressBar->finish();
167 | }
168 |
169 | $this->output->newLine(2);
170 | }
171 |
172 |
173 | protected function createProgressBar(string $label): ProgressBar
174 | {
175 | $progressBar = $this->output->createProgressBar();
176 | $progressBar->setFormat(" <fg=green>$label</> %current%/%max% %bar% %percent:3s%% %message%");
177 | $progressBar->setBarCharacter("\u{2588}");
178 | $progressBar->setProgressCharacter('_');
179 | $progressBar->setEmptyBarCharacter('_');
180 |
181 | return $progressBar;
182 | }
183 |
184 |
185 | protected function performance(float $analyzeTime, int $analyzeMemory, float $indexTime, int $indexMemory, float $renderTime, int $renderMemory): void
186 | {
187 | if ($this->output->isVeryVerbose()) {
188 | $lines = [
189 | 'Analyze time' => sprintf('%6.0f ms', $analyzeTime / 1e6),
190 | 'Index time' => sprintf('%6.0f ms', $indexTime / 1e6),
191 | 'Render time' => sprintf('%6.0f ms', $renderTime / 1e6),
192 | '' => '',
193 | 'Analyze peak memory' => sprintf('%6.0f MB', $analyzeMemory / 1e6),
194 | 'Index peak memory' => sprintf('%6.0f MB', $indexMemory / 1e6),
195 | 'Render peak memory' => sprintf('%6.0f MB', $renderMemory / 1e6),
196 | ];
197 |
198 | foreach ($lines as $label => $value) {
199 | $this->output->text(sprintf('<info>%-20s</info> %s', $label, $value));
200 | }
201 | }
202 | }
203 |
204 |
205 | protected function finish(AnalyzeResult $analyzeResult): bool
206 | {
207 | if (count($analyzeResult->error) === 0) {
208 | $this->output->success('Finished OK');
209 | return true;
210 | }
211 |
212 | $hasError = false;
213 | foreach ($analyzeResult->error as $errorKind => $errorGroup) {
214 | $errorLines = array_column($errorGroup, 'message');
215 |
216 | if (!$this->output->isVerbose() && count($errorLines) > 5) {
217 | $errorLines = array_slice($errorLines, 0, 5);
218 | $errorLines[] = '...';
219 | $errorLines[] = sprintf('and %d more (use --verbose to show all)', count($errorGroup) - 5);
220 | }
221 |
222 | if ($errorKind === ErrorKind::InternalError->name) {
223 | $hasError = true;
224 | $this->output->error(implode("\n\n", $errorLines));
225 |
226 | } else {
227 | $this->output->warning(implode("\n\n", $errorLines));
228 | }
229 | }
230 |
231 | if ($hasError) {
232 | $this->output->error('Finished with errors');
233 | return false;
234 | }
235 |
236 | $this->output->success('Finished with warnings');
237 | return true;
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/Bootstrap.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen;
4 |
5 | use Composer\InstalledVersions;
6 | use ErrorException;
7 | use Nette\DI\Compiler;
8 | use Nette\DI\Config\Loader;
9 | use Nette\DI\Container;
10 | use Nette\DI\ContainerLoader;
11 | use Nette\DI\Extensions\ExtensionsExtension;
12 | use Nette\DI\Helpers as DIHelpers;
13 | use Nette\Schema\Expect;
14 | use Nette\Schema\Helpers as SchemaHelpers;
15 | use Nette\Schema\Processor;
16 | use Nette\Utils\FileSystem;
17 | use Symfony\Component\Console\Style\OutputStyle;
18 |
19 | use function array_keys;
20 | use function array_map;
21 | use function assert;
22 | use function count;
23 | use function dirname;
24 | use function error_reporting;
25 | use function getcwd;
26 | use function ini_set;
27 | use function is_array;
28 | use function is_file;
29 | use function is_int;
30 | use function method_exists;
31 | use function set_error_handler;
32 | use function str_starts_with;
33 | use function sys_get_temp_dir;
34 |
35 | use const E_ALL;
36 | use const E_DEPRECATED;
37 | use const E_USER_DEPRECATED;
38 | use const PHP_RELEASE_VERSION;
39 | use const PHP_VERSION_ID;
40 |
41 |
42 | class Bootstrap
43 | {
44 | public static function configureErrorHandling(): void
45 | {
46 | error_reporting(E_ALL);
47 | ini_set('display_errors', 'stderr');
48 | ini_set('log_errors', '0');
49 |
50 | set_error_handler(function (int $severity, string $message, string $file, int $line): bool {
51 | if (error_reporting() & $severity && $severity !== E_DEPRECATED && $severity !== E_USER_DEPRECATED) {
52 | throw new ErrorException($message, 0, $severity, $file, $line);
53 |
54 | } else {
55 | return false;
56 | }
57 | });
58 | }
59 |
60 |
61 | /**
62 | * @param mixed[] $parameters indexed by [parameterName]
63 | * @param string[] $configPaths indexed by []
64 | */
65 | public static function createApiGen(OutputStyle $output, array $parameters, array $configPaths): ApiGen
66 | {
67 | $workingDir = getcwd();
68 | $tempDir = sys_get_temp_dir() . '/apigen';
69 | $version = InstalledVersions::getPrettyVersion('apigen/apigen');
70 |
71 | if ($workingDir === false) {
72 | throw new \RuntimeException('Unable to get current working directory.');
73 | }
74 |
75 | $autoDiscoveryPath = "$workingDir/apigen.neon";
76 | if (count($configPaths) === 0 && is_file($autoDiscoveryPath)) {
77 | $output->text("Using configuration file $autoDiscoveryPath.\n");
78 | $configPaths[] = $autoDiscoveryPath;
79 | }
80 |
81 | $config = self::mergeConfigs(
82 | ['parameters' => ['workingDir' => $workingDir, 'tempDir' => $tempDir, 'version' => $version]],
83 | self::loadConfig(__DIR__ . '/../apigen.neon'),
84 | ...array_map(self::loadConfig(...), $configPaths),
85 | ...[['parameters' => self::resolvePaths($parameters, $workingDir)]],
86 | );
87 |
88 | $parameters = $config['parameters'];
89 | unset($config['parameters']);
90 |
91 | self::validateParameters($parameters);
92 | $parameters = DIHelpers::expand($parameters, $parameters);
93 | $containerLoader = new ContainerLoader($parameters['tempDir'], autoRebuild: true);
94 |
95 | $containerGenerator = function (Compiler $compiler) use ($config, $parameters): void {
96 | $compiler->addExtension('extensions', new ExtensionsExtension);
97 | $compiler->addConfig($config);
98 | $compiler->setDynamicParameterNames(array_keys($parameters));
99 | };
100 |
101 | $containerKey = [
102 | $config,
103 | PHP_VERSION_ID - PHP_RELEASE_VERSION,
104 | ];
105 |
106 | /** @var class-string<Container> $containerClassName */
107 | $containerClassName = $containerLoader->load($containerGenerator, $containerKey);
108 |
109 | $container = new $containerClassName($parameters);
110 | $container->addService('symfonyConsole.output', $output);
111 | $container->initialize();
112 | ini_set('memory_limit', $container->getParameter('memoryLimit'));
113 |
114 | return $container->getByType(ApiGen::class);
115 | }
116 |
117 |
118 | /**
119 | * @param mixed[] $parameters indexed by [parameterName]
120 | */
121 | protected static function validateParameters(array $parameters): void
122 | {
123 | $schema = Expect::structure([
124 | // input
125 | 'paths' => Expect::listOf('string')->min(1),
126 | 'include' => Expect::listOf('string'),
127 | 'exclude' => Expect::listOf('string'),
128 |
129 | // analysis
130 | 'excludeProtected' => Expect::bool(),
131 | 'excludePrivate' => Expect::bool(),
132 | 'excludeTagged' => Expect::listOf('string'),
133 |
134 | // output
135 | 'outputDir' => Expect::string(),
136 | 'themeDir' => Expect::string()->nullable(),
137 | 'title' => Expect::string(),
138 | 'version' => Expect::string(),
139 | 'baseUrl' => Expect::string(),
140 |
141 | // system
142 | 'workingDir' => Expect::string(),
143 | 'tempDir' => Expect::string(),
144 | 'workerCount' => Expect::int()->min(1),
145 | 'memoryLimit' => Expect::string(),
146 | ]);
147 |
148 | (new Processor)->process($schema, $parameters);
149 | }
150 |
151 |
152 | /**
153 | * @param mixed[][] $configs indexed by [][configKey]
154 | * @return mixed[] indexed by [configKey]
155 | */
156 | protected static function mergeConfigs(array...$configs): array
157 | {
158 | $mergedConfig = [];
159 |
160 | foreach ($configs as $config) {
161 | foreach ($config['parameters'] ?? [] as $key => $value) {
162 | if (is_array($value)) {
163 | $config['parameters'][$key][SchemaHelpers::PreventMerging] = true;
164 | }
165 | }
166 |
167 | $mergedConfig = SchemaHelpers::merge($config, $mergedConfig);
168 | assert(is_array($mergedConfig));
169 | }
170 |
171 | return $mergedConfig;
172 | }
173 |
174 |
175 | /**
176 | * @return mixed[] indexed by [configKey]
177 | */
178 | protected static function loadConfig(string $path): array
179 | {
180 | $data = (new Loader)->load($path);
181 | $data['parameters'] = self::resolvePaths($data['parameters'] ?? [], Helpers::realPath(dirname($path)));
182 |
183 | return $data;
184 | }
185 |
186 |
187 | /**
188 | * @param mixed[] $parameters indexed by [parameterName]
189 | * @return mixed[] indexed by [parameterName]
190 | */
191 | protected static function resolvePaths(array $parameters, string $base): array
192 | {
193 | foreach (['tempDir', 'workingDir', 'outputDir', 'themeDir'] as $parameterKey) {
194 | if (isset($parameters[$parameterKey])) {
195 | $parameters[$parameterKey] = self::resolvePath($parameters[$parameterKey], $base);
196 | }
197 | }
198 |
199 | foreach ($parameters['paths'] ?? [] as $i => $path) {
200 | if (is_int($i)) {
201 | $parameters['paths'][$i] = self::resolvePath($parameters['paths'][$i], $base);
202 | }
203 | }
204 |
205 | return $parameters;
206 | }
207 |
208 |
209 | protected static function resolvePath(string $path, string $base): string
210 | {
211 | return (FileSystem::isAbsolute($path) || str_starts_with($path, '%'))
212 | ? $path
213 | : FileSystem::joinPaths($base, $path);
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/src/Helpers.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen;
4 |
5 | use ReflectionClass;
6 |
7 | use function array_map;
8 | use function array_shift;
9 | use function assert;
10 | use function count;
11 | use function realpath;
12 | use function str_starts_with;
13 | use function strlen;
14 | use function substr;
15 |
16 | use const DIRECTORY_SEPARATOR;
17 |
18 |
19 | class Helpers
20 | {
21 | /**
22 | * @param string[] $paths indexed by [], non-empty
23 | */
24 | public static function baseDir(array $paths): string
25 | {
26 | assert(count($paths) > 0);
27 |
28 | $paths = array_map(self::realPath(...), $paths);
29 | $first = array_shift($paths);
30 | $j = 0;
31 |
32 | for ($i = 0; $i < strlen($first); $i++) {
33 | foreach ($paths as $path) {
34 | if (!isset($path[$i]) || $path[$i] !== $first[$i]) {
35 | return substr($first, 0, $j);
36 |
37 | } elseif ($first[$i] === DIRECTORY_SEPARATOR) {
38 | $j = $i;
39 | }
40 | }
41 | }
42 |
43 | return $first;
44 | }
45 |
46 |
47 | public static function realPath(string $path): string
48 | {
49 | if (str_starts_with($path, 'phar://')) {
50 | return $path;
51 | }
52 |
53 | $realPath = realpath($path);
54 |
55 | if ($realPath === false) {
56 | throw new \RuntimeException("File $path does not exist.");
57 | }
58 |
59 | return $realPath;
60 | }
61 |
62 |
63 | /**
64 | * @param class-string $name
65 | */
66 | public static function classLikePath(string $name): string
67 | {
68 | $reflection = new ReflectionClass($name);
69 | $path = $reflection->getFileName();
70 |
71 | if ($path === false) {
72 | throw new \RuntimeException("Class-like $name has no path.");
73 | }
74 |
75 | return $path;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Index/FileIndex.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Index;
4 |
5 | use ApiGen\Info\ClassLikeInfo;
6 | use ApiGen\Info\FunctionInfo;
7 |
8 |
9 | class FileIndex
10 | {
11 | /** @var ClassLikeInfo[] indexed by [classLikeName] */
12 | public array $classLike = [];
13 |
14 | /** @var FunctionInfo[] indexed by [functionName] */
15 | public array $function = [];
16 |
17 |
18 | public function __construct(
19 | public string $path,
20 | public bool $primary,
21 | ) {
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Index/Index.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Index;
4 |
5 | use ApiGen\Info\ClassInfo;
6 | use ApiGen\Info\ClassLikeInfo;
7 | use ApiGen\Info\EnumInfo;
8 | use ApiGen\Info\FunctionInfo;
9 | use ApiGen\Info\InterfaceInfo;
10 | use ApiGen\Info\TraitInfo;
11 |
12 |
13 | class Index
14 | {
15 | /** @var FileIndex[] indexed by [filePath] */
16 | public array $files = [];
17 |
18 | /** @var NamespaceIndex[] indexed by [namespaceName] */
19 | public array $namespace = [];
20 |
21 | /** @var ClassLikeInfo[] indexed by [classLikeName] */
22 | public array $classLike = [];
23 |
24 | /** @var ClassInfo[] indexed by [className] */
25 | public array $class = [];
26 |
27 | /** @var InterfaceInfo[] indexed by [interfaceName] */
28 | public array $interface = [];
29 |
30 | /** @var TraitInfo[] indexed by [traitName] */
31 | public array $trait = [];
32 |
33 | /** @var EnumInfo[] indexed by [enumName] */
34 | public array $enum = [];
35 |
36 | /** @var FunctionInfo[] indexed by [functionName] */
37 | public array $function = [];
38 |
39 | /** @var ClassInfo[][] indexed by [classLikeName][classLikeName] */
40 | public array $classExtends = [];
41 |
42 | /** @var ClassInfo[][] indexed by [classLikeName][classLikeName] */
43 | public array $classImplements = [];
44 |
45 | /** @var ClassInfo[][] indexed by [classLikeName][classLikeName] */
46 | public array $classUses = [];
47 |
48 | /** @var InterfaceInfo[][] indexed by [classLikeName][classLikeName] */
49 | public array $interfaceExtends = [];
50 |
51 | /** @var EnumInfo[][] indexed by [classLikeName][classLikeName] */
52 | public array $enumImplements = [];
53 |
54 | /** @var ClassLikeInfo[][] indexed by [classLikeName][classLikeName] classExtends + classImplements + classUses + interfaceExtends + enumImplements */
55 | public array $dag = [];
56 |
57 | /** @var ClassLikeInfo[][] indexed by [classLikeName][classLikeName], e.g. ['a']['b'] means that B instance of A */
58 | public array $instanceOf = [];
59 |
60 | /** @var ClassLikeInfo[][] indexed by [constantName][] */
61 | public array $constants = [];
62 |
63 | /** @var ClassLikeInfo[][] indexed by [propertyName][] */
64 | public array $properties = [];
65 |
66 | /** @var ClassLikeInfo[][] indexed by [methodName][] */
67 | public array $methods = [];
68 |
69 | /** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName], e.g. ['a']['b'] = [C] means method A::b is overriding C::b */
70 | public array $methodOverrides = [];
71 |
72 | /** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName][], e.g. ['c']['b'] = [A] means method C::b is overridden by A::b */
73 | public array $methodOverriddenBy = [];
74 |
75 | /** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName], e.g. ['a']['b'] = [C] means method A::b is implementing C::b */
76 | public array $methodImplements = [];
77 |
78 | /** @var ClassLikeInfo[][][] indexed by [classLikeName][methodName][], e.g. ['c']['b'] = [A] means method C::b is implemented by A::b */
79 | public array $methodImplementedBy = [];
80 | }
81 |
--------------------------------------------------------------------------------
/src/Index/NamespaceIndex.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Index;
4 |
5 | use ApiGen\Info\ClassInfo;
6 | use ApiGen\Info\ElementInfo;
7 | use ApiGen\Info\EnumInfo;
8 | use ApiGen\Info\FunctionInfo;
9 | use ApiGen\Info\InterfaceInfo;
10 | use ApiGen\Info\NameInfo;
11 | use ApiGen\Info\TraitInfo;
12 |
13 |
14 | class NamespaceIndex implements ElementInfo
15 | {
16 | /** @var ClassInfo[] indexed by [classShortName] (excludes exceptions) */
17 | public array $class = [];
18 |
19 | /** @var InterfaceInfo[] indexed by [interfaceShortName] */
20 | public array $interface = [];
21 |
22 | /** @var TraitInfo[] indexed by [traitShortName] */
23 | public array $trait = [];
24 |
25 | /** @var EnumInfo[] indexed by [enumShortName] */
26 | public array $enum = [];
27 |
28 | /** @var ClassInfo[] indexed by [exceptionShortName] */
29 | public array $exception = [];
30 |
31 | /** @var FunctionInfo[] indexed by [functionShortName] */
32 | public array $function = [];
33 |
34 | /** @var NamespaceIndex[] indexed by [namespaceShortName] */
35 | public array $children = [];
36 |
37 |
38 | public function __construct(
39 | public NameInfo $name,
40 | public bool $primary,
41 | public bool $deprecated,
42 | ) {
43 | }
44 |
45 |
46 | public function isDeprecated(): bool
47 | {
48 | return $this->deprecated;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Info/AliasInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use ApiGen\Info\Traits\HasLineLocation;
6 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7 |
8 |
9 | class AliasInfo
10 | {
11 | use HasLineLocation;
12 |
13 |
14 | public function __construct(
15 | public string $name,
16 | public TypeNode $type,
17 | ) {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Info/AliasReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use function strtolower;
6 |
7 |
8 | class AliasReferenceInfo
9 | {
10 | /** @var ClassLikeReferenceInfo */
11 | public ClassLikeReferenceInfo $classLike;
12 |
13 | /** @var string e.g. 'DatabaseOptions' */
14 | public string $alias;
15 |
16 | /** @var string e.g. 'databaseoptions' */
17 | public string $aliasLower;
18 |
19 |
20 | public function __construct(
21 | ClassLikeReferenceInfo $classLike,
22 | string $alias,
23 | ?string $aliasLower = null,
24 | ) {
25 | $this->classLike = $classLike;
26 | $this->alias = $alias;
27 | $this->aliasLower = $aliasLower ?? strtolower($alias);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Info/ClassInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class ClassInfo extends ClassLikeInfo
7 | {
8 | /** @var bool */
9 | public bool $abstract = false;
10 |
11 | /** @var bool */
12 | public bool $final = false;
13 |
14 | /** @var bool */
15 | public bool $readOnly = false;
16 |
17 | /** @var ClassLikeReferenceInfo|null */
18 | public ?ClassLikeReferenceInfo $extends = null;
19 |
20 | /** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
21 | public array $implements = [];
22 |
23 | /** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
24 | public array $uses = [];
25 | }
26 |
--------------------------------------------------------------------------------
/src/Info/ClassLikeInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Info\Traits\HasGenericParameters;
7 | use ApiGen\Info\Traits\HasLineLocation;
8 | use ApiGen\Info\Traits\HasTags;
9 |
10 |
11 | abstract class ClassLikeInfo implements ElementInfo
12 | {
13 | use HasTags;
14 | use HasLineLocation;
15 | use HasGenericParameters;
16 |
17 |
18 | /** @var string|null */
19 | public ?string $file = null;
20 |
21 | /** @var ConstantInfo[] indexed by [constantName] */
22 | public array $constants = [];
23 |
24 | /** @var PropertyInfo[] indexed by [propertyName] */
25 | public array $properties = [];
26 |
27 | /** @var MethodInfo[] indexed by [methodName] */
28 | public array $methods = [];
29 |
30 | /** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
31 | public array $mixins = [];
32 |
33 | /** @var AliasInfo[] indexed by [aliasName] */
34 | public array $aliases = [];
35 |
36 |
37 | public function __construct(
38 | public NameInfo $name,
39 | public bool $primary,
40 | ) {
41 | }
42 |
43 |
44 | public function isInstanceOf(Index $index, string $type): bool
45 | {
46 | return isset($index->instanceOf[$type][$this->name->fullLower]);
47 | }
48 |
49 |
50 | public function isThrowable(Index $index): bool
51 | {
52 | return $this->isInstanceOf($index, 'throwable');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Info/ClassLikeReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use ApiGen\Index\Index;
6 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
7 |
8 | use function strtolower;
9 |
10 |
11 | class ClassLikeReferenceInfo
12 | {
13 | /** @var string e.g. 'ApiGen\Info\Traits\HasName' */
14 | public string $full;
15 |
16 | /** @var string e.g. 'apigen\info\traits\hasname' */
17 | public string $fullLower;
18 |
19 | /** @var TypeNode[] */
20 | public array $genericArgs = [];
21 |
22 |
23 | public function __construct(string $full, ?string $fullLower = null)
24 | {
25 | $this->full = $full;
26 | $this->fullLower = $fullLower ?? strtolower($full);
27 | }
28 |
29 |
30 | public function resolve(Index $index, ?ClassLikeInfo $scope = null): ?ClassLikeInfo
31 | {
32 | if ($this->fullLower === 'self' || $this->fullLower === 'static') {
33 | return $scope;
34 | }
35 |
36 | if ($this->fullLower === 'parent' && $scope instanceof ClassInfo && $scope->extends !== null) {
37 | return $index->classLike[$scope->extends->fullLower] ?? null;
38 | }
39 |
40 | return $index->classLike[$this->fullLower] ?? null;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Info/ConstantInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
6 |
7 |
8 | class ConstantInfo extends MemberInfo
9 | {
10 | /** @var TypeNode|null */
11 | public ?TypeNode $type = null;
12 |
13 | /** @var ExprInfo */
14 | public ExprInfo $value;
15 |
16 | /** @var bool */
17 | public bool $final = false;
18 |
19 |
20 | public function __construct(string $name, ExprInfo $value)
21 | {
22 | parent::__construct($name);
23 | $this->value = $value;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Info/ConstantReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class ConstantReferenceInfo extends MemberReferenceInfo
7 | {
8 | }
9 |
--------------------------------------------------------------------------------
/src/Info/ElementInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | /**
7 | * @property-read bool $primary
8 | */
9 | interface ElementInfo
10 | {
11 | public function isDeprecated(): bool;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Info/EnumCaseInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class EnumCaseInfo extends MemberInfo
7 | {
8 | public function __construct(
9 | string $name,
10 | public ?ExprInfo $value,
11 | ) {
12 | parent::__construct($name);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Info/EnumInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class EnumInfo extends ClassLikeInfo
7 | {
8 | /** @var string|null */
9 | public ?string $scalarType = null;
10 |
11 | /** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
12 | public array $implements = [];
13 |
14 | /** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
15 | public array $uses = [];
16 |
17 | /** @var EnumCaseInfo[] indexed by [caseName] */
18 | public array $cases = [];
19 | }
20 |
--------------------------------------------------------------------------------
/src/Info/ErrorInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class ErrorInfo
7 | {
8 | public function __construct(
9 | public ErrorKind $kind,
10 | public string $message,
11 | ) {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Info/ErrorKind.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | enum ErrorKind
7 | {
8 | case InternalError;
9 | case SyntaxError;
10 | case InvalidEncoding;
11 | case MissingSymbol;
12 | case DuplicateSymbol;
13 | }
14 |
--------------------------------------------------------------------------------
/src/Info/Expr/ArgExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class ArgExprInfo
9 | {
10 | public function __construct(
11 | public ?string $name,
12 | public ExprInfo $value,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Info/Expr/ArrayExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class ArrayExprInfo implements ExprInfo
9 | {
10 | /**
11 | * @param ArrayItemExprInfo[] $items
12 | */
13 | public function __construct(
14 | public array $items,
15 | ) {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Info/Expr/ArrayItemExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class ArrayItemExprInfo
9 | {
10 | public function __construct(
11 | public ?ExprInfo $key,
12 | public ExprInfo $value,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Info/Expr/BinaryOpExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class BinaryOpExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public string $op,
12 | public ExprInfo $left,
13 | public ExprInfo $right,
14 | ) {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Info/Expr/BooleanExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class BooleanExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public bool $value,
12 | ) {
13 | }
14 |
15 |
16 | public function toString(): string
17 | {
18 | return $this->value ? 'true' : 'false';
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/Info/Expr/ClassConstantFetchExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ClassLikeReferenceInfo;
6 | use ApiGen\Info\ExprInfo;
7 |
8 |
9 | class ClassConstantFetchExprInfo implements ExprInfo
10 | {
11 | public function __construct(
12 | public ClassLikeReferenceInfo $classLike,
13 | public string $name,
14 | ) {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Info/Expr/ConstantFetchExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class ConstantFetchExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public string $name,
12 | ) {
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Info/Expr/DimFetchExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class DimFetchExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public ExprInfo $expr,
12 | public ExprInfo $dim,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Info/Expr/FloatExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 | use function is_finite;
8 | use function json_encode;
9 | use function str_contains;
10 |
11 | use const JSON_THROW_ON_ERROR;
12 |
13 |
14 | class FloatExprInfo implements ExprInfo
15 | {
16 | public function __construct(
17 | public float $value,
18 | public string $raw,
19 | ) {
20 | }
21 |
22 |
23 | public function toString(): string
24 | {
25 | if (!is_finite($this->value)) {
26 | return (string) $this->value;
27 | }
28 |
29 | $json = json_encode($this->value, JSON_THROW_ON_ERROR);
30 | return str_contains($json, '.') ? $json : "$json.0";
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Info/Expr/IntegerExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 | use function abs;
8 | use function base_convert;
9 |
10 |
11 | class IntegerExprInfo implements ExprInfo
12 | {
13 | public function __construct(
14 | public int $value,
15 | public int $base,
16 | public string $raw,
17 | ) {
18 | }
19 |
20 |
21 | public function toString(): string
22 | {
23 | if ($this->base === 10) {
24 | return (string) $this->value;
25 | }
26 |
27 | $sign = $this->value < 0 ? '-' : '';
28 | $base = [2 => '0b', 8 => '0', 16 => '0x'][$this->base];
29 | return $sign . $base . base_convert((string) abs($this->value), 10, $this->base);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Info/Expr/NewExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ClassLikeReferenceInfo;
6 | use ApiGen\Info\ExprInfo;
7 |
8 |
9 | class NewExprInfo implements ExprInfo
10 | {
11 | /**
12 | * @param ArgExprInfo[] $args
13 | */
14 | public function __construct(
15 | public ClassLikeReferenceInfo $classLike,
16 | public array $args,
17 | ) {
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Info/Expr/NullExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class NullExprInfo implements ExprInfo
9 | {
10 | public function toString(): string
11 | {
12 | return 'null';
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/Info/Expr/NullSafePropertyFetchExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class NullSafePropertyFetchExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public ExprInfo $expr,
12 | public ExprInfo|string $property,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Info/Expr/PropertyFetchExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class PropertyFetchExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public ExprInfo $expr,
12 | public ExprInfo|string $property,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Info/Expr/StringExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 | use function mb_ord;
8 | use function ord;
9 | use function preg_match;
10 | use function preg_replace_callback;
11 | use function sprintf;
12 | use function strlen;
13 |
14 |
15 | class StringExprInfo implements ExprInfo
16 | {
17 | public function __construct(
18 | public string $value,
19 | public ?string $raw = null,
20 | ) {
21 | }
22 |
23 |
24 | public function toString(): string
25 | {
26 | $utf8 = (bool) preg_match('##u', $this->value);
27 | $pattern = $utf8 ? '#[\p{C}"\'\\\\]#u' : '#[\x00-\x1F\x7F-\xFF"\'\\\\]#';
28 | $special = ["\r" => '\r', "\n" => '\n', "\t" => '\t', '\\' => '\\\\', '"' => '\\"', '\'' => '\''];
29 |
30 | $s = preg_replace_callback(
31 | $pattern,
32 | function ($m) use ($special) {
33 | if (isset($special[$m[0]])) {
34 | return $special[$m[0]];
35 |
36 | } elseif (strlen($m[0]) === 1) {
37 | return sprintf('\x%02X', ord($m[0]));
38 |
39 | } else {
40 | return sprintf('\u{%X}', mb_ord($m[0]));
41 | }
42 | },
43 | $this->value,
44 | -1,
45 | $count,
46 | );
47 |
48 | $quote = $count === 0 ? "'" : '"';
49 | return $quote . $s . $quote;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/Info/Expr/TernaryExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class TernaryExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public ExprInfo $condition,
12 | public ?ExprInfo $if,
13 | public ExprInfo $else,
14 | ) {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Info/Expr/UnaryOpExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Expr;
4 |
5 | use ApiGen\Info\ExprInfo;
6 |
7 |
8 | class UnaryOpExprInfo implements ExprInfo
9 | {
10 | public function __construct(
11 | public string $op,
12 | public ExprInfo $expr,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Info/ExprInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | interface ExprInfo
7 | {
8 | }
9 |
--------------------------------------------------------------------------------
/src/Info/FunctionInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use ApiGen\Info\Traits\HasGenericParameters;
6 | use ApiGen\Info\Traits\HasLineLocation;
7 | use ApiGen\Info\Traits\HasTags;
8 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
9 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
10 |
11 |
12 | class FunctionInfo implements ElementInfo
13 | {
14 | use HasTags;
15 | use HasLineLocation;
16 | use HasGenericParameters;
17 |
18 |
19 | /** @var string|null */
20 | public ?string $file = null;
21 |
22 | /** @var ParameterInfo[] indexed by [parameterName] */
23 | public array $parameters = [];
24 |
25 | /** @var TypeNode|null */
26 | public ?TypeNode $returnType = null;
27 |
28 | /** @var PhpDocTextNode[] indexed by [] */
29 | public array $returnDescription = [];
30 |
31 | /** @var bool */
32 | public bool $byRef = false;
33 |
34 |
35 | public function __construct(
36 | public NameInfo $name,
37 | public bool $primary,
38 | ) {
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Info/FunctionReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use function strtolower;
6 |
7 |
8 | class FunctionReferenceInfo
9 | {
10 | /** @var string e.g. 'Tracy\dump' */
11 | public string $full;
12 |
13 | /** @var string e.g. 'tracy\dump' */
14 | public string $fullLower;
15 |
16 |
17 | public function __construct(string $full, ?string $fullLower = null)
18 | {
19 | $this->full = $full;
20 | $this->fullLower = $fullLower ?? strtolower($full);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Info/GenericParameterInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
6 |
7 |
8 | class GenericParameterInfo
9 | {
10 | public function __construct(
11 | public string $name,
12 | public GenericParameterVariance $variance,
13 | public ?TypeNode $bound = null,
14 | public ?TypeNode $default = null,
15 | public string $description = '',
16 | ) {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Info/GenericParameterVariance.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | enum GenericParameterVariance
7 | {
8 | case Invariant;
9 | case Covariant;
10 | case Contravariant;
11 | }
12 |
--------------------------------------------------------------------------------
/src/Info/InterfaceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class InterfaceInfo extends ClassLikeInfo
7 | {
8 | /** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
9 | public array $extends = [];
10 | }
11 |
--------------------------------------------------------------------------------
/src/Info/MemberInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Info\Traits\HasLineLocation;
7 | use ApiGen\Info\Traits\HasTags;
8 | use ApiGen\Info\Traits\HasVisibility;
9 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
10 |
11 |
12 | abstract class MemberInfo
13 | {
14 | use HasTags;
15 | use HasLineLocation;
16 | use HasVisibility;
17 |
18 |
19 | /** @var string */
20 | public string $name;
21 |
22 | /** @var bool */
23 | public bool $magic = false;
24 |
25 |
26 | public function __construct(string $name)
27 | {
28 | $this->name = $name;
29 | }
30 |
31 |
32 | /**
33 | * @return PhpDocTextNode[] indexed by []
34 | */
35 | public function getEffectiveDescription(Index $index, ClassLikeInfo $classLike): array
36 | {
37 | return $this->description;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Info/MemberReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | abstract class MemberReferenceInfo
7 | {
8 | public function __construct(
9 | public ClassLikeReferenceInfo $classLike,
10 | public string $name,
11 | ) {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Info/MethodInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Info\Traits\HasGenericParameters;
7 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
8 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
9 |
10 | use function count;
11 | use function strtolower;
12 |
13 |
14 | class MethodInfo extends MemberInfo
15 | {
16 | use HasGenericParameters;
17 |
18 |
19 | /** @var string */
20 | public string $nameLower;
21 |
22 | /** @var ParameterInfo[] indexed by [parameterName] */
23 | public array $parameters = [];
24 |
25 | /** @var TypeNode|null */
26 | public ?TypeNode $returnType = null;
27 |
28 | /** @var PhpDocTextNode[] indexed by [] */
29 | public array $returnDescription = [];
30 |
31 | /** @var bool */
32 | public bool $byRef = false;
33 |
34 | /** @var bool */
35 | public bool $static = false;
36 |
37 | /** @var bool */
38 | public bool $abstract = false;
39 |
40 | /** @var bool */
41 | public bool $final = false;
42 |
43 |
44 | public function __construct(string $name)
45 | {
46 | parent::__construct($name);
47 | $this->nameLower = strtolower($name);
48 | }
49 |
50 |
51 | /**
52 | * @return PhpDocTextNode[] indexed by []
53 | */
54 | public function getEffectiveDescription(Index $index, ClassLikeInfo $classLike): array
55 | {
56 | if (count($this->description) > 0) {
57 | return $this->description;
58 | }
59 |
60 | foreach ($this->ancestors($index, $classLike) as $ancestor) {
61 | $description = $ancestor->methods[$this->nameLower]->getEffectiveDescription($index, $ancestor);
62 |
63 | if (count($description) > 0) {
64 | return $description;
65 | }
66 | }
67 |
68 | return [];
69 | }
70 |
71 |
72 | /**
73 | * @return PhpDocTextNode[] indexed by []
74 | */
75 | public function getEffectiveReturnDescription(Index $index, ClassLikeInfo $classLike): array
76 | {
77 | if (count($this->returnDescription) > 0) {
78 | return $this->returnDescription;
79 | }
80 |
81 | foreach ($this->ancestors($index, $classLike) as $ancestor) {
82 | $description = $ancestor->methods[$this->nameLower]->getEffectiveReturnDescription($index, $ancestor);
83 |
84 | if (count($description) > 0) {
85 | return $description;
86 | }
87 | }
88 |
89 | return [];
90 | }
91 |
92 |
93 | /**
94 | * @return iterable<ClassLikeInfo>
95 | */
96 | public function ancestors(Index $index, ClassLikeInfo $classLike): iterable
97 | {
98 | yield from $index->methodOverrides[$classLike->name->fullLower][$this->nameLower] ?? [];
99 | yield from $index->methodImplements[$classLike->name->fullLower][$this->nameLower] ?? [];
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/Info/MethodReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use function strtolower;
6 |
7 |
8 | class MethodReferenceInfo extends MemberReferenceInfo
9 | {
10 | public string $nameLower;
11 |
12 | public function __construct(
13 | ClassLikeReferenceInfo $classLike,
14 | string $name,
15 | ) {
16 | parent::__construct($classLike, $name);
17 | $this->nameLower = strtolower($name);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/Info/MissingInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class MissingInfo extends ClassLikeInfo
7 | {
8 | public function __construct(
9 | NameInfo $name,
10 | public NameInfo $referencedBy,
11 | ) {
12 | parent::__construct(
13 | $name,
14 | primary: false,
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Info/NameInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use function strrpos;
6 | use function strtolower;
7 | use function substr;
8 |
9 |
10 | class NameInfo
11 | {
12 | /** @var string e.g. 'ApiGen\Info\Traits\HasName' */
13 | public string $full;
14 |
15 | /** @var string e.g. 'apigen\info\traits\hasname' */
16 | public string $fullLower;
17 |
18 | /** @var string e.g. 'HasName' */
19 | public string $short;
20 |
21 | /** @var string e.g. 'hasname' */
22 | public string $shortLower;
23 |
24 | /** @var string e.g. 'ApiGen\Info\Traits' */
25 | public string $namespace;
26 |
27 | /** @var string e.g. 'apigen\info\traits' */
28 | public string $namespaceLower;
29 |
30 |
31 | public function __construct(string $full, ?string $fullLower = null)
32 | {
33 | $pos = strrpos($full, '\\');
34 |
35 | $this->full = $full;
36 | $this->fullLower = $fullLower ?? strtolower($full);
37 |
38 | $this->short = $pos === false ? $full : substr($full, $pos + 1);
39 | $this->shortLower = strtolower($this->short);
40 |
41 | $this->namespace = $pos === false ? '' : substr($full, 0, $pos);
42 | $this->namespaceLower = strtolower($this->namespace);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Info/ParameterInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use ApiGen\Index\Index;
6 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
7 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
8 |
9 | use function array_values;
10 | use function count;
11 |
12 |
13 | class ParameterInfo
14 | {
15 | /** @var string */
16 | public string $name;
17 |
18 | /** @var int */
19 | public int $position;
20 |
21 | /** @var PhpDocTextNode[] indexed by [] */
22 | public array $description = [];
23 |
24 | /** @var TypeNode|null */
25 | public ?TypeNode $type = null;
26 |
27 | /** @var bool */
28 | public bool $byRef = false;
29 |
30 | /** @var bool */
31 | public bool $variadic = false;
32 |
33 | /** @var ExprInfo|null */
34 | public ?ExprInfo $default = null;
35 |
36 |
37 | public function __construct(string $name, int $position)
38 | {
39 | $this->name = $name;
40 | $this->position = $position;
41 | }
42 |
43 |
44 | /**
45 | * @return PhpDocTextNode[] indexed by []
46 | */
47 | public function getEffectiveDescription(Index $index, ClassLikeInfo $classLike, MethodInfo $method): array
48 | {
49 | if (count($this->description) > 0) {
50 | return $this->description;
51 | }
52 |
53 | foreach ($method->ancestors($index, $classLike) as $ancestor) {
54 | $ancestorMethod = $ancestor->methods[$method->nameLower];
55 | $ancestorParameter = array_values($ancestorMethod->parameters)[$this->position] ?? null;
56 | $description = $ancestorParameter?->getEffectiveDescription($index, $ancestor, $ancestorMethod) ?? [];
57 |
58 | if (count($description) > 0) {
59 | return $description;
60 | }
61 | }
62 |
63 | return [];
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Info/PropertyInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 | use PHPStan\PhpDocParser\Ast\Type\TypeNode;
6 |
7 |
8 | class PropertyInfo extends MemberInfo
9 | {
10 | /** @var ExprInfo|null */
11 | public ?ExprInfo $default = null;
12 |
13 | /** @var TypeNode|null */
14 | public ?TypeNode $type = null;
15 |
16 | /** @var bool */
17 | public bool $static = false;
18 |
19 | /** @var bool */
20 | public bool $readOnly = false;
21 |
22 | /** @var bool */
23 | public bool $writeOnly = false;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Info/PropertyReferenceInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class PropertyReferenceInfo extends MemberReferenceInfo
7 | {
8 | }
9 |
--------------------------------------------------------------------------------
/src/Info/TraitInfo.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info;
4 |
5 |
6 | class TraitInfo extends ClassLikeInfo
7 | {
8 | /** @var ClassLikeReferenceInfo[] indexed by [classLikeName] */
9 | public array $uses = [];
10 | }
11 |
--------------------------------------------------------------------------------
/src/Info/Traits/HasGenericParameters.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Traits;
4 |
5 | use ApiGen\Info\GenericParameterInfo;
6 |
7 |
8 | trait HasGenericParameters
9 | {
10 | /** @var GenericParameterInfo[] indexed by [parameterName] */
11 | public array $genericParameters = [];
12 | }
13 |
--------------------------------------------------------------------------------
/src/Info/Traits/HasLineLocation.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Traits;
4 |
5 |
6 | trait HasLineLocation
7 | {
8 | /** @var int|null */
9 | public ?int $startLine = null;
10 |
11 | /** @var int|null */
12 | public ?int $endLine = null;
13 | }
14 |
--------------------------------------------------------------------------------
/src/Info/Traits/HasTags.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Traits;
4 |
5 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagValueNode;
6 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTextNode;
7 |
8 |
9 | trait HasTags
10 | {
11 | /** @var PhpDocTextNode[] indexed by [] */
12 | public array $description = [];
13 |
14 | /** @var PhpDocTagValueNode[][] indexed by [tagName][] */
15 | public array $tags = [];
16 |
17 |
18 | public function isDeprecated(): bool
19 | {
20 | return isset($this->tags['deprecated']);
21 | }
22 |
23 |
24 | public function isInternal(): bool
25 | {
26 | return isset($this->tags['internal']);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Info/Traits/HasVisibility.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Info\Traits;
4 |
5 |
6 | trait HasVisibility
7 | {
8 | /** @var bool */
9 | public bool $public = false;
10 |
11 | /** @var bool */
12 | public bool $protected = false;
13 |
14 | /** @var bool */
15 | public bool $private = false;
16 | }
17 |
--------------------------------------------------------------------------------
/src/Locator.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen;
4 |
5 | use ApiGen\Info\ClassLikeReferenceInfo;
6 | use Composer\Autoload\ClassLoader;
7 | use JetBrains\PHPStormStub\PhpStormStubsMap;
8 | use League;
9 | use Nette\Utils\FileSystem;
10 | use Nette\Utils\Json;
11 | use PHPStan\Php8StubsMap;
12 | use Symfony\Component\Console\Style\OutputStyle;
13 |
14 | use function dirname;
15 | use function implode;
16 | use function is_dir;
17 | use function is_file;
18 | use function strtolower;
19 |
20 | use const PHP_VERSION_ID;
21 |
22 |
23 | class Locator
24 | {
25 | /**
26 | * @param string[] $stubsMap indexed by [classLikeName]
27 | */
28 | public function __construct(
29 | protected array $stubsMap,
30 | protected ClassLoader $classLoader,
31 | ) {
32 | }
33 |
34 |
35 | public static function create(OutputStyle $output, string $projectDir): self
36 | {
37 | return new self(
38 | self::createStubsMap(),
39 | self::createComposerClassLoader($output, $projectDir),
40 | );
41 | }
42 |
43 |
44 | /**
45 | * @return string[] indexed by [classLikeName]
46 | */
47 | protected static function createStubsMap(): array
48 | {
49 | $stubsMap = [];
50 |
51 | $phpStormStubsDir = dirname(Helpers::classLikePath(PhpStormStubsMap::class));
52 | foreach (PhpStormStubsMap::CLASSES as $class => $path) {
53 | $stubsMap[strtolower($class)] = "$phpStormStubsDir/$path";
54 | }
55 |
56 | $phpStanStubsDir = dirname(Helpers::classLikePath(Php8StubsMap::class));
57 | foreach ((new Php8StubsMap(PHP_VERSION_ID))->classes as $class => $path) {
58 | $stubsMap[$class] = "$phpStanStubsDir/$path";
59 | }
60 |
61 | return $stubsMap;
62 | }
63 |
64 |
65 | protected static function createComposerClassLoader(OutputStyle $output, string $projectDir): ClassLoader
66 | {
67 | $loader = new ClassLoader();
68 | $composerJsonPath = "$projectDir/composer.json";
69 |
70 | if (!is_file($composerJsonPath)) {
71 | $output->warning(implode("\n", [
72 | "Unable to use Composer autoloader for finding dependencies because file",
73 | "$composerJsonPath does not exist. Use --working-dir to specify directory where composer.json is located",
74 | ]));
75 |
76 | return $loader;
77 | }
78 |
79 | $composerJson = Json::decode(FileSystem::read($composerJsonPath), forceArrays: true);
80 | $vendorDir = FileSystem::joinPaths($projectDir, $composerJson['config']['vendor-dir'] ?? 'vendor');
81 |
82 | if (!is_dir($vendorDir)) {
83 | $output->warning(implode("\n", [
84 | "Unable to use Composer autoloader for finding dependencies because directory",
85 | "$vendorDir does not exist. Run composer install to install dependencies.",
86 | ]));
87 |
88 | return $loader;
89 | }
90 |
91 | $output->text("Using Composer autoloader for finding dependencies in $vendorDir.\n");
92 | $loader->addClassMap(require "$vendorDir/composer/autoload_classmap.php");
93 |
94 | foreach (require "$vendorDir/composer/autoload_namespaces.php" as $prefix => $paths) {
95 | $loader->set($prefix, $paths);
96 | }
97 |
98 | foreach (require "$vendorDir/composer/autoload_psr4.php" as $prefix => $paths) {
99 | $loader->setPsr4($prefix, $paths);
100 | }
101 |
102 | return $loader;
103 | }
104 |
105 |
106 | public function locate(ClassLikeReferenceInfo $name): ?string
107 | {
108 | return $this->classLoader->findFile($name->full) ?: $this->stubsMap[$name->fullLower] ?? null;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Renderer.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen;
4 |
5 | use ApiGen\Index\Index;
6 | use Symfony\Component\Console\Helper\ProgressBar;
7 |
8 |
9 | interface Renderer
10 | {
11 | public function render(ProgressBar $progressBar, Index $index): void;
12 | }
13 |
--------------------------------------------------------------------------------
/src/Renderer/Filter.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer;
4 |
5 | use ApiGen\Index\FileIndex;
6 | use ApiGen\Index\NamespaceIndex;
7 | use ApiGen\Info\ClassLikeInfo;
8 | use ApiGen\Info\FunctionInfo;
9 | use ApiGen\Info\MissingInfo;
10 |
11 |
12 | class Filter
13 | {
14 | public function filterTreePage(): bool
15 | {
16 | return true;
17 | }
18 |
19 |
20 | public function filterSitemapPage(): bool
21 | {
22 | return true;
23 | }
24 |
25 |
26 | public function filterNamespacePage(NamespaceIndex $namespace): bool
27 | {
28 | return true;
29 | }
30 |
31 |
32 | public function filterClassLikePage(ClassLikeInfo $classLike): bool
33 | {
34 | return !$classLike instanceof MissingInfo;
35 | }
36 |
37 |
38 | public function filterFunctionPage(FunctionInfo $function): bool
39 | {
40 | return true;
41 | }
42 |
43 |
44 | public function filterSourcePage(FileIndex $file): bool
45 | {
46 | return $file->primary;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteCascadingLoader.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use Latte;
6 |
7 | use function is_file;
8 |
9 |
10 | class LatteCascadingLoader implements Latte\Loader
11 | {
12 | /**
13 | * @param string[] $baseDirs
14 | */
15 | public function __construct(
16 | protected array $baseDirs,
17 | protected Latte\Loader $inner = new Latte\Loaders\FileLoader(),
18 | ) {
19 | }
20 |
21 |
22 | public function getContent(string $name): string
23 | {
24 | foreach ($this->baseDirs as $baseDir) {
25 | $path = $baseDir . '/' . $name;
26 |
27 | if (is_file($path)) {
28 | return $this->inner->getContent($path);
29 | }
30 | }
31 |
32 | throw new Latte\RuntimeException("Missing template file '$name'.");
33 | }
34 |
35 |
36 | public function isExpired(string $name, int $time): bool
37 | {
38 | foreach ($this->baseDirs as $baseDir) {
39 | $path = $baseDir . '/' . $name;
40 |
41 | if (is_file($path)) {
42 | return $this->inner->isExpired($path, $time);
43 | }
44 | }
45 |
46 | throw new Latte\RuntimeException("Missing template file '$name'.");
47 | }
48 |
49 |
50 | public function getReferredName(string $name, string $referringName): string
51 | {
52 | return $this->inner->getReferredName($name, $referringName);
53 | }
54 |
55 |
56 | public function getUniqueId(string $name): string
57 | {
58 | foreach ($this->baseDirs as $baseDir) {
59 | $path = $baseDir . '/' . $name;
60 |
61 | if (is_file($path)) {
62 | return $this->inner->getUniqueId($path);
63 | }
64 | }
65 |
66 | throw new Latte\RuntimeException("Missing template file '$name'.");
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteEngineFactory.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use Latte;
6 | use Throwable;
7 |
8 | use function array_filter;
9 |
10 |
11 | class LatteEngineFactory
12 | {
13 | public function __construct(
14 | protected LatteExtension $extension,
15 | protected ?string $tempDir,
16 | protected ?string $themeDir,
17 | ) {
18 | }
19 |
20 |
21 | public function create(): Latte\Engine
22 | {
23 | $latte = new Latte\Engine();
24 | $latte->setStrictTypes();
25 | $latte->setExceptionHandler(fn(Throwable $e) => throw $e);
26 | $latte->setTempDirectory($this->tempDir);
27 | $latte->setLoader(new LatteCascadingLoader(array_filter([$this->themeDir, __DIR__ . '/Template'])));
28 | $latte->addExtension($this->extension);
29 |
30 | return $latte;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteExtension.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use ApiGen\Renderer\Filter;
6 | use ApiGen\Renderer\UrlGenerator;
7 | use Latte;
8 |
9 |
10 | class LatteExtension extends Latte\Extension
11 | {
12 | public function __construct(
13 | protected LatteFunctions $functions,
14 | protected Filter $filter,
15 | protected UrlGenerator $url,
16 | ) {
17 | }
18 |
19 |
20 | public function getFunctions(): array
21 | {
22 | return [
23 | 'isClass' => $this->functions->isClass(...),
24 | 'isInterface' => $this->functions->isInterface(...),
25 | 'isTrait' => $this->functions->isTrait(...),
26 | 'isEnum' => $this->functions->isEnum(...),
27 |
28 | 'textWidth' => $this->functions->textWidth(...),
29 | 'htmlWidth' => $this->functions->htmlWidth(...),
30 | 'highlight' => $this->functions->highlight(...),
31 | 'shortDescription' => $this->functions->shortDescription(...),
32 | 'longDescription' => $this->functions->longDescription(...),
33 |
34 | 'treePageExists' => $this->filter->filterTreePage(...),
35 | 'namespacePageExists' => $this->filter->filterNamespacePage(...),
36 | 'classLikePageExists' => $this->filter->filterClassLikePage(...),
37 | 'functionPageExists' => $this->filter->filterFunctionPage(...),
38 | 'sourcePageExists' => $this->filter->filterSourcePage(...),
39 |
40 | 'elementName' => $this->functions->elementName(...),
41 | 'elementShortDescription' => $this->functions->elementShortDescription(...),
42 | 'elementPageExists' => $this->functions->elementPageExists(...),
43 | 'elementUrl' => $this->functions->elementUrl(...),
44 |
45 | 'relativePath' => $this->url->getRelativePath(...),
46 | 'assetUrl' => $this->url->getAssetUrl(...),
47 | 'indexUrl' => $this->url->getIndexUrl(...),
48 | 'treeUrl' => $this->url->getTreeUrl(...),
49 | 'namespaceUrl' => $this->url->getNamespaceUrl(...),
50 | 'classLikeUrl' => $this->url->getClassLikeUrl(...),
51 | 'classLikeSourceUrl' => $this->url->getClassLikeSourceUrl(...),
52 | 'memberUrl' => $this->url->getMemberUrl(...),
53 | 'memberAnchor' => $this->url->getMemberAnchor(...),
54 | 'memberSourceUrl' => $this->url->getMemberSourceUrl(...),
55 | 'aliasUrl' => $this->url->getAliasUrl(...),
56 | 'aliasAnchor' => $this->url->getAliasAnchor(...),
57 | 'aliasSourceUrl' => $this->url->getAliasSourceUrl(...),
58 | 'functionUrl' => $this->url->getFunctionUrl(...),
59 | 'functionSourceUrl' => $this->url->getFunctionSourceUrl(...),
60 | 'parameterAnchor' => $this->url->getParameterAnchor(...),
61 | 'sourceUrl' => $this->url->getSourceUrl(...),
62 | ];
63 | }
64 |
65 |
66 | public function getTags(): array
67 | {
68 | return [
69 | 'pre' => LattePreNode::create(...),
70 | ];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LattePreNode.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use Generator;
6 | use Latte;
7 | use Latte\Compiler\Node;
8 | use Latte\Compiler\Nodes\AreaNode;
9 | use Latte\Compiler\Nodes\TextNode;
10 | use Latte\Compiler\NodeTraverser;
11 | use Latte\Compiler\PrintContext;
12 | use Latte\Compiler\Tag;
13 | use Latte\Compiler\TemplateParser;
14 | use Nette\Utils\Strings;
15 |
16 | use function assert;
17 |
18 |
19 | class LattePreNode extends Latte\Compiler\Nodes\StatementNode
20 | {
21 | public function __construct(
22 | public AreaNode $content,
23 | ) {
24 | }
25 |
26 |
27 | /**
28 | * @return Generator<int, null, array{AreaNode, ?Tag}, self>
29 | */
30 | public static function create(Tag $tag, TemplateParser $parser): Generator
31 | {
32 | [$content] = yield;
33 | $transformed = (new NodeTraverser)->traverse($content, self::removeWhitespace(...));
34 | assert($transformed instanceof AreaNode);
35 |
36 | return new self($transformed);
37 | }
38 |
39 |
40 | public function print(PrintContext $context): string
41 | {
42 | return $this->content->print($context);
43 | }
44 |
45 |
46 | public function &getIterator(): Generator
47 | {
48 | yield $this->content;
49 | }
50 |
51 |
52 | protected static function removeWhitespace(Node $node): Node
53 | {
54 | return $node instanceof TextNode ? new TextNode(Strings::replace($node->content, '#[\n\t]++#')) : $node;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTask.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use ApiGen\Task\Task;
6 |
7 |
8 | class LatteRenderTask implements Task
9 | {
10 | public function __construct(
11 | public LatteRenderTaskType $type,
12 | public string $key = '',
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskContext.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Renderer\Latte\Template\ConfigParameters;
7 |
8 |
9 | class LatteRenderTaskContext
10 | {
11 | public function __construct(
12 | public Index $index,
13 | public ConfigParameters $config,
14 | ) {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskHandler.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Renderer\Filter;
7 | use ApiGen\Renderer\Latte\Template\ClassLikeTemplate;
8 | use ApiGen\Renderer\Latte\Template\ConfigParameters;
9 | use ApiGen\Renderer\Latte\Template\FunctionTemplate;
10 | use ApiGen\Renderer\Latte\Template\IndexTemplate;
11 | use ApiGen\Renderer\Latte\Template\LayoutParameters;
12 | use ApiGen\Renderer\Latte\Template\NamespaceTemplate;
13 | use ApiGen\Renderer\Latte\Template\SitemapTemplate;
14 | use ApiGen\Renderer\Latte\Template\SourceTemplate;
15 | use ApiGen\Renderer\Latte\Template\TreeTemplate;
16 | use ApiGen\Renderer\UrlGenerator;
17 | use ApiGen\Task\Task;
18 | use ApiGen\Task\TaskHandler;
19 | use Latte;
20 | use Nette\Utils\FileSystem;
21 | use Nette\Utils\Json;
22 | use ReflectionClass;
23 |
24 | use function array_key_first;
25 | use function assert;
26 | use function basename;
27 | use function lcfirst;
28 | use function sprintf;
29 | use function substr;
30 |
31 |
32 | /**
33 | * @implements TaskHandler<LatteRenderTask, string>
34 | */
35 | class LatteRenderTaskHandler implements TaskHandler
36 | {
37 | protected Index $index;
38 |
39 | protected ConfigParameters $config;
40 |
41 | public function __construct(
42 | protected Latte\Engine $latte,
43 | protected UrlGenerator $urlGenerator,
44 | protected Filter $filter,
45 | protected string $outputDir,
46 | mixed $context,
47 | ) {
48 | assert($context instanceof LatteRenderTaskContext);
49 | $this->index = $context->index;
50 | $this->config = $context->config;
51 | }
52 |
53 |
54 | /**
55 | * @param LatteRenderTask $task
56 | */
57 | public function handle(Task $task): string
58 | {
59 | return match ($task->type) {
60 | LatteRenderTaskType::Asset => $this->copyAsset($task->key),
61 |
62 | LatteRenderTaskType::ElementsJs => $this->renderElementsJs(),
63 | LatteRenderTaskType::Index => $this->renderIndex(),
64 | LatteRenderTaskType::Tree => $this->renderTree(),
65 | LatteRenderTaskType::Sitemap => $this->renderSitemap(),
66 |
67 | LatteRenderTaskType::Namespace => $this->renderNamespace($task->key),
68 | LatteRenderTaskType::ClassLike => $this->renderClassLike($task->key),
69 | LatteRenderTaskType::Function => $this->renderFunction($task->key),
70 | LatteRenderTaskType::Source => $this->renderSource($task->key),
71 | };
72 | }
73 |
74 |
75 | protected function copyAsset(string $key): string
76 | {
77 | $assetPath = $this->urlGenerator->getAssetPath(basename($key));
78 | FileSystem::copy($key, "$this->outputDir/$assetPath");
79 |
80 | return $assetPath;
81 | }
82 |
83 |
84 | protected function renderElementsJs(): string
85 | {
86 | $elements = [];
87 |
88 | foreach ($this->index->namespace as $namespace) {
89 | if ($this->filter->filterNamespacePage($namespace)) {
90 | $elements['namespace'][] = [$namespace->name->full, $this->urlGenerator->getNamespaceUrl($namespace)];
91 | }
92 | }
93 |
94 | foreach ($this->index->classLike as $classLike) {
95 | if (!$this->filter->filterClassLikePage($classLike)) {
96 | continue;
97 | }
98 |
99 | $members = [];
100 |
101 | foreach ($classLike->constants as $constant) {
102 | $members['constant'][] = [$constant->name, $this->urlGenerator->getMemberAnchor($constant)];
103 | }
104 |
105 | foreach ($classLike->properties as $property) {
106 | $members['property'][] = [$property->name, $this->urlGenerator->getMemberAnchor($property)];
107 | }
108 |
109 | foreach ($classLike->methods as $method) {
110 | $members['method'][] = [$method->name, $this->urlGenerator->getMemberAnchor($method)];
111 | }
112 |
113 | $elements['classLike'][] = [$classLike->name->full, $this->urlGenerator->getClassLikeUrl($classLike), $members];
114 | }
115 |
116 | foreach ($this->index->function as $function) {
117 | if ($this->filter->filterFunctionPage($function)) {
118 | $elements['function'][] = [$function->name->full, $this->urlGenerator->getFunctionUrl($function)];
119 | }
120 | }
121 |
122 | $js = sprintf('window.ApiGen?.resolveElements(%s)', Json::encode($elements));
123 | $assetPath = $this->urlGenerator->getAssetPath('elements.js');
124 | FileSystem::write("$this->outputDir/$assetPath", $js);
125 |
126 | return $assetPath;
127 | }
128 |
129 |
130 | protected function renderIndex(): string
131 | {
132 | return $this->renderTemplate($this->urlGenerator->getIndexPath(), new IndexTemplate(
133 | index: $this->index,
134 | config: $this->config,
135 | layout: new LayoutParameters(activePage: 'index'),
136 | ));
137 | }
138 |
139 |
140 | protected function renderTree(): string
141 | {
142 | return $this->renderTemplate($this->urlGenerator->getTreePath(), new TreeTemplate(
143 | index: $this->index,
144 | config: $this->config,
145 | layout: new LayoutParameters(activePage: 'tree'),
146 | ));
147 | }
148 |
149 |
150 | protected function renderSitemap(): string
151 | {
152 | return $this->renderTemplate($this->urlGenerator->getSitemapPath(), new SitemapTemplate(
153 | index: $this->index,
154 | config: $this->config,
155 | ));
156 | }
157 |
158 |
159 | protected function renderNamespace(string $key): string
160 | {
161 | $info = $this->index->namespace[$key];
162 |
163 | return $this->renderTemplate($this->urlGenerator->getNamespacePath($info), new NamespaceTemplate(
164 | index: $this->index,
165 | config: $this->config,
166 | layout: new LayoutParameters('namespace', activeNamespace: $info, noindex: !$info->primary),
167 | namespace: $info,
168 | ));
169 | }
170 |
171 |
172 | protected function renderClassLike(string $key): string
173 | {
174 | $info = $this->index->classLike[$key];
175 | $activeNamespace = $this->index->namespace[$info->name->namespaceLower];
176 |
177 | return $this->renderTemplate($this->urlGenerator->getClassLikePath($info), new ClassLikeTemplate(
178 | index: $this->index,
179 | config: $this->config,
180 | layout: new LayoutParameters('classLike', $activeNamespace, $info, noindex: !$info->primary),
181 | classLike: $info,
182 | ));
183 | }
184 |
185 |
186 | protected function renderFunction(string $key): string
187 | {
188 | $info = $this->index->function[$key];
189 | $activeNamespace = $this->index->namespace[$info->name->namespaceLower];
190 |
191 | return $this->renderTemplate($this->urlGenerator->getFunctionPath($info), new FunctionTemplate(
192 | index: $this->index,
193 | config: $this->config,
194 | layout: new LayoutParameters('function', $activeNamespace, $info, noindex: !$info->primary),
195 | function: $info,
196 | ));
197 | }
198 |
199 |
200 | protected function renderSource(string $key): string
201 | {
202 | $info = $this->index->files[$key];
203 | $activeElement = $info->classLike[array_key_first($info->classLike)] ?? $info->function[array_key_first($info->function)] ?? null;
204 | $activeNamespace = $activeElement ? $this->index->namespace[$activeElement->name->namespaceLower] : null;
205 |
206 | return $this->renderTemplate($this->urlGenerator->getSourcePath($info->path), new SourceTemplate(
207 | index: $this->index,
208 | config: $this->config,
209 | layout: new LayoutParameters('source', $activeNamespace, $activeElement, noindex: !$info->primary),
210 | path: $info->path,
211 | ));
212 | }
213 |
214 |
215 | protected function renderTemplate(string $outputPath, object $template): string
216 | {
217 | $className = (new ReflectionClass($template))->getShortName();
218 | $lattePath = 'pages/' . lcfirst(substr($className, 0, -8)) . '.latte';
219 | FileSystem::write("$this->outputDir/$outputPath", $this->latte->renderToString($lattePath, $template));
220 |
221 | return $outputPath;
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskHandlerFactory.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use ApiGen\Task\TaskHandlerFactory;
6 |
7 |
8 | /**
9 | * @extends TaskHandlerFactory<LatteRenderTaskContext, LatteRenderTaskHandler>
10 | */
11 | interface LatteRenderTaskHandlerFactory extends TaskHandlerFactory
12 | {
13 | /**
14 | * @param LatteRenderTaskContext $context
15 | */
16 | public function create(mixed $context): LatteRenderTaskHandler;
17 | }
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderTaskType.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 |
6 | enum LatteRenderTaskType
7 | {
8 | case Asset;
9 | case ElementsJs;
10 | case Index;
11 | case Tree;
12 | case Sitemap;
13 | case Namespace;
14 | case ClassLike;
15 | case Function;
16 | case Source;
17 | }
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/LatteRenderer.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Renderer;
7 | use ApiGen\Renderer\Filter;
8 | use ApiGen\Renderer\Latte\Template\ConfigParameters;
9 | use ApiGen\Scheduler;
10 | use ApiGen\Scheduler\SchedulerFactory;
11 | use Nette\Utils\FileSystem;
12 | use Nette\Utils\Finder;
13 | use Symfony\Component\Console\Helper\ProgressBar;
14 |
15 |
16 | class LatteRenderer implements Renderer
17 | {
18 | public function __construct(
19 | protected SchedulerFactory $schedulerFactory,
20 | protected Filter $filter,
21 | protected string $title,
22 | protected string $version,
23 | protected string $outputDir,
24 | ) {
25 | }
26 |
27 |
28 | public function render(ProgressBar $progressBar, Index $index): void
29 | {
30 | $this->prepareOutputDir();
31 | $scheduler = $this->createScheduler($index);
32 |
33 | foreach ($this->getRenderTasks($index) as $task) {
34 | $scheduler->schedule($task);
35 | $progressBar->setMaxSteps($progressBar->getMaxSteps() + 1);
36 | }
37 |
38 | foreach ($scheduler->process() as $path) {
39 | $progressBar->setMessage($path);
40 | $progressBar->advance();
41 | }
42 | }
43 |
44 |
45 | protected function prepareOutputDir(): void
46 | {
47 | FileSystem::delete($this->outputDir);
48 | FileSystem::createDir($this->outputDir);
49 | }
50 |
51 |
52 | /**
53 | * @return Scheduler<LatteRenderTask, string>
54 | */
55 | protected function createScheduler(Index $index): Scheduler
56 | {
57 | $context = new LatteRenderTaskContext($index, new ConfigParameters($this->title, $this->version));
58 | return $this->schedulerFactory->create(LatteRenderTaskHandlerFactory::class, $context);
59 | }
60 |
61 |
62 | /**
63 | * @return iterable<LatteRenderTask>
64 | */
65 | protected function getRenderTasks(Index $index): iterable
66 | {
67 | yield from $this->getAssetsCopyTasks();
68 | yield from $this->getUnitRenderTasks();
69 | yield from $this->getNamespaceRenderTasks($index);
70 | yield from $this->getClassLikeRenderTasks($index);
71 | yield from $this->getFunctionRenderTasks($index);
72 | yield from $this->getSourceRenderTasks($index);
73 | }
74 |
75 |
76 | /**
77 | * @return iterable<LatteRenderTask>
78 | */
79 | protected function getAssetsCopyTasks(): iterable
80 | {
81 | foreach (Finder::findFiles(__DIR__ . '/Template/assets/*') as $asset) {
82 | yield new LatteRenderTask(LatteRenderTaskType::Asset, $asset->getPathname());
83 | }
84 | }
85 |
86 |
87 | /**
88 | * @return iterable<LatteRenderTask>
89 | */
90 | protected function getUnitRenderTasks(): iterable
91 | {
92 | yield new LatteRenderTask(LatteRenderTaskType::ElementsJs);
93 | yield new LatteRenderTask(LatteRenderTaskType::Index);
94 |
95 | if ($this->filter->filterTreePage()) {
96 | yield new LatteRenderTask(LatteRenderTaskType::Tree);
97 | }
98 |
99 | if ($this->filter->filterSitemapPage()) {
100 | yield new LatteRenderTask(LatteRenderTaskType::Sitemap);
101 | }
102 | }
103 |
104 |
105 | /**
106 | * @return iterable<LatteRenderTask>
107 | */
108 | protected function getNamespaceRenderTasks(Index $index): iterable
109 | {
110 | foreach ($index->namespace as $namespaceKey => $namespace) {
111 | if ($this->filter->filterNamespacePage($namespace)) {
112 | yield new LatteRenderTask(LatteRenderTaskType::Namespace, $namespaceKey);
113 | }
114 | }
115 | }
116 |
117 |
118 | /**
119 | * @return iterable<LatteRenderTask>
120 | */
121 | protected function getClassLikeRenderTasks(Index $index): iterable
122 | {
123 | foreach ($index->classLike as $classLikeKey => $classLike) {
124 | if ($this->filter->filterClassLikePage($classLike)) {
125 | yield new LatteRenderTask(LatteRenderTaskType::ClassLike, $classLikeKey);
126 | }
127 | }
128 | }
129 |
130 |
131 | /**
132 | * @return iterable<LatteRenderTask>
133 | */
134 | protected function getFunctionRenderTasks(Index $index): iterable
135 | {
136 | foreach ($index->function as $functionKey => $function) {
137 | if ($this->filter->filterFunctionPage($function)) {
138 | yield new LatteRenderTask(LatteRenderTaskType::Function, $functionKey);
139 | }
140 | }
141 | }
142 |
143 |
144 | /**
145 | * @return iterable<LatteRenderTask>
146 | */
147 | protected function getSourceRenderTasks(Index $index): iterable
148 | {
149 | foreach ($index->files as $fileKey => $file) {
150 | if ($this->filter->filterSourcePage($file)) {
151 | yield new LatteRenderTask(LatteRenderTaskType::Source, $fileKey);
152 | }
153 | }
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/ClassLikeTemplate.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Info\ClassLikeInfo;
7 |
8 |
9 | class ClassLikeTemplate
10 | {
11 | public function __construct(
12 | public Index $index,
13 | public ConfigParameters $config,
14 | public LayoutParameters $layout,
15 | public ClassLikeInfo $classLike,
16 | ) {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/ConfigParameters.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 |
6 | class ConfigParameters
7 | {
8 | public function __construct(
9 | public string $title,
10 | public string $version,
11 | ) {
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/FunctionTemplate.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Info\FunctionInfo;
7 |
8 |
9 | class FunctionTemplate
10 | {
11 | public function __construct(
12 | public Index $index,
13 | public ConfigParameters $config,
14 | public LayoutParameters $layout,
15 | public FunctionInfo $function,
16 | ) {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/IndexTemplate.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\Index;
6 |
7 |
8 | class IndexTemplate
9 | {
10 | public function __construct(
11 | public Index $index,
12 | public ConfigParameters $config,
13 | public LayoutParameters $layout,
14 | ) {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/LayoutParameters.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\NamespaceIndex;
6 | use ApiGen\Info\ElementInfo;
7 |
8 |
9 | class LayoutParameters
10 | {
11 | public function __construct(
12 | public string $activePage,
13 | public ?NamespaceIndex $activeNamespace = null,
14 | public ?ElementInfo $activeElement = null,
15 | public bool $noindex = false,
16 | ) {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/NamespaceTemplate.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\Index;
6 | use ApiGen\Index\NamespaceIndex;
7 |
8 |
9 | class NamespaceTemplate
10 | {
11 | public function __construct(
12 | public Index $index,
13 | public ConfigParameters $config,
14 | public LayoutParameters $layout,
15 | public NamespaceIndex $namespace,
16 | ) {
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/SitemapTemplate.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\Index;
6 |
7 |
8 | class SitemapTemplate
9 | {
10 | public function __construct(
11 | public Index $index,
12 | public ConfigParameters $config,
13 | ) {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/SourceTemplate.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\Index;
6 |
7 |
8 | class SourceTemplate
9 | {
10 | public function __construct(
11 | public Index $index,
12 | public ConfigParameters $config,
13 | public LayoutParameters $layout,
14 | public string $path,
15 | ) {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/TreeTemplate.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer\Latte\Template;
4 |
5 | use ApiGen\Index\Index;
6 |
7 |
8 | class TreeTemplate
9 | {
10 | public function __construct(
11 | public Index $index,
12 | public ConfigParameters $config,
13 | public LayoutParameters $layout,
14 | ) {
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/@index.latte:
--------------------------------------------------------------------------------
1 | {import 'alias.latte'}
2 | {import 'aliasLink.latte'}
3 | {import 'aliasSummary.latte'}
4 | {import 'autoBreakingLine.latte'}
5 | {import 'classLikeDescription.latte'}
6 | {import 'classLikeKind.latte'}
7 | {import 'classLikeLink.latte'}
8 | {import 'classLikeLinks.latte'}
9 | {import 'classLikeReference.latte'}
10 | {import 'classLikeSignature.latte'}
11 | {import 'classLikeSignatureTable.latte'}
12 | {import 'classTree.latte'}
13 | {import 'constant.latte'}
14 | {import 'constantInherited.latte'}
15 | {import 'constantInheritedSummary.latte'}
16 | {import 'constantSummary.latte'}
17 | {import 'description.latte'}
18 | {import 'elementSummary.latte'}
19 | {import 'elementSummaryGroup.latte'}
20 | {import 'enumCase.latte'}
21 | {import 'enumCaseSummary.latte'}
22 | {import 'expr.latte'}
23 | {import 'functionDescription.latte'}
24 | {import 'functionLinks.latte'}
25 | {import 'genericParameters.latte'}
26 | {import 'head.latte'}
27 | {import 'layout.latte'}
28 | {import 'member.latte'}
29 | {import 'memberDescription.latte'}
30 | {import 'memberLink.latte'}
31 | {import 'memberSourceLink.latte'}
32 | {import 'memberVisibility.latte'}
33 | {import 'menu.latte'}
34 | {import 'menuElements.latte'}
35 | {import 'menuGroup.latte'}
36 | {import 'method.latte'}
37 | {import 'methodInherited.latte'}
38 | {import 'methodInheritedSummary.latte'}
39 | {import 'methodRelation.latte'}
40 | {import 'methodSignature.latte'}
41 | {import 'methodSummary.latte'}
42 | {import 'methodUsed.latte'}
43 | {import 'methodUsedSummary.latte'}
44 | {import 'namespaceLinks.latte'}
45 | {import 'navbar.latte'}
46 | {import 'parameter.latte'}
47 | {import 'property.latte'}
48 | {import 'propertyInherited.latte'}
49 | {import 'propertyInheritedSummary.latte'}
50 | {import 'propertySummary.latte'}
51 | {import 'propertyUsed.latte'}
52 | {import 'propertyUsedSummary.latte'}
53 | {import 'search.latte'}
54 | {import 'source.latte'}
55 | {import 'type.latte'}
56 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/alias.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define alias, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\AliasInfo $alias}
6 | <tr data-order="{$alias->name}" id="{aliasAnchor($alias)}">
7 | <td class="table-cell table-shrink">
8 | <code class="nowrap">
9 | <a n:tag-if="$classLike->file && sourcePageExists($index->files[$classLike->file])" href="{aliasSourceUrl($classLike, $alias)}" title="Go to source code">
10 | {$alias->name}
11 | </a>
12 | </code>
13 | </td>
14 |
15 | <td class="table-cell">
16 | <code class="nowrap">{include type, type: $alias->type, scope: $classLike}</code>
17 | </td>
18 |
19 | <td class="table-anchor">
20 | <a href="#{aliasAnchor($alias)}">#</a>
21 | </td>
22 | </tr>
23 | {/define}
24 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/aliasLink.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define aliasLink, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\AliasInfo $alias, ?string $text = null}
6 | {pre}
7 | <a n:tag-if="classLikePageExists($classLike)" href="{aliasUrl($classLike, $alias)}">
8 | <span title="{$alias->type}">{if $text}{$text}{else}{$alias->name}{/if}</span>
9 | </a>
10 | {/pre}
11 | {/define}
12 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/aliasSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define aliasSummary, ApiGen\Info\ClassLikeInfo $classLike}
6 | {if $classLike->aliases}
7 | <table class="table" id="aliases">
8 | <tr>
9 | <th class="table-heading sortable" colspan="3" title="Switch between natural and alphabetical order">Type aliases</th>
10 | </tr>
11 |
12 | {foreach $classLike->aliases as $alias}
13 | {include alias, classLike: $classLike, alias: $alias}
14 | {/foreach}
15 | </table>
16 | {/if}
17 | {/define}
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/autoBreakingLine.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define autoBreakingLine, iterable $items, int $maxWidth, string $indent = ''}
6 | {pre}
7 | {var int $width = textWidth($indent)}
8 | {var Latte\Runtime\HtmlStringable[] $segments = []}
9 |
10 | {capture $before}{include before}{/capture}
11 | {var $width = $width + htmlWidth($before)}
12 |
13 | {foreach $items as $item}
14 | {capture $segment}{include item, item: $item}{sep}{include sep}{/sep}{/capture}
15 | {var $width = $width + htmlWidth($segment) + ($iterator->last ? 0 : 1)}
16 | {var $segments[] = $segment}
17 | {/foreach}
18 |
19 | {capture $after}{include after}{/capture}
20 | {var $width = $width + htmlWidth($after)}
21 |
22 | {if $width <= $maxWidth}
23 | {$before}
24 | {foreach $segments as $segment}
25 | {$segment}
26 | {sep} {/sep}
27 | {/foreach}
28 | {$after}
29 | {else}
30 | {$before}{="\n$indent\t"}
31 | {foreach $segments as $segment}
32 | {$segment}
33 | {last}{include sep}{/last}
34 | {sep}{="\n$indent\t"}{/sep}
35 | {/foreach}
36 | {="\n$indent"}{$after}
37 | {/if}
38 | {/pre}
39 | {/define}
40 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeDescription.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classLikeDescription, ApiGen\Info\ClassLikeInfo $classLike}
6 | {if $classLike->description}
7 | <div class="classLikeDescription">
8 | {include description, $classLike->description, $classLike}
9 | </div>
10 | {/if}
11 | {/define}
12 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeKind.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classLikeKind, ApiGen\Info\ClassLikeInfo $classLike}
6 | {pre}
7 | {if isInterface($classLike)}Interface
8 | {elseif isTrait($classLike)}Trait
9 | {elseif isClass($classLike)}Class
10 | {elseif isEnum($classLike)}Enum
11 | {else}{/if}
12 | {/pre}
13 | {/define}
14 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeLink.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classLikeLink, ApiGen\Info\ClassLikeInfo $classLike, bool $short = false, ?string $text = null}
6 | {pre}
7 | {var bool $active = $classLike === $layout->activeElement}
8 | <a n:tag-if="!$active && classLikePageExists($classLike)" href="{classLikeUrl($classLike)}">
9 | <b n:tag-if="$active">
10 | <span n:class="$classLike->isDeprecated() ? deprecated, $active ? active" n:attr="title: ($short || $text) ? $classLike->name->full">
11 | {if $text}{$text}{elseif $short}{$classLike->name->short}{else}{$classLike->name->full}{/if}
12 | </span>
13 | </b>
14 | </a>
15 | {/pre}
16 | {/define}
17 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeLinks.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classLikeLinks, ApiGen\Info\ClassLikeInfo $classLike}
6 | {pre}
7 | {if $classLike->name->namespaceLower}{include namespaceLinks, $index->namespace[$classLike->name->namespaceLower]}\{/if}
8 | <a href="{classLikeUrl($classLike)}">{$classLike->name->short}</a>
9 | {/pre}
10 | {/define}
11 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeReference.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classLikeReference, ApiGen\Info\ClassLikeReferenceInfo $ref, bool $short = true}
6 | {pre}
7 | {include classLikeLink, $index->classLike[$ref->fullLower], short: $short}
8 | {if $ref->genericArgs}
9 | <
10 | {foreach $ref->genericArgs as $arg}
11 | {include type, $arg}{sep}, {/sep}
12 | {/foreach}
13 | >
14 | {/if}
15 | {/pre}
16 | {/define}
17 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeSignature.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classLikeSignature, ApiGen\Info\ClassLikeInfo $classLike}
6 | {include classLikeLink, $classLike}
7 |
8 | {if isInterface($classLike) && $classLike->extends}
9 | extends
10 | {foreach $classLike->extends as $ref}
11 | {include classLikeReference, $ref}{sep}, {/sep}
12 | {/foreach}
13 | {/if}
14 |
15 | {if (isClass($classLike) || isEnum($classLike)) && $classLike->implements}
16 | implements
17 | {foreach $classLike->implements as $ref}
18 | {include classLikeReference, $ref}{sep}, {/sep}
19 | {/foreach}
20 | {/if}
21 |
22 | {if (isClass($classLike) || isEnum($classLike)) && $classLike->uses}
23 | uses
24 | {foreach $classLike->uses as $ref}
25 | {include classLikeReference, $ref}{sep}, {/sep}
26 | {/foreach}
27 | {/if}
28 | {/define}
29 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classLikeSignatureTable.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classLikeSignatureTable, ApiGen\Info\ClassLikeInfo $classLike}
6 | <table class="classLikeSignatureTable">
7 | {if isClass($classLike)}
8 | {if $classLike->abstract}<tr><th><code>abstract</code></th></tr>{/if}
9 | {if $classLike->final}<tr><th><code>final</code></th></tr>{/if}
10 | {if $classLike->readOnly}<tr><th><code>readonly</code></th></tr>{/if}
11 | {include classLikeSignatureTableRow, 'extends', $classLike->extends ? [$classLike->extends] : []}
12 | {include classLikeSignatureTableRow, 'implements', $classLike->implements}
13 | {include classLikeSignatureTableRow, 'uses', $classLike->uses}
14 |
15 | {elseif isInterface($classLike)}
16 | {include classLikeSignatureTableRow, 'extends', $classLike->extends}
17 |
18 | {elseif isTrait($classLike)}
19 | {include classLikeSignatureTableRow, 'uses', $classLike->uses}
20 |
21 | {elseif isEnum($classLike)}
22 | {include classLikeSignatureTableRow, 'implements', $classLike->implements}
23 | {include classLikeSignatureTableRow, 'uses', $classLike->uses}
24 | {/if}
25 | </table>
26 | {/define}
27 |
28 | {define classLikeSignatureTableRow, string $label, ApiGen\Info\ClassLikeReferenceInfo[] $refs}
29 | {if $refs}
30 | <tr>
31 | <th><code>{$label}</code></th>
32 | <td>
33 | <code n:pre>
34 | {foreach $refs as $ref}
35 | {include classLikeReference, $ref}{sep}, {/sep}
36 | {/foreach}
37 | </code>
38 | </td>
39 | </tr>
40 | {/if}
41 | {/define}
42 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/classTree.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define classTree, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\ClassLikeInfo[][] $children}
6 | {if isClass($classLike) && $classLike->extends}
7 | {include this, classLike: $index->classLike[$classLike->extends->fullLower], children: [$classLike->extends->fullLower => [$classLike]] + $children}
8 | {else}
9 | {include classTreeList, classLikes: [$classLike], children: $children}
10 | {/if}
11 | {/define}
12 |
13 | {define classTreeList, ApiGen\Info\ClassLikeInfo[] $classLikes, ApiGen\Info\ClassLikeInfo[][] $children}
14 | <ul class="classTree">
15 | {foreach $classLikes as $classLike}
16 | {include classTreeItem, classLike: $classLike, children: $children}
17 | {/foreach}
18 | </ul>
19 | {/define}
20 |
21 | {define local classTreeItem, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\ClassLikeInfo[][] $children}
22 | <li>
23 | {include classLikeSignature, $classLike}
24 | {if !empty($children[$classLike->name->fullLower])}
25 | {include classTreeList, classLikes: $children[$classLike->name->fullLower], children: $children}
26 | {/if}
27 | </li>
28 | {/define}
29 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constant.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define constant, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\ConstantInfo $constant}
6 | {embed member, $constant}
7 | {block cells}
8 | <td class="table-cell table-shrink">
9 | <code class="nowrap">
10 | {if $constant->final}final{/if}
11 | {include memberVisibility, $constant}
12 | </code>
13 | </td>
14 |
15 | <td class="table-cell table-shrink" n:pre>
16 | <code class="nowrap" n:if="$constant->type">{include type, $constant->type, scope: $classLike}</code>
17 | </td>
18 |
19 | <td class="table-cell">
20 | <code n:pre>
21 | {embed memberSourceLink, classLike: $classLike, member: $constant}{block content}{$constant->name}{/block}{/embed}
22 | = {include expr, expr: $constant->value, scope: $classLike}
23 | </code>
24 |
25 | {embed memberDescription, classLike: $classLike, member: $constant}
26 | {block details}{* TODO: other tags (uses, internal...) *}{/block}
27 | {/embed}
28 | </td>
29 | {/block}
30 | {/embed}
31 | {/define}
32 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constantInherited.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define constantInherited ApiGen\Info\ClassLikeInfo $from, array $overriden}
6 | {try}
7 | <table class="table">
8 | <tr>
9 | <th class="table-heading">Constants inherited from {include classLikeLink, $from}</th>
10 | </tr>
11 | <tr>
12 | <td class="table-cell">
13 | <code n:pre>
14 | {foreach $from->constants as $constantName => $constant}
15 | {skipIf isset($overriden[$constantName])}
16 | {embed memberLink, $from, $constant}{block content}<code>{$constant->name}</code>{/block}{/embed}
17 | {sep}, {/sep}
18 | {else}
19 | {rollback}
20 | {/foreach}
21 | </code>
22 | </td>
23 | </tr>
24 | </table>
25 | {/try}
26 |
27 | {include constantInheritedSummary, $from, $overriden}
28 | {/define}
29 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constantInheritedSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define constantInheritedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
6 | {if isClass($classLike)}
7 | {if $classLike->extends}
8 | {include constantInherited, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->constants}
9 | {/if}
10 |
11 | {foreach $classLike->implements as $interface}
12 | {include constantInherited, $index->classLike[$interface->fullLower], $overriden + $classLike->constants}
13 | {/foreach}
14 |
15 | {elseif isInterface($classLike)}
16 | {foreach $classLike->extends as $parent}
17 | {include constantInherited, $index->classLike[$parent->fullLower], $overriden + $classLike->constants}
18 | {/foreach}
19 | {/if}
20 | {/define}
21 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/constantSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define constantSummary, ApiGen\Info\ClassLikeInfo $classLike}
6 | {if $classLike->constants}
7 | <table class="table" id="constants">
8 | <tr>
9 | <th class="table-heading sortable" colspan="4" title="Switch between natural and alphabetical order">Constants</th>
10 | </tr>
11 |
12 | {foreach $classLike->constants as $constant}
13 | {include constant, classLike: $classLike, constant: $constant}
14 | {/foreach}
15 | </table>
16 | {/if}
17 | {/define}
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/description.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define description, string $description, ?ApiGen\Info\ClassLikeInfo $scope}
6 | <div n:if="$description" class="description">{longDescription($index, $scope, $description)}</div>
7 | {/define}
8 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/elementSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define elementSummary}
6 | <table class="table elementSummary">
7 | {include elementSummaryContent}
8 | </table>
9 | {/define}
10 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/elementSummaryGroup.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define elementSummaryGroup, string $heading, ApiGen\Info\ElementInfo[] $elements, bool $showDescription = true, bool $onlyPrimary = false}
6 | {try}
7 | <tr>
8 | <th class="table-heading" colspan="2">{$heading}</th>
9 | </tr>
10 |
11 | {foreach $elements as $element}
12 | {skipIf $onlyPrimary && !$element->primary}
13 | {skipIf !elementPageExists($element)}
14 | <tr>
15 | <td class="table-cell" colspan="{$showDescription ? 1 : 2}"><a href="{elementUrl($element)}" n:class="$element->isDeprecated() ? deprecated">{elementName($element)}</a></td>
16 | <td class="table-cell" n:if="$showDescription">{elementShortDescription($index, null, $element)}</td>
17 | </tr>
18 | {else}
19 | {rollback}
20 | {/foreach}
21 |
22 | <tr>
23 | <td class="table-spacer" colspan="2"></td>
24 | </tr>
25 | {/try}
26 | {/define}
27 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/enumCase.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define enumCase, ApiGen\Info\EnumInfo $enum, ApiGen\Info\EnumCaseInfo $enumCase}
6 | {embed member, $enumCase}
7 | {block cells}
8 | <td class="table-cell">
9 | <code n:pre>
10 | {embed memberSourceLink, classLike: $enum, member: $enumCase}{block content}{$enumCase->name}{/block}{/embed}
11 | {if $enumCase->value} = {include expr, expr: $enumCase->value, scope: $enum}{/if}
12 | </code>
13 |
14 | {embed memberDescription, classLike: $enum, member: $enumCase}
15 | {block details}{* TODO: other tags (uses, internal...) *}{/block}
16 | {/embed}
17 | </td>
18 | {/block}
19 | {/embed}
20 | {/define}
21 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/enumCaseSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define enumCaseSummary, ApiGen\Info\EnumInfo $enum}
6 | {if $enum->cases}
7 | <table class="table" id="cases">
8 | <tr>
9 | <th class="table-heading sortable" colspan="2" title="Switch between natural and alphabetical order">Cases</th>
10 | </tr>
11 |
12 | {foreach $enum->cases as $case}
13 | {include enumCase, enum: $enum, enumCase: $case}
14 | {/foreach}
15 | </table>
16 | {/if}
17 | {/define}
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/expr.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define expr, ApiGen\Info\ExprInfo $expr, ?ApiGen\Info\ClassLikeInfo $scope, string $indent = ''}
6 | {pre}
7 | {if $expr instanceof ApiGen\Info\Expr\BooleanExprInfo || $expr instanceof ApiGen\Info\Expr\NullExprInfo}
8 | <span class="php-kw">{$expr->toString()}</span>
9 | {elseif $expr instanceof ApiGen\Info\Expr\IntegerExprInfo || $expr instanceof ApiGen\Info\Expr\FloatExprInfo}
10 | <span class="php-num">{$expr->raw}</span>
11 | {elseif $expr instanceof ApiGen\Info\Expr\StringExprInfo}
12 | <span class="php-str">{$expr->raw ?? $expr->toString()}</span>
13 | {elseif $expr instanceof ApiGen\Info\Expr\ArrayExprInfo}
14 | {embed autoBreakingLine, items: $expr->items, maxWidth: 100, indent: $indent}
15 | {block before}{="["}{/block}
16 | {block after}{="]"}{/block}
17 | {block item}
18 | {if $item->key}{include expr, expr: $item->key, scope: $scope} => {/if}
19 | {include expr, expr: $item->value, scope: $scope, indent: "\t$indent"}
20 | {/block}
21 | {block sep},{/block}
22 | {/embed}
23 | {elseif $expr instanceof ApiGen\Info\Expr\ClassConstantFetchExprInfo}
24 | {if $expr->classLike->fullLower === 'self' && $scope}
25 | {var $memberClassLike = $scope}
26 | {include classLikeLink, classLike: $memberClassLike, text: $expr->classLike->fullLower}
27 | {elseif $expr->classLike->fullLower === 'parent' && $scope && isClass($scope) && $scope->extends}
28 | {var $memberClassLike = $index->classLike[$scope->extends->fullLower]}
29 | {include classLikeLink, classLike: $memberClassLike, text: $expr->classLike->fullLower}
30 | {elseif isset($index->classLike[$expr->classLike->fullLower])}
31 | {var $memberClassLike = $index->classLike[$expr->classLike->fullLower]}
32 | {include classLikeLink, classLike: $memberClassLike, short: true}
33 | {else}
34 | {var $memberClassLike = null}
35 | {$expr->classLike->full}
36 | {/if}
37 | ::
38 | {if $memberClassLike !== null && isset($memberClassLike->constants[$expr->name])}
39 | {var $member = $memberClassLike->constants[$expr->name]}
40 | {embed memberLink, $memberClassLike, $member}{block content}{$member->name}{/block}{/embed}
41 | {elseif $memberClassLike !== null && isEnum($memberClassLike) && isset($memberClassLike->cases[$expr->name])}
42 | {var $member = $memberClassLike->cases[$expr->name]}
43 | {embed memberLink, $memberClassLike, $member}{block content}{$member->name}{/block}{/embed}
44 | {else}
45 | {$expr->name}
46 | {/if}
47 | {elseif $expr instanceof ApiGen\Info\Expr\ConstantFetchExprInfo}
48 | {$expr->name}
49 | {elseif $expr instanceof ApiGen\Info\Expr\UnaryOpExprInfo}
50 | {$expr->op}
51 | {include this, expr: $expr->expr, scope: $scope}
52 | {elseif $expr instanceof ApiGen\Info\Expr\BinaryOpExprInfo}
53 | {include this, expr: $expr->left, scope: $scope}
54 | {=" "}{$expr->op}{=" "}
55 | {include this, expr: $expr->right, scope: $scope}
56 | {elseif $expr instanceof ApiGen\Info\Expr\TernaryExprInfo}
57 | {include this, expr: $expr->condition, scope: $scope}
58 | {if $expr->if} ? {include this, expr: $expr->if, scope: $scope} : {else} ?: {/if}
59 | {include this, expr: $expr->else, scope: $scope}
60 | {elseif $expr instanceof ApiGen\Info\Expr\DimFetchExprInfo}
61 | {include this, expr: $expr->expr, scope: $scope}
62 | [{include this, expr: $expr->dim, scope: $scope}]
63 | {elseif $expr instanceof ApiGen\Info\Expr\PropertyFetchExprInfo}
64 | {include this, expr: $expr->expr, scope: $scope}
65 | ->
66 | {if is_string($expr->property)}
67 | {$expr->property}
68 | {else}
69 | {{include this, expr: $expr->property, scope: $scope}}
70 | {/if}
71 | {elseif $expr instanceof ApiGen\Info\Expr\NullSafePropertyFetchExprInfo}
72 | {include this, expr: $expr->expr, scope: $scope}
73 | ?->
74 | {if is_string($expr->property)}
75 | {$expr->property}
76 | {else}
77 | {{include this, expr: $expr->property, scope: $scope}}
78 | {/if}
79 | {elseif $expr instanceof ApiGen\Info\Expr\NewExprInfo}
80 | {var $classLike = $index->classLike[$expr->classLike->fullLower]}
81 | <span class="php-kw">new</span> {include classLikeLink, classLike: $classLike, short: true}
82 | (
83 | {foreach $expr->args as $arg}
84 | {if $arg->name}{$arg->name}: {/if}
85 | {include this, expr: $arg->value, scope: $scope}
86 | {sep}, {/sep}
87 | {/foreach}
88 | )
89 | {else}
90 | ERROR
91 | {/if}
92 | {/pre}
93 | {/define}
94 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/functionDescription.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define functionDescription, ApiGen\Info\FunctionInfo $function}
6 | {if $function->description}
7 | <div class="classLikeDescription">
8 | {include description, $function->description, null}
9 | </div>
10 | {/if}
11 | {/define}
12 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/functionLinks.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define functionLinks, ApiGen\Info\FunctionInfo $function}
6 | {pre}
7 | {if $function->name->namespaceLower}{include namespaceLinks, $index->namespace[$function->name->namespaceLower]}\{/if}
8 | <a href="{functionUrl($function)}">{$function->name->short}</a>
9 | {/pre}
10 | {/define}
11 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/genericParameters.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define genericParameters, ApiGen\Info\GenericParameterInfo[] $genericParameters}
6 | {pre}
7 | {if $genericParameters}
8 | <
9 | {foreach $genericParameters as $genericParameter}
10 | {include genericParameter, $genericParameter}
11 | {sep}, {/sep}
12 | {/foreach}
13 | >
14 | {/if}
15 | {/pre}
16 | {/define}
17 |
18 | {define genericParameter, ApiGen\Info\GenericParameterInfo $genericParameter}
19 | {pre}
20 | {include genericParameterVariance, $genericParameter->variance}
21 | <span title="{$genericParameter->description}">{$genericParameter->name}</span>
22 | {if $genericParameter->bound} is {include type, $genericParameter->bound}{/if}
23 | {if $genericParameter->default} = {include type, $genericParameter->default}{/if}
24 | {/pre}
25 | {/define}
26 |
27 | {define genericParameterVariance, ApiGen\Info\GenericParameterVariance $variance}
28 | {pre}
29 | {if $variance === ApiGen\Info\GenericParameterVariance::Covariant}<span title="covariant">out</span>{=' '}
30 | {elseif $variance === ApiGen\Info\GenericParameterVariance::Contravariant}<span title="contravariant">in</span>{=' '}
31 | {/if}
32 | {/pre}
33 | {/define}
34 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/head.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define head}{/define}
6 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/layout.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define layout}
6 | <!doctype html>
7 | <html lang="en">
8 | <head>
9 | <meta charset="utf-8">
10 | <meta name="viewport" content="width=device-width">
11 | {if $layout->noindex}<meta name="robots" content="noindex">{/if}
12 | <title>{ifset title}{include title|spaceless} | {/ifset}{$config->title} API</title>
13 | <link rel="stylesheet" href="{assetUrl('main.css')}">
14 | <script src="{assetUrl('main.js')}" defer></script>
15 | {include head}
16 | </head>
17 |
18 | <body class="layout">
19 | <div class="layout-aside">
20 | {include menu}
21 | </div>
22 |
23 | <div class="layout-main">
24 | <div class="layout-navbar">
25 | {include navbar}
26 | </div>
27 |
28 | <div class="layout-content">
29 | {include content}
30 | </div>
31 |
32 | <div class="layout-footer">
33 | {$config->title} API documentation generated by <a href="https://github.com/ApiGen/ApiGen">ApiGen {$config->version}</a>
34 | </div>
35 | </div>
36 |
37 | <div class="layout-rest">
38 | <div class="navbar"></div>
39 | </div>
40 | </body>
41 | </html>
42 | {/define}
43 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/member.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define member, ApiGen\Info\MemberInfo $member}
6 | <tr class="expandable collapsed" data-order="{$member->name}" id="{memberAnchor($member)}">
7 | {include cells}
8 | <td class="table-anchor">
9 | <a href="#{memberAnchor($member)}">#</a>
10 | </td>
11 | </tr>
12 | {/define}
13 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberDescription.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define memberDescription, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MemberInfo $member}
6 | {var $description = $member->getEffectiveDescription($index, $classLike)}
7 |
8 | <div n:if="$description" class="expandable-collapsedView">
9 | {shortDescription($index, $classLike, $description)}
10 | </div>
11 |
12 | <div class="expandable-expandedView">
13 | {include description, $description, $classLike}
14 | {block details}{/block}
15 | </div>
16 | {/define}
17 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberLink.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define memberLink, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MemberInfo $member}
6 | <a n:pre n:tag-if="classLikePageExists($classLike)" href="{memberUrl($classLike, $member)}" n:class="$member->isDeprecated() ? deprecated">{include content}</a>
7 | {/define}
8 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberSourceLink.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define memberSourceLink, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MemberInfo $member}
6 | {pre}
7 | <a n:tag-if="$classLike->file && sourcePageExists($index->files[$classLike->file])" href="{memberSourceUrl($classLike, $member)}" title="Go to source code">
8 | <span n:tag-if="$member->isDeprecated()" class="deprecated">{include content}</span>
9 | </a>
10 | {/pre}
11 | {/define}
12 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/memberVisibility.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define memberVisibility, ApiGen\Info\MemberInfo $member}
6 | {if $member->protected}protected{elseif $member->private}private{else}public{/if}
7 | {/define}
8 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/menu.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define menu}
6 | <div class="menu">
7 | {ifset $index->namespace['']}
8 | <div class="menu-section">
9 | <h3>Namespaces</h3>
10 | {include menuGroup, namespace: $index->namespace['']}
11 | </div>
12 | {/ifset}
13 |
14 | {if $layout->activeNamespace}
15 | {include menuElements, elements: $layout->activeNamespace->class, heading: 'Classes', onlyPrimary: $layout->activeNamespace->primary}
16 | {include menuElements, elements: $layout->activeNamespace->interface, heading: 'Interfaces', onlyPrimary: $layout->activeNamespace->primary}
17 | {include menuElements, elements: $layout->activeNamespace->trait, heading: 'Traits', onlyPrimary: $layout->activeNamespace->primary}
18 | {include menuElements, elements: $layout->activeNamespace->enum, heading: 'Enums', onlyPrimary: $layout->activeNamespace->primary}
19 | {include menuElements, elements: $layout->activeNamespace->exception, heading: 'Exceptions', onlyPrimary: $layout->activeNamespace->primary}
20 | {include menuElements, elements: $layout->activeNamespace->function, heading: 'Functions', onlyPrimary: $layout->activeNamespace->primary}
21 | {/if}
22 | </div>
23 | {/define}
24 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/menuElements.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define menuElements, ApiGen\Info\ElementInfo[] $elements, string $heading, bool $onlyPrimary = true}
6 | {try}
7 | <div class="menu-section">
8 | <h3>{$heading}</h3>
9 | <ul>
10 | {foreach $elements as $element}
11 | {skipIf $onlyPrimary && !$element->primary}
12 | {skipIf !elementPageExists($element)}
13 | <li n:class="$element === $layout->activeElement ? active">
14 | <a n:class="$element->isDeprecated() ? deprecated" href="{elementUrl($element)}">
15 | {elementName($element)}
16 | </a>
17 | </li>
18 | {else}
19 | {rollback}
20 | {/foreach}
21 | </ul>
22 | </div>
23 | {/try}
24 | {/define}
25 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/menuGroup.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define menuGroup, ApiGen\Index\NamespaceIndex $namespace, int $level = 0}
6 | <ul>
7 | {foreach $namespace->children as $child}
8 | {var bool $isActive = $layout->activeNamespace && str_starts_with("{$layout->activeNamespace->name->fullLower}\\", "{$child->name->fullLower}\\")}
9 | {var bool $isCollapsed = $level > 0 && !$isActive}
10 | {skipIf !$child->primary && !$isActive}
11 | {skipIf !namespacePageExists($child)}
12 | <li n:class="menuGroup-item, $isActive ? active">
13 | <a href="{namespaceUrl($child)}" n:class="$child->deprecated ? deprecated">{$child->name->short ?: 'none'}</a>
14 | {if !$isCollapsed && $child->children && $child->name->short !== ''}
15 | {include this, namespace: $child, level: $level + 1}
16 | {/if}
17 | </li>
18 | {/foreach}
19 | </ul>
20 | {/define}
21 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/method.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define method, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MethodInfo $method}
6 | {embed member, $method}
7 | {block cells}
8 | <td class="table-cell table-shrink">
9 | <code class="nowrap">
10 | {if !isInterface($classLike) && $method->abstract}abstract{elseif $method->final}final{/if}
11 | {include memberVisibility, $method}
12 | {if $method->static}static{/if}
13 | {if $method->byRef}&{/if}
14 | </code>
15 | </td>
16 |
17 | <td class="table-cell">
18 | <code>{include methodSignature, classLike: $classLike, method: $method}</code>
19 |
20 | {embed memberDescription, classLike: $classLike, member: $method}
21 | {block details}
22 | {try}
23 | <h4>Deprecated</h4>
24 | <div>
25 | {foreach $method->tags['deprecated'] ?? [] as $tag}
26 | {skipIf !$tag->description}
27 | {longDescription($index, $classLike, $tag->description)}{sep}<br>{/sep}
28 | {else}
29 | {rollback}
30 | {/foreach}
31 | </div>
32 | {/try}
33 |
34 | {try}
35 | <h4>Parameters</h4>
36 | <table>
37 | {foreach $method->parameters as $parameter}
38 | {var string $description = $parameter->getEffectiveDescription($index, $classLike, $method)}
39 | {skipIf !$description}
40 | <tr>
41 | <td>{if $parameter->variadic}...{/if}<var>${$parameter->name}</var> </td>
42 | <td>{longDescription($index, $classLike, $description)}</td>
43 | </tr>
44 | {else}
45 | {rollback}
46 | {/foreach}
47 | </table>
48 | {/try}
49 |
50 | {var string $returnDescription = $method->getEffectiveReturnDescription($index, $classLike)}
51 | {if $returnDescription}
52 | <h4>Returns</h4>
53 | <div>{longDescription($index, $classLike, $returnDescription)}</div>
54 | {/if}
55 |
56 | {if !empty($method->tags['throws'])}
57 | <h4>Throws</h4>
58 | <table>
59 | {foreach $method->tags['throws'] as $tag}
60 | <tr>
61 | <td><code>{include type, type: $tag->type, scope: $classLike, short: false}</code> </td>
62 | <td>{longDescription($index, $classLike, $tag->description)}</td>
63 | </tr>
64 | {/foreach}
65 | </table>
66 | {/if}
67 |
68 | {* TODO: other tags (uses, internal...) *}
69 |
70 | {include methodRelation, title: 'Overrides', method: $method, related: $index->methodOverrides[$classLike->name->fullLower][$method->nameLower] ?? []}
71 | {include methodRelation, title: 'Overridden by', method: $method, related: $index->methodOverriddenBy[$classLike->name->fullLower][$method->nameLower] ?? []}
72 | {include methodRelation, title: 'Implements', method: $method, related: $index->methodImplements[$classLike->name->fullLower][$method->nameLower] ?? []}
73 | {include methodRelation, title: 'Implemented by', method: $method, related: $index->methodImplementedBy[$classLike->name->fullLower][$method->nameLower] ?? []}
74 | {/block}
75 | {/embed}
76 | </td>
77 | {/block}
78 | {/embed}
79 | {/define}
80 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodInherited.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define methodInherited, ApiGen\Info\ClassLikeInfo $from, array $overriden}
6 | {try}
7 | <table class="table">
8 | <tr>
9 | <th class="table-heading">Methods inherited from {include classLikeLink, $from}</th>
10 | </tr>
11 | <tr>
12 | <td class="table-cell">
13 | <code n:pre>
14 | {foreach $from->methods as $methodName => $method}
15 | {skipIf isset($overriden[$methodName]) || $method->private}
16 | {embed memberLink, $from, $method}{block content}{$method->name}(){/block}{/embed}
17 | {sep}, {/sep}
18 | {else}
19 | {rollback}
20 | {/foreach}
21 | </code>
22 | </td>
23 | </tr>
24 | </table>
25 | {/try}
26 |
27 | {include methodInheritedSummary, $from, $overriden}
28 | {/define}
29 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodInheritedSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define methodInheritedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
6 | {if isClass($classLike)}
7 | {if $classLike->extends}
8 | {include methodInherited, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->methods}
9 | {/if}
10 | {elseif isInterface($classLike)}
11 | {foreach $classLike->extends as $parent}
12 | {include methodInherited, $index->classLike[$parent->fullLower], $overriden + $classLike->methods}
13 | {/foreach}
14 | {/if}
15 | {/define}
16 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodRelation.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define methodRelation, ApiGen\Info\MethodInfo $method, string $title, ApiGen\Info\ClassLikeInfo[] $related}
6 | {if $related}
7 | <h4>{$title}</h4>
8 | <ul>
9 | {foreach $related as $classLike}
10 | <li>
11 | {embed memberLink, $classLike, $method}
12 | {block content}<code>{$classLike->name->full}::{$method->name}()</code>{/block}
13 | {/embed}
14 | </li>
15 | {/foreach}
16 | </ul>
17 | {/if}
18 | {/define}
19 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodSignature.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define methodSignature, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\MethodInfo $method}
6 | {pre}
7 | {embed autoBreakingLine, items: $method->parameters, maxWidth: 120}
8 | {block before}
9 | {embed memberSourceLink, classLike: $classLike, member: $method}{block content}{$method->name}{/block}{/embed}
10 | {include genericParameters, $method->genericParameters}
11 | (
12 | {/block}
13 | {block after}){if $method->returnType}: {include type, type: $method->returnType, scope: $classLike}{/if}{/block}
14 | {block item}{include parameter, classLike: $classLike, parameter: $item}{/block}
15 | {block sep},{/block}
16 | {/embed}
17 | {/pre}
18 | {/define}
19 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define methodSummary, ApiGen\Info\ClassLikeInfo $classLike}
6 | {if $classLike->methods}
7 | <table class="table" id="methods">
8 | <tr>
9 | <th class="table-heading sortable" colspan="3" title="Switch between natural and alphabetical order">Methods</th>
10 | </tr>
11 |
12 | {foreach $classLike->methods as $method}
13 | {include method, classLike: $classLike, method: $method}
14 | {/foreach}
15 | </table>
16 | {/if}
17 | {/define}
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodUsed.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define methodUsed, ApiGen\Info\ClassLikeInfo $from, array $overriden}
6 | {try}
7 | <table class="table" n:if="isTrait($from)">
8 | <tr>
9 | <th class="table-heading">Methods used from {include classLikeLink, $from}</th>
10 | </tr>
11 | <tr>
12 | <td class="table-cell">
13 | <code n:pre>
14 | {foreach $from->methods as $methodName => $method}
15 | {skipIf isset($overriden[$methodName])}
16 | {embed memberLink, $from, $method}{block content}{$method->name}(){/block}{/embed}
17 | {sep}, {/sep}
18 | {else}
19 | {rollback}
20 | {/foreach}
21 | </code>
22 | </td>
23 | </tr>
24 | </table>
25 | {/try}
26 |
27 | {include methodUsedSummary, $from, $overriden}
28 | {/define}
29 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/methodUsedSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define methodUsedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
6 | {if isClass($classLike)}
7 | {if $classLike->extends}
8 | {include methodUsed, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->methods}
9 | {/if}
10 |
11 | {foreach $classLike->uses as $use}
12 | {include methodUsed, $index->classLike[$use->fullLower], $overriden + $classLike->methods}
13 | {/foreach}
14 |
15 | {elseif isInterface($classLike)}
16 | {foreach $classLike->extends as $parent}
17 | {include methodUsed, $index->classLike[$parent->fullLower], $overriden + $classLike->methods}
18 | {/foreach}
19 |
20 | {elseif isTrait($classLike) || isEnum($classLike)}
21 | {foreach $classLike->uses as $use}
22 | {include methodUsed, $index->classLike[$use->fullLower], $overriden + $classLike->methods}
23 | {/foreach}
24 | {/if}
25 | {/define}
26 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/namespaceLinks.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define namespaceLinks, ApiGen\Index\NamespaceIndex $namespace}
6 | {pre}
7 | {if $namespace->name->namespaceLower !== ''}
8 | {include this, $index->namespace[$namespace->name->namespaceLower]}\
9 | {/if}
10 | <a n:tag-if="namespacePageExists($namespace)" href="{namespaceUrl($namespace)}">{$namespace->name->short}</a>
11 | {/pre}
12 | {/define}
13 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/navbar.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define navbar}
6 | <nav class="navbar">
7 | <div class="navbar-left">
8 | <ul class="navbar-links">
9 | <li n:class="$layout->activePage === 'index' ? active">
10 | <a href="{indexUrl()}">Overview</a>
11 | </li>
12 | <li n:class="$layout->activePage === 'namespace' ? active">
13 | <span n:tag-if="!$layout->activeNamespace">
14 | <a n:tag-if="$layout->activeNamespace && namespacePageExists($layout->activeNamespace)" href="{namespaceUrl($layout->activeNamespace)}">Namespace</a>
15 | </span>
16 | </li>
17 | <li n:class="$layout->activeElement ? active" n:pre>
18 | <span n:tag-if="!$layout->activeElement">
19 | <a n:tag-if="$layout->activeElement" href="{elementUrl($layout->activeElement)}">
20 | {if $layout->activeElement instanceof ApiGen\Info\ClassLikeInfo}{include classLikeKind, $layout->activeElement}
21 | {elseif $layout->activeElement instanceof ApiGen\Info\FunctionInfo}Function
22 | {else}Class
23 | {/if}
24 | </a>
25 | </span>
26 | </li>
27 | <li n:if="treePageExists()" n:class="$layout->activePage === 'tree' ? active">
28 | <a href="{treeUrl()}">Tree</a>
29 | </li>
30 | </ul>
31 | </div>
32 | <div class="navbar-right">
33 | {include search}
34 | </div>
35 | </nav>
36 | {/define}
37 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/parameter.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define parameter, ApiGen\Info\ParameterInfo $parameter, ?ApiGen\Info\ClassLikeInfo $classLike = null}
6 | <span n:pre>
7 | {if $parameter->type}{include type, type: $parameter->type, scope: $classLike} {/if}
8 | {if $parameter->byRef}&{/if}{if $parameter->variadic}...{/if}<var>${$parameter->name}</var>
9 | {if $parameter->default} = {include expr, expr: $parameter->default, scope: $classLike}{/if}
10 | </span>
11 | {/define}
12 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/property.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define property, ApiGen\Info\ClassLikeInfo $classLike, ApiGen\Info\PropertyInfo $property}
6 | {embed member, $property}
7 | {block cells}
8 | <td class="table-cell table-shrink">
9 | <code class="nowrap">
10 | {include memberVisibility, $property}
11 | {if $property->static}static{/if}
12 | {if $property->readOnly}readonly{elseif $property->writeOnly}writeonly{/if}
13 | </code>
14 | </td>
15 |
16 | <td class="table-cell table-shrink">
17 | <code class="nowrap">{if $property->type}{include type, $property->type, scope: $classLike}{/if}</code>
18 | </td>
19 |
20 | <td class="table-cell">
21 | <code n:pre>
22 | <var>{embed memberSourceLink, $classLike, $property}{block content}${$property->name}{/block}{/embed}</var>
23 | {if $property->default} = {include expr, expr: $property->default, scope: $classLike}{/if}
24 | </code>
25 |
26 | {embed memberDescription, classLike: $classLike, member: $property}}
27 | {block details}{* TODO: other tags (uses, internal...) *}{/block}
28 | {/embed}
29 | </td>
30 | {/block}
31 | {/embed}
32 | {/define}
33 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyInherited.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define propertyInherited, ApiGen\Info\ClassLikeInfo $from, array $overriden}
6 | {try}
7 | <table class="table">
8 | <tr>
9 | <th class="table-heading">Properties inherited from {include classLikeLink, $from}</th>
10 | </tr>
11 | <tr>
12 | <td class="table-cell">
13 | <code n:pre>
14 | {foreach $from->properties as $propertyName => $property}
15 | {skipIf isset($overriden[$propertyName])}
16 | <var>{embed memberLink, $from, $property}{block content}${$property->name}{/block}{/embed}</var>
17 | {sep}, {/sep}
18 | {else}
19 | {rollback}
20 | {/foreach}
21 | </code>
22 | </td>
23 | </tr>
24 | </table>
25 | {/try}
26 |
27 | {include propertyInheritedSummary, $from, $overriden}
28 | {/define}
29 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyInheritedSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define propertyInheritedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
6 | {if isClass($classLike)}
7 | {if $classLike->extends}
8 | {include propertyInherited, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->properties}
9 | {/if}
10 | {elseif isInterface($classLike)}
11 | {foreach $classLike->extends as $parent}
12 | {include propertyInherited, $index->classLike[$parent->fullLower], $overriden + $classLike->properties}
13 | {/foreach}
14 | {/if}
15 | {/define}
16 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertySummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define propertySummary, ApiGen\Info\ClassLikeInfo $classLike}
6 | {if $classLike->properties}
7 | <table class="table" id="properties">
8 | <tr>
9 | <th class="table-heading sortable" colspan="4" title="Switch between natural and alphabetical order">Properties</th>
10 | </tr>
11 |
12 | {foreach $classLike->properties as $property}
13 | {include property, classLike: $classLike, property: $property}
14 | {/foreach}
15 | </table>
16 | {/if}
17 | {/define}
18 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyUsed.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define propertyUsed, ApiGen\Info\ClassLikeInfo $from, array $overriden}
6 | {try}
7 | <table class="table" n:if="isTrait($from)">
8 | <tr>
9 | <th class="table-heading">Properties used from {include classLikeLink, $from}</th>
10 | </tr>
11 | <tr>
12 | <td class="table-cell">
13 | <code n:pre>
14 | {foreach $from->properties as $propertyName => $property}
15 | {skipIf isset($overriden[$propertyName])}
16 | <var>{embed memberLink, $from, $property}{block content}${$property->name}{/block}{/embed}</var>
17 | {sep}, {/sep}
18 | {else}
19 | {rollback}
20 | {/foreach}
21 | </code>
22 | </td>
23 | </tr>
24 | </table>
25 | {/try}
26 |
27 | {include propertyUsedSummary, $from, $overriden}
28 | {/define}
29 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/propertyUsedSummary.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define propertyUsedSummary, ApiGen\Info\ClassLikeInfo $classLike, array $overriden}
6 | {if isClass($classLike)}
7 | {if $classLike->extends}
8 | {include propertyUsed, $index->classLike[$classLike->extends->fullLower], $overriden + $classLike->properties}
9 | {/if}
10 |
11 | {foreach $classLike->uses as $use}
12 | {include propertyUsed, $index->classLike[$use->fullLower], $overriden + $classLike->properties}
13 | {/foreach}
14 |
15 | {elseif isInterface($classLike)}
16 | {foreach $classLike->extends as $parent}
17 | {include propertyUsed, $index->classLike[$parent->fullLower], $overriden + $classLike->properties}
18 | {/foreach}
19 |
20 | {elseif isTrait($classLike) || isEnum($classLike)}
21 | {foreach $classLike->uses as $use}
22 | {include propertyUsed, $index->classLike[$use->fullLower], $overriden + $classLike->properties}
23 | {/foreach}
24 | {/if}
25 | {/define}
26 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/search.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define search}
6 | <div class="search" data-elements="{assetUrl('elements.js')}">
7 | <input class="search-input" placeholder="Search class, function or namespace" autofocus>
8 | <ul class="search-results"></ul>
9 | </div>
10 | {/define}
11 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/source.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define source, string $path}
6 | <table class="source">{highlight($path)}</table>
7 | {/define}
8 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/blocks/type.latte:
--------------------------------------------------------------------------------
1 | {varType ApiGen\Index\Index $index}
2 | {varType ApiGen\Renderer\Latte\Template\ConfigParameters $config}
3 | {varType ApiGen\Renderer\Latte\Template\LayoutParameters $layout}
4 |
5 | {define type, PHPStan\PhpDocParser\Ast\Type\TypeNode $type, ?ApiGen\Info\ClassLikeInfo $scope, bool $brackets = false, bool $short = true}
6 | {pre}
7 | {if $type instanceof PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode}
8 | {var ApiGen\Analyzer\IdentifierKind $kind = $type->getAttribute('kind')}
9 | {if $kind === ApiGen\Analyzer\IdentifierKind::ClassLike}
10 | {var ApiGen\Info\ClassLikeReferenceInfo $ref = $type->getAttribute('classLikeReference')}
11 | {include classLikeLink, $index->classLike[$ref->fullLower], $short}
12 | {elseif $kind === ApiGen\Analyzer\IdentifierKind::Alias}
13 | {var ApiGen\Info\AliasReferenceInfo $ref = $type->getAttribute('aliasReference')}
14 | {var ApiGen\Info\ClassLikeInfo $classLike = $index->classLike[$ref->classLike->fullLower]}
15 | {var ApiGen\Info\AliasInfo $alias = $classLike->aliases[$ref->aliasLower] ?? null}
16 | {if $alias !== null}
17 | {include aliasLink, classLike: $classLike, alias: $alias}
18 | {else}
19 | <span title="Alias">{$type}</span>
20 | {/if}
21 | {elseif $kind === ApiGen\Analyzer\IdentifierKind::Keyword && $type->name === 'parent' && $scope && isClass($scope) && $scope->extends}
22 | {include classLikeLink, $index->classLike[$scope->extends->fullLower], text: 'parent'}
23 | {elseif $kind === ApiGen\Analyzer\IdentifierKind::Keyword && $type->name === 'self' && $scope}
24 | {include classLikeLink, $scope, text: 'self'}
25 | {else}
26 | {$type}
27 | {/if}
28 |
29 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ArrayTypeNode}
30 | {include this, type: $type->type, scope: $scope, brackets: true, short: $short}[]
31 |
32 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ArrayShapeNode}
33 | {$type->kind}{
34 | {foreach $type->items as $item}
35 | {if $item->keyName}
36 | {$item->keyName}
37 | {if $item->optional}?{/if}{=": "}
38 | {/if}
39 | {include this, type: $item->valueType, scope: $scope, brackets: true, short: $short}
40 | {sep}, {/sep}
41 | {/foreach}
42 | {if !$type->sealed}{if $type->items}, {/if}...{/if}
43 | }
44 |
45 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ObjectShapeNode}
46 | object{
47 | {foreach $type->items as $item}
48 | {$item->keyName}
49 | {if $item->optional}?{/if}{=": "}
50 | {include this, type: $item->valueType, scope: $scope, brackets: true, short: $short}
51 | {sep}, {/sep}
52 | {/foreach}
53 | }
54 |
55 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\NullableTypeNode}
56 | ?{include this, type: $type->type, scope: $scope, brackets: true, short: $short}
57 |
58 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\UnionTypeNode}
59 | {if $brackets}({/if}
60 | {foreach $type->types as $innerType}
61 | {include this, type: $innerType, scope: $scope, brackets: true, short: $short}
62 | {sep}|{/sep}
63 | {/foreach}
64 | {if $brackets}){/if}
65 |
66 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\IntersectionTypeNode}
67 | {if $brackets}({/if}
68 | {foreach $type->types as $innerType}
69 | {include this, type: $innerType, scope: $scope, brackets: true, short: $short}
70 | {sep}&{/sep}
71 | {/foreach}
72 | {if $brackets}){/if}
73 |
74 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\GenericTypeNode}
75 | {include this, type: $type->type, scope: $scope, short: $short}
76 | <
77 | {foreach $type->genericTypes as $idx => $genericType}
78 | {var $variance = $type->variances[$idx] ?? null}
79 | {if $variance === PHPStan\PhpDocParser\Ast\Type\GenericTypeNode::VARIANCE_BIVARIANT}
80 | *
81 | {elseif $variance === PHPStan\PhpDocParser\Ast\Type\GenericTypeNode::VARIANCE_COVARIANT}
82 | <span title="covariant">out</span>{=' '}
83 | {include this, type: $genericType, scope: $scope, short: $short}
84 | {elseif $variance === PHPStan\PhpDocParser\Ast\Type\GenericTypeNode::VARIANCE_CONTRAVARIANT}
85 | <span title="contravariant">in</span>{=' '}
86 | {include this, type: $genericType, scope: $scope, short: $short}
87 | {else}
88 | {include this, type: $genericType, scope: $scope, short: $short}
89 | {/if}
90 | {sep}, {/sep}
91 | {/foreach}
92 | >
93 |
94 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\CallableTypeNode}
95 | {if $brackets}({/if}
96 | {include this, type: $type->identifier, scope: $scope, short: $short}
97 | (
98 | {foreach $type->parameters as $parameter}
99 | {include this, type: $parameter->type, scope: $scope, short: $short}
100 | {if $parameter->isReference}&{/if}{if $parameter->isVariadic}...{/if}
101 | {if $parameter->parameterName && $parameter->type} {/if}
102 | {$parameter->parameterName}
103 | {sep}, {/sep}
104 | {/foreach}
105 | ):
106 | {include this, type: $type->returnType, scope: $scope, brackets: true, short: $short}
107 | {if $brackets}){/if}
108 |
109 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ThisTypeNode}
110 | {$type}
111 |
112 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ConstTypeNode}
113 | {var ApiGen\Info\ExprInfo $expr = $type->constExpr->getAttribute('info')}
114 | {include expr, expr: $expr, scope: $scope}
115 |
116 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\OffsetAccessTypeNode}
117 | {include this, type: $type->type, scope: $scope, short: $short}
118 | [{include this, type: $type->offset, scope: $scope, short: $short}]
119 |
120 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ConditionalTypeNode}
121 | (
122 | {include this, type: $type->subjectType, scope: $scope, short: $short}
123 | {if $type->negated} is not {else} is {/if}
124 | {include this, type: $type->targetType, scope: $scope, short: $short}
125 | ?
126 | {include this, type: $type->if, scope: $scope, short: $short}
127 | :
128 | {include this, type: $type->else, scope: $scope, short: $short}
129 | )
130 |
131 | {elseif $type instanceof PHPStan\PhpDocParser\Ast\Type\ConditionalTypeForParameterNode}
132 | (
133 | {$type->parameterName}
134 | {if $type->negated} is not {else} is {/if}
135 | {include this, type: $type->targetType, scope: $scope, short: $short}
136 | ?
137 | {include this, type: $type->if, scope: $scope, short: $short}
138 | :
139 | {include this, type: $type->else, scope: $scope, short: $short}
140 | )
141 |
142 | {else}
143 | UNRESOLVED
144 | {/if}
145 | {/pre}
146 | {/define}
147 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/classLike.latte:
--------------------------------------------------------------------------------
1 | {templateType ApiGen\Renderer\Latte\Template\ClassLikeTemplate}
2 | {import '../blocks/@index.latte'}
3 |
4 | {embed layout}
5 | {block title}
6 | {if $classLike->isDeprecated()}Deprecated {/if}
7 | {include classLikeKind, $classLike}
8 | {$classLike->name->full}
9 | {/block}
10 |
11 | {block content}
12 | <h1 n:class="$classLike->isDeprecated() ? deprecated">
13 | {include classLikeKind, $classLike} {include classLikeLinks, $classLike}
14 | </h1>
15 |
16 | {if $classLike->genericParameters}
17 | <h2><code>{include genericParameters, $classLike->genericParameters}</code></h2>
18 | {/if}
19 |
20 | {include classLikeSignatureTable, $classLike}
21 | {include classLikeDescription, $classLike}
22 |
23 | {if !empty($index->dag[$classLike->name->fullLower]) || (isClass($classLike) && $classLike->extends) || (isInterface($classLike) && $classLike->extends)}
24 | <div class="classLikeSection">
25 | {include classTree, $classLike, $index->dag}
26 | </div>
27 | {/if}
28 |
29 | <div class="classLikeSection">
30 | {if $classLike->file && sourcePageExists($index->files[$classLike->file])}<b>Located at</b> <a href="{classLikeSourceUrl($classLike)}" title="Go to source code">{relativePath($classLike->file)}</a><br>{/if}
31 | </div>
32 |
33 | {include aliasSummary, $classLike}
34 |
35 | {if isEnum($classLike)}
36 | {include enumCaseSummary, $classLike}
37 | {/if}
38 |
39 | {include methodSummary, $classLike}
40 | {include methodInheritedSummary, $classLike, []}
41 | {include methodUsedSummary, $classLike, []}
42 |
43 | {include constantSummary, $classLike}
44 | {include constantInheritedSummary, $classLike, []}
45 |
46 | {include propertySummary, $classLike}
47 | {include propertyInheritedSummary, $classLike, []}
48 | {include propertyUsedSummary, $classLike, []}
49 | {/block}
50 | {/embed}
51 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/function.latte:
--------------------------------------------------------------------------------
1 | {templateType ApiGen\Renderer\Latte\Template\FunctionTemplate}
2 | {import '../blocks/@index.latte'}
3 |
4 | {embed layout}
5 | {block title}
6 | {if $function->isDeprecated()}Deprecated {/if}
7 | {$function->name->full}
8 | {/block}
9 |
10 | {block content}
11 | <h1 n:class="$function->isDeprecated() ? deprecated">
12 | {include functionLinks, $function}
13 | </h1>
14 |
15 | {if $function->genericParameters}
16 | <h2><code>{include genericParameters, $function->genericParameters}</code></h2>
17 | {/if}
18 |
19 | {include functionDescription, $function}
20 |
21 | <div class="classLikeSection" n:if="$function->file && sourcePageExists($index->files[$function->file])">
22 | <b>Located at</b> <a href="{functionSourceUrl($function)}" title="Go to source code">{relativePath($function->file)}</a>
23 | </div>
24 |
25 | <table class="table" n:if="$function->parameters">
26 | <tr>
27 | <th class="table-heading" colspan="4">Parameters</th>
28 | </tr>
29 |
30 | {foreach $function->parameters as $parameter}
31 | <tr id="{parameterAnchor($parameter)}">
32 | <td class="table-cell table-shrink">
33 | <code class="nowrap">{if $parameter->type}{include type, $parameter->type}{/if}</code>
34 | </td>
35 | <td class="table-cell table-shrink">
36 | <code class="nowrap" n:pre>
37 | {if $parameter->byRef}&{/if}{if $parameter->variadic}...{/if}<var>${$parameter->name}</var>
38 | {if $parameter->default} = {include expr, expr: $parameter->default}{/if}
39 | </code>
40 | </td>
41 | <td class="table-cell">{longDescription($index, null, $parameter->description)}</td>
42 | <td class="table-anchor">
43 | <a href="#{parameterAnchor($parameter)}">#</a>
44 | </td>
45 | </tr>
46 | {/foreach}
47 | </table>
48 |
49 | <table class="table" n:if="$function->byRef || $function->returnType || $function->returnDescription">
50 | <tr>
51 | <th class="table-heading" colspan="2">Returns</th>
52 | </tr>
53 |
54 | <tr>
55 | <td class="table-cell table-shrink" n:if="$function->byRef || $function->returnType">
56 | <code class="nowrap">{if $function->byRef}&{/if}{include type, $function->returnType}</code>
57 | </td>
58 | <td class="table-cell" n:if="$function->returnDescription">
59 | {longDescription($index, null, $function->returnDescription)}
60 | </td>
61 | </tr>
62 | </table>
63 |
64 | <table class="table" n:ifset="$function->tags[throws]">
65 | <tr>
66 | <th class="table-heading" colspan="2">Throws</th>
67 | </tr>
68 |
69 | {foreach $function->tags[throws] as $tag}
70 | <tr>
71 | <td class="table-cell table-shrink"><code class="nowrap">{include type, type: $tag->type, short: false}</code></td>
72 | <td class="table-cell" n:if="$tag->description">{longDescription($index, $tag->description)}</td>
73 | </tr>
74 | {/foreach}
75 | </table>
76 | {/block}
77 | {/embed}
78 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/index.latte:
--------------------------------------------------------------------------------
1 | {templateType ApiGen\Renderer\Latte\Template\TreeTemplate}
2 | {import '../blocks/@index.latte'}
3 |
4 | {embed layout}
5 | {block content}
6 | <h1>{$config->title} API documentation</h1>
7 |
8 | {embed elementSummary}
9 | {block elementSummaryContent}
10 | {include elementSummaryGroup, heading: 'Namespaces', elements: $index->namespace, showDescription: false, onlyPrimary: true}
11 | {/block}
12 | {/embed}
13 | {/block}
14 | {/embed}
15 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/namespace.latte:
--------------------------------------------------------------------------------
1 | {templateType ApiGen\Renderer\Latte\Template\NamespaceTemplate}
2 | {import '../blocks/@index.latte'}
3 |
4 | {embed layout}
5 | {block title}{if $namespace->name->full !== ''}Namespace {$namespace->name->full}{else}No namespace{/if}{/block}
6 |
7 | {block content}
8 | <h1>{if $namespace->name->full !== ''}Namespace {include namespaceLinks, $namespace}{else}No namespace{/if}</h1>
9 |
10 | {embed elementSummary}
11 | {block elementSummaryContent}
12 | {include elementSummaryGroup, heading: 'Classes', elements: $namespace->class, onlyPrimary: $namespace->primary}
13 | {include elementSummaryGroup, heading: 'Interfaces', elements: $namespace->interface, onlyPrimary: $namespace->primary}
14 | {include elementSummaryGroup, heading: 'Traits', elements: $namespace->trait, onlyPrimary: $namespace->primary}
15 | {include elementSummaryGroup, heading: 'Enums', elements: $namespace->enum, onlyPrimary: $namespace->primary}
16 | {include elementSummaryGroup, heading: 'Exceptions', elements: $namespace->exception, onlyPrimary: $namespace->primary}
17 | {include elementSummaryGroup, heading: 'Functions', elements: $namespace->function, onlyPrimary: $namespace->primary}
18 | {include elementSummaryGroup, heading: 'Namespaces', elements: $namespace->children, showDescription: false, onlyPrimary: $namespace->primary}
19 | {/block}
20 | {/embed}
21 | {/block}
22 | {/embed}
23 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/sitemap.latte:
--------------------------------------------------------------------------------
1 | {templateType ApiGen\Renderer\Latte\Template\SitemapTemplate}
2 | {contentType 'application/xml'}
3 | <?xml version="1.0" encoding="UTF-8"?>
4 | <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
5 | <url><loc>{indexUrl()}</loc></url>
6 |
7 | {if treePageExists()}
8 | <url><loc>{treeUrl()}</loc></url>
9 | {/if}
10 |
11 | {foreach $index->namespace as $namespace}
12 | {if namespacePageExists($namespace) && $namespace->primary}
13 | <url><loc>{namespaceUrl($namespace)}</loc></url>
14 | {/if}
15 | {/foreach}
16 |
17 | {foreach $index->classLike as $classLike}
18 | {if classLikePageExists($classLike) && $classLike->primary}
19 | <url><loc>{classLikeUrl($classLike)}</loc></url>
20 | {/if}
21 | {/foreach}
22 |
23 | {foreach $index->function as $function}
24 | {if functionPageExists($function) && $function->primary}
25 | <url><loc>{functionUrl($function)}</loc></url>
26 | {/if}
27 | {/foreach}
28 |
29 | {foreach $index->files as $file}
30 | {if sourcePageExists($file) && $file->primary}
31 | <url><loc>{sourceUrl($file->path, null, null)}</loc></url>
32 | {/if}
33 | {/foreach}
34 | </urlset>
35 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/source.latte:
--------------------------------------------------------------------------------
1 | {templateType ApiGen\Renderer\Latte\Template\SourceTemplate}
2 | {import '../blocks/@index.latte'}
3 |
4 | {embed layout}
5 | {block title}File {relativePath($path)}{/block}
6 |
7 | {block content}
8 | {include source, $path}
9 | {/block}
10 | {/embed}
11 |
--------------------------------------------------------------------------------
/src/Renderer/Latte/Template/pages/tree.latte:
--------------------------------------------------------------------------------
1 | {templateType ApiGen\Renderer\Latte\Template\TreeTemplate}
2 | {import '../blocks/@index.latte'}
3 |
4 | {embed layout}
5 | {block title}Tree{/block}
6 |
7 | {block content}
8 | <h1>{include title}</h1>
9 |
10 | {ifset $index->classExtends['']}
11 | <h3>Classes</h3>
12 | {include classTreeList, $index->classExtends[''], $index->classExtends}
13 | {/ifset}
14 |
15 | {if $index->interface}
16 | <h3>Interfaces</h3>
17 | {include classTreeList, $index->interface, []}
18 | {/if}
19 |
20 | {if $index->trait}
21 | <h3>Traits</h3>
22 | {include classTreeList, $index->trait, []}
23 | {/if}
24 |
25 | {if $index->enum}
26 | <h3>Enums</h3>
27 | {include classTreeList, $index->enum, []}
28 | {/if}
29 | {/block}
30 | {/embed}
31 |
--------------------------------------------------------------------------------
/src/Renderer/SourceHighlighter.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer;
4 |
5 | use Nette\Utils\FileSystem;
6 | use PhpToken;
7 | use Symfony\Component\Console\Style\OutputStyle;
8 |
9 | use function count;
10 | use function explode;
11 | use function htmlspecialchars;
12 | use function sprintf;
13 | use function strlen;
14 | use function strtolower;
15 | use function strval;
16 | use function substr_count;
17 |
18 | use const TOKEN_PARSE;
19 | use const T_ABSTRACT;
20 | use const T_ARRAY;
21 | use const T_AS;
22 | use const T_BREAK;
23 | use const T_CASE;
24 | use const T_CATCH;
25 | use const T_CLASS;
26 | use const T_CLONE;
27 | use const T_CLOSE_TAG;
28 | use const T_COMMENT;
29 | use const T_CONST;
30 | use const T_CONSTANT_ENCAPSED_STRING;
31 | use const T_CONTINUE;
32 | use const T_DECLARE;
33 | use const T_DEFAULT;
34 | use const T_DNUMBER;
35 | use const T_DO;
36 | use const T_DOC_COMMENT;
37 | use const T_ECHO;
38 | use const T_ELSE;
39 | use const T_ELSEIF;
40 | use const T_EMPTY;
41 | use const T_ENCAPSED_AND_WHITESPACE;
42 | use const T_ENDDECLARE;
43 | use const T_ENDFOR;
44 | use const T_ENDFOREACH;
45 | use const T_ENDIF;
46 | use const T_ENDSWITCH;
47 | use const T_ENDWHILE;
48 | use const T_ENUM;
49 | use const T_EVAL;
50 | use const T_EXIT;
51 | use const T_EXTENDS;
52 | use const T_FINAL;
53 | use const T_FINALLY;
54 | use const T_FN;
55 | use const T_FOR;
56 | use const T_FOREACH;
57 | use const T_FUNCTION;
58 | use const T_GLOBAL;
59 | use const T_GOTO;
60 | use const T_HALT_COMPILER;
61 | use const T_IF;
62 | use const T_IMPLEMENTS;
63 | use const T_INCLUDE;
64 | use const T_INCLUDE_ONCE;
65 | use const T_INLINE_HTML;
66 | use const T_INSTANCEOF;
67 | use const T_INSTEADOF;
68 | use const T_INTERFACE;
69 | use const T_ISSET;
70 | use const T_LIST;
71 | use const T_LNUMBER;
72 | use const T_LOGICAL_AND;
73 | use const T_LOGICAL_OR;
74 | use const T_LOGICAL_XOR;
75 | use const T_MATCH;
76 | use const T_NAMESPACE;
77 | use const T_NEW;
78 | use const T_OPEN_TAG;
79 | use const T_OPEN_TAG_WITH_ECHO;
80 | use const T_PRINT;
81 | use const T_PRIVATE;
82 | use const T_PROTECTED;
83 | use const T_PUBLIC;
84 | use const T_READONLY;
85 | use const T_REQUIRE;
86 | use const T_REQUIRE_ONCE;
87 | use const T_RETURN;
88 | use const T_STATIC;
89 | use const T_STRING;
90 | use const T_SWITCH;
91 | use const T_THROW;
92 | use const T_TRAIT;
93 | use const T_TRY;
94 | use const T_UNSET;
95 | use const T_USE;
96 | use const T_VAR;
97 | use const T_VARIABLE;
98 | use const T_WHILE;
99 | use const T_WHITESPACE;
100 | use const T_YIELD;
101 | use const T_YIELD_FROM;
102 |
103 |
104 | class SourceHighlighter
105 | {
106 | public const PHP_TAG = 'php-tag';
107 | public const PHP_KEYWORD = 'php-kw';
108 | public const PHP_NUMBER = 'php-num';
109 | public const PHP_STRING = 'php-str';
110 | public const PHP_VARIABLE = 'php-var';
111 | public const PHP_COMMENT = 'php-comment';
112 |
113 | /** @var string[] indexed by [tokenId] */
114 | public array $tokenClass = [
115 | T_OPEN_TAG => self::PHP_TAG,
116 | T_OPEN_TAG_WITH_ECHO => self::PHP_TAG,
117 | T_CLOSE_TAG => self::PHP_TAG,
118 | T_INCLUDE => self::PHP_KEYWORD,
119 | T_INCLUDE_ONCE => self::PHP_KEYWORD,
120 | T_REQUIRE => self::PHP_KEYWORD,
121 | T_REQUIRE_ONCE => self::PHP_KEYWORD,
122 | T_LOGICAL_OR => self::PHP_KEYWORD,
123 | T_LOGICAL_XOR => self::PHP_KEYWORD,
124 | T_LOGICAL_AND => self::PHP_KEYWORD,
125 | T_PRINT => self::PHP_KEYWORD,
126 | T_YIELD => self::PHP_KEYWORD,
127 | T_YIELD_FROM => self::PHP_KEYWORD,
128 | T_INSTANCEOF => self::PHP_KEYWORD,
129 | T_NEW => self::PHP_KEYWORD,
130 | T_CLONE => self::PHP_KEYWORD,
131 | T_ELSEIF => self::PHP_KEYWORD,
132 | T_ELSE => self::PHP_KEYWORD,
133 | T_EVAL => self::PHP_KEYWORD,
134 | T_EXIT => self::PHP_KEYWORD,
135 | T_IF => self::PHP_KEYWORD,
136 | T_ENDIF => self::PHP_KEYWORD,
137 | T_ECHO => self::PHP_KEYWORD,
138 | T_DO => self::PHP_KEYWORD,
139 | T_WHILE => self::PHP_KEYWORD,
140 | T_ENDWHILE => self::PHP_KEYWORD,
141 | T_FOR => self::PHP_KEYWORD,
142 | T_ENDFOR => self::PHP_KEYWORD,
143 | T_FOREACH => self::PHP_KEYWORD,
144 | T_ENDFOREACH => self::PHP_KEYWORD,
145 | T_DECLARE => self::PHP_KEYWORD,
146 | T_ENDDECLARE => self::PHP_KEYWORD,
147 | T_AS => self::PHP_KEYWORD,
148 | T_SWITCH => self::PHP_KEYWORD,
149 | T_ENDSWITCH => self::PHP_KEYWORD,
150 | T_CASE => self::PHP_KEYWORD,
151 | T_DEFAULT => self::PHP_KEYWORD,
152 | T_BREAK => self::PHP_KEYWORD,
153 | T_CONTINUE => self::PHP_KEYWORD,
154 | T_GOTO => self::PHP_KEYWORD,
155 | T_FUNCTION => self::PHP_KEYWORD,
156 | T_FN => self::PHP_KEYWORD,
157 | T_CONST => self::PHP_KEYWORD,
158 | T_RETURN => self::PHP_KEYWORD,
159 | T_CATCH => self::PHP_KEYWORD,
160 | T_TRY => self::PHP_KEYWORD,
161 | T_FINALLY => self::PHP_KEYWORD,
162 | T_THROW => self::PHP_KEYWORD,
163 | T_USE => self::PHP_KEYWORD,
164 | T_INSTEADOF => self::PHP_KEYWORD,
165 | T_GLOBAL => self::PHP_KEYWORD,
166 | T_STATIC => self::PHP_KEYWORD,
167 | T_ABSTRACT => self::PHP_KEYWORD,
168 | T_FINAL => self::PHP_KEYWORD,
169 | T_PRIVATE => self::PHP_KEYWORD,
170 | T_PROTECTED => self::PHP_KEYWORD,
171 | T_PUBLIC => self::PHP_KEYWORD,
172 | T_VAR => self::PHP_KEYWORD,
173 | T_UNSET => self::PHP_KEYWORD,
174 | T_ISSET => self::PHP_KEYWORD,
175 | T_EMPTY => self::PHP_KEYWORD,
176 | T_HALT_COMPILER => self::PHP_KEYWORD,
177 | T_CLASS => self::PHP_KEYWORD,
178 | T_TRAIT => self::PHP_KEYWORD,
179 | T_INTERFACE => self::PHP_KEYWORD,
180 | T_EXTENDS => self::PHP_KEYWORD,
181 | T_IMPLEMENTS => self::PHP_KEYWORD,
182 | T_LIST => self::PHP_KEYWORD,
183 | T_ARRAY => self::PHP_KEYWORD,
184 | T_NAMESPACE => self::PHP_KEYWORD,
185 | T_ENUM => self::PHP_KEYWORD,
186 | T_READONLY => self::PHP_KEYWORD,
187 | T_MATCH => self::PHP_KEYWORD,
188 | T_LNUMBER => self::PHP_NUMBER,
189 | T_DNUMBER => self::PHP_NUMBER,
190 | T_CONSTANT_ENCAPSED_STRING => self::PHP_STRING,
191 | T_ENCAPSED_AND_WHITESPACE => self::PHP_STRING,
192 | T_VARIABLE => self::PHP_VARIABLE,
193 | T_COMMENT => self::PHP_COMMENT,
194 | T_DOC_COMMENT => self::PHP_COMMENT,
195 | ];
196 |
197 | /** @var string[] indexed by [identifierName] */
198 | public array $identifierClass = [
199 | 'true' => self::PHP_KEYWORD,
200 | 'false' => self::PHP_KEYWORD,
201 | 'null' => self::PHP_KEYWORD,
202 | ];
203 |
204 |
205 | public function __construct(
206 | protected OutputStyle $output,
207 | ) {
208 | }
209 |
210 |
211 | public function highlight(string $path): string
212 | {
213 | $source = FileSystem::read($path);
214 | $align = strlen(strval(1 + substr_count($source, "\n")));
215 | $lineStart = "<tr id=\"%1\$d\" class=\"source-line\"><td><a class=\"source-lineNum\" href=\"#%1\$d\">%1\${$align}d: </a></td><td>";
216 | $lineEnd = '</td></tr>';
217 |
218 | $line = 1;
219 | $out = sprintf($lineStart, $line);
220 |
221 | foreach ($this->tokenize($path, $source) as $id => $text) {
222 | if ($text === "\n") {
223 | $out .= $lineEnd . sprintf($lineStart, ++$line);
224 |
225 | } else {
226 | $html = htmlspecialchars($text);
227 | $class = $this->tokenClass[$id] ?? ($id === T_STRING ? $this->identifierClass[strtolower($text)] ?? null : null);
228 | $out .= $class ? "<span class=\"{$class}\">{$html}</span>" : $html;
229 | }
230 | }
231 |
232 | return $out . $lineEnd;
233 | }
234 |
235 |
236 | /**
237 | * @return iterable<int, string>
238 | */
239 | protected function tokenize(string $path, string $source): iterable
240 | {
241 | try {
242 | $tokens = PhpToken::tokenize($source, TOKEN_PARSE);
243 |
244 | } catch (\ParseError $e) {
245 | $this->output->newLine();
246 | $this->output->warning(sprintf("Parse error in %s:%d\n%s", $path, $e->getLine(), $e->getMessage()));
247 | $tokens = [new PhpToken(T_INLINE_HTML, $source)];
248 | }
249 |
250 | foreach ($tokens as $token) {
251 | $lines = explode("\n", $token->text);
252 | $lastLine = count($lines) - 1;
253 |
254 | foreach ($lines as $i => $line) {
255 | yield $token->id => $line;
256 |
257 | if ($i !== $lastLine) {
258 | yield T_WHITESPACE => "\n";
259 | }
260 | }
261 | }
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/src/Renderer/UrlGenerator.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Renderer;
4 |
5 | use ApiGen\Index\NamespaceIndex;
6 | use ApiGen\Info\AliasInfo;
7 | use ApiGen\Info\ClassLikeInfo;
8 | use ApiGen\Info\ConstantInfo;
9 | use ApiGen\Info\EnumCaseInfo;
10 | use ApiGen\Info\FunctionInfo;
11 | use ApiGen\Info\MemberInfo;
12 | use ApiGen\Info\MethodInfo;
13 | use ApiGen\Info\ParameterInfo;
14 | use ApiGen\Info\PropertyInfo;
15 |
16 | use function assert;
17 | use function get_debug_type;
18 | use function sprintf;
19 | use function str_starts_with;
20 | use function strlen;
21 | use function strrpos;
22 | use function strtr;
23 | use function substr;
24 |
25 | use const DIRECTORY_SEPARATOR;
26 |
27 |
28 | class UrlGenerator
29 | {
30 | public function __construct(
31 | protected string $baseDir,
32 | protected string $baseUrl,
33 | ) {
34 | }
35 |
36 |
37 | public function getRelativePath(string $path): string
38 | {
39 | if (str_starts_with($path, $this->baseDir)) {
40 | return substr($path, strlen($this->baseDir) + 1);
41 |
42 | } else {
43 | throw new \LogicException("{$path} does not start with {$this->baseDir}");
44 | }
45 | }
46 |
47 |
48 | public function getAssetUrl(string $name): string
49 | {
50 | return $this->baseUrl . $this->getAssetPath($name);
51 | }
52 |
53 |
54 | public function getAssetPath(string $name): string
55 | {
56 | return "assets/$name";
57 | }
58 |
59 |
60 | public function getIndexUrl(): string
61 | {
62 | return $this->baseUrl . $this->getIndexPath();
63 | }
64 |
65 |
66 | public function getIndexPath(): string
67 | {
68 | return 'index.html';
69 | }
70 |
71 |
72 | public function getTreeUrl(): string
73 | {
74 | return $this->baseUrl . $this->getTreePath();
75 | }
76 |
77 |
78 | public function getTreePath(): string
79 | {
80 | return 'tree.html';
81 | }
82 |
83 |
84 | public function getSitemapPath(): string
85 | {
86 | return 'sitemap.xml';
87 | }
88 |
89 |
90 | public function getSitemapUrl(): string
91 | {
92 | return $this->baseUrl . $this->getSitemapPath();
93 | }
94 |
95 |
96 | public function getNamespaceUrl(NamespaceIndex $namespace): string
97 | {
98 | return $this->baseUrl . $this->getNamespacePath($namespace);
99 | }
100 |
101 |
102 | public function getNamespacePath(NamespaceIndex $namespace): string
103 | {
104 | return 'namespace-' . strtr($namespace->name->full ?: 'none', '\\', '.') . '.html';
105 | }
106 |
107 |
108 | public function getClassLikeUrl(ClassLikeInfo $classLike): string
109 | {
110 | return $this->baseUrl . $this->getClassLikePath($classLike);
111 | }
112 |
113 |
114 | public function getClassLikePath(ClassLikeInfo $classLike): string
115 | {
116 | return strtr($classLike->name->full, '\\', '.') . '.html';
117 | }
118 |
119 |
120 | public function getClassLikeSourceUrl(ClassLikeInfo $classLike): string
121 | {
122 | assert($classLike->file !== null);
123 | return $this->getSourceUrl($classLike->file, $classLike->startLine, null); // intentionally not passing endLine
124 | }
125 |
126 |
127 | public function getMemberUrl(ClassLikeInfo $classLike, MemberInfo $member): string
128 | {
129 | return $this->getClassLikeUrl($classLike) . '#' . $this->getMemberAnchor($member);
130 | }
131 |
132 |
133 | public function getMemberAnchor(MemberInfo $member): string
134 | {
135 | if ($member instanceof ConstantInfo || $member instanceof EnumCaseInfo) {
136 | return $member->name;
137 |
138 | } elseif ($member instanceof PropertyInfo) {
139 | return '#39; . $member->name;
140 |
141 | } elseif ($member instanceof MethodInfo) {
142 | return '_' . $member->name;
143 |
144 | } else {
145 | throw new \LogicException(sprintf('Unexpected member type %s', get_debug_type($member)));
146 | }
147 | }
148 |
149 |
150 | public function getMemberSourceUrl(ClassLikeInfo $classLike, MemberInfo $member): string
151 | {
152 | assert($classLike->file !== null);
153 | return $this->getSourceUrl($classLike->file, $member->startLine, $member->endLine);
154 | }
155 |
156 |
157 | public function getAliasUrl(ClassLikeInfo $classLike, AliasInfo $alias): string
158 | {
159 | return $this->getClassLikeUrl($classLike) . '#' . $this->getAliasAnchor($alias);
160 | }
161 |
162 |
163 | public function getAliasAnchor(AliasInfo $alias): string
164 | {
165 | return '~' . $alias->name;
166 | }
167 |
168 |
169 | public function getAliasSourceUrl(ClassLikeInfo $classLike, AliasInfo $alias): string
170 | {
171 | assert($classLike->file !== null);
172 | return $this->getSourceUrl($classLike->file, $alias->startLine, $alias->endLine);
173 | }
174 |
175 |
176 | public function getFunctionUrl(FunctionInfo $function): string
177 | {
178 | return $this->baseUrl . $this->getFunctionPath($function);
179 | }
180 |
181 |
182 | public function getFunctionPath(FunctionInfo $function): string
183 | {
184 | return 'function-' . strtr($function->name->full, '\\', '.') . '.html';
185 | }
186 |
187 |
188 | public function getFunctionSourceUrl(FunctionInfo $function): string
189 | {
190 | assert($function->file !== null);
191 | return $this->getSourceUrl($function->file, $function->startLine, $function->endLine);
192 | }
193 |
194 |
195 | public function getParameterAnchor(ParameterInfo $parameter): string
196 | {
197 | return '#39; . $parameter->name;
198 | }
199 |
200 |
201 | public function getSourceUrl(string $path, ?int $startLine, ?int $endLine): string
202 | {
203 | if ($startLine === null) {
204 | $fragment = '';
205 |
206 | } elseif ($endLine === null || $endLine === $startLine) {
207 | $fragment = "#$startLine";
208 |
209 | } else {
210 | $fragment = "#$startLine-$endLine";
211 | }
212 |
213 | return $this->baseUrl . $this->getSourcePath($path) . $fragment;
214 | }
215 |
216 |
217 | public function getSourcePath(string $path): string
218 | {
219 | $relativePath = $this->getRelativePath($path);
220 | $relativePathWithoutExtension = substr($relativePath, 0, strrpos($relativePath, '.') ?: null);
221 | return 'source-' . strtr($relativePathWithoutExtension, DIRECTORY_SEPARATOR, '.') . '.html';
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/Scheduler.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen;
4 |
5 | use ApiGen\Task\Task;
6 |
7 |
8 | /**
9 | * @template TTask of Task
10 | * @template TResult
11 | */
12 | interface Scheduler
13 | {
14 | /**
15 | * @param TTask $task
16 | */
17 | public function schedule(Task $task): void;
18 |
19 |
20 | /**
21 | * @return iterable<TTask, TResult>
22 | */
23 | public function process(): iterable;
24 | }
25 |
--------------------------------------------------------------------------------
/src/Scheduler/ExecScheduler.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Scheduler;
4 |
5 | use ApiGen\Helpers;
6 | use ApiGen\Task\Task;
7 | use ApiGen\Task\TaskHandler;
8 | use ApiGen\Task\TaskHandlerFactory;
9 | use Composer\Autoload\ClassLoader;
10 | use Nette\DI\Container;
11 |
12 | use function dirname;
13 | use function proc_close;
14 | use function proc_open;
15 | use function sprintf;
16 | use function var_export;
17 |
18 | use const PHP_BINARY;
19 | use const PHP_OS_FAMILY;
20 | use const STDERR;
21 |
22 |
23 | /**
24 | * @template TTask of Task
25 | * @template TResult
26 | * @template TContext
27 | * @extends WorkerScheduler<TTask, TResult>
28 | */
29 | class ExecScheduler extends WorkerScheduler
30 | {
31 | /** @var resource[] $workers indexed by [workerId] */
32 | protected array $workers = [];
33 |
34 |
35 | /**
36 | * @param class-string<Container> $containerClass
37 | * @param array<string, mixed> $containerParameters
38 | * @param class-string<TaskHandlerFactory<TContext, TaskHandler<TTask, TResult>>> $handlerFactoryClass
39 | * @param TContext $context
40 | */
41 | public function __construct(
42 | protected string $containerClass,
43 | protected array $containerParameters,
44 | protected string $handlerFactoryClass,
45 | protected mixed $context,
46 | int $workerCount,
47 | ) {
48 | parent::__construct($workerCount);
49 | }
50 |
51 |
52 | protected function start(): void
53 | {
54 | $command = [
55 | PHP_BINARY,
56 | '--run',
57 | sprintf('require %s;', var_export(__DIR__ . '/worker.php', return: true)),
58 | '--',
59 | dirname(Helpers::classLikePath(ClassLoader::class), 2) . '/autoload.php',
60 | Helpers::classLikePath($this->containerClass),
61 | $this->containerClass,
62 | $this->handlerFactoryClass,
63 | ];
64 |
65 | $descriptors = [
66 | PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'r'],
67 | PHP_OS_FAMILY === 'Windows' ? ['socket'] : ['pipe', 'w'],
68 | STDERR,
69 | ];
70 |
71 | for ($workerId = 0; $workerId < $this->workerCount; $workerId++) {
72 | $workerProcess = proc_open($command, $descriptors, $pipes);
73 |
74 | if ($workerProcess === false) {
75 | throw new \RuntimeException('Failed to start worker process, try running ApiGen with --workers 1');
76 | }
77 |
78 | $this->workers[$workerId] = $workerProcess;
79 | $this->workerWritableStreams[$workerId] = $pipes[0];
80 | $this->workerReadableStreams[$workerId] = $pipes[1];
81 | self::writeMessage($this->workerWritableStreams[$workerId], $this->containerParameters);
82 | self::writeMessage($this->workerWritableStreams[$workerId], $this->context);
83 | }
84 | }
85 |
86 |
87 | protected function stop(): void
88 | {
89 | foreach ($this->workers as $worker) {
90 | if (proc_close($worker) !== 0) {
91 | throw new \RuntimeException('Worker process crashed, try running ApiGen with --workers 1');
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Scheduler/ForkScheduler.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Scheduler;
4 |
5 | use ApiGen\Task\Task;
6 | use ApiGen\Task\TaskHandler;
7 |
8 | use function fclose;
9 | use function pcntl_fork;
10 | use function pcntl_waitpid;
11 | use function pcntl_wexitstatus;
12 | use function pcntl_wifexited;
13 | use function pcntl_wifsignaled;
14 | use function pcntl_wtermsig;
15 | use function stream_socket_pair;
16 |
17 | use const STREAM_IPPROTO_IP;
18 | use const STREAM_PF_UNIX;
19 | use const STREAM_SOCK_STREAM;
20 |
21 |
22 | /**
23 | * @template TTask of Task
24 | * @template TResult
25 | * @extends WorkerScheduler<TTask, TResult>
26 | */
27 | class ForkScheduler extends WorkerScheduler
28 | {
29 | /** @var int[] $workers indexed by [workerId] */
30 | protected array $workers = [];
31 |
32 |
33 | /**
34 | * @param TaskHandler<TTask, TResult> $handler
35 | */
36 | public function __construct(
37 | protected TaskHandler $handler,
38 | int $workerCount,
39 | ) {
40 | parent::__construct($workerCount);
41 | }
42 |
43 |
44 | protected function start(): void
45 | {
46 | for ($workerId = 0; $workerId < $this->workerCount; $workerId++) {
47 | $pair = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
48 |
49 | if ($pair === false) {
50 | throw new \RuntimeException('Failed to create socket pair, try running ApiGen with --workers 1');
51 | }
52 |
53 | $pid = pcntl_fork();
54 |
55 | if ($pid < 0) {
56 | throw new \RuntimeException('Failed to fork process, try running ApiGen with --workers 1');
57 |
58 | } elseif ($pid === 0) {
59 | fclose($pair[0]);
60 | self::workerLoop($this->handler, $pair[1], $pair[1]);
61 | exit(0);
62 |
63 | } else {
64 | fclose($pair[1]);
65 | $this->workers[$workerId] = $pid;
66 | $this->workerReadableStreams[$workerId] = $pair[0];
67 | $this->workerWritableStreams[$workerId] = $pair[0];
68 | }
69 | }
70 | }
71 |
72 |
73 | protected function stop(): void
74 | {
75 | foreach ($this->workerWritableStreams as $stream) {
76 | fclose($stream);
77 | }
78 |
79 | foreach ($this->workers as $pid) {
80 | pcntl_waitpid($pid, $status);
81 |
82 | if (pcntl_wifexited($status)) {
83 | if (($exitCode = pcntl_wexitstatus($status)) !== 0) {
84 | throw new \RuntimeException("Worker with PID $pid exited with code $exitCode, try running ApiGen with --workers 1");
85 | }
86 |
87 | } elseif (pcntl_wifsignaled($status)) {
88 | $signal = pcntl_wtermsig($status);
89 | throw new \RuntimeException("Worker with PID $pid was killed by signal $signal, try running ApiGen with --workers 1");
90 |
91 | } else {
92 | throw new \LogicException('Invalid worker state');
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Scheduler/SchedulerFactory.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Scheduler;
4 |
5 | use ApiGen\Scheduler;
6 | use ApiGen\Task\Task;
7 | use ApiGen\Task\TaskHandler;
8 | use ApiGen\Task\TaskHandlerFactory;
9 | use Nette\DI\Container;
10 |
11 | use function extension_loaded;
12 | use function function_exists;
13 |
14 | use const PHP_SAPI;
15 |
16 |
17 | class SchedulerFactory
18 | {
19 | public function __construct(
20 | protected Container $container,
21 | protected int $workerCount,
22 | ) {
23 | }
24 |
25 |
26 | /**
27 | * @template TTask of Task
28 | * @template TResult
29 | * @template TContext
30 | *
31 | * @param class-string<TaskHandlerFactory<TContext, TaskHandler<TTask, TResult>>> $handlerFactoryType
32 | * @param TContext $context
33 | * @return Scheduler<TTask, TResult>
34 | */
35 | public function create(string $handlerFactoryType, mixed $context): Scheduler
36 | {
37 | if ($this->workerCount > 1 && PHP_SAPI === 'cli') {
38 | if (extension_loaded('pcntl')) {
39 | $handler = $this->createHandler($handlerFactoryType, $context);
40 | return new ForkScheduler($handler, $this->workerCount);
41 |
42 | } elseif (function_exists('proc_open')) {
43 | return new ExecScheduler($this->container::class, $this->container->getParameters(), $handlerFactoryType, $context, $this->workerCount);
44 | }
45 | }
46 |
47 | $handler = $this->createHandler($handlerFactoryType, $context);
48 | return new SimpleScheduler($handler);
49 | }
50 |
51 |
52 | /**
53 | * @template TTask of Task
54 | * @template TResult
55 | * @template TContext
56 | *
57 | * @param class-string<TaskHandlerFactory<TContext, TaskHandler<TTask, TResult>>> $handlerFactoryType
58 | * @param TContext $context
59 | * @return TaskHandler<TTask, TResult>
60 | */
61 | private function createHandler(string $handlerFactoryType, mixed $context): TaskHandler
62 | {
63 | $factory = $this->container->getByType($handlerFactoryType);
64 | return $factory->create($context);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Scheduler/SimpleScheduler.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Scheduler;
4 |
5 | use ApiGen\Scheduler;
6 | use ApiGen\Task\Task;
7 | use ApiGen\Task\TaskHandler;
8 | use SplQueue;
9 |
10 |
11 | /**
12 | * @template TTask of Task
13 | * @template TResult
14 | * @implements Scheduler<TTask, TResult>
15 | */
16 | class SimpleScheduler implements Scheduler
17 | {
18 | /** @var SplQueue<TTask> */
19 | protected SplQueue $tasks;
20 |
21 |
22 | /**
23 | * @param TaskHandler<TTask, TResult> $handler
24 | */
25 | public function __construct(
26 | protected TaskHandler $handler,
27 | ) {
28 | $this->tasks = new SplQueue();
29 | }
30 |
31 |
32 | /**
33 | * @param TTask $task
34 | */
35 | public function schedule(Task $task): void
36 | {
37 | $this->tasks->enqueue($task);
38 | }
39 |
40 |
41 | /**
42 | * @return iterable<TTask, TResult>
43 | */
44 | public function process(): iterable
45 | {
46 | while (!$this->tasks->isEmpty()) {
47 | $task = $this->tasks->dequeue();
48 | $result = $this->handler->handle($task);
49 | yield $task => $result;
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Scheduler/WorkerScheduler.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Scheduler;
4 |
5 | use ApiGen\Scheduler;
6 | use ApiGen\Task\Task;
7 | use ApiGen\Task\TaskHandler;
8 | use SplQueue;
9 |
10 | use function array_fill_keys;
11 | use function array_key_first;
12 | use function array_keys;
13 | use function base64_decode;
14 | use function base64_encode;
15 | use function count;
16 | use function extension_loaded;
17 | use function fgets;
18 | use function fwrite;
19 | use function igbinary_serialize;
20 | use function igbinary_unserialize;
21 | use function serialize;
22 | use function stream_select;
23 | use function strlen;
24 | use function unserialize;
25 |
26 |
27 | /**
28 | * @template TTask of Task
29 | * @template TResult
30 | * @implements Scheduler<TTask, TResult>
31 | */
32 | abstract class WorkerScheduler implements Scheduler
33 | {
34 | protected const WORKER_CAPACITY_LIMIT = 8;
35 |
36 | /** @var SplQueue<TTask> queue of tasks which needs to be sent to workers */
37 | protected SplQueue $tasks;
38 |
39 | /** @var int total number of pending tasks (including those already sent to workers) */
40 | protected int $pendingTaskCount = 0;
41 |
42 | /** @var resource[] indexed by [workerId] */
43 | protected array $workerReadableStreams = [];
44 |
45 | /** @var resource[] indexed by [workerId] */
46 | protected array $workerWritableStreams = [];
47 |
48 |
49 | public function __construct(
50 | protected int $workerCount,
51 | ) {
52 | $this->tasks = new SplQueue();
53 | }
54 |
55 |
56 | /**
57 | * @param resource $stream
58 | */
59 | public static function writeMessage($stream, mixed $message): void
60 | {
61 | $serialized = extension_loaded('igbinary')
62 | ? igbinary_serialize($message) ?? throw new \LogicException('Failed to serialize message.')
63 | : serialize($message);
64 |
65 | $line = base64_encode($serialized) . "\n";
66 |
67 | if (fwrite($stream, $line) !== strlen($line)) {
68 | throw new \RuntimeException('Failed to write message to stream.');
69 | }
70 | }
71 |
72 |
73 | /**
74 | * @param resource $stream
75 | */
76 | public static function readMessage($stream): mixed
77 | {
78 | $line = fgets($stream);
79 |
80 | if ($line === false) {
81 | return null;
82 | }
83 |
84 | $serialized = base64_decode($line, strict: true);
85 |
86 | if ($serialized === false) {
87 | throw new \RuntimeException('Failed to decode message.');
88 | }
89 |
90 | return extension_loaded('igbinary')
91 | ? igbinary_unserialize($serialized)
92 | : unserialize($serialized);
93 | }
94 |
95 |
96 | /**
97 | * @template T2 of Task
98 | * @template R2
99 | *
100 | * @param TaskHandler<T2, R2> $handler
101 | * @param resource $inputStream
102 | * @param resource $outputStream
103 | */
104 | public static function workerLoop(TaskHandler $handler, $inputStream, $outputStream): void
105 | {
106 | while (($task = self::readMessage($inputStream)) !== null) {
107 | $result = $handler->handle($task);
108 | self::writeMessage($outputStream, [$task, $result]);
109 | }
110 | }
111 |
112 |
113 | /**
114 | * @param TTask $task
115 | */
116 | public function schedule(Task $task): void
117 | {
118 | $this->tasks->enqueue($task);
119 | $this->pendingTaskCount++;
120 | }
121 |
122 |
123 | /**
124 | * @return iterable<TTask, TResult>
125 | */
126 | public function process(): iterable
127 | {
128 | try {
129 | $this->start();
130 |
131 | $idleWorkers = array_fill_keys(array_keys($this->workerWritableStreams), self::WORKER_CAPACITY_LIMIT);
132 |
133 | while ($this->pendingTaskCount > 0) {
134 | while (count($idleWorkers) > 0 && !$this->tasks->isEmpty()) {
135 | $idleWorkerId = array_key_first($idleWorkers);
136 | $idleWorkerCapacity = $idleWorkers[$idleWorkerId];
137 | self::writeMessage($this->workerWritableStreams[$idleWorkerId], $this->tasks->dequeue());
138 | unset($idleWorkers[$idleWorkerId]);
139 |
140 | if ($idleWorkerCapacity > 1) {
141 | $idleWorkers[$idleWorkerId] = $idleWorkerCapacity - 1;
142 | }
143 | }
144 |
145 | $readable = $this->workerReadableStreams;
146 | $writable = null;
147 | $except = null;
148 | $changedCount = stream_select($readable, $writable, $except, null);
149 |
150 | if ($changedCount === false || $changedCount === 0) {
151 | throw new \RuntimeException('stream_select() failed.');
152 | }
153 |
154 | foreach ($readable as $workerId => $stream) {
155 | [$task, $result] = self::readMessage($stream) ?? throw new \RuntimeException('Failed to read message from worker.');
156 | $idleWorkers[$workerId] = ($idleWorkers[$workerId] ?? 0) + 1;
157 | $this->pendingTaskCount--;
158 | yield $task => $result;
159 | }
160 | }
161 |
162 | } finally {
163 | $this->pendingTaskCount = 0;
164 | $this->stop();
165 | }
166 | }
167 |
168 |
169 | abstract protected function start(): void;
170 |
171 |
172 | abstract protected function stop(): void;
173 | }
174 |
--------------------------------------------------------------------------------
/src/Scheduler/worker.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Scheduler;
4 |
5 | use ApiGen\Bootstrap;
6 | use ApiGen\Task\Task;
7 | use ApiGen\Task\TaskHandler;
8 | use ApiGen\Task\TaskHandlerFactory;
9 | use Nette\DI\Container;
10 | use Symfony\Component\Console\Input\ArgvInput;
11 | use Symfony\Component\Console\Output\StreamOutput;
12 | use Symfony\Component\Console\Style\SymfonyStyle;
13 |
14 | use function ini_set;
15 |
16 | use const STDERR;
17 | use const STDIN;
18 | use const STDOUT;
19 |
20 |
21 | if (count($argv) !== 5) {
22 | throw new \RuntimeException('Invalid number of arguments.');
23 | }
24 |
25 | /** @var string $autoloadPath */
26 | $autoloadPath = $argv[1];
27 |
28 | /** @var string $containerClassPath */
29 | $containerClassPath = $argv[2];
30 |
31 | /** @var class-string<Container> $containerClassName */
32 | $containerClassName = $argv[3];
33 |
34 | /** @var class-string<TaskHandlerFactory<mixed, TaskHandler<Task, mixed>>> $handlerFactoryClassName */
35 | $handlerFactoryClassName = $argv[4];
36 |
37 | require $autoloadPath;
38 | Bootstrap::configureErrorHandling();
39 |
40 | require $containerClassPath;
41 |
42 | $containerParameters = WorkerScheduler::readMessage(STDIN);
43 | $container = new $containerClassName($containerParameters);
44 | $container->addService('symfonyConsole.output', new SymfonyStyle(new ArgvInput(), new StreamOutput(STDERR)));
45 | ini_set('memory_limit', $container->getParameter('memoryLimit'));
46 |
47 | $context = WorkerScheduler::readMessage(STDIN);
48 | $handlerFactory = $container->getByType($handlerFactoryClassName);
49 | $handler = $handlerFactory->create($context);
50 | WorkerScheduler::workerLoop($handler, STDIN, STDOUT);
51 |
--------------------------------------------------------------------------------
/src/Task/Task.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Task;
4 |
5 |
6 | interface Task
7 | {
8 | }
9 |
--------------------------------------------------------------------------------
/src/Task/TaskHandler.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Task;
4 |
5 |
6 | /**
7 | * @template-contravariant T of Task
8 | * @template-covariant R
9 | */
10 | interface TaskHandler
11 | {
12 | /**
13 | * @param T $task
14 | * @return R
15 | */
16 | public function handle(Task $task): mixed;
17 | }
18 |
--------------------------------------------------------------------------------
/src/Task/TaskHandlerFactory.php:
--------------------------------------------------------------------------------
1 | <?php declare(strict_types = 1);
2 |
3 | namespace ApiGen\Task;
4 |
5 |
6 | /**
7 | * @template TContext
8 | * @template-covariant THandler of TaskHandler<never, mixed>
9 | */
10 | interface TaskHandlerFactory
11 | {
12 | /**
13 | * @param TContext $context
14 | * @return THandler
15 | */
16 | public function create(mixed $context): TaskHandler;
17 | }
18 |
--------------------------------------------------------------------------------