• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Composer-Graph / 5399088069

pending completion
5399088069

push

github

web-flow
New code style and PHP 8.1+ (#14)

86 of 86 new or added lines in 5 files covered. (100.0%)

276 of 310 relevant lines covered (89.03%)

76.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.25
/src/ComposerGraph.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Composer-Graph.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Composer-Graph
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\ComposerGraph;
18

19
use JBZoo\Data\Data;
20
use JBZoo\MermaidPHP\Graph;
21
use JBZoo\MermaidPHP\Link;
22
use JBZoo\MermaidPHP\Node;
23

24
use function JBZoo\Data\data;
25
use function JBZoo\Utils\bool;
26

27
/**
28
 * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
29
 */
30
class ComposerGraph
31
{
32
    public const FORMAT_HTML    = 'html';
33
    public const FORMAT_MERMAID = 'mermaid';
34

35
    protected Data $params;
36

37
    private Graph $graphWrapper;
38

39
    private Graph $graphMain;
40

41
    private Graph $graphRequire;
42

43
    private Graph $graphDev;
44

45
    private Graph $graphPlatform;
46

47
    private Collection $collection;
48

49
    /** @var string[] */
50
    private array $createdLinks = [];
51

52
    private array $renderedNodes = [];
53

54
    public function __construct(Collection $collection, array $params = [])
55
    {
56
        $this->collection = $collection;
96✔
57

58
        $this->params = data(\array_merge([
96✔
59
            // view options
60
            'php'          => false,
48✔
61
            'ext'          => false,
48✔
62
            'dev'          => false,
48✔
63
            'suggest'      => false,
48✔
64
            'link-version' => false,
48✔
65
            'lib-version'  => false,
48✔
66
            // output options
67
            'output-path' => null,
48✔
68
            'direction'   => Graph::LEFT_RIGHT,
48✔
69
            'format'      => self::FORMAT_HTML,
48✔
70
            'vendor-dir'  => null,
48✔
71
            'abc-order'   => false,
48✔
72
        ], $params));
48✔
73

74
        $direction = $this->params->getString('direction');
96✔
75
        $order     = $this->params->getBool('abc-order');
96✔
76

77
        $this->graphWrapper = new Graph(['direction' => $direction, 'abc_order' => $order]);
96✔
78
        $this->graphWrapper->addSubGraph($this->graphMain = new Graph(['title' => 'Your Package']));
96✔
79

80
        $this->graphRequire  = new Graph(['direction' => $direction, 'title' => 'Required', 'abc_order' => $order]);
96✔
81
        $this->graphDev      = new Graph(['direction' => $direction, 'title' => 'Required Dev', 'abc_order' => $order]);
96✔
82
        $this->graphPlatform = new Graph(['direction' => $direction, 'title' => 'PHP Platform', 'abc_order' => $order]);
96✔
83
    }
84

85
    public function build(): string
86
    {
87
        /** @phpstan-ignore-next-line */
88
        $isSafeMode = \defined('\IS_PHPUNIT_TEST') && !IS_PHPUNIT_TEST;
96✔
89
        Node::safeMode($isSafeMode);
96✔
90

91
        $main = $this->collection->getMain();
96✔
92
        $this->renderNodeTree($main);
96✔
93

94
        return $this->render();
96✔
95
    }
96

97
    /**
98
     * @SuppressWarnings(PHPMD.NPathComplexity)
99
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
100
     */
101
    public function renderNodeTree(Package $source): bool
102
    {
103
        if (\in_array($source->getId(), $this->renderedNodes, true)) {
96✔
104
            return false;
48✔
105
        }
106
        $this->renderedNodes[] = $source->getId();
96✔
107

108
        $showPhp     = $this->params->getBool('php');
96✔
109
        $showExt     = $this->params->getBool('ext');
96✔
110
        $showDev     = $this->params->getBool('dev');
96✔
111
        $showSuggest = $this->params->getBool('suggest');
96✔
112

113
        if (!$showSuggest && !$source->isTag(Package::HAS_META)) {
96✔
114
            return false;
×
115
        }
116

117
        foreach ($source->getRequired() as $target => $version) {
96✔
118
            $target = (string)$target;
92✔
119
            $target = $this->collection->getByName($target);
92✔
120
            if (
121
                (!$showExt && $target->isPhpExt())
92✔
122
                || (!$showPhp && $target->isPhp())
92✔
123
            ) {
124
                continue;
80✔
125
            }
126

127
            $this->renderNodeTree($target);
92✔
128
            $this->addLink($source, $target, $version);
92✔
129
        }
130

131
        if ($showDev) {
96✔
132
            foreach ($source->getRequiredDev() as $target => $version) {
48✔
133
                $target = (string)$target;
36✔
134
                $target = $this->collection->getByName($target);
36✔
135
                if (
136
                    (!$showExt && $target->isPhpExt())
36✔
137
                    || (!$showPhp && $target->isPhp())
36✔
138
                ) {
139
                    continue;
20✔
140
                }
141

142
                $this->renderNodeTree($target);
36✔
143
                $this->addLink($source, $target, $version);
36✔
144
            }
145
        }
146

147
        if ($showSuggest) {
96✔
148
            foreach (\array_keys($source->getSuggested()) as $target) {
48✔
149
                $target = $this->collection->getByName((string)$target);
36✔
150
                if (
151
                    (!$showExt && $target->isPhpExt())
36✔
152
                    || (
153
                        !$showDev
36✔
154
                        && !$target->isTag(Package::REQUIRED)
36✔
155
                        && $target->isTag(Package::REQUIRED_DEV)
36✔
156
                    )
157
                ) {
158
                    continue;
28✔
159
                }
160

161
                $this->renderNodeTree($target);
36✔
162
                $this->addLink($source, $target, 'suggest');
36✔
163
            }
164
        }
165

166
        return true;
96✔
167
    }
168

169
    protected function render(): string
170
    {
171
        if (\count($this->graphRequire->getNodes()) > 0) {
96✔
172
            $this->graphWrapper->addSubGraph($this->graphRequire);
92✔
173
        }
174

175
        if (\count($this->graphDev->getNodes()) > 0) {
96✔
176
            $this->graphWrapper->addSubGraph($this->graphDev);
44✔
177
        }
178

179
        if (\count($this->graphPlatform->getNodes()) > 0) {
96✔
180
            $this->graphWrapper->addSubGraph($this->graphPlatform);
40✔
181
        }
182

183
        $format = \strtolower(\trim($this->params->getString('format')));
96✔
184
        if ($format === self::FORMAT_HTML) {
96✔
185
            $htmlPath = (string)$this->params->get('output-path');
96✔
186

187
            $headerKeys = \array_filter(
96✔
188
                \array_keys(
96✔
189
                    $this->params->getArrayCopy(),
96✔
190
                    static fn (string $key): bool => \in_array(
96✔
191
                        $key,
48✔
192
                        ['php', 'ext', 'dev', 'suggest', 'link-version', 'lib-version'],
48✔
193
                        true,
48✔
194
                    ),
48✔
195
                    true,
48✔
196
                ),
48✔
197
            );
48✔
198

199
            /**
200
             * @psalm-suppress InvalidArgument
201
             * @phpstan-ignore-next-line
202
             */
203
            $headerKeys = \array_reduce($headerKeys, function (array $acc, string $key): array {
96✔
204
                if (bool($this->params->get($key))) {
×
205
                    $acc[] = $key;
×
206
                }
207

208
                return $acc;
×
209
            }, []);
48✔
210

211
            $titlePostfix = '';
96✔
212
            if (\count($headerKeys) > 0) {
96✔
213
                $flags        = \implode(' / ', $headerKeys);
×
214
                $titlePostfix = "\n<br><small><small>Flags: {$flags}</small></small>";
×
215
            }
216

217
            $main    = $this->collection->getMain();
96✔
218
            $htmlDir = \dirname($htmlPath);
96✔
219
            if (!\is_dir($htmlDir) && !\mkdir($htmlDir, 0755, true) && !\is_dir($htmlDir)) {
96✔
220
                throw new \RuntimeException("Directory \"{$htmlDir}\" was not created");
×
221
            }
222

223
            \file_put_contents($htmlPath, $this->graphWrapper->renderHtml([
96✔
224
                'version' => '8.6.0',
48✔
225
                'title'   => $main->getName() . ' - Graph of Dependencies' . $titlePostfix,
96✔
226
            ]));
48✔
227

228
            return $htmlPath;
96✔
229
        }
230

231
        if ($format === self::FORMAT_MERMAID) {
96✔
232
            return $this->graphWrapper->render();
96✔
233
        }
234

235
        throw new Exception("Invalid format: \"{$format}\"");
×
236
    }
237

238
    protected function createNode(Package $package): Node
239
    {
240
        $graph = $this->getGraph($package);
92✔
241

242
        $nodeId      = $package->getId();
92✔
243
        $showVersion = (bool)$this->params->getString('lib-version');
92✔
244

245
        $currentNode = $graph->getNode($nodeId);
92✔
246
        if ($currentNode !== null) {
92✔
247
            return $currentNode;
72✔
248
        }
249

250
        $vendorDir = $this->params->getString('vendor-dir');
92✔
251
        if ($vendorDir !== '') {
92✔
252
            $isInstalled = $package->isTag(Package::INSTALLED);
×
253
        } else {
254
            $isInstalled = $package->isTag(Package::HAS_META);
92✔
255
        }
256

257
        if ($isInstalled) {
92✔
258
            $node = new Node($nodeId, $package->getName($showVersion));
92✔
259
        } else {
260
            $node = new Node($nodeId, $package->getName($showVersion), Node::STADIUM);
36✔
261
        }
262

263
        $graph->addNode($node);
92✔
264

265
        return $node;
92✔
266
    }
267

268
    private function addLink(Package $source, Package $target, string $version): void
269
    {
270
        $sourceName = $source->getId();
92✔
271
        $targetName = $target->getId();
92✔
272
        $version    = $version === '*' ? '' : $version;
92✔
273

274
        $pattern = "{$sourceName}=={$targetName}";
92✔
275

276
        if (!\array_key_exists($pattern, $this->createdLinks)) {
92✔
277
            $sourceNode  = $this->createNode($source);
92✔
278
            $targetNode  = $this->createNode($target);
92✔
279
            $isSuggested = $version === 'suggest';
92✔
280

281
            if (!$this->params->getBool('link-version')) {
92✔
282
                $version = '';
60✔
283
            }
284

285
            if ($source->isMain() && $target->isDirectPackage()) {
92✔
286
                $this->graphWrapper->addLink(new Link($sourceNode, $targetNode, $version, Link::THICK));
92✔
287
            } elseif ($source->isMain() && $target->isDirectPackageDev()) {
72✔
288
                $this->graphWrapper->addLink(new Link($sourceNode, $targetNode, $version, Link::THICK));
36✔
289
            } elseif ($isSuggested) {
64✔
290
                $this->graphWrapper->addLink(new Link($sourceNode, $targetNode, $version, Link::DOTTED));
36✔
291
            } else {
292
                $this->graphWrapper->addLink(new Link($sourceNode, $targetNode, $version, Link::ARROW));
56✔
293
            }
294

295
            $this->createdLinks[$pattern] = $version;
92✔
296
        }
297
    }
298

299
    private function getGraph(Package $package): Graph
300
    {
301
        if ($package->isMain()) {
92✔
302
            return $this->graphMain;
92✔
303
        }
304

305
        if ($package->isPlatform()) {
92✔
306
            return $this->graphPlatform;
40✔
307
        }
308

309
        if ($package->isTag(Package::DIRECT)) {
92✔
310
            if ($package->isTag(Package::REQUIRED) || $package->isTag(Package::SUGGEST)) {
92✔
311
                return $this->graphRequire;
92✔
312
            }
313

314
            if ($package->isTag(Package::REQUIRED_DEV)) {
36✔
315
                return $this->graphDev;
36✔
316
            }
317
        }
318

319
        if ($package->isTag(Package::REQUIRED)) {
48✔
320
            return $this->graphRequire;
48✔
321
        }
322

323
        if ($package->isTag(Package::REQUIRED_DEV)) {
28✔
324
            return $this->graphDev;
28✔
325
        }
326

327
        throw new Exception("Can't detect env for package: {$package->getName(false)}");
×
328
    }
329
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc