Passed
Branch entity_code_generation (017b91)
by Gerrit
14:35
created

skipIrrelevantCode()   D

Complexity

Conditions 19
Paths 6

Size

Total Lines 37
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 1
Metric Value
cc 19
eloc 28
c 1
b 1
f 1
nc 6
nop 2
dl 0
loc 37
rs 4.5166

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Copyright (C) 2019 Gerrit Addiks.
4
 * This package (including this file) was released under the terms of the GPL-3.0.
5
 * You should have received a copy of the GNU General Public License along with this program.
6
 * If not, see <http://www.gnu.org/licenses/> or send me a mail so i can send you a copy.
7
 *
8
 * @license GPL-3.0
9
 *
10
 * @author Gerrit Addiks <[email protected]>
11
 */
12
13
namespace Addiks\RDMBundle\DataLoader\BlackMagic;
14
15
use Addiks\RDMBundle\Mapping\EntityMappingInterface;
16
use Composer\Autoload\ClassLoader;
17
use Webmozart\Assert\Assert;
18
use Addiks\RDMBundle\Mapping\MappingInterface;
19
use ErrorException;
20
use Doctrine\DBAL\Schema\Column;
21
use Addiks\RDMBundle\DataLoader\BlackMagic\BlackMagicDataLoader;
22
23
final class BlackMagicEntityCodeGenerator
24
{
25
26
    public function __construct(
27
        public readonly string $targetDirectory,
28
        private BlackMagicDataLoader $dataLoader,
29
        public readonly string $indenting = "    "
30
    ) {
31
    }
32
    
33
    public function processMapping(
34
        EntityMappingInterface $mapping,
35
        ClassLoader $loader
36
    ): string|null {
37
        /** @var array<array-key, Column> $columns */
38
        $columns = $mapping->collectDBALColumns();
39
        
40
        if (empty($columns)) {
41
            return null;
42
        }
43
        
44
        /** @var string $fullClassName */
45
        $fullClassName = $mapping->getEntityClassName();
46
        
47
        /** @var string|false $filePath */
48
        $filePath = $loader->findFile($fullClassName);
49
        
50
        if ($filePath === false) {
51
            return null;
52
        }
53
        
54
        /** @var string $entityPHP */
55
        $entityPHP = file_get_contents($filePath);
56
        
57
        /** @var int $classStartPosition */
58
        $classStartPosition = self::findClassStartPosition($fullClassName, $entityPHP);
59
        
60
        /** @var array<string, Column> $writtenFieldNames */
61
        $writtenFieldNames = array();
62
        
63
        foreach ($columns as $column) {
64
            
65
            /** @var string $fieldName */
66
            $fieldName = $this->dataLoader->columnToFieldName($column);
67
            
68
            if (isset($writtenFieldNames[$fieldName])) {
69
                continue;
70
            }
71
            
72
            $writtenFieldNames[$fieldName] = $column;
73
            
74
            /** @var string $fieldPHP */
75
            $fieldPHP = sprintf(
76
                "\n%spublic $%s;\n",
77
                $this->indenting,
78
                $fieldName
79
            );
80
            
81
            $entityPHP = sprintf(
82
                '%s%s%s',
83
                substr($entityPHP, 0, $classStartPosition),
84
                $fieldPHP,
85
                substr($entityPHP, $classStartPosition)
86
            );
87
        }
88
        
89
        $targetFilePath = sprintf(
90
            '%s/%s.php',
91
            $this->targetDirectory,
92
            str_replace('\\', '_', $fullClassName)
93
        );
94
        
95
        if (!is_dir($this->targetDirectory)) {
96
            mkdir($this->targetDirectory, 0777, true);
97
        }
98
        
99
        file_put_contents($targetFilePath, $entityPHP);
100
        
101
        return $targetFilePath;
102
    }
103
    
104
    private static function findClassStartPosition(
105
        string $fullClassName, 
106
        string $entityPHP
107
    ): int {
108
        
109
        /** @var int|null|false $classStartPosition */
110
        $classStartPosition = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $classStartPosition is dead and can be removed.
Loading history...
111
        
112
        /** @var int $position */
113
        $position = 0;
114
        
115
        /** @var array<int, string> $namespacePath */
116
        $namespacePath = explode("\\", $fullClassName);
117
        
118
        /** @var string $className */
119
        $className = array_pop($namespacePath);
120
        
121
        /** @var string $pattern */
122
        $pattern = sprintf(
123
            '/^(interface|trait|class)\s+%s\W*/is',
124
            $className
125
        );
126
        
127
        /** @var int $codeLength */
128
        $codeLength = strlen($entityPHP);
129
        
130
        do {
131
            if (preg_match($pattern, substr($entityPHP, $position, 128))) {
132
                $classStartPosition = strpos($entityPHP, '{', $position);
133
                
134
                if (is_int($classStartPosition)) {
135
                    return $classStartPosition + 1;
136
137
                } else {
138
                    $classStartPosition = null;
139
                }
140
            }
141
            
142
            self::skipIrrelevantCode($entityPHP, $position);
143
            
144
            $position++;
145
        } while ($position < $codeLength);
146
        
147
        throw new ErrorException(sprintf(
148
            'Could not find start position of class "%s" if file "%s"!',
149
            $fullClassName,
150
            $entityPHP
151
        ));
152
    }
153
    
154
    private static function skipIrrelevantCode(string $phpCode, int &$position): void
155
    {
156
        /** @var int $codeLength */
157
        $codeLength = strlen($phpCode);
158
        
159
        if (substr($phpCode, $position, 2) === '/*') {
160
            do {
161
                $position++;
162
            } while (substr($phpCode, $position, 2) !== '*/' && $position < $codeLength);
163
            
164
        } elseif (substr($phpCode, $position, 2) === '//') {
165
            do {
166
                $position++;
167
            } while ($phpCode[$position] !== "\n" && $position < $codeLength);
168
            
169
        } elseif (substr($phpCode, $position, 3) === '<<<') {
170
            $position += 3;
171
            $eofFlag = "";
172
            do {
173
                $eofFlag .= $phpCode[$position];
174
                $position++;
175
            } while ($phpCode[$position] !== "\n");
176
            
177
            do {
178
                $position++;
179
                $charsAtPosition = substr($phpCode, $position, strlen($eofFlag) + 1);
180
            } while ($charsAtPosition !== $eofFlag . ';' && $position < $codeLength);
181
            
182
        } elseif ($phpCode[$position] === '"') {
183
            do {
184
                $position++;
185
            } while ($phpCode[$position] !== '"' && $phpCode[$position - 1] !== '\\' && $position < $codeLength);
186
187
        } elseif ($phpCode[$position] === "'") {
188
            do {
189
                $position++;
190
            } while ($phpCode[$position] !== "'" && $phpCode[$position - 1] !== '\\' && $position < $codeLength);
191
        }
192
    }
193
    
194
}
195