PHP code example of devitools / serendipity

1. Go to this page and download the library: Download devitools/serendipity library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

devitools / serendipity example snippets




return [
    'providers' => [
        Serendipity\ConfigProvider::class,
    ],
];



return [
    \Constructo\Contract\Reflect\TypesFactory::class => 
        \Serendipity\Hyperf\Support\HyperfTypesFactory::class,
    \Constructo\Contract\Reflect\SpecsFactory::class => 
        \Serendipity\Hyperf\Support\HyperfSpecsFactory::class,
];



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,
        );
    }
}



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);
    }
}



use Serendipity\Presentation\Input;

final class HealthInput extends Input
{
    public function rules(): array
    {
        return [
            'message' => 'sometimes|string|max:255',
            'level' => '



readonly class HealthAction
{
    public function __invoke(HealthInput $input): array
    {
        return [
            'method' => $input->getMethod(),
            'message' => $input->value('message', 'Sistema funcionando perfeitamente!'),
            'timestamp' => time(),
            'status' => 'healthy'
        ];
    }
}



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(),
        ];
    }
}



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];
    }
}



namespace App\Presentation\Input;

use Serendipity\Presentation\Input;

final class CreateUserInput extends Input
{
    public function rules(): array
    {
        return [
            'name' => 'ail' => '',
            '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',
        ];
    }
}



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,
            ],
        ];
    }
}



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);
    }
}



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;
}



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();
    }
}



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();
    }
}



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' => '[email protected]',
            '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());
    }
}



$this->logger->info('Lead processado com sucesso', [
    'lead_id' => $leadId,
    'source' => $source,
    'processing_time_ms' => $processingTime,
    'memory_usage' => memory_get_usage(true),
]);



// 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(),
]);



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',
        ],
    ],
];



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);
    }
}
bash
# 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