Skip to content

Instantly share code, notes, and snippets.

@fesor
Last active October 11, 2016 11:00
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save fesor/33f041e3f362beff8d0ef977afd1ff66 to your computer and use it in GitHub Desktop.
Save fesor/33f041e3f362beff8d0ef977afd1ff66 to your computer and use it in GitHub Desktop.
User
<?php
class RegisterUserHandler
{
private $encoder;
private $users;
private $notificator;
public function __construct(PasswordEncoder $encoder, UserRepository $users, Notificator $notificator)
{
$this->encoder = $encoder;
$this->users = $users;
$this->notificator = $notificator;
}
public function handle(RegisterUserRequest $request)
{
$user = (new UserBuilder()
->withEmail($request->get('email'))
->withPassword($request->get('password', $this->encoder)
->withName($request->get('name'))
->withBirthDay($request->get('birthday'))
->build();
$this->users->add($user);
$this->notificator->notify(new UserRegisteredNotification($user));
}
}
<?php
namespace App\Domain\User;
class User
{
private $id;
private $email;
private $unconfirmedEmail;
private $emailConfirmationToken;
private $password;
private $passwordsHistory;
private $name;
private $birthDay;
public function __construct(UserBuilder $builder)
{
$this->email = $builder->email();
$this->emailConfirmationToken = uuid4();
$this->password = $builder->password();
$this->passwordsHistory = [];
$this->name = $builder->name();
$this->birthDay = $builder->birthDay();
}
public function changeEmail(string $email, Notificator $notificator)
{
$this->unconfirmedEmail = $email;
$this->resendConfirmationEmail($notificator);
}
public function confirmEmail(string $confirmationToken)
{
if ($this->emailConfirmationToken !== $confirmationToken) {
throw new InvalidConfirmationTokenException();
}
$this->emailConfirmationToken = null;
}
public function resendConfirmationEmail(Notificator $notificator)
{
$this->emailConfirmationToken = uuid4();
$notificator->notify(new ConfirmNewEmailAddressNotification($this->unconfirmedEmail, $this->email));
}
public function changePassword(string $password, PasswordEncoder $encoder)
{
$this->passwordsHistory[] = $this->password;
$this->failIfPasswordAlreadyHasBeenUsed();
$this->password = $encoer->encode($password);
}
private function failIfPasswordAlreadyHasBeenUsed(string $password, PasswordEncoder $encoder)
{
foreach ($this->passwordsHistory as $previousPassword) {
if ($this->encoder->isPasswordValid($previousPassword, $password) {
throw new PasswordIsAlreadyHasBeenUsedException();
}
}
}
}
<?php
class UserBuilder
{
private $email;
private $password;
private $name;
private $birthDay;
public function withEmail($email)
{
$this->email = $email;
return $this;
}
public function withPassword($password, PasswordEncoder $encoder)
{
$this->password = $encoder->encode($password);
return $this;
}
public function withName($name)
{
$this->name = $name;
return $this;
}
public function withBirthDay($birthDay)
{
$this->birthDay = $birthDay;
return $this;
}
/**
* @return mixed
*/
public function email()
{
return $this->email;
}
/**
* @return mixed
*/
public function password()
{
return $this->password;
}
/**
* @return mixed
*/
public function name()
{
return $this->name;
}
/**
* @return mixed
*/
public function birthDay()
{
return $this->birthDay;
}
public function build() : User
{
if (empty($this->email)
|| empty($this->password)
|| empty($this->name) {
throw \InvalidArgumentException('Fields email, password and name are required');
}
return new User($this);
}
}
@dimaxz
Copy link

dimaxz commented Sep 29, 2016

правильно я понимаю UserBuilder отвечает за валидацию данных при создании объекта? почему нельзя сразу создать entity

$user = (new User()
       ->setEmail($request->get('email'))
       ->setPassword($request->get('password', $this->encoder)
       ->setName($request->get('name'))
       ->setBirthDay($request->get('birthday'))
;

@phudinsky
Copy link

думаю, для того, чтобы сущность User никогда не была в невалидном состоянии (например, с установленным емейлом, но не установленным паролем). А билдер в данном случае гарантирует нам создание только валидных сущностей

@dimaxz
Copy link

dimaxz commented Sep 29, 2016

но для этого можно использовать конструктор, то-есть мы не можем создать объект без обязательных свойств

$user = (new User($request->get('name'),$request->get('email'),$request->get('password', $this->encoder))
       ->setBirthDay($request->get('birthday'))
;

@fesor
Copy link
Author

fesor commented Sep 30, 2016

@dimaxz, когда у тебя 10 аргументов в конструкторе - это не дело. Вопервых это не читабельно, во вторых - это не очень гибко (когда у тебя может быть несколько наборов обязательных полей в зависимости от какой-то логики), в третьих - добавь 11-ый аргумент в середину и все сломаешь.

А с билдерами - у тебя все читабельно, правила валидации можно перенести в конструктор, мне просто так не очень нравится, с точки зрения никапсуляции тоже все гуд.

@dimaxz
Copy link

dimaxz commented Oct 6, 2016

@fesor убедительно, но остается такой момент если делаем вставку не валидного значения с последующим сохранением в бд например через маппер, как быть в данном случае делать еще одну валидацию?

$user->setEmail("incorrenct_mail");//или даже $user->setEmail(NULL);
try{
$userMapper->save($user);
}catch(Exception $e){
echo $e->getMessage();
}

@fesor
Copy link
Author

fesor commented Oct 10, 2016

@dimaxz так, вопервых допущение простое - у тебя никогда не должно быть такого кода, который "меняет состояние сущности и делает ее невалидно". Инварианты объекта всегда должны сохраняться.

@itcreator
Copy link

 ->withEmail($request->get('email'))

И на первый взгляд это не сеттер ))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment