Serverless PHP: deploy no AWS Lambda ou Bref

1. Introdução ao Serverless PHP

A computação serverless representa uma mudança de paradigma no desenvolvimento backend. Em vez de gerenciar servidores, você escreve código que é executado sob demanda em infraestrutura gerenciada pela nuvem. Para o ecossistema PHP, isso abre possibilidades interessantes: escalabilidade automática, pagamento por execução (redução de custos em aplicações com tráfego variável) e eliminação da sobrecarga operacional de manter servidores web.

No entanto, o modelo serverless traz desafios específicos. O PHP tradicional foi projetado para ambientes com estado persistente (como Apache ou Nginx com PHP-FPM). Em ambientes serverless como AWS Lambda, cada execução é efêmera e stateless. Além disso, cold starts (inicialização a frio) podem impactar a latência, e há limites rígidos de tempo de execução (15 minutos no Lambda) e tamanho de pacote (250 MB).

2. Arquitetura Serverless para PHP

A arquitetura serverless para PHP segue o modelo FaaS (Function as a Service). O AWS Lambda executa funções PHP em resposta a eventos. Para aplicações web, o API Gateway atua como ponto de entrada HTTP, roteando requisições para funções Lambda específicas.

O ciclo de vida de uma requisição típica:
1. Cliente faz requisição HTTP ao API Gateway
2. API Gateway converte a requisição em um evento JSON
3. Lambda executa o runtime PHP com o handler definido
4. O handler processa o evento e retorna uma resposta
5. API Gateway converte a resposta de volta para HTTP

Esse modelo elimina a necessidade de um servidor web persistente, mas exige que o código PHP seja adaptado para funcionar em ambiente stateless.

3. Bref: O Framework Serverless para PHP

Bref é o framework serverless mais maduro para PHP no ecossistema AWS. Ele resolve a incompatibilidade fundamental entre PHP e Lambda fornecendo um runtime PHP otimizado e ferramentas de deploy.

Instalação e Configuração Inicial

composer require bref/bref

Após instalar, você precisa configurar o arquivo serverless.yml na raiz do projeto:

service: minha-api-php

provider:
  name: aws
  region: us-east-1
  runtime: provided.al2

plugins:
  - ./vendor/bref/bref

functions:
  api:
    handler: public/index.php
    runtime: php-81
    events:
      - httpApi: '*'

package:
  patterns:
    - '!node_modules/**'
    - '!tests/**'

Estrutura de um Projeto Bref

Um projeto Bref típico contém:
- serverless.yml: configuração de deploy
- public/index.php: ponto de entrada da aplicação
- vendor/: dependências gerenciadas pelo Composer
- Handlers específicos para cada função Lambda

4. Deploy no AWS Lambda com Bref

Configuração do serverless.yml

O arquivo serverless.yml define funções, eventos e recursos:

functions:
  api:
    handler: public/index.php
    runtime: php-81
    environment:
      DB_HOST: ${env:DB_HOST}
      DB_NAME: ${env:DB_NAME}
    events:
      - httpApi:
          method: GET
          path: /users
      - httpApi:
          method: POST
          path: /users
      - schedule:
          rate: rate(5 minutes)
          enabled: true

Deploy Manual

# Instalar dependências
composer install --no-dev --optimize-autoloader

# Deploy
serverless deploy --stage production --region us-east-1

# Variáveis de ambiente
serverless deploy --stage production --env DB_HOST=meu-host.rds.amazonaws.com

Gerenciamento de Dependências

O Bref empacota automaticamente o diretório vendor/ junto com o código. Para otimizar o tamanho do pacote:

composer install --no-dev --optimize-autoloader --classmap-authoritative

5. Criando Handlers e Rotas

Handler Básico

<?php
// public/index.php

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

use Bref\Context\Context;
use Bref\Event\Http\HttpRequestEvent;
use Bref\Event\Http\HttpResponse;

