Chris@13
|
1 <?php
|
Chris@13
|
2
|
Chris@13
|
3 /*
|
Chris@13
|
4 * This file is part of Psy Shell.
|
Chris@13
|
5 *
|
Chris@13
|
6 * (c) 2012-2018 Justin Hileman
|
Chris@13
|
7 *
|
Chris@13
|
8 * For the full copyright and license information, please view the LICENSE
|
Chris@13
|
9 * file that was distributed with this source code.
|
Chris@13
|
10 */
|
Chris@13
|
11
|
Chris@13
|
12 namespace Psy\ExecutionLoop;
|
Chris@13
|
13
|
Chris@13
|
14 use Psy\Exception\ParseErrorException;
|
Chris@13
|
15 use Psy\ParserFactory;
|
Chris@13
|
16 use Psy\Shell;
|
Chris@13
|
17
|
Chris@13
|
18 /**
|
Chris@13
|
19 * A runkit-based code reloader, which is pretty much magic.
|
Chris@13
|
20 */
|
Chris@13
|
21 class RunkitReloader extends AbstractListener
|
Chris@13
|
22 {
|
Chris@13
|
23 private $parser;
|
Chris@13
|
24 private $timestamps = [];
|
Chris@13
|
25
|
Chris@13
|
26 /**
|
Chris@13
|
27 * Only enabled if Runkit is installed.
|
Chris@13
|
28 *
|
Chris@13
|
29 * @return bool
|
Chris@13
|
30 */
|
Chris@13
|
31 public static function isSupported()
|
Chris@13
|
32 {
|
Chris@17
|
33 return \extension_loaded('runkit');
|
Chris@13
|
34 }
|
Chris@13
|
35
|
Chris@13
|
36 /**
|
Chris@13
|
37 * Construct a Runkit Reloader.
|
Chris@13
|
38 *
|
Chris@13
|
39 * @todo Pass in Parser Factory instance for dependency injection?
|
Chris@13
|
40 */
|
Chris@13
|
41 public function __construct()
|
Chris@13
|
42 {
|
Chris@13
|
43 $parserFactory = new ParserFactory();
|
Chris@13
|
44 $this->parser = $parserFactory->createParser();
|
Chris@13
|
45 }
|
Chris@13
|
46
|
Chris@13
|
47 /**
|
Chris@13
|
48 * Reload code on input.
|
Chris@13
|
49 *
|
Chris@13
|
50 * @param Shell $shell
|
Chris@13
|
51 * @param string $input
|
Chris@13
|
52 */
|
Chris@13
|
53 public function onInput(Shell $shell, $input)
|
Chris@13
|
54 {
|
Chris@13
|
55 $this->reload($shell);
|
Chris@13
|
56 }
|
Chris@13
|
57
|
Chris@13
|
58 /**
|
Chris@13
|
59 * Look through included files and update anything with a new timestamp.
|
Chris@13
|
60 *
|
Chris@13
|
61 * @param Shell $shell
|
Chris@13
|
62 */
|
Chris@13
|
63 private function reload(Shell $shell)
|
Chris@13
|
64 {
|
Chris@17
|
65 \clearstatcache();
|
Chris@13
|
66 $modified = [];
|
Chris@13
|
67
|
Chris@17
|
68 foreach (\get_included_files() as $file) {
|
Chris@17
|
69 $timestamp = \filemtime($file);
|
Chris@13
|
70
|
Chris@13
|
71 if (!isset($this->timestamps[$file])) {
|
Chris@13
|
72 $this->timestamps[$file] = $timestamp;
|
Chris@13
|
73 continue;
|
Chris@13
|
74 }
|
Chris@13
|
75
|
Chris@13
|
76 if ($this->timestamps[$file] === $timestamp) {
|
Chris@13
|
77 continue;
|
Chris@13
|
78 }
|
Chris@13
|
79
|
Chris@13
|
80 if (!$this->lintFile($file)) {
|
Chris@17
|
81 $msg = \sprintf('Modified file "%s" could not be reloaded', $file);
|
Chris@13
|
82 $shell->writeException(new ParseErrorException($msg));
|
Chris@13
|
83 continue;
|
Chris@13
|
84 }
|
Chris@13
|
85
|
Chris@13
|
86 $modified[] = $file;
|
Chris@13
|
87 $this->timestamps[$file] = $timestamp;
|
Chris@13
|
88 }
|
Chris@13
|
89
|
Chris@13
|
90 // switch (count($modified)) {
|
Chris@13
|
91 // case 0:
|
Chris@13
|
92 // return;
|
Chris@13
|
93
|
Chris@13
|
94 // case 1:
|
Chris@13
|
95 // printf("Reloading modified file: \"%s\"\n", str_replace(getcwd(), '.', $file));
|
Chris@13
|
96 // break;
|
Chris@13
|
97
|
Chris@13
|
98 // default:
|
Chris@13
|
99 // printf("Reloading %d modified files\n", count($modified));
|
Chris@13
|
100 // break;
|
Chris@13
|
101 // }
|
Chris@13
|
102
|
Chris@13
|
103 foreach ($modified as $file) {
|
Chris@13
|
104 runkit_import($file, (
|
Chris@13
|
105 RUNKIT_IMPORT_FUNCTIONS |
|
Chris@13
|
106 RUNKIT_IMPORT_CLASSES |
|
Chris@13
|
107 RUNKIT_IMPORT_CLASS_METHODS |
|
Chris@13
|
108 RUNKIT_IMPORT_CLASS_CONSTS |
|
Chris@13
|
109 RUNKIT_IMPORT_CLASS_PROPS |
|
Chris@13
|
110 RUNKIT_IMPORT_OVERRIDE
|
Chris@13
|
111 ));
|
Chris@13
|
112 }
|
Chris@13
|
113 }
|
Chris@13
|
114
|
Chris@13
|
115 /**
|
Chris@13
|
116 * Should this file be re-imported?
|
Chris@13
|
117 *
|
Chris@13
|
118 * Use PHP-Parser to ensure that the file is valid PHP.
|
Chris@13
|
119 *
|
Chris@13
|
120 * @param string $file
|
Chris@13
|
121 *
|
Chris@13
|
122 * @return bool
|
Chris@13
|
123 */
|
Chris@13
|
124 private function lintFile($file)
|
Chris@13
|
125 {
|
Chris@13
|
126 // first try to parse it
|
Chris@13
|
127 try {
|
Chris@17
|
128 $this->parser->parse(\file_get_contents($file));
|
Chris@13
|
129 } catch (\Exception $e) {
|
Chris@13
|
130 return false;
|
Chris@13
|
131 }
|
Chris@13
|
132
|
Chris@13
|
133 return true;
|
Chris@13
|
134 }
|
Chris@13
|
135 }
|