Completed
Push — master ( e5efab...bb5eb4 )
by Théo
19:57
created

AutoloadPrefixer::transformPsr0ToPsr4AndClassmap()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
nc 7
nop 3
dl 0
loc 27
ccs 13
cts 13
cp 1
crap 5
rs 9.1768
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the humbug/php-scoper package.
7
 *
8
 * Copyright (c) 2017 Théo FIDRY <[email protected]>,
9
 *                    Pádraic Brady <[email protected]>
10
 *
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace Humbug\PhpScoper\Scoper\Composer;
16
17
use Humbug\PhpScoper\Whitelist;
18
use stdClass;
19
use function array_map;
20
use function is_array;
21
use function is_string;
22
use function rtrim;
23
use function str_replace;
24
use function strpos;
25
26
/**
27
 * @private
28
 */
29
final class AutoloadPrefixer
30
{
31
    /**
32
     * @param stdClass $contents Decoded JSON
33
     * @param string   $prefix
34
     *
35
     * @return stdClass Prefixed decoded JSON
36
     */
37 11
    public static function prefixPackageAutoloadStatements(stdClass $contents, string $prefix, Whitelist $whitelist): stdClass
38
    {
39 11
        if (isset($contents->autoload)) {
40 11
            $contents->autoload = self::prefixAutoloadStatements($contents->autoload, $prefix, $whitelist);
41
        }
42
43 11
        if (isset($contents->{'autoload-dev'})) {
44 3
            $contents->{'autoload-dev'} = self::prefixAutoloadStatements($contents->{'autoload-dev'}, $prefix, $whitelist);
45
        }
46
47 11
        if (isset($contents->extra, $contents->extra->laravel, $contents->extra->laravel->providers)) {
48 1
            $contents->extra->laravel->providers = self::prefixLaravelProviders($contents->extra->laravel->providers, $prefix, $whitelist);
49
        }
50
51 11
        return $contents;
52
    }
53
54 11
    private static function prefixAutoloadStatements(stdClass $autoload, string $prefix, Whitelist $whitelist): stdClass
55
    {
56 11
        if (false === isset($autoload->{'psr-4'}) && false === isset($autoload->{'psr-0'})) {
57 1
            return $autoload;
58
        }
59
60 10
        if (isset($autoload->{'psr-0'})) {
61 7
            [$psr4, $classMap] = self::transformPsr0ToPsr4AndClassmap(
2 ignored issues
show
Bug introduced by
The variable $psr4 does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $classMap does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
62 7
                (array) $autoload->{'psr-0'},
63 7
                (array) ($autoload->{'psr-4'} ?? new stdClass()),
64 7
                (array) ($autoload->{'classmap'} ?? new stdClass())
65
            );
66
67 7
            if ([] === $psr4) {
68 2
                unset($autoload->{'psr-4'});
69
            } else {
70 5
                $autoload->{'psr-4'} = $psr4;
71
            }
72
73 7
            if ([] === $classMap) {
74 5
                unset($autoload->{'classmap'});
75
            } else {
76 2
                $autoload->{'classmap'} = $classMap;
77
            }
78
        }
79 10
        unset($autoload->{'psr-0'});
80
81 10
        if (isset($autoload->{'psr-4'})) {
82 8
            $autoload->{'psr-4'} = self::prefixAutoload((array) $autoload->{'psr-4'}, $prefix, $whitelist);
83
        }
84
85 10
        return $autoload;
86
    }
87
88 8
    private static function prefixAutoload(array $autoload, string $prefix, Whitelist $whitelist): array
89
    {
90 8
        $loader = [];
91
92 8
        foreach ($autoload as $namespace => $paths) {
93 8
            $newNamespace = $whitelist->belongsToWhitelistedNamespace($namespace)
94
                ? $namespace
95 8
                : sprintf('%s\\%s', $prefix, $namespace)
96
            ;
97
98 8
            $loader[$newNamespace] = $paths;
99
        }
100
101 8
        return $loader;
102
    }
103
104
    /**
105
     * @param array<string, (string|string[])> $psr0
106
     * @param (string|string[])[]              $psr4
107
     * @param string[]                         $classMap
108
     */
109 7
    private static function transformPsr0ToPsr4AndClassmap(array $psr0, array $psr4, array $classMap): array
110
    {
111 7
        foreach ($psr0 as $namespace => $path) {
112
            //Append backslashes, if needed, since psr-0 does not require this
113 7
            if ('\\' !== substr($namespace, -1)) {
114 3
                $namespace .= '\\';
115
            }
116
117 7
            if (false !== strpos($namespace, '_')) {
118 2
                $classMap[] = $path;
119
120 2
                continue;
121
            }
122
123 5
            $path = self::updatePSR0Path($path, $namespace);
124
125 5
            if (!isset($psr4[$namespace])) {
126 2
                $psr4[$namespace] = $path;
127
128 2
                continue;
129
            }
130
131 3
            $psr4[$namespace] = self::mergeNamespaces($namespace, $path, $psr4);
2 ignored issues
show
Bug introduced by
It seems like $path defined by self::updatePSR0Path($path, $namespace) on line 123 can also be of type array; however, Humbug\PhpScoper\Scoper\...ixer::mergeNamespaces() does only seem to accept string|array<integer,string>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Documentation introduced by
$psr4 is of type array<integer|string,string|array>, but the function expects a array<integer,string|array<integer,string>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
132
        }
133
134 7
        return [$psr4, $classMap];
135
    }
136
137
    /**
138
     * @param string|string[] $path
139
     *
140
     * @return string|string[]
141
     */
142 5
    private static function updatePSR0Path($path, string $namespace)
143
    {
144 5
        $namespaceForPsr = rtrim(
145 5
            str_replace('\\', '/', $namespace),
146 5
            '/'
147
        );
148
149 5
        if (false === is_array($path)) {
150 4
            if ('/' !== substr($path, -1)) {
151 1
                $path .= '/';
152
            }
153
154 4
            $path .= $namespaceForPsr.'/';
155
156 4
            return $path;
157
        }
158
159 2
        foreach ($path as $key => $item) {
160 2
            if ('/' !== substr($item, -1)) {
161 1
                $item .= '/';
162
            }
163
164 2
            $item .= $namespaceForPsr.'/';
165 2
            $path[$key] = $item;
166
        }
167
168 2
        return $path;
169
    }
170
171
    /**
172
     * Deals with the 4 possible scenarios:
173
     *       PSR0 | PSR4
174
     * array      |
175
     * string     |
176
     * or simply the namespace not existing as a psr-4 entry.
177
     *
178
     * @param string              $psr0Namespace
179
     * @param string|string[]     $psr0Path
180
     * @param (string|string[])[] $psr4
181
     *
182
     * @return string|string[]
183
     */
184 3
    private static function mergeNamespaces(string $psr0Namespace, $psr0Path, array $psr4)
185
    {
186
        // Both strings
187 3
        if (is_string($psr4[$psr0Namespace]) && is_string($psr0Path)) {
188 1
            return [$psr4[$psr0Namespace], $psr0Path];
189
        }
190
191
        // PSR-4 is string, and PSR-0 is array
192 2
        if (is_string($psr4[$psr0Namespace]) && is_array($psr0Path)) {
193 1
            $psr0Path[] = $psr4[$psr0Namespace];
194
195 1
            return $psr0Path;
196
        }
197
198
        // Psr-4 is array and psr-0 is string
199 2
        if (is_array($psr4[$psr0Namespace]) && is_string($psr0Path)) {
200 1
            $psr4[$psr0Namespace][] = $psr0Path;
201
202 1
            return $psr4[$psr0Namespace];
203
        }
204
205 1
        if (is_array($psr4[$psr0Namespace]) && is_array($psr0Path)) {
206 1
            return array_merge($psr4[$psr0Namespace], $psr0Path);
207
        }
208
209
        return $psr0Path;
210
    }
211
212 1
    private static function prefixLaravelProviders(array $providers, string $prefix, Whitelist $whitelist): array
213
    {
214 1
        return array_map(
215
            static function (string $provider) use ($prefix, $whitelist): string {
216 1
                return $whitelist->belongsToWhitelistedNamespace($provider)
217
                    ? $provider
218 1
                    : sprintf('%s\\%s', $prefix, $provider)
219
                ;
220 1
            },
221 1
            $providers
222
        );
223
    }
224
}
225