blitz-php /
framework
| 1 | <?php |
||||||
| 2 | |||||||
| 3 | /** |
||||||
| 4 | * This file is part of Blitz PHP framework. |
||||||
| 5 | * |
||||||
| 6 | * (c) 2022 Dimitri Sitchet Tomkeu <[email protected]> |
||||||
| 7 | * |
||||||
| 8 | * For the full copyright and license information, please view |
||||||
| 9 | * the LICENSE file that was distributed with this source code. |
||||||
| 10 | */ |
||||||
| 11 | |||||||
| 12 | namespace BlitzPHP\Http; |
||||||
| 13 | |||||||
| 14 | use BlitzPHP\Contracts\Http\StatusCode; |
||||||
| 15 | use BlitzPHP\Contracts\Session\CookieInterface; |
||||||
| 16 | use BlitzPHP\Exceptions\HttpException; |
||||||
| 17 | use BlitzPHP\Exceptions\LoadException; |
||||||
| 18 | use BlitzPHP\Http\Concerns\ResponseTrait; |
||||||
| 19 | use BlitzPHP\Session\Cookie\Cookie; |
||||||
| 20 | use BlitzPHP\Session\Cookie\CookieCollection; |
||||||
| 21 | use DateTime; |
||||||
| 22 | use DateTimeInterface; |
||||||
| 23 | use DateTimeZone; |
||||||
| 24 | use GuzzleHttp\Psr7\MessageTrait; |
||||||
| 25 | use GuzzleHttp\Psr7\Stream; |
||||||
| 26 | use GuzzleHttp\Psr7\Utils; |
||||||
| 27 | use InvalidArgumentException; |
||||||
| 28 | use Psr\Http\Message\ResponseInterface; |
||||||
| 29 | use Psr\Http\Message\StreamInterface; |
||||||
| 30 | use SplFileInfo; |
||||||
| 31 | use Stringable; |
||||||
| 32 | |||||||
| 33 | /** |
||||||
| 34 | * Les réponses contiennent le texte de la réponse, l'état et les en-têtes d'une réponse HTTP. |
||||||
| 35 | * |
||||||
| 36 | * Il existe des packages externes tels que `fig/http-message-util` qui fournissent HTTP |
||||||
| 37 | * constantes de code d'état. Ceux-ci peuvent être utilisés avec n'importe quelle méthode qui accepte ou |
||||||
| 38 | * renvoie un entier de code d'état. Gardez à l'esprit que ces constantes peuvent |
||||||
| 39 | * inclure les codes d'état qui sont maintenant autorisés, ce qui lancera un |
||||||
| 40 | * `\InvalidArgumentException`. |
||||||
| 41 | * |
||||||
| 42 | * @credit CakePHP <a href="https://api.cakephp.org/4.3/class-Cake.Http.Response.html">Cake\Http\Response</a> |
||||||
| 43 | */ |
||||||
| 44 | class Response implements ResponseInterface, Stringable |
||||||
| 45 | { |
||||||
| 46 | use MessageTrait; |
||||||
| 47 | use ResponseTrait; |
||||||
| 48 | |||||||
| 49 | /** |
||||||
| 50 | * @var int |
||||||
| 51 | */ |
||||||
| 52 | public const STATUS_CODE_MIN = 100; |
||||||
| 53 | |||||||
| 54 | /** |
||||||
| 55 | * @var int |
||||||
| 56 | */ |
||||||
| 57 | public const STATUS_CODE_MAX = 599; |
||||||
| 58 | |||||||
| 59 | /** |
||||||
| 60 | * Codes d'état HTTP autorisés et leur description par défaut. |
||||||
| 61 | * |
||||||
| 62 | * @var array<int, string> |
||||||
| 63 | */ |
||||||
| 64 | protected array $_statusCodes = StatusCode::VALID_CODES; |
||||||
| 65 | |||||||
| 66 | /** |
||||||
| 67 | * Contient la clé de type pour les mappages de type mime pour les types mime connus. |
||||||
| 68 | * |
||||||
| 69 | * @var array<string, mixed> |
||||||
| 70 | */ |
||||||
| 71 | protected array $_mimeTypes = [ |
||||||
| 72 | 'html' => ['text/html', '*/*'], |
||||||
| 73 | 'json' => 'application/json', |
||||||
| 74 | 'xml' => ['application/xml', 'text/xml'], |
||||||
| 75 | 'xhtml' => ['application/xhtml+xml', 'application/xhtml', 'text/xhtml'], |
||||||
| 76 | 'webp' => 'image/webp', |
||||||
| 77 | 'rss' => 'application/rss+xml', |
||||||
| 78 | 'ai' => 'application/postscript', |
||||||
| 79 | 'bcpio' => 'application/x-bcpio', |
||||||
| 80 | 'bin' => 'application/octet-stream', |
||||||
| 81 | 'ccad' => 'application/clariscad', |
||||||
| 82 | 'cdf' => 'application/x-netcdf', |
||||||
| 83 | 'class' => 'application/octet-stream', |
||||||
| 84 | 'cpio' => 'application/x-cpio', |
||||||
| 85 | 'cpt' => 'application/mac-compactpro', |
||||||
| 86 | 'csh' => 'application/x-csh', |
||||||
| 87 | 'csv' => ['text/csv', 'application/vnd.ms-excel'], |
||||||
| 88 | 'dcr' => 'application/x-director', |
||||||
| 89 | 'dir' => 'application/x-director', |
||||||
| 90 | 'dms' => 'application/octet-stream', |
||||||
| 91 | 'doc' => 'application/msword', |
||||||
| 92 | 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', |
||||||
| 93 | 'drw' => 'application/drafting', |
||||||
| 94 | 'dvi' => 'application/x-dvi', |
||||||
| 95 | 'dwg' => 'application/acad', |
||||||
| 96 | 'dxf' => 'application/dxf', |
||||||
| 97 | 'dxr' => 'application/x-director', |
||||||
| 98 | 'eot' => 'application/vnd.ms-fontobject', |
||||||
| 99 | 'eps' => 'application/postscript', |
||||||
| 100 | 'exe' => 'application/octet-stream', |
||||||
| 101 | 'ez' => 'application/andrew-inset', |
||||||
| 102 | 'flv' => 'video/x-flv', |
||||||
| 103 | 'gtar' => 'application/x-gtar', |
||||||
| 104 | 'gz' => 'application/x-gzip', |
||||||
| 105 | 'bz2' => 'application/x-bzip', |
||||||
| 106 | '7z' => 'application/x-7z-compressed', |
||||||
| 107 | 'hal' => ['application/hal+xml', 'application/vnd.hal+xml'], |
||||||
| 108 | 'haljson' => ['application/hal+json', 'application/vnd.hal+json'], |
||||||
| 109 | 'halxml' => ['application/hal+xml', 'application/vnd.hal+xml'], |
||||||
| 110 | 'hdf' => 'application/x-hdf', |
||||||
| 111 | 'hqx' => 'application/mac-binhex40', |
||||||
| 112 | 'ico' => 'image/x-icon', |
||||||
| 113 | 'ips' => 'application/x-ipscript', |
||||||
| 114 | 'ipx' => 'application/x-ipix', |
||||||
| 115 | 'js' => 'application/javascript', |
||||||
| 116 | 'jsonapi' => 'application/vnd.api+json', |
||||||
| 117 | 'latex' => 'application/x-latex', |
||||||
| 118 | 'jsonld' => 'application/ld+json', |
||||||
| 119 | 'kml' => 'application/vnd.google-earth.kml+xml', |
||||||
| 120 | 'kmz' => 'application/vnd.google-earth.kmz', |
||||||
| 121 | 'lha' => 'application/octet-stream', |
||||||
| 122 | 'lsp' => 'application/x-lisp', |
||||||
| 123 | 'lzh' => 'application/octet-stream', |
||||||
| 124 | 'man' => 'application/x-troff-man', |
||||||
| 125 | 'me' => 'application/x-troff-me', |
||||||
| 126 | 'mif' => 'application/vnd.mif', |
||||||
| 127 | 'ms' => 'application/x-troff-ms', |
||||||
| 128 | 'nc' => 'application/x-netcdf', |
||||||
| 129 | 'oda' => 'application/oda', |
||||||
| 130 | 'otf' => 'font/otf', |
||||||
| 131 | 'pdf' => 'application/pdf', |
||||||
| 132 | 'pgn' => 'application/x-chess-pgn', |
||||||
| 133 | 'pot' => 'application/vnd.ms-powerpoint', |
||||||
| 134 | 'pps' => 'application/vnd.ms-powerpoint', |
||||||
| 135 | 'ppt' => 'application/vnd.ms-powerpoint', |
||||||
| 136 | 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', |
||||||
| 137 | 'ppz' => 'application/vnd.ms-powerpoint', |
||||||
| 138 | 'pre' => 'application/x-freelance', |
||||||
| 139 | 'prt' => 'application/pro_eng', |
||||||
| 140 | 'ps' => 'application/postscript', |
||||||
| 141 | 'roff' => 'application/x-troff', |
||||||
| 142 | 'scm' => 'application/x-lotusscreencam', |
||||||
| 143 | 'set' => 'application/set', |
||||||
| 144 | 'sh' => 'application/x-sh', |
||||||
| 145 | 'shar' => 'application/x-shar', |
||||||
| 146 | 'sit' => 'application/x-stuffit', |
||||||
| 147 | 'skd' => 'application/x-koan', |
||||||
| 148 | 'skm' => 'application/x-koan', |
||||||
| 149 | 'skp' => 'application/x-koan', |
||||||
| 150 | 'skt' => 'application/x-koan', |
||||||
| 151 | 'smi' => 'application/smil', |
||||||
| 152 | 'smil' => 'application/smil', |
||||||
| 153 | 'sol' => 'application/solids', |
||||||
| 154 | 'spl' => 'application/x-futuresplash', |
||||||
| 155 | 'src' => 'application/x-wais-source', |
||||||
| 156 | 'step' => 'application/STEP', |
||||||
| 157 | 'stl' => 'application/SLA', |
||||||
| 158 | 'stp' => 'application/STEP', |
||||||
| 159 | 'sv4cpio' => 'application/x-sv4cpio', |
||||||
| 160 | 'sv4crc' => 'application/x-sv4crc', |
||||||
| 161 | 'svg' => 'image/svg+xml', |
||||||
| 162 | 'svgz' => 'image/svg+xml', |
||||||
| 163 | 'swf' => 'application/x-shockwave-flash', |
||||||
| 164 | 't' => 'application/x-troff', |
||||||
| 165 | 'tar' => 'application/x-tar', |
||||||
| 166 | 'tcl' => 'application/x-tcl', |
||||||
| 167 | 'tex' => 'application/x-tex', |
||||||
| 168 | 'texi' => 'application/x-texinfo', |
||||||
| 169 | 'texinfo' => 'application/x-texinfo', |
||||||
| 170 | 'tr' => 'application/x-troff', |
||||||
| 171 | 'tsp' => 'application/dsptype', |
||||||
| 172 | 'ttc' => 'font/ttf', |
||||||
| 173 | 'ttf' => 'font/ttf', |
||||||
| 174 | 'unv' => 'application/i-deas', |
||||||
| 175 | 'ustar' => 'application/x-ustar', |
||||||
| 176 | 'vcd' => 'application/x-cdlink', |
||||||
| 177 | 'vda' => 'application/vda', |
||||||
| 178 | 'xlc' => 'application/vnd.ms-excel', |
||||||
| 179 | 'xll' => 'application/vnd.ms-excel', |
||||||
| 180 | 'xlm' => 'application/vnd.ms-excel', |
||||||
| 181 | 'xls' => 'application/vnd.ms-excel', |
||||||
| 182 | 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', |
||||||
| 183 | 'xlw' => 'application/vnd.ms-excel', |
||||||
| 184 | 'zip' => 'application/zip', |
||||||
| 185 | 'aif' => 'audio/x-aiff', |
||||||
| 186 | 'aifc' => 'audio/x-aiff', |
||||||
| 187 | 'aiff' => 'audio/x-aiff', |
||||||
| 188 | 'au' => 'audio/basic', |
||||||
| 189 | 'kar' => 'audio/midi', |
||||||
| 190 | 'mid' => 'audio/midi', |
||||||
| 191 | 'midi' => 'audio/midi', |
||||||
| 192 | 'mp2' => 'audio/mpeg', |
||||||
| 193 | 'mp3' => 'audio/mpeg', |
||||||
| 194 | 'mpga' => 'audio/mpeg', |
||||||
| 195 | 'ogg' => 'audio/ogg', |
||||||
| 196 | 'oga' => 'audio/ogg', |
||||||
| 197 | 'spx' => 'audio/ogg', |
||||||
| 198 | 'ra' => 'audio/x-realaudio', |
||||||
| 199 | 'ram' => 'audio/x-pn-realaudio', |
||||||
| 200 | 'rm' => 'audio/x-pn-realaudio', |
||||||
| 201 | 'rpm' => 'audio/x-pn-realaudio-plugin', |
||||||
| 202 | 'snd' => 'audio/basic', |
||||||
| 203 | 'tsi' => 'audio/TSP-audio', |
||||||
| 204 | 'wav' => 'audio/x-wav', |
||||||
| 205 | 'aac' => 'audio/aac', |
||||||
| 206 | 'asc' => 'text/plain', |
||||||
| 207 | 'c' => 'text/plain', |
||||||
| 208 | 'cc' => 'text/plain', |
||||||
| 209 | 'css' => 'text/css', |
||||||
| 210 | 'etx' => 'text/x-setext', |
||||||
| 211 | 'f' => 'text/plain', |
||||||
| 212 | 'f90' => 'text/plain', |
||||||
| 213 | 'h' => 'text/plain', |
||||||
| 214 | 'hh' => 'text/plain', |
||||||
| 215 | 'htm' => ['text/html', '*/*'], |
||||||
| 216 | 'ics' => 'text/calendar', |
||||||
| 217 | 'm' => 'text/plain', |
||||||
| 218 | 'rtf' => 'text/rtf', |
||||||
| 219 | 'rtx' => 'text/richtext', |
||||||
| 220 | 'sgm' => 'text/sgml', |
||||||
| 221 | 'sgml' => 'text/sgml', |
||||||
| 222 | 'tsv' => 'text/tab-separated-values', |
||||||
| 223 | 'tpl' => 'text/template', |
||||||
| 224 | 'txt' => 'text/plain', |
||||||
| 225 | 'text' => 'text/plain', |
||||||
| 226 | 'avi' => 'video/x-msvideo', |
||||||
| 227 | 'fli' => 'video/x-fli', |
||||||
| 228 | 'mov' => 'video/quicktime', |
||||||
| 229 | 'movie' => 'video/x-sgi-movie', |
||||||
| 230 | 'mpe' => 'video/mpeg', |
||||||
| 231 | 'mpeg' => 'video/mpeg', |
||||||
| 232 | 'mpg' => 'video/mpeg', |
||||||
| 233 | 'qt' => 'video/quicktime', |
||||||
| 234 | 'viv' => 'video/vnd.vivo', |
||||||
| 235 | 'vivo' => 'video/vnd.vivo', |
||||||
| 236 | 'ogv' => 'video/ogg', |
||||||
| 237 | 'webm' => 'video/webm', |
||||||
| 238 | 'mp4' => 'video/mp4', |
||||||
| 239 | 'm4v' => 'video/mp4', |
||||||
| 240 | 'f4v' => 'video/mp4', |
||||||
| 241 | 'f4p' => 'video/mp4', |
||||||
| 242 | 'm4a' => 'audio/mp4', |
||||||
| 243 | 'f4a' => 'audio/mp4', |
||||||
| 244 | 'f4b' => 'audio/mp4', |
||||||
| 245 | 'gif' => 'image/gif', |
||||||
| 246 | 'ief' => 'image/ief', |
||||||
| 247 | 'jpg' => 'image/jpeg', |
||||||
| 248 | 'jpeg' => 'image/jpeg', |
||||||
| 249 | 'jpe' => 'image/jpeg', |
||||||
| 250 | 'pbm' => 'image/x-portable-bitmap', |
||||||
| 251 | 'pgm' => 'image/x-portable-graymap', |
||||||
| 252 | 'png' => 'image/png', |
||||||
| 253 | 'pnm' => 'image/x-portable-anymap', |
||||||
| 254 | 'ppm' => 'image/x-portable-pixmap', |
||||||
| 255 | 'ras' => 'image/cmu-raster', |
||||||
| 256 | 'rgb' => 'image/x-rgb', |
||||||
| 257 | 'tif' => 'image/tiff', |
||||||
| 258 | 'tiff' => 'image/tiff', |
||||||
| 259 | 'xbm' => 'image/x-xbitmap', |
||||||
| 260 | 'xpm' => 'image/x-xpixmap', |
||||||
| 261 | 'xwd' => 'image/x-xwindowdump', |
||||||
| 262 | 'psd' => [ |
||||||
| 263 | 'application/photoshop', |
||||||
| 264 | 'application/psd', |
||||||
| 265 | 'image/psd', |
||||||
| 266 | 'image/x-photoshop', |
||||||
| 267 | 'image/photoshop', |
||||||
| 268 | 'zz-application/zz-winassoc-psd', |
||||||
| 269 | ], |
||||||
| 270 | 'ice' => 'x-conference/x-cooltalk', |
||||||
| 271 | 'iges' => 'model/iges', |
||||||
| 272 | 'igs' => 'model/iges', |
||||||
| 273 | 'mesh' => 'model/mesh', |
||||||
| 274 | 'msh' => 'model/mesh', |
||||||
| 275 | 'silo' => 'model/mesh', |
||||||
| 276 | 'vrml' => 'model/vrml', |
||||||
| 277 | 'wrl' => 'model/vrml', |
||||||
| 278 | 'mime' => 'www/mime', |
||||||
| 279 | 'pdb' => 'chemical/x-pdb', |
||||||
| 280 | 'xyz' => 'chemical/x-pdb', |
||||||
| 281 | 'javascript' => 'application/javascript', |
||||||
| 282 | 'form' => 'application/x-www-form-urlencoded', |
||||||
| 283 | 'file' => 'multipart/form-data', |
||||||
| 284 | 'xhtml-mobile' => 'application/vnd.wap.xhtml+xml', |
||||||
| 285 | 'atom' => 'application/atom+xml', |
||||||
| 286 | 'amf' => 'application/x-amf', |
||||||
| 287 | 'wap' => ['text/vnd.wap.wml', 'text/vnd.wap.wmlscript', 'image/vnd.wap.wbmp'], |
||||||
| 288 | 'wml' => 'text/vnd.wap.wml', |
||||||
| 289 | 'wmlscript' => 'text/vnd.wap.wmlscript', |
||||||
| 290 | 'wbmp' => 'image/vnd.wap.wbmp', |
||||||
| 291 | 'woff' => 'application/x-font-woff', |
||||||
| 292 | 'appcache' => 'text/cache-manifest', |
||||||
| 293 | 'manifest' => 'text/cache-manifest', |
||||||
| 294 | 'htc' => 'text/x-component', |
||||||
| 295 | 'rdf' => 'application/xml', |
||||||
| 296 | 'crx' => 'application/x-chrome-extension', |
||||||
| 297 | 'oex' => 'application/x-opera-extension', |
||||||
| 298 | 'xpi' => 'application/x-xpinstall', |
||||||
| 299 | 'safariextz' => 'application/octet-stream', |
||||||
| 300 | 'webapp' => 'application/x-web-app-manifest+json', |
||||||
| 301 | 'vcf' => 'text/x-vcard', |
||||||
| 302 | 'vtt' => 'text/vtt', |
||||||
| 303 | 'mkv' => 'video/x-matroska', |
||||||
| 304 | 'pkpass' => 'application/vnd.apple.pkpass', |
||||||
| 305 | 'ajax' => 'text/html', |
||||||
| 306 | 'bmp' => 'image/bmp', |
||||||
| 307 | ]; |
||||||
| 308 | |||||||
| 309 | /** |
||||||
| 310 | * Code de statut à envoyer au client |
||||||
| 311 | */ |
||||||
| 312 | protected int $_status = StatusCode::OK; |
||||||
| 313 | |||||||
| 314 | /** |
||||||
| 315 | * Objet de fichier pour le fichier à lire comme réponse |
||||||
| 316 | * |
||||||
| 317 | * @var SplFileInfo|null |
||||||
| 318 | */ |
||||||
| 319 | protected $_file; |
||||||
| 320 | |||||||
| 321 | /** |
||||||
| 322 | * Gamme de fichiers. Utilisé pour demander des plages de fichiers. |
||||||
| 323 | * |
||||||
| 324 | * @var list<int> |
||||||
|
0 ignored issues
–
show
|
|||||||
| 325 | */ |
||||||
| 326 | protected array $_fileRange = []; |
||||||
| 327 | |||||||
| 328 | /** |
||||||
| 329 | * Le jeu de caractères avec lequel le corps de la réponse est encodé |
||||||
| 330 | */ |
||||||
| 331 | protected string $_charset = 'UTF-8'; |
||||||
| 332 | |||||||
| 333 | /** |
||||||
| 334 | * Contient toutes les directives de cache qui seront converties |
||||||
| 335 | * dans les en-têtes lors de l'envoi de la requête |
||||||
| 336 | */ |
||||||
| 337 | protected array $_cacheDirectives = []; |
||||||
| 338 | |||||||
| 339 | /** |
||||||
| 340 | * Collecte de cookies à envoyer au client |
||||||
| 341 | * |
||||||
| 342 | * @var CookieCollection |
||||||
| 343 | */ |
||||||
| 344 | protected $_cookies; |
||||||
| 345 | |||||||
| 346 | /** |
||||||
| 347 | * Phrase de raison |
||||||
| 348 | */ |
||||||
| 349 | protected string $_reasonPhrase = 'OK'; |
||||||
| 350 | |||||||
| 351 | /** |
||||||
| 352 | * Options du mode flux. |
||||||
| 353 | */ |
||||||
| 354 | protected string $_streamMode = 'wb+'; |
||||||
| 355 | |||||||
| 356 | /** |
||||||
| 357 | * Cible de flux ou objet de ressource. |
||||||
| 358 | * |
||||||
| 359 | * @var resource|string |
||||||
| 360 | */ |
||||||
| 361 | protected $_streamTarget = 'php://memory'; |
||||||
| 362 | |||||||
| 363 | /** |
||||||
| 364 | * Constructeur |
||||||
| 365 | * |
||||||
| 366 | * @param array<string, mixed> $options liste de paramètres pour configurer la réponse. Les valeurs possibles sont : |
||||||
| 367 | * |
||||||
| 368 | * - body : le texte de réponse qui doit être envoyé au client |
||||||
| 369 | * - status : le code d'état HTTP avec lequel répondre |
||||||
| 370 | * - type : une chaîne complète de type mime ou une extension mappée dans cette classe |
||||||
| 371 | * - charset : le jeu de caractères pour le corps de la réponse |
||||||
| 372 | * |
||||||
| 373 | * @throws InvalidArgumentException |
||||||
| 374 | */ |
||||||
| 375 | public function __construct(array $options = []) |
||||||
| 376 | { |
||||||
| 377 | 35 | $this->_streamTarget = $options['streamTarget'] ?? $this->_streamTarget; |
|||||
| 378 | 35 | $this->_streamMode = $options['streamMode'] ?? $this->_streamMode; |
|||||
| 379 | |||||||
| 380 | if (isset($options['stream'])) { |
||||||
| 381 | if (! $options['stream'] instanceof StreamInterface) { |
||||||
| 382 | throw new InvalidArgumentException('Stream option must be an object that implements StreamInterface'); |
||||||
| 383 | } |
||||||
| 384 | $this->stream = $options['stream']; |
||||||
| 385 | } else { |
||||||
| 386 | 35 | $this->_createStream(); |
|||||
| 387 | } |
||||||
| 388 | |||||||
| 389 | if (isset($options['body'])) { |
||||||
| 390 | 4 | $this->stream->write($options['body']); |
|||||
| 391 | } |
||||||
| 392 | |||||||
| 393 | if (isset($options['status'])) { |
||||||
| 394 | 2 | $this->_setStatus($options['status']); |
|||||
| 395 | } |
||||||
| 396 | |||||||
| 397 | if (! isset($options['charset'])) { |
||||||
| 398 | 35 | $options['charset'] = config('app.charset'); |
|||||
| 399 | } |
||||||
| 400 | 35 | $this->_charset = $options['charset']; |
|||||
| 401 | |||||||
| 402 | 35 | $type = 'text/html'; |
|||||
| 403 | if (isset($options['type'])) { |
||||||
| 404 | 2 | $type = $this->resolveType($options['type']); |
|||||
| 405 | } |
||||||
| 406 | 35 | $this->_setContentType($type); |
|||||
| 407 | |||||||
| 408 | 35 | $this->_cookies = new CookieCollection(); |
|||||
| 409 | } |
||||||
| 410 | |||||||
| 411 | /** |
||||||
| 412 | * Crée l'objet de flux. |
||||||
| 413 | */ |
||||||
| 414 | protected function _createStream(): void |
||||||
| 415 | { |
||||||
| 416 | 35 | $this->stream = new Stream(Utils::tryFopen($this->_streamTarget, $this->_streamMode)); |
|||||
|
0 ignored issues
–
show
It seems like
$this->_streamTarget can also be of type resource; however, parameter $filename of GuzzleHttp\Psr7\Utils::tryFopen() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 417 | } |
||||||
| 418 | |||||||
| 419 | /** |
||||||
| 420 | * Formate l'en-tête Content-Type en fonction du contentType et du jeu de caractères configurés |
||||||
| 421 | * le jeu de caractères ne sera défini dans l'en-tête que si la réponse est de type texte |
||||||
| 422 | */ |
||||||
| 423 | protected function _setContentType(string $type): void |
||||||
| 424 | { |
||||||
| 425 | if (in_array($this->_status, [304, 204], true)) { |
||||||
| 426 | 2 | $this->_clearHeader('Content-Type'); |
|||||
| 427 | |||||||
| 428 | 2 | return; |
|||||
| 429 | } |
||||||
| 430 | $allowed = [ |
||||||
| 431 | 'application/javascript', 'application/xml', 'application/rss+xml', |
||||||
| 432 | 35 | ]; |
|||||
| 433 | |||||||
| 434 | 35 | $charset = false; |
|||||
| 435 | if ( |
||||||
| 436 | $this->_charset |
||||||
| 437 | && ( |
||||||
| 438 | str_starts_with($type, 'text/') |
||||||
| 439 | || in_array($type, $allowed, true) |
||||||
| 440 | ) |
||||||
| 441 | ) { |
||||||
| 442 | 35 | $charset = true; |
|||||
| 443 | } |
||||||
| 444 | |||||||
| 445 | if ($charset && ! str_contains($type, ';')) { |
||||||
| 446 | 35 | $this->_setHeader('Content-Type', "{$type}; charset={$this->_charset}"); |
|||||
| 447 | } else { |
||||||
| 448 | 10 | $this->_setHeader('Content-Type', $type); |
|||||
| 449 | } |
||||||
| 450 | } |
||||||
| 451 | |||||||
| 452 | /** |
||||||
| 453 | * Effectuez une redirection vers une nouvelle URL, en deux versions : en-tête ou emplacement. |
||||||
| 454 | * |
||||||
| 455 | * @param string $uri L'URI vers laquelle rediriger |
||||||
| 456 | * @param int|null $code Le type de redirection, par défaut à 302 |
||||||
| 457 | * |
||||||
| 458 | * @throws HttpException Pour un code d'état invalide. |
||||||
| 459 | */ |
||||||
| 460 | public function redirect(string $uri, string $method = 'auto', ?int $code = null): static |
||||||
| 461 | { |
||||||
| 462 | // Suppose une réponse de code d'état 302 ; remplacer si nécessaire |
||||||
| 463 | if ($code === null || $code === 0) { |
||||||
| 464 | 4 | $code = StatusCode::FOUND; |
|||||
| 465 | } |
||||||
| 466 | |||||||
| 467 | // Environnement IIS probable ? Utilisez 'refresh' pour une meilleure compatibilité |
||||||
| 468 | if ($method === 'auto' && isset($_SERVER['SERVER_SOFTWARE']) && str_contains($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS')) { |
||||||
| 469 | 10 | $method = 'refresh'; |
|||||
| 470 | } |
||||||
| 471 | |||||||
| 472 | // remplace le code d'état pour HTTP/1.1 et supérieur |
||||||
| 473 | // reference: http://en.wikipedia.org/wiki/Post/Redirect/Get |
||||||
| 474 | if (isset($_SERVER['SERVER_PROTOCOL'], $_SERVER['REQUEST_METHOD']) && $this->getProtocolVersion() >= 1.1 && $method !== 'refresh') { |
||||||
| 475 | 2 | $code = ($_SERVER['REQUEST_METHOD'] !== 'GET') ? StatusCode::SEE_OTHER : ($code === StatusCode::FOUND ? StatusCode::TEMPORARY_REDIRECT : $code); |
|||||
| 476 | } |
||||||
| 477 | |||||||
| 478 | $new = $method === 'refresh' |
||||||
| 479 | ? $this->withHeader('Refresh', '0;url=' . $uri) |
||||||
| 480 | 10 | : $this->withLocation($uri); |
|||||
| 481 | |||||||
| 482 | 10 | return $new->withStatus($code); |
|||||
| 483 | } |
||||||
| 484 | |||||||
| 485 | /** |
||||||
| 486 | * Renvoie une instance avec un en-tête d'emplacement mis à jour. |
||||||
| 487 | * |
||||||
| 488 | * Si le code d'état actuel est 200, il sera remplacé |
||||||
| 489 | * avec 302. |
||||||
| 490 | * |
||||||
| 491 | * @param string $url L'emplacement vers lequel rediriger. |
||||||
| 492 | * |
||||||
| 493 | * @return static Une nouvelle réponse avec l'en-tête Location défini. |
||||||
| 494 | */ |
||||||
| 495 | public function withLocation(string $url): static |
||||||
| 496 | { |
||||||
| 497 | 10 | $new = $this->withHeader('Location', $url); |
|||||
| 498 | if ($new->_status === StatusCode::OK) { |
||||||
| 499 | 10 | $new->_status = StatusCode::FOUND; |
|||||
| 500 | } |
||||||
| 501 | |||||||
| 502 | 10 | return $new; |
|||||
| 503 | } |
||||||
| 504 | |||||||
| 505 | /** |
||||||
| 506 | * Définit un en-tête. |
||||||
| 507 | * |
||||||
| 508 | * @phpstan-param non-empty-string $header |
||||||
| 509 | */ |
||||||
| 510 | protected function _setHeader(string $header, string $value): void |
||||||
| 511 | { |
||||||
| 512 | 35 | $normalized = strtolower($header); |
|||||
| 513 | 35 | $this->headerNames[$normalized] = $header; |
|||||
| 514 | 35 | $this->headers[$header] = [$value]; |
|||||
| 515 | } |
||||||
| 516 | |||||||
| 517 | /** |
||||||
| 518 | * Effacer l'en-tête |
||||||
| 519 | * |
||||||
| 520 | * @phpstan-param non-empty-string $header |
||||||
| 521 | */ |
||||||
| 522 | protected function _clearHeader(string $header): void |
||||||
| 523 | { |
||||||
| 524 | 8 | $normalized = strtolower($header); |
|||||
| 525 | if (! isset($this->headerNames[$normalized])) { |
||||||
| 526 | 2 | return; |
|||||
| 527 | } |
||||||
| 528 | 8 | $original = $this->headerNames[$normalized]; |
|||||
| 529 | 8 | unset($this->headerNames[$normalized], $this->headers[$original]); |
|||||
| 530 | } |
||||||
| 531 | |||||||
| 532 | /** |
||||||
| 533 | * Obtient le code d'état de la réponse. |
||||||
| 534 | * |
||||||
| 535 | * Le code d'état est un code de résultat entier à 3 chiffres de la tentative du serveur |
||||||
| 536 | * pour comprendre et satisfaire la demande. |
||||||
| 537 | */ |
||||||
| 538 | public function getStatusCode(): int |
||||||
| 539 | { |
||||||
| 540 | 16 | return $this->_status; |
|||||
| 541 | } |
||||||
| 542 | |||||||
| 543 | /** |
||||||
| 544 | * Renvoie une instance avec le code d'état spécifié et, éventuellement, la phrase de raison. |
||||||
| 545 | * |
||||||
| 546 | * Si aucune expression de raison n'est spécifiée, les implémentations PEUVENT choisir par défaut |
||||||
| 547 | * à la RFC 7231 ou à l'expression de raison recommandée par l'IANA pour la réponse |
||||||
| 548 | * code d'état. |
||||||
| 549 | * |
||||||
| 550 | * Cette méthode DOIT être mise en œuvre de manière à conserver la |
||||||
| 551 | * immuabilité du message, et DOIT retourner une instance qui a le |
||||||
| 552 | * état mis à jour et expression de raison. |
||||||
| 553 | * |
||||||
| 554 | * Si le code d'état est 304 ou 204, l'en-tête Content-Type existant |
||||||
| 555 | * sera effacé, car ces codes de réponse n'ont pas de corps. |
||||||
| 556 | * |
||||||
| 557 | * Il existe des packages externes tels que `fig/http-message-util` qui fournissent HTTP |
||||||
| 558 | * constantes de code d'état. Ceux-ci peuvent être utilisés avec n'importe quelle méthode qui accepte ou |
||||||
| 559 | * renvoie un entier de code d'état. Cependant, gardez à l'esprit que ces constantes |
||||||
| 560 | * peut inclure des codes d'état qui sont maintenant autorisés, ce qui lancera un |
||||||
| 561 | * `\InvalidArgumentException`. |
||||||
| 562 | * |
||||||
| 563 | * @see https://tools.ietf.org/html/rfc7231#section-6 |
||||||
| 564 | * @see https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml |
||||||
| 565 | * |
||||||
| 566 | * @param int $code Le code d'état entier à 3 chiffres à définir. |
||||||
| 567 | * @param string $reasonPhrase La phrase de raison à utiliser avec le |
||||||
| 568 | * code d'état fourni ; si aucun n'est fourni, les implémentations PEUVENT |
||||||
| 569 | * utilisez les valeurs par défaut comme suggéré dans la spécification HTTP. |
||||||
| 570 | * |
||||||
| 571 | * @throws HttpException Pour les arguments de code d'état non valides. |
||||||
| 572 | */ |
||||||
| 573 | public function withStatus($code, $reasonPhrase = ''): static |
||||||
| 574 | { |
||||||
| 575 | 20 | $new = clone $this; |
|||||
| 576 | 20 | $new->_setStatus($code, $reasonPhrase); |
|||||
| 577 | |||||||
| 578 | 20 | return $new; |
|||||
| 579 | } |
||||||
| 580 | |||||||
| 581 | /** |
||||||
| 582 | * Modificateur pour l'état de la réponse |
||||||
| 583 | * |
||||||
| 584 | * @throws HttpException Pour les arguments de code d'état non valides. |
||||||
| 585 | */ |
||||||
| 586 | protected function _setStatus(int $code, string $reasonPhrase = ''): void |
||||||
| 587 | { |
||||||
| 588 | if ($code < static::STATUS_CODE_MIN || $code > static::STATUS_CODE_MAX) { |
||||||
| 589 | 2 | throw HttpException::invalidStatusCode($code); |
|||||
| 590 | } |
||||||
| 591 | |||||||
| 592 | if (! array_key_exists($code, $this->_statusCodes) && ($reasonPhrase === '' || $reasonPhrase === '0')) { |
||||||
| 593 | 2 | throw HttpException::unkownStatusCode($code); |
|||||
| 594 | } |
||||||
| 595 | |||||||
| 596 | 22 | $this->_status = $code; |
|||||
| 597 | if ($reasonPhrase === '' && isset($this->_statusCodes[$code])) { |
||||||
| 598 | 22 | $reasonPhrase = $this->_statusCodes[$code]; |
|||||
| 599 | } |
||||||
| 600 | 22 | $this->_reasonPhrase = $reasonPhrase; |
|||||
| 601 | |||||||
| 602 | // Ces codes d'état n'ont pas de corps et ne peuvent pas avoir de types de contenu. |
||||||
| 603 | if (in_array($code, [304, 204], true)) { |
||||||
| 604 | 8 | $this->_clearHeader('Content-Type'); |
|||||
| 605 | } |
||||||
| 606 | } |
||||||
| 607 | |||||||
| 608 | /** |
||||||
| 609 | * Obtient la phrase de motif de réponse associée au code d'état. |
||||||
| 610 | * |
||||||
| 611 | * Parce qu'une phrase de raison n'est pas un élément obligatoire dans une réponse |
||||||
| 612 | * ligne d'état, la valeur de la phrase de raison PEUT être nulle. Implémentations MAI |
||||||
| 613 | * choisissez de renvoyer la phrase de raison recommandée par défaut RFC 7231 (ou celles |
||||||
| 614 | * répertorié dans le registre des codes d'état HTTP IANA) pour la réponse |
||||||
| 615 | * code d'état. |
||||||
| 616 | * |
||||||
| 617 | * @see https://tools.ietf.org/html/rfc7231#section-6 |
||||||
| 618 | * @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml |
||||||
| 619 | */ |
||||||
| 620 | public function getReasonPhrase(): string |
||||||
| 621 | { |
||||||
| 622 | 2 | return $this->_reasonPhrase; |
|||||
| 623 | } |
||||||
| 624 | |||||||
| 625 | /** |
||||||
| 626 | * Définit une définition de type de contenu dans la collection. |
||||||
| 627 | * |
||||||
| 628 | * Ex : setTypeMap('xhtml', ['application/xhtml+xml', 'application/xhtml']) |
||||||
| 629 | * |
||||||
| 630 | * Ceci est nécessaire pour RequestHandlerComponent et la reconnaissance des types. |
||||||
| 631 | * |
||||||
| 632 | * @param string $type Type de contenu. |
||||||
| 633 | * @param list<string>|string $mimeType Définition du type mime. |
||||||
| 634 | */ |
||||||
| 635 | public function setTypeMap(string $type, $mimeType): void |
||||||
| 636 | { |
||||||
| 637 | 2 | $this->_mimeTypes[$type] = $mimeType; |
|||||
| 638 | } |
||||||
| 639 | |||||||
| 640 | /** |
||||||
| 641 | * Renvoie le type de contenu actuel. |
||||||
| 642 | */ |
||||||
| 643 | public function getType(): string |
||||||
| 644 | { |
||||||
| 645 | 8 | $header = $this->getHeaderLine('Content-Type'); |
|||||
| 646 | if (str_contains($header, ';')) { |
||||||
| 647 | 6 | return explode(';', $header)[0]; |
|||||
| 648 | } |
||||||
| 649 | |||||||
| 650 | 6 | return $header; |
|||||
| 651 | } |
||||||
| 652 | |||||||
| 653 | /** |
||||||
| 654 | * Obtenez une réponse mise à jour avec le type de contenu défini. |
||||||
| 655 | * |
||||||
| 656 | * Si vous tentez de définir le type sur une réponse de code d'état 304 ou 204, le |
||||||
| 657 | * Le type de contenu ne prendra pas effet car ces codes d'état n'ont pas de types de contenu. |
||||||
| 658 | * |
||||||
| 659 | * @param string $contentType Soit une extension de fichier qui sera mappée à un type MIME, soit un type MIME concret. |
||||||
| 660 | */ |
||||||
| 661 | public function withType(string $contentType): static |
||||||
| 662 | { |
||||||
| 663 | 10 | $mappedType = $this->resolveType($contentType); |
|||||
| 664 | 10 | $new = clone $this; |
|||||
| 665 | 10 | $new->_setContentType($mappedType); |
|||||
| 666 | |||||||
| 667 | 10 | return $new; |
|||||
| 668 | } |
||||||
| 669 | |||||||
| 670 | /** |
||||||
| 671 | * Traduire et valider les types de contenu. |
||||||
| 672 | * |
||||||
| 673 | * @param string $contentType Type de contenu ou alias de type. |
||||||
| 674 | * |
||||||
| 675 | * @return string Le type de contenu résolu |
||||||
| 676 | * |
||||||
| 677 | * @throws InvalidArgumentException Lorsqu'un type de contenu ou un alias non valide est utilisé. |
||||||
| 678 | */ |
||||||
| 679 | protected function resolveType(string $contentType): string |
||||||
| 680 | { |
||||||
| 681 | 12 | $mapped = $this->getMimeType($contentType); |
|||||
| 682 | if ($mapped) { |
||||||
| 683 | 8 | return is_array($mapped) ? current($mapped) : $mapped; |
|||||
| 684 | } |
||||||
| 685 | if (! str_contains($contentType, '/')) { |
||||||
| 686 | 2 | throw new InvalidArgumentException(sprintf('`%s` est un content type invalide.', $contentType)); |
|||||
| 687 | } |
||||||
| 688 | |||||||
| 689 | 6 | return $contentType; |
|||||
| 690 | } |
||||||
| 691 | |||||||
| 692 | /** |
||||||
| 693 | * Renvoie la définition du type mime pour un alias |
||||||
| 694 | * |
||||||
| 695 | * par exemple `getMimeType('pdf'); // renvoie 'application/pdf'` |
||||||
| 696 | * |
||||||
| 697 | * @param string $alias l'alias du type de contenu à mapper |
||||||
| 698 | * |
||||||
| 699 | * @return array|false|string Type mime mappé en chaîne ou false si $alias n'est pas mappé |
||||||
| 700 | */ |
||||||
| 701 | public function getMimeType(string $alias) |
||||||
| 702 | { |
||||||
| 703 | 12 | return $this->_mimeTypes[$alias] ?? false; |
|||||
| 704 | } |
||||||
| 705 | |||||||
| 706 | /** |
||||||
| 707 | * Mappe un type de contenu vers un alias |
||||||
| 708 | * |
||||||
| 709 | * par exemple `mapType('application/pdf'); // renvoie 'pdf'` |
||||||
| 710 | * |
||||||
| 711 | * @param array|string $ctype Soit un type de contenu de chaîne à mapper, soit un tableau de types. |
||||||
| 712 | * |
||||||
| 713 | * @return array|string|null Alias pour les types fournis. |
||||||
| 714 | */ |
||||||
| 715 | public function mapType($ctype) |
||||||
| 716 | { |
||||||
| 717 | if (is_array($ctype)) { |
||||||
| 718 | 2 | return array_map($this->mapType(...), $ctype); |
|||||
| 719 | } |
||||||
| 720 | |||||||
| 721 | foreach ($this->_mimeTypes as $alias => $types) { |
||||||
| 722 | if (in_array($ctype, (array) $types, true)) { |
||||||
| 723 | 2 | return $alias; |
|||||
| 724 | } |
||||||
| 725 | } |
||||||
| 726 | |||||||
| 727 | return null; |
||||||
| 728 | } |
||||||
| 729 | |||||||
| 730 | /** |
||||||
| 731 | * Renvoie le jeu de caractères actuel. |
||||||
| 732 | */ |
||||||
| 733 | public function getCharset(): string |
||||||
| 734 | { |
||||||
| 735 | 4 | return $this->_charset; |
|||||
| 736 | } |
||||||
| 737 | |||||||
| 738 | /** |
||||||
| 739 | * Obtenez une nouvelle instance avec un jeu de caractères mis à jour. |
||||||
| 740 | */ |
||||||
| 741 | public function withCharset(string $charset): static |
||||||
| 742 | { |
||||||
| 743 | 2 | $new = clone $this; |
|||||
| 744 | 2 | $new->_charset = $charset; |
|||||
| 745 | 2 | $new->_setContentType($this->getType()); |
|||||
| 746 | |||||||
| 747 | 2 | return $new; |
|||||
| 748 | } |
||||||
| 749 | |||||||
| 750 | /** |
||||||
| 751 | * Créez une nouvelle instance avec des en-têtes pour indiquer au client de ne pas mettre en cache la réponse |
||||||
| 752 | */ |
||||||
| 753 | public function withDisabledCache(): static |
||||||
| 754 | { |
||||||
| 755 | return $this->withHeader('Expires', 'Mon, 26 Jul 1997 05:00:00 GMT') |
||||||
| 756 | ->withHeader('Last-Modified', gmdate(DATE_RFC7231)) |
||||||
| 757 | 2 | ->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'); |
|||||
| 758 | } |
||||||
| 759 | |||||||
| 760 | /** |
||||||
| 761 | * Créez une nouvelle instance avec les en-têtes pour activer la mise en cache du client. |
||||||
| 762 | * |
||||||
| 763 | * @param int|string $since un temps valide depuis que le texte de la réponse n'a pas été modifié |
||||||
| 764 | * @param int|string $time une heure valide pour l'expiration du cache |
||||||
| 765 | */ |
||||||
| 766 | public function withCache($since, $time = '+1 day'): static |
||||||
| 767 | { |
||||||
| 768 | if (! is_int($time)) { |
||||||
| 769 | 2 | $time = strtotime($time); |
|||||
| 770 | if ($time === false) { |
||||||
| 771 | throw new InvalidArgumentException( |
||||||
| 772 | 'Invalid time parameter. Ensure your time value can be parsed by strtotime' |
||||||
| 773 | 2 | ); |
|||||
| 774 | } |
||||||
| 775 | } |
||||||
| 776 | |||||||
| 777 | return $this |
||||||
| 778 | ->withModified($since) |
||||||
| 779 | ->withExpires($time) |
||||||
| 780 | ->withSharable(true) |
||||||
| 781 | ->withMaxAge($time - time()) |
||||||
| 782 | 2 | ->withHeader('Date', gmdate(DATE_RFC7231, time())); |
|||||
| 783 | } |
||||||
| 784 | |||||||
| 785 | /** |
||||||
| 786 | * Créez une nouvelle instance avec le jeu de directives public/privé Cache-Control. |
||||||
| 787 | * |
||||||
| 788 | * @param bool $public Si défini sur true, l'en-tête Cache-Control sera défini comme public |
||||||
| 789 | * si défini sur false, la réponse sera définie sur privé. |
||||||
| 790 | * @param int|null $time temps en secondes après lequel la réponse ne doit plus être considérée comme fraîche. |
||||||
| 791 | */ |
||||||
| 792 | public function withSharable(bool $public, ?int $time = null): static |
||||||
| 793 | { |
||||||
| 794 | 2 | $new = clone $this; |
|||||
| 795 | 2 | unset($new->_cacheDirectives['private'], $new->_cacheDirectives['public']); |
|||||
| 796 | |||||||
| 797 | 2 | $key = $public ? 'public' : 'private'; |
|||||
| 798 | 2 | $new->_cacheDirectives[$key] = true; |
|||||
| 799 | |||||||
| 800 | if ($time !== null) { |
||||||
| 801 | 2 | $new->_cacheDirectives['max-age'] = $time; |
|||||
| 802 | } |
||||||
| 803 | 2 | $new->_setCacheControl(); |
|||||
| 804 | |||||||
| 805 | 2 | return $new; |
|||||
| 806 | } |
||||||
| 807 | |||||||
| 808 | /** |
||||||
| 809 | * Créez une nouvelle instance avec la directive Cache-Control s-maxage. |
||||||
| 810 | * |
||||||
| 811 | * Le max-age est le nombre de secondes après lesquelles la réponse ne doit plus être prise en compte |
||||||
| 812 | * un bon candidat pour être extrait d'un cache partagé (comme dans un serveur proxy). |
||||||
| 813 | * |
||||||
| 814 | * @param int $seconds Le nombre de secondes pour max-age partagé |
||||||
| 815 | */ |
||||||
| 816 | public function withSharedMaxAge(int $seconds): static |
||||||
| 817 | { |
||||||
| 818 | 2 | $new = clone $this; |
|||||
| 819 | 2 | $new->_cacheDirectives['s-maxage'] = $seconds; |
|||||
| 820 | 2 | $new->_setCacheControl(); |
|||||
| 821 | |||||||
| 822 | 2 | return $new; |
|||||
| 823 | } |
||||||
| 824 | |||||||
| 825 | /** |
||||||
| 826 | * Créez une instance avec l'ensemble de directives Cache-Control max-age. |
||||||
| 827 | * |
||||||
| 828 | * Le max-age est le nombre de secondes après lesquelles la réponse ne doit plus être prise en compte |
||||||
| 829 | * un bon candidat à récupérer dans le cache local (client). |
||||||
| 830 | * |
||||||
| 831 | * @param int $seconds Les secondes pendant lesquelles une réponse mise en cache peut être considérée comme valide |
||||||
| 832 | */ |
||||||
| 833 | public function withMaxAge(int $seconds): static |
||||||
| 834 | { |
||||||
| 835 | 2 | $new = clone $this; |
|||||
| 836 | 2 | $new->_cacheDirectives['max-age'] = $seconds; |
|||||
| 837 | 2 | $new->_setCacheControl(); |
|||||
| 838 | |||||||
| 839 | 2 | return $new; |
|||||
| 840 | } |
||||||
| 841 | |||||||
| 842 | /** |
||||||
| 843 | * Créez une instance avec le jeu de directives must-revalidate de Cache-Control. |
||||||
| 844 | * |
||||||
| 845 | * Définit la directive Cache-Control must-revalidate. |
||||||
| 846 | * must-revalidate indique que la réponse ne doit pas être servie |
||||||
| 847 | * obsolète par un cache en toutes circonstances sans revalidation préalable |
||||||
| 848 | * avec l'origine. |
||||||
| 849 | * |
||||||
| 850 | * @param bool $enable active ou désactive la directive. |
||||||
| 851 | */ |
||||||
| 852 | public function withMustRevalidate(bool $enable): static |
||||||
| 853 | { |
||||||
| 854 | 2 | $new = clone $this; |
|||||
| 855 | if ($enable) { |
||||||
| 856 | 2 | $new->_cacheDirectives['must-revalidate'] = true; |
|||||
| 857 | } else { |
||||||
| 858 | 2 | unset($new->_cacheDirectives['must-revalidate']); |
|||||
| 859 | } |
||||||
| 860 | 2 | $new->_setCacheControl(); |
|||||
| 861 | |||||||
| 862 | 2 | return $new; |
|||||
| 863 | } |
||||||
| 864 | |||||||
| 865 | /** |
||||||
| 866 | * Méthode d'assistance pour générer un en-tête Cache-Control valide à partir du jeu d'options |
||||||
| 867 | * dans d'autres méthodes |
||||||
| 868 | */ |
||||||
| 869 | protected function _setCacheControl(): void |
||||||
| 870 | { |
||||||
| 871 | 2 | $control = ''; |
|||||
| 872 | |||||||
| 873 | foreach ($this->_cacheDirectives as $key => $val) { |
||||||
| 874 | 2 | $control .= $val === true ? $key : sprintf('%s=%s', $key, $val); |
|||||
| 875 | 2 | $control .= ', '; |
|||||
| 876 | } |
||||||
| 877 | 2 | $control = rtrim($control, ', '); |
|||||
| 878 | 2 | $this->_setHeader('Cache-Control', $control); |
|||||
| 879 | } |
||||||
| 880 | |||||||
| 881 | /** |
||||||
| 882 | * Créez une nouvelle instance avec l'ensemble d'en-tête Expires. |
||||||
| 883 | * |
||||||
| 884 | * ### Exemples: |
||||||
| 885 | * |
||||||
| 886 | * ``` |
||||||
| 887 | * // Va expirer le cache de réponse maintenant |
||||||
| 888 | * $response->withExpires('maintenant') |
||||||
| 889 | * |
||||||
| 890 | * // Définira l'expiration dans les prochaines 24 heures |
||||||
| 891 | * $response->withExpires(new DateTime('+1 jour')) |
||||||
| 892 | * ``` |
||||||
| 893 | * |
||||||
| 894 | * @param DateTimeInterface|int|string|null $time Chaîne d'heure valide ou instance de \DateTime. |
||||||
| 895 | */ |
||||||
| 896 | public function withExpires($time): static |
||||||
| 897 | { |
||||||
| 898 | 2 | $date = $this->_getUTCDate($time); |
|||||
| 899 | |||||||
| 900 | 2 | return $this->withHeader('Expires', $date->format(DATE_RFC7231)); |
|||||
| 901 | } |
||||||
| 902 | |||||||
| 903 | /** |
||||||
| 904 | * Créez une nouvelle instance avec le jeu d'en-tête Last-Modified. |
||||||
| 905 | * |
||||||
| 906 | * ### Exemples: |
||||||
| 907 | * |
||||||
| 908 | * ``` |
||||||
| 909 | * // Va expirer le cache de réponse maintenant |
||||||
| 910 | * $response->withModified('now') |
||||||
| 911 | * |
||||||
| 912 | * // Définira l'expiration dans les prochaines 24 heures |
||||||
| 913 | * $response->withModified(new DateTime('+1 jour')) |
||||||
| 914 | * ``` |
||||||
| 915 | * |
||||||
| 916 | * @param DateTimeInterface|int|string $time Chaîne d'heure valide ou instance de \DateTime. |
||||||
| 917 | */ |
||||||
| 918 | public function withModified($time): static |
||||||
| 919 | { |
||||||
| 920 | 2 | $date = $this->_getUTCDate($time); |
|||||
| 921 | |||||||
| 922 | 2 | return $this->withHeader('Last-Modified', $date->format(DATE_RFC7231)); |
|||||
| 923 | } |
||||||
| 924 | |||||||
| 925 | /** |
||||||
| 926 | * Définit la réponse comme non modifiée en supprimant tout contenu du corps |
||||||
| 927 | * définir le code d'état sur "304 Non modifié" et supprimer tous |
||||||
| 928 | * en-têtes contradictoires |
||||||
| 929 | * |
||||||
| 930 | * *Avertissement* Cette méthode modifie la réponse sur place et doit être évitée. |
||||||
| 931 | */ |
||||||
| 932 | public function notModified(): void |
||||||
| 933 | { |
||||||
| 934 | $this->_createStream(); |
||||||
| 935 | $this->_setStatus(StatusCode::NOT_MODIFIED); |
||||||
| 936 | |||||||
| 937 | $remove = [ |
||||||
| 938 | 'Allow', |
||||||
| 939 | 'Content-Encoding', |
||||||
| 940 | 'Content-Language', |
||||||
| 941 | 'Content-Length', |
||||||
| 942 | 'Content-MD5', |
||||||
| 943 | 'Content-Type', |
||||||
| 944 | 'Last-Modified', |
||||||
| 945 | ]; |
||||||
| 946 | |||||||
| 947 | foreach ($remove as $header) { |
||||||
| 948 | $this->_clearHeader($header); |
||||||
| 949 | } |
||||||
| 950 | } |
||||||
| 951 | |||||||
| 952 | /** |
||||||
| 953 | * Créer une nouvelle instance comme "non modifiée" |
||||||
| 954 | * |
||||||
| 955 | * Cela supprimera tout contenu du corps défini le code d'état |
||||||
| 956 | * à "304" et en supprimant les en-têtes qui décrivent |
||||||
| 957 | * un corps de réponse. |
||||||
| 958 | */ |
||||||
| 959 | public function withNotModified(): static |
||||||
| 960 | { |
||||||
| 961 | 2 | $new = $this->withStatus(StatusCode::NOT_MODIFIED); |
|||||
| 962 | 2 | $new->_createStream(); |
|||||
| 963 | $remove = [ |
||||||
| 964 | 'Allow', |
||||||
| 965 | 'Content-Encoding', |
||||||
| 966 | 'Content-Language', |
||||||
| 967 | 'Content-Length', |
||||||
| 968 | 'Content-MD5', |
||||||
| 969 | 'Content-Type', |
||||||
| 970 | 'Last-Modified', |
||||||
| 971 | 2 | ]; |
|||||
| 972 | |||||||
| 973 | foreach ($remove as $header) { |
||||||
| 974 | 2 | $new = $new->withoutHeader($header); |
|||||
| 975 | } |
||||||
| 976 | |||||||
| 977 | 2 | return $new; |
|||||
| 978 | } |
||||||
| 979 | |||||||
| 980 | /** |
||||||
| 981 | * Créez une nouvelle instance avec l'ensemble d'en-tête Vary. |
||||||
| 982 | * |
||||||
| 983 | * Si un tableau est passé, les valeurs seront implosées dans une virgule |
||||||
| 984 | * chaîne séparée. Si aucun paramètre n'est passé, alors un |
||||||
| 985 | * le tableau avec la valeur actuelle de l'en-tête Vary est renvoyé |
||||||
| 986 | * |
||||||
| 987 | * @param list<string>|string $cacheVariances Une seule chaîne Vary ou un tableau contenant la liste des écarts. |
||||||
| 988 | */ |
||||||
| 989 | public function withVary($cacheVariances): static |
||||||
| 990 | { |
||||||
| 991 | 2 | return $this->withHeader('Vary', (array) $cacheVariances); |
|||||
| 992 | } |
||||||
| 993 | |||||||
| 994 | /** |
||||||
| 995 | * Créez une nouvelle instance avec l'ensemble d'en-tête Etag. |
||||||
| 996 | * |
||||||
| 997 | * Les Etags sont une indication forte qu'une réponse peut être mise en cache par un |
||||||
| 998 | * Client HTTP. Une mauvaise façon de générer des Etags est de créer un hachage de |
||||||
| 999 | * la sortie de la réponse, génère à la place un hachage unique du |
||||||
| 1000 | * composants uniques qui identifient une demande, comme un |
||||||
| 1001 | * l'heure de modification, un identifiant de ressource et tout ce que vous considérez |
||||||
| 1002 | * qui rend la réponse unique. |
||||||
| 1003 | * |
||||||
| 1004 | * Le deuxième paramètre est utilisé pour informer les clients que le contenu a |
||||||
| 1005 | * modifié, mais sémantiquement, il est équivalent aux valeurs mises en cache existantes. Envisager |
||||||
| 1006 | * une page avec un compteur de visites, deux pages vues différentes sont équivalentes, mais |
||||||
| 1007 | * ils diffèrent de quelques octets. Cela permet au client de décider s'il doit |
||||||
| 1008 | * utiliser les données mises en cache. |
||||||
| 1009 | * |
||||||
| 1010 | * @param string $hash Le hachage unique qui identifie cette réponse |
||||||
| 1011 | * @param bool $weak Si la réponse est sémantiquement la même que |
||||||
| 1012 | * autre avec le même hash ou non. La valeur par défaut est false |
||||||
| 1013 | */ |
||||||
| 1014 | public function withEtag(string $hash, bool $weak = false): static |
||||||
| 1015 | { |
||||||
| 1016 | 2 | $hash = sprintf('%s"%s"', $weak ? 'W/' : '', $hash); |
|||||
| 1017 | |||||||
| 1018 | 2 | return $this->withHeader('Etag', $hash); |
|||||
| 1019 | } |
||||||
| 1020 | |||||||
| 1021 | /** |
||||||
| 1022 | * Renvoie un objet DateTime initialisé au paramètre $time et utilisant UTC |
||||||
| 1023 | * comme fuseau horaire |
||||||
| 1024 | * |
||||||
| 1025 | * @param DateTimeInterface|int|string|null $time Chaîne d'heure valide ou instance de \DateTimeInterface. |
||||||
| 1026 | */ |
||||||
| 1027 | protected function _getUTCDate($time = null): DateTimeInterface |
||||||
| 1028 | { |
||||||
| 1029 | if ($time instanceof DateTimeInterface) { |
||||||
| 1030 | 2 | $result = clone $time; |
|||||
| 1031 | } elseif (is_int($time)) { |
||||||
| 1032 | 2 | $result = new DateTime(date('Y-m-d H:i:s', $time)); |
|||||
| 1033 | } else { |
||||||
| 1034 | 2 | $result = new DateTime($time ?? 'now'); |
|||||
| 1035 | } |
||||||
| 1036 | |||||||
| 1037 | /** @psalm-suppress UndefinedInterfaceMethod */ |
||||||
| 1038 | 2 | return $result->setTimezone(new DateTimeZone('UTC')); |
|||||
| 1039 | } |
||||||
| 1040 | |||||||
| 1041 | /** |
||||||
| 1042 | * Définit le bon gestionnaire de mise en mémoire tampon de sortie pour envoyer une réponse compressée. |
||||||
| 1043 | * Les réponses seront compressé avec zlib, si l'extension est disponible. |
||||||
| 1044 | * |
||||||
| 1045 | * @return bool false si le client n'accepte pas les réponses compressées ou si aucun gestionnaire n'est disponible, true sinon |
||||||
| 1046 | */ |
||||||
| 1047 | public function compress(): bool |
||||||
| 1048 | { |
||||||
| 1049 | $compressionEnabled = ini_get('zlib.output_compression') !== '1' |
||||||
| 1050 | && extension_loaded('zlib') |
||||||
| 1051 | 2 | && (str_contains((string) env('HTTP_ACCEPT_ENCODING'), 'gzip')); |
|||||
| 1052 | |||||||
| 1053 | 2 | return $compressionEnabled && ob_start('ob_gzhandler'); |
|||||
| 1054 | } |
||||||
| 1055 | |||||||
| 1056 | /** |
||||||
| 1057 | * Retourne VRAI si la sortie résultante sera compressée par PHP |
||||||
| 1058 | */ |
||||||
| 1059 | public function outputCompressed(): bool |
||||||
| 1060 | { |
||||||
| 1061 | return str_contains((string) env('HTTP_ACCEPT_ENCODING'), 'gzip') |
||||||
| 1062 | 2 | && (ini_get('zlib.output_compression') === '1' || in_array('ob_gzhandler', ob_list_handlers(), true)); |
|||||
| 1063 | } |
||||||
| 1064 | |||||||
| 1065 | /** |
||||||
| 1066 | * Créez une nouvelle instance avec l'ensemble d'en-tête Content-Disposition. |
||||||
| 1067 | * |
||||||
| 1068 | * @param string $filename Le nom du fichier car le navigateur téléchargera la réponse |
||||||
| 1069 | */ |
||||||
| 1070 | public function withDownload(string $filename): static |
||||||
| 1071 | { |
||||||
| 1072 | 2 | return $this->withHeader('Content-Disposition', 'attachment; filename="' . $filename . '"'); |
|||||
| 1073 | } |
||||||
| 1074 | |||||||
| 1075 | /** |
||||||
| 1076 | * Créez une nouvelle réponse avec l'ensemble d'en-tête Content-Length. |
||||||
| 1077 | * |
||||||
| 1078 | * @param int|string $bytes Nombre d'octets |
||||||
| 1079 | */ |
||||||
| 1080 | public function withLength(int|string $bytes): static |
||||||
| 1081 | { |
||||||
| 1082 | 2 | return $this->withHeader('Content-Length', (string) $bytes); |
|||||
| 1083 | } |
||||||
| 1084 | |||||||
| 1085 | /** |
||||||
| 1086 | * Créez une nouvelle réponse avec l'ensemble d'en-tête de lien. |
||||||
| 1087 | * |
||||||
| 1088 | * ### Exemples |
||||||
| 1089 | * |
||||||
| 1090 | * ``` |
||||||
| 1091 | * $response = $response->withAddedLink('http://example.com?page=1', ['rel' => 'prev']) |
||||||
| 1092 | * ->withAddedLink('http://example.com?page=3', ['rel' => 'next']); |
||||||
| 1093 | * ``` |
||||||
| 1094 | * |
||||||
| 1095 | * Générera : |
||||||
| 1096 | * |
||||||
| 1097 | * ``` |
||||||
| 1098 | * Link : <http://example.com?page=1> ; rel="prev" |
||||||
| 1099 | * Link : <http://example.com?page=3> ; rel="suivant" |
||||||
| 1100 | * ``` |
||||||
| 1101 | * |
||||||
| 1102 | * @param string $url L'URL LinkHeader. |
||||||
| 1103 | * @param array<string, mixed> $options Les paramètres LinkHeader. |
||||||
| 1104 | */ |
||||||
| 1105 | public function withAddedLink(string $url, array $options = []): static |
||||||
| 1106 | { |
||||||
| 1107 | 2 | $params = []; |
|||||
| 1108 | |||||||
| 1109 | foreach ($options as $key => $option) { |
||||||
| 1110 | 2 | $params[] = $key . '="' . $option . '"'; |
|||||
| 1111 | } |
||||||
| 1112 | |||||||
| 1113 | 2 | $param = ''; |
|||||
| 1114 | if ($params !== []) { |
||||||
| 1115 | 2 | $param = '; ' . implode('; ', $params); |
|||||
| 1116 | } |
||||||
| 1117 | |||||||
| 1118 | 2 | return $this->withAddedHeader('Link', '<' . $url . '>' . $param); |
|||||
| 1119 | } |
||||||
| 1120 | |||||||
| 1121 | /** |
||||||
| 1122 | * Vérifie si une réponse n'a pas été modifiée selon le 'If-None-Match' |
||||||
| 1123 | * (Etags) et requête 'If-Modified-Since' (dernière modification) |
||||||
| 1124 | * en-têtes. Si la réponse est détectée comme n'étant pas modifiée, elle |
||||||
| 1125 | * est marqué comme tel afin que le client puisse en être informé. |
||||||
| 1126 | * |
||||||
| 1127 | * Pour marquer une réponse comme non modifiée, vous devez définir au moins |
||||||
| 1128 | * l'en-tête de réponse etag Last-Modified avant d'appeler cette méthode. Autrement |
||||||
| 1129 | * une comparaison ne sera pas possible. |
||||||
| 1130 | * |
||||||
| 1131 | * *Avertissement* Cette méthode modifie la réponse sur place et doit être évitée. |
||||||
| 1132 | * |
||||||
| 1133 | * @param ServerRequest $request Objet de requête |
||||||
| 1134 | * |
||||||
| 1135 | * @return bool Indique si la réponse a été marquée comme non modifiée ou non. |
||||||
| 1136 | */ |
||||||
| 1137 | public function checkNotModified(ServerRequest $request): bool |
||||||
| 1138 | { |
||||||
| 1139 | $etags = preg_split('/\s*,\s*/', $request->getHeaderLine('If-None-Match'), 0, PREG_SPLIT_NO_EMPTY); |
||||||
| 1140 | $responseTag = $this->getHeaderLine('Etag'); |
||||||
| 1141 | $etagMatches = null; |
||||||
| 1142 | if ($responseTag !== '' && $responseTag !== '0') { |
||||||
| 1143 | $etagMatches = in_array('*', $etags, true) || in_array($responseTag, $etags, true); |
||||||
| 1144 | } |
||||||
| 1145 | |||||||
| 1146 | $modifiedSince = $request->getHeaderLine('If-Modified-Since'); |
||||||
| 1147 | $timeMatches = null; |
||||||
| 1148 | if ($modifiedSince && $this->hasHeader('Last-Modified')) { |
||||||
| 1149 | $timeMatches = strtotime($this->getHeaderLine('Last-Modified')) === strtotime($modifiedSince); |
||||||
| 1150 | } |
||||||
| 1151 | if ($etagMatches === null && $timeMatches === null) { |
||||||
| 1152 | return false; |
||||||
| 1153 | } |
||||||
| 1154 | $notModified = $etagMatches !== false && $timeMatches !== false; |
||||||
| 1155 | if ($notModified) { |
||||||
| 1156 | $this->notModified(); |
||||||
| 1157 | } |
||||||
| 1158 | |||||||
| 1159 | return $notModified; |
||||||
| 1160 | } |
||||||
| 1161 | |||||||
| 1162 | /** |
||||||
| 1163 | * Conversion de chaînes. Récupère le corps de la réponse sous forme de chaîne. |
||||||
| 1164 | * N'envoie *pas* d'en-têtes. |
||||||
| 1165 | * Si body est un appelable, une chaîne vide est renvoyée. |
||||||
| 1166 | */ |
||||||
| 1167 | public function __toString(): string |
||||||
| 1168 | { |
||||||
| 1169 | $this->stream->rewind(); |
||||||
|
0 ignored issues
–
show
The method
rewind() does not exist on null.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. Loading history...
|
|||||||
| 1170 | |||||||
| 1171 | return $this->stream->getContents(); |
||||||
| 1172 | } |
||||||
| 1173 | |||||||
| 1174 | /** |
||||||
| 1175 | * Créez une nouvelle réponse avec un jeu de cookies. |
||||||
| 1176 | * |
||||||
| 1177 | * ### Exemple |
||||||
| 1178 | * |
||||||
| 1179 | * ``` |
||||||
| 1180 | * // ajouter un objet cookie |
||||||
| 1181 | * $response = $response->withCookie(new Cookie('remember_me', 1)); |
||||||
| 1182 | */ |
||||||
| 1183 | public function withCookie(CookieInterface $cookie): static |
||||||
| 1184 | { |
||||||
| 1185 | 4 | $new = clone $this; |
|||||
| 1186 | 4 | $new->_cookies = $new->_cookies->add($cookie); |
|||||
| 1187 | |||||||
| 1188 | 4 | return $new; |
|||||
| 1189 | } |
||||||
| 1190 | |||||||
| 1191 | /** |
||||||
| 1192 | * Créez une nouvelle réponse avec un jeu de cookies expiré. |
||||||
| 1193 | * |
||||||
| 1194 | * ### Exemple |
||||||
| 1195 | * |
||||||
| 1196 | * ``` |
||||||
| 1197 | * // ajouter un objet cookie |
||||||
| 1198 | * $response = $response->withExpiredCookie(new Cookie('remember_me')); |
||||||
| 1199 | */ |
||||||
| 1200 | public function withExpiredCookie(CookieInterface $cookie): static |
||||||
| 1201 | { |
||||||
| 1202 | $cookie = $cookie->withExpired(); |
||||||
| 1203 | |||||||
| 1204 | $new = clone $this; |
||||||
| 1205 | $new->_cookies = $new->_cookies->add($cookie); |
||||||
| 1206 | |||||||
| 1207 | return $new; |
||||||
| 1208 | } |
||||||
| 1209 | |||||||
| 1210 | /** |
||||||
| 1211 | * Expire un cookie lors de l'envoi de la réponse. |
||||||
| 1212 | * |
||||||
| 1213 | * @param CookieInterface|string $cookie |
||||||
| 1214 | */ |
||||||
| 1215 | public function withoutCookie($cookie, ?string $path = null, ?string $domain = null) |
||||||
| 1216 | { |
||||||
| 1217 | if (is_string($cookie) && function_exists('cookie')) { |
||||||
| 1218 | $cookie = cookie($cookie, null, -2628000, compact('path', 'domain')); |
||||||
| 1219 | } |
||||||
| 1220 | |||||||
| 1221 | return $this->withExpiredCookie($cookie); |
||||||
| 1222 | } |
||||||
| 1223 | |||||||
| 1224 | /** |
||||||
| 1225 | * Lire un seul cookie à partir de la réponse. |
||||||
| 1226 | * |
||||||
| 1227 | * Cette méthode fournit un accès en lecture aux cookies en attente. Ce sera |
||||||
| 1228 | * ne lit pas l'en-tête `Set-Cookie` s'il est défini. |
||||||
| 1229 | * |
||||||
| 1230 | * @param string $name Le nom du cookie que vous souhaitez lire. |
||||||
| 1231 | * |
||||||
| 1232 | * @return array|null Soit les données du cookie, soit null |
||||||
| 1233 | */ |
||||||
| 1234 | public function getCookie(string $name): ?array |
||||||
| 1235 | { |
||||||
| 1236 | if (! $this->hasCookie($name)) { |
||||||
| 1237 | return null; |
||||||
| 1238 | } |
||||||
| 1239 | |||||||
| 1240 | 2 | return $this->_cookies->get($name)->toArray(); |
|||||
| 1241 | } |
||||||
| 1242 | |||||||
| 1243 | /** |
||||||
| 1244 | * Vérifier si la reponse contient un cookie avec le nom donné |
||||||
| 1245 | */ |
||||||
| 1246 | public function hasCookie(string $name, ?string $value = null): bool |
||||||
| 1247 | { |
||||||
| 1248 | if (! $this->_cookies->has($name)) { |
||||||
| 1249 | 4 | return false; |
|||||
| 1250 | } |
||||||
| 1251 | |||||||
| 1252 | if ($value !== null) { |
||||||
| 1253 | 2 | return $this->_cookies->get($name)->getValue() === $value; |
|||||
| 1254 | } |
||||||
| 1255 | |||||||
| 1256 | 4 | return true; |
|||||
| 1257 | } |
||||||
| 1258 | |||||||
| 1259 | /** |
||||||
| 1260 | * Obtenez tous les cookies dans la réponse. |
||||||
| 1261 | * |
||||||
| 1262 | * Renvoie un tableau associatif de nom de cookie => données de cookie. |
||||||
| 1263 | */ |
||||||
| 1264 | public function getCookies(): array |
||||||
| 1265 | { |
||||||
| 1266 | 2 | $out = []; |
|||||
| 1267 | /** @var list<Cookie> $cookies */ |
||||||
| 1268 | 2 | $cookies = $this->_cookies; |
|||||
| 1269 | |||||||
| 1270 | foreach ($cookies as $cookie) { |
||||||
| 1271 | 2 | $out[$cookie->getName()] = $cookie->toArray(); |
|||||
| 1272 | } |
||||||
| 1273 | |||||||
| 1274 | 2 | return $out; |
|||||
| 1275 | } |
||||||
| 1276 | |||||||
| 1277 | /** |
||||||
| 1278 | * Obtenez la CookieCollection à partir de la réponse |
||||||
| 1279 | */ |
||||||
| 1280 | public function getCookieCollection(): CookieCollection |
||||||
| 1281 | { |
||||||
| 1282 | 2 | return $this->_cookies; |
|||||
| 1283 | } |
||||||
| 1284 | |||||||
| 1285 | /** |
||||||
| 1286 | * Obtenez une nouvelle instance avec la collection de cookies fournie. |
||||||
| 1287 | */ |
||||||
| 1288 | public function withCookieCollection(CookieCollection $cookieCollection): static |
||||||
| 1289 | { |
||||||
| 1290 | 2 | $new = clone $this; |
|||||
| 1291 | 2 | $new->_cookies = $cookieCollection; |
|||||
| 1292 | |||||||
| 1293 | 2 | return $new; |
|||||
| 1294 | } |
||||||
| 1295 | |||||||
| 1296 | /** |
||||||
| 1297 | * Créez une nouvelle instance basée sur un fichier. |
||||||
| 1298 | * |
||||||
| 1299 | * Cette méthode augmentera à la fois le corps et un certain nombre d'en-têtes associés. |
||||||
| 1300 | * |
||||||
| 1301 | * Si `$_SERVER['HTTP_RANGE']` est défini, une tranche du fichier sera |
||||||
| 1302 | * retourné au lieu du fichier entier. |
||||||
| 1303 | * |
||||||
| 1304 | * ### Touches d'options |
||||||
| 1305 | * |
||||||
| 1306 | * - nom : autre nom de téléchargement |
||||||
| 1307 | * - download : si `true` définit l'en-tête de téléchargement et force le fichier à |
||||||
| 1308 | * être téléchargé plutôt qu'affiché en ligne. |
||||||
| 1309 | * |
||||||
| 1310 | * @param SplFileInfo|string $file Chemin d'accès absolu au fichier ou instance de \SplFileInfo. |
||||||
| 1311 | * @param array<string, mixed> $options Options Voir ci-dessus. |
||||||
| 1312 | * |
||||||
| 1313 | * @throws LoadException |
||||||
| 1314 | */ |
||||||
| 1315 | public function withFile(SplFileInfo|string $file, array $options = []): static |
||||||
| 1316 | { |
||||||
| 1317 | 2 | $file = is_string($file) ? $this->validateFile($file) : $file; |
|||||
| 1318 | $options += [ |
||||||
| 1319 | 'name' => null, |
||||||
| 1320 | 'download' => null, |
||||||
| 1321 | 2 | ]; |
|||||
| 1322 | |||||||
| 1323 | 2 | $extension = strtolower($file->getExtension()); |
|||||
| 1324 | 2 | $mapped = $this->getMimeType($extension); |
|||||
| 1325 | if ((! $extension || ! $mapped) && $options['download'] === null) { |
||||||
| 1326 | 2 | $options['download'] = true; |
|||||
| 1327 | } |
||||||
| 1328 | |||||||
| 1329 | 2 | $new = clone $this; |
|||||
| 1330 | if ($mapped) { |
||||||
| 1331 | 2 | $new = $new->withType($extension); |
|||||
| 1332 | } |
||||||
| 1333 | |||||||
| 1334 | 2 | $fileSize = $file->getSize(); |
|||||
| 1335 | if ($options['download']) { |
||||||
| 1336 | 2 | $agent = (string) env('HTTP_USER_AGENT'); |
|||||
| 1337 | |||||||
| 1338 | if ($agent && preg_match('%Opera([/ ])([0-9].[0-9]{1,2})%', $agent)) { |
||||||
| 1339 | 2 | $contentType = 'application/octet-stream'; |
|||||
| 1340 | } elseif ($agent && preg_match('/MSIE ([0-9].[0-9]{1,2})/', $agent)) { |
||||||
| 1341 | 2 | $contentType = 'application/force-download'; |
|||||
| 1342 | } |
||||||
| 1343 | |||||||
| 1344 | if (isset($contentType)) { |
||||||
| 1345 | 2 | $new = $new->withType($contentType); |
|||||
| 1346 | } |
||||||
| 1347 | 2 | $name = $options['name'] ?: $file->getFilename(); |
|||||
| 1348 | $new = $new->withDownload($name) |
||||||
| 1349 | 2 | ->withHeader('Content-Transfer-Encoding', 'binary'); |
|||||
| 1350 | } |
||||||
| 1351 | |||||||
| 1352 | 2 | $new = $new->withHeader('Accept-Ranges', 'bytes'); |
|||||
| 1353 | 2 | $httpRange = (string) env('HTTP_RANGE'); |
|||||
| 1354 | if ($httpRange !== '' && $httpRange !== '0') { |
||||||
| 1355 | 2 | $new->_fileRange($file, $httpRange); |
|||||
| 1356 | } else { |
||||||
| 1357 | 2 | $new = $new->withHeader('Content-Length', (string) $fileSize); |
|||||
| 1358 | } |
||||||
| 1359 | 2 | $new->_file = $file; |
|||||
| 1360 | 2 | $new->stream = new Stream(Utils::tryFopen($file->getPathname(), 'rb')); |
|||||
| 1361 | |||||||
| 1362 | 2 | return $new; |
|||||
| 1363 | } |
||||||
| 1364 | |||||||
| 1365 | /** |
||||||
| 1366 | * Méthode pratique pour définir une chaîne dans le corps de la réponse |
||||||
| 1367 | * |
||||||
| 1368 | * @param string|null $string La chaîne à envoyer |
||||||
| 1369 | */ |
||||||
| 1370 | public function withStringBody(?string $string): static |
||||||
| 1371 | { |
||||||
| 1372 | 2 | $new = clone $this; |
|||||
| 1373 | |||||||
| 1374 | 2 | return $new->withBody(Utils::streamFor($string)); |
|||||
| 1375 | } |
||||||
| 1376 | |||||||
| 1377 | /** |
||||||
| 1378 | * Valider qu'un chemin de fichier est un corps de réponse valide. |
||||||
| 1379 | * |
||||||
| 1380 | * @throws LoadException |
||||||
| 1381 | */ |
||||||
| 1382 | protected function validateFile(string $path): SplFileInfo |
||||||
| 1383 | { |
||||||
| 1384 | if (str_contains($path, '../') || str_contains($path, '..\\')) { |
||||||
| 1385 | 2 | throw new LoadException('The requested file contains `..` and will not be read.'); |
|||||
| 1386 | } |
||||||
| 1387 | if (! is_file($path)) { |
||||||
| 1388 | $path = APP_PATH . $path; |
||||||
| 1389 | } |
||||||
| 1390 | |||||||
| 1391 | 2 | $file = new SplFileInfo($path); |
|||||
| 1392 | if (! $file->isFile() || ! $file->isReadable()) { |
||||||
| 1393 | if (on_dev()) { |
||||||
| 1394 | throw new LoadException(sprintf('The requested file %s was not found or not readable', $path)); |
||||||
| 1395 | } |
||||||
| 1396 | |||||||
| 1397 | throw new LoadException('The requested file was not found'); |
||||||
| 1398 | } |
||||||
| 1399 | |||||||
| 1400 | 2 | return $file; |
|||||
| 1401 | } |
||||||
| 1402 | |||||||
| 1403 | /** |
||||||
| 1404 | * Obtenir le fichier actuel s'il en existe un. |
||||||
| 1405 | * |
||||||
| 1406 | * @return SplFileInfo|null Le fichier à utiliser dans la réponse ou null |
||||||
| 1407 | */ |
||||||
| 1408 | public function getFile(): ?SplFileInfo |
||||||
| 1409 | { |
||||||
| 1410 | return $this->_file; |
||||||
| 1411 | } |
||||||
| 1412 | |||||||
| 1413 | /** |
||||||
| 1414 | * Appliquez une plage de fichiers à un fichier et définissez le décalage de fin. |
||||||
| 1415 | * |
||||||
| 1416 | * Si une plage non valide est demandée, un code d'état 416 sera utilisé |
||||||
| 1417 | * dans la réponse. |
||||||
| 1418 | * |
||||||
| 1419 | * @param SplFileInfo $file Le fichier sur lequel définir une plage. |
||||||
| 1420 | * @param string $httpRange La plage à utiliser. |
||||||
| 1421 | */ |
||||||
| 1422 | protected function _fileRange(SplFileInfo $file, string $httpRange): void |
||||||
| 1423 | { |
||||||
| 1424 | $fileSize = $file->getSize(); |
||||||
| 1425 | $lastByte = $fileSize - 1; |
||||||
| 1426 | $start = 0; |
||||||
| 1427 | $end = $lastByte; |
||||||
| 1428 | |||||||
| 1429 | preg_match('/^bytes\s*=\s*(\d+)?\s*-\s*(\d+)?$/', $httpRange, $matches); |
||||||
| 1430 | if ($matches !== []) { |
||||||
| 1431 | $start = $matches[1]; |
||||||
| 1432 | $end = $matches[2] ?? ''; |
||||||
| 1433 | } |
||||||
| 1434 | |||||||
| 1435 | if ($start === '') { |
||||||
| 1436 | $start = $fileSize - (int) $end; |
||||||
| 1437 | $end = $lastByte; |
||||||
| 1438 | } |
||||||
| 1439 | if ($end === '') { |
||||||
| 1440 | $end = $lastByte; |
||||||
| 1441 | } |
||||||
| 1442 | |||||||
| 1443 | if ($start > $end || $end > $lastByte || $start > $lastByte) { |
||||||
| 1444 | $this->_setStatus(416); |
||||||
| 1445 | $this->_setHeader('Content-Range', 'bytes 0-' . $lastByte . '/' . $fileSize); |
||||||
| 1446 | |||||||
| 1447 | return; |
||||||
| 1448 | } |
||||||
| 1449 | |||||||
| 1450 | /** @psalm-suppress PossiblyInvalidOperand */ |
||||||
| 1451 | $this->_setHeader('Content-Length', (string) ($end - $start + 1)); |
||||||
| 1452 | $this->_setHeader('Content-Range', 'bytes ' . $start . '-' . $end . '/' . $fileSize); |
||||||
| 1453 | $this->_setStatus(206); |
||||||
| 1454 | /** |
||||||
| 1455 | * @var int $start |
||||||
| 1456 | * @var int $end |
||||||
| 1457 | */ |
||||||
| 1458 | $this->_fileRange = [$start, $end]; |
||||||
|
0 ignored issues
–
show
It seems like
array($start, $end) of type array<integer,integer> is incompatible with the declared type BlitzPHP\Http\list of property $_fileRange.
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property.. Loading history...
|
|||||||
| 1459 | } |
||||||
| 1460 | |||||||
| 1461 | /** |
||||||
| 1462 | * Retourne un tableau qui peut être utilisé pour décrire l'état interne de cet objet. |
||||||
| 1463 | * |
||||||
| 1464 | * @return array<string, mixed> |
||||||
| 1465 | */ |
||||||
| 1466 | public function __debugInfo(): array |
||||||
| 1467 | { |
||||||
| 1468 | return [ |
||||||
| 1469 | 'status' => $this->_status, |
||||||
| 1470 | 'contentType' => $this->getType(), |
||||||
| 1471 | 'headers' => $this->headers, |
||||||
| 1472 | 'file' => $this->_file, |
||||||
| 1473 | 'fileRange' => $this->_fileRange, |
||||||
| 1474 | 'cookies' => $this->_cookies, |
||||||
| 1475 | 'cacheDirectives' => $this->_cacheDirectives, |
||||||
| 1476 | 'body' => (string) $this->getBody(), |
||||||
| 1477 | ]; |
||||||
| 1478 | } |
||||||
| 1479 | } |
||||||
| 1480 |
The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g.
excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths