spatial/core

Spatial API Core Contains Attributes

Installs: 452

Dependents: 3

Suggesters: 0

Security: 0

Stars: 3

Watchers: 2

Forks: 0

Open Issues: 0

pkg:composer/spatial/core


README

The core package of the Spatial PHP Framework - a modern, attribute-based web framework for building high-performance APIs with PHP 8.2+ and OpenSwoole.

PHP Version License

Features

  • 🚀 High Performance - Built for OpenSwoole async server
  • 🎯 Attribute-Based Routing - Define routes with PHP 8 attributes
  • 📦 Modular Architecture - Angular-inspired module system
  • 🔄 CQRS Ready - Built-in Mediator pattern support
  • Validation Attributes - Declarative DTO validation
  • 🛠️ CLI Tools - Code generators for rapid development
  • 🔐 Authorization - Attribute-based auth guards
  • 📝 PSR-7 Compliant - Standard HTTP message interfaces

Installation

composer require spatial/core

Quick Start

1. Create a Controller

<?php

use Spatial\Core\Attributes\ApiController;
use Spatial\Core\Attributes\Route;
use Spatial\Core\ControllerBase;
use Spatial\Common\HttpAttributes\HttpGet;
use Spatial\Common\HttpAttributes\HttpPost;
use Spatial\Common\BindSourceAttributes\FromBody;
use Psr\Http\Message\ResponseInterface;

#[ApiController]
#[Route('api/[controller]')]
class UsersController extends ControllerBase
{
    #[HttpGet]
    public function index(): ResponseInterface
    {
        return $this->ok(['users' => []]);
    }

    #[HttpGet('{id:int}')]
    public function show(int $id): ResponseInterface
    {
        return $this->ok(['id' => $id]);
    }

    #[HttpPost]
    public function create(#[FromBody] CreateUserDto $dto): ResponseInterface
    {
        if ($error = $this->validate($dto)) {
            return $error; // Returns 400 with validation errors
        }
        return $this->created($dto);
    }
}

2. Create a DTO with Validation

<?php

use Spatial\Common\ValidationAttributes\Required;
use Spatial\Common\ValidationAttributes\Email;
use Spatial\Common\ValidationAttributes\MinLength;
use Spatial\Common\ValidationAttributes\MaxLength;

class CreateUserDto
{
    #[Required]
    #[Email]
    public string $email;
    
    #[Required]
    #[MinLength(8)]
    public string $password;
    
    #[MaxLength(100)]
    public ?string $name = null;
}

3. Create a Module

<?php

use Spatial\Core\Attributes\ApiModule;

#[ApiModule(
    imports: [],
    declarations: [UsersController::class],
    providers: [UserService::class]
)]
class AppModule
{
    public function configure(ApplicationBuilderInterface $app): void
    {
        $app->useRouting();
        $app->useEndpoints(fn($endpoints) => $endpoints->mapControllers());
    }
}

4. Bootstrap the Application

<?php
// public/index.php

require __DIR__ . '/../vendor/autoload.php';

$app = new \Spatial\Core\App();
$app->boot(\Presentation\AppModule::class);

Core Components

Attributes

Attribute Target Description
#[ApiModule] Class Defines an application module
#[ApiController] Class Marks a class as an API controller
#[Route('path')] Class/Method URL route template
#[HttpGet], #[HttpPost], etc. Method HTTP verb mapping
#[FromBody], #[FromQuery] Parameter Request data binding
#[Authorize] Class/Method Authorization requirement

Response Helpers

ControllerBase provides convenient response methods:

$this->ok($data);                    // 200 OK
$this->created($data, $location);    // 201 Created
$this->noContent();                  // 204 No Content
$this->badRequest($errors);          // 400 Bad Request
$this->notFound($message);           // 404 Not Found
$this->unauthorized($message);       // 401 Unauthorized
$this->forbidden($message);          // 403 Forbidden
$this->json($data, $status);         // Custom JSON response
$this->validate($dto);               // Validate DTO, returns error or null

Validation Attributes

Declarative validation for DTOs:

Attribute Description Example
#[Required] Not null/empty #[Required]
#[Email] Valid email format #[Email]
#[MinLength(n)] Minimum length #[MinLength(8)]
#[MaxLength(n)] Maximum length #[MaxLength(255)]
#[Range(min, max)] Numeric bounds #[Range(0, 100)]
#[Pattern(regex)] Regex match #[Pattern('/^\d+$/')]

