OXIESEC PANEL
- Current Dir:
/
/
var
/
www
/
cream
/
Xpress
/
vendor
/
aws
/
aws-sdk-php
/
build
/
docs
/
classes
Server IP: 139.59.38.164
Upload:
Create Dir:
Name
Size
Modified
Perms
📁
..
-
03/05/2025 10:57:01 AM
rwxr-xr-x
📄
CodeSnippetGenerator.php
5.16 KB
05/19/2025 10:07:17 AM
rw-r--r--
📄
DocsBuilder.php
41.25 KB
05/19/2025 10:07:17 AM
rw-r--r--
📄
EventStreamExampleBuilder.php
2.26 KB
05/19/2025 10:07:17 AM
rw-r--r--
📄
ExampleBuilder.php
3.44 KB
05/19/2025 10:07:17 AM
rw-r--r--
📄
HtmlDocument.php
3.21 KB
05/19/2025 10:07:17 AM
rw-r--r--
📄
RedirectMapBuilder.php
3.53 KB
05/19/2025 10:07:17 AM
rw-r--r--
📄
Service.php
2.8 KB
05/19/2025 10:07:17 AM
rw-r--r--
📄
ShapeIterator.php
6.68 KB
05/19/2025 10:07:17 AM
rw-r--r--
Editing: DocsBuilder.php
Close
<?php namespace Aws\Build\Docs; use Aws\Api\AbstractModel; use Aws\Api\ApiProvider; use Aws\Api\ListShape; use Aws\Api\MapShape; use Aws\Api\Operation; use Aws\Api\Service as Api; use Aws\Api\StructureShape; use Aws\Api\DocModel; use TokenReflection\Broker; use TokenReflection\ReflectionBase; use TokenReflection\ReflectionFunction; use TokenReflection\ReflectionMethod; /** * Builds documentation for a given service. * * @internal */ class DocsBuilder { /** @var string HTML template to replace {{ contents }} */ private $template; /** @var string */ private $outputDir; /** @var ApiProvider */ private $apiProvider; /** @var \SplObjectStorage Hash of members to skip when generating shape docs. */ private $skipMembers; /** @var string */ private $baseUrl; /** @var string[] */ private $quickLinks; /** @var string[] */ private $sources; /** @var bool[][][] */ private $issues = []; /** @var bool Enables writing of build-issues.log file when set. */ private $issueLoggingEnabled; /** @var array Printable error names for build-issues.log file */ private static $ERROR_PRINT_NAMES =[ E_ERROR => 'Error', E_WARNING => 'Warning', E_PARSE => 'Parse Error', E_NOTICE => 'Notice', E_CORE_ERROR => 'Core Error', E_CORE_WARNING => 'Core Warning', E_COMPILE_ERROR => 'Compile Error', E_COMPILE_WARNING => 'Compile Warning', E_USER_ERROR => 'User Error', E_USER_WARNING => 'User Warning', E_USER_NOTICE => 'User Notice', E_STRICT => 'Strict Notice', E_RECOVERABLE_ERROR => 'Recoverable Error' ]; public function __construct( ApiProvider $provider, $outputDir, $template, $baseUrl, array $quickLinks, array $sources, $issueLoggingEnabled = false ) { $this->apiProvider = $provider; $this->outputDir = $outputDir; $this->template = $template; $this->baseUrl = $baseUrl; $this->quickLinks = $quickLinks; $this->sources = $sources; $this->issueLoggingEnabled = $issueLoggingEnabled; } public function build() { $this->skipMembers = new \SplObjectStorage(); fwrite(STDOUT, "Parsing available service API versions...\n"); // Collect versions $services = []; $aliases = []; foreach ($this->gatherServiceVersions() as $name => $data) { // Skip "latest" unset($data['versions']['latest']); $ns = $data['namespace']; foreach ($data['versions'] as $alias => $version) { list($api, $docModel) = call_user_func( "Aws\\{$ns}\\{$ns}Client::applyDocFilters", ApiProvider::resolve($this->apiProvider, 'api', $name, $version), ApiProvider::resolve($this->apiProvider, 'docs', $name, $version) ); $service = new Service($api, $docModel); $title = isset($service->shortTitle) && $service->shortTitle !== '' ? $service->shortTitle : $service->title; if (isset($services[$title][$version])) { if (empty($aliases[$title][$version])) { $aliases[$title][$version] = []; } $aliases[$title][$version] []= $alias; continue; } $examples = $this->loadExamples($name, $version); if (method_exists("Aws\\{$ns}\\{$ns}Client", 'addDocExamples')) { $examples = call_user_func( "Aws\\{$ns}\\{$ns}Client::addDocExamples", $examples ); } $this->renderService($service, $examples); $services[$title][$version] = $service; } } uasort($services, function($a, $b) { $serviceA = current($a); $serviceB = current($b); return strcasecmp($serviceA->namespace, $serviceB->namespace); }); $this->updateHomepage($services); $this->updateClients($services); $this->updateExceptions($services); $this->updateAliases($services, $aliases); $this->updateSitemap(); $this->updateSearch($services); if ($this->issueLoggingEnabled) { $this->updateIssues(); } } private function updateHomepage(array $services) { $this->updateServiceTable($services); $this->updateQuickLinks($services); } private function updateServiceTable(array $services) { fwrite(STDOUT, "Building homepage service table\n"); // Build up the list of services for the homepage. $servicesTable = ''; foreach ($services as $versions) { krsort($versions); $service = reset($versions); $servicesTable .= "<tr><td><a href=\"{$service->serviceLink}\">{$service->title}</a></td>"; $servicesTable .= "<td><a title=\"{$service->client}\" href=\"{$service->clientLink}\">{$service->client}</a></td>"; $servicesTable .= "<td class=\"nowrap\"><ul>"; $latest = count($versions) > 1 ? ' (latest)' : ''; foreach ($versions as $sv) { $servicesTable .= "<li><a href=\"{$sv->serviceLink}\">{$sv->version} {$latest}</a></li>"; $latest = ''; } $servicesTable .= "</ul></td></tr>"; } $this->replaceInner('index', $servicesTable, ':services:'); } private function updateQuickLinks(array $services) { fwrite(STDOUT, "Updating homepage quick links\n"); // Determine which services in the provided array should have a quick link $services = array_filter($services, function (array $versions) { return 0 < count(array_filter($versions, function (Service $service) { return in_array($service->name, $this->quickLinks); })); }); // Drop all but the latest version of each service from the array $services = array_map(function (array $versions) { return array_shift($versions); }, $services); $serviceMap = []; foreach ($services as $service) { $isOlderVersion = isset($serviceMap[$service->name]) && $service->version < $serviceMap[$service->name]->version; if ($isOlderVersion) { continue; } $serviceMap[$service->name] = $service; } $services = array_values($serviceMap); // Sort the services in the order provided in the config usort($services, function (Service $a, Service $b) { return array_search($a->name, $this->quickLinks) - array_search($b->name, $this->quickLinks); }); // Build up the quick links for the home page $quickLinks = ''; foreach ($services as $service) { $title = $service->shortTitle ?: $service->title; $quickLinks .= <<<EOT <div class="col-md-3"> <a class="btn btn-default btn-lg btn-block lead" href="{$service->serviceLink}" role="button"> <span class="awsicon awsicon-{$service->name}"></span> {$title} </a> </div> EOT; } $this->replaceInner('index', $quickLinks, ':quickLinks:'); } private function updateIssues() { $text = ''; foreach ($this->issues as $level => $levelIssues) { foreach ($levelIssues as $serviceName => $versions) { foreach ($versions as $serviceVersion => $messages) { foreach (array_keys($messages) as $message) { if (!empty($text)) { $text .= PHP_EOL; } $levelName = isset(DocsBuilder::$ERROR_PRINT_NAMES[$level]) ? DocsBuilder::$ERROR_PRINT_NAMES[$level] : 'Unknown'; $text .= '[' . date("Y-m-d H:i:s (T)") . '] ' . '[' . $levelName . '] ' . $serviceName . '-' . $serviceVersion . ': ' . $message; } } } } return (bool) file_put_contents("{$this->outputDir}/build-issues.log", $text); } private function renderService(Service $service, $examples) { $html = new HtmlDocument; $html->open('div', 'page-header'); $html->elem('h1', 'phpdocumentor-content__title', "$service->title <small>{$service->version}</small>"); $html->close(); $metadata = <<<EOT <dd><strong>Client:</strong> <a href="{$service->clientLink}">{$service->client}</a></dd> <dd><strong>Service ID:</strong> {$service->name}</dd> <dd><strong>Version:</strong> {$service->version}</dd> EOT; $html->elem('dl', 'tree well', $metadata); $desc = <<<EOT This page describes the parameters and results for the operations of the {$service->fullTitle}, and shows how to use the <a href="{$service->clientLink}">{$service->client}</a> object to call the described operations. This documentation is specific to the {$service->version} API version of the service. EOT; $html->elem('p', null, $desc); $html->elem('h2', null, 'Operation Summary'); $desc = <<<EOT Each of the following operations can be created from a client using <code class="phpdocumentor-code">\$client->getCommand('CommandName')</code>, where "CommandName" is the name of one of the following operations. Note: a command is a value that encapsulates an operation and the parameters used to create an HTTP request. EOT; $html->elem('p', null, $desc); $desc = <<<EOT You can also create and send a command immediately using the magic methods available on a client object: <code class="phpdocumentor-code">\$client->commandName(/* parameters */)</code>. You can send the command asynchronously (returning a promise) by appending the word "Async" to the operation name: <code class="phpdocumentor-code">\$client->commandNameAsync(/* parameters */)</code>. EOT; $html->elem('p', null, $desc); $html->append($this->createHtmlForToc($service, $service->api->getOperations())); $this->createHtmlForPaginators($html, $service->api); $this->createHtmlForWaiters($html, $service->api); $html->open('section', 'phpdocumentor-methods'); $html->section(2, 'Operations'); foreach ($service->api->getOperations() as $opName => $operation) { $html->append($this->createHtmlForOperation( $service, $opName, $operation, isset($examples[$opName]) ? $examples[$opName] : [] )); } $html->close(); //close operations section $html->open('section'); $html->section(2, 'Shapes'); $map = $service->api->getShapeMap(); $keys = array_keys($service->api['shapes']); sort($keys); foreach ($keys as $name) { $shape = $map->resolve(['shape' => $name]); // Do not render top-level input or output shapes. if ($shape['type'] == 'structure' && !isset($this->skipMembers[$shape]) ) { $html->open('article', 'phpdocumentor-element'); $html->section(3, $name, 'shape', 'method-title'); $html->append($this->renderShape($service->docs, $shape)); $html->close(); } } $html->close(); $this->writeThemeFile( $service->serviceLink, "<section><article class=\"phpdocumentor-element\">{$html->render()}</article></section>"); } private function createHtmlForToc(Service $service, array $operations) { $html = new HtmlDocument(); $html->open('dl', 'phpdocumentor-table-of-contents'); foreach ($operations as $opName => $operation) { $item = '<a class="method-summary-link" href="#' . $html->slug($opName) . '"><strong>' . "{$opName}</strong> ( array \$params = [] )</a>"; if ($description = $service->docs->getOperationDocs($opName)) { $shortened = strip_tags($description); $shortened = strpos($shortened, '.') === false ? $shortened : substr($shortened, 0, strpos($shortened, '.') + 1); $item .= '<dd>' . $shortened . '</dd>'; } $html->elem('dt', 'phpdocumentor-table-of-contents__entry -method -public', $item); } $html->close(); return $html; } private function writeThemeFile($name, $contents) { $name = str_replace('.html', '', $name); $name .= '.html'; fwrite(STDOUT, "Writing file: {$name}.\n"); $updatedTemplate = str_replace('index.html#top', $name . '#top', $this->template); $html = str_replace('{{ contents }}', $contents, $updatedTemplate); return (bool) file_put_contents("{$this->outputDir}/{$name}", $html); } private function replaceInner($name, $replace, $search = '{{ contents }}') { $name = str_replace('.html', '', $name); fwrite(STDOUT, "Updating file: {$name}.\n"); $path = "{$this->outputDir}/{$name}.html"; $contents = file_get_contents($path); $contents = str_replace($search, $replace, $contents); file_put_contents($path, $contents); } private function gatherServiceVersions() { $manifest = __DIR__ . '/../../../src/data/manifest.json'; return json_decode(file_get_contents($manifest), true); } private function loadExamples($name, $version) { $path = __DIR__ . "/../../../src/data/{$name}/{$version}/examples-1.json"; try { return \Aws\load_compiled_json($path)['examples']; } catch (\InvalidArgumentException $e) { return []; } } private function updateClients(array $services) { fwrite(STDOUT, "Updating client pages with service links\n"); foreach ($services as $versions) { krsort($versions); $service = reset($versions); $html = '<h2>Supported API Versions</h2>'; $html .= <<<EOT <p>This class uses a <em>service description model</em> that is associated at runtime based on the <code class="phpdocumentor-code">version</code> option given when constructing the client. The <code class="phpdocumentor-code">version</code> option will determine which API operations, waiters, and paginators are available for a client. Creating a command or a specific API operation can be done using magic methods (e.g., <code class="phpdocumentor-code">\$client->commandName(/** parameters */)</code>, or using the <code class="phpdocumentor-code">\$client->getCommand</code> method of the client.</p> EOT; $html .= '<article class="api-version-list element-summary article-container"><ul>'; $latest = count($versions) > 1 ? ' (latest)' : ''; foreach ($versions as $sv) { $html .= "<li>"; $html .= "<p><a href=\"{$sv->serviceLink}\"><strong>{$sv->version} {$latest}</strong></a></p>"; $html .= "<ul class='supported-api-versions-methods my-container'>"; foreach (array_keys($sv->api->getOperations()) as $operation) { $html .= "<li class='my-col-full my-col-half my-col-third'>"; $html .= "<a href=\"{$sv->serviceLink}#" . strtolower($operation) . "\" style='white-space: nowrap;'>$operation</a>"; $html .= "</li>"; } $html .= "</ul>"; $html .= "</li>"; $latest = ''; } $html .= '</ul></article>'; $this->replaceInner($service->clientLink, $html, '<!-- api -->'); } } private function updateExceptions(array $services) { fwrite(STDOUT, "Updating exception pages with modeled exception data...\n"); foreach ($services as $versions) { krsort($versions); $service = reset($versions); $shapes = $service->api->getErrorShapes(); if (count($shapes) > 0) { $html = new HtmlDocument; $html->section(2, 'Expected Exception Codes'); $desc = <<<EOT The following are the known exception codes and corresponding data shapes that this service may return as part of an error response. EOT; $html->elem('div', null, $desc); foreach ($shapes as $shape) { if ($shape['type'] == 'structure' && !isset($this->skipMembers[$shape]) ) { $html->section(3, $shape->getName(), 'shape', 'method-title'); // Add error syntax $outputShapes = new ShapeIterator($shape, $service->docs); $outputExample = new ExampleBuilder($shape->getName(), false); foreach ($outputShapes as $outputShape) { $outputExample->addShape($outputShape); } $html->elem('pre', 'phpdocumentor-code', htmlentities($outputExample->getCode())); // Add member details $html->append($this->renderShape($service->docs, $shape)); } } $this->replaceInner($service->exceptionLink, $html->render(), '<!-- modeled_exceptions -->'); } } } private function updateAliases(array $services, array $compatibleVersions) { fwrite(STDOUT, "Updating redirects for forward-compatible service versions\n"); foreach ($compatibleVersions as $service => $aliasedVersions) { foreach ($aliasedVersions as $version => $aliases) { $redirectPage = <<<EOHTML <!DOCTYPE html> <html> <head> <!-- HTML meta refresh URL redirection --> <meta http-equiv="refresh" content="0; url={$services[$service][$version]->serviceLink}"> </head> </html> EOHTML; foreach ($aliases as $alias) { $redirectFrom = str_replace($version, $alias, $services[$service][$version]->serviceLink); file_put_contents("{$this->outputDir}/$redirectFrom", $redirectPage); } } } } private function updateSearch(array $services) { fwrite(STDOUT, "Updating search index\n"); $serviceIndex = $this->getServiceAutocompleteIndex($services); $jsonIndex = json_encode($this->utf8Encode($serviceIndex)); $jsonIndex = ',' . substr($jsonIndex, 1, -1); $currentSearchIndex = file_get_contents("{$this->outputDir}/js/searchIndex.js"); $insertPosition = strrpos($currentSearchIndex, ']'); $updatedIndex = substr_replace($currentSearchIndex, $jsonIndex, $insertPosition, 0); file_put_contents("{$this->outputDir}/js/searchIndex.js", $updatedIndex); } private function utf8Encode($mixed) { if (is_array($mixed)) { return array_map([$this, 'utf8Encode'], $mixed); } elseif (is_string($mixed)) { return utf8_encode($mixed); } elseif (empty($mixed)) { return ''; } throw new \InvalidArgumentException('Expecting string or array, got ' . gettype($mixed)); } private function getServiceAutocompleteIndex(array $services) { $autoComplete = []; // Drop all but the latest version of each service from the array $services = array_map(function (array $versions) { return array_shift($versions); }, $services); // Add operations from latest version of each service to autocomplete index foreach ($services as $service) { foreach ($service->api->getOperations() as $operation => $def) { $summary = $service->docs->getOperationDocs($operation); $autoComplete []= [ 'fqsen' => $service->namespace . '::' . lcfirst($operation), 'name' => $operation, 'url' => $service->serviceLink . '#' . strtolower($operation), 'summary' => $summary ? strip_tags($summary) : null ]; } } return $autoComplete; } private function getClassAutocompleteIndex(Broker $broker) { $methodsToSkip = $this->getMethodsToSkip(); $autoComplete = []; $classes = array_filter( array_values($broker->getClasses()), [$this, 'filterVisible'] ); foreach ($classes as $class) { // Add class to autocomplete index $autoComplete []= [ 'name' => $class->getName(), 'match' => $class->getShortName(), 'link' => 'class-' . str_replace('\\', '.', $class->getName()) . '.html', 'description' => $this->shiftDocCommentLine($class->getDocComment()), ]; $methods = array_filter( $class->getOwnMethods(\ReflectionMethod::IS_PUBLIC|\ReflectionMethod::IS_PROTECTED), [$this, 'filterVisible'] ); // Skip over methods implementing base interfaces or that start with // an underscore $methods = array_filter($methods, function (ReflectionMethod $method) use ($methodsToSkip) { $name = $method->getName(); return !in_array($name, $methodsToSkip) && $name[0] !== '_'; }); foreach ($methods as $method) { $autoComplete []= [ 'name' => $class->getName() . '::' . $method->getName(), 'match' => $method->getName(), 'link' => 'class-' . str_replace('\\', '.', $class->getName()) . '.html' . '#_' . $method->getName(), 'description' => '', ]; } } return $autoComplete; } private function getMethodsToSkip() { $interfacesToSkip = [ '\\ArrayAccess', '\\Countable', '\\Iterator', '\\IteratorAggregate', '\\JsonSerializable', '\\Serializable', ]; $methodsToSkip = []; foreach ($interfacesToSkip as $interfaceToSkip) { $methodsToSkip = array_merge( $methodsToSkip, array_map(function (\ReflectionMethod $method) { return $method->getName(); }, (new \ReflectionClass($interfaceToSkip))->getMethods()) ); } return $methodsToSkip; } private function getFunctionAutocompleteIndex(Broker $broker) { $functions = array_filter( array_values($broker->getFunctions()), [$this, 'filterVisible'] ); return array_map(function (ReflectionFunction $function) { return [ 'name' => $function->getName(), 'match' => $function->getShortName(), 'link' => 'function-' . str_replace('\\', '.', $function->getName()) . '.html', 'description' => $this->shiftDocCommentLine($function->getDocComment()), ]; }, $functions); } private function filterVisible(ReflectionBase $reflected) { $annotations = $reflected->getAnnotations(); return empty($annotations['internal']) && empty($annotations['deprecated']); } private function shiftDocCommentLine($comment) { $comment = array_map(function ($line) { return ltrim($line, '/* '); }, explode("\n", $comment)); $comment = array_filter($comment, function ($line) { return !empty($line); }); return array_shift($comment); } private function createHtmlForWaiters(HtmlDocument $html, Api $service) { $waiters = $service->getWaiters(); if (!$waiters) { return; } $desc = <<<EOT Waiters allow you to poll a resource until it enters into a desired state. A waiter has a name used to describe what it does, and is associated with an API operation. When creating a waiter, you can provide the API operation parameters associated with the corresponding operation. Waiters can be accessed using the <a href="class-Aws.AwsClientInterface.html#_getWaiter"> getWaiter(\$waiterName, \$operationParameters)</a> method of a client object. This client supports the following waiters: EOT; $html->section(2, 'Waiters'); $html->elem('p', 'phpdocumentor-summary', $desc); $html->open('table', 'table-responsive table-striped'); $html->open('thead'); $html->open('tr'); $html->elem('th', null, 'Waiter name'); $html->elem('th', null, 'API Operation'); $html->elem('th', null, 'Delay'); $html->elem('th', null, 'Max Attempts'); $html->close(); $html->close(); $html->open('tbody'); foreach ($waiters as $name => $config) { $html->open('tr'); $html->elem('td', null, $name); $html->elem('td', null, '<a href="#' . strtolower($config['operation']) . '">' . $config['operation'] . '</a>'); $html->elem('td', null, $config['delay']); $html->elem('td', null, $config['maxAttempts']); $html->close(); } $html->close(); $html->close(); } private function createHtmlForPaginators(HtmlDocument $html, Api $service) { $paginators = $service->getPaginators(); if (!$paginators) { return; } $desc = <<<EOT Paginators handle automatically iterating over paginated API results. Paginators are associated with specific API operations, and they accept the parameters that the corresponding API operation accepts. You can get a paginator from a client class using <a href="class-Aws.AwsClientInterface.html#_getPaginator"> getPaginator(\$paginatorName, \$operationParameters)</a>. This client supports the following paginators: EOT; $html->section(2, 'Paginators'); $html->elem('p', 'phpdocumentor-summary', $desc); $html->open('dl'); foreach ($paginators as $name => $config) { $html->open('dt', 'phpdocumentor-table-of-contents__entry'); $attr = ['href' => '#' . strtolower($name), 'aria-label' => strtolower($name)]; $html->elem('a', $attr, '<strong>' . $name . '</strong>'); $html->close(); } $html->close(); } private function createHtmlForOperation(Service $service, $name, Operation $operation, $examples) { $html = new HtmlDocument; $html->open('article', 'phpdocumentor-element -method -public'); // Name $html->section( 3, $html->glyph('cog') . ' ' . $name, null, 'method-title', $name ); // Code $html->elem( 'pre', 'phpdocumentor-code', '$result = $client-><code class=\"phpdocumentor-code\">' . lcfirst($name) . '</code>([/* ... */]);' . "\n" . '$promise = $client-><code class=\"phpdocumentor-code\">' . lcfirst($name) . 'Async</code>([/* ... */]);' . "\n" ); // Description if ($description = $service->docs->getOperationDocs($name)) { $html->elem('p', 'phpdocumentor-summary', $description); } // Parameters $input = $operation->getInput(); $this->skipMembers->attach($input); $output = $operation->getOutput(); $this->skipMembers->attach($output); $inputShapes = new ShapeIterator($input, $service->docs); $inputExample = new ExampleBuilder($name); foreach ($inputShapes as $shape) { $inputExample->addShape($shape); } $html ->elem('h4', null, 'Parameter Syntax') ->elem('pre', 'phpdocumentor-code', htmlentities($inputExample->getCode())) ->elem('h4', null, 'Parameter Details') ->append($this->renderShape($service->docs, $input, false)); // Results $html->elem('h4', null, 'Result Syntax'); if (!count($output->getMembers())) { $html->elem('pre', 'phpdocumentor-code', '[]'); $html->elem('h4', null, 'Result Details'); $html->elem('div', 'alert alert-info', 'The results for this operation are always empty.'); } else { $outputShapes = new ShapeIterator($output, $service->docs); $eventStreamExample = null; foreach ($outputShapes as $shape) { if (!empty($shape['eventstream'])) { $eventStreamExample = new EventStreamExampleBuilder($shape['param']); } } $outputExample = new ExampleBuilder($name, false); foreach ($outputShapes as $shape) { $outputExample->addShape($shape); if ($eventStreamExample) { $eventStreamExample->addShape($shape); } } $html->elem('pre', 'phpdocumentor-code', htmlentities($outputExample->getCode())) ->elem('h4', null, 'Result Details') ->append($this->renderShape($service->docs, $output, false)); if ($eventStreamExample) { $desc = <<<EOT To use an EventParsingIterator, you will need to loop over the events it will generate and check the top-level field to determine which type of event it is. EOT; $html->elem('h5', null, 'Using an EventParsingIterator') ->elem('p', 'phpdocumentor-summary', $desc) ->elem('pre', 'phpdocumentor-code', htmlentities($eventStreamExample->getCode())); } } // Errors $html->elem('h4', null, 'Errors'); $errors = $operation->getErrors(); if (!$errors) { $html->elem('p', 'phpdocumentor-summary', 'There are no errors described for this operation.'); } else { $html->open('dl'); foreach ($errors as $error) { $desc = $service->docs->getErrorDocs($error->getName()) ?: 'This error does not currently have a description.'; $html ->open('dt') ->elem( 'a', [ 'href' => $service->exceptionLink . '#shape-' . strtolower($error->getName()), 'aria-label' => strtolower($error->getName()) ], '<strong>' . $error['name'] . ': ' . '</strong>') ->elem('dd', 'phpdocumentor-summary', $desc) ->close(); } $html->close(); } // Examples if (!empty($examples)) { $generator = new CodeSnippetGenerator($service->api); $html->elem('h4', null, 'Examples'); foreach ($examples as $number => $example) { $exampleNumber = $number + 1; $exampleId = $this->exampleSlug($name, $exampleNumber); $html->open('h5', ['id' => $exampleId]); $html->elem('span', null, 'Example ' . $exampleNumber . ': ' . $example['title'] ?? ''); $html->elem('a', ['href' => '#' . $exampleId, 'aria-label' => 'example link'], $html->glyph('link')); $html->close(); if (isset($example['description'])) { $html->elem('p', 'phpdocumentor-summary', $example['description']); } $comments = $example['comments'] ?? []; $html->elem('pre', 'phpdocumentor-code', $generator->generateInput( $name, isset($example['input']) ? $example['input'] : [], isset($comments['input']) ? $comments['input'] : [] )); if (isset($example['output'])) { $html->elem('p', 'phpdocumentor-summary', 'Result syntax:'); $html->elem('pre', 'phpdocumentor-code', $generator->generateOutput( $name, $example['output'], isset($comments['output']) ? $comments['output'] : [] )); } } $generatorIssues = $generator->getIssues(); foreach ($generatorIssues as $shapeName => $levelIssues) { foreach ($levelIssues as $level => $messages) { foreach (array_keys($messages) as $message) { $generatorIssues[$shapeName][$level][$message] = $name; } } } $this->logIssues( $service->api->getServiceName(), $service->api->getApiVersion(), $generatorIssues ); } $html->close(); // operation-container return $html; } private function logIssues($serviceName, $serviceVersion, $issuesToLog) { if (!isset($this->issues[$serviceName][$serviceVersion])) { $this->issues[$serviceName][$serviceVersion] = []; } foreach ($issuesToLog as $shapeName=>$shapeIssues) { foreach ($shapeIssues as $level => $messages) { foreach ($messages as $message => $exampleName) { $this->issues[$level][$serviceName][$serviceVersion][ $exampleName . ' has an issue - ' . $message . ' on ' . $shapeName ] = true; } } } } private function renderShape( DocModel $docs, StructureShape $shape, $showTitle = true ) { $html = new HtmlDocument(); $html->open('div', 'shape-container'); // Disable show title for input and output parameters. if ($showTitle) { $desc = $docs->getShapeDocs($shape['name'], null, null); if ($desc) { $html->elem('h5', null, 'Description'); $html->elem('div', 'shape-description', $desc); } } $html->elem('h5', null, 'Members'); $html->open('dl', 'shape-members'); $members = $shape->getMembers(); ksort($members); foreach ($members as $name => $member) { $html->open('dt', 'param-def'); $html->elem('strong', 'term', $name); $html->close(); $html->open('dd', 'param-def'); $required = !empty($shape['required']) && in_array($name, $shape['required']); $html->append($this->describeParam($member, $required)); $desc = $docs->getShapeDocs($member['name'], $shape['name'], $name); $html->elem('div', 'param-def-doc', $desc); $html->close(); } $html->close(); $html->close(); return $html; } private function describeParam(AbstractModel $member, $required = false) { $html = new HtmlDocument(); if ($member instanceof StructureShape) { $typeDesc = $this->getMemberText($member); } elseif ($member instanceof ListShape) { $typeDesc = 'Array of ' . $this->getMemberText($member->getMember()) . 's'; } elseif ($member instanceof MapShape) { $typeDesc = 'Associative array of custom strings keys (' . $member->getKey()->getName() . ') to ' . $this->getMemberText($member->getValue()) . 's'; } else { $typeDesc = $this->getPrimitivePhpType($member); } $html->open('div', 'param-attributes')->open('dl'); if ($required) { $html->elem('dt', 'required', '<dd><strong>Required</strong>: <em>Yes</em></dd>'); } $html->elem('dt', null, '<dd><strong>Type:</strong> ' . "<em>{$typeDesc}</em></dd>"); $html->close(); $html->close(); return $html; } private function getMemberText(AbstractModel $member) { if ($member instanceof StructureShape) { if (!empty($member['eventstream'])) { return $this->getEventStreamMemberText($member); } if (!empty($member['document'])) { return $this->getDocumentText($member); } return $this->memberLink($member->getName()) . ' structure'; } elseif ($member instanceof ListShape) { switch ($member->getMember()['type']) { case 'string': return 'strings'; case 'double': return 'floats'; case 'integer': return 'integers'; case 'boolean': return 'booleans'; case 'structure': return $this->getMemberText($member->getMember()) . 's'; } } elseif ($member instanceof MapShape) { switch ($member->getValue()['type']) { case 'string': return 'strings'; case 'double': return 'floats'; case 'integer': return 'integers'; case 'boolean': return 'booleans'; case 'structure': return $this->getMemberText($member->getValue()) . 's'; } } return $this->getPrimitivePhpType($member); } private function getEventStreamMemberText(StructureShape $member) { return 'EventParsingIterator supplying the following structures: ' . implode(', ', array_map( [$this, 'memberLink'], array_reduce( $member->getMembers(), function ($carry, $item) { $carry []= $item['name']; return $carry; }, [] ) ) ); } private function getDocumentText(StructureShape $member) { return 'document (null|bool|string|numeric) or an (array|associative array)' . ' whose members are all valid documents'; } private function getPrimitivePhpType($member) { switch ($member['type']) { case 'long': return 'long (int|float)'; case 'integer': return 'int'; case 'blob': return 'blob (string|resource|Psr\Http\Message\StreamInterface)'; case 'char': return 'char (string)'; case 'timestamp': return 'timestamp (string|DateTime or anything parsable by strtotime)'; case 'string': if ($member['jsonvalue']){ return 'string (string|number|array|map or anything parsable by json_encode)'; } default: return $member['type']; } } private function exampleSlug($name, $number) { return strtolower($name) . '-example-' . $number; } private function memberSlug($name) { return 'shape-' . strtolower($name); } private function memberLink($name) { return '<a href="#' . $this->memberSlug($name) . '" aria-label="' . $name . '">' . $name . '</a>'; } private function updateSitemap() { fwrite(STDOUT, "Updating sitemap\n"); $writer = new \SimpleXMLElement("<urlset></urlset>"); $writer->addAttribute('xmlns', "http://www.sitemaps.org/schemas/sitemap/0.9"); $linksToIndex = new \GlobIterator("{$this->outputDir}/*.html", \FilesystemIterator::CURRENT_AS_FILEINFO); foreach ($linksToIndex as $link) { $url = $writer->addChild('url'); $url->addChild('loc', "{$this->baseUrl}{$link->getBasename()}"); } $writer->asXML("{$this->outputDir}/sitemap.xml"); } }