Milan Felix Šulc
📦 Package maker • 👷 Architect ModernTV • 🐘 PHP enthusiast • 💿 Multimedia engineer • 👨👩👧👦 Father • ☕️ Coffee-based
@xf3l1x
f3l1x.io
30.04.2024
Command
Bus
From 0 to 🧙♂️
Bus, bus, bus
Hierarchy / Layers
Types of objects
Coding
Tips & tricks
class UserPresenter
{
public function actionDefault()
{
$pdo = new PDO('localhost', 'user', 'password');
$stmt = $pdo->prepare('SELECT * FROM users');
$stmt->execute();
$users = $stmt->fetchAll();
$this->template->users = $users;
}
}
class DB {
public static function getUsers()
{
$pdo = new PDO('localhost', 'user', 'password');
$stmt = $pdo->prepare('SELECT * FROM users');
$stmt->execute();
return $stmt->fetchAll();
}
}
class User1Presenter
{
public function actionDefault()
{
$users = DB::getUsers();
}
}
class User2Presenter
{
public function actionDefault()
{
$users = DB::getUsers();
}
}
class DB {
public function __construct($host, $user, $password)
{
$this->pdo = new PDO($host, $user, $password);
}
public function getUsers()
{
$stmt = $this->pdo->prepare('SELECT * FROM users');
$stmt->execute();
return $stmt->fetchAll();
}
}
class UserPresenter
{
public DB $db;
public function actionDefault()
{
$users = $this->db->getUsers();
}
}
class DB {
public function query($sql) {
// ...
}
}
class UserModel
{
public DB $db;
public function getUsers()
{
return $this->db->query('SELECT * FROM users');
}
}
class UserPresenter
{
public UserModel $userModel;
public function actionDefault()
{
$users = $this->userModel->getUsers();
}
}
class UserModel
{
public Nette\Database\Connection $db;
public function getUser($id)
{
return $this->db->fetch('SELECT * FROM users WHERE id = ?', $id);
}
public function getUsers($filters = [])
{
return $this->db->fetchAll('SELECT * FROM users WHERE', $filters);
}
}
class UserPresenter
{
public UserModel $userModel;
public function actionDefault()
{
$users = $this->userModel->getUsers();
}
public function actionDetail($id)
{
$user = $this->userModel->getUser($id);
}
}
class UserPresenter
{
public UserRepository $userRepository;
public function actionDefault()
{
$users = $this->userRepository->findAll(['role' => 'admin']);
}
public function actionDetail($id)
{
$user = $this->userRepository->findBy(['id' => $id]);
}
}
class UserRepository
{
public Connection $db;
public function findAll($filters, $limit, $offset);
public function findBy($filters);
public function fetchBy($filters);
public function create($data);
public function update($data, $filters);
public function delete($filters);
}
class UserPresenter
{
protected function createComponentForm()
{
$form->onSuccess[] = function($form) {
$this->userRepository->create([
'name' => $form->values->name,
'email' => $form->values->email,
]);
};
}
}
class UserApi
{
public function __invoke(Request $request)
{
$this->userRepository->create([
'name' => $request->values->name,
'email' => $request->values->email,
]);
}
}
class UserPresenter
{
protected function createComponentForm()
{
$form->onSuccess[] = function($form) {
$this->userService->create($form->values);
};
}
}
class UserApi
{
public function __invoke(Request $request)
{
$this->userService->create($request->values);
}
}
class UserService
{
public UserRepository $userRepository;
public function create($data)
{
Assert::string($data, 'name');
Assert::email($data, 'email');
$this->userRepository->create([
'name' => $data['name'],
'email' => $data['email'],
]);
}
}
class UserPresenter
{
protected function createComponentForm()
{
$form->onSuccess[] = function($form) {
$this->userService->create($form->values);
$this->mailer->send($form->values->email, 'Welcome');
};
}
}
class UserApi
{
public function __invoke(Request $request)
{
$this->userService->create($request->values);
$this->mailer->send($request->values->email, 'Welcome');
}
}
class UserPresenter
{
protected function createComponentForm()
{
$form->onSuccess[] = function($form) {
$this->userFacade->create($form);
};
}
}
class UserFacade
{
public UserRepository $userRepository;
public Mailer $mailer;
public function create($data)
{
Assert::data($data);
$this->userRepository->create([
'name' => $data['name'],
'email' => $data['email'],
]);
$this->mailer->send($data['email'], 'Welcome');
}
}
class UserFacade
{
public function __construct(
UserRepository $userRepository,
ProfileRepository $profileRepository,
Translator $translator,
AcmeHttpConnector $httpConnector,
CurrencyConverter $currencyConverter,
DataValidator $dataValidator,
Mailer $mailer
)
{
}
public function createUser(...);
public function updateUser(...);
public function delateUser(...);
public function findUserById(...);
public function findAllUsers(...);
public function sendEmailToUsers(...);
public function deactivateUsers(...);
public function activateProfiles(...);
}
class CreateUserCommand
{
public function __construct(
public string $name,
public string $email
)
{
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$data = [
'name' => $command->name,
'email' => $command->email,
];
$this->userRepository->create($data);
}
}
class UserPresenter
{
protected function createComponentForm()
{
$form->onSuccess[] = function($form) {
$this->commandBus->dispatch(
new CreateUserCommand(
$form->values->name,
$form->values->email
)
);
};
}
}
class CommandBus
{
public function dispatch($command) {
if ($command instanceof CreateUserCommand) {
$this->createUserHandler->handle($command);
}
// ...
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$data = [
'name' => $command->name,
'email' => $command->email,
];
$this->userRepository->create($data);
$this->mailer->send($command->email, 'Welcome');
$this->logger->info('User created', $data);
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$data = [
'name' => $command->name,
'email' => $command->email,
];
$this->userRepository->create($data);
$this->mailer->send($command->email, 'Welcome');
$this->logger->info('User created', $data);
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$data = [
'name' => $command->name,
'email' => $command->email,
];
$this->userRepository->create($data);
$this->eventDispatcher->dispatch(
new UserCreatedEvent($data)
);
}
}
class EventDispatcher
{
public function dispatch($event) {
foreach ($this->subscribers[$event] as $subscriber) {
$subscriber->handle($event);
}
}
}
class LoggerSubscriber
{
public static function getSubscribedEvents()
{
return [UserCreatedEvent::class => 'handle'];
}
public function handle(UserCreatedEvent $event) {
$this->logger->info('User created', $event->data);
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$data = [
'name' => $command->name,
'email' => $command->email,
];
$this->userRepository->create($data);
$this->eventDispatcher->dispatch(
new UserCreatedEvent($data)
);
}
}
class UserPresenter
{
protected function createComponentForm()
{
$form->onSuccess[] = function($form) {
$user = $this->commandBus->dispatch(
new CreateUserCommand(
$form->values->name,
$form->values->email
)
);
$this->flashMessage(
sprintf('User %s created', $user['name'])
);
};
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$data = [
'name' => $command->name,
'email' => $command->email,
];
$user = $this->userRepository->create($data);
return $user;
}
}
class UserPresenter
{
protected function createComponentForm()
{
$form->onSuccess[] = function($form) {
$userRecord = $this->commandBus->dispatch(
new CreateUserCommand(
$form->values->name,
$form->values->email
)
);
$this->flashMessage(
sprintf('User %s created', $userRecord->name)
);
$userRecord->ref('profile')->nickname;
$userRecord->update(['email' => 'john@email.tld']);
$userRecord->delete();
};
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$data = [
'name' => $command->name,
'email' => $command->email,
];
$userRecord = $this->userRepository->create($data);
return $userRecord;
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$user = new User();
$user->name = $command->name;
$user->email = $command->email;
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$user = new User();
$user->name = $command->name;
$user->email = $command->email;
try {
$this->entityManager->persist($user);
$this->entityManager->flush();
} catch (UniqueConstraintViolationException $e) {
throw EntityExistsException::from($user, $e);
}
}
}
class UpdateUserHandler
{
public function handle(UpdateUserHandler $command)
{
$user = $this->entityManager
->getRepository(User::class)
->findOneBy(['uuid' => $command->uuid]);
if ($user === null) {
throw EntityNotFoundException::byUuid($command->uuid);
}
if ($command->name !== null) {
$user->name = $command->name;
}
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}
class UserPresenter
{
public function actionList()
{
$filter = new UserFilter();
$filter->setName('name', $this->getParameter('name'));
$filter->setName('email', $this->getParameter('email'));
}
public function actionList()
{
$filter = new UserFilter();
$parameters = $this->getParameters();
if (isset($parameters['name'])) {
$filter->setName('name', $parameters['name']);
}
if (isset($parameters['email'])) {
$filter->setName('email', $parameters['email']);
}
}
}
class UserPresenter
{
public function actionList()
{
$filter = UserFilter::fromRequest($this->getParameters());
}
}
class UserFilter
{
public static function fromRequest(array $parameters): self
{
$filter = new self();
if (isset($parameters['name'])) {
$filter->setName('name', $parameters['name']);
}
if (isset($parameters['email'])) {
$filter->setName('email', $parameters['email']);
}
return $filter;
}
}
class UserFilter
{
public static function fromRequest(array $parameters): self
{
$filter = new self();
if (isset($parameters['name'])) {
$filter->setName('name', $parameters['name']);
}
if (isset($parameters['email'])) {
$filter->setName('email', $parameters['email']);
}
return $filter;
}
public static function fromConsole(array $args): self
{
$filter = new self();
if (isset($args['--name'])) {
$filter->setName('name', $args['--name']);
}
if (isset($args['--email'])) {
$filter->setEmail('email', $args['--email']);
}
return $filter;
}
}
class UserPresenter
{
public function actionList()
{
$this->bus->dispatch(
new GetUsersQuery(
filter: UserFilter::fromRequest($this->getParameters())
)
);
}
}
class GetUsersQuery
{
public function __construct(
public UserFilter $filter
)
{
}
}
#[ORM\Entity]
#[ORM\Table(name: 'users')]
class UserEntity
{
#[ORM\Column(type: 'uuid', unique: true)]
#[ORM\Id]
public UuidInterface|string $uuid;
#[ORM\Column(type: 'text', nullable: false)]
public string $name;
}
class EmailValueObject
{
public function __construct(string $email)
{
if (!Validators::isEmail($email)) {
throw new InvalidArgumentException;
}
$this->email = $email;
}
}
class UserDto
{
public function __construct(
public string $name,
public EmailValueObject $email
)
{
}
public function format(): string
{
return sprintf('%s <%s>', $this->name, $this->email->email);
}
}
Entrypoint
CreateUserPresenter
CreateUserController
-CreateUserRequest
---CreateUserDto (body)
-CreateUserResponse
CreateUserCommand
Domain
CreateUserCommand
CreateUserHandler
CreateUserResult
ListUserCommand
-ListUserFilter
ListUserResult
ListDeviceQuery
ListDeviceHandler
Data
UserEntity
UserRepository
UserQuery
UserServiceRepository
https://git.moderntv.eu/playground/ddd-playground
composer require nette/utils
if (!Validators::isEmail($email)) {
throw new InvalidArgumentException;
}
if (!Validators::is($val, 'int|string|bool')) {
// ...
}
$arr = ['foo' => 'Nette'];
Validators::assertField($arr, 'foo', 'string:5');
Validators::isNumeric(23);
Validators::isNone(0);
Validators::isTypeDeclaration('string|null');
Validators::isUrl('https://nette.org:8080/path?query#fragment');
composer require nette/utils
Validators::assert*; // throws exception
Validators::is*; // return bool
composer require nette/schema
$schema = Expect::structure([
'uuid' => Expect::string(),
'name' => Expect::string()->required(),
'description' => Expect::string()->required(),
'created' => Expect::string()
->assert(static fn ($date) => Validators::isDateTime($date), 'datetime'),
'owner' => Expect::string()->required(),
])->castTo(CreateProjectDto::class);
try {
$normalized = (new Process())->process($schema, $data);
} catch (ValidationException $e) {
echo 'Data is invalid: ' . $e->getMessage();
}
class CreateUserHandler
{
public function handle(CreateUserCommand $command) {
$user = new User();
$user->name = $command->name;
$user->email = $command->email;
$this->entityManager->beginTransaction();
try {
$this->entityManager->persist($user);
$this->entityManager->flush();
$this->entityManager->commit();
} catch (UniqueConstraintViolationException $e) {
$this->entityManager->rollback();
throw EntityExistsException::from($user, $e);
}
}
}
class ComplexCreateUserHandler
{
public function handle(CreateUserCommand $command) {
$this->bus->dispatch(new CreateUserCommand());
$this->bus->dispatch(new SetupUserCommand());
$this->bus->dispatch(new MakeUserFreeCommand());
}
}
class ComplexCreateUserHandler
{
public function handle(CreateUserCommand $command) {
$this->bus->dispatch(new CreateUserCommand());
$this->eventDispatcher->dispatch(UserCreated::create());
}
}
services:
fooFactory: FooFactory
barFactory: BarFactory
eventFactory:
class: EventFactory
setup:
- addFactory('foo', @fooFactory)
- addFactory('bar', @barFactory)
services:
fooFactory: FooFactory
barFactory: BarFactory
eventFactory:
class: EventFactory
setup:
- addFactory('foo', @fooFactory)
- addFactory('bar', @barFactory)
public function createServiceBarFactory(): BarFactory
{
return new BarFactory;
}
public function createServiceFooFactory(): FooFactory
{
return new FooFactory;
}
public function createServiceEventFactory(): EventFactory
{
$service = new EventFactory;
$service->addFactory('foo', $this->getService('fooFactory'));
$service->addFactory('bar', $this->getService('barFactory'));
return $service;
}
class EventService
{
public function __construct(EventFactory $eventFactory)
{
}
}
public function createServiceEventService(): EventService
{
$service = new EventService($this->getService('eventFactory'));
return $service;
}
class Container
{
public function getService($service)
{
if (!isset($this->services[$service])) {
$this->services[$service] = $this->{'createService' . ucfirst($service)}();
}
return $this->services[$service];
}
}
class EventService
{
public function __construct(EventFactory $eventFactory)
{
}
}
public function createServiceEventService(): EventService
{
$service = new EventService($this->getService('eventFactory'));
return $service;
}
class Container
{
public function getService($service)
{
if (!isset($this->services[$service])) {
$this->services[$service] = $this->{'createService' . ucfirst($service)}();
}
return $this->services[$service];
}
}
class ContainerEventFactory
{
private array $factories = [];
private array $services = [];
public function __construct(
private Container $container
)
{
}
public function add(string $factory, string $event): void
{
$this->factories[$event] = $factory;
}
public function create(string $event): Event
{
if (!isset($this->services[$event])) {
$this->services[$event] = $this->container->getService($this->factories[$event]);
}
return $this->factories[$event]->create($event);
}
}
services:
fooFactory: FooFactory
barFactory: BarFactory
eventFactory:
class: ContainerEventFactory
setup:
- addFactory('foo', fooFactory)
- addFactory('bar', barFactory)
Thank you!
@xf3l1x
f3l1x.io
By Milan Felix Šulc
📦 Package maker • 👷 Architect ModernTV • 🐘 PHP enthusiast • 💿 Multimedia engineer • 👨👩👧👦 Father • ☕️ Coffee-based