Passed
Pull Request — master (#555)
by Théo
02:04
created

AutoloadPrefixer::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
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 array_merge;
21
use function is_array;
22
use function is_string;
23
use function rtrim;
24
use function Safe\sprintf;
25
use function Safe\substr;
26
use function str_replace;
27
use function strpos;
28
29
/**
30
 * @private
31
 */
32
final class AutoloadPrefixer
33
{
34
    private string $prefix;
35
    private Whitelist $whitelist;
36
37 11
    public function __construct(string $prefix, Whitelist $whitelist)
38
    {
39 11
        $this->prefix = $prefix;
40 11
        $this->whitelist = $whitelist;
41
    }
42
43 11
    /**
44 3
     * @param stdClass $contents Decoded JSON
45
     *
46
     * @return stdClass Prefixed decoded JSON
47 11
     */
48 1
    public function prefixPackageAutoloadStatements(stdClass $contents): stdClass
49
    {
50
        if (isset($contents->autoload)) {
51 11
            $contents->autoload = self::prefixAutoloadStatements(
52
                $contents->autoload,
53
                $this->prefix,
54 11
                $this->whitelist,
55
            );
56 11
        }
57 1
58
        if (isset($contents->{'autoload-dev'})) {
59
            $contents->{'autoload-dev'} = self::prefixAutoloadStatements(
60 10
                $contents->{'autoload-dev'},
61 7
                $this->prefix,
62 7
                $this->whitelist,
63 7
            );
64 7
        }
65
66
        if (isset($contents->extra->laravel->providers)) {
67 7
            $contents->extra->laravel->providers = self::prefixLaravelProviders(
68 2
                $contents->extra->laravel->providers,
69
                $this->prefix,
70 5
                $this->whitelist,
71
            );
72
        }
73 7
74 5
        return $contents;
75
    }
76 2
77
    private static function prefixAutoloadStatements(stdClass $autoload, string $prefix, Whitelist $whitelist): stdClass
78
    {
79 10
        if (!isset($autoload->{'psr-4'}) && !isset($autoload->{'psr-0'})) {
80
            return $autoload;
81 10
        }
82 8
83
        if (isset($autoload->{'psr-0'})) {
84
            [$psr4, $classMap] = self::transformPsr0ToPsr4AndClassmap(
85 10
                (array) $autoload->{'psr-0'},
86
                (array) ($autoload->{'psr-4'} ?? new stdClass()),
87
                (array) ($autoload->{'classmap'} ?? new stdClass())
88 8
            );
89
90 8
            if ([] === $psr4) {
91
                unset($autoload->{'psr-4'});
92 8
            } else {
93 8
                $autoload->{'psr-4'} = $psr4;
94
            }
95 8
96
            if ([] === $classMap) {
97
                unset($autoload->{'classmap'});
98 8
            } else {
99
                $autoload->{'classmap'} = $classMap;
100
            }
101 8
        }
102
        unset($autoload->{'psr-0'});
103
104
        if (isset($autoload->{'psr-4'})) {
105
            $autoload->{'psr-4'} = self::prefixAutoload((array) $autoload->{'psr-4'}, $prefix, $whitelist);
106
        }
107
108
        return $autoload;
109 7
    }
110
111 7
    private static function prefixAutoload(array $autoload, string $prefix, Whitelist $whitelist): array
112
    {
113 7
        $loader = [];
114 3
115
        foreach ($autoload as $namespace => $paths) {
116
            $newNamespace = $whitelist->isExcludedNamespace($namespace)
117 7
                ? $namespace
118 2
                : sprintf('%s\\%s', $prefix, $namespace)
119
            ;
120 2
121
            $loader[$newNamespace] = $paths;
122
        }
123 5
124
        return $loader;
125 5
    }
126 2
127
    /**
128 2
     * @param array<string, (string|string[])> $psr0
129
     * @param (string|string[])[]              $psr4
130
     * @param string[]                         $classMap
131 3
     */
132
    private static function transformPsr0ToPsr4AndClassmap(array $psr0, array $psr4, array $classMap): array
133
    {
134 7
        foreach ($psr0 as $namespace => $path) {
135
            //Append backslashes, if needed, since psr-0 does not require this
136
            if ('\\' !== substr($namespace, -1)) {
137
                $namespace .= '\\';
138
            }
139
140
            if (false !== strpos($namespace, '_')) {
141
                $classMap[] = $path;
142 5
143
                continue;
144 5
            }
145 5
146 5
            $path = self::updatePSR0Path($path, $namespace);
147
148
            if (!isset($psr4[$namespace])) {
149 5
                $psr4[$namespace] = $path;
150 4
151 1
                continue;
152
            }
153
154 4
            $psr4[$namespace] = self::mergeNamespaces($namespace, $path, $psr4);
155
        }
156 4
157
        return [$psr4, $classMap];
158
    }
159 2
160 2
    /**
161 1
     * @param string|string[] $path
162
     *
163
     * @return string|string[]
164 2
     */
165 2
    private static function updatePSR0Path($path, string $namespace)
166
    {
167
        $namespaceForPsr = rtrim(
168 2
            str_replace('\\', '/', $namespace),
169
            '/'
170
        );
171
172
        if (!is_array($path)) {
173
            if ('/' !== substr($path, -1)) {
174
                $path .= '/';
175
            }
176
177
            $path .= $namespaceForPsr.'/';
178
179
            return $path;
180
        }
181
182
        foreach ($path as $key => $item) {
183
            if ('/' !== substr($item, -1)) {
184 3
                $item .= '/';
185
            }
186
187 3
            $item .= $namespaceForPsr.'/';
188 1
            $path[$key] = $item;
189
        }
190
191
        return $path;
192 2
    }
193 1
194
    /**
195 1
     * Deals with the 4 possible scenarios:
196
     *       PSR0 | PSR4
197
     * array      |
198
     * string     |
199 2
     * or simply the namespace not existing as a psr-4 entry.
200 1
     *
201
     * @param string              $psr0Namespace
202 1
     * @param string|string[]     $psr0Path
203
     * @param (string|string[])[] $psr4
204
     *
205 1
     * @return string|string[]
206 1
     */
207
    private static function mergeNamespaces(string $psr0Namespace, $psr0Path, array $psr4)
208
    {
209
        // Both strings
210
        if (is_string($psr0Path) && is_string($psr4[$psr0Namespace])) {
211
            return [$psr4[$psr0Namespace], $psr0Path];
212 1
        }
213
214 1
        // PSR-4 is string, and PSR-0 is array
215
        if (is_array($psr0Path) && is_string($psr4[$psr0Namespace])) {
216 1
            $psr0Path[] = $psr4[$psr0Namespace];
217
218 1
            return $psr0Path;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $psr0Path returns an array which contains values of type string[] which are incompatible with the documented value type string.
Loading history...
219
        }
220 1
221 1
        // PSR-4 is array and PSR-0 is string
222
        if (is_string($psr0Path) && is_array($psr4[$psr0Namespace])) {
223
            $psr4[$psr0Namespace][] = $psr0Path;
224
225
            return $psr4[$psr0Namespace];
226
        }
227
228
        if (is_array($psr0Path) && is_array($psr4[$psr0Namespace])) {
229
            return array_merge($psr4[$psr0Namespace], $psr0Path);
0 ignored issues
show
Bug introduced by
It seems like $psr4[$psr0Namespace] can also be of type string; however, parameter $arrays of array_merge() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

229
            return array_merge(/** @scrutinizer ignore-type */ $psr4[$psr0Namespace], $psr0Path);
Loading history...
230
        }
231
232
        return $psr0Path;
233
    }
234
235
    private static function prefixLaravelProviders(array $providers, string $prefix, Whitelist $whitelist): array
236
    {
237
        return array_map(
238
            static function (string $provider) use ($prefix, $whitelist): string {
239
                return $whitelist->isExcludedNamespace($provider)
240
                    ? $provider
241
                    : sprintf('%s\\%s', $prefix, $provider)
242
                ;
243
            },
244
            $providers
245
        );
246
    }
247
}
248