- Visão Geral da Arquitetura
- Sistema de Components
- Helpers Especializados
- Sistema de Segurança
- Bridge Pattern Implementation
- Request Lifecycle
- Performance & Monitoring
- Testing Architecture
- Production Guidelines
- Troubleshooting
src/
├── Adapter/ # Adaptadores PSR-7 (deprecated na v0.1.0)
├── Bridge/ # Conversores Request/Response
├── Commands/ # Comandos console Symfony
├── Helpers/ # ⭐ NOVO: Sistema de helpers especializados
├── Middleware/ # ⭐ NOVO: Middleware de segurança
├── Monitoring/ # ⭐ NOVO: Sistema de monitoramento
├── Providers/ # Service Providers
├── Security/ # ⭐ NOVO: Componentes de segurança
└── Server/ # Servidor ReactPHP principal
graph TD
A[ReactPHP Request] --> B[RequestBridge]
B --> C[GlobalStateHelper.backup]
C --> D[Setup $_POST & $_SERVER]
D --> E[PivotPHP Request.createFromGlobals]
E --> F[SecurityMiddleware]
F --> G[PivotPHP Application]
G --> H[Route Handler]
H --> I[PivotPHP Response]
I --> J[ResponseBridge]
J --> K[ReactPHP Response]
K --> L[GlobalStateHelper.restore]
Servidor principal que orquestra toda a integração:
class ReactServer
{
private Application $application;
private RequestBridge $requestBridge;
private ResponseBridge $responseBridge;
private LoopInterface $loop;
public function handleRequest(ServerRequestInterface $request): Promise
{
// Conversão PSR-7 -> PivotPHP com estado global
$psrRequest = $this->requestBridge->convertFromReact($request);
// Setup global state para compatibilidade PivotPHP
$originalPost = $_POST;
$originalServer = $_SERVER;
try {
// Configurar globals que PivotPHP espera
$_SERVER['REQUEST_METHOD'] = $psrRequest->getMethod();
$_POST = $psrRequest->getParsedBody() ?? [];
// Criar PivotPHP Request usando factory method
$pivotRequest = Request::createFromGlobals();
// Processar através da aplicação
$pivotResponse = $this->application->handle($pivotRequest);
} finally {
// Sempre restaurar estado original
$_POST = $originalPost;
$_SERVER = $originalServer;
}
return $this->responseBridge->convertToReact($pivotResponse);
}
}Converte requisições ReactPHP para PivotPHP com suporte completo a POST:
class RequestBridge
{
public function convertFromReact(ServerRequestInterface $reactRequest): ServerRequestInterface
{
// Crucial: Rewind stream antes de ler
$bodyStream = $reactRequest->getBody();
$bodyStream->rewind();
$bodyContents = (string) $bodyStream;
// Parse automático baseado em Content-Type
if (stripos($contentType, 'application/json') !== false) {
$decoded = json_decode($bodyContents, true);
if (json_last_error() === JSON_ERROR_NONE) {
$request = $request->withParsedBody($decoded);
}
}
return $request;
}
}Converte respostas PivotPHP para ReactPHP:
class ResponseBridge
{
public function convertToReact(ResponseInterface $pivotResponse): ReactResponse
{
// Conversão de headers usando HeaderHelper
$headers = HeaderHelper::convertPsrToArray($pivotResponse->getHeaders());
// Streaming detection automática
if ($this->isStreamingResponse($pivotResponse)) {
return $this->convertToReactStream($pivotResponse);
}
return new ReactResponse(
$pivotResponse->getStatusCode(),
$headers,
(string) $pivotResponse->getBody()
);
}
}Centraliza processamento de headers HTTP:
class HeaderHelper
{
// Converte headers PSR-7 para array simples
public static function convertPsrToArray(array $headers): array
{
$result = [];
foreach ($headers as $name => $values) {
$result[$name] = self::normalizeHeaderValue($values);
}
return $result;
}
// Normaliza valores de header (array|string -> string)
public static function normalizeHeaderValue(mixed $values): string
{
if (is_array($values)) {
return implode(', ', $values);
}
return (string) $values;
}
// Headers de segurança para produção
public static function getSecurityHeaders(bool $isProduction = false): array
{
$headers = [
'X-Content-Type-Options' => 'nosniff',
'X-Frame-Options' => 'DENY',
'X-XSS-Protection' => '1; mode=block',
];
if ($isProduction) {
$headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains';
}
return $headers;
}
}Operações JSON type-safe:
class JsonHelper
{
public static function encode(mixed $data, string $fallback = '{}'): string
{
try {
$result = json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE);
return $result !== false ? $result : $fallback;
} catch (JsonException) {
return $fallback;
}
}
public static function decode(string $json): ?array
{
try {
$result = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
return is_array($result) ? $result : null;
} catch (JsonException) {
return null;
}
}
}Criação padronizada de respostas:
class ResponseHelper
{
public static function createErrorResponse(
int $statusCode,
string $message,
array $details = [],
?string $errorId = null
): ResponseInterface {
$errorId = $errorId ?? uniqid('err_', true);
$body = [
'error' => [
'message' => $message,
'code' => $statusCode,
'error_id' => $errorId,
'timestamp' => date('c'),
]
];
if (!empty($details)) {
$body['error']['details'] = $details;
}
return (new Response($statusCode))
->withHeader('Content-Type', 'application/json')
->withBody(new Stream(JsonHelper::encode($body)));
}
}Gerenciamento seguro de estado global:
class GlobalStateHelper
{
public static function backup(): array
{
return [
'server' => $_SERVER ?? [],
'post' => $_POST ?? [],
'get' => $_GET ?? [],
'cookie' => $_COOKIE ?? [],
'session' => $_SESSION ?? [],
];
}
public static function restore(array $backup): void
{
$_SERVER = $backup['server'] ?? [];
$_POST = $backup['post'] ?? [];
$_GET = $backup['get'] ?? [];
$_COOKIE = $backup['cookie'] ?? [];
$_SESSION = $backup['session'] ?? [];
}
public static function getSafeServerVars(): array
{
$safe = $_SERVER;
// Remove informações sensíveis
$sensitiveKeys = [
'HTTP_AUTHORIZATION', 'HTTP_X_API_KEY', 'PHP_AUTH_PW',
'HTTP_COOKIE', 'HTTP_X_FORWARDED_FOR'
];
foreach ($sensitiveKeys as $key) {
if (isset($safe[$key])) {
$safe[$key] = '[REDACTED]';
}
}
return $safe;
}
}Análise e identificação de requisições:
class RequestHelper
{
public static function getClientIp(
ServerRequestInterface $request,
bool $trustProxies = false
): string {
// Headers prioritários para detecção de IP
$headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_REAL_IP', // Nginx
'HTTP_X_FORWARDED_FOR', // Load balancers
'HTTP_CLIENT_IP', // Proxies
'REMOTE_ADDR' // Direct connection
];
foreach ($headers as $header) {
$ip = $request->getServerParams()[$header] ?? null;
if ($ip && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
return $ip;
}
}
return $request->getServerParams()['REMOTE_ADDR'] ?? 'unknown';
}
public static function getClientIdentifier(
ServerRequestInterface $request,
bool $includeUserAgent = true
): string {
$ip = self::getClientIp($request);
$identifier = "ip:{$ip}";
if ($includeUserAgent) {
$userAgent = $request->getHeaderLine('User-Agent');
$identifier .= '|ua:' . md5($userAgent);
}
return $identifier;
}
}Middleware principal de segurança:
class SecurityMiddleware
{
private RequestIsolationInterface $isolation;
private MemoryGuard $memoryGuard;
private RuntimeBlockingDetector $blockingDetector;
public function handle(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next
) {
// Criar contexto isolado para a requisição
$contextId = $this->isolation->createContext($request);
try {
// Monitorar memória antes da requisição
$this->memoryGuard->startMonitoring();
// Detectar código bloqueante
$this->blockingDetector->startDetection();
// Processar requisição
$result = $next($request, $response);
// Verificar integridade após processamento
$this->isolation->checkContextLeaks($contextId);
return $result;
} finally {
// Sempre limpar contexto
$this->isolation->destroyContext($contextId);
$this->memoryGuard->stopMonitoring();
$this->blockingDetector->stopDetection();
}
}
}Isolamento completo entre requisições:
class RequestIsolation implements RequestIsolationInterface
{
private array $contexts = [];
private StaticPropertyGuard $staticGuard;
public function createContext(ServerRequestInterface $request): string
{
$contextId = uniqid('ctx_', true);
// Backup estado atual
$this->contexts[$contextId] = [
'globals' => GlobalStateHelper::backup(),
'static_properties' => $this->staticGuard->backup(),
'created_at' => microtime(true),
'request_id' => $request->getHeaderLine('X-Request-ID') ?: $contextId,
];
return $contextId;
}
public function destroyContext(string $contextId): void
{
if (!isset($this->contexts[$contextId])) {
return;
}
$context = $this->contexts[$contextId];
// Restaurar estado
GlobalStateHelper::restore($context['globals']);
$this->staticGuard->restore($context['static_properties']);
unset($this->contexts[$contextId]);
}
}Monitoramento de memória em tempo real:
class MemoryGuard
{
private array $thresholds = [
'warning' => 67108864, // 64MB
'critical' => 134217728, // 128MB
];
private array $memoryHistory = [];
private ?callable $leakCallback = null;
public function startMonitoring(): void
{
$this->memoryHistory = [];
$initial = memory_get_usage(true);
$this->memoryHistory[] = ['time' => microtime(true), 'memory' => $initial];
}
public function checkMemoryUsage(): array
{
$current = memory_get_usage(true);
$peak = memory_get_peak_usage(true);
$stats = [
'current' => $current,
'peak' => $peak,
'limit' => ini_get('memory_limit'),
'percentage' => ($current / $this->parseMemoryLimit()) * 100,
];
// Alertas automáticos
if ($current > $this->thresholds['critical']) {
$this->triggerCriticalAlert($stats);
} elseif ($current > $this->thresholds['warning']) {
$this->triggerWarningAlert($stats);
}
return $stats;
}
}Detecção de código bloqueante:
class BlockingCodeDetector
{
private array $blockingFunctions = [
'sleep', 'usleep', 'time_nanosleep',
'file_get_contents', 'fopen', 'fread',
'curl_exec', 'mysqli_query', 'pg_query',
'redis_connect', 'memcache_connect'
];
public function analyzeCode(string $code): array
{
$parser = new Parser(new PhpParser\Lexer\Emulative());
$ast = $parser->parse($code);
$visitor = new BlockingCodeVisitor($this->blockingFunctions);
$traverser = new NodeTraverser();
$traverser->addVisitor($visitor);
$traverser->traverse($ast);
return $visitor->getBlockingCalls();
}
public function startRuntimeDetection(): void
{
// Monitor function calls em runtime
$this->registerTickFunction();
}
}// ReactPHP ServerRequest -> PivotPHP Request
$reactRequest = new React\Http\Message\ServerRequest('POST', '/api/data');
// 1. RequestBridge converte para PSR-7 compatível
$psrRequest = $requestBridge->convertFromReact($reactRequest);
// 2. ReactServer configura estado global
$_POST = $psrRequest->getParsedBody() ?? [];
$_SERVER['REQUEST_METHOD'] = $psrRequest->getMethod();
// 3. PivotPHP Request criado via factory
$pivotRequest = \PivotPHP\Core\Http\Request::createFromGlobals();
// 4. Request agora tem acesso ao body via $request->body
$data = $pivotRequest->body; // stdClass com dados JSON parseados// PivotPHP Response -> ReactPHP Response
$pivotResponse = (new Response())->json(['data' => $data]);
// 1. ResponseBridge detecta tipo de resposta
if ($this->isStreamingResponse($pivotResponse)) {
return $this->convertToReactStream($pivotResponse);
}
// 2. Conversão padrão
$headers = HeaderHelper::convertPsrToArray($pivotResponse->getHeaders());
$body = (string) $pivotResponse->getBody();
return new React\Http\Message\Response(
$pivotResponse->getStatusCode(),
$headers,
$body
);sequenceDiagram
participant C as Cliente
participant R as ReactPHP
participant RB as RequestBridge
participant GH as GlobalStateHelper
participant RS as ReactServer
participant SM as SecurityMiddleware
participant PA as PivotPHP App
participant RH as Route Handler
participant ReB as ResponseBridge
C->>R: HTTP Request
R->>RB: ServerRequest
RB->>RB: Stream rewind + JSON parse
RB->>RS: PSR-7 Request
RS->>GH: backup()
RS->>RS: Setup $_POST, $_SERVER
RS->>PA: Request::createFromGlobals()
PA->>SM: handle()
SM->>SM: createContext()
SM->>RH: Invoke route
RH->>RH: Access $request->body
RH->>PA: Response
PA->>ReB: PivotPHP Response
ReB->>R: ReactPHP Response
R->>GH: restore()
R->>C: HTTP Response
- Stream Rewind: Essencial para leitura correta do body
- Global State: Backup/restore para isolamento
- Factory Method:
createFromGlobals()ao invés de constructor - Context Creation: Isolamento de segurança por requisição
- Memory Monitoring: Controle contínuo de vazamentos
class HealthMonitor
{
public function getHealthStatus(): array
{
return [
'status' => 'healthy',
'timestamp' => date('c'),
'metrics' => [
'memory' => $this->getMemoryMetrics(),
'requests' => $this->getRequestMetrics(),
'errors' => $this->getErrorMetrics(),
'performance' => $this->getPerformanceMetrics(),
],
'checks' => [
'database' => $this->checkDatabase(),
'cache' => $this->checkCache(),
'storage' => $this->checkStorage(),
]
];
}
private function getMemoryMetrics(): array
{
return [
'current_usage' => memory_get_usage(true),
'peak_usage' => memory_get_peak_usage(true),
'limit' => ini_get('memory_limit'),
'percentage' => $this->calculateMemoryPercentage(),
];
}
}// Logging automático por requisição
$this->logger->info('Request handled', [
'method' => $request->getMethod(),
'uri' => (string) $request->getUri(),
'status' => $response->getStatusCode(),
'duration_ms' => round($duration, 2),
'memory' => memory_get_usage(true),
'client_ip' => RequestHelper::getClientIp($request),
]);abstract class TestCase extends BaseTestCase
{
protected function setUp(): void
{
parent::setUp();
// Configurar controle de output
$this->configureTestOutputControl();
// Setup factories PSR-7
$this->setupPsr7Factories();
// Criar aplicação de teste
$this->app = $this->createApplication();
}
private function configureTestOutputControl(): void
{
// Definir constante de teste para PivotPHP Core
if (!defined('PHPUNIT_TESTSUITE')) {
define('PHPUNIT_TESTSUITE', true);
}
// Buffer de output para capturar saídas inesperadas
if (ob_get_level() === 0) {
ob_start();
}
}
protected function withoutOutput(callable $callback): mixed
{
ob_start();
try {
return $callback();
} finally {
ob_end_clean();
}
}
}class MockHelper
{
public static function createMockRequest(
string $method = 'GET',
string $uri = '/',
array $headers = [],
string $body = ''
): ServerRequestInterface {
return (new ServerRequest($method, new Uri($uri), $headers))
->withBody(new Stream($body));
}
public static function createMockLogger(): LoggerInterface
{
return new class implements LoggerInterface {
public function log($level, string|\Stringable $message, array $context = []): void {
// Mock implementation
}
// ... outros métodos
};
}
}class AssertionHelper
{
public static function assertJsonResponse(
ResponseInterface $response,
int $expectedStatus = 200,
?array $expectedData = null
): void {
Assert::assertEquals($expectedStatus, $response->getStatusCode());
Assert::assertEquals('application/json', $response->getHeaderLine('Content-Type'));
$body = JsonHelper::decode((string) $response->getBody());
Assert::assertNotNull($body, 'Response should contain valid JSON');
if ($expectedData !== null) {
Assert::assertEquals($expectedData, $body);
}
}
}// config/reactphp.php
return [
'server' => [
'debug' => false,
'streaming' => true,
'max_concurrent_requests' => 1000,
'request_body_size_limit' => 16777216, // 16MB
'request_body_buffer_size' => 8192, // 8KB
],
'security' => [
'enable_request_isolation' => true,
'enable_memory_guard' => true,
'enable_blocking_detection' => true,
'memory_limit_warning' => 134217728, // 128MB
'memory_limit_critical' => 268435456, // 256MB
],
'monitoring' => [
'enable_health_checks' => true,
'metrics_retention_hours' => 24,
'alert_thresholds' => [
'response_time_ms' => 1000,
'error_rate_percent' => 5,
'memory_usage_percent' => 80,
],
],
];[program:pivotphp-reactphp]
command=php /var/www/artisan serve:reactphp --host=0.0.0.0 --port=8080
directory=/var/www
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/pivotphp-reactphp.log
environment=APP_ENV=production,APP_DEBUG=falseupstream pivotphp_backend {
server 127.0.0.1:8080;
server 127.0.0.1:8081;
server 127.0.0.1:8082;
server 127.0.0.1:8083;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://pivotphp_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}# Diagnóstico
composer test -- --filter testServerHandlesPostRequest
# Verificar logs
tail -f /var/log/pivotphp-reactphp.logSolução: Verificar se o RequestBridge está fazendo stream rewind e se o ReactServer está configurando $_POST corretamente.
# Monitorar memória
watch -n 1 'ps aux | grep "serve:reactphp"'
# Analisar com MemoryGuard
$guard = new MemoryGuard();
$guard->startMonitoring();Solução: Usar RequestIsolation e verificar cleanup de contextos.
// Enable em desenvolvimento
$detector = new BlockingCodeDetector();
$detector->startRuntimeDetection();
// Verificar código suspeito
$blockingCalls = $detector->analyzeCode($sourceCode);// Usar TestCase::withoutOutput()
$result = $this->withoutOutput(function() {
return $this->server->handleRequest($request);
});$app->get('/health', function($req, $res) {
$monitor = new HealthMonitor();
return $res->json($monitor->getHealthStatus());
});$app->get('/metrics', function($req, $res) {
$memoryGuard = app(MemoryGuard::class);
$stats = $memoryGuard->getStats();
return $res->json([
'memory' => $stats,
'requests' => $this->getRequestStats(),
'performance' => $this->getPerformanceStats(),
]);
});- PivotPHP Core Documentation
- ReactPHP Documentation
- PSR-7 HTTP Message Interface
- PSR-15 HTTP Server Request Handlers
🎯 Esta documentação reflete a implementação real da v0.1.0, testada e validada em produção.