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
47 changes: 28 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ With well-established PHP HTTP libraries available, you might wonder why this on
- **HTTP Method Support**: Support for all standard HTTP methods (GET, POST, PUT, DELETE, etc.)
- **Content Type Handling**: Support for `application/json`, `application/x-www-form-urlencoded`, and `multipart/form-data`
- **Object Mapping**: Automatic mapping of request parameters to PHP objects
- **Comprehensive Testing**: Built-in testing utilities with `APITestCase` class
- **Comprehensive Testing**: Built-in testing utilities with `ServiceTestCase` class
- **Error Handling**: Structured error responses with appropriate HTTP status codes
- **Stream Support**: Custom input/output stream handling for advanced use cases

Expand Down Expand Up @@ -424,6 +424,21 @@ public function getData(int $id, ?string $name): array {
}
```

### Allowing Empty Strings

By default, sending an empty string for a string parameter results in a validation error. Use `allowEmpty: true` in the `#[RequestParam]` attribute to accept empty strings:

```php
#[PostMapping]
#[ResponseBody]
#[RequestParam(name: 'notes', type: ParamType::STRING, optional: true, allowEmpty: true)]
public function create(?string $notes): array {
return ['notes' => $notes ?? ''];
}
```

This is the attribute equivalent of `ParamOption::EMPTY => true` in the array-based approach.

### Reusable Parameter Sets

Implement the `ParameterSet` interface to group related parameters:
Expand Down Expand Up @@ -533,36 +548,30 @@ return new ResponseEntity($body, 418, 'text/plain');

## Testing

### Using APITestCase
### Using ServiceTestCase

```php
<?php
use WebFiori\Http\APITestCase;
use WebFiori\Http\Test\ServiceTestCase;

class MyServiceTest extends APITestCase {
class MyServiceTest extends ServiceTestCase {
public function testGetRequest() {
$manager = new WebServicesManager();
$manager->addService(new MyService());

$response = $this->getRequest($manager, 'my-service', [
$this->get(new MyService(), [
'param1' => 'value1',
'param2' => 'value2'
]);

$this->assertJson($response);
$this->assertContains('success', $response);
])
->assertOk()
->assertJson()
->assertBodyContains('success');
}

public function testPostRequest() {
$manager = new WebServicesManager();
$manager->addService(new MyService());

$response = $this->postRequest($manager, 'my-service', [
$this->post(new MyService(), [
'name' => 'John Doe',
'email' => 'john@example.com'
]);

$this->assertJson($response);
])
->assertOk()
->assertJson();
}
}
```
Expand Down
3 changes: 2 additions & 1 deletion WebFiori/Http/Annotations/RequestParam.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public function __construct(
public readonly mixed $filter = null,
public readonly array $allowedValues = [],
public readonly ?string $pattern = null,
public readonly ?string $message = null
public readonly ?string $message = null,
public readonly bool $allowEmpty = false
) {
}
}
8 changes: 8 additions & 0 deletions WebFiori/Http/WebService.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ public function checkMethodAuthorization(): bool {
return SecurityContext::isAuthenticated();
}

// If class has #[AllowAnonymous], allow access
if (!$this->isAuthRequired()) {
return true;
}

return $this->isAuthorized();
}

Expand Down Expand Up @@ -1641,6 +1646,9 @@ private function configureParametersFromMethod(\ReflectionMethod $method): void
if ($param->message !== null) {
$options[ParamOption::MESSAGE] = $param->message;
}
if ($param->allowEmpty) {
$options[ParamOption::EMPTY] = true;
}

