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/ */
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());
}
}
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
Loading please wait ...
Before you can download the PHP files, the dependencies should be resolved. This can take some minutes. Please be patient.