<?php
namespace spec\PHPBenelux\Domain;
use PhpSpec\ObjectBehavior;
class UserSpec extends ObjectBehavior
{
function it_has_email_and_password()
{
$this->setEmail('leszek.prabucki@gmail.com');
$this->email()->shouldBe('leszek.prabucki@gmail.com');
$hash = password_hash('secretPass', PASSWORD_BCRYPT);
$this->setPasswordHash();
$this->passwordHash()->shouldBe($hash);
}
}
Why not use constructor instead?
<?php
namespace spec\PHPBenelux\Domain;
use PhpSpec\ObjectBehavior;
class UserSpec extends ObjectBehavior
{
function it_has_email_and_password()
{
$hash = password_hash('secretPass', PASSWORD_BCRYPT);
$this->beConstructedWith('leszek.prabucki@gmail.com', $hash);
$this->email()->shouldBe('leszek.prabucki@gmail.com');
$this->passwordHash()->shouldBe($hash);
}
}
We can add Value Objects to protect invariants
<?php
namespace spec\PHPBenelux\Domain;
use PHPBenelux\Domain\Email;
use PHPBenelux\Domain\PasswordHash;
use PHPBenelux\Domain\UserId;
use PhpSpec\ObjectBehavior;
class UserSpec extends ObjectBehavior
{
function it_has_email_and_password()
{
$hash = password_hash('secretPass', PASSWORD_BCRYPT);
$this->beConstructedWith(
UserId::generate(),
new Email('leszek.prabucki@gmail.com'),
PasswordHash::fromHash($hash)
);
$this->id()->shouldHaveType(UserId::class);
$this->email()->shouldBeLike(new Email('leszek.prabucki@gmail.com'));
$this->passwordHash()->shouldBeLike(PasswordHash::fromHash($hash));
}
}
<?php
declare(strict_types=1);
namespace PHPBenelux\Domain;
class User
{
public function __construct($argument1, $argument2, $argument3)
{
// TODO: write logic here
}
public function id()
{
// TODO: write logic here
}
}
<?php
declare(strict_types=1);
namespace PHPBenelux\Domain;
class User
{
private $id;
private $email;
private $passwordHash;
public function __construct(UserId $id, Email $email, PasswordHash $passwordHash)
{
$this->id = $id;
$this->email = $email;
$this->passwordHash = $passwordHash;
}
public function id(): UserId
{
return $this->id;
}
public function email(): Email
{
return $this->email;
}
public function passwordHash(): PasswordHash
{
return $this->passwordHash;
}
}
<?php
namespace spec\PHPBenelux\Application\UseCase;
use Exception;
use InvalidArgumentException;
use PHPBenelux\Application\UseCase\RegisterUser;
use PHPBenelux\Application\TransactionManager;
use PHPBenelux\Domain\Users;
use PHPBenelux\Domain\UserFactory;
use PHPBenelux\Domain\User;
use PhpSpec\ObjectBehavior;
class RegisterUserSpec extends ObjectBehavior
{
function let(
TransactionManager $transactionManager,
Users $users,
UserFactory $userFactory
) {
$this->beConstructedWith($transactionManager, $users, $userFactory);
}
function it_creates_and_store_user_in_repository(
TransactionManager $transactionManager,
Users $users,
UserFactory $userFactory,
User $user
) {
$transactionManager->begin()->shouldBeCalled();
$userFactory->create(
'leszek.prabucki@gmail.com',
'password'
)->willReturn($user);
$users->has($user)->willReturn(false);
$users->add($user)->shouldBeCalled();
$transactionManager->commit()->shouldBeCalled();
$this->execute(new RegisterUser\Command(
'leszek.prabucki@gmail.com',
'password'
));
}
}
<?php
declare(strict_types=1);
namespace PHPBenelux\Application\UseCase;
use PHPBenelux\Application\TransactionManager;
use PHPBenelux\Domain\UserFactory;
use PHPBenelux\Domain\Users;
use Throwable;
class RegisterUser
{
private $transactionManager;
private $users;
private $userFactory;
public function __construct(TransactionManager $transactionManager, Users $users, UserFactory $userFactory)
{
$this->transactionManager = $transactionManager;
$this->users = $users;
$this->userFactory = $userFactory;
}
public function execute(RegisterUser\Command $command): void
{
$this->transactionManager->begin();
try {
$user = $this->userFactory->create($command->email(), $command->password());
if ($this->users->has($user)) {
$this->transactionManager->rollback();
return;
}
$this->users->add($user);
$this->transactionManager->commit();
} catch (Throwable $exception) {
$this->transactionManager->rollback();
throw $exception;
}
}
}
<?php
declare(strict_types=1);
namespace PHPBenelux\Adapter\Doctrine\Domain;
use Doctrine\ORM\EntityManagerInterface;
use PHPBenelux\Domain\Email;
use PHPBenelux\Domain\Exception\UserNotFound;
use PHPBenelux\Domain\Users as UsersInterface;
final class Users implements UsersInterface
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function add(User $user): void
{
$this->entityManager->persist($user);
}
public function has(User $user): bool
{
return (bool) $this
->entityManager
->getRepository(User::class)
->findOneBy(['email.email' => (string) $user->email()])
;
}
public function get(Email $email): User
{
/** @var User $user */
$user = $this->entityManager->getRepository(User::class)->findOneBy(['email.email' => (string) $email]);
if (!$user) {
throw new UserNotFound();
}
return $user;
}
}
<?php
declare(strict_types=1);
namespace PHPBenelux\Adapter\Doctrine\Application;
use Doctrine\ORM\EntityManagerInterface;
use PHPBenelux\Application\TransactionManager as TransactionManagerInterface;
final class TransactionManager implements TransactionManagerInterface
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function begin(): void
{
$this->entityManager->beginTransaction();
}
public function commit(): void
{
$this->entityManager->flush();
$this->entityManager->commit();
}
public function rollback(): void
{
$this->entityManager->rollback();
}
}