Completed
Push — master ( 546525...9be8ea )
by Taha
01:26
created

XdebugToggle::initialize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 12
rs 9.8666
c 0
b 0
f 0
cc 2
nc 2
nop 0
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 boolean
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 boolean
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 initialize()
75
    {
76
        // Define custom format for bold text
77
        $style = new OutputFormatterStyle('default', 'default', array('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
        if ($verbosityLevel > OutputInterface::VERBOSITY_DEBUG) {
83
            $this->debug = true;
84
        }
85
    }
86
87
    /**
88
     * The method that handles the command
89
     */
90
    public function handle()
91
    {
92
        // Get XDebug desired status from the command line arguments
93
        $desiredStatus = strval($this->argument("status"));
94
95
        // do the validation
96
        if ($this->validateDesiredStatus($desiredStatus) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
97
            return false;
98
        }
99
100
        // Retrieve the INI path to the global variable
101
        if ($this->getIniPath() == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
102
            return false;
103
        }
104
105
        // Get the XDebug extension information from the INI file
106
        $this->getXDebugStatus();
107
108
        // do the validation
109
        if ($this->validateXDebugStatus($desiredStatus) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
110
            return false;
111
        }
112
113
        // we need to alter the status to the new one. Do it!
114
        $this->setXDebugStatus($desiredStatus);
115
    }
116
117
    /**
118
     * Validates the desired status argument received from console
119
     *
120
     * @param   string  $desiredStatus  Should be "on" or "off"
121
     *
122
     * @return  boolean                 Whether it is a valid input
123
     */
124
    public function validateDesiredStatus(string $desiredStatus)
125
    {
126
        if ($this->debug) {
127
            echo "Desired Status: " . ($desiredStatus) . "\n";
128
        }
129
130
        // validate desired XDebug status
131
        if (!in_array($desiredStatus, ["on", "off"])) {
132
            $this->line("Status should be \"on\" or \"off\". Other values are not accepted.", "fg=red;bold");
133
            return false;
134
        }
135
136
        return true;
137
    }
138
139
    /**
140
     * Gets the XDebug status and related configuration from the loaded php.ini file
141
     */
142
    private function getXDebugStatus()
143
    {
144
        // get the extension status
145
        $this->getExtensionStatus();
146
147
        // get extemsion settings
148
        $this->getExtensionSettings();
149
    }
150
151
    /**
152
     * Retrieves the INI path from php.ini file
153
     *
154
     * @return void
155
     */
156
    private function getIniPath()
157
    {
158
        $this->iniPath = php_ini_loaded_file() ?? "";
159
160
        // If we can't retrieve the loaded INI path, bail out
161
        if ($this->iniPath === "") {
162
            $this->line("Can't get php.ini file path from phpinfo() output.
163
            Make sure that the function is allowed inside your php.ini configuration.", "bold");
164
            return false;
165
        }
166
167
        return true;
168
    }
169
170
    /**
171
     * Validates the XDebug status received
172
     *
173
     * @param   string  $desiredStatus  The desired status
174
     *
175
     * @return  boolean                 Whether we should continue to modify the status or not
176
     */
177
    public function validateXDebugStatus(string $desiredStatus)
178
    {
179
        // prepare variables for comparison and output
180
        $currentStatus = $this->extensionStatus ? "on" : "off";
181
        $styledStatus = $this->extensionStatus ? "<fg=green;bold>on" : "<fg=red;bold>off";
182
183
        // print current status to the user
184
        $this->line("<fg=yellow>Current XDebug Status: $styledStatus</>");
185
186
        // if the desired status and current status are the same, we don't need to alter anything
187
        // inform the user and exit
188
        if ($currentStatus === $desiredStatus) {
189
            $this->line("<fg=green>Already at the desired state. No action has been taken.</>");
190
            return false;
191
        }
192
193
        return true;
194
    }
195
196
    /**
197
     * Sets the new XDebug extension status
198
     *
199
     * @param   string  $status  Whether the extension should be active or not
200
     *
201
     * @return  void
202
     */
203
    private function setXDebugStatus($status)
204
    {
205
        // inform the user about the current operation
206
        $this->line("<bold>Setting status to \"$status\"</bold>");
207
208
        // read the ini file
209
        $contents = file_get_contents($this->iniPath);
210
211
        if ($this->debug) {
212
            echo "status: $status\n";
213
            echo "line: " . $this->extensionLine . "\n";
214
            echo "new: " . trim($this->extensionLine, ";") . "\n";
215
        }
216
217
        // replace the "zend_extension=*xdebug.*" line with the active/passive equivalent
218
        switch ($status) {
219
            case 'on':
220
                $contents = str_replace($this->extensionLine, trim($this->extensionLine, ";"), $contents);
221
                break;
222
223
            case 'off':
224
                $contents = str_replace($this->extensionLine, ";" . $this->extensionLine, $contents);
225
                break;
226
        }
227
228
        // rewrite the php.ini file
229
        file_put_contents($this->iniPath, $contents);
230
231
        // restart the service to put the changes in effect
232
        $this->restartServices();
233
    }
234
235
    /**
236
     * Reads the extension status from PHP ini file
237
     *
238
     * @return void
239
     */
240
    private function getExtensionStatus()
241
    {
242
        // read the extension line from file,
243
        // can't use parse_ini_file here because the keyed array overwrites "extension" lines and keeps the last one
244
        $this->extensionLine = collect(file_get_contents($this->iniPath))
245
            ->explode("\n")
246
            ->filter(function ($line) {
247
                return Str::contains($line, "extension=") && Str::contains($line, "xdebug");
248
            })
249
            ->first();
250
251
        $this->extensionLine = trim($this->extensionLine ?? "");
252
253
        if ($this->debug) {
254
            echo "line: " . $this->extensionLine . "\n";
255
        }
256
257
        if (strlen($this->extensionLine) > 0) {
258
            $this->extensionStatus = $this->extensionLine[0] == ";" ? false : true;
259
        } else {
260
            $this->extensionStatus = false;
261
        }
262
263
        if ($this->debug) {
264
            echo "ext.status: " . $this->extensionStatus . "\n";
265
        }
266
    }
267
268
    /**
269
     * Reads the extension settings from PHP ini file
270
     *
271
     * @return void
272
     */
273
    private function getExtensionSettings()
274
    {
275
        $settings = collect(parse_ini_file($this->iniPath))->filter(function ($setting, $key) {
276
            return Str::startsWith($key, "xdebug.");
277
        });
278
        $this->extensionSettings = $settings->toArray();
279
    }
280
281
    /**
282
     * Restarts the services that takes the modification into effect
283
     *
284
     * @return void
285
     */
286
    private function restartServices()
287
    {
288
        /**
289
         * Define a global outputter to display command output to user
290
         *
291
         * @param   string  $type  The type of the output
292
         * @param   string  $data  The output
293
         *
294
         * @return  void
295
         */
296
        $output = function ($type, $data) {
297
            $this->info($data);
298
        };
299
        // run the command(s) needed to restart the service
300
        (new Process([env("XDEBUG_SERVICE_RESTART_COMMAND", "valet restart nginx")]))->run($output);
301
        // display the new extension status
302
        (new Process(["php --ri xdebug"]))->run($output);
303
    }
304
}
305