Completed
Push — master ( 390331...f1e09e )
by Lawrence
03:24
created

Server::execute()   C

Complexity

Conditions 11
Paths 23

Size

Total Lines 77
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 77
rs 5.3974
cc 11
eloc 37
nc 23
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace Plinker\Core;
4
5
/**
6
 * Server endpoint class.
7
 */
8
class Server
9
{
10
    private $post = [];
11
    private $config = [];
12
    private $publicKey = '';
13
    private $privateKey = '';
14
15
    /**
16
     * @param string $post
0 ignored issues
show
Documentation introduced by
Should the type for parameter $post not be string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
17
     * @param string $publicKey
18
     * @param string $privateKey
19
     * @param array  $config
20
     */
21
    public function __construct(
22
        $post = [],
23
        $publicKey = '',
24
        $privateKey = '',
25
        $config = []
26
    ) {
27
        // define vars
28
        $this->post = $post;
0 ignored issues
show
Documentation Bug introduced by
It seems like $post can also be of type string. However, the property $post is declared as type array. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
29
        $this->config = $config;
30
        $this->publicKey = hash('sha256', gmdate('h').$publicKey);
31
        $this->privateKey = hash('sha256', gmdate('h').$privateKey);
32
33
        // init signer
34
        $this->signer = new Signer(
0 ignored issues
show
Bug introduced by
The property signer does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
35
            $this->publicKey,
36
            $this->privateKey,
37
            (!empty($this->post['encrypt']) ? true : false)
38
        );
39
    }
40
    
41
    /**
42
     * Check client IP is in allowed list if allowed list is set.
43
     * 
44
     * @return bool
45
     */
46
    final private function checkAllowedIp()
0 ignored issues
show
Coding Style introduced by
function checkAllowedIp() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Coding Style introduced by
checkAllowedIp uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
47
    {
48
        // check allowed ips
49
        if (
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !(!empty($this->c...onfig['allowed_ips']));.
Loading history...
50
            !empty($this->config['allowed_ips']) &&
51
            !in_array($_SERVER['REMOTE_ADDR'], $this->config['allowed_ips'])
52
        ) {
53
            return false;
54
        }
55
        
56
        return true;
57
    }
58
    
59
    /**
60
     * Check client IP is in allowed list if allowed list is set.
61
     * 
62
     * @return bool
63
     */
64
    final private function verifyRequestToken()
0 ignored issues
show
Coding Style introduced by
function verifyRequestToken() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
Coding Style introduced by
verifyRequestToken uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
65
    {
66
        if (
0 ignored issues
show
Unused Code introduced by
This if statement and the following return statement are superfluous as you return always false.
Loading history...
67
            empty($this->post['token']) ||
68
            empty($_SERVER['HTTP_TOKEN']) ||
69
            hash_hmac('sha256', $this->post['token'], $this->privateKey) != $_SERVER['HTTP_TOKEN']
70
        ) {
71
            return false;
72
        }
73
        
74
        return false;
75
    }
76
77
    /**
78
     * Server exection method.
79
     * 
80
     * @return string
81
     */
82
    public function execute()
0 ignored issues
show
Coding Style introduced by
execute uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
83
    {
84
        // set response header
85
        header('Content-Type: text/plain; charset=utf-8');
86
87
        // check 
88
        if (!$this->checkAllowedIp()) {
89
            return serialize($this->signer->encode([
90
                'response' => [
91
                    'error' => 'IP not in allowed list: '.$_SERVER['REMOTE_ADDR'],
92
                ],
93
            ]));
94
        }
95
96
        // verify request token
97
        if (!$this->verifyRequestToken()) {
98
            return serialize($this->signer->encode([
99
                'response' => [
100
                    'error' => 'invalid request token',
101
                ],
102
            ]));
103
        }
104
105
        // decode post payload
106
        $data = $this->signer->decode(
107
            $this->post
108
        );
109
110
        // check client post is an array
111
        if (!is_array($data)) {
112
            return serialize($data);
113
        }
114
115
        // check data params array or set
116
        if (!isset($data['params'])) {
117
            $data['params'] = [];
118
        }
119
120
        // check data config array, set into scope
121
        if (!empty($data['config'])) {
122
            $this->config = $data['config'];
123
        }
124
125
        // check for empty
126
        if (empty($data['component']) || empty($data['action'])) {
127
            $error = empty($data['component']) ? 'component class' : 'action';
128
            return serialize($this->signer->encode([
129
                'response' => [
130
                    'error' => $error.' cannot be empty',
131
                ],
132
            ]));
133
        }
134
135
        $class = '\\Plinker\\'.$data['component'];
136
137
        if (class_exists($class)) {
138
            $componentClass = new $class($this->config + $data + $this->post);
139
140
            if (method_exists($componentClass, $data['action'])) {
141
                $return = call_user_func(
142
                    [
143
                        $componentClass,
144
                        $data['action'],
145
                    ],
146
                    $data['params']
147
                );
148
            } else {
149
                $return = 'action not implemented';
150
            }
151
        } else {
152
            $return = 'not implemented';
153
        }
154
155
        return serialize($this->signer->encode([
156
            'response' => $return,
157
        ]));
158
    }
159
}
160