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