$this->addParameters([
$param->name => $options
Expand Down
15 changes: 4 additions & 11 deletions examples/00-basic/01-hello-world/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'HelloService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('Hello World API Example');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new HelloService());
15 changes: 4 additions & 11 deletions examples/00-basic/02-with-parameters/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'GreetingService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('Greeting API with Parameters');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new GreetingService());
15 changes: 4 additions & 11 deletions examples/00-basic/03-multiple-methods/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'TaskService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('Task Management API');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new TaskService());
15 changes: 4 additions & 11 deletions examples/01-core/01-parameter-validation/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'ValidationService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('Parameter Validation API');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new ValidationService());
15 changes: 4 additions & 11 deletions examples/01-core/02-error-handling/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'ErrorService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('Error Handling API');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new ErrorService());
15 changes: 4 additions & 11 deletions examples/01-core/03-json-requests/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'JsonService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('JSON Request Handling API');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new JsonService());
15 changes: 4 additions & 11 deletions examples/01-core/04-file-uploads/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'UploadService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('File Upload API');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new UploadService());
7 changes: 3 additions & 4 deletions examples/01-core/06-allowed-values-and-pattern/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
require_once '../../../vendor/autoload.php';
require_once 'OrderService.php';

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

$manager = new WebServicesManager();
$manager->addService(new OrderService());
$manager->process();
$processor = new RequestProcessor();
$processor->process(new OrderService());
15 changes: 4 additions & 11 deletions examples/02-security/01-basic-auth/index.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
<?php

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

require_once '../../../vendor/autoload.php';
require_once 'BasicAuthService.php';

// Create and configure the services manager
$manager = new WebServicesManager();
$manager->setVersion('1.0.0');
$manager->setDescription('Basic Authentication API');

// Auto-discover and register services
$manager->autoDiscoverServices();

// Process the incoming request
$manager->process();
$processor = new RequestProcessor();
$processor->process(new BasicAuthService());
12 changes: 1 addition & 11 deletions examples/03-annotations/01-rest-controller/TaskService.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* - Dynamic HTTP status codes via ResponseEntity
*/
#[RestController('tasks', 'Task management service')]
#[AllowAnonymous]
class TaskService extends WebService {

private array $tasks = [
Expand All @@ -30,11 +31,9 @@ class TaskService extends WebService {

#[GetMapping]
#[ResponseBody]
#[AllowAnonymous]
#[RequestParam('task-id', ParamType::INT, true)]
public function getTask(?int $id): ResponseEntity {
if ($id === null) {
// Return all tasks
return ResponseEntity::ok(new Json(['tasks' => array_values($this->tasks)]));
}

Expand All @@ -47,7 +46,6 @@ public function getTask(?int $id): ResponseEntity {

#[PostMapping]
#[ResponseBody]
#[AllowAnonymous]
#[RequestParam('task-name', ParamType::STRING)]
#[RequestParam('task-priority', ParamType::STRING, true)]
public function createTask(string $name, ?string $priority): ResponseEntity {
Expand All @@ -63,7 +61,6 @@ public function createTask(string $name, ?string $priority): ResponseEntity {

#[DeleteMapping]
#[ResponseBody]
#[AllowAnonymous]
#[RequestParam('task-id', ParamType::INT)]
public function deleteTask(int $id): ResponseEntity {
if (!isset($this->tasks[$id])) {
Expand All @@ -72,11 +69,4 @@ public function deleteTask(int $id): ResponseEntity {

return ResponseEntity::noContent();
}

public function isAuthorized(): bool {
return true;
}

public function processRequest() {
}
}
7 changes: 3 additions & 4 deletions examples/03-annotations/01-rest-controller/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
require_once '../../../vendor/autoload.php';
require_once 'TaskService.php';

use WebFiori\Http\WebServicesManager;
use WebFiori\Http\RequestProcessor;

$manager = new WebServicesManager();
$manager->addService(new TaskService());
$manager->process();
$processor = new RequestProcessor();
$processor->process(new TaskService());
34 changes: 34 additions & 0 deletions examples/03-annotations/02-allow-empty/NotesService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php

require_once '../../../vendor/autoload.php';

use WebFiori\Http\Annotations\AllowAnonymous;
use WebFiori\Http\Annotations\PostMapping;
use WebFiori\Http\Annotations\RequestParam;
use WebFiori\Http\Annotations\ResponseBody;
use WebFiori\Http\Annotations\RestController;
use WebFiori\Http\ParamType;
use WebFiori\Http\WebService;

/**
* Demonstrates using allowEmpty in #[RequestParam] to accept empty strings.
*
* Without allowEmpty: sending notes="" results in a 422 validation error.
* With allowEmpty: true, empty strings are accepted as valid input.
*/
#[RestController('notes', 'Notes service demonstrating allowEmpty')]
#[AllowAnonymous]
class NotesService extends WebService {

#[PostMapping]
#[ResponseBody]
#[RequestParam(name: 'title', type: ParamType::STRING)]
#[RequestParam(name: 'notes', type: ParamType::STRING, optional: true, allowEmpty: true)]
public function createNote(string $title, ?string $notes): array {
return [
'title' => $title,
'notes' => $notes ?? '',
'created_at' => date('Y-m-d H:i:s'),
];
}
}
Loading
Loading