Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/claude-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ class UserController extends Controller
```

**Key points:**
- Each HTTP method gets a handler: `handleGET()`, `handlePOST()`, `handlePUT()`, `handleDELETE()`, `handlePATCH()`, `handleHEAD()`, `handleOPTIONS()`
- Each HTTP method gets a handler: `handleGET()`, `handlePOST()`, `handlePUT()`, `handleDELETE()`, `handlePATCH()`, `handleHEAD()`, `handleOPTIONS()`, `handleQUERY()`
- URL parameters are passed as `$args` array
- Always return `ResponseInterface`
- Use global helpers (`response()`, `responseJson()`) for response construction
Expand Down
6 changes: 4 additions & 2 deletions .claude/llm-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ Routes are **never** defined in PHP. Instead, use JSON files:
- `groups`: organize routes by prefix with optional middleware
- `routes`: root-level routes
- URL parameters use `:paramName` syntax (e.g., `/:id`, `/:slug`)
- `methods` array specifies HTTP verbs (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
- `methods` array specifies HTTP verbs (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, QUERY)

See `doc/Routing.md` for full specification.

Expand Down Expand Up @@ -201,7 +201,9 @@ class UserController extends Controller
}
```

**Handler methods:** `handleGET()`, `handlePOST()`, `handlePUT()`, `handleDELETE()`, `handlePATCH()`, `handleHEAD()`, `handleOPTIONS()`
**Handler methods:** `handleGET()`, `handlePOST()`, `handlePUT()`, `handleDELETE()`, `handlePATCH()`, `handleHEAD()`, `handleOPTIONS()`, `handleQUERY()`

> `handleQUERY()` handles the QUERY verb — safe + idempotent like GET but with a request body. Read it via `getRequest()->getBody()`.

**Conventions:**
- `$args` contains URL parameters as array keys (e.g., `['id' => '123']`)
Expand Down
1 change: 1 addition & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ doc/ # Feature documentation
```php
public function handleGET(array $args): ResponseInterface { }
public function handlePOST(array $args): ResponseInterface { }
public function handleQUERY(array $args): ResponseInterface { } // safe+idempotent like GET, with body
private function validateInput(array $data): bool { }
```
- **Variables** — camelCase
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.41
0.0.42
7 changes: 7 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
# Changelog
SPIN Framework Changelog

## 0.0.42
- **Feature:** HTTP `QUERY` method support — controllers gain a first-class `handleQUERY()` handler and `QUERY` is in the default route method set. QUERY is safe + idempotent like GET but carries a request body (read via `getRequest()->getBody()`). See IETF `draft-ietf-httpbis-safe-method-w-body` and OpenAPI 3.2.

