Completed
Push — master ( 3aaf31...f42553 )
by Lawrence
01:34
created

Server::execute()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 2
eloc 10
nc 2
nop 2
dl 0
loc 18
ccs 0
cts 16
cp 0
crap 6
rs 9.4285
c 3
b 0
f 0
1
<?php
2
/*
3
 +------------------------------------------------------------------------+
4
 | Plinker-RPC PHP                                                        |
5
 +------------------------------------------------------------------------+
6
 | Copyright (c)2017-2018 (https://github.com/plinker-rpc/core)           |
7
 +------------------------------------------------------------------------+
8
 | This source file is subject to MIT License                             |
9
 | that is bundled with this package in the file LICENSE.                 |
10
 |                                                                        |
11
 | If you did not receive a copy of the license and are unable to         |
12
 | obtain it through the world-wide-web, please send an email             |
13
 | to [email protected] so we can send you a copy immediately.        |
14
 +------------------------------------------------------------------------+
15
 | Authors: Lawrence Cherone <[email protected]>                     |
16
 +------------------------------------------------------------------------+
17
 */
18
19
namespace Plinker\Core;
20
21
/**
22
 * Plinker\Core\Server
23
 */
24
final class Server
25
{
26
    /**
27
     * @var
28
     */
29
    protected $post;
30
31
    /**
32
     * @var
33
     */
34
    protected $config;
35
36
    /**
37
     * @var
38
     */
39
    protected $signer;
40
41
    /**
42
     * @const - error strings
43
     */
44
    const ERROR_IP    = "IP not in allowed list (%s)";
45
    const ERROR_TOKEN = "Plinker token mismatch";
46
    const ERROR_DECODE = "Failed to decode payload, check secret";
47
    const ERROR_USR_COMPONENT = "User component class (%s) not found";
48
    const ERROR_EXT_COMPONENT = "Component (%s) not implemented";
49
    const ERROR_ACTION = "Component action (%s) not implemented in: %s";
50
51
    /**
52
     * Class construct
53
     *
54
     * @param  array $config - config array which holds object configuration
55
     * @return void
56
     */
57
    public function __construct($config = [])
58
    {
59
        $this->config = array_merge([
60
            "debug"       => false,
61
            "secret"      => null,
62
            "allowed_ips" => []
63
        ], $config);
64
65
        // check and set client timeout
66
        if (!isset($this->config["timeout"]) || !is_numeric($this->config["timeout"])) {
67
            $this->config["timeout"] = 10;
68
        }
69
    }
70
71
    /**
72
     * Listen method
73
     *
74
     * <code>
75
     *  $server->listen();
76
     * </code>
77
     *
78
     * @return void
79
     */
80
    public function listen()
81
    {
82
        $this->post = file_get_contents("php://input");
83
        $this->post = gzinflate($this->post);
84
        $this->post = json_decode($this->post, true);
85
86
        header("Content-Type: text/plain; charset=utf-8");
87
88
        // check allowed ips
89
        if (!empty($this->config["allowed_ips"]) &&
90
           !in_array($_SERVER["REMOTE_ADDR"], $this->config["allowed_ips"])) {
91
            exit(serialize([
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...
92
                "error" => sprintf(Server::ERROR_IP, $_SERVER["REMOTE_ADDR"]),
93
                "code" => 403
94
            ]));
95
        }
96
97
        // check header token matches data token
98
        if ($_SERVER["HTTP_PLINKER"] != $this->post["token"]) {
99
            exit(serialize([
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...
100
                "error" => Server::ERROR_TOKEN,
101
                "code" => 422
102
            ]));
103
        }
104
105
        // load signer
106
        if (!$this->signer) {
107
            $this->signer = new Lib\Signer($this->config);
108
        }
109
110
        // decode post payload
111
        $this->post = $this->signer->decode($this->post);
112
113
        // could not decode payload
114
        if ($this->post === null) {
115
            exit(serialize([
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...
116
                "error" => Server::ERROR_DECODE,
117
                "code" => 422
118
            ]));
119
        }
120
121
        //
122
        $response = null;
123
        $ns = null;
124
        $action = $this->post["action"];
125
        $this->config = array_merge(
126
            $this->config,
127
            $this->post
128
        );
129
130
        // component is in classes config
131
        if (array_key_exists($this->post["component"], $this->config["classes"])) {
132
            //
133
            if (!empty($this->config["classes"][$this->post["component"]][0])) {
134
                $ns = $this->config["classes"][$this->post["component"]][0];
135
            }
136
137
            //
138
            if (!empty($this->config["classes"][$this->post["component"]][1])) {
139
                $this->config = array_merge(
140
                    $this->config,
141
                    $this->config["classes"][$this->post["component"]][1]
142
                );
143
            }
144
145
            //
146
            if (!empty($ns) && !file_exists($ns)) {
147
                exit(serialize([
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...
148
                    "error" => sprintf(Server::ERROR_USR_COMPONENT, $this->post["component"]),
149
                    "code"  => 422
150
                ]));
151
            }
152
153
            //
154
            require($ns);
155
156
            //
157
            if (!class_exists($this->post["component"])) {
158
                exit(serialize([
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...
159
                    "error" => sprintf(Server::ERROR_USR_COMPONENT, $this->post["component"]),
160
                    "code"  => 422
161
                ]));
162
            }
163
164
            //
165
            $response = $this->execute($this->post["component"], $action);
166
167
            exit(serialize($response));
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...
168
        }
169
170
        // component is plinker endpoint
171
        $ns = "\\Plinker\\Core\\Endpoint\\".ucfirst($this->post["component"]);
172
173
        if (class_exists($ns)) {
174
            //
175
            $response = $this->execute($ns, $action);
0 ignored issues
show
Bug introduced by
$ns of type string is incompatible with the type Plinker\Core\component expected by parameter $ns of Plinker\Core\Server::execute(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

175
            $response = $this->execute(/** @scrutinizer ignore-type */ $ns, $action);
Loading history...
176
        } else {
177
            if (empty($this->post["component"]) && $action === "info") {
178
                $response = $this->info();
179
            } else {
180
                $response = sprintf(Server::ERROR_EXT_COMPONENT, $this->post["component"]);
181
            }
182
        }
183
184
        exit(serialize($response));
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...
185
    }
186
187
    /**
188
     * Return info about available classes
189
     *
190
     * <code>
191
     *  $client->info();
192
     * </code>
193
     *
194
     * @return array
195
     */
196
    private function info()
197
    {
198
        $response = [
199
            "class" => []
200
        ];
201
        foreach ($this->config["classes"] as $key => $val) {
202
            //
203
            require($val[0]);
204
205
            $reflection = new \ReflectionClass($key);
206
207
            foreach ($reflection->getMethods() as $method) {
208
                if (!in_array($method->getName(), ["__construct"])) {
209
                    $param = [];
210
                    foreach ($method->getParameters() as $parameter) {
211
                        $param[] = $parameter->getName();
212
                    }
213
                    $response["class"][$key]["methods"][$method->getName()] = $param;
214
                }
215
            }
216
        }
217
218
        return $response;
219
    }
220
221
    /**
222
     * Execute component
0 ignored issues
show
Bug introduced by
The type Plinker\Core\component was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
223
     *
224
     * @param  ns      component class namespace
225
     * @param  action  component action
226
     * @return string
227
     */
228
    private function execute($ns, $action)
229
    {
230
        $response  = null;
231
        $component = new $ns($this->config);
232
233
        if (method_exists($component, $action)) {
234
            $response = call_user_func_array(
235
                [
236
                    $component,
237
                    $action
238
                ],
239
                $this->post["params"]
240
            );
241
        } else {
242
            $response = sprintf(Server::ERROR_ACTION, $action, $ns);
243
        }
244
245
        return $response;
246
    }
247
}
248