devitools / serendipity
The Hyperf missing component
Installs: 1 130
Dependents: 0
Suggesters: 0
Security: 0
Stars: 2
Watchers: 3
Forks: 0
Open Issues: 0
Requires
- php: ^8.3
- ext-ds: *
- ext-json: *
- ext-mongodb: *
- ext-pdo: *
- ext-swoole: *
- devitools/constructo: ^1.4.4
- fakerphp/faker: ^1.24
- google/cloud-logging: ^1.32
- guzzlehttp/guzzle: ^7.9
- guzzlehttp/psr7: ^2.7
- hyperf/cache: ~3.1.0
- hyperf/collection: ^3.1
- hyperf/command: ^3.1
- hyperf/config: ~3.1.0
- hyperf/config-apollo: ~3.1.0
- hyperf/config-center: ^3.1
- hyperf/constants: ~3.1.0
- hyperf/context: ^3.1
- hyperf/contract: ^3.1
- hyperf/coordinator: ^3.1
- hyperf/coroutine: ^3.1
- hyperf/database: ^3.1
- hyperf/database-pgsql: ^3.1
- hyperf/db: ^3.1
- hyperf/db-connection: ^3.1
- hyperf/di: 3.1.*
- hyperf/elasticsearch: ~3.1.0
- hyperf/engine: ^2.10
- hyperf/event: ^3.1
- hyperf/exception-handler: ^3.1
- hyperf/framework: 3.1.*
- hyperf/guzzle: ~3.1.0
- hyperf/http-message: ^3.1
- hyperf/http-server: ^3.1
- hyperf/json-rpc: ~3.1.0
- hyperf/logger: ~3.1.0
- hyperf/memory: ~3.1.0
- hyperf/model-cache: ~3.1.0
- hyperf/process: ~3.1.0
- hyperf/rpc: ~3.1.0
- hyperf/rpc-client: ~3.1.0
- hyperf/rpc-server: ~3.1.0
- hyperf/service-governance: ~3.1.0
- hyperf/support: ^3.1
- hyperf/tracer: ~3.1.0
- hyperf/validation: ^3.1
- jawira/case-converter: ^3.5
- mongodb/mongodb: ^1.19
- monolog/monolog: ^3.8
- mustache/mustache: ^2.14
- php-ds/php-ds: ^1.5
- psr/container: ^2.0
- psr/event-dispatcher: ^1.0
- psr/http-client: ^1.0
- psr/http-message: ^2.0
- psr/http-server-handler: ^1.0
- psr/http-server-middleware: ^1.0
- psr/log: ^3.0
- rakibtg/sleekdb: ^2.15
- sentry/sentry: ^4.11
- swow/psr7-plus: ^1.1
- symfony/console: ^7.2
Requires (Dev)
- bnf/phpstan-psr-container: ^1.1
- deptrac/deptrac: ^3.0
- friendsofphp/php-cs-fixer: ^3.0
- hyperf/devtool: ~3.1.0
- hyperf/testing: ~3.1.0
- php-mock/php-mock-phpunit: ^2.12
- phpmd/phpmd: ^2.15
- phpstan/phpstan: ^2
- phpunit/phpunit: ^10.5
- rector/rector: ^2
- roave/security-advisories: dev-latest
- robiningelbrecht/phpunit-pretty-print: ^1.3
- squizlabs/php_codesniffer: ^3.11
- swoole/ide-helper: ^5.0
- vimeo/psalm: ^5.26
- dev-main
- 1.16.1
- 1.16.0
- 1.15.5
- 1.15.4
- 1.15.3
- 1.15.2
- 1.15.1
- 1.15.0
- 1.14.11
- 1.14.10
- 1.14.9
- 1.14.8
- 1.14.7
- 1.14.6
- 1.14.5
- 1.14.4
- 1.14.3
- 1.14.2
- 1.14.1
- 1.14.0
- 1.13.5
- 1.13.4
- 1.13.3
- 1.13.2
- 1.13.1
- 1.13.0
- 1.12.7
- 1.12.6
- 1.12.5
- 1.12.4
- 1.12.3
- 1.12.2
- 1.12.1
- 1.12.0
- 1.11.2
- 1.11.1
- 1.11.0
- 1.10.11
- 1.10.10
- 1.10.9
- 1.10.8
- 1.10.7
- 1.10.6
- 1.10.5
- 1.10.4
- 1.10.3
- 1.10.2
- 1.10.1
- 1.10.0
- 1.9.1
- 1.9.0
- 1.8.6
- 1.8.5
- 1.8.4
- 1.8.3
- 1.8.2
- 1.8.1
- 1.8.0
- 1.7.1
- 1.7.0
- 1.6.1
- 1.6.0
- 1.5.2
- 1.5.1
- 1.5.0
- 1.4.6
- 1.4.5
- 1.4.4
- 1.4.3
- 1.4.2
- 1.4.1
- 1.4.0
- 1.3.0
- 1.2.1
- 1.2.0
- 1.1.9
- 1.1.8
- 1.1.7
- 1.1.6
- 1.1.5
- 1.1.4
- 1.1.3
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0.10
- 1.0.9
- 1.0.8
- 1.0.7
- 1.0.6
- 1.0.5
- 1.0.4
- 1.0.3
- 1.0.2
- 1.0.0
This package is auto-updated.
Last update: 2025-08-08 14:56:28 UTC
README
Serendipity
O componente que faltava no Hyperf
Serendipity é uma biblioteca PHP que estende o framework Hyperf com funcionalidades avançadas de Domain-Driven Design ( DDD), validação inteligente, serialização automática e infraestrutura robusta para aplicações de alta performance.
🍿 Visão Geral
Serendipity preenche as lacunas do ecossistema Hyperf, oferecendo uma camada de abstração poderosa que combina os melhores padrões de desenvolvimento com a performance assíncrona do Hyperf. Utilizando o Constructo como base, oferece metaprogramação avançada para resolver dependências e formatar dados de forma flexível.
Principais Características
- 🏗️ Arquitetura DDD: Estrutura completa seguindo Domain-Driven Design
- ⚡ Assíncrono por Padrão: Totalmente compatível com corrotinas do Hyperf
- 🔍 Validação Inteligente: Sistema de validação baseado em atributos e regras
- 📊 Serialização Automática: Conversão inteligente de entidades para diferentes formatos
- 🎯 Type Safety: Tipagem forte com suporte a generics
- 🧪 Testabilidade: Ferramentas completas para testes unitários e de integração
- 📈 Observabilidade: Logging estruturado e monitoramento integrado
🚀 Instalação
Pré-requisitos
- PHP 8.3+
- Extensões: ds, json, mongodb, pdo, swoole
- Hyperf 3.1+
- Docker 25+ (para desenvolvimento)
- Docker Compose 2.23+
Instalação via Composer
composer require devitools/serendipity
Configuração Básica
Registre o ConfigProvider no seu config/config.php
:
<?php return [ 'providers' => [ Serendipity\ConfigProvider::class, ], ];
Configure as dependências em config/autoload/dependencies.php
:
<?php return [ \Constructo\Contract\Reflect\TypesFactory::class => \Serendipity\Hyperf\Support\HyperfTypesFactory::class, \Constructo\Contract\Reflect\SpecsFactory::class => \Serendipity\Hyperf\Support\HyperfSpecsFactory::class, ];
🎯 Funcionalidades Principais
Entidades com Tipagem Forte
Crie entidades robustas com validação automática e serialização inteligente:
<?php use Constructo\Support\Reflective\Attribute\Managed; use Constructo\Support\Reflective\Attribute\Pattern; use Constructo\Type\Timestamp; class Game extends GameCommand { public function __construct( #[Managed('id')] public readonly string $id, #[Managed('timestamp')] public readonly Timestamp $createdAt, #[Managed('timestamp')] public readonly Timestamp $updatedAt, #[Pattern('/^[a-zA-Z]{1,255}$/')] string $name, #[Pattern] string $slug, Timestamp $publishedAt, array $data, FeatureCollection $features, ) { parent::__construct( name: $name, slug: $slug, publishedAt: $publishedAt, data: $data, features: $features, ); } }
Coleções Tipadas
Trabalhe com coleções type-safe que garantem integridade dos dados:
<?php use Constructo\Type\Collection; /** * @extends Collection<Feature> */ class FeatureCollection extends Collection { public function current(): Feature { return $this->validate($this->datum()); } protected function validate(mixed $datum): Feature { return ($datum instanceof Feature) ? $datum : throw $this->exception(Feature::class, $datum); } }
Validação de Input Inteligente
Sistema de validação integrado com Hyperf que suporta regras complexas:
<?php use Serendipity\Presentation\Input; final class HealthInput extends Input { public function rules(): array { return [ 'message' => 'sometimes|string|max:255', 'level' => 'required|in:debug,info,warning,error', 'metadata' => 'array', ]; } }
Actions com Injeção de Dependência
Crie actions limpas com injeção automática de dependências:
<?php readonly class HealthAction { public function __invoke(HealthInput $input): array { return [ 'method' => $input->getMethod(), 'message' => $input->value('message', 'Sistema funcionando perfeitamente!'), 'timestamp' => time(), 'status' => 'healthy' ]; } }
🏗️ Arquitetura de Projeto com Serendipity
Estrutura recomendada para projetos que utilizam Serendipity, baseada em projetos reais em produção:
/
├── app/ # Código fonte da aplicação
│ ├── Application/ # Casos de uso da aplicação
│ │ ├── Exception/ # Exceções de aplicação
│ │ └── Service/ # Serviços de aplicação
│ ├── Domain/ # Lógica de negócio pura
│ │ ├── Entity/ # Entidades do domínio
│ │ ├── Enum/ # Enums do domínio
│ │ ├── Provider/ # Provedores de domínio
│ │ ├── Repository/ # Contratos de repositório
│ │ ├── Service/ # Serviços de domínio
│ │ ├── Support/ # Utilitários do domínio
│ │ └── Validator/ # Validadores de negócio
│ ├── Infrastructure/ # Implementações de infraestrutura
│ │ ├── Exception/ # Exceções de infraestrutura
│ │ ├── Parser/ # Parsers de dados
│ │ ├── Repository/ # Implementações de repositório
│ │ ├── Service/ # Serviços de infraestrutura
│ │ ├── Support/ # Utilitários de infraestrutura
│ │ └── Validator/ # Validadores de infraestrutura
│ └── Presentation/ # Camada de apresentação
│ ├── Action/ # Controllers/Actions
│ ├── Input/ # Validação de entrada
│ └── Service/ # Serviços de apresentação
├── bin/ # Scripts executáveis
│ ├── hyperf.php # Script principal do Hyperf
│ └── phpunit.php # Script de testes
├── compose.yml # Configuração principal do Docker Compose
├── composer.json # Dependências do Composer
├── composer.lock # Lock das dependências
├── config/ # Configurações da aplicação
│ └── autoload/ # Configurações carregadas automaticamente
├── deptrac.yaml # Configuração de análise de dependências
├── Dockerfile # Configuração do Docker
├── LICENSE # Licença do projeto
├── makefile # Comandos de desenvolvimento
├── migrations/ # Migrações do banco de dados
├── phpcs.xml # Configuração do PHP CodeSniffer
├── phpmd.xml # Configuração do PHP Mess Detector
├── phpstan.neon # Configuração do PHPStan
├── phpunit.xml # Configuração do PHPUnit
├── psalm.xml # Configuração do Psalm
├── README.md # Documentação principal
├── rector.php # Configuração do Rector
├── runtime/ # Arquivos temporários e cache
├── sonar-project.properties # Configuração do SonarQube
├── storage/ # Armazenamento local
└── tests/ # Testes automatizados
├── Application/ # Testes de aplicação
├── Domain/ # Testes de domínio
├── Infrastructure/ # Testes de infraestrutura
└── Presentation/ # Testes de apresentação
Organização das Camadas
Application Layer - Casos de uso e orquestração
- Service/: Coordenam operações entre domínio e infraestrutura
- Exception/: Exceções específicas da camada de aplicação
Domain Layer - Lógica de negócio pura
- Entity/: Entidades principais do negócio
- Enum/: Enumerações e constantes do domínio
- Repository/: Interfaces para persistência
- Service/: Regras de negócio complexas
- Validator/: Validações de regras de negócio
Infrastructure Layer - Implementações técnicas
- Repository/: Implementações concretas dos repositórios
- Service/: Integrações com APIs externas
- Parser/: Processamento e transformação de dados
- Support/: Utilitários técnicos
Presentation Layer - Interface com o mundo externo
- Action/: Endpoints HTTP e handlers
- Input/: Validação e sanitização de entrada
- Service/: Formatação de resposta
Exemplo de Estrutura de Action
<?php namespace App\Presentation\Action; use App\Presentation\Input\ProcessLeadInput; use App\Application\Service\LeadProcessorService; readonly class ProcessLeadAction { public function __construct( private LeadProcessorService $processor ) {} public function __invoke(ProcessLeadInput $input): array { $result = $this->processor->process($input->validated()); return [ 'success' => true, 'data' => $result->toArray(), ]; } }
📋 Exemplos Práticos
Entidade User com Validação
<?php namespace App\Domain\Entity; use Constructo\Support\Reflective\Attribute\Managed; use Constructo\Support\Reflective\Attribute\Pattern; use DateTime; readonly class User { public function __construct( #[Managed('id')] public int $id, #[Pattern('/^[a-zA-Z\s]{2,100}$/')] public string $name, public DateTime $birthDate, public bool $isActive = true, public array $tags = [], ) { } public function getAge(): int { return $this->birthDate->diff(new DateTime())->y; } public function isAdult(): bool { return $this->getAge() >= 18; } public function addTag(string $tag): array { return [...$this->tags, $tag]; } }
Input de Validação para User
<?php namespace App\Presentation\Input; use Serendipity\Presentation\Input; final class CreateUserInput extends Input { public function rules(): array { return [ 'name' => 'required|string|min:2|max:100|regex:/^[a-zA-Z\s]+$/', 'birth_date' => 'required|date|before:today', 'is_active' => 'sometimes|boolean', 'tags' => 'sometimes|array', 'tags.*' => 'string|max:50', 'email' => 'required|email|unique:users,email', 'password' => 'required|string|min:8|confirmed', ]; } public function messages(): array { return [ 'name.regex' => 'O nome deve conter apenas letras e espaços', 'birth_date.before' => 'A data de nascimento deve ser anterior a hoje', 'email.unique' => 'Este email já está em uso', 'password.confirmed' => 'A confirmação da senha não confere', ]; } }
Action para Criação de User
<?php namespace App\Presentation\Action; use App\Domain\Entity\User; use App\Presentation\Input\CreateUserInput; use App\Domain\Service\UserService; use DateTime; use Psr\Log\LoggerInterface; readonly class CreateUserAction { public function __construct( private UserService $userService, private LoggerInterface $logger ) {} public function __invoke(CreateUserInput $input): array { $userData = $input->validated(); $user = new User( id: 0, // Será preenchido pelo banco name: $userData['name'], birthDate: new DateTime($userData['birth_date']), isActive: $userData['is_active'] ?? true, tags: $userData['tags'] ?? [] ); $savedUser = $this->userService->create($user, $userData['password']); $this->logger->info('Usuário criado com sucesso', [ 'user_id' => $savedUser->id, 'name' => $savedUser->name, 'is_adult' => $savedUser->isAdult(), ]); return [ 'success' => true, 'user' => [ 'id' => $savedUser->id, 'name' => $savedUser->name, 'age' => $savedUser->getAge(), 'is_adult' => $savedUser->isAdult(), 'is_active' => $savedUser->isActive, 'tags' => $savedUser->tags, ], ]; } }
Serviço de Domínio para User
<?php namespace App\Domain\Service; use App\Domain\Entity\User; use App\Domain\Repository\UserRepositoryInterface; use App\Infrastructure\Service\PasswordHashService; readonly class UserService { public function __construct( private UserRepositoryInterface $userRepository, private PasswordHashService $passwordService ) {} public function create(User $user, string $password): User { // Validações de negócio if (!$user->isAdult()) { throw new \DomainException('Usuário deve ser maior de idade'); } if (count($user->tags) > 10) { throw new \DomainException('Usuário não pode ter mais de 10 tags'); } // Hash da senha $hashedPassword = $this->passwordService->hash($password); // Persistir no banco return $this->userRepository->save($user, $hashedPassword); } public function updateTags(int $userId, array $newTags): User { $user = $this->userRepository->findById($userId); if (!$user) { throw new \DomainException('Usuário não encontrado'); } if (count($newTags) > 10) { throw new \DomainException('Usuário não pode ter mais de 10 tags'); } return $this->userRepository->updateTags($userId, $newTags); } }
Repositório de User
<?php namespace App\Domain\Repository; use App\Domain\Entity\User; interface UserRepositoryInterface { public function save(User $user, string $hashedPassword): User; public function findById(int $id): ?User; public function findByEmail(string $email): ?User; public function updateTags(int $userId, array $tags): User; public function findActiveUsers(): array; public function findUsersByTag(string $tag): array; }
Implementação do Repositório
<?php namespace App\Infrastructure\Repository; use App\Domain\Entity\User; use App\Domain\Repository\UserRepositoryInterface; use Hyperf\Database\ConnectionInterface; use DateTime; readonly class UserRepository implements UserRepositoryInterface { public function __construct( private ConnectionInterface $connection ) {} public function save(User $user, string $hashedPassword): User { $id = $this->connection->table('users')->insertGetId([ 'name' => $user->name, 'email' => $user->email ?? '', 'password' => $hashedPassword, 'birth_date' => $user->birthDate->format('Y-m-d'), 'is_active' => $user->isActive, 'tags' => json_encode($user->tags), 'created_at' => now(), 'updated_at' => now(), ]); return new User( id: $id, name: $user->name, birthDate: $user->birthDate, isActive: $user->isActive, tags: $user->tags ); } public function findById(int $id): ?User { $userData = $this->connection ->table('users') ->where('id', $id) ->first(); if (!$userData) { return null; } return new User( id: $userData->id, name: $userData->name, birthDate: new DateTime($userData->birth_date), isActive: (bool) $userData->is_active, tags: json_decode($userData->tags, true) ?? [] ); } public function findByEmail(string $email): ?User { $userData = $this->connection ->table('users') ->where('email', $email) ->first(); if (!$userData) { return null; } return new User( id: $userData->id, name: $userData->name, birthDate: new DateTime($userData->birth_date), isActive: (bool) $userData->is_active, tags: json_decode($userData->tags, true) ?? [] ); } public function updateTags(int $userId, array $tags): User { $this->connection ->table('users') ->where('id', $userId) ->update([ 'tags' => json_encode($tags), 'updated_at' => now(), ]); return $this->findById($userId); } public function findActiveUsers(): array { $users = $this->connection ->table('users') ->where('is_active', true) ->get(); return $users->map(fn($userData) => new User( id: $userData->id, name: $userData->name, birthDate: new DateTime($userData->birth_date), isActive: true, tags: json_decode($userData->tags, true) ?? [] ))->toArray(); } public function findUsersByTag(string $tag): array { $users = $this->connection ->table('users') ->whereJsonContains('tags', $tag) ->get(); return $users->map(fn($userData) => new User( id: $userData->id, name: $userData->name, birthDate: new DateTime($userData->birth_date), isActive: (bool) $userData->is_active, tags: json_decode($userData->tags, true) ?? [] ))->toArray(); } }
Coleção Tipada de Users
<?php namespace App\Domain\Collection; use Constructo\Type\Collection; use App\Domain\Entity\User; /** * @extends Collection<User> */ class UserCollection extends Collection { public function current(): User { return $this->validate($this->datum()); } protected function validate(mixed $datum): User { return ($datum instanceof User) ? $datum : throw $this->exception(User::class, $datum); } public function getActiveUsers(): UserCollection { return new self( array_filter($this->items, fn(User $user) => $user->isActive) ); } public function getAdultUsers(): UserCollection { return new self( array_filter($this->items, fn(User $user) => $user->isAdult()) ); } public function getUsersByTag(string $tag): UserCollection { return new self( array_filter($this->items, fn(User $user) => in_array($tag, $user->tags)) ); } public function getAverageAge(): float { if ($this->count() === 0) { return 0; } $totalAge = array_sum( array_map(fn(User $user) => $user->getAge(), $this->items) ); return $totalAge / $this->count(); } }
🧪 Testes
Serendipity fornece ferramentas robustas para testes:
<?php use Serendipity\Testing\TestCase; use App\Domain\Entity\User; use App\Presentation\Input\CreateUserInput; use App\Presentation\Action\CreateUserAction; use DateTime; class CreateUserActionTest extends TestCase { public function testCreateUserSuccess(): void { $input = new CreateUserInput([ 'name' => 'João Silva', 'birth_date' => '1990-05-15', 'email' => 'joao@example.com', 'password' => 'senha123456', 'password_confirmation' => 'senha123456', 'is_active' => true, 'tags' => ['desenvolvedor', 'php'], ]); $action = $this->container()->get(CreateUserAction::class); $result = $action($input); $this->assertTrue($result['success']); $this->assertArrayHasKey('user', $result); $this->assertEquals('João Silva', $result['user']['name']); $this->assertTrue($result['user']['is_adult']); $this->assertTrue($result['user']['is_active']); $this->assertContains('desenvolvedor', $result['user']['tags']); } public function testCreateUserValidationFails(): void { $this->expectException(\Hyperf\Validation\ValidationException::class); $input = new CreateUserInput([ 'name' => '', // Nome vazio 'birth_date' => '2020-01-01', // Menor de idade 'email' => 'email-invalido', // Email inválido 'password' => '123', // Senha muito curta ]); $input->validated(); } public function testUserEntityMethods(): void { $user = new User( id: 1, name: 'Maria Santos', birthDate: new DateTime('1985-03-20'), isActive: true, tags: ['designer', 'ui-ux'] ); $this->assertEquals(39, $user->getAge()); // Assumindo 2024 $this->assertTrue($user->isAdult()); $this->assertEquals(['designer', 'ui-ux', 'frontend'], $user->addTag('frontend')); } } class UserServiceTest extends TestCase { public function testCreateUserWithBusinessRules(): void { $userService = $this->container()->get(\App\Domain\Service\UserService::class); $user = new User( id: 0, name: 'Pedro Costa', birthDate: new DateTime('1992-08-10'), isActive: true, tags: ['backend'] ); $result = $userService->create($user, 'senhaSegura123'); $this->assertInstanceOf(User::class, $result); $this->assertGreaterThan(0, $result->id); } public function testCreateMinorUserFails(): void { $this->expectException(\DomainException::class); $this->expectExceptionMessage('Usuário deve ser maior de idade'); $userService = $this->container()->get(\App\Domain\Service\UserService::class); $minorUser = new User( id: 0, name: 'Criança', birthDate: new DateTime('2020-01-01'), isActive: true, tags: [] ); $userService->create($minorUser, 'senha123'); } public function testUpdateTagsSuccess(): void { $userService = $this->container()->get(\App\Domain\Service\UserService::class); // Mock do usuário existente $existingUser = new User( id: 1, name: 'Ana Silva', birthDate: new DateTime('1988-12-05'), isActive: true, tags: ['old-tag'] ); $newTags = ['new-tag', 'another-tag']; $result = $userService->updateTags(1, $newTags); $this->assertInstanceOf(User::class, $result); $this->assertEquals($newTags, $result->tags); } } class UserCollectionTest extends TestCase { public function testUserCollectionFilters(): void { $users = [ new User(1, 'João', new DateTime('1990-01-01'), true, ['php']), new User(2, 'Maria', new DateTime('2010-01-01'), true, ['js']), // Menor new User(3, 'Pedro', new DateTime('1985-01-01'), false, ['python']), // Inativo new User(4, 'Ana', new DateTime('1992-01-01'), true, ['php', 'laravel']), ]; $collection = new \App\Domain\Collection\UserCollection($users); // Teste filtro de usuários ativos $activeUsers = $collection->getActiveUsers(); $this->assertCount(3, $activeUsers); // Teste filtro de usuários adultos $adultUsers = $collection->getAdultUsers(); $this->assertCount(3, $adultUsers); // Teste filtro por tag $phpUsers = $collection->getUsersByTag('php'); $this->assertCount(2, $phpUsers); // Teste média de idade $averageAge = $collection->getAverageAge(); $this->assertGreaterThan(0, $averageAge); } public function testEmptyCollectionAverageAge(): void { $collection = new \App\Domain\Collection\UserCollection([]); $this->assertEquals(0, $collection->getAverageAge()); } }
⚡ Performance e Observabilidade
Logging Estruturado
<?php $this->logger->info('Lead processado com sucesso', [ 'lead_id' => $leadId, 'source' => $source, 'processing_time_ms' => $processingTime, 'memory_usage' => memory_get_usage(true), ]);
Métricas e Monitoramento
<?php // Integração com sistemas de métricas use Hyperf\Context\Context; Context::set('metrics.processing_start', microtime(true)); $result = $this->processLead($input); $duration = microtime(true) - Context::get('metrics.processing_start'); $this->logger->info('Métrica de performance', [ 'operation' => 'process_lead', 'duration_ms' => round($duration * 1000, 2), 'success' => $result->isSuccess(), ]);
🔧 Configuração Avançada
Schema e Especificações
Configure schemas personalizados em config/autoload/schema.php
:
<?php return [ 'specs' => [ 'lead' => [ 'id' => 'string', 'name' => 'string', 'email' => 'email', 'phone' => 'string', 'created_at' => 'timestamp', ], 'quote' => [ 'id' => 'string', 'lead_id' => 'string', 'amount' => 'decimal', 'status' => 'enum:pending,approved,rejected', ], ], ];
Middlewares Personalizados
<?php use Serendipity\Hyperf\Middleware\AbstractMiddleware; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ResponseInterface; use Psr\Http\Server\RequestHandlerInterface; class LeadValidationMiddleware extends AbstractMiddleware { public function process( ServerRequestInterface $request, RequestHandlerInterface $handler ): ResponseInterface { // Validação específica de leads $body = $request->getParsedBody(); if (isset($body['email']) && !filter_var($body['email'], FILTER_VALIDATE_EMAIL)) { throw new InvalidArgumentException('Email inválido'); } return $handler->handle($request); } }
📚 Comandos CLI
Serendipity inclui comandos úteis para desenvolvimento:
# Gerar regras de validação php bin/hyperf.php gen:rules LeadRules # Executar health check via CLI php bin/hyperf.php health:check # Processar leads em lote php bin/hyperf.php lead:process-batch # Limpar caches php bin/hyperf.php cache:clear
🤝 Contribuindo
Fork o projeto, crie uma branch para sua feature, commit suas mudanças, push para a branch e abra um Pull Request.
Padrões de Desenvolvimento
- Siga PSR-12 para código PHP
- Use tipagem forte sempre que possível
- Implemente testes para novas funcionalidades
- Documente mudanças no README
📄 Licença
Este projeto está licenciado sob a Licença MIT - veja o arquivo LICENSE para detalhes.
🔗 Links Relacionados
Serendipity - Descobrindo o potencial completo do Hyperf através de componentes elegantes e poderosos.