Skip to content

Commit

Permalink
Support Location attribute on templates
Browse files Browse the repository at this point in the history
In this commit, I have added handling, tests and documentation for the
'location' attribute in the template configuration entry. This should
now work and attempt to load the template from the given directory on
the host's filesystem.

Because I kept running into issues with how phpstan and psalm interpret
the configuration array shape; I have also cleaned that part up and used
psalm-type definitions to provide a reusable shape. I chose not to use
phpstan-type annotations because phpstan understands the psalm
annotations but not vice versa.

Unfortunately, I discovered that we are using an antiquated version of
phpstan that does not like array-shape type aliases. This is fixed in a
newer version but I need @jaapio his input whether we version locked for
a reason.

Signed-off-by: Mike van Riel <me@mikevanriel.com>
  • Loading branch information
mvriel committed Jul 17, 2022
1 parent 45bb695 commit bd0712c
Show file tree
Hide file tree
Showing 16 changed files with 234 additions and 67 deletions.
10 changes: 10 additions & 0 deletions docs/references/full-config-reference.rst
Expand Up @@ -334,6 +334,16 @@ name
location
~~~~~~~~

[optional] The path where the template can be found. Unless your project uses a custom template in a specific folder,
this attribute should not be provided. When provided, phpDocumentor will load your template from that folder.

When a relative folder is provided, phpDocumentor will attempt to find that relative to the location of your
configuration file. An absolute path is also allowed for when you have multiple projects that should use the same
template.

For example: When the location ``my/phpdoc/templates`` is provided and ``myTemplate`` as name, phpDocumentor will
attempt to load the template configuration at ``[configPath]/my/phpdoc/templates/myTemplate/template.xml``.

parameters
~~~~~~~~~~

Expand Down
2 changes: 1 addition & 1 deletion phpdoc.dist.xml
Expand Up @@ -42,5 +42,5 @@
</guide>
</version>
<setting name="guides.enabled" value="true"/>
<template name="default"/>
<template name="default" />
</phpdocumentor>
5 changes: 2 additions & 3 deletions phpstan.neon
Expand Up @@ -22,6 +22,7 @@ parameters:

# Bad design of descriptors.
- '#Access to an undefined property phpDocumentor\\Descriptor\\Collection<phpDocumentor\\Descriptor\\Collection<phpDocumentor\\Descriptor\\DescriptorAbstract>>::\$[a-z]+#'

# https://github.com/phpDocumentor/phpDocumentor/issues/2279
- '#Instanceof between phpDocumentor\\Descriptor\\Collection<phpDocumentor\\Descriptor\\InterfaceDescriptor\|phpDocumentor\\Reflection\\Fqsen>\|phpDocumentor\\Reflection\\Fqsen\|string\|null and phpDocumentor\\Descriptor\\InterfaceDescriptor will always evaluate to false\.#'

Expand All @@ -37,9 +38,6 @@ parameters:
# Design improvement of configuration needed
- '#Parameter \#1 \$templates of method phpDocumentor\\Transformer\\Template\\Factory::getTemplates\(\) expects array<int, array\(.*\)>, array<int, string> given\.#'

# Advanced transformations of array. PHPStan doesn't seem to keep nonEmpty flag through some array functions
- '#Method phpDocumentor\\Configuration\\Definition\\Version2::upgrade\(\) should return.+#'

-
message: "#^Parameter \\#1 \\$value of method phpDocumentor\\\\GraphViz\\\\Graph\\:\\:setCenter\\(\\) expects bool, string given\\.$#"
count: 1
Expand All @@ -64,6 +62,7 @@ parameters:
message: "#^Parameter \\#1 \\$value of method phpDocumentor\\\\GraphViz\\\\Graph\\:\\:setFontSize\\(\\) expects float, string given\\.$#"
count: 1
path: src/phpDocumentor/Transformer/Writer/Graph/GraphVizClassDiagram.php

excludes_analyse:
#test data
- %currentWorkingDirectory%/tests/features/**/*.php
Expand Down
19 changes: 13 additions & 6 deletions src/phpDocumentor/Configuration/ApiSpecification.php
Expand Up @@ -5,13 +5,15 @@
namespace phpDocumentor\Configuration;

use ArrayAccess;
use phpDocumentor\Configuration\Definition\Version3;
use phpDocumentor\Dsn;
use phpDocumentor\Path;
use RuntimeException;

use function sprintf;

