Skip to content

Converted the generic concept of User entity to a complete aggregate object #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: feature/value-types
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions public/login.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

use Authentication\Value\ClearTextPassword;
use Authentication\Value\EmailAddress;
use Infrastructure\Authentication\ReadModel\HardcodedIsUserBlocked;
use Infrastructure\Authentication\Repository\FilesystemUsers;
use Infrastructure\Authentication\Service\SendNotifyOfIntrusionDetectionToStderr;

require_once __DIR__ . '/../vendor/autoload.php';

Expand All @@ -19,11 +21,7 @@

$user = $users->get($email);

if (! $user->authenticate($password)) {
echo 'Nope';

return;
}
$user->authenticate($password, new HardcodedIsUserBlocked(), new SendNotifyOfIntrusionDetectionToStderr());

echo 'OK';

11 changes: 3 additions & 8 deletions public/register.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
<?php

use Authentication\Entity\User;
use Authentication\Aggregate\User;
use Authentication\Value\ClearTextPassword;
use Authentication\Value\EmailAddress;
use Infrastructure\Authentication\ReadModel\CheckRegisteredEmailFromRepository;
use Infrastructure\Authentication\Repository\FilesystemUsers;

require_once __DIR__ . '/../vendor/autoload.php';
Expand All @@ -12,13 +13,7 @@

$users = new FilesystemUsers(__DIR__ . '/../data/users.json');

if ($users->exists($email)) {
echo 'Already registered';

return;
}

$users->store(new User($email, $password));
$users->store(User::register($email, $password, new CheckRegisteredEmailFromRepository($users)));

/** @var Notifier $notify */
// send email abstraction?
Expand Down
53 changes: 53 additions & 0 deletions src/Authentication/Aggregate/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

namespace Authentication\Aggregate;

use Authentication\ReadModel\EmailIsRegistered;
use Authentication\ReadModel\IsUserBlocked;
use Authentication\Service\NotifyOfIntrusionDetection;
use Authentication\Value\ClearTextPassword;
use Authentication\Value\EmailAddress;
use Authentication\Value\PasswordHash;
use Webmozart\Assert\Assert;

class User
{
/** @var EmailAddress */
private $email;

/** @var PasswordHash */
private $passwordHash;

private function __construct(EmailAddress $email, ClearTextPassword $password)
{
$this->email = $email;
$this->passwordHash = $password->makeHash();
}

public static function register(
EmailAddress $email,
ClearTextPassword $password,
EmailIsRegistered $emailIsRegistered
) : self {
Assert::false($emailIsRegistered->__invoke($email));

return new self($email, $password);
}

public function authenticate(
ClearTextPassword $password,
IsUserBlocked $blockedUsers,
NotifyOfIntrusionDetection $intrusionDetection
) : void {
Assert::true($password->verify($this->passwordHash));

if ($blockedUsers->__invoke($this->email)) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can I ask.. Why magic method __invoke is used, but it is not called like this $blockedUsers($this->email)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question: this is because of an IDE issue. This is a known issue of PHPStorm, which cannot detect usages of callable objects used with the syntax that you proposed.

While it is true that I shouldn't please my tools too much, the IDE is such a massive central point in my day-to-day operations (especially around refactoring/inspection) that I'd rather keep the __invoke explicit until the bug is solved (should already be for PHPStorm 2019.02)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow such a quick reply :) Ok I understand. Thank you!

$intrusionDetection->__invoke($this->email);

throw new \RuntimeException(sprintf(
'User %s is blocked, and cannot authenticate',
$this->email->toString()
));
}
}
}
27 changes: 0 additions & 27 deletions src/Authentication/Entity/User.php

This file was deleted.

10 changes: 10 additions & 0 deletions src/Authentication/ReadModel/EmailIsRegistered.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Authentication\ReadModel;

use Authentication\Value\EmailAddress;

interface EmailIsRegistered
{
public function __invoke(EmailAddress $emailAddress) : bool;
}
10 changes: 10 additions & 0 deletions src/Authentication/ReadModel/IsUserBlocked.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Authentication\ReadModel;

