Completed
Push — master ( f9d643...c4427d )
by Jakob
03:18
created

Server::optionsResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 14
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
1
<?php declare(strict_types = 1);
2
3
namespace JSKOS;
4
5
use Psr\Http\Message\RequestInterface;
6
use Psr\Http\Message\ResponseInterface;
7
use Http\Message\ResponseFactory;
8
use Http\Discovery\MessageFactoryDiscovery;
9
use Psr\Log\LoggerInterface;
10
use Psr\Log\NullLogger;
11
12
/**
13
 * A JSKOS Server.
14
 */
15
class Server implements \Psr\Log\LoggerAwareInterface
16
{
17
    protected $service;
18
    protected $responseFactory;
19
    protected $logger;
20
21
    public function __construct(
22
        Service $service, 
23
        ResponseFactory $responseFactory=null,
24
        LoggerInterface $logger=null 
25
    )
26
    {
27
        $this->service = $service;
28
        $this->responseFactory = $responseFactory ?: MessageFactoryDiscovery::find();
29
        $this->logger = $logger ?: new NullLogger();
30
    }
31
32
    public function setLogger(LoggerInterface $logger)
33
    {
34
        $this->logger = $logger;
35
    }
36
37
    protected function parseRequest(RequestInterface $request): array
38
    {
39
        $uri = $request->getUri();
40
        $path = $uri->getPath();
41
        $query = [];
42
        parse_str($uri->getQuery(), $query);
43
44
        $callback = null;
45
        if (isset($query['callback'])) {
46
            if (preg_match('/^[$A-Z_][0-9A-Z_$.]*$/i', $query['callback'])) {
47
                $callback = $query['callback'];
48
            }
49
            unset($query['callback']);
50
        }
51
52
        # TODO: get language parameter from headers
53
54
        return [$query, $path, $callback];
55
    }
56
57
    public function query(RequestInterface $request): ResponseInterface
58
    {    
59
        $method = $request->getMethod();
60
61
        $result = null;
62
        $callback = null;
63
64
        if ($method == 'GET' || $method == 'HEAD') {
65
            list ($query, $path, $callback) = $this->parseRequest($request);
66
67
            # TODO: detect conflicting parameters?
68
            # if (isset($params['uri']) and isset($params['search'])) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
80% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
69
            #   $error = new Error(422, 'request_error', 'Conflicting request parameters uri & search');
0 ignored issues
show
Unused Code Comprehensibility introduced by
56% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
70
            # }
71
72
            try {
73
                $result = $this->service->query($query, $path);
74
            } catch(Error $error) {
75
                $result = $error;
76
            }
77
            # TODO: catch other kinds of errors:
78
            # } catch (\Exception $e) {
0 ignored issues
show
Unused Code Comprehensibility introduced by
50% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
79
            # $this->logger->error('Service Exception', ['exception' => $e]);
0 ignored issues
show
Unused Code Comprehensibility introduced by
69% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
80
            # $error = new Error(500, 'Internal server error');
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
81
        } elseif ($request->getMethod() == 'OPTIONS') {            
82
            return $this->optionsResponse();
83
        } else {
84
            $result = new Error(405, 'Method not allowed');
85
        }
86
87
88
        if ($result instanceof Result) {
89
            $code = 200;
90
            $headers = [
91
                'Access-Control-Allow-Origin' => '*',
92
                'Content-Type' => 'application/json; charset=UTF-8',
93
                'X-Total-Count' => $result->getTotalCount()
0 ignored issues
show
Bug introduced by
The method getTotalCount does only exist in JSKOS\Result, but not in JSKOS\Error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
94
            ];
95
        } else {
96
            $code = $result->code;
97
            $headers = [
98
                'Access-Control-Allow-Origin' => '*',
99
                'Content-Type' => 'application/json; charset=UTF-8',
100
            ];
101
        }
102
103
        $body = $result->json();
0 ignored issues
show
Bug introduced by
The method json does only exist in JSKOS\Result, but not in JSKOS\Error.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
104
        $headers['Content-Length'] = strlen($body);
105
        if ($method == 'HEAD') {
106
            $body = '';
107
        }
108
109
        if ($callback) {
110
            $body = "/**/$callback($body);";
111
            $headers['Content-Type'] = 'application/javascript; charset=UTF-8';
112
        }
113
114
        return $this->responseFactory->createResponse($code, null, $headers, $body);
115
    }
116
117
118
    public function optionsResponse()
119
    {
120
        $headers = [
121
            'Access-Control-Allow-Methods' => 'GET, HEAD, OPTIONS',
122
        ];
123
124
        # TODO:
125
        # if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']) &&
0 ignored issues
show
Unused Code Comprehensibility introduced by
77% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
126
        #    $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] == 'GET') {
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
127
        #    $response->headers['Access-Control-Allow-Origin'] = '*';
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
128
        #    $response->headers['Acess-Control-Expose-Headers'] = 'Link, X-Total-Count';
0 ignored issues
show
Unused Code Comprehensibility introduced by
59% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
129
130
        return $this->responseFactory->createResponse(200, null, $headers, '');
131
    }
132
133
    /**
134
     * TODO: Extract requested languages(s) from request.
135
    public function extractRequestLanguage($params)
136
    {
137
        $language = null;
138
139
        # get query modifier: language
140
        if (isset($params['language'])) {
141
            $language = $params['language'];
142
            unset($params['language']);
143
            # TODO: parse language
144
        } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
145
            # parse accept-language-header
146
            preg_match_all(
147
                '/([a-z]+(?:-[a-z]+)?)\s*(?:;\s*q\s*=\s*(1|0?\.[0-9]+))?/i',
148
                $_SERVER['HTTP_ACCEPT_LANGUAGE'],
149
                $match);
150
            if (count($match[1])) {
151
                foreach ($match[1] as $i => $l) {
152
                    if (isset($match[2][$i]) && $match[2][$i] != '') {
153
                        $langs[strtolower($l)] = (float) $match[2][$i];
154
                    } else {
155
                        $langs[strtolower($l)] = 1;
156
                    }
157
                }
158
                arsort($langs, SORT_NUMERIC);
159
                reset($langs);
160
                $language = key($langs); # most wanted language
161
            }
162
        }
163
        
164
        return $language;
165
    }
166
*/
167
168
	/**
169
	 * Utility function to emit a Response without additional framework.
170
	 */
171
    public static function sendResponse(ResponseInterface $response) 
172
    {
173
		$code = $response->getStatusCode();
174
		$reason = $response->getReasonPhrase();
175
		header(
176
			sprintf('HTTP/%s %s %s', $response->getProtocolVersion(), $code, $reason),
177
			true, $code 
178
		);
179
180
		foreach ($response->getHeaders() as $header => $values) {
181
			foreach ($values as $value) {
182
				header("$header: $value", false);
183
			}
184
		}
185
186
		echo $response->getBody();
187
	}
188
}
189