/**
* @psalm-import-type ConfigurationApiMap from Version3
* @implements ArrayAccess<String, mixed>
*/
final class ApiSpecification implements ArrayAccess
Expand All @@ -33,7 +35,12 @@ final class ApiSpecification implements ArrayAccess
/** @var string */
private $output;

/** @var array{paths: array<Path>} */
/** @var array{
* hidden: bool,
* symlinks: bool,
* paths: list<string>
* }
*/
private $ignore;

/** @var non-empty-list<string> */
Expand Down Expand Up @@ -64,7 +71,7 @@ final class ApiSpecification implements ArrayAccess
private $validate;

/**
* @param array{paths: array<Path>} $ignore
* @param array{hidden: bool, symlinks: bool, paths: list<string>} $ignore
* @param non-empty-list<string> $extensions
* @param array<string> $visibility
* @param array<string> $markers
Expand Down Expand Up @@ -98,11 +105,9 @@ private function __construct(
$this->validate = $validate;
}

//phpcs:disable Generic.Files.LineLength.TooLong
/**
* @param array{ignore-tags: array<string>, extensions: non-empty-array<string>, markers: non-empty-array<string>, visibility: non-empty-array<string>, source: array{dsn: Dsn, paths: array}, ignore: array{paths: array}, encoding: string, output: string, default-package-name: string, examples: array{dsn: string, paths: array}, include-source: bool, validate: bool} $api
* @param ConfigurationApiMap $api
*/
//phpcs:enable Generic.Files.LineLength.TooLong
public static function createFromArray(array $api): self
{
return new self(
Expand Down Expand Up @@ -132,6 +137,8 @@ public static function createDefault(): ApiSpecification
),
'./api',
[
'hidden' => true,
'symlinks' => true,
'paths' => [],
],
['php'],
Expand All @@ -155,7 +162,7 @@ public function withSource(Source $source): self
}