Usage in Controller:

#[HttpPost]
public function create(#[FromBody] CreateUserDto $dto): ResponseInterface
{
    if ($error = $this->validate($dto)) {
        return $error; // Automatic 400 response with errors
    }
    // DTO is valid, continue...
}

Route Caching

In production, routes are automatically cached for faster boot times:

// Cache is stored in: var/cache/routes.cache.php
// Clear cache with CLI: php spatial cache:clear

Caster - JSON/Array to Object Hydration

The Caster class provides powerful JSON/array to PHP object hydration with type safety.

Basic Usage

use Spatial\Common\Helper\Caster;

// Cast JSON string to object
$dto = Caster::cast(CreateUserDto::class, '{"email":"user@example.com"}');

// Cast array to object
$dto = Caster::cast(CreateUserDto::class, ['email' => 'user@example.com']);

// Cast array of items
$users = Caster::castMany(UserDto::class, $usersArray);

With Validation

// Enable auto-validation globally
Caster::configure(['autoValidate' => true]);
$dto = Caster::cast(CreateUserDto::class, $data); // Throws if invalid

// Validate on specific cast
$dto = Caster::cast(CreateUserDto::class, $data, validate: true);

// Try cast (returns result instead of throwing)
$result = Caster::tryCast(CreateUserDto::class, $data);
if (!$result->isValid()) {
    return $this->badRequest($result->getErrors());
}
$dto = $result->object;

Property Name Mapping

Map between JSON naming conventions and PHP property names:

// JSON uses snake_case, PHP uses camelCase
Caster::configure(['nameStrategy' => 'snake_case']);

// {"user_name": "John", "created_at": "2024-01-01"} 
// → $dto->userName, $dto->createdAt
$dto = Caster::cast(UserDto::class, $jsonString);

Supported Types

Type Description
Scalars int, float, string, bool, array
Objects Nested object hydration (recursive)
Enums Backed enums and unit enums
DateTime DateTime, DateTimeImmutable (from string or timestamp)
Nullable ?Type and union types with null
Union Types int|string (tries each type)

DateTime Handling

class EventDto
{
    public DateTime $startDate;
    public DateTimeImmutable $createdAt;
}

// From ISO string
$dto = Caster::cast(EventDto::class, [
    'startDate' => '2024-12-25T10:00:00Z',
    'createdAt' => '2024-01-01'
]);

// From Unix timestamp
$dto = Caster::cast(EventDto::class, [
    'startDate' => 1703505600,
    'createdAt' => 1704067200
]);

Enum Handling

enum Status: string {
    case Active = 'active';
    case Inactive = 'inactive';
}

class ProductDto
{
    public Status $status;
}

// Cast backed enum
$dto = Caster::cast(ProductDto::class, ['status' => 'active']);
echo $dto->status->value; // 'active'

CLI Tool

Spatial includes a CLI tool for code generation:

# Show help
php spatial --help

# Generate a CRUD controller
php spatial make:controller Product

# Generate a CQRS command with handler
php spatial make:command CreateOrder --domain=Orders

# Generate a CQRS query with handler
php spatial make:query GetProducts --domain=Products

# Generate an API module
php spatial make:module IdentityModule

# Generate a DTO with validation
php spatial make:dto CreateProductDto --domain=Products

# List all registered routes
php spatial route:list

# Clear cached routes
php spatial cache:clear

Generated Code Examples

Controller (make:controller User):

#[ApiController]
#[Route('[area]/[controller]')]
class UserController extends ControllerBase
{
    #[HttpGet]
    public function index(): ResponseInterface { ... }
    
    #[HttpGet('{id:int}')]
    public function show(int $id): ResponseInterface { ... }
    
    #[HttpPost]
    public function create(#[FromBody] array $data): ResponseInterface { ... }
    
    #[HttpPut('{id:int}')]
    public function update(int $id, #[FromBody] array $data): ResponseInterface { ... }
    
    #[HttpDelete('{id:int}')]
    public function delete(int $id): ResponseInterface { ... }
}

Architecture

Package Structure

spatial-core/src/
├── core/                           # Core framework
│   ├── App.php                     # Application bootstrap
│   ├── ControllerBase.php          # Base controller class
│   ├── ConfigurationLoader.php     # YAML config loading
│   ├── ModuleRegistrar.php         # Module registration
│   ├── RouteTableBuilder.php       # Route table construction
│   ├── RouteTableRenderer.php      # API docs generation
│   ├── RouteCache.php              # Production route caching
│   ├── AppHandler.php              # PSR-15 request handler
│   ├── ApplicationBuilder.php      # App configuration
│   ├── Attributes/                 # Core attributes
│   └── Interfaces/                 # Interfaces
│
├── common/                         # Shared utilities
│   ├── HttpAttributes/             # HTTP verb attributes
│   ├── BindSourceAttributes/       # Parameter binding
│   ├── ValidationAttributes/       # Validation rules
│   ├── Helper/                     # Utilities
│   │   └── Caster.php              # JSON/Array hydration
│   └── Middleware/                 # Built-in middlewares
│
├── router/                         # Routing system
│   ├── RouterModule.php            # Route resolution
│   └── RouteBuilder.php            # Route configuration
│
├── swoole/                         # OpenSwoole integration
│   └── BridgeManager.php           # Swoole ↔ PSR-7 bridge
│
├── console/                        # CLI tools
│   ├── Application.php             # CLI application
│   ├── CommandInterface.php        # Command interface
│   ├── AbstractCommand.php         # Base command
│   └── Commands/                   # Built-in commands
│
└── telemetry/                      # OpenTelemetry integration

Request Flow

Client Request
     ↓
OpenSwoole Server
     ↓
BridgeManager (Swoole → PSR-7)
     ↓
App.process()
     ↓
MiddlewareProcessor (Auth, CORS, etc.)
     ↓
AppHandler (Route matching)
     ↓
RouterModule (Controller invocation)
     ↓
Caster (JSON → DTO hydration)
     ↓
Controller → Mediator → Handler
     ↓
ResponseInterface
     ↓
BridgeManager (PSR-7 → Swoole)
     ↓
Client Response

Configuration

YAML Configuration

# config/packages/framework.yaml
enableProdMode: false
cors:
  allowedOrigins: ['*']
  allowedMethods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
  allowedHeaders: ['Content-Type', 'Authorization']

Environment Variables

Use %env(VAR_NAME)% syntax in YAML:

# config/packages/doctrine.yaml
database:
  host: '%env(DB_HOST)%'
  user: '%env(DB_USER)%'
  password: '%env(DB_PASSWORD)%'

Advanced Features

Authorization Guards

use Spatial\Core\Attributes\Authorize;

#[ApiController]
#[Authorize(JwtGuard::class)]  // Controller-level auth
class AdminController extends ControllerBase
{
    #[HttpGet]
    #[Authorize(AdminGuard::class)]  // Method-level auth
    public function dashboard(): ResponseInterface
    {
        return $this->ok(['admin' => true]);
    }
}

Custom Validation Attributes

use Spatial\Common\ValidationAttributes\ValidationAttribute;
use Spatial\Common\ValidationAttributes\ValidationResult;

#[Attribute(Attribute::TARGET_PROPERTY)]
class UniqueEmail extends ValidationAttribute
{
    public function validate(mixed $value, string $propertyName): ValidationResult
    {
        // Check database for uniqueness
        $exists = $this->checkEmailExists($value);
        
        if ($exists) {
            return ValidationResult::failure(
                "Email already exists",
                $propertyName
            );
        }
        
        return ValidationResult::success();
    }
    
    protected function getDefaultMessage(string $propertyName): string
    {
        return "The {$propertyName} must be unique.";
    }
}

CQRS with Mediator

// In controller
#[HttpGet]
public function index(): ResponseInterface
{
    return $this->mediator->process(new GetUsersQuery());
}

#[HttpPost]
public function create(#[FromBody] CreateUserDto $dto): ResponseInterface
{
    return $this->mediator->process(new CreateUserCommand($dto));
}

Requirements

  • PHP 8.2+
  • OpenSwoole extension
  • Composer

Dependencies

  • php-di/php-di - Dependency injection
  • guzzlehttp/psr7 - PSR-7 implementation
  • symfony/yaml - YAML parsing

License

MIT License - see LICENSE for details.

Contributing

Contributions are welcome! Please read the contributing guidelines before submitting PRs.

Credits

Created by the Spatial Framework Team.