DataClassFileHandler::addText()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 2
dl 0
loc 13
ccs 7
cts 7
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace CSoellinger\SilverStripe\ModelAnnotations\Handler;
4
5
use CSoellinger\SilverStripe\ModelAnnotations\Task\ModelAnnotationsTask;
6
use Exception;
7
use InvalidArgumentException;
8
use SilverStripe\Core\ClassInfo;
9
use SilverStripe\Core\Config\Config;
10
use SilverStripe\Core\Injector\Injectable;
11
use Psl\Filesystem as File;
12
13
/**
14
 * Manages the file from the data class. Saving path, content and some other
15
 * things.
16
 */
17
class DataClassFileHandler
18
{
19
    use Injectable;
20
21
    /**
22
     * @var string File path
23
     */
24
    private string $path;
25
26
    /**
27
     * @var string File content
28
     */
29
    private string $content;
30
31
    /**
32
     * @var \ast\Node File abstract syntax tree
33
     */
34
    private \ast\Node $ast;
35
36
    /**
37
     * Constructor. Given path has to be a valid file.
38
     */
39 14
    public function __construct(string $path)
40
    {
41
        // If we did not get a valid file we are throwing an exception
42 14
        if ($path === '' || File\exists($path) === false || File\is_file($path) === false) {
43 1
            throw new InvalidArgumentException('Error with file at path "' . $path . '"', 1);
44
        }
45
46 14
        $this->path = $path;
47 14
        $this->content = File\read_file($path);
48 14
        $this->ast = \ast\parse_code($this->content, 80);
49
    }
50
51
    /**
52
     * Get the file path.
53
     */
54 1
    public function getPath(): string
55
    {
56 1
        return $this->path;
57
    }
58
59
    /**
60
     * Get the file content.
61
     */
62 6
    public function getContent(): string
63
    {
64 6
        return $this->content;
65
    }
66
67
    /**
68
     * Get the file abstract syntax tree.
69
     */
70 1
    public function getAst(): \ast\Node
71
    {
72 1
        return $this->ast;
73
    }
74
75
    /**
76
     * Get namespace from abstract syntax tree.
77
     */
78 2
    public function getNamespaceAst(): ?\ast\Node
79
    {
80 2
        return $this->searchNamespaceAst($this->ast);
81
    }
82
83
    /**
84
     * Get class from abstract syntax tree.
85
     */
86 1
    public function getClassAst(string $fqn): ?\ast\Node
87
    {
88 1
        return $this->searchClassAst($this->ast, ClassInfo::shortName($fqn));
89
    }
90
91
    /**
92
     * Get all use statements from abstract syntax tree.
93
     *
94
     * @return \ast\Node[]
95
     */
96 1
    public function getUseStatementsFromAst()
97
    {
98 1
        return $this->searchUseStatementsInAst($this->ast);
99
    }
100
101
    /**
102
     * Add some text to {@see self::$content} at a specified line.
103
     *
104
     * @param string $text   - Text to add
105
     * @param int    $atLine - Insert at this line number
106
     */
107 2
    public function addText(string $text, int $atLine = 1): self
108
    {
109 2
        if ($atLine <= 0) {
110 1
            $atLine = 1;
111
        }
112
113 2
        $contentLines = explode(PHP_EOL, $this->content);
114
115 2
        array_splice($contentLines, $atLine - 1, 0, $text);
116
117 2
        $this->content = implode(PHP_EOL, $contentLines);
118
119 2
        return $this;
120
    }
121
122
    /**
123
     * Replace string in {@see self::$content}.
124
     */
125 1
    public function contentReplace(string $search, string $replace): self
126
    {
127 1
        $this->content = str_replace($search, $replace, $this->content);
128
129 1
        return $this;
130
    }
131
132
    /**
133
     * Write {@see self::$content} to the {@see self::$path}. Optionally create
134
     * a backup file.
135
     */
136 2
    public function write(): self
137
    {
138 2
        $createBackupFile = (bool) Config::forClass(ModelAnnotationsTask::class)->get('createBackupFile');
139
140 2
        if ($createBackupFile === true) {
141 1
            $backupFile = $this->path . '.bck';
142 1
            $cnt = 1;
143
144 1
            while (file_exists($backupFile)) {
145 1
                $backupFile = $this->path . '.' . $cnt . '.bck';
146 1
                ++$cnt;
147
            }
148
149 1
            File\copy($this->path, $backupFile, false);
150
        }
151
152 2
        File\write_file($this->path, $this->content);
153
154 2
        return $this;
155
    }
156
157
    /**
158
     * Search for a namespace declaration.
159
     *
160
     * @param \ast\Node|string $ast - Abstract syntax tree to search in
161
     */
162 2
    private function searchNamespaceAst($ast): ?\ast\Node
163
    {
164 2
        if ($ast instanceof \ast\Node) {
165 2
            if ($ast->kind === \ast\AST_NAMESPACE) {
166 1
                return $ast;
167
            }
168
169
            /** @var \ast\Node $child */
170 2
            foreach ($ast->children as $child) {
171 1
                $childAst = $this->searchNamespaceAst($child);
172
173 1
                if ($childAst !== null) {
174 1
                    return $childAst;
175
                }
176
            }
177
        }
178
179 1
        return null;
180
    }
181
182
    /**
183
     * Search for a class abstract syntax tree.
184
     *
185
     * @param \ast\Node|string $ast       - Abstract syntax tree to search in
186
     * @param string           $className - The class to search for
187
     */
188 1
    private function searchClassAst($ast, string $className): ?\ast\Node
189
    {
190 1
        if ($ast instanceof \ast\Node) {
191 1
            if ($ast->kind === \ast\AST_CLASS) {
192
                /** @var array<string,string> $meta */
193 1
                $meta = $ast->children;
194
195 1
                if ($meta['name'] === $className) {
196 1
                    return $ast;
197
                }
198
            }
199
200
            /** @var \ast\Node $child */
201 1
            foreach ($ast->children as $child) {
202 1
                $childAst = $this->searchClassAst($child, $className);
203
204 1
                if ($childAst !== null) {
205 1
                    return $childAst;
206
                }
207
            }
208
        }
209
210 1
        return null;
211
    }
212
213
    /**
214
     * Search for all use statements inside an abstract syntax tree.
215
     *
216
     * @param \ast\Node|string     $ast           - Abstract syntax tree
217
     * @param array<int,\ast\Node> $useStatements - Collected use statements
218
     *
219
     * @return \ast\Node[]
220
     */
221 1
    private function searchUseStatementsInAst($ast, $useStatements = [])
222
    {
223 1
        if ($ast instanceof \ast\Node) {
224 1
            if ($ast->kind === \ast\AST_USE_ELEM) {
225 1
                $useStatements[] = $ast;
226
            }
227
228
            /** @var \ast\Node $child */
229 1
            foreach ($ast->children as $child) {
230
                /** @var array<int,\ast\Node> $useStatements */
231 1
                $useStatements = $this->searchUseStatementsInAst($child, $useStatements);
232
            }
233
        }
234
235
        /** @var array<int,\ast\Node> $useStatements */
236 1
        return $useStatements;
237
    }
238
}
239