use Authentication\Value\EmailAddress;

interface IsUserBlocked
{
public function __invoke(EmailAddress $emailAddress) : bool;
}
2 changes: 1 addition & 1 deletion src/Authentication/Repository/Users.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Authentication\Repository;

use Authentication\Entity\User;
use Authentication\Aggregate\User;
use Authentication\Value\EmailAddress;

interface Users
Expand Down
10 changes: 10 additions & 0 deletions src/Authentication/Service/NotifyOfIntrusionDetection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

namespace Authentication\Service;

use Authentication\Value\EmailAddress;

interface NotifyOfIntrusionDetection
{
public function __invoke(EmailAddress $emailAddress) : void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

declare(strict_types=1);

namespace Infrastructure\Authentication\ReadModel;

use Authentication\ReadModel\EmailIsRegistered;
use Authentication\Repository\Users;
use Authentication\Value\EmailAddress;

final class CheckRegisteredEmailFromRepository implements EmailIsRegistered
{
/** @var Users */
private $users;

public function __construct(Users $users)
{
$this->users = $users;
}

public function __invoke(EmailAddress $emailAddress) : bool
{
return $this->users->exists($emailAddress);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace Infrastructure\Authentication\ReadModel;

use Authentication\ReadModel\IsUserBlocked;
use Authentication\Value\EmailAddress;

final class HardcodedIsUserBlocked implements IsUserBlocked
{
public function __invoke(EmailAddress $emailAddress) : bool
{
return $emailAddress->toString() === '[email protected]';
}
}
30 changes: 26 additions & 4 deletions src/Infrastructure/Authentication/Repository/FilesystemUsers.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

namespace Infrastructure\Authentication\Repository;

use Authentication\Entity\User;
use Authentication\Aggregate\User;
use Authentication\Repository\Users;
use Authentication\Value\EmailAddress;
use Authentication\Value\PasswordHash;
use ReflectionProperty;

final class FilesystemUsers implements Users
{
Expand Down Expand Up @@ -35,8 +36,8 @@ public function get(EmailAddress $emailAddress) : User
/** @var User $user */
$user = (new \ReflectionClass(User::class))->newInstanceWithoutConstructor();

$user->email = $emailAddress;
$user->passwordHash = PasswordHash::fromHash($existingUsers[$emailAddress->toString()]);
$this->reflectionEmail()->setValue($user, $emailAddress);
$this->reflectionPasswordHash()->setValue($user, PasswordHash::fromHash($existingUsers[$emailAddress->toString()]));

return $user;
}
Expand All @@ -45,7 +46,9 @@ public function store(User $user) : void
{
$existingUsers = $this->existingUsers();

$existingUsers[$user->email->toString()] = $user->passwordHash->toString();
$existingUsers[
$this->reflectionEmail()->getValue($user)->toString()
] = $this->reflectionPasswordHash()->getValue($user)->toString();

file_put_contents($this->usersPath, json_encode($existingUsers));
}
Expand All @@ -59,4 +62,23 @@ private function existingUsers() : array

return json_decode($fileContentsReallyReally, true);
}

private function reflectionEmail() : ReflectionProperty
{
$reflectionEmail = new ReflectionProperty(User::class, 'email');

$reflectionEmail->setAccessible(true);

return $reflectionEmail;
}

private function reflectionPasswordHash() : ReflectionProperty
{
$reflectionEmail = new ReflectionProperty(User::class, 'passwordHash');

$reflectionEmail->setAccessible(true);

return $reflectionEmail;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Infrastructure\Authentication\Service;

use Authentication\Service\NotifyOfIntrusionDetection;
use Authentication\Value\EmailAddress;

final class SendNotifyOfIntrusionDetectionToStderr implements NotifyOfIntrusionDetection
{
public function __invoke(EmailAddress $emailAddress) : void
{
error_log(sprintf(
'Intrusion detected: unauthorised user %s',
$emailAddress->toString()
));
}
}