├── .gitignore ├── .php-cs-fixer.dist.php ├── .phpmd-ruleset.xml ├── .phpunit.dist.xml ├── .readme.template.md ├── LICENSE ├── README.md ├── composer.json ├── composer.lock ├── src ├── HtmxResponse.php ├── HtmxServerRequest.php └── Proxy │ ├── AbstractMessageInterfaceProxy.php │ ├── ResponseInterfaceProxy.php │ └── ServerRequestInterfaceProxy.php └── tests ├── HtmxResponseTest.php └── HtmxServerRequestTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | *.cache 3 | test.php 4 | -------------------------------------------------------------------------------- /.php-cs-fixer.dist.php: -------------------------------------------------------------------------------- 1 | setRules([ 7 | '@PSR12' => true, 8 | '@PSR12:risky' => true, 9 | '@PhpCsFixer' => true, 10 | '@PhpCsFixer:risky' => true, 11 | '@PHP80Migration' => true, 12 | '@PHP80Migration:risky' => true, 13 | 'final_internal_class' => false, 14 | 'ordered_class_elements' => [ 15 | 'sort_algorithm' => 'alpha', 16 | ], 17 | ]) 18 | ->setFinder( 19 | PhpCsFixer\Finder::create() 20 | ->in(__DIR__.'/src') 21 | ) 22 | ; 23 | -------------------------------------------------------------------------------- /.phpmd-ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | PHPMD ruleset 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /.phpunit.dist.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | tests 6 | 7 | 8 | 9 | 10 | 11 | src 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.readme.template.md: -------------------------------------------------------------------------------- 1 | # :package_project - :package_description 2 | 3 | [![PHP Version Require](http://poser.pugx.org/:package_name/require/php?style=flat-square)](https://packagist.org/packages/:package_name) [![Latest Stable Version](http://poser.pugx.org/:package_name/v?style=flat-square)](https://packagist.org/packages/:package_name) [![License](http://poser.pugx.org/:package_name/license?style=flat-square)](https://packagist.org/packages/:package_name) 4 | 5 | :package_extra_intro 6 | 7 | ## Installation 8 | Installation via composer: 9 | 10 | ```bash 11 | composer require :package_name 12 | ``` 13 | 14 | ## Usage example 15 | ```php 16 | :package_extra_example 17 | ``` 18 | 19 | ## Testing 20 | ```bash 21 | composer test 22 | ``` 23 | 24 | ## License 25 | This project is released under the :package_license License (:package_license). 26 | See [LICENSE](LICENSE) for more information. 27 | 28 | ## Documentation 29 | :documentation_toc 30 | :documentation_body 31 | 32 | *** 33 | 34 | _Generated :date using 📚[tomrf/readme-gen](https://packagist.org/packages/tomrf/readme-gen)_ 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021-2022 Tom Rune Flo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # htmx-message - PHP PSR-7 proxy classes with convenience methods for htmx requests and responses 2 | 3 | [![PHP Version Require](http://poser.pugx.org/tomrf/htmx-message/require/php?style=flat-square)](https://packagist.org/packages/tomrf/htmx-message) [![Latest Stable Version](http://poser.pugx.org/tomrf/htmx-message/v?style=flat-square)](https://packagist.org/packages/tomrf/htmx-message) [![License](http://poser.pugx.org/tomrf/htmx-message/license?style=flat-square)](https://packagist.org/packages/tomrf/htmx-message) 4 | 5 | PSR-7 `ServerRequestInterface` and `ResponseInterface` proxy classes for [`htmx`](https://htmx.org) 6 | with convenience methods for all special htmx headers (`HX-*`) in request and 7 | response objects. 8 | 9 | `HtmxResponse` is a proxy for PSR-7 response objects implementing `\Psr\Http\Message\ResponseInterface`. 10 | 11 | `HtmxServerRequest` is a proxy for PSR-7 server request objects implementing `\Psr\Http\Message\ServerRequestInterface`. 12 | 13 | You can safely wrap any `ResponseInterface` or `ServerRequestInterface` object, also those not originating from [`htmx`](htmx.org), without affecting your application. 14 | 15 | `HtmxResponse` and `HtmxServerRequest` objects retains immutability in the same fashion as one would expect from a PSR-7 `MessageInterface`, returning a new object wrapping a new `ResponseInterface` instance whenever the `HtmxResponse` is changed. 16 | 17 | Conforms to [`htmx`](https://htmx.org) version 1.9.x (latest) as per 2023-08-01. 18 | 19 | 📔 [Go to documentation](#documentation) 20 | 21 | ## Installation 22 | Installation via composer: 23 | 24 | ```bash 25 | composer require tomrf/htmx-message 26 | ``` 27 | 28 | ## Usage 29 | ```php 30 | $request = new HtmxRequest($request); // PSR-7 `ServerRequestInterface` 31 | 32 | if ($request->isHxRequest() && $request->isHxBoosted()) { 33 | $layout = 'ajax.layout'; 34 | } 35 | 36 | [...] 37 | 38 | $response = new HtmxResponse($response); // PSR-7 `ResponseInterface` 39 | 40 | $response = $response->withHxTrigger('aSimpleTrigger') 41 | ->withAddedHxTrigger('triggerWithParams', ['arg' => true, 'arg2' => 7]) 42 | ->withTriggerAfterSwap('afterSwap', time()) 43 | ->withHxPush($newUrl); 44 | 45 | // Emit $response as normal 46 | [...] 47 | ``` 48 | 49 | ## Testing 50 | ```bash 51 | composer test 52 | ``` 53 | 54 | ## License 55 | This project is released under the MIT License (MIT). 56 | See [LICENSE](LICENSE) for more information. 57 | 58 | ## Documentation 59 | - [Tomrf\HtmxMessage\HtmxResponse](#-tomrfhtmxmessagehtmxresponseclass) 60 | - [getHxPush](#gethxpush) 61 | - [getHxRedirect](#gethxredirect) 62 | - [getHxRetarget](#gethxretarget) 63 | - [getHxTrigger](#gethxtrigger) 64 | - [getHxTriggerAfterSettle](#gethxtriggeraftersettle) 65 | - [getHxTriggerAfterSwap](#gethxtriggerafterswap) 66 | - [hasHxPush](#hashxpush) 67 | - [hasHxRedirect](#hashxredirect) 68 | - [hasHxRefresh](#hashxrefresh) 69 | - [hasHxRetarget](#hashxretarget) 70 | - [hasHxTrigger](#hashxtrigger) 71 | - [hasHxTriggerAfterSettle](#hashxtriggeraftersettle) 72 | - [hasHxTriggerAfterSwap](#hashxtriggerafterswap) 73 | - [withAddedHxTrigger](#withaddedhxtrigger) 74 | - [withAddedHxTriggerAfterSettle](#withaddedhxtriggeraftersettle) 75 | - [withAddedHxTriggerAfterSwap](#withaddedhxtriggerafterswap) 76 | - [withHxPush](#withhxpush) 77 | - [withHxRedirect](#withhxredirect) 78 | - [withHxRefresh](#withhxrefresh) 79 | - [withHxRetarget](#withhxretarget) 80 | - [withHxTrigger](#withhxtrigger) 81 | - [withHxTriggerAfterSettle](#withhxtriggeraftersettle) 82 | - [withHxTriggerAfterSwap](#withhxtriggerafterswap) 83 | - [withoutHxPush](#withouthxpush) 84 | - [withoutHxRedirect](#withouthxredirect) 85 | - [withoutHxRefresh](#withouthxrefresh) 86 | - [withoutHxRetarget](#withouthxretarget) 87 | - [withoutHxTrigger](#withouthxtrigger) 88 | - [withoutHxTriggerAfterSettle](#withouthxtriggeraftersettle) 89 | - [withoutHxTriggerAfterSwap](#withouthxtriggerafterswap) 90 | - [Tomrf\HtmxMessage\HtmxServerRequest](#-tomrfhtmxmessagehtmxserverrequestclass) 91 | - [getHxCurrentUrl](#gethxcurrenturl) 92 | - [getHxPrompt](#gethxprompt) 93 | - [getHxTarget](#gethxtarget) 94 | - [getHxTrigger](#gethxtrigger) 95 | - [getHxTriggerName](#gethxtriggername) 96 | - [hasHxPrompt](#hashxprompt) 97 | - [hasHxTarget](#hashxtarget) 98 | - [hasHxTrigger](#hashxtrigger) 99 | - [hasHxTriggerName](#hashxtriggername) 100 | - [isHxBoosted](#ishxboosted) 101 | - [isHxHistoryRestoreRequest](#ishxhistoryrestorerequest) 102 | - [isHxRequest](#ishxrequest) 103 | - [__construct](#__construct) 104 | 105 | 106 | *** 107 | 108 | ### 📂 Tomrf\HtmxMessage\HtmxResponse::class 109 | 110 | HtmxResponse is a proxy for PSR-7 response objects implementing 111 | \Psr\Http\Message\ResponseInterface. 112 | 113 | You can safely wrap any ResponseInterface object, also those not 114 | responding to a htmx request, without affecting your application. 115 | 116 | HtmxResponse objects retains immutability in the same fashion as 117 | one would expect from a PSR-7 MessageInterface, returning a new object 118 | wrapping a new ResponseInterface instance whenever the HtmxResponse 119 | is changed. 120 | 121 | Example: 122 | 123 | $response = new HtmxResponse($response); 124 | $response = $response->withHxTrigger('myTrigger') 125 | ->withRedirect('/user/redirected') 126 | ->withHxPush(false); 127 | 128 | // ... 129 | 130 | #### getHxPush() 131 | 132 | ```php 133 | public function getHxPush(): string 134 | ``` 135 | 136 | #### getHxRedirect() 137 | 138 | ```php 139 | public function getHxRedirect(): string 140 | ``` 141 | 142 | #### getHxRetarget() 143 | 144 | ```php 145 | public function getHxRetarget(): string 146 | ``` 147 | 148 | #### getHxTrigger() 149 | 150 | ```php 151 | public function getHxTrigger(): array 152 | 153 | @return array 154 | ``` 155 | 156 | #### getHxTriggerAfterSettle() 157 | 158 | ```php 159 | public function getHxTriggerAfterSettle(): array 160 | 161 | @return array 162 | ``` 163 | 164 | #### getHxTriggerAfterSwap() 165 | 166 | ```php 167 | public function getHxTriggerAfterSwap(): array 168 | 169 | @return array 170 | ``` 171 | 172 | #### hasHxPush() 173 | 174 | ```php 175 | public function hasHxPush(): bool 176 | ``` 177 | 178 | #### hasHxRedirect() 179 | 180 | ```php 181 | public function hasHxRedirect(): bool 182 | ``` 183 | 184 | #### hasHxRefresh() 185 | 186 | ```php 187 | public function hasHxRefresh(): bool 188 | ``` 189 | 190 | #### hasHxRetarget() 191 | 192 | ```php 193 | public function hasHxRetarget(): bool 194 | ``` 195 | 196 | #### hasHxTrigger() 197 | 198 | ```php 199 | public function hasHxTrigger(): bool 200 | ``` 201 | 202 | #### hasHxTriggerAfterSettle() 203 | 204 | ```php 205 | public function hasHxTriggerAfterSettle(): bool 206 | ``` 207 | 208 | #### hasHxTriggerAfterSwap() 209 | 210 | ```php 211 | public function hasHxTriggerAfterSwap(): bool 212 | ``` 213 | 214 | #### withAddedHxTrigger() 215 | 216 | ```php 217 | public function withAddedHxTrigger( 218 | string $trigger, 219 | mixed $argument = null 220 | ): static 221 | ``` 222 | 223 | #### withAddedHxTriggerAfterSettle() 224 | 225 | ```php 226 | public function withAddedHxTriggerAfterSettle( 227 | string $trigger, 228 | mixed $argument = null 229 | ): static 230 | ``` 231 | 232 | #### withAddedHxTriggerAfterSwap() 233 | 234 | ```php 235 | public function withAddedHxTriggerAfterSwap( 236 | string $trigger, 237 | mixed $argument = null 238 | ): static 239 | ``` 240 | 241 | #### withHxPush() 242 | 243 | ```php 244 | public function withHxPush( 245 | string|bool $url 246 | ): static 247 | ``` 248 | 249 | #### withHxRedirect() 250 | 251 | ```php 252 | public function withHxRedirect( 253 | string $url 254 | ): static 255 | ``` 256 | 257 | #### withHxRefresh() 258 | 259 | ```php 260 | public function withHxRefresh(): static 261 | ``` 262 | 263 | #### withHxRetarget() 264 | 265 | ```php 266 | public function withHxRetarget( 267 | string $selector 268 | ): static 269 | ``` 270 | 271 | #### withHxTrigger() 272 | 273 | ```php 274 | public function withHxTrigger( 275 | string $trigger, 276 | mixed $argument = null 277 | ): static 278 | ``` 279 | 280 | #### withHxTriggerAfterSettle() 281 | 282 | ```php 283 | public function withHxTriggerAfterSettle( 284 | string $trigger, 285 | mixed $argument = null 286 | ): static 287 | ``` 288 | 289 | #### withHxTriggerAfterSwap() 290 | 291 | ```php 292 | public function withHxTriggerAfterSwap( 293 | string $trigger, 294 | mixed $argument = null 295 | ): static 296 | ``` 297 | 298 | #### withoutHxPush() 299 | 300 | ```php 301 | public function withoutHxPush(): static 302 | ``` 303 | 304 | #### withoutHxRedirect() 305 | 306 | ```php 307 | public function withoutHxRedirect(): static 308 | ``` 309 | 310 | #### withoutHxRefresh() 311 | 312 | ```php 313 | public function withoutHxRefresh(): static 314 | ``` 315 | 316 | #### withoutHxRetarget() 317 | 318 | ```php 319 | public function withoutHxRetarget(): static 320 | ``` 321 | 322 | #### withoutHxTrigger() 323 | 324 | ```php 325 | public function withoutHxTrigger(): static 326 | ``` 327 | 328 | #### withoutHxTriggerAfterSettle() 329 | 330 | ```php 331 | public function withoutHxTriggerAfterSettle(): static 332 | ``` 333 | 334 | #### withoutHxTriggerAfterSwap() 335 | 336 | ```php 337 | public function withoutHxTriggerAfterSwap(): static 338 | ``` 339 | 340 | 341 | *** 342 | 343 | ### 📂 Tomrf\HtmxMessage\HtmxServerRequest::class 344 | 345 | HtmxServerRequest is a proxy for PSR-7 server request objects implementing 346 | \Psr\Http\Message\ServerRequestInterface. 347 | 348 | You can safely wrap any ServerRequestInterface object, also those not 349 | originating from htmx, without affecting your application. 350 | 351 | Check if the request originated from htmx using the isHxRequest() method. 352 | 353 | HtmxServerRequest objects retains immutability in the same fashion as 354 | one would expect from a PSR-7 MessageInterface, returning a new object 355 | wrapping a new ServerRequestInterface instance whenever the HtmxServerRequest 356 | is changed. 357 | 358 | Example: 359 | 360 | $request = new HtmxServerRequest($request); 361 | if ($request->isHxRequest() && $request->isHxBoosted()) { 362 | // htmx request from boosted client, respond accordingly 363 | } 364 | 365 | $userPrompt = $request->getHxPrompt(); 366 | // ... 367 | 368 | #### getHxCurrentUrl() 369 | 370 | ```php 371 | public function getHxCurrentUrl(): string 372 | ``` 373 | 374 | #### getHxPrompt() 375 | 376 | ```php 377 | public function getHxPrompt(): string 378 | ``` 379 | 380 | #### getHxTarget() 381 | 382 | ```php 383 | public function getHxTarget(): string 384 | ``` 385 | 386 | #### getHxTrigger() 387 | 388 | ```php 389 | public function getHxTrigger(): string 390 | ``` 391 | 392 | #### getHxTriggerName() 393 | 394 | ```php 395 | public function getHxTriggerName(): string 396 | ``` 397 | 398 | #### hasHxPrompt() 399 | 400 | ```php 401 | public function hasHxPrompt(): bool 402 | ``` 403 | 404 | #### hasHxTarget() 405 | 406 | ```php 407 | public function hasHxTarget(): bool 408 | ``` 409 | 410 | #### hasHxTrigger() 411 | 412 | ```php 413 | public function hasHxTrigger(): bool 414 | ``` 415 | 416 | #### hasHxTriggerName() 417 | 418 | ```php 419 | public function hasHxTriggerName(): bool 420 | ``` 421 | 422 | #### isHxBoosted() 423 | 424 | ```php 425 | public function isHxBoosted(): bool 426 | ``` 427 | 428 | #### isHxHistoryRestoreRequest() 429 | 430 | ```php 431 | public function isHxHistoryRestoreRequest(): bool 432 | ``` 433 | 434 | #### isHxRequest() 435 | 436 | ```php 437 | public function isHxRequest(): bool 438 | ``` 439 | 440 | #### __construct() 441 | 442 | ```php 443 | public function __construct( 444 | Psr\Http\Message\ServerRequestInterface $message 445 | ): void 446 | ``` 447 | 448 | 449 | 450 | *** 451 | 452 | _Generated 2023-08-18T12:46:49+00:00 using 📚[tomrf/readme-gen](https://packagist.org/packages/tomrf/readme-gen)_ 453 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tomrf/htmx-message", 3 | "type": "library", 4 | "description": "PHP PSR-7 proxy classes with convenience methods for htmx requests and responses", 5 | "keywords": [ "htmx", "http-message", "psr-7", "server-request", "http-request", "http-response" ], 6 | "homepage": "http://github.com/tomrf/htmx-message", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Tom Rune Flo", 11 | "email": "tom@x86.no", 12 | "homepage": "http://github.com/tomrf/" 13 | } 14 | ], 15 | "extra": { 16 | "intro": [ 17 | "PSR-7 `ServerRequestInterface` and `ResponseInterface` proxy classes for [`htmx`](https://htmx.org)", 18 | "with convenience methods for all special htmx headers (`HX-*`) in request and", 19 | "response objects.", 20 | "", 21 | "`HtmxResponse` is a proxy for PSR-7 response objects implementing `\\Psr\\Http\\Message\\ResponseInterface`.", 22 | "", 23 | "`HtmxServerRequest` is a proxy for PSR-7 server request objects implementing `\\Psr\\Http\\Message\\ServerRequestInterface`.", 24 | "", 25 | "You can safely wrap any `ResponseInterface` or `ServerRequestInterface` object, also those not originating from [`htmx`](htmx.org), without affecting your application.", 26 | "", 27 | "`HtmxResponse` and `HtmxServerRequest` objects retains immutability in the same fashion as one would expect from a PSR-7 `MessageInterface`, returning a new object wrapping a new `ResponseInterface` instance whenever the `HtmxResponse` is changed.", 28 | "", 29 | "Conforms to [`htmx`](https://htmx.org) version 1.9.x (latest) as per 2023-11-10." 30 | ], 31 | "example": [ 32 | "$request = new HtmxRequest($request); // PSR-7 `ServerRequestInterface`", 33 | "", 34 | "if ($request->isHxRequest() && $request->isHxBoosted()) {", 35 | " $layout = 'ajax.layout';", 36 | "}", 37 | "", 38 | "[...]", 39 | "", 40 | "$response = new HtmxResponse($response); // PSR-7 `ResponseInterface`", 41 | "", 42 | "$response = $response->withHxTrigger('aSimpleTrigger')", 43 | " ->withAddedHxTrigger('triggerWithParams', ['arg' => true, 'arg2' => 7])", 44 | " ->withTriggerAfterSwap('afterSwap', time())", 45 | " ->withHxPush($newUrl);", 46 | "", 47 | "// Emit $response as normal", 48 | "[...]" 49 | ] 50 | }, 51 | "require": { 52 | "php": "^8.0", 53 | "psr/http-message": "^1.0" 54 | }, 55 | "require-dev": { 56 | "phpunit/phpunit": "^10.1", 57 | "phpstan/phpstan": "^1.10", 58 | "friendsofphp/php-cs-fixer": "^3.17", 59 | "phpmd/phpmd": "^2.12", 60 | "bmitch/churn-php": "^1.7", 61 | "roave/security-advisories": "dev-latest", 62 | "nyholm/psr7-server": "^1.0", 63 | "nyholm/psr7": "^1.8", 64 | "tomrf/http-emitter": "^0.0", 65 | "tomrf/readme-gen": "^0.0" 66 | }, 67 | "autoload": { 68 | "psr-4": { 69 | "Tomrf\\HtmxMessage\\": "src/" 70 | } 71 | }, 72 | "autoload-dev": { 73 | "psr-4": { 74 | "Tomrf\\HtmxMessage\\Test\\": "tests/" 75 | } 76 | }, 77 | "scripts": { 78 | "phpunit": [ 79 | "phpunit -c .phpunit.dist.xml --colors=auto --no-coverage --stderr" 80 | ], 81 | "cs-fixer": [ 82 | "php-cs-fixer fix --allow-risky=yes --diff --verbose src/" 83 | ], 84 | "phpstan": [ 85 | "phpstan analyze --level max src/" 86 | ], 87 | "phpmd": [ 88 | "phpmd src/ ansi .phpmd-ruleset.xml" 89 | ], 90 | "churn": [ 91 | "churn run src/ | tail -n +7" 92 | ], 93 | "test": [ 94 | "@phpunit" 95 | ], 96 | "test-coverage": [ 97 | "@putenv XDEBUG_MODE=coverage", 98 | "@phpunit --coverage-text" 99 | ], 100 | "test-coverage-html": [ 101 | "@putenv XDEBUG_MODE=coverage", 102 | "@phpunit --coverage-html .phpunit.cache/code-coverage/html", 103 | "php -S localhost:0 -t .phpunit.cache/code-coverage/html" 104 | ], 105 | "test-coverage-xml": [ 106 | "@putenv XDEBUG_MODE=coverage", 107 | "@phpunit --coverage-xml .phpunit.cache/code-coverage/xml", 108 | "ls -al .phpunit.cache/code-coverage/xml" 109 | ], 110 | "check": [ 111 | "@test", 112 | "@phpmd", 113 | "@phpstan", 114 | "@churn" 115 | ], 116 | "clean": [ 117 | "rm -rf vendor/ .phpunit.cache .php-cs-fixer.cache" 118 | ], 119 | "readme-gen": [ 120 | "readme-gen ./ ./.readme.template.md" 121 | ] 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/HtmxResponse.php: -------------------------------------------------------------------------------- 1 | withHxTrigger('myTrigger') 25 | * ->withRedirect('/user/redirected') 26 | * ->withHxPush(false); 27 | * 28 | * // ... 29 | */ 30 | class HtmxResponse extends ResponseInterfaceProxy 31 | { 32 | public function getHxPush(): string 33 | { 34 | return $this->getHeaderLine('HX-Push'); 35 | } 36 | 37 | public function getHxRedirect(): string 38 | { 39 | return $this->getHeaderLine('HX-Redirect'); 40 | } 41 | 42 | public function getHxRetarget(): string 43 | { 44 | return $this->getHeaderLine('HX-Retarget'); 45 | } 46 | 47 | /** 48 | * @return array 49 | */ 50 | public function getHxTrigger(): array 51 | { 52 | return (array) json_decode($this->getHeaderLine('HX-Trigger'), true); 53 | } 54 | 55 | /** 56 | * @return array 57 | */ 58 | public function getHxTriggerAfterSettle(): array 59 | { 60 | return (array) json_decode($this->getHeaderLine('HX-Trigger-After-Settle'), true); 61 | } 62 | 63 | /** 64 | * @return array 65 | */ 66 | public function getHxTriggerAfterSwap(): array 67 | { 68 | return (array) json_decode($this->getHeaderLine('HX-Trigger-After-Swap'), true); 69 | } 70 | 71 | // HX-Push 72 | public function hasHxPush(): bool 73 | { 74 | return 'true' === $this->getHeaderLine('HX-Push'); 75 | } 76 | 77 | // HX-Redirect 78 | public function hasHxRedirect(): bool 79 | { 80 | return $this->hasHeader('HX-Redirect'); 81 | } 82 | 83 | // HX-Refresh 84 | 85 | public function hasHxRefresh(): bool 86 | { 87 | return 'true' === $this->getHeaderLine('HX-Refresh'); 88 | } 89 | 90 | // HX-Retarget 91 | public function hasHxRetarget(): bool 92 | { 93 | return 'true' === $this->getHeaderLine('HX-Retarget'); 94 | } 95 | 96 | // HX-Trigger 97 | public function hasHxTrigger(): bool 98 | { 99 | return $this->hasHeader('HX-Trigger'); 100 | } 101 | 102 | // HX-Trigger-After-Settle 103 | public function hasHxTriggerAfterSettle(): bool 104 | { 105 | return $this->hasHeader('HX-Trigger-After-Settle'); 106 | } 107 | 108 | public function hasHxTriggerAfterSwap(): bool 109 | { 110 | return $this->hasHeader('HX-Trigger-After-Swap'); 111 | } 112 | 113 | public function withAddedHxTrigger(string $trigger, mixed $argument = null): static 114 | { 115 | $value = (array) json_decode($this->getHeaderLine('HX-Trigger'), true); 116 | $value[$trigger] = $argument; 117 | 118 | return $this->withHeader('HX-Trigger', (string) json_encode($value)); 119 | } 120 | 121 | public function withAddedHxTriggerAfterSettle(string $trigger, mixed $argument = null): static 122 | { 123 | $value = (array) json_decode($this->getHeaderLine('HX-Trigger-After-Settle'), true); 124 | $value[$trigger] = $argument; 125 | 126 | return $this->withHeader('HX-Trigger-After-Settle', (string) json_encode($value)); 127 | } 128 | 129 | public function withAddedHxTriggerAfterSwap(string $trigger, mixed $argument = null): static 130 | { 131 | $value = (array) json_decode($this->getHeaderLine('HX-Trigger-After-Settle'), true); 132 | $value[$trigger] = $argument; 133 | 134 | return $this->withHeader('HX-Trigger-After-Settle', (string) json_encode($value)); 135 | } 136 | 137 | public function withHxPush(string|bool $url): static 138 | { 139 | return $this->withHeader('HX-Push', (string) $url); 140 | } 141 | 142 | public function withHxRedirect(string $url): static 143 | { 144 | return $this->withHeader('HX-Redirect', $url); 145 | } 146 | 147 | public function withHxRefresh(): static 148 | { 149 | return $this->withHeader('HX-Refresh', 'true'); 150 | } 151 | 152 | public function withHxRetarget(string $selector): static 153 | { 154 | return $this->withHeader('HX-Retarget', $selector); 155 | } 156 | 157 | public function withHxTrigger(string $trigger, mixed $argument = null): static 158 | { 159 | return $this->withHeader('HX-Trigger', (string) json_encode([$trigger => $argument])); 160 | } 161 | 162 | public function withHxTriggerAfterSettle(string $trigger, mixed $argument = null): static 163 | { 164 | return $this->withHeader('HX-Trigger-After-Settle', (string) json_encode([$trigger => $argument])); 165 | } 166 | 167 | public function withHxTriggerAfterSwap(string $trigger, mixed $argument = null): static 168 | { 169 | return $this->withHeader('HX-Trigger-After-Settle', (string) json_encode([$trigger => $argument])); 170 | } 171 | 172 | public function withoutHxPush(): static 173 | { 174 | return $this->withoutHeader('HX-Push'); 175 | } 176 | 177 | public function withoutHxRedirect(): static 178 | { 179 | return $this->withoutHeader('HX-Redirect'); 180 | } 181 | 182 | public function withoutHxRefresh(): static 183 | { 184 | return $this->withoutHeader('HX-Refresh'); 185 | } 186 | 187 | public function withoutHxRetarget(): static 188 | { 189 | return $this->withoutHeader('HX-Retarget'); 190 | } 191 | 192 | public function withoutHxTrigger(): static 193 | { 194 | return $this->withoutHeader('HX-Trigger'); 195 | } 196 | 197 | public function withoutHxTriggerAfterSettle(): static 198 | { 199 | return $this->withoutHeader('HX-Trigger-After-Settle'); 200 | } 201 | 202 | public function withoutHxTriggerAfterSwap(): static 203 | { 204 | return $this->withoutHeader('HX-Trigger-After-Swap'); 205 | } 206 | } 207 | -------------------------------------------------------------------------------- /src/HtmxServerRequest.php: -------------------------------------------------------------------------------- 1 | isHxRequest() && $request->isHxBoosted()) { 27 | * // htmx request from boosted client, respond accordingly 28 | * } 29 | * 30 | * $userPrompt = $request->getHxPrompt(); 31 | * // ... 32 | */ 33 | class HtmxServerRequest extends ServerRequestInterfaceProxy 34 | { 35 | public function getHxCurrentUrl(): string 36 | { 37 | return $this->message->getHeaderLine('HX-Current-URL'); 38 | } 39 | 40 | public function getHxPrompt(): string 41 | { 42 | return $this->message->getHeaderLine('HX-Prompt'); 43 | } 44 | 45 | public function getHxTarget(): string 46 | { 47 | return $this->message->getHeaderLine('HX-Target'); 48 | } 49 | 50 | public function getHxTrigger(): string 51 | { 52 | return $this->message->getHeaderLine('HX-Trigger'); 53 | } 54 | 55 | public function getHxTriggerName(): string 56 | { 57 | return $this->message->getHeaderLine('HX-Trigger-Name'); 58 | } 59 | 60 | public function hasHxPrompt(): bool 61 | { 62 | return $this->message->hasHeader('HX-Prompt'); 63 | } 64 | 65 | public function hasHxTarget(): bool 66 | { 67 | return $this->message->hasHeader('HX-Target'); 68 | } 69 | 70 | public function hasHxTrigger(): bool 71 | { 72 | return $this->message->hasHeader('HX-Trigger'); 73 | } 74 | 75 | public function hasHxTriggerName(): bool 76 | { 77 | return $this->message->hasHeader('HX-Trigger-Name'); 78 | } 79 | 80 | public function isHxBoosted(): bool 81 | { 82 | return $this->message->hasHeader('HX-Boosted'); 83 | } 84 | 85 | public function isHxHistoryRestoreRequest(): bool 86 | { 87 | return $this->message->hasHeader('HX-History-Restore-Request'); 88 | } 89 | 90 | public function isHxRequest(): bool 91 | { 92 | return $this->message->hasHeader('HX-Request'); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Proxy/AbstractMessageInterfaceProxy.php: -------------------------------------------------------------------------------- 1 | message = $message; 24 | } 25 | 26 | public function getBody() 27 | { 28 | return $this->message->getBody(); 29 | } 30 | 31 | /** 32 | * @param string $name 33 | */ 34 | public function getHeader($name) 35 | { 36 | return $this->message->getHeader($name); 37 | } 38 | 39 | /** 40 | * @param string $name 41 | */ 42 | public function getHeaderLine($name) 43 | { 44 | return $this->message->getHeaderLine($name); 45 | } 46 | 47 | public function getHeaders() 48 | { 49 | return $this->message->getHeaders(); 50 | } 51 | 52 | public function getProtocolVersion() 53 | { 54 | return $this->message->getProtocolVersion(); 55 | } 56 | 57 | /** 58 | * @param string $name 59 | */ 60 | public function hasHeader($name) 61 | { 62 | return $this->message->hasHeader($name); 63 | } 64 | 65 | /** 66 | * @param string $name 67 | * @param array|string $value 68 | */ 69 | public function withAddedHeader($name, $value) 70 | { 71 | return new static($this->message->withAddedHeader($name, $value)); 72 | } 73 | 74 | public function withBody(StreamInterface $body) 75 | { 76 | return new static($this->message->withBody($body)); 77 | } 78 | 79 | /** 80 | * @param string $name 81 | * @param array|string $value 82 | */ 83 | public function withHeader($name, $value) 84 | { 85 | return new static($this->message->withHeader($name, $value)); 86 | } 87 | 88 | /** 89 | * @param string $name 90 | */ 91 | public function withoutHeader($name) 92 | { 93 | return new static($this->message->withoutHeader($name)); 94 | } 95 | 96 | /** 97 | * @param string $version 98 | */ 99 | public function withProtocolVersion($version) 100 | { 101 | return new static($this->message->withProtocolVersion($version)); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Proxy/ResponseInterfaceProxy.php: -------------------------------------------------------------------------------- 1 | message = $message; 23 | } 24 | 25 | public function getReasonPhrase() 26 | { 27 | return $this->message->getReasonPhrase(); 28 | } 29 | 30 | public function getResponse(): ResponseInterface 31 | { 32 | return $this->message; 33 | } 34 | 35 | public function getStatusCode() 36 | { 37 | return $this->message->getStatusCode(); 38 | } 39 | 40 | /** 41 | * @param int $code 42 | * @param string $reasonPhrase 43 | */ 44 | public function withStatus($code, $reasonPhrase = '') 45 | { 46 | return new static($this->message->withStatus($code, $reasonPhrase)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Proxy/ServerRequestInterfaceProxy.php: -------------------------------------------------------------------------------- 1 | message = $message; 25 | } 26 | 27 | /** 28 | * @param string $name 29 | * @param null|mixed $default 30 | */ 31 | public function getAttribute($name, $default = null) 32 | { 33 | return $this->message->getAttribute($name, $default); 34 | } 35 | 36 | /** 37 | * @return array 38 | */ 39 | public function getAttributes() 40 | { 41 | return $this->message->getAttributes(); 42 | } 43 | 44 | /** 45 | * @return array 46 | */ 47 | public function getCookieParams() 48 | { 49 | return $this->message->getCookieParams(); 50 | } 51 | 52 | public function getMethod() 53 | { 54 | return $this->message->getMethod(); 55 | } 56 | 57 | /** 58 | * @return null|array|object 59 | */ 60 | public function getParsedBody() 61 | { 62 | return $this->message->getParsedBody(); 63 | } 64 | 65 | /** 66 | * @return array 67 | */ 68 | public function getQueryParams() 69 | { 70 | return $this->message->getQueryParams(); 71 | } 72 | 73 | public function getRequest(): ServerRequestInterface 74 | { 75 | return $this->message; 76 | } 77 | 78 | public function getRequestTarget() 79 | { 80 | return $this->message->getRequestTarget(); 81 | } 82 | 83 | /** 84 | * @return array 85 | */ 86 | public function getServerParams() 87 | { 88 | return $this->message->getServerParams(); 89 | } 90 | 91 | /** 92 | * @return array 93 | */ 94 | public function getUploadedFiles() 95 | { 96 | return $this->message->getUploadedFiles(); 97 | } 98 | 99 | public function getUri() 100 | { 101 | return $this->message->getUri(); 102 | } 103 | 104 | /** 105 | * @param string $name 106 | * @param mixed $value 107 | */ 108 | public function withAttribute($name, $value) 109 | { 110 | return new static($this->message->withAttribute($name, $value)); 111 | } 112 | 113 | /** 114 | * @param array $cookies 115 | */ 116 | public function withCookieParams(array $cookies) 117 | { 118 | return new static($this->message->withCookieParams($cookies)); 119 | } 120 | 121 | /** 122 | * @param string $method 123 | */ 124 | public function withMethod($method) 125 | { 126 | return new static($this->message->withMethod($method)); 127 | } 128 | 129 | /** 130 | * @param string $name 131 | */ 132 | public function withoutAttribute($name) 133 | { 134 | return new static($this->message->withoutAttribute($name)); 135 | } 136 | 137 | /** 138 | * @param null|array|object $data 139 | */ 140 | public function withParsedBody($data) 141 | { 142 | return new static($this->message->withParsedBody($data)); 143 | } 144 | 145 | /** 146 | * @param string $version 147 | */ 148 | public function withProtocolVersion($version) 149 | { 150 | return new static($this->message->withProtocolVersion($version)); 151 | } 152 | 153 | /** 154 | * @param array $query 155 | */ 156 | public function withQueryParams(array $query) 157 | { 158 | return new static($this->message->withQueryParams($query)); 159 | } 160 | 161 | /** 162 | * @param string $requestTarget 163 | */ 164 | public function withRequestTarget($requestTarget) 165 | { 166 | return new static($this->message->withRequestTarget($requestTarget)); 167 | } 168 | 169 | /** 170 | * @param array $uploadedFiles 171 | */ 172 | public function withUploadedFiles(array $uploadedFiles) 173 | { 174 | return new static($this->message->withUploadedFiles($uploadedFiles)); 175 | } 176 | 177 | /** 178 | * @SuppressWarnings(PHPMD.BooleanArgumentFlag) 179 | * 180 | * @param bool $preserveHost 181 | */ 182 | public function withUri(UriInterface $uri, $preserveHost = false) 183 | { 184 | return new static($this->message->withUri($uri, $preserveHost)); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /tests/HtmxResponseTest.php: -------------------------------------------------------------------------------- 1 | withHxTrigger('first', 'value-first'); 25 | $response = $response->withAddedHxTrigger('added', 'value-added'); 26 | 27 | self::assertTrue($response->hasHeader('HX-Trigger')); 28 | 29 | self::assertSame( 30 | json_encode(['first' => 'value-first', 'added' => 'value-added']), 31 | $response->getHeaderLine('HX-Trigger') 32 | ); 33 | 34 | self::assertSame( 35 | ['first' => 'value-first', 'added' => 'value-added'], 36 | $response->getHxTrigger() 37 | ); 38 | } 39 | 40 | public function testHtmxAddedTriggerAndValueAndReplace(): void 41 | { 42 | $response = new HtmxResponse( 43 | new \Nyholm\Psr7\Response(), 44 | ); 45 | $response = $response->withHxTrigger('first', 'value-first'); 46 | $response = $response->withAddedHxTrigger('added', 'value-added'); 47 | $response = $response->withAddedHxTrigger('added', 'value-added-replace', true); 48 | 49 | self::assertSame( 50 | json_encode(['first' => 'value-first', 'added' => 'value-added-replace']), 51 | $response->getHeaderLine('HX-Trigger') 52 | ); 53 | } 54 | 55 | public function testHtmxPush(): void 56 | { 57 | $response = new HtmxResponse( 58 | new \Nyholm\Psr7\Response(), 59 | ); 60 | $response = $response->withHxPush('url'); 61 | 62 | self::assertTrue($response->hasHeader('HX-Push')); 63 | 64 | self::assertSame( 65 | 'url', 66 | $response->getHeaderLine('HX-Push') 67 | ); 68 | self::assertSame('url', $response->getHxPush()); 69 | } 70 | 71 | public function testHtmxRefresh(): void 72 | { 73 | $response = new HtmxResponse( 74 | new \Nyholm\Psr7\Response(), 75 | ); 76 | $response = $response->withHxRefresh(); 77 | 78 | self::assertTrue($response->hasHeader('HX-Refresh')); 79 | self::assertSame('true', $response->getHeaderLine('HX-Refresh')); 80 | } 81 | 82 | public function testHtmxRetargetWithSelector(): void 83 | { 84 | $response = new HtmxResponse( 85 | new \Nyholm\Psr7\Response(), 86 | ); 87 | $response = $response->withHxRetarget('selector'); 88 | 89 | self::assertTrue($response->hasHeader('HX-Retarget')); 90 | self::assertSame('selector', $response->getHeaderLine('HX-Retarget')); 91 | self::assertSame('selector', $response->getHxRetarget()); 92 | } 93 | 94 | public function testHtmxTrigger(): void 95 | { 96 | $response = new HtmxResponse( 97 | new \Nyholm\Psr7\Response(), 98 | ); 99 | $response = $response->withHxTrigger('test'); 100 | 101 | self::assertTrue($response->hasHeader('HX-Trigger')); 102 | 103 | self::assertSame( 104 | json_encode(['test' => null]), 105 | $response->getHeaderLine('HX-Trigger') 106 | ); 107 | 108 | self::assertSame( 109 | ['test' => null], 110 | $response->getHxTrigger() 111 | ); 112 | } 113 | 114 | public function testHtmxTriggerWithValues(): void 115 | { 116 | $response = new HtmxResponse( 117 | new \Nyholm\Psr7\Response(), 118 | ); 119 | $response = $response->withHxTrigger('test', 'value'); 120 | 121 | self::assertTrue($response->hasHeader('HX-Trigger')); 122 | 123 | self::assertSame( 124 | json_encode(['test' => 'value']), 125 | $response->getHeaderLine('HX-Trigger') 126 | ); 127 | 128 | self::assertSame( 129 | ['test' => 'value'], 130 | $response->getHxTrigger() 131 | ); 132 | } 133 | 134 | public function testHtmxTriggerWithValuesAndAdded(): void 135 | { 136 | $response = new HtmxResponse( 137 | new \Nyholm\Psr7\Response(), 138 | ); 139 | $response = $response->withHxTrigger('test', 'value'); 140 | $response = $response->withAddedHxTrigger('added', 'value-added'); 141 | 142 | self::assertSame( 143 | json_encode(['test' => 'value', 'added' => 'value-added']), 144 | $response->getHeaderLine('HX-Trigger') 145 | ); 146 | } 147 | 148 | public function testHtmxTriggerWithValuesAndAddedAndReplace(): void 149 | { 150 | $response = new HtmxResponse( 151 | new \Nyholm\Psr7\Response(), 152 | ); 153 | $response = $response->withHxTrigger('test', 'value'); 154 | $response = $response->withAddedHxTrigger('added', 'value-added'); 155 | $response = $response->withAddedHxTrigger('added', 'value-added-replace', true); 156 | 157 | self::assertSame( 158 | json_encode(['test' => 'value', 'added' => 'value-added-replace']), 159 | $response->getHeaderLine('HX-Trigger') 160 | ); 161 | } 162 | 163 | public function testHtmxTriggerWithValuesAndReplace(): void 164 | { 165 | $response = new HtmxResponse( 166 | new \Nyholm\Psr7\Response(), 167 | ); 168 | $response = $response->withHxTrigger('test', 'value'); 169 | $response = $response->withHxTrigger('test', 'value-replace', true); 170 | 171 | self::assertSame( 172 | json_encode(['test' => 'value-replace']), 173 | $response->getHeaderLine('HX-Trigger') 174 | ); 175 | } 176 | 177 | public function testHtmxTriggerWithValuesAndReplaceAndAdded(): void 178 | { 179 | $response = new HtmxResponse( 180 | new \Nyholm\Psr7\Response(), 181 | ); 182 | $response = $response->withHxTrigger('test', 'value'); 183 | $response = $response->withHxTrigger('test', 'value-replace', true); 184 | $response = $response->withAddedHxTrigger('added', 'value-added'); 185 | 186 | self::assertSame( 187 | json_encode(['test' => 'value-replace', 'added' => 'value-added']), 188 | $response->getHeaderLine('HX-Trigger') 189 | ); 190 | } 191 | 192 | public function testNewInstance(): void 193 | { 194 | $response = new HtmxResponse( 195 | new \Nyholm\Psr7\Response(), 196 | ); 197 | self::assertInstanceOf(HtmxResponse::class, $response); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /tests/HtmxServerRequestTest.php: -------------------------------------------------------------------------------- 1 | withoutHeader('HX-Request'))) 40 | ->isHxRequest() 41 | ); 42 | } 43 | 44 | public function testNewInstanceFromServerRequest(): void 45 | { 46 | $serverRequest = self::createServerRequest(); 47 | $htmxRequest = new HtmxServerRequest($serverRequest); 48 | 49 | self::assertInstanceOf(ServerRequestInterface::class, $serverRequest); 50 | self::assertInstanceOf(ServerRequestInterface::class, $htmxRequest); 51 | 52 | // assert isHxRequest 53 | self::assertTrue($htmxRequest->isHxRequest()); 54 | 55 | // assert request method 56 | self::assertSame('GET', $serverRequest->getMethod()); 57 | self::assertSame($serverRequest->getMethod(), $htmxRequest->getMethod()); 58 | 59 | // assert request uri path 60 | self::assertSame('/', $serverRequest->getUri()->getPath()); 61 | self::assertSame($serverRequest->getUri(), $htmxRequest->getUri()); 62 | 63 | // assert protocol version change 64 | self::assertSame('1.1', $htmxRequest->getProtocolVersion()); 65 | $newRequest = $htmxRequest->withProtocolVersion('1.0'); 66 | self::assertSame('1.0', $newRequest->getProtocolVersion()); 67 | self::assertNotSame($htmxRequest, $newRequest); 68 | 69 | // assert adding attribute 70 | $newRequest = $htmxRequest->withAttribute('attr', 123); 71 | self::assertSame(123, $newRequest->getAttribute('attr')); 72 | self::assertNotSame($htmxRequest, $newRequest); 73 | 74 | // assert manipulating body content 75 | $newRequest->getBody()->write('foo'); 76 | $newRequest->getBody()->rewind(); 77 | self::assertSame('foo', $newRequest->getBody()->getContents()); 78 | } 79 | 80 | public function testNewInstanceFromServerRequestWithBody(): void 81 | { 82 | $serverRequest = self::createServerRequest('POST', '/', 'foo'); 83 | $htmxRequest = new HtmxServerRequest($serverRequest); 84 | 85 | self::assertInstanceOf(ServerRequestInterface::class, $serverRequest); 86 | self::assertInstanceOf(ServerRequestInterface::class, $htmxRequest); 87 | 88 | // assert isHxRequest 89 | self::assertTrue($htmxRequest->isHxRequest()); 90 | 91 | // assert request method 92 | self::assertSame('POST', $serverRequest->getMethod()); 93 | self::assertSame($serverRequest->getMethod(), $htmxRequest->getMethod()); 94 | 95 | // assert request uri path 96 | self::assertSame('/', $serverRequest->getUri()->getPath()); 97 | self::assertSame($serverRequest->getUri(), $htmxRequest->getUri()); 98 | 99 | // assert protocol version change 100 | self::assertSame('1.1', $htmxRequest->getProtocolVersion()); 101 | $newRequest = $htmxRequest->withProtocolVersion('1.0'); 102 | self::assertSame('1.0', $newRequest->getProtocolVersion()); 103 | self::assertNotSame($htmxRequest, $newRequest); 104 | 105 | // assert adding attribute 106 | $newRequest = $htmxRequest->withAttribute('attr', 123); 107 | self::assertSame(123, $newRequest->getAttribute('attr')); 108 | self::assertNotSame($htmxRequest, $newRequest); 109 | } 110 | 111 | public function testNewInstanceFromServerRequestWithBodyAndQueryParams(): void 112 | { 113 | $serverRequest = self::createServerRequest('POST', '/?foo=bar', 'foo=bar'); 114 | $htmxRequest = new HtmxServerRequest($serverRequest); 115 | 116 | self::assertInstanceOf(ServerRequestInterface::class, $serverRequest); 117 | self::assertInstanceOf(ServerRequestInterface::class, $htmxRequest); 118 | 119 | // assert isHxRequest 120 | self::assertTrue($htmxRequest->isHxRequest()); 121 | 122 | // assert request method 123 | self::assertSame('POST', $serverRequest->getMethod()); 124 | self::assertSame($serverRequest->getMethod(), $htmxRequest->getMethod()); 125 | 126 | // assert request uri path 127 | self::assertSame('/', $serverRequest->getUri()->getPath()); 128 | self::assertSame($serverRequest->getUri(), $htmxRequest->getUri()); 129 | 130 | // assert protocol version change 131 | self::assertSame('1.1', $htmxRequest->getProtocolVersion()); 132 | $newRequest = $htmxRequest->withProtocolVersion('1.0'); 133 | self::assertSame('1.0', $newRequest->getProtocolVersion()); 134 | self::assertNotSame($htmxRequest, $newRequest); 135 | 136 | // assert adding attribute 137 | $newRequest = $htmxRequest->withAttribute('attr', 123); 138 | self::assertSame(123, $newRequest->getAttribute('attr')); 139 | self::assertNotSame($htmxRequest, $newRequest); 140 | } 141 | 142 | private static function createServerRequest(string $method = 'GET', string $uri = '/', ?string $body = null): ServerRequestInterface 143 | { 144 | return self::$serverRequestCreator->fromArrays( 145 | [ 146 | 'REQUEST_METHOD' => $method, 147 | 'REQUEST_URI' => $uri, 148 | 'SERVER_PROTOCOL' => '1.1', 149 | 'SERVER_NAME' => 'localhost', 150 | 'SERVER_PORT' => '80', 151 | 'REMOTE_ADDR' => '::1', 152 | 'REMOTE_PORT' => '58981', 153 | 'SERVER_ADDR' => '::1', 154 | ], 155 | [ 156 | 'HX-Request' => 'true', 157 | 'Accept' => 'text/html', 158 | 'Accept-Charset' => 'utf-8', 159 | 'Accept-Language' => 'en-US,en;q=0.9', 160 | 'Cache-Control' => 'no-cache', 161 | 'User-Agent' => 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36', 162 | ], 163 | $_COOKIE, 164 | $_GET, 165 | $_POST, 166 | $_FILES, 167 | $body 168 | ); 169 | } 170 | } 171 | --------------------------------------------------------------------------------