PHP8
[m6w6/seekat] / lib / API / ContentType.php
1 <?php
2
3 namespace seekat\API;
4
5 use http\Header;
6 use http\Message\Body;
7 use http\Params;
8 use seekat\API;
9 use seekat\API\ContentType\Handler;
10 use seekat\Exception\InvalidArgumentException;
11 use seekat\Exception\UnexpectedValueException;
12
13 final class ContentType {
14 /**
15 * @var int
16 */
17 private $version;
18
19 /**
20 * Content type abbreviation
21 * @var string
22 */
23 private $type;
24
25 /**
26 * Content type handler map
27 * @var array
28 */
29 static private $types = [];
30
31 /**
32 * Register a content type handler
33 */
34 static function register(Handler $handler) {
35 foreach ($handler->types() as $type) {
36 self::$types[$type] = $handler;
37 }
38 }
39
40 /**
41 * Check whether a handler is registered for a particular content type
42 * @param string $type The (abbreviated) content type
43 * @return bool
44 */
45 static function registered(string $type) : bool {
46 return isset(self::$types[$type]);
47 }
48
49 /**
50 * Unregister a content type handler
51 * @param string $type
52 */
53 static function unregister(string $type) {
54 unset(self::$types[$type]);
55 }
56
57 /**
58 * API Version
59 *
60 * @param int $version
61 * @param string $type
62 */
63 function __construct(int $version = 3, string $type = null) {
64 $this->version = $version;
65 if (isset($type)) {
66 $this->setContentType($this->extractTypeFromParams(new Params($type)));
67 }
68 }
69
70 /**
71 * @param Header $contentType
72 */
73 function setContentTypeHeader(Header $contentType) {
74 $this->type = $this->extractTypeFromHeader($contentType);
75 }
76
77 /**
78 * @param string $contentType
79 */
80 function setContentType(string $contentType) {
81 $this->type = $this->extractType($contentType);
82 }
83
84 /**
85 * @return int
86 */
87 function getVersion() : int {
88 return $this->version;
89 }
90
91 /**
92 * Get the (abbreviated) content type name
93 * @return string
94 */
95 function getType() : string {
96 return $this->type;
97 }
98
99 /**
100 * Get the (full) content type
101 * @return string
102 */
103 function getContentType() : string {
104 return $this->composeType($this->type);
105 }
106
107 /**
108 * @param API $api
109 * @return API clone
110 */
111 function apply(API $api) : API {
112 return $api->withHeader("Accept", $this->getContentType());
113 }
114
115 /**
116 * Decode a response message's body according to its content type
117 * @param Body $data
118 * @return mixed
119 * @throws UnexpectedValueException
120 */
121 function decode(Body $data) {
122 $type = $this->getType();
123 if (static::registered($type)) {
124 return self::$types[$type]->decode($data);
125 }
126 throw new UnexpectedValueException("Unhandled content type '$type'");
127 }
128
129 /**
130 * Encode a request message's body according to its content type
131 * @param mixed $data
132 * @return Body
133 * @throws UnexpectedValueException
134 */
135 function encode($data) : Body {
136 $type = $this->getType();
137 if (static::registered($type)) {
138 return self::$types[$type]->encode($data);
139 }
140 throw new UnexpectedValueException("Unhandled content type '$type'");
141 }
142
143 private function composeType(string $type) : string {
144 $part = "[^()<>@,;:\\\"\/\[\]?.=[:space:][:cntrl:]]+";
145 if (preg_match("/^$part\/$part\$/", $type)) {
146 return $type;
147 }
148
149 switch (substr($type, 0, 1)) {
150 case "+":
151 case ".":
152 case "":
153 break;
154 default:
155 $type = ".$type";
156 break;
157 }
158 return "application/vnd.github.v{$this->version}$type";
159 }
160
161 private function extractType(string $type) : string {
162 return preg_replace("/
163 (?:application\/(?:vnd\.github(?:\.v{$this->version})?)?)
164 (?|
165 \. ([^.+]+)
166 | (?:\.[^.+]+)?\+? (json)
167 )/x", "\\1", $type);
168 }
169
170 private function extractTypeFromParams(Params $params) : string {
171 return $this->extractType(current(array_keys($params->params)));
172 }
173
174 private function extractTypeFromHeader(Header $header) : string {
175 if (strcasecmp($header->name, "Content-Type")) {
176 throw new InvalidArgumentException(
177 "Expected Content-Type header, got ". $header->name);
178 }
179 return $this->extractTypeFromParams($header->getParams());
180 }
181 }
182
183 ContentType::register(new Handler\Text);
184 ContentType::register(new Handler\Json);
185 ContentType::register(new Handler\Base64);
186 ContentType::register(new Handler\Stream);