File::create()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 10
nc 2
nop 3
dl 0
loc 18
ccs 11
cts 11
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Jhoff\PhpEditor;
4
5
use Exception;
6
use ReflectionClass;
7
use PhpParser\Parser\Php7;
8
use PhpParser\NodeTraverser;
9
use PhpParser\ParserFactory;
10
use Jhoff\PhpEditor\DocBlock;
11
use PhpParser\BuilderFactory;
12
use PhpParser\Lexer\Emulative;
13
use PhpParser\PrettyPrinter\Standard;
14
use PhpParser\NodeVisitor\CloningVisitor;
15
16
class File
17
{
18
    /**
19
     * The php file to be edited
20
     *
21
     * @var string
22
     */
23
    protected $file;
24
25
    /**
26
     * The original parsed statements
27
     *
28
     * @var array
29
     */
30
    protected $originalStatements;
31
32
    /**
33
     * The original tokens
34
     *
35
     * @var array
36
     */
37
    protected $originalTokens;
38
39
    /**
40
     * The new modified statements
41
     *
42
     * @var array
43
     */
44
    protected $newStatements;
45
46
    /**
47
     * Creates a new skeleton class and writes it to disk
48
     *
49
     * @param string $file
50
     * @param string $namespace
51
     * @param string $class
52
     *
53
     * @return static
54
     */
55 10
    public static function create(string $file, string $namespace, string $class)
56
    {
57 10
        if (file_exists($file)) {
58 1
            throw new Exception("Cannot create $file. File already exists.");
59
        }
60
61 9
        $factory = new BuilderFactory;
62
63 9
        $contents = (new Standard)
64 9
            ->prettyPrintFile([
65 9
                $factory->namespace($namespace)
66 9
                    ->addStmt($factory->class($class))
67 9
                    ->getNode()
68
            ]);
69
70 9
        file_put_contents($file, $contents);
71
72 9
        return new static($file);
73
    }
74
75
    /**
76
     * Open an existing class
77
     *
78
     * @param string $class
79
     *
80
     * @return static
81
     */
82 2
    public static function forClass(string $class)
83
    {
84 2
        if (! class_exists($class)) {
85 1
            throw new Exception("$class does not exist.");
86
        }
87
88 1
        return new static((new ReflectionClass($class))->getFileName());
89
    }
90
91
    /**
92
     * Open an existing file
93
     *
94
     * @param string $file
95
     *
96
     * @return static
97
     */
98 3
    public static function open(string $file)
99
    {
100 3
        if (! file_exists($file)) {
101 1
            throw new Exception("Cannot open $file. File does not exist.");
102
        }
103
104 2
        return new static($file);
105
    }
106
107
    /**
108
     * Open or create existing file
109
     *
110
     * @param string $file
111
     * @param string $namespace
112
     * @param string $class
113
     *
114
     * @return static
115
     */
116 2
    public static function openOrCreate(string $file, string $namespace, string $class)
117
    {
118 2
        return file_exists($file)
119 1
            ? static::open($file)
120 2
            : static::create($file, $namespace, $class);
121
    }
122
123
    /**
124
     * Instantiate a new file class
125
     *
126
     * @param string $file
127
     */
128 12
    public function __construct(string $file)
129
    {
130 12
        $this->file = $file;
131 12
        $this->parseStatements();
132 12
    }
133
134
    /**
135
     * Adds a method to the class, lazily at the end
136
     *
137
     * @param string $visibility
138
     * @param string $method
139
     * @param string $contents
140
     * @param array $docblock
141
     *
142
     * @return $this
143
     */
144 3
    public function addMethod(string $visibility, string $method, string $contents, array $docblock = [])
145
    {
146 3
        $namespace = $this->newStatements[0];
147
148 3
        $namespace->stmts[count($namespace->stmts) - 1]
149 3
            ->stmts[] = (new BuilderFactory)
150 3
                ->method($method)
151 3
                ->{'make' . ucfirst($visibility)}()
152 3
                ->addStmts($this->parseArbitraryCode($contents))
153 3
                ->setDocComment(DocBlock::create($docblock)->getOutput())
154 3
                ->getNode();
155
156 3
        return $this;
157
    }
158
159
    /**
160
     * Add a private method to the class
161
     *
162
     * @param string $method
163
     * @param string $contents
164
     * @param array $docblock
165
     *
166
     * @return $this
167
     */
168 1
    public function addPrivateMethod(string $method, string $contents, array $docblock = [])
169
    {
170 1
        return $this->addMethod('private', $method, $contents, $docblock);
171
    }
172
173
    /**
174
     * Add a protected method to the class
175
     *
176
     * @param string $method
177
     * @param string $contents
178
     * @param array $docblock
179
     *
180
     * @return $this
181
     */
182 1
    public function addProtectedMethod(string $method, string $contents, array $docblock = [])
183
    {
184 1
        return $this->addMethod('protected', $method, $contents, $docblock);
185
    }
186
187
    /**
188
     * Add a public method to the class
189
     *
190
     * @param string $method
191
     * @param string $contents
192
     * @param array $docblock
193
     *
194
     * @return $this
195
     */
196 1
    public function addPublicMethod(string $method, string $contents, array $docblock = [])
197
    {
198 1
        return $this->addMethod('public', $method, $contents, $docblock);
199
    }
200
201
    /**
202
     * Add a use statement to the class, lazily at the beginning
203
     *
204
     * @return $this
205
     */
206 3
    public function addUse()
207
    {
208 3
        $uses = collect($this->newStatements[0]->stmts);
209 3
        $class = $uses->pop();
210
211 3
        foreach (func_get_args() as $use) {
212 3
            $uses->push(
213 3
                (new BuilderFactory)
214 3
                    ->use((string) $use)
215 3
                    ->getNode()
216
            );
217
        }
218
219 3
        $class->setAttribute('startLine', 0);
220
221 3
        $this->newStatements[0]->stmts = $uses
222
            ->transform(function ($use) {
223
                return [
224 3
                    'name' => $name = $use->uses[0]->name->toString(),
225 3
                    'length' => strlen($name),
226 3
                    'use' => $use,
227
                ];
228 3
            })
229 3
            ->unique('name')
230 3
            ->sortBy('name')
231 3
            ->sortBy('length')
232 3
            ->pluck('use')
233 3
            ->push($class)
234 3
            ->all();
235
236 3
        return $this;
237
    }
238
239
    /**
240
     * Gets the current filename
241
     *
242
     * @return string
243
     */
244 4
    public function getFilename()
245
    {
246 4
        return $this->file;
247
    }
248
249
    /**
250
     * Gets the modified file contents
251
     *
252
     * @return string
253
     */
254 9
    public function getNewFileContents()
255
    {
256 9
        return (new Standard)
257 9
            ->printFormatPreserving(
258 9
                $this->newStatements,
259 9
                $this->originalStatements,
260 9
                $this->originalTokens
261
            );
262
    }
263
264
    /**
265
     * Writes the newly formatted code to disk
266
     *
267
     * @return $this
268
     */
269 1
    public function write()
270
    {
271 1
        file_put_contents(
272 1
            $this->file,
273 1
            $this->getNewFileContents()
274
        );
275
276 1
        return $this;
277
    }
278
279
    /**
280
     * Parses some arbitrary code and returns a statement tree
281
     *
282
     * @param string $code
283
     * @return array
284
     */
285 3
    protected function parseArbitraryCode(string $code)
286
    {
287 3
        return (new ParserFactory)
288 3
            ->create(ParserFactory::PREFER_PHP7)
289 3
            ->parse('<?php ' . $code);
290
    }
291
292
    /**
293
     * Parses the file and stores the original state so any modifications
294
     * to the code will preserve any existing formatting
295
     *
296
     * @return $this
297
     */
298 12
    protected function parseStatements()
299
    {
300 12
        $lexer = new Emulative([
301 12
            'usedAttributes' => ['comments', 'startLine', 'endLine', 'startTokenPos', 'endTokenPos'],
302
        ]);
303
304 12
        $this->originalStatements = (new Php7($lexer))
305 12
            ->parse(file_get_contents($this->file));
306 12
        $this->originalTokens = $lexer->getTokens();
307
308 12
        $traverser = new NodeTraverser;
309 12
        $traverser->addVisitor(new CloningVisitor);
310
311 12
        $this->newStatements = $traverser->traverse($this->originalStatements);
312
313 12
        return $this;
314
    }
315
}
316