## 0.0.41
- **Fix:** Redis adapter `get()` returned `false` for integer values stored via `set()` — Predis always returns strings, so the `is_int()` fast-path never matched and the raw integer was passed to `unserialize()`. `get()` now detects raw integers via `filter_var()` and uses an explicit null check, so falsy stored values are no longer mistaken for cache misses (#78)
- **Test:** Add `testValueRoundTrip` data-provider regression tests to the Redis and APCu adapter suites

## 0.0.40
- **Feature:** Add domain exception classes: `CacheException`, `ConfigException`, `DatabaseException`, `MiddlewareException` — all extending `SpinException`
- **Feature:** Wire `CacheException` into cache adapters (APCu, Redis), `ConfigException` into `Config`, `DatabaseException` into `PdoConnection` and `ConnectionManager`
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "celarius/spin-framework",
"description": "A super lightweight PHP UI/REST Framework",
"version": "0.0.41",
"version": "0.0.42",
"keywords": [
"php8",
"php-framework",
Expand Down
3 changes: 2 additions & 1 deletion doc/Contributor-Guide/Architecture-Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ abstract class Controller
public function handlePOST(array $args): ResponseInterface
public function handlePUT(array $args): ResponseInterface
public function handleDELETE(array $args): ResponseInterface
// ... other HTTP methods
public function handleQUERY(array $args): ResponseInterface
// ... other HTTP methods (PATCH, HEAD, OPTIONS)
}
```

Expand Down
2 changes: 1 addition & 1 deletion doc/Getting-Started/Project-Structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ Never commit `.env` to version control. Add `.env` to `.gitignore`.
| Config files | `config{-env}.json` | `config.json`, `config-dev.json` |
| Route files | `routes{-env}.json` | `routes.json`, `routes-dev.json` |
| Classes | `{Name}` (PascalCase) | `TaskService`, `SessionManager` |
| Methods | `handle{METHOD}` | `handleGET`, `handlePOST` |
| Methods | `handle{METHOD}` | `handleGET`, `handlePOST`, `handleQUERY` |
| Namespaces | `App\{Area}` | `App\Controllers`, `App\Middlewares` |

## PSR-4 Autoloading
Expand Down
3 changes: 3 additions & 0 deletions doc/Reference/API-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ Base controller class providing HTTP method handlers and convenient access to fr
| `handleDELETE()` | `public function handleDELETE(array $args): ResponseInterface` | Handle DELETE requests |
| `handleHEAD()` | `public function handleHEAD(array $args): ResponseInterface` | Handle HEAD requests |
| `handleOPTIONS()` | `public function handleOPTIONS(array $args): ResponseInterface` | Handle OPTIONS requests |
| `handleQUERY()` | `public function handleQUERY(array $args): ResponseInterface` | Handle QUERY requests (safe + idempotent like GET, with a request body) |

> **QUERY** carries a request body — read it via `getRequest()->getBody()`. See IETF `draft-ietf-httpbis-safe-method-w-body` and OpenAPI 3.2.

**Usage Example:**

Expand Down
3 changes: 3 additions & 0 deletions doc/User-Guide/Routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ SPIN controllers use specific method names to handle different HTTP requests:
- `handlePUT(array $args)` - Handles PUT requests
- `handleDELETE(array $args)` - Handles DELETE requests
- `handlePATCH(array $args)` - Handles PATCH requests
- `handleQUERY(array $args)` - Handles QUERY requests

> **QUERY** is safe and idempotent like GET but carries a request body. Read it via `getRequest()->getBody()` (same as POST). See IETF `draft-ietf-httpbis-safe-method-w-body` and OpenAPI 3.2.

### Controller Parameters

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "spin-framework",
"title": "Spin Framework",
"version": "0.0.41",
"version": "0.0.42",
"homepage": "https://github.com/Celarius/spin-framework",
"description": "A super lightweight PHP UI/REST Framework",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class AuthMiddleware extends Middleware

### 🛣️ JSON-Based Routing
- **Route Groups** - Organize routes with shared middleware and prefixes
- **HTTP Method Support** - Full support for GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
- **HTTP Method Support** - Full support for GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS, QUERY
- **Dynamic Parameters** - Capture URL parameters with `{paramName}` syntax
- **Middleware Integration** - Apply middleware at common, group, or route level

Expand Down
17 changes: 17 additions & 0 deletions src/Core/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public function handle(array $args)
case "DELETE" : return $this->handleDELETE($args);
case "HEAD" : return $this->handleHEAD($args);
case "OPTIONS": return $this->handleOPTIONS($args);
case "QUERY" : return $this->handleQUERY($args);
default : return $this->handleCUSTOM($args);
}
}
Expand Down Expand Up @@ -155,6 +156,22 @@ public function handleOPTIONS(array $args)
return \response('',405);
}

/**
* Handle QUERY request
*
* QUERY is safe and idempotent like GET but carries a request body. Read the
* body via getRequest()->getBody() (same as POST). See IETF
* draft-ietf-httpbis-safe-method-w-body and OpenAPI 3.2.
*
* @param array $args Path variable arguments as name=value pairs
*
* @return Response Value returned by $app->run()
*/
public function handleQUERY(array $args)
{
return \response('',405);
}

/**
* Handle custom request
*
Expand Down
11 changes: 11 additions & 0 deletions src/Core/ControllerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,17 @@ public function handleHEAD(array $args);
*/
public function handleOPTIONS(array $args);

/**
* Handle QUERY request
*
* QUERY is safe and idempotent like GET but carries a request body.
*
* @param array $args Path variable arguments as name=value pairs
*
* @return bool Value returned by $app->run()
*/
public function handleQUERY(array $args);

/**
* Handle custom request
*
Expand Down
2 changes: 1 addition & 1 deletion src/Core/RouteGroup.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function __construct(array $definition)
# Method extraction

# Default to ALL
$methods = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS'];
$methods = ['GET','POST','PUT','PATCH','DELETE','HEAD','OPTIONS','QUERY'];

if (isset($route['methods'])) {
$methods = $route['methods'];
Expand Down
53 changes: 53 additions & 0 deletions tests/Core/ControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types=1);

/**
* This file is part of the spin-framework
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @package spin-framework
*/

namespace Tests\Core;

use PHPUnit\Framework\TestCase;
use Spin\Core\Controller;

/**
* Tests for the base Controller HTTP verb handlers, focused on the QUERY method.
*/
class ControllerTest extends TestCase
{
/**
* Base handleQUERY() returns 405 Method Not Allowed when not overridden,
* mirroring every other default verb handler.
*/
public function testHandleQueryDefaultsTo405(): void
{
$controller = new class extends Controller {};

$response = $controller->handleQUERY([]);

$this->assertSame(405, $response->getStatusCode());
}

/**
* A controller overriding handleQUERY() returns its own response — proving
* QUERY is a first-class, dispatchable verb.
*/
public function testHandleQueryCanBeOverridden(): void
{
$controller = new class extends Controller {
public function handleQUERY(array $args)
{
return \response('ok', 200);
}
};

$response = $controller->handleQUERY([]);

$this->assertSame(200, $response->getStatusCode());
$this->assertSame('ok', (string) $response->getBody());
}
}