Passed
Push — develop ( 558982...38f712 )
by Nikolay
06:07
created

WorkerApiCommands   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 21
eloc 67
c 5
b 0
f 0
dl 0
loc 142
rs 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A start() 0 14 3
A checkNeedReload() 0 11 5
F prepareAnswer() 0 62 12
A getNeedRestartActions() 0 10 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\PBXCoreREST\Workers;
21
22
use MikoPBX\Common\Handlers\CriticalErrorsHandler;
23
use MikoPBX\Common\Providers\BeanstalkConnectionWorkerApiProvider;
24
use MikoPBX\Core\System\{BeanstalkClient, Configs\BeanstalkConf, Processes, Util};
25
use MikoPBX\Core\Workers\WorkerBase;
26
use MikoPBX\PBXCoreREST\Lib\ModulesManagementProcessor;
27
use MikoPBX\PBXCoreREST\Lib\PBXApiResult;
28
use MikoPBX\PBXCoreREST\Lib\SystemManagementProcessor;
29
use Throwable;
30
use function xdebug_break;
31
32
require_once 'Globals.php';
33
34
35
/**
36
 * The WorkerApiCommands class is responsible for handling API command requests from the frontend.
37
 *
38
 * It handles API command requests, delegates the processing to the appropriate processor classes,
39
 * and checks for restart requirements based on the received requests.
40
 *
41
 *
42
 * @package MikoPBX\PBXCoreREST\Workers
43
 */
44
class WorkerApiCommands extends WorkerBase
45
{
46
    /**
47
     * The maximum parallel worker processes
48
     *
49
     * @var int
50
     */
51
    public int $maxProc = 4;
52
53
54
    /**
55
     * Starts the worker.
56
     *
57
     * @param array $argv The command-line arguments passed to the worker.
58
     *
59
     * @return void
60
     */
61
    public function start(array $argv): void
62
    {
63
        /** @var BeanstalkConnectionWorkerApiProvider $beanstalk */
64
        $beanstalk = $this->di->getShared(BeanstalkConnectionWorkerApiProvider::SERVICE_NAME);
65
        if ($beanstalk->isConnected() === false) {
66
            Util::sysLogMsg(self::class, 'Fail connect to beanstalkd...');
67
            sleep(2);
68
            return;
69
        }
70
        $beanstalk->subscribe($this->makePingTubeName(self::class), [$this, 'pingCallBack']);
71
        $beanstalk->subscribe(__CLASS__, [$this, 'prepareAnswer']);
72
73
        while ($this->needRestart === false) {
74
            $beanstalk->wait();
75
        }
76
    }
77
78
    /**
79
     * Process API request from frontend
80
     *
81
     * @param BeanstalkClient $message
82
     *
83
     */
84
    public function prepareAnswer(BeanstalkClient $message): void
85
    {
86
        // Use fork to run the callback in a separate process
87
        $pid = pcntl_fork();
88
        if ($pid == -1) {
89
            // Fork failed
90
            throw new \RuntimeException("Failed to fork a new process.");
91
        } elseif ($pid == 0) {
92
            $res = new PBXApiResult();
93
            $res->processor = __METHOD__;
94
            try {
95
                $request = json_decode($message->getBody(), true, 512, JSON_THROW_ON_ERROR);
96
                $processor = $request['processor'];
97
                $res->processor = $processor;
98
                // Start xdebug session, don't forget to install xdebug.remote_mode = jit on xdebug.ini
99
                // and set XDEBUG_SESSION Cookie header on REST request to debug it
100
                if (isset($request['debug']) && $request['debug'] === true && extension_loaded('xdebug')) {
101
                    xdebug_break();
102
                }
103
104
                // This is the child process
105
                if (method_exists($processor, 'callback')) {
106
                    $res = $processor::callback($request);
107
                } else {
108
                    $res->success = false;
109
                    $res->messages['error'][] = "Unknown processor - {$processor} in prepareAnswer";
110
                }
111
            } catch (Throwable $exception) {
112
                $request = [];
113
                $this->needRestart = true;
114
                // Prepare answer with pretty error description
115
                $res->messages['error'][] = CriticalErrorsHandler::handleExceptionWithSyslog($exception);
116
            } finally {
117
                $encodedResult = json_encode($res->getResult());
118
                if ($encodedResult === false) {
119
                    $res->data = [];
120
                    $res->messages['error'][] = 'It is impossible to encode to json current processor answer';
121
                    $encodedResult = json_encode($res->getResult());
122
                }
123
124
                // Check response size and write in on file if it bigger than Beanstalk can digest
125
                if (strlen($encodedResult)>BeanstalkConf::JOB_DATA_SIZE_LIMIT){
126
                    $dirsConfig  = $this->di->getShared('config');
127
                    $filenameTmp = $dirsConfig->path('www.downloadCacheDir') . '/temp-' . __FUNCTION__ . '_' . microtime() . '.data';
128
                    if (file_put_contents($filenameTmp, serialize($res->getResult()))){
129
                        $encodedResult = json_encode([BeanstalkClient::RESPONSE_IN_FILE=>$filenameTmp]);
130
                    } else {
131
                        $res->data = [];
132
                        $res->messages['error'][] = 'It is impossible to write answer into file '.$filenameTmp;
133
                        $encodedResult = json_encode($res->getResult());
134
                    }
135
                }
136
137
                $message->reply($encodedResult);
138
                if ($res->success) {
139
                    $this->checkNeedReload($request);
140
                }
141
            }
142
            exit(0); // Exit the child process
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
143
        } else {
144
            // This is the parent process
145
            pcntl_wait($status); // Wait for the child process to complete
146
        }
147
148
    }
149
150
    /**
151
     * Checks if the module or worker needs to be reloaded.
152
     *
153
     * @param array $request
154
     */
155
    private function checkNeedReload(array $request): void
156
    {
157
        // Check if new code added from modules
158
        $restartActions = $this->getNeedRestartActions();
159
        foreach ($restartActions as $processor => $actions) {
160
            foreach ($actions as $action) {
161
                if ($processor === $request['processor']
162
                    && $action === $request['action']) {
163
                    $this->needRestart = true;
164
                    Processes::restartAllWorkers();
165
                    return;
166
                }
167
            }
168
        }
169
    }
170
171
    /**
172
     * Prepares array of processor => action depends restart this kind worker
173
     *
174
     * @return array
175
     */
176
    private function getNeedRestartActions(): array
177
    {
178
        return [
179
            SystemManagementProcessor::class => [
180
                'restoreDefault',
181
            ],
182
            ModulesManagementProcessor::class => [
183
                'enableModule',
184
                'disableModule',
185
                'uninstallModule',
186
            ],
187
        ];
188
    }
189
}
190
191
// Start worker process
192
WorkerApiCommands::startWorker($argv ?? []);