TerminalController::renderException()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: sheldon
5
 * Date: 18-4-19
6
 * Time: 下午2:18.
7
 */
8
9
namespace Yeelight\Http\Controllers\Backend\Tools\Terminal;
10
11
use Exception;
12
use Illuminate\Contracts\Support\Arrayable;
13
use Illuminate\Support\Facades\Artisan;
14
use Illuminate\Support\Facades\DB;
15
use Illuminate\Support\Facades\Redis;
16
use Illuminate\Support\Facades\Request;
17
use Illuminate\Support\Str;
18
use MongoDB\Driver\Command;
19
use MongoDB\Driver\Manager;
20
use Symfony\Component\Console\Helper\Table;
21
use Symfony\Component\Console\Input\ArgvInput;
22
use Yeelight\Http\Controllers\BaseController;
23
use Yeelight\Models\Tools\Terminal\StringOutput;
24
25
/**
26
 * Class TerminalController
27
 *
28
 * @category Yeelight
29
 *
30
 * @package Yeelight\Http\Controllers\Backend\Tools\Terminal
31
 *
32
 * @author Sheldon Lee <[email protected]>
33
 *
34
 * @license https://opensource.org/licenses/MIT MIT
35
 *
36
 * @link https://www.yeelight.com
37
 */
38
class TerminalController extends BaseController
39
{
40
    /**
41
     * Artisan
42
     *
43
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
44
     */
45
    public function artisan()
46
    {
47
        $header_description = 'Artisan 终端';
48
49
        return view('backend.tools.terminal.artisan', [
50
            'header_description' => $header_description,
51
            'commands'           => $this->organizedCommands(),
52
        ]);
53
    }
54
55
    /**
56
     * RunArtisan
57
     *
58
     * @return string
59
     */
60
    public function runArtisan()
61
    {
62
        $command = Request::get('c', 'list');
63
        // If Exception raised.
64
        if (1 === Artisan::handle(
65
                new ArgvInput(explode(' ', 'artisan '.trim($command))),
66
                $output = new StringOutput()
67
            )) {
68
            return $this->renderException(new Exception($output->getContent()));
69
        }
70
71
        return sprintf('<pre>%s</pre>', $output->getContent());
72
    }
73
74
    /**
75
     * 数据库终端
76
     *
77
     * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
78
     */
79
    public function database()
80
    {
81
        $header_description = '数据库终端';
82
83
        return view('backend.tools.terminal.database', [
84
            'header_description' => $header_description,
85
            'connections'        => $this->connections(),
86
        ]);
87
    }
88
89
    public function runDatabase()
90
    {
91
        $query = Request::get('q');
92
        $connection = Request::get('c', config('database.default'));
93
94
        return $this->dispatchQuery($connection, $query);
95
    }
96
97
    protected function getDumpedHtml($var)
98
    {
99
        ob_start();
100
        dump($var);
101
        $content = ob_get_contents();
102
        ob_get_clean();
103
104
        return substr($content, strpos($content, '<pre '));
105
    }
106
107
    protected function connections()
108
    {
109
        $dbs = $redis = [];
110
        foreach (config('database.connections') as $name => $_) {
111
            $dbs[] = [
112
                'option'   => $name,
113
                'value'    => "db:$name",
114
                'selected' => $name == config('database.default'),
115
            ];
116
        }
117
        $connections = array_filter(config('database.redis'), function ($config) {
118
            return is_array($config);
119
        });
120
        foreach ($connections as $name => $_) {
121
            $redis[] = [
122
                'value'     => "redis:$name",
123
                'option'    => $name,
124
            ];
125
        }
126
127
        return compact('dbs', 'redis');
128
    }
129
130
    protected function table(array $headers, $rows, $style = 'default')
131
    {
132
        $output = new StringOutput();
133
        $table = new Table($output);
134
        if ($rows instanceof Arrayable) {
135
            $rows = $rows->toArray();
136
        }
137
        $table->setHeaders($headers)->setRows($rows)->setStyle($style)->render();
138
139
        return $output->getContent();
140
    }
141
142
    protected function dispatchQuery($connection, $query)
143
    {
144
        list($type, $connection) = explode(':', $connection);
145
        if ($type == 'redis') {
146
            return $this->execRedisCommand($connection, $query);
147
        }
148
        $config = config('database.connections.'.$connection);
149
        if ($config['driver'] == 'mongodb') {
150
            return $this->execMongodbQuery($config, $query);
151
        }
152
        /* @var \Illuminate\Database\Connection $connection */
153
        $connection = DB::connection($connection);
154
        $connection->enableQueryLog();
155
156
        try {
157
            $result = $connection->select(str_replace([';', "\G"], '', $query));
158
        } catch (Exception $exception) {
159
            return $this->renderException($exception);
160
        }
161
        $log = current($connection->getQueryLog());
162
        if (empty($result)) {
163
            return sprintf("<pre>Empty set (%s sec)</pre>\r\n", number_format($log['time'] / 1000, 2));
164
        }
165
        $result = json_decode(json_encode($result), true);
166
        if (Str::contains($query, "\G")) {
167
            return $this->getDumpedHtml($result);
168
        }
169
170
        return sprintf(
171
            "<pre>%s \n%d %s in set (%s sec)</pre>\r\n",
172
            $this->table(array_keys(current($result)), $result),
173
            count($result),
174
            count($result) == 1 ? 'row' : 'rows',
175
            number_format($log['time'] / 1000, 2)
176
        );
177
    }
178
179
    protected function execMongodbQuery($config, $query)
180
    {
181
        if (Str::contains($query, '.find(') && !Str::contains($query, '.toArray(')) {
182
            $query .= '.toArray()';
183
        }
184
        $manager = new Manager("mongodb://{$config['host']}:{$config['port']}");
185
        $command = new Command(['eval' => $query]);
186
187
        try {
188
            $cursor = $manager->executeCommand($config['database'], $command);
189
        } catch (Exception $exception) {
190
            return $this->renderException($exception);
191
        }
192
        $result = $cursor->toArray()[0];
193
        $result = json_decode(json_encode($result), true);
194
        if (isset($result['errmsg'])) {
195
            return $this->renderException(new Exception($result['errmsg']));
196
        }
197
198
        return $this->getDumpedHtml($result['retval']);
199
    }
200
201
    protected function execRedisCommand($connection, $command)
202
    {
203
        $command = explode(' ', $command);
204
205
        try {
206
            $result = Redis::connection($connection)->executeRaw($command);
207
        } catch (Exception $exception) {
208
            return $this->renderException($exception);
209
        }
210
        if (is_string($result) && Str::startsWith($result, ['ERR ', 'WRONGTYPE '])) {
211
            return $this->renderException(new Exception($result));
212
        }
213
214
        return $this->getDumpedHtml($result);
215
    }
216
217
    protected function organizedCommands()
218
    {
219
        $commands = array_keys(Artisan::all());
220
        $groups = $others = [];
221
        foreach ($commands as $command) {
222
            $parts = explode(':', $command);
223
            if (isset($parts[1])) {
224
                $groups[$parts[0]][] = $command;
225
            } else {
226
                $others[] = $command;
227
            }
228
        }
229
        foreach ($groups as $key => $group) {
230
            if (count($group) === 1) {
231
                $others[] = $group[0];
232
                unset($groups[$key]);
233
            }
234
        }
235
        ksort($groups);
236
        sort($others);
237
238
        return compact('groups', 'others');
239
    }
240
241
    protected function renderException(Exception $exception)
242
    {
243
        return sprintf(
244
            "<div class='callout callout-warning'><i class='icon fa fa-warning'></i>&nbsp;&nbsp;&nbsp;%s</div>",
245
            str_replace("\n", '<br />', $exception->getMessage())
246
        );
247
    }
248
}
249