/**
* @param array{paths: non-empty-array<Path>} $ignore
* @param array{hidden: bool, symlinks: bool, paths: list<string>} $ignore
*/
public function setIgnore(array $ignore): void
{
Expand Down
5 changes: 2 additions & 3 deletions src/phpDocumentor/Configuration/ConfigurationFactory.php
Expand Up @@ -24,6 +24,7 @@

/**
* The ConfigurationFactory converts the configuration xml from a Uri into an array.
* @psalm-import-type ConfigurationMap from SymfonyConfigFactory
*/
/*final*/ class ConfigurationFactory
{
Expand Down Expand Up @@ -118,11 +119,9 @@ private function applyMiddleware(Configuration $configuration, ?UriInterface $ur
return $configuration;
}

//phpcs:disable Generic.Files.LineLength.TooLong
/**
* @param array{phpdocumentor: array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: string, cache: string}, versions?: array<string, array{ api: array{ignore-tags: array<string>, extensions: non-empty-array<string>, markers: non-empty-array<string>, visibility: non-empty-array<string>, source: array{dsn: Dsn, paths: array}, ignore: array{paths: array}, encoding: string, output: string, default-package-name: string, examples: array{dsn: Dsn, paths: array}, include-source: bool, validate: bool}, guides: array}>, settings?: array<mixed>, templates?: non-empty-list<string>}} $configuration
* @param ConfigurationMap $configuration
*/
//phpcs:enable Generic.Files.LineLength.TooLong
private function createConfigurationFromArray(array $configuration): Configuration
{
if (isset($configuration['phpdocumentor']['versions'])) {
Expand Down
11 changes: 6 additions & 5 deletions src/phpDocumentor/Configuration/Definition/Normalizable.php
Expand Up @@ -7,14 +7,15 @@
use phpDocumentor\Dsn;
use phpDocumentor\Path;

/**
* @template TBaseConfiguration of array
* @template TNormalizedConfiguration of array
*/
interface Normalizable
{
//phpcs:disable Generic.Files.LineLength.TooLong
/**
* @param array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: string, cache: string}, versions?: array<string, mixed>, settings?: array<mixed>, templates?: non-empty-list<string>} $configuration
*
* @return array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: Dsn, cache: Path}, versions?: array<string, array{api: array<int, array{ignore-tags: array, extensions: non-empty-array<string>, markers: non-empty-array<string>, visibillity: string, source: array{dsn: Dsn, paths: array}, ignore: array{paths: array}}>, guides: array}>, settings?: array<mixed>, templates?: non-empty-list<string>}
* @param TBaseConfiguration $configuration
* @return TNormalizedConfiguration
*/
//phpcs:enable Generic.Files.LineLength.TooLong
public function normalize(array $configuration): array;
}
10 changes: 6 additions & 4 deletions src/phpDocumentor/Configuration/Definition/Upgradable.php
Expand Up @@ -4,9 +4,12 @@

namespace phpDocumentor\Configuration\Definition;

/**
* @template TPreviousConfigurationMap of array
* @template TUpgradedConfigurationMap of array
*/
interface Upgradable
{
//phpcs:disable Generic.Files.LineLength.TooLong
/**
* Attempt to upgrade the given result of this definition to a newer version of the configuration.
*
Expand All @@ -21,10 +24,9 @@ interface Upgradable
* The 'configVersion' field in the result will inform the ConfigurationFactory what the next Configuration
* definition should be used to parse this result.
*
* @param array<string, mixed> $values
* @param TPreviousConfigurationMap $values
*
* @return array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: string, cache: string}, version?: array{array{api: array{array{default-package-name: string, extensions: array{extensions: array<array-key, string>}, ignore: array{paths: array<array-key, string>}, markers: array{markers: array<array-key, mixed>}, source: array{paths: array<array-key, string>}, visibilities: non-empty-list<string>}}, number: string}}}, settings?: array<mixed>, templates?: non-empty-list<string>}
* @return TUpgradedConfigurationMap
*/
//phpcs:enable Generic.Files.LineLength.TooLong
public function upgrade(array $values): array;
}
16 changes: 7 additions & 9 deletions src/phpDocumentor/Configuration/Definition/Version2.php
Expand Up @@ -25,6 +25,12 @@
use function implode;
use function substr;

/**
* @psalm-type ConfigurationMap = array<mixed>
* @psalm-import-type BaseConfiguration from Version3 as UpgradedConfiguration
*
* @implements Upgradable<ConfigurationMap, UpgradedConfiguration>
*/
final class Version2 implements ConfigurationInterface, Upgradable
{
/** @var string This is injected so that the name of the default template can be defined globally in the app */
Expand Down Expand Up @@ -148,17 +154,9 @@ public function getConfigTreeBuilder(): TreeBuilder
return $treebuilder;
}

//phpcs:disable Generic.Files.LineLength.TooLong
/**
* Upgrades the version 2 configuration to the version 3 configuration.
*
* @param array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: string, cache: string}, versions?: array<string, mixed>, settings?: array<mixed>, templates?: non-empty-list<string>, transformer: array{target: string}, parser: array{target: string, default-package-name: string, extensions: array{extensions: array}, visibility: string, markers: array{items: array}}, files: array{files: array, directories: array, ignores: array}, transformations: array{templates: array<string>}} $values
*
* @return array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: string, cache: string}, version?: array{array{api: array{array{default-package-name: string, extensions: array{extensions: array<array-key, string>}, ignore: array{paths: array<array-key, string>}, markers: array{markers: array<array-key, mixed>}, source: array{paths: array<array-key, string>}, visibilities: non-empty-list<string>}}, number: string}}}, settings?: array<mixed>, templates?: non-empty-list<string>}
*
* @todo not all options are included yet; finish this
* Upgrades the version 2 configuration to the version 3 pre-normalized configuration.
*/
//phpcs:enable Generic.Files.LineLength.TooLong
public function upgrade(array $values): array
{
return [
Expand Down
77 changes: 72 additions & 5 deletions src/phpDocumentor/Configuration/Definition/Version3.php
Expand Up @@ -24,6 +24,62 @@
use function is_string;
use function var_export;

/**
* @psalm-type BaseConfiguration = array<mixed>
* @psalm-type ConfigurationApiMap = array{
* ignore-tags: list<string>,
* extensions: non-empty-array<string>,
* markers: list<string>,
* visibility: non-empty-array<string>,
* source: array{dsn: Dsn, paths: list<Path>},
* ignore: array{
* hidden: bool,
* symlinks: bool,
* paths: list<string>
* },
* ignore-tags: list<string>,
* encoding: string,
* output: string,
* format: string,
* default-package-name: string,
* examples?: array{dsn: string, paths: array},
* include-source: bool,
* validate: bool,
* visibility: non-empty-array<array-key, string>
* }
*
* @psalm-type ConfigurationMap = array{
* configVersion: string,
* title?: string,
* paths: array{output: Dsn, cache: Path},
* versions: array<
* string,
* array{
* number: string,
* folder: string,
* api: list<ConfigurationApiMap>,
* guides: list<
* array{
* source: array{dsn: Dsn, paths: Path[]},
* output: string,
* format: string,
* }
* >
* }
* >,
* use-cache: bool,
* settings: array<string, mixed>,
* templates: non-empty-list<
* array{
* name: string,
* location: ?Path,
* parameters: list<string, mixed>
* }
* >
* }
*
* @implements Normalizable<BaseConfiguration, ConfigurationMap>
*/
final class Version3 implements ConfigurationInterface, Normalizable
{
/** @var string This is injected so that the name of the default template can be defined globally in the app */
Expand Down Expand Up @@ -115,13 +171,9 @@ static function ($value) {
return $treebuilder;
}

//phpcs:disable Generic.Files.LineLength.TooLong
/**
* @param array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: string, cache: string}, versions?: array<string, array{api: array<int, array{ignore-tags: array, extensions: non-empty-array<string>, markers: non-empty-array<string>, visibillity: string, source: array{dsn: Dsn, paths: array}, ignore: array{paths: array}}>, apis: array, guides: array}>, settings?: array<mixed>, templates?: non-empty-list<string>} $configuration
*
* @return array{configVersion: string, title?: string, use-cache?: bool, paths?: array{output: Dsn, cache: Path}, versions?: array<string, array{api: array<int, array{ignore-tags: array, extensions: non-empty-array<string>, markers: non-empty-array<string>, visibillity: string, source: array{dsn: Dsn, paths: array}, ignore: array{paths: array}}>, guides: array}>, settings?: array<mixed>, templates?: non-empty-list<string>}
* {@inheritDoc}
*/
//phpcs:enable Generic.Files.LineLength.TooLong
public function normalize(array $configuration): array
{
$configuration['configVersion'] = (string) $configuration['configVersion'];
Expand Down Expand Up @@ -164,6 +216,21 @@ public function normalize(array $configuration): array
}
}

foreach ($configuration['templates'] as $key => $template) {
if (is_string($template)) {
$template = [
'name' => $template,
'location' => null,
'parameters' => [],
];
}

$location = $template['location'] ?? null;
$template['location'] = $location ? new Path((string) $location) : null;

$configuration['templates'][$key] = $template;
}

return $configuration;
}

Expand Down
23 changes: 19 additions & 4 deletions src/phpDocumentor/Configuration/PathNormalizingMiddleware.php
Expand Up @@ -16,6 +16,7 @@
use League\Uri\Contracts\UriInterface;
use phpDocumentor\Dsn;
use phpDocumentor\Path;
use Symfony\Component\Filesystem\Path as SymfonyPath;

use function array_map;
use function array_merge;
Expand Down Expand Up @@ -58,29 +59,43 @@ private function makeDsnRelativeToConfig(Configuration $configuration, ?UriInter
}

$configFile = Dsn::createFromUri($uri);
$configPath = $configFile->withPath(Path::dirname($configFile->getPath()));
$configPath = Path::dirname($configFile->getPath());
$configDsn = $configFile->withPath($configPath);

$configuration['phpdocumentor']['paths']['output'] =
$configuration['phpdocumentor']['paths']['output']->resolve($configPath);
$configuration['phpdocumentor']['paths']['output']->resolve($configDsn);

/** @var VersionSpecification $version */
foreach ($configuration['phpdocumentor']['versions'] as $version) {
$apiConfigs = [];

foreach ($version->getApi() as $api) {
$apiConfigs[] = $api->withSource($api->source()->withDsn($api['source']['dsn']->resolve($configPath)));
$apiConfigs[] = $api->withSource($api->source()->withDsn($api['source']['dsn']->resolve($configDsn)));
}

$version->setApi($apiConfigs);

foreach ($version->getGuides() ?? [] as $key => $guide) {
$version->guides[$key]->withSource(
$guide->source()->withDsn(
$guide->source()->dsn()->resolve($configPath)
$guide->source()->dsn()->resolve($configDsn)
)
);
}
}

/** @var array{name: string, location?: ?Path, parameters?: array} $template */
foreach ($configuration['phpdocumentor']['templates'] as $key => $template) {
$location = $template['location'];
if ($location instanceof Path && SymfonyPath::isAbsolute((string) $location) === false) {
$location = new Path($configPath . '/' . $location);
}

$template['location'] = $location;

$configuration['phpdocumentor']['templates'][$key] = $template;
}

return $configuration;
}

Expand Down

0 comments on commit bd0712c

Please sign in to comment.