Completed
Push — master ( 0e0888...d9bd11 )
by Mike
07:38
created

convertSingleStarPathEndingIntoGlobPattern()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
cc 3
nc 2
nop 1
dl 0
loc 8
ccs 0
cts 0
cp 0
crap 12
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of phpDocumentor.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @link http://phpdoc.org
12
 */
13
14
namespace phpDocumentor\Configuration\Definition;
15
16
use phpDocumentor\Configuration\SymfonyConfigFactory;
17
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
18
use Symfony\Component\Config\Definition\ConfigurationInterface;
19
use function array_merge;
20
use function array_values;
21
use function getcwd;
22
23
final class Version2 implements ConfigurationInterface, Upgradable
24
{
25
    /** @var string This is injected so that the name of the default template can be defined globally in the app */
26
    private $defaultTemplateName;
27 2
28
    public function __construct(string $defaultTemplateName)
29 2
    {
30 2
        $this->defaultTemplateName = $defaultTemplateName;
31
    }
32 1
33
    public function getConfigTreeBuilder() : TreeBuilder
34 1
    {
35
        $treebuilder = new TreeBuilder('phpdocumentor');
36 1
37 1
        $treebuilder->getRootNode()
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class Symfony\Component\Config...\Builder\NodeDefinition as the method addDefaultsIfNotSet() does only exist in the following sub-classes of Symfony\Component\Config...\Builder\NodeDefinition: Symfony\Component\Config...der\ArrayNodeDefinition. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
38 1
            ->addDefaultsIfNotSet()
39 1
            ->children()
40 1
                ->scalarNode(SymfonyConfigFactory::FIELD_CONFIG_VERSION)->defaultValue('2')->end()
41 1
                ->scalarNode('title')->defaultValue('Documentation')->end()
42 1
                ->arrayNode('parser')
43 1
                    ->addDefaultsIfNotSet()
44 1
                    ->normalizeKeys(false)
45 1
                    ->children()
46 1
                        ->scalarNode('default-package-name')->defaultValue('Application')->end()
47 1
                        ->arrayNode('visibility')
48 1
                            ->defaultValue(['public', 'protected', 'private'])
49 1
                            ->prototype('enum')
50 1
                                ->info('What is the deepest level of visibility to include in the documentation?')
51 1
                                ->values([
52
                                    'api', // include all elements tagged with the `@api` tag
53
                                    'public', // include all methods, properties and constants that are public
54
                                    'protected', // include all methods, properties and constants that are protected
55
                                    'private', // include all methods, properties and constants that are private
56
                                    'internal', // include all elements tagged with `@internal`
57 1
                                ])
58 1
                            ->end()
59 1
                        ->end()
60 1
                        ->scalarNode('target')->defaultValue('build/api-cache')->end()
61 1
                        ->scalarNode('encoding')
62 1
                            ->defaultValue('utf-8')
63 1
                        ->end()
64 1
                        ->arrayNode('extensions')
65 1
                            ->addDefaultsIfNotSet()
66 1
                            ->fixXmlConfig('extension')
67 1
                            ->children()
68 1
                                ->arrayNode('extensions')
69 1
                                    ->defaultValue(['php', 'php3', 'phtml'])
70 1
                                    ->beforeNormalization()->castToArray()->end()
71 1
                                    ->prototype('scalar')->end()
72 1
                                ->end()
73 1
                            ->end()
74 1
                        ->end()
75 1
                        ->arrayNode('markers')
76 1
                            ->addDefaultsIfNotSet()
77 1
                            ->fixXmlConfig('item')
78 1
                            ->children()
79 1
                                ->arrayNode('items')
80 1
                                    ->defaultValue(['TODO', 'FIXME'])
81 1
                                    ->beforeNormalization()->castToArray()->end()
82 1
                                    ->prototype('scalar')->end()
83 1
                                ->end()
84 1
                            ->end()
85 1
                        ->end()
86 1
                    ->end()
87 1
                ->end()
88 1
                ->arrayNode('transformer')
89 1
                    ->addDefaultsIfNotSet()
90 1
                    ->children()
91 1
                        ->scalarNode('target')->defaultValue('build/api')->end()
92 1
                    ->end()
93 1
                ->end()
94 1
                ->arrayNode('logging')
95 1
                    ->addDefaultsIfNotSet()
96 1
                    ->children()
97 1
                        ->scalarNode('level')->defaultValue('error')->end()
98 1
                    ->end()
99 1
                ->end()
100 1
                ->arrayNode('transformations')
101 1
                    ->addDefaultsIfNotSet()
102 1
                    ->fixXmlConfig('template')
103 1
                    ->children()
104 1
                        ->arrayNode('templates')
105 1
                            ->fixXmlConfig('parameter')
106 1
                            ->useAttributeAsKey('name')
107 1
                            ->defaultValue([$this->defaultTemplateName => ['name' => $this->defaultTemplateName]])
108 1
                            ->prototype('array')
109 1
                                ->children()
110 1
                                    ->scalarNode('location')->end()
111 1
                                    ->arrayNode('parameters')
112 1
                                        ->children()
113 1
                                            ->scalarNode('name')->end()
114 1
                                            ->scalarNode('value')->end()
115 1
                                        ->end()
116 1
                                    ->end()
117 1
                                ->end()
118 1
                            ->end()
119 1
                        ->end()
120 1
                    ->end()
121 1
                ->end()
122 1
                ->arrayNode('files')
123 1
                    ->addDefaultsIfNotSet()
124 1
                    ->fixXmlConfig('file', 'files')
125 1
                    ->fixXmlConfig('directory', 'directories')
126 1
                    ->fixXmlConfig('ignore', 'ignores')
127 1
                    ->normalizeKeys(false)
128 1
                    ->children()
129 1
                        ->booleanNode('ignore-hidden')
130 1
                            ->defaultTrue()
131 1
                        ->end()
132 1
                        ->booleanNode('ignore-symlinks')
133 1
                            ->defaultTrue()
134 1
                        ->end()
135 1
                        ->arrayNode('directories')
136 1
                            ->defaultValue([getcwd()])
137 1
                            ->beforeNormalization()->castToArray()->end()
138 1
                            ->prototype('scalar')->end()
139 1
                        ->end()
140 1
                        ->arrayNode('files')
141 1
                            ->beforeNormalization()->castToArray()->end()
142 1
                            ->prototype('scalar')->end()
143 1
                        ->end()
144 1
                        ->arrayNode('ignores')
145 1
                            ->beforeNormalization()->castToArray()->end()
146 1
                            ->prototype('scalar')->end()
147 1
                        ->end()
148 1
                    ->end()
149 1
                ->end()
150
            ->end();
151 1
152
        return $treebuilder;
153
    }
154
155
    /**
156
     * Upgrades the version 2 configuration to the version 3 configuration.
157
     *
158
     * @param array<string, array<string, mixed>> $values
159
     *
160
     * @return array<string, array<int|string, mixed>|string>
0 ignored issues
show
Documentation introduced by
The doc-type array<string, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
161
     *
162
     * @todo not all options are included yet; finish this
163 1
     */
164
    public function upgrade(array $values) : array
165
    {
166
        return [
167 1
            SymfonyConfigFactory::FIELD_CONFIG_VERSION => '3',
168
            'title' => $values['title'],
169 1
            'paths' => [
170 1
                'output' => $values['transformer']['target'],
171
                'cache' => $values['parser']['target'],
172
            ],
173
            'version' => [
174 1
                [
175
                    'number' => '1.0.0',
176
                    'api' => [
177 1
                        [
178
                            'default-package-name' => $values['parser']['default-package-name'],
179 1
                            'source' => [
180
                                'paths' => array_map(
181
                                    [$this, 'convertSingleStarPathEndingIntoGlobPattern'],
182 1
                                    array_merge($values['files']['files'], $values['files']['directories'])
183
                                ),
184
                            ],
185 1
                            'ignore' => [
186
                                'paths' => array_map(
187
                                    [$this, 'convertSingleStarPathEndingIntoGlobPattern'],
188 1
                                    $values['files']['ignores']
189
                                ),
190
                            ],
191
                            'extensions' => [
192
                                'extensions' => $values['parser']['extensions']['extensions'],
193
                            ],
194
                            'markers' => [
195
                                'markers' => $values['parser']['markers']['items'],
196
                            ],
197
                        ],
198
                    ],
199
                ],
200
            ],
201
            'templates' => array_values($values['transformations']['templates']),
202
        ];
203
    }
204
205
    /**
206
     * Make a `/*` ending backwards compatible for v2.
207
     *
208
     * In phpDocumentor 3 we started adopting the glob pattern with globstar extension to properly define patterns
209
     * matching file paths. This is incompatible with phpDocumentor 2, that interpreted a * to mean any number of
210
     * characters, including the path separator.
211
     *
212
     * To ensure this behaviour is properly translated, this method will detect if a path ends with /*, and if it is
213
     * not a globstar pattern, we convert it to one. This matches the behaviour in phpDocumentor 2 without user
214
     * interaction.
215
     *
216
     * @link https://www.gnu.org/software/bash/manual/html_node/Pattern-Matching.html
217
     */
218
    private function convertSingleStarPathEndingIntoGlobPattern(string $path): string
219
    {
220
        if (mb_substr($path, -2) === '/*' && mb_substr($path, -4) !== '**/*') {
221
            $path .= '*/*';
222
        }
223
224
        return $path;
225
    }
226
}
227