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