Test Failed
Pull Request — master (#8)
by
unknown
05:03
created

CandidateExtractor::getPackageByName()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 2
dl 0
loc 9
ccs 4
cts 4
cp 1
crap 3
rs 9.9666
c 0
b 0
f 0
1
<?php
2
/**
3
 * Copyright MediaCT. All rights reserved.
4
 * https://www.mediact.nl
5
 */
6
7
namespace Mediact\DependencyGuard\Candidate;
8
9
use Composer\Composer;
10
use Composer\Package\PackageInterface;
11
use Mediact\DependencyGuard\Php\SymbolInterface;
12
use Mediact\DependencyGuard\Php\SymbolIterator;
13
use Mediact\DependencyGuard\Php\SymbolIteratorInterface;
14
use ReflectionClass;
15
16
class CandidateExtractor implements CandidateExtractorInterface
17
{
18
    /**
19
     * Extract violation candidates from the given Composer instance and symbols.
20
     *
21
     * @param Composer                $composer
22
     * @param SymbolIteratorInterface $symbols
23
     *
24
     * @return iterable|CandidateInterface[]
25
     */
26 6
    public function extract(
27
        Composer $composer,
28
        SymbolIteratorInterface $symbols
29
    ): iterable {
30 6
        $repository = $composer->getRepositoryManager()->getLocalRepository();
31 6
        $vendorPath = $composer->getConfig()->get('vendor-dir', 0);
32
33 6
        $packages = [];
34
35 6
        foreach ($symbols as $symbol) {
36 4
            $package = $this->extractPackage($vendorPath, $symbol);
37
38 4
            if ($package === null) {
39 1
                continue;
40
            }
41
42 4
            if (!array_key_exists($package, $packages)) {
43 4
                $packages[$package] = [];
44
            }
45
46 4
            $packages[$package][] = $symbol;
47
        }
48
49 6
        $candidates = [];
50 6
        $installed  = $repository->getPackages();
51
52 6
        foreach ($packages as $name => $symbols) {
53 4
            $package = $this->getPackageByName($installed, $name);
54 4
55
            if ($package === null) {
56
                continue;
57
            }
58
59 3
            $candidates[] = new Candidate(
60
                $package,
61 3
                new SymbolIterator(...$symbols)
62 3
            );
63 3
        }
64 3
65
        return $candidates;
66 4
    }
67
68
    /**
69 4
     * @param PackageInterface[]|iterable $packages
70 1
     * @param string $name
71
     * @return PackageInterface|null
72
     */
73 3
    private function getPackageByName(iterable $packages, string $name): ?PackageInterface
74 3
    {
75 3
        foreach ($packages as $package) {
76
            if ($package->getName() === $name) {
77
                return $package;
78
            }
79 6
        }
80
81
        return null;
82
    }
83
84
    /**
85
     * Extract the package name from the given PHP symbol.
86
     *
87
     * @param string          $vendorPath
88
     * @param SymbolInterface $symbol
89
     *
90 4
     * @return string|null
91
     */
92
    private function extractPackage(
93
        string $vendorPath,
94 4
        SymbolInterface $symbol
95
    ): ?string {
96 4
        static $packagesPerSymbol = [];
97
98 4
        $name = $symbol->getName();
99 1
100 1
        if (!array_key_exists($name, $packagesPerSymbol)) {
101
            $reflection = new ReflectionClass($name);
102
            $file       = $reflection->getFileName();
103 1
104 1
            // This happens for symbols in the current package.
105
            if (strpos($file, $vendorPath) !== 0) {
106
                return null;
107 1
            }
108 1
109 1
            $structure = explode(
110 1
                DIRECTORY_SEPARATOR,
111 1
                preg_replace(
112 1
                    sprintf(
113
                        '/^%s/',
114 1
                        preg_quote($vendorPath . DIRECTORY_SEPARATOR, '/')
115 1
                    ),
116
                    '',
117 1
                    $file
118
                ),
119
                3
120
            );
121
122 1
            // This happens when other code extends Composer root code, like:
123 1
            // composer/ClassLoader.php
124
            if (count($structure) < 3) {
125
                $packagesPerSymbol[$name] = null;
126 1
            }
127
128 1
            [$vendor, $package] = $structure;
129
130
            $packagesPerSymbol[$name] = sprintf('%s/%s', $vendor, $package);
131 4
        }
132
133
        return $packagesPerSymbol[$name] ?? null;
134
    }
135
}
136