Issues (21)

src/Support/responders.php (1 issue)

1
<?php declare(strict_types=1);
2
3
use Koded\Http\{HTTPBadRequest, HttpFactory, HTTPMethodNotAllowed, HTTPNotFound, HTTPNotImplemented, ServerResponse};
4
use Koded\Http\Client\ClientFactory;
5
use Koded\Http\Interfaces\{ClientType, HttpStatus};
6
use Psr\Http\Message\ResponseInterface;
7
use function Koded\Http\create_stream;
8
9
10
function path_not_found(string $path): callable
11
{
12 1
    throw new HTTPNotFound(instance: $path);
13
}
14
15
16
function bad_request(...$args): callable
17
{
18
    throw new HTTPBadRequest(...$args);
19
}
20
21
22
function method_not_allowed(array $allowed): callable
23
{
24 1
    throw new HTTPMethodNotAllowed($allowed);
25
}
26
27
28
function no_app_routes(): callable
29
{
30 1
    throw (new HTTPNotImplemented(
31 1
        title: 'No Routes',
32 1
        detail: 'No routes are defined in your application',
33 1
        type: 'https://kodeart.github.io/koded/routing/'
34 1
    ))
35 1
        ->setMember('framework', 'Koded Framework')
36 1
        ->setMember('version', get_version());
37
}
38
39
/**
40
 * Creates a responder for HTTP HEAD method.
41
 *
42
 * @param string $uri
43
 * @param array $methods
44
 * @return callable
45
 */
46
function head_response(string $uri, array $methods): callable
47
{
48 2
    return function () use ($uri, $methods): ResponseInterface {
49 2
        empty($methods) and $methods = ['HEAD', 'OPTIONS'];
50 2
        if (false === in_array('GET', $methods)) {
51 2
            return (new ServerResponse)->withHeader('Allow', $methods);
52
        }
53
        $get = (new ClientFactory(
54
            function_exists('curl_init')
55
                ? ClientType::CURL
56
                : ClientType::PHP
57
        ))
58
            ->get($uri, ['Connection' => 'close'])
59
            ->timeout(5)
60
            ->read()
61
            ->withoutHeader('Set-Cookie')
62
            ->withHeader('Allow', join(',', $methods));
63
64
        if ($get->getStatusCode() < HttpStatus::BAD_REQUEST) {
65
            return $get;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $get returns the type Psr\Http\Message\MessageInterface which includes types incompatible with the type-hinted return Psr\Http\Message\ResponseInterface.
Loading history...
66
        }
67
        // If GET request fails, it returns the Allow header
68
        // with the failure reason in the "X-Error-*" headers
69
        error_log($get->getBody()->getContents());
70
        return $get
71
            ->withHeader('X-Error-Status', join(' ', [$get->getStatusCode(), $get->getReasonPhrase()]))
72
            ->withHeader('X-Error-Message', str_replace(["\n", "\r", "\t"], ' ', $get->getBody()))
73
            ->withStatus(HttpStatus::OK)
74
            ->withBody(create_stream(''));
75 2
    };
76
}
77
78
/**
79
 * Creates a responder for HTTP OPTIONS method.
80
 * This method does not return the Origin header, because it may
81
 * be a CORS request which should be handled by the middleware.
82
 *
83
 * @param array $methods Supported HTTP methods for the URI in question
84
 * @return callable The responder for the OPTIONS method
85
 * @link https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/OPTIONS
86
 */
87
function create_options_response(array $methods): callable
88
{
89 7
    return fn(): ResponseInterface => (new HttpFactory)
90 7
        ->createResponse(HttpStatus::NO_CONTENT)
91 7
        ->withHeader('Cache-Control', 'no-cache, max-age=0')
92 7
        ->withHeader('Allow', join(',', $methods))
93 7
        ->withHeader('Content-Type', 'text/plain');
94
}
95
96
/**
97
 * Maps the HTTP methods to responder (public) methods.
98
 *
99
 * @param callable|object|string $resource
100
 * @return array|callable[]|object[]|string[]
101
 */
102
function map_http_methods(callable|object|string $resource): array
103
{
104 36
    $map = [
105 36
        'HEAD' => 'head',
106 36
        'OPTIONS' => 'options'
107 36
    ];
108 36
    foreach ([
109 36
                 'GET',
110 36
                 'POST',
111 36
                 'PUT',
112 36
                 'PATCH',
113 36
                 'DELETE'
114 36
             ] as $method) {
115 36
        if (method_exists($resource, $method)) {
116 18
            $map = [$method => strtolower($method)] + $map;
117 36
        } elseif (is_callable($resource)) {
118 15
            $map = [$method => $resource] + $map;
119
        }
120
    }
121 36
    return $map;
122
}
123