Test Failed
Pull Request — master (#37)
by Divine Niiquaye
03:11
created

DefinitionsSplitter   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 117
Duplicated Lines 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 57
c 2
b 0
f 0
dl 0
loc 117
rs 10
wmc 20

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 5 1
A traitHash() 0 3 1
B enterNode() 0 33 7
A getTraits() 0 3 1
B buildTraits() 0 47 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of DivineNii opensource projects.
7
 *
8
 * PHP version 7.4 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2021 DivineNii (https://divinenii.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Rade\DI\NodeVisitor;
19
20
use PhpParser\Node\Expr\Include_;
21
use PhpParser\Node\Scalar\String_;
22
use PhpParser\Node\Stmt\{Class_, Declare_, Expression};
23
use PhpParser\NodeVisitorAbstract;
24
use Rade\DI\Builder\CodePrinter;
25
use Symfony\Component\Config\ConfigCache;
26
27
/**
28
 * This class splits definitions equally with the total amount of $maxDefinitions
29
 * into traits, and imported as use traits into compiled container class.
30
 *
31
 * @author Divine Niiquaye Ibok <[email protected]>
32
 */
33
class DefinitionsSplitter extends NodeVisitorAbstract
34
{
35
    private int $maxCount;
36
    private string $fileName;
37
    private array $traits = [];
38
    private ?string $previousTrait = null;
39
    private \PhpParser\BuilderFactory $builder;
40
    private ?Declare_ $strictDeclare = null;
41
42
    public function __construct(int $maxDefinitions = 500, string $fileName = 'definitions_autoload.php')
43
    {
44
        $this->maxCount = $maxDefinitions;
45
        $this->fileName = $fileName;
46
        $this->builder = new \PhpParser\BuilderFactory();
47
    }
48
49
    public function getTraits(): array
50
    {
51
        return $this->traits;
52
    }
53
54
    /**
55
     * Use this function to build generated traits into a cache directory
56
     * and return the file that requires all traits.
57
     *
58
     * @param array<int,string> $includePaths
59
     */
60
    public function buildTraits(string $cacheDirectory, bool $debug = false, array $includePaths = []): string
61
    {
62
        $traitsDirectory = \rtrim($cacheDirectory, '/') . '/Definitions' . $this->traitHash($cacheDirectory);
63
        $autoLoadAst = [];
64
65
        if (null !== $this->strictDeclare) {
66
            $autoLoadAst[] = $this->strictDeclare;
67
        }
68
69
        foreach ($includePaths as $bPath) {
70
            $autoLoadAst[] = new Expression(new Include_(new String_($bPath), Include_::TYPE_REQUIRE));
71
        }
72
73
        foreach ($this->traits as $traitName => $traitStmts) {
74
            $cache = new ConfigCache($traitsDirectory . '/' . $traitName . '.php', $debug);
75
            $autoLoadInclude = new Expression(new Include_(new String_($cache->getPath()), Include_::TYPE_REQUIRE));
76
77
            if (\count($autoLoadAst) <= 1) {
78
                $autoLoadInclude->setDocComment(new \PhpParser\Comment\Doc(\str_replace('class', 'file', CodePrinter::COMMENT)));
79
            }
80
81
            if (!$cache->isFresh() || $debug) {
82
                $traitAst = [];
83
                $traitComment = "\n *\n" . ' * @property array<int,mixed> $services' . \PHP_EOL . ' * @property array<int,mixed> $privates';
84
85
                if (null !== $this->strictDeclare) {
86
                    $traitAst[] = $this->strictDeclare;
87
                }
88
89
                $traitAst[] = $this->builder->trait($traitName)
90
                    ->setDocComment(\strtr(CodePrinter::COMMENT, ['class' => 'trait', '.' => '.' . $traitComment . \PHP_EOL]))
91
                    ->addStmts($traitStmts)
92
                    ->getNode();
93
94
                $cache->write(CodePrinter::print($traitAst));
95
            }
96
97
            $autoLoadAst[] = $autoLoadInclude;
98
        }
99
100
        $autoloadCache = new ConfigCache(\rtrim($cacheDirectory, '/') . '/' . $this->fileName, $debug);
101
102
        if (!$autoloadCache->isFresh() || $debug) {
103
            $autoloadCache->write(CodePrinter::print($autoLoadAst) . \PHP_EOL);
104
        }
105
106
        return $autoloadCache->getPath();
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function enterNode(\PhpParser\Node $node)
113
    {
114
        if ($node instanceof Declare_) {
115
            $this->strictDeclare = $node;
116
        } elseif ($node instanceof Class_) {
117
            $indexHash = ($stmtsCount = \count($nodeStmts = $node->stmts)) . 'a';
118
119
            while ($stmtsCount >= $this->maxCount) {
120
                $traitName = 'Definition_' . $this->traitHash($indexHash) . 'Trait';
121
122
                if (null === $this->previousTrait) {
123
                    $stmtsCount = \count($this->traits[$traitName] = \array_splice($nodeStmts, $this->maxCount));
124
                } else {
125
                    $traitStmts = &$this->traits[$this->previousTrait];
126
                    $stmtsCount = \count($this->traits[$traitName] = \array_splice($traitStmts, $this->maxCount));
127
                }
128
129
                $this->previousTrait = $traitName;
130
                ++$indexHash;
131
            }
132
133
            if (empty($this->traits[$this->previousTrait])) {
134
                unset($this->traits[$this->previousTrait]);
135
            }
136
137
            if (!empty($this->traits)) {
138
                $node->stmts = [$this->builder->useTrait(...\array_keys($this->traits))->getNode(), ...$nodeStmts];
139
            }
140
141
            $this->previousTrait = null;
142
        }
143
144
        return parent::enterNode($node);
0 ignored issues
show
Bug introduced by
Are you sure the usage of parent::enterNode($node) targeting PhpParser\NodeVisitorAbstract::enterNode() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
145
    }
146
147
    private function traitHash(string $indexHash): string
148
    {
149
        return \substr(\ucwords(\base64_encode(\hash('sha256', $indexHash))), 0, 7);
150
    }
151
}
152