XdebugToggle   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Importance

Changes 9
Bugs 5 Features 0
Metric Value
eloc 83
c 9
b 5
f 0
dl 0
loc 301
rs 10
wmc 28

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getExtensionSettings() 0 6 1
A handle() 0 27 4
A getIniPath() 0 13 2
A initializeCommand() 0 11 2
A setXDebugStatus() 0 30 4
A validateDesiredStatus() 0 14 3
A getXDebugStatus() 0 7 1
A validateXDebugStatus() 0 18 4
A getExtensionStatus() 0 21 4
A restartServices() 0 24 2
1
<?php
2
3
namespace Tpaksu\XdebugToggle\Commands;
4
5
use Illuminate\Console\Command;
6
use Illuminate\Support\Str;
7
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use Symfony\Component\Process\Process;
10
11
class XdebugToggle extends Command
12
{
13
    /**
14
     * The complete line containing "*_extension=*xdebug*".
15
     *
16
     * @var string
17
     */
18
    protected $extensionLine;
19
20
    /**
21
     * Extension active status.
22
     *
23
     * @var bool
24
     */
25
    protected $extensionStatus;
26
27
    /**
28
     * The configuration written in php.ini for XDebug.
29
     *
30
     * @var array
31
     */
32
    protected $extensionSettings;
33
34
    /**
35
     * Path of the Loaded INI file.
36
     *
37
     * @var string
38
     */
39
    protected $iniPath;
40
41
    /**
42
     * Debug mode active flag.
43
     *
44
     * @var bool
45
     */
46
    protected $debug;
47
48
    /**
49
     * The command signature.
50
     *
51
     * @var string
52
     */
53
    protected $signature = 'xdebug {status : "on" or "off" to enable/disable XDebug}';
54
55
    /**
56
     * The command description.
57
     *
58
     * @var string
59
     */
60
    protected $description = 'Enables or disables XDebug extension';
61
62
    /**
63
     * Class constructor.
64
     */
65
    public function __construct()
66
    {
67
        parent::__construct();
68
        $this->debug = false;
69
    }
70
71
    /**
72
     * initialization routines.
73
     */
74
    public function initializeCommand()
75
    {
76
        // Define custom format for bold text
77
        $style = new OutputFormatterStyle('default', 'default', ['bold']);
78
        $this->output->getFormatter()->setStyle('bold', $style);
79
80
        // Get the verbosity level to set debug mode flag
81
        $verbosityLevel = $this->getOutput()->getVerbosity();
82
83
        if ($verbosityLevel > OutputInterface::VERBOSITY_NORMAL) {
84
            $this->debug = true;
85
        }
86
    }
87
88
    /**
89
     * The method that handles the command.
90
     */
91
    public function handle()
92
    {
93
        $this->initializeCommand();
94
95
        // Get XDebug desired status from the command line arguments
96
        $desiredStatus = strval($this->argument('status'));
97
98
        // do the validation
99
        if ($this->validateDesiredStatus($desiredStatus) === false) {
100
            return false;
101
        }
102
103
        // Retrieve the INI path to the global variable
104
        if ($this->getIniPath() === false) {
105
            return false;
106
        }
107
108
        // Get the XDebug extension information from the INI file
109
        $this->getXDebugStatus();
110
111
        // do the validation
112
        if ($this->validateXDebugStatus($desiredStatus) === false) {
113
            return false;
114
        }
115
116
        // we need to alter the status to the new one. Do it!
117
        $this->setXDebugStatus($desiredStatus);
118
    }
119
120
    /**
121
     * Validates the desired status argument received from console.
122
     *
123
     * @param   string  $desiredStatus  Should be "on" or "off"
124
     *
125
     * @return  bool                 Whether it is a valid input
126
     */
127
    public function validateDesiredStatus(string $desiredStatus)
128
    {
129
        if ($this->debug) {
130
            echo 'Desired Status: ' . ($desiredStatus) . "\n";
131
        }
132
133
        // validate desired XDebug status
134
        if (!in_array($desiredStatus, ['on', 'off'])) {
135
            $this->line('Status should be "on" or "off". Other values are not accepted.', 'fg=red;bold');
136
137
            return false;
138
        }
139
140
        return true;
141
    }
142
143
    /**
144
     * Gets the XDebug status and related configuration from the loaded php.ini file.
145
     */
146
    private function getXDebugStatus()
147
    {
148
        // get the extension status
149
        $this->getExtensionStatus();
150
151
        // get extemsion settings
152
        $this->getExtensionSettings();
153
    }
154
155
    /**
156
     * Retrieves the INI path from php.ini file.
157
     *
158
     * @return bool
159
     */
160
    private function getIniPath()
161
    {
162
        $this->iniPath = php_ini_loaded_file() ?? '';
163
164
        // If we can't retrieve the loaded INI path, bail out
165
        if ($this->iniPath === '') {
166
            $this->line("Can't get php.ini file path from phpinfo() output.
167
            Make sure that the function is allowed inside your php.ini configuration.", 'bold');
168
169
            return false;
170
        }
171
172
        return true;
173
    }
174
175
    /**
176
     * Validates the XDebug status received.
177
     *
178
     * @param   string  $desiredStatus  The desired status
179
     *
180
     * @return  bool                 Whether we should continue to modify the status or not
181
     */
182
    public function validateXDebugStatus(string $desiredStatus)
183
    {
184
        // prepare variables for comparison and output
185
        $currentStatus = $this->extensionStatus ? 'on' : 'off';
186
        $styledStatus = $this->extensionStatus ? '<fg=green;bold>on' : '<fg=red;bold>off';
187
188
        // print current status to the user
189
        $this->line("<fg=yellow>Current XDebug Status: $styledStatus</>");
190
191
        // if the desired status and current status are the same, we don't need to alter anything
192
        // inform the user and exit
193
        if ($currentStatus === $desiredStatus) {
194
            $this->line('<fg=green>Already at the desired state. No action has been taken.</>');
195
196
            return false;
197
        }
198
199
        return true;
200
    }
201
202
    /**
203
     * Sets the new XDebug extension status.
204
     *
205
     * @param   string  $status  Whether the extension should be active or not
206
     *
207
     * @return  void
208
     */
209
    private function setXDebugStatus($status)
210
    {
211
        // inform the user about the current operation
212
        $this->line("<bold>Setting status to \"$status\"</bold>");
213
214
        // read the ini file
215
        $contents = file_get_contents($this->iniPath);
216
217
        if ($this->debug) {
218
            echo "status: $status\n";
219
            echo 'line: ' . $this->extensionLine . "\n";
220
            echo 'new: ' . trim($this->extensionLine, ';') . "\n";
221
        }
222
223
        // replace the "zend_extension=*xdebug.*" line with the active/passive equivalent
224
        switch ($status) {
225
            case 'on':
226
                $contents = str_replace($this->extensionLine, trim($this->extensionLine, ';'), $contents);
227
                break;
228
229
            case 'off':
230
                $contents = str_replace($this->extensionLine, ';' . $this->extensionLine, $contents);
231
                break;
232
        }
233
234
        // rewrite the php.ini file
235
        file_put_contents($this->iniPath, $contents);
236
237
        // restart the service to put the changes in effect
238
        $this->restartServices();
239
    }
240
241
    /**
242
     * Reads the extension status from PHP ini file.
243
     *
244
     * @return void
245
     */
246
    private function getExtensionStatus()
247
    {
248
        // read the extension line from file,
249
        // can't use parse_ini_file here because the keyed array overwrites "extension" lines and keeps the last one
250
        $this->extensionLine = collect(explode("\n", file_get_contents($this->iniPath)))
251
            ->filter(function ($line) {
252
                return Str::contains($line, 'extension=') && Str::contains($line, 'xdebug');
253
            })
254
            ->first();
255
256
        $this->extensionLine = trim($this->extensionLine ?? '');
257
258
        if (strlen($this->extensionLine) > 0) {
259
            $this->extensionStatus = $this->extensionLine[0] !== ';';
260
        } else {
261
            $this->extensionStatus = false;
262
        }
263
264
        if ($this->debug) {
265
            echo 'line: ' . $this->extensionLine . "\n";
266
            echo 'ext.status: ' . $this->extensionStatus . "\n";
267
        }
268
    }
269
270
    /**
271
     * Reads the extension settings from PHP ini file.
272
     *
273
     * @return void
274
     */
275
    private function getExtensionSettings()
276
    {
277
        $settings = collect(parse_ini_file($this->iniPath))->filter(function ($setting, $key) {
278
            return Str::startsWith($key, 'xdebug.');
279
        });
280
        $this->extensionSettings = $settings->toArray();
281
    }
282
283
    /**
284
     * Restarts the services that takes the modification into effect.
285
     *
286
     * @return void
287
     */
288
    private function restartServices()
289
    {
290
        /**
291
         * Define a global outputter to display command output to user.
292
         *
293
         * @param   string  $type  The type of the output
294
         * @param   string  $data  The output
295
         *
296
         * @return  void
297
         */
298
        $output = function ($type, $data) {
299
            if ($type == "err") {
300
                $this->error($data);
301
302
                return;
303
            }
304
            $this->info($data);
305
        };
306
307
        // run the command(s) needed to restart the service
308
        Process::fromShellCommandline(config('xdebugtoggle.service_restart_command'))->run($output);
309
310
        // display the new extension status
311
        Process::fromShellCommandline('php --ri xdebug')->run($output);
312
    }
313
}
314