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

CandidateExtractor   A

Complexity

Total Complexity 13

Size/Duplication

Total Lines 120
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 45
dl 0
loc 120
ccs 50
cts 50
cp 1
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 6
51
        $candidates = [];
52 6
53 4
        foreach ($packages as $name => $symbols) {
54 4
            $package = $this->getPackageByName($installed, $name);
55
56
            if ($package === null) {
57
                continue;
58
            }
59 3
60
            $candidates[] = new Candidate(
61 3
                $package,
62 3
                new SymbolIterator(...$symbols)
63 3
            );
64 3
        }
65
66 4
        return $candidates;
67
    }
68
69 4
    /**
70 1
     * @param PackageInterface[]|iterable $packages
71
     * @param string                      $name
72
     *
73 3
     * @return PackageInterface|null
74 3
     */
75 3
    private function getPackageByName(iterable $packages, string $name): ?PackageInterface
76
    {
77
        foreach ($packages as $package) {
78
            if ($package->getName() === $name) {
79 6
                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 4
     * @param SymbolInterface $symbol
91
     *
92
     * @return string|null
93
     */
94 4
    private function extractPackage(
95
        string $vendorPath,
96 4
        SymbolInterface $symbol
97
    ): ?string {
98 4
        static $packagesPerSymbol = [];
99 1
100 1
        $name = $symbol->getName();
101
102
        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 1
            }
110 1
111 1
            $structure = explode(
112 1
                DIRECTORY_SEPARATOR,
113
                preg_replace(
114 1
                    sprintf(
115 1
                        '/^%s/',
116
                        preg_quote($vendorPath . DIRECTORY_SEPARATOR, '/')
117 1
                    ),
118
                    '',
119
                    $file
120
                ),
121
                3
122 1
            );
123 1
124
            // This happens when other code extends Composer root code, like:
125
            // composer/ClassLoader.php
126 1
            if (count($structure) < 3) {
127
                $packagesPerSymbol[$name] = null;
128 1
            }
129
130
            [$vendor, $package] = $structure;
131 4
132
            $packagesPerSymbol[$name] = sprintf('%s/%s', $vendor, $package);
133
        }
134
135
        return $packagesPerSymbol[$name] ?? null;
136
    }
137
}
138