Skip to content

Commit bf0cfe6

Browse files
author
pablo.garcia
committed
feat: Implement CQRS pattern for YouTube bundle operations
1 parent 276fb17 commit bf0cfe6

File tree

232 files changed

+13854
-235
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

232 files changed

+13854
-235
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Create;
6+
7+
final class CreateAccountMessage
8+
{
9+
private string $login;
10+
private array $i18nTitle;
11+
private \DateTimeInterface $createdAt;
12+
13+
public function __construct(string $login, array $i18nTitle)
14+
{
15+
$this->login = $login;
16+
$this->i18nTitle = $i18nTitle;
17+
$this->createdAt = new \DateTimeImmutable();
18+
}
19+
20+
public function getLogin(): string
21+
{
22+
return $this->login;
23+
}
24+
25+
public function getI18nTitle(): array
26+
{
27+
return $this->i18nTitle;
28+
}
29+
30+
public function getCreatedAt(): \DateTimeInterface
31+
{
32+
return $this->createdAt;
33+
}
34+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Create;
6+
7+
use Psr\Log\LoggerInterface;
8+
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
9+
10+
#[AsMessageHandler(fromTransport: 'pumukit.youtube.events')]
11+
final class CreateAccountMessageHandler
12+
{
13+
private CreateAccountService $createAccountService;
14+
private LoggerInterface $logger;
15+
16+
public function __construct(
17+
CreateAccountService $createAccountService,
18+
LoggerInterface $logger
19+
) {
20+
$this->createAccountService = $createAccountService;
21+
$this->logger = $logger;
22+
}
23+
24+
public function __invoke(CreateAccountMessage $message): void
25+
{
26+
$this->logger->info('[AccountHexagonal] Processing CreateAccountMessage', [
27+
'login' => $message->getLogin(),
28+
]);
29+
30+
try {
31+
// Convert Message to Request
32+
$request = new CreateAccountRequest(
33+
login: $message->getLogin(),
34+
i18nTitle: $message->getI18nTitle()
35+
);
36+
37+
// Execute service
38+
$response = $this->createAccountService->__invoke($request);
39+
40+
$this->logger->info('[AccountHexagonal] Account created successfully', [
41+
'accountId' => $response->getAccount()->getId(),
42+
'login' => $message->getLogin(),
43+
]);
44+
} catch (\Exception $e) {
45+
$this->logger->error('[AccountHexagonal] Failed to create account', [
46+
'login' => $message->getLogin(),
47+
'error' => $e->getMessage(),
48+
]);
49+
50+
throw $e;
51+
}
52+
}
53+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Create;
6+
7+
final class CreateAccountRequest
8+
{
9+
private string $login;
10+
private array $i18nTitle;
11+
12+
public function __construct(string $login, array $i18nTitle)
13+
{
14+
$this->login = $login;
15+
$this->i18nTitle = $i18nTitle;
16+
}
17+
18+
public function getLogin(): string
19+
{
20+
return $this->login;
21+
}
22+
23+
public function getI18nTitle(): array
24+
{
25+
return $this->i18nTitle;
26+
}
27+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Create;
6+
7+
use Pumukit\SchemaBundle\Document\Tag;
8+
9+
final class CreateAccountResponse
10+
{
11+
private Tag $account;
12+
13+
public function __construct(Tag $account)
14+
{
15+
$this->account = $account;
16+
}
17+
18+
public function getAccount(): Tag
19+
{
20+
return $this->account;
21+
}
22+
23+
public function toArray(): array
24+
{
25+
return [
26+
'id' => $this->account->getId(),
27+
'login' => $this->account->getProperty('login'),
28+
'title' => $this->account->getI18nTitle(),
29+
'cod' => $this->account->getCod(),
30+
];
31+
}
32+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Create;
6+
7+
use Doctrine\ODM\MongoDB\DocumentManager;
8+
use Pumukit\SchemaBundle\Document\Tag;
9+
use Pumukit\YoutubeBundle\AccountHexagonal\Domain\Event\AccountCreatedEvent;
10+
use Pumukit\YoutubeBundle\AccountHexagonal\Domain\Exception\AccountAlreadyExistsException;
11+
use Pumukit\YoutubeBundle\AccountHexagonal\Domain\Repository\AccountRepositoryInterface;
12+
use Pumukit\YoutubeBundle\PumukitYoutubeBundle;
13+
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
14+
15+
final class CreateAccountService
16+
{
17+
private AccountRepositoryInterface $accountRepository;
18+
private DocumentManager $documentManager;
19+
private EventDispatcherInterface $eventDispatcher;
20+
private CreateAccountValidator $validator;
21+
22+
public function __construct(
23+
AccountRepositoryInterface $accountRepository,
24+
DocumentManager $documentManager,
25+
EventDispatcherInterface $eventDispatcher,
26+
CreateAccountValidator $validator
27+
) {
28+
$this->accountRepository = $accountRepository;
29+
$this->documentManager = $documentManager;
30+
$this->eventDispatcher = $eventDispatcher;
31+
$this->validator = $validator;
32+
}
33+
34+
public function __invoke(CreateAccountRequest $request): CreateAccountResponse
35+
{
36+
// 1. Validate request
37+
$this->validator->validate($request);
38+
39+
// 2. Check if account already exists
40+
$existingAccount = $this->accountRepository->findByLogin($request->getLogin());
41+
if ($existingAccount) {
42+
throw AccountAlreadyExistsException::withLogin($request->getLogin());
43+
}
44+
45+
// 3. Get YouTube parent tag
46+
$youtubeTag = $this->documentManager->getRepository(Tag::class)->findOneBy([
47+
'cod' => PumukitYoutubeBundle::YOUTUBE_TAG_CODE,
48+
]);
49+
50+
if (!$youtubeTag) {
51+
throw new \RuntimeException('YouTube parent tag not found');
52+
}
53+
54+
// 4. Create new account Tag
55+
$account = new Tag();
56+
$account->setMetatag(false);
57+
$account->setProperty('login', $request->getLogin());
58+
$account->setDisplay(false);
59+
$account->setI18nTitle($request->getI18nTitle());
60+
$account->setParent($youtubeTag);
61+
62+
// 5. Save account
63+
$this->accountRepository->save($account);
64+
65+
// 6. Set cod to ID after persistence
66+
$account->setCod($account->getId());
67+
$this->documentManager->flush();
68+
69+
// 7. Dispatch domain event
70+
$this->eventDispatcher->dispatch(new AccountCreatedEvent($account));
71+
72+
return new CreateAccountResponse($account);
73+
}
74+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Create;
6+
7+
use Pumukit\YoutubeBundle\AccountHexagonal\Domain\ValueObject\AccountLogin;
8+
9+
final class CreateAccountValidator
10+
{
11+
public function validate(CreateAccountRequest $request): void
12+
{
13+
// Validate login format
14+
new AccountLogin($request->getLogin());
15+
16+
// Validate i18n title is not empty
17+
if (empty($request->getI18nTitle())) {
18+
throw new \InvalidArgumentException('Account title cannot be empty');
19+
}
20+
21+
// Validate at least one title is provided
22+
$hasTitle = false;
23+
foreach ($request->getI18nTitle() as $title) {
24+
if (!empty($title)) {
25+
$hasTitle = true;
26+
break;
27+
}
28+
}
29+
30+
if (!$hasTitle) {
31+
throw new \InvalidArgumentException('At least one translated title must be provided');
32+
}
33+
}
34+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Delete;
6+
7+
final class DeleteAccountMessage
8+
{
9+
private string $accountId;
10+
private \DateTimeInterface $createdAt;
11+
12+
public function __construct(string $accountId)
13+
{
14+
$this->accountId = $accountId;
15+
$this->createdAt = new \DateTimeImmutable();
16+
}
17+
18+
public function getAccountId(): string
19+
{
20+
return $this->accountId;
21+
}
22+
23+
public function getCreatedAt(): \DateTimeInterface
24+
{
25+
return $this->createdAt;
26+
}
27+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Delete;
6+
7+
use Psr\Log\LoggerInterface;
8+
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
9+
10+
#[AsMessageHandler(fromTransport: 'pumukit.youtube.events')]
11+
final class DeleteAccountMessageHandler
12+
{
13+
private DeleteAccountService $deleteAccountService;
14+
private LoggerInterface $logger;
15+
16+
public function __construct(
17+
DeleteAccountService $deleteAccountService,
18+
LoggerInterface $logger
19+
) {
20+
$this->deleteAccountService = $deleteAccountService;
21+
$this->logger = $logger;
22+
}
23+
24+
public function __invoke(DeleteAccountMessage $message): void
25+
{
26+
$this->logger->info('[AccountHexagonal] Processing DeleteAccountMessage', [
27+
'accountId' => $message->getAccountId(),
28+
]);
29+
30+
try {
31+
// Convert Message to Request
32+
$request = new DeleteAccountRequest(
33+
accountId: $message->getAccountId()
34+
);
35+
36+
// Execute service
37+
$response = $this->deleteAccountService->__invoke($request);
38+
39+
$this->logger->info('[AccountHexagonal] Account deleted successfully', [
40+
'accountId' => $response->getAccountId(),
41+
]);
42+
} catch (\Exception $e) {
43+
$this->logger->error('[AccountHexagonal] Failed to delete account', [
44+
'accountId' => $message->getAccountId(),
45+
'error' => $e->getMessage(),
46+
]);
47+
48+
throw $e;
49+
}
50+
}
51+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Delete;
6+
7+
final class DeleteAccountRequest
8+
{
9+
private string $accountId;
10+
11+
public function __construct(string $accountId)
12+
{
13+
$this->accountId = $accountId;
14+
}
15+
16+
public function getAccountId(): string
17+
{
18+
return $this->accountId;
19+
}
20+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pumukit\YoutubeBundle\AccountHexagonal\Application\Delete;
6+
7+
final class DeleteAccountResponse
8+
{
9+
private string $accountId;
10+
private bool $success;
11+
12+
public function __construct(string $accountId, bool $success = true)
13+
{
14+
$this->accountId = $accountId;
15+
$this->success = $success;
16+
}
17+
18+
public function getAccountId(): string
19+
{
20+
return $this->accountId;
21+
}
22+
23+
public function isSuccess(): bool
24+
{
25+
return $this->success;
26+
}
27+
28+
public function toArray(): array
29+
{
30+
return [
31+
'accountId' => $this->accountId,
32+
'success' => $this->success,
33+
];
34+
}
35+
}

0 commit comments

Comments
 (0)