Passed
Pull Request — master (#8)
by
unknown
03:48 queued 20s
created

CandidateExtractor   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Test Coverage

Coverage 89.58%

Importance

Changes 0
Metric Value
dl 0
loc 120
ccs 43
cts 48
cp 0.8958
rs 10
c 0
b 0
f 0
wmc 13

3 Methods

Rating   Name   Duplication   Size   Complexity  
A extractPackage() 0 42 4
B extract() 0 41 6
A getPackageByName() 0 9 3
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
        $installed = $repository->getPackages();
50
51 6
        $candidates = [];
52
53 6
        foreach ($packages as $name => $symbols) {
54 4
            $package = $this->getPackageByName($installed, $name);
55
56 4
            if ($package === null) {
57 1
                continue;
58
            }
59
60 3
            $candidates[] = new Candidate(
61 3
                $package,
62 3
                new SymbolIterator(...$symbols)
63
            );
64
        }
65
66 6
        return $candidates;
67
    }
68
69
    /**
70
     * @param PackageInterface[]|iterable $packages
71
     * @param string                      $name
72
     *
73
     * @return PackageInterface|null
74
     */
75
    private function getPackageByName(iterable $packages, string $name): ?PackageInterface
76
    {
77
        foreach ($packages as $package) {
78
            if ($package->getName() === $name) {
79
                return $package;
80
            }
81
        }
82
83
        return null;
84
    }
85
86
    /**
87
     * Extract the package name from the given PHP symbol.
88
     *
89
     * @param string          $vendorPath
90
     * @param SymbolInterface $symbol
91
     *
92
     * @return string|null
93
     */
94 4
    private function extractPackage(
95
        string $vendorPath,
96
        SymbolInterface $symbol
97
    ): ?string {
98 4
        static $packagesPerSymbol = [];
99
100 4
        $name = $symbol->getName();
101
102 4
        if (!array_key_exists($name, $packagesPerSymbol)) {
103 1
            $reflection = new ReflectionClass($name);
104 1
            $file       = $reflection->getFileName();
105
106
            // This happens for symbols in the current package.
107 1
            if (strpos($file, $vendorPath) !== 0) {
108 1
                return null;
109
            }
110
111 1
            $structure = explode(
112 1
                DIRECTORY_SEPARATOR,
113 1
                preg_replace(
114 1
                    sprintf(
115 1
                        '/^%s/',
116 1
                        preg_quote($vendorPath . DIRECTORY_SEPARATOR, '/')
117
                    ),
118 1
                    '',
119 1
                    $file
120
                ),
121 1
                3
122
            );
123
124
            // This happens when other code extends Composer root code, like:
125
            // composer/ClassLoader.php
126 1
            if (count($structure) < 3) {
127 1
                $packagesPerSymbol[$name] = null;
128
            }
129
130 1
            [$vendor, $package] = $structure;
131
132 1
            $packagesPerSymbol[$name] = sprintf('%s/%s', $vendor, $package);
133
        }
134
135 4
        return $packagesPerSymbol[$name] ?? null;
136
    }
137
}
138