Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
ContentType
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
7 / 7
10
100.00% covered (success)
100.00%
1 / 1
 fromHeaderString
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getCharset
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 withCharset
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isJson
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isXml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isText
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isMultipart
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/**
6 * This file is part of php-fast-forward/http-message.
7 *
8 * This source file is subject to the license bundled
9 * with this source code in the file LICENSE.
10 *
11 * @link      https://github.com/php-fast-forward/http-message
12 * @copyright Copyright (c) 2025 Felipe Sayão Lobato Abreu <github@mentordosnerds.com>
13 * @license   https://opensource.org/licenses/MIT MIT License
14 */
15
16namespace FastForward\Http\Message\Header;
17
18/**
19 * Enum ContentType.
20 *
21 * Represents a comprehensive set of HTTP Content-Type header values.
22 * Each enum case describes a MIME type that MAY be used when constructing or
23 * parsing HTTP messages. Implementations interacting with this enum SHOULD
24 * ensure appropriate handling based on RFC 2119 requirement levels.
25 *
26 * This enum MUST be used when normalizing, validating, or comparing Content-Type
27 * header values in a strict and type-safe manner. It SHALL provide helper
28 * methods for extracting metadata, ensuring consistent behavior across HTTP
29 * message handling.
30 */
31enum ContentType: string
32{
33    // Text Types
34    case TextPlain = 'text/plain';
35    case TextHtml  = 'text/html';
36    case TextCss   = 'text/css';
37    case TextCsv   = 'text/csv';
38    case TextXml   = 'text/xml';
39
40    // Application Types
41    case ApplicationJson           = 'application/json';
42    case ApplicationXml            = 'application/xml';
43    case ApplicationFormUrlencoded = 'application/x-www-form-urlencoded';
44    case ApplicationPdf            = 'application/pdf';
45    case ApplicationJavascript     = 'application/javascript';
46    case ApplicationOctetStream    = 'application/octet-stream';
47
48    // Multipart Types
49    case MultipartFormData = 'multipart/form-data';
50
51    // Image Types
52    case ImageJpeg = 'image/jpeg';
53    case ImagePng  = 'image/png';
54    case ImageGif  = 'image/gif';
55    case ImageSvg  = 'image/svg+xml';
56
57    /**
58     * Creates a ContentType instance from a full Content-Type header string.
59     *
60     * This method SHALL parse header values that include parameters such as
61     * charsets or boundary markers. Only the primary MIME type SHALL be used
62     * for determining the enum case. If the base MIME type does not match any
63     * known ContentType, this method MUST return null.
64     *
65     * Example:
66     *   "application/json; charset=utf-8" → ContentType::ApplicationJson
67     *
68     * @param string $header the full Content-Type header string
69     *
70     * @return null|self the derived ContentType case or null if unsupported
71     */
72    public static function fromHeaderString(string $header): ?self
73    {
74        $baseType = strtok($header, ';');
75        if (false === $baseType) {
76            return null;
77        }
78
79        return self::tryFrom($baseType);
80    }
81
82    /**
83     * Extracts the charset parameter from a Content-Type header string.
84     *
85     * This method SHOULD be used when charset negotiation or validation is
86     * required. If no charset is present, this method MUST return null. The
87     * extracted charset value SHALL be trimmed of surrounding whitespace.
88     *
89     * Example:
90     *   "application/json; charset=utf-8" → "utf-8"
91     *
92     * @param string $contentTypeHeader the full Content-Type header value
93     *
94     * @return null|string the charset value or null if absent
95     */
96    public static function getCharset(string $contentTypeHeader): ?string
97    {
98        if (preg_match('/charset=([^;]+)/i', $contentTypeHeader, $matches)) {
99            return mb_trim($matches[1]);
100        }
101
102        return null;
103    }
104
105    /**
106     * Returns the Content-Type header value with an appended charset parameter.
107     *
108     * The returned string MUST follow the standard "type/subtype; charset=X"
109     * format. Implementations SHOULD ensure the provided charset is valid
110     * according to application requirements.
111     *
112     * @param string $charset the charset to append to the Content-Type
113     *
114     * @return string the resulting Content-Type header value
115     */
116    public function withCharset(string $charset): string
117    {
118        return $this->value . '; charset=' . $charset;
119    }
120
121    /**
122     * Determines whether the content type represents JSON data.
123     *
124     * This method SHALL evaluate strictly via enum identity comparison. It MUST
125     * return true only for application/json.
126     *
127     * @return bool true if JSON type, false otherwise
128     */
129    public function isJson(): bool
130    {
131        return self::ApplicationJson === $this;
132    }
133
134    /**
135     * Determines whether the content type represents XML data.
136     *
137     * This method SHALL consider both application/xml and text/xml as valid XML
138     * content types. It MUST return true for either enumeration case.
139     *
140     * @return bool true if XML type, false otherwise
141     */
142    public function isXml(): bool
143    {
144        return self::ApplicationXml === $this || self::TextXml === $this;
145    }
146
147    /**
148     * Determines whether the content type represents text-based content.
149     *
150     * Any MIME type beginning with "text/" SHALL be treated as text content.
151     * This method MUST use string prefix evaluation according to the enum value.
152     *
153     * @return bool true if text-based type, false otherwise
154     */
155    public function isText(): bool
156    {
157        return str_starts_with($this->value, 'text/');
158    }
159
160    /**
161     * Determines whether the content type represents multipart data.
162     *
163     * Multipart types are typically used for form uploads and MUST begin with
164     * "multipart/". This method SHALL match MIME type prefixes accordingly.
165     *
166     * @return bool true if multipart type, false otherwise
167     */
168    public function isMultipart(): bool
169    {
170        return str_starts_with($this->value, 'multipart/');
171    }
172}