Passed
Pull Request — master (#34)
by
unknown
05:32 queued 02:27
created

CandidateExtractor::extractPackage()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 42
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 4

Importance

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