return function (HttpRequestEvent $event, Context $context): HttpResponse {
    $method = $event->getMethod();
    $path = $event->getPath();

    if ($method === 'GET' && $path === '/health') {
        return new HttpResponse('OK', ['Content-Type' => 'text/plain'], 200);
    }

    return new HttpResponse('Not Found', [], 404);
};

Integração com Laravel

composer require bref/laravel-bridge
<?php
// public/index.php

use Bref\LaravelBridge\BrefServiceProvider;

$app = require __DIR__ . '/../bootstrap/app.php';
$app->register(BrefServiceProvider::class);

return $app;

Rotas Personalizadas com JSON

<?php
// src/Handlers/UserHandler.php

namespace App\Handlers;

use Bref\Context\Context;
use Bref\Event\Http\HttpRequestEvent;
use Bref\Event\Http\HttpResponse;

class UserHandler
{
    public function handle(HttpRequestEvent $event, Context $context): HttpResponse
    {
        $users = [
            ['id' => 1, 'name' => 'Alice'],
            ['id' => 2, 'name' => 'Bob']
        ];

        return new HttpResponse(
            json_encode($users),
            ['Content-Type' => 'application/json'],
            200
        );
    }
}

6. Banco de Dados e Estado

Conexão com RDS MySQL

<?php
// src/Database/Connection.php

namespace App\Database;

class Connection
{
    private static ?\PDO $instance = null;

    public static function get(): \PDO
    {
        if (self::$instance === null) {
            self::$instance = new \PDO(
                sprintf(
                    'mysql:host=%s;dbname=%s',
                    getenv('DB_HOST'),
                    getenv('DB_NAME')
                ),
                getenv('DB_USER'),
                getenv('DB_PASSWORD'),
                [\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION]
            );
        }
        return self::$instance;
    }
}

DynamoDB para Escalabilidade

<?php
use Aws\DynamoDb\DynamoDbClient;

$dynamoDb = new DynamoDbClient([
    'region' => getenv('AWS_REGION'),
    'version' => 'latest'
]);

$result = $dynamoDb->putItem([
    'TableName' => 'Users',
    'Item' => [
        'id' => ['S' => uniqid()],
        'name' => ['S' => 'Alice'],
        'created_at' => ['N' => (string) time()]
    ]
]);

7. Monitoramento, Logs e Otimização

Logs Estruturados com Monolog

<?php
use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('app');
$logger->pushHandler(new StreamHandler('php://stderr', Logger::INFO));

$logger->info('Requisição processada', [
    'path' => $event->getPath(),
    'method' => $event->getMethod(),
    'duration_ms' => round((microtime(true) - $start) * 1000)
]);

Métricas no CloudWatch

O Bref envia automaticamente métricas para CloudWatch:
- Duration: tempo de execução em milissegundos
- Invocations: número de invocações
- Errors: erros não tratados
- ColdStart: indica se foi uma inicialização a frio

Otimização de Cold Starts

functions:
  api:
    handler: public/index.php
    runtime: php-81
    provisionedConcurrency: 2  # Mantém 2 instâncias sempre quentes
    layers:
      - ${bref:layer.php-81}  # Usa layer otimizado do Bref

8. Casos de Uso e Boas Práticas

Aplicações Típicas

  • APIs REST: endpoints leves com alto tráfego variável
  • Webhooks: processamento de eventos de serviços externos
  • Processamento de Filas: integração com SQS para jobs assíncronos
  • Backends para SPAs: servindo dados para aplicações React/Vue

Segurança

provider:
  iam:
    role:
      statements:
        - Effect: Allow
          Action:
            - dynamodb:GetItem
            - dynamodb:PutItem
          Resource: arn:aws:dynamodb:us-east-1:*:table/Users
        - Effect: Allow
          Action:
            - s3:GetObject
          Resource: arn:aws:s3:::meu-bucket/*

Limitações a Considerar

  • Tempo máximo de execução: 15 minutos (configurável até 900 segundos)
  • Tamanho do pacote: 250 MB (incluindo layers)
  • Memória máxima: 10 GB
  • Cold starts: podem levar de 1 a 5 segundos em PHP

Referências