Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/267.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/objective-c/26.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php 域对象和数据映射器应该如何在使用MVC的身份验证系统的服务类中交互_Php_Model View Controller_Datamapper_Slim 3_Domain Object - Fatal编程技术网

Php 域对象和数据映射器应该如何在使用MVC的身份验证系统的服务类中交互

Php 域对象和数据映射器应该如何在使用MVC的身份验证系统的服务类中交互,php,model-view-controller,datamapper,slim-3,domain-object,Php,Model View Controller,Datamapper,Slim 3,Domain Object,我正在创建一个身份验证/登录系统,在后端使用Slim 3 PHP,在前端使用Angular,我试图理解MVC结构中模型层的“域对象”和“数据映射器”部分。我已经阅读了很多关于各种问题的有用答案,从中我了解到模型应该由“域对象”、“数据映射器”和“服务”组成 然而,我不确定在用户能够注册和登录到网站的情况下,这应该是什么样的结构 据我所知,我可以拥有一个具有用户名和密码等属性的用户“域对象”。它还可以有注册或登录等方法来表示业务逻辑 然后我会有一个服务类来创建一个用户对象的新实例,在这个实例中我会

我正在创建一个身份验证/登录系统,在后端使用Slim 3 PHP,在前端使用Angular,我试图理解MVC结构中模型层的“域对象”和“数据映射器”部分。我已经阅读了很多关于各种问题的有用答案,从中我了解到模型应该由“域对象”、“数据映射器”和“服务”组成

然而,我不确定在用户能够注册和登录到网站的情况下,这应该是什么样的结构

据我所知,我可以拥有一个具有用户名和密码等属性的用户“域对象”。它还可以有注册或登录等方法来表示业务逻辑

然后我会有一个服务类来创建一个用户对象的新实例,在这个实例中我会将表单数据传递到该对象中吗?那么现在我的用户对象实例将设置用户名和密码值

现在我不确定如何将这些对象属性数据插入数据库。我是否会使用user objects register方法通过传入用户名和密码作为参数将数据插入数据库

显然,服务应该是域对象和数据映射器交互的地方,但是如果register方法在用户域对象中,我不确定这将如何工作

我希望有人能向我展示一些代码示例,说明服务类中应该包含什么,以及域对象和数据映射器之间的交互如何在用户注册和登录的上下文中工作

注意我不想使用任何框架,我想尝试手动实现一个合适的MVC结构,因为我觉得我会学到更多

到目前为止,我有一个注册用户的结构:

我有一个AuthenticationController,其方法registerUser允许用户创建帐户:

 class AuthenticationController
{
    protected $authenticationService;

    public function __construct(AuthenticationService $authenticationService)
    {
        $this->authenticationService = $authenticationService;
    }

    public function registerUser($request, $response)
    {
        $this->authenticationService->registerUser($request, $response);
    }
}
然后使用registerUser方法创建AuthenticationService类:

class AuthenticationService
{
    protected $database;

    public function __construct(PDO $database)
    {
        $this->database = $database;
    }

    public function registerUser ($request, $response)
    {
        $strings = $request→getParsedBody(); // will be sanitised / validated later
        $username = $strings['username'];
        $password = $strings['password'];
        $email = "temp random email";

        $stmt = $this->database->prepare("INSERT INTO users (email, username, password) values (:email, :username, :password)");
        $stmt->bindParam(':email', $email);
        $stmt->bindParam(':username', $username);
        $stmt->bindParam(':password', $password);
        $stmt->execute();
    }
}
稍后,我打算将SQL放入AuthenticationRepository,将PDO逻辑放入它自己的类中。此AuthenticationService方法还将确保使用PHP的内置函数清理用户详细信息

我不确定提议的PDO数据库类或AuthenticationRepository是否算作数据映射器。

  • 注册将由该服务执行
  • 该服务可以“直接”使用,以便将实体“转移”到数据库中或从数据库中“转移”。然而,另外,可以实现。服务将看到它并与它通信,就像与一个或多个实体的集合通信一样
  • 因为服务是模型层(域模型)的一部分,所以它不应该知道任何请求或响应对象。控制器应该从请求中提取所需的值,并将它们作为参数传递给服务方法。响应可以由控制器或视图发回,具体取决于您尝试实现的MVC变体
  • 您会说“我打算将[…]PDO逻辑放到它自己的类中”。您实际上不需要为PDO扩展实现包装器

这里是一个注册示例。我根本没有测试它。有关更多详细信息,请参阅本答案末尾的参考资料列表。也许从最后一个开始,我刚刚意识到,这是你一个问题的答案

使用的文件系统结构:

<?php

namespace MyApp\UI\Web\Controller\Users;

use Psr\Http\Message\ServerRequestInterface;
use MyApp\Domain\Model\Users\Exception\InvalidData;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
use MyApp\Domain\Service\Users\Registration as RegistrationService;

class Registration {

    private $registration;

    public function __construct(RegistrationService $registration) {
        $this->registration = $registration;
    }

    public function register(ServerRequestInterface $request) {
        $username = $request->getParsedBody()['username'];
        $password = $request->getParsedBody()['password'];
        $email = $request->getParsedBody()['email'];

        try {
            $user = $this->registration->register($username, $password, $email);
        } catch (InvalidData $exc) {
            // Write the exception message to a flash messenger, for example, 
            // in order to be read and displayed by the specific view component.
            var_dump($exc->getMessage());
        } catch (FailedRegistration $exc) {
            // Write the exception message to the flash messenger.
            var_dump($exc->getMessage());
        }

        // In the view component, if no exception messages are found in the flash messenger, display a success message.
        var_dump('Successfully registered.');
    }

}
<?php

namespace MyApp\Domain\Service\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Service\Users\Exception\UserExists;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

class Registration {

    /**
     * User collection, e.g. user repository.
     * 
     * @var UserCollectionInterface
     */
    private $userCollection;

    public function __construct(UserCollectionInterface $userCollection) {
        $this->userCollection = $userCollection;
    }

    /**
     * Register user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    public function register(string $username, string $password, string $email) {
        $user = $this->createUser($username, $password, $email);

        return $this->storeUser($user);
    }

    /**
     * Create user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(string $username, string $password, string $email) {
        // Create the object values (containing specific validation).
        $email = new Email($email);
        $password = new Password($password);

        // Create the entity (e.g. the domain object).
        $user = new User();

        $user->setUsername($username);
        $user->setEmail($email);
        $user->setPassword($password);

        return $user;
    }

    /**
     * Store user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function storeUser(User $user) {
        // Check if user already exists.
        if ($this->userCollection->exists($user)) {
            throw new UserExists();
        }

        return $this->userCollection->store($user);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;

/**
 * User entity (e.g. domain object).
 */
class User {

    private $id;
    private $username;
    private $email;
    private $password;

    public function getId() {
        return $this->id;
    }

    public function setId(int id) {
        $this->id = $id;
        return $this;
    }

    public function getUsername() {
        return $this->username;
    }

    public function setUsername(string $username) {
        $this->username = $username;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setEmail(Email $email) {
        $this->email = $email;
        return $this;
    }

    public function getPassword() {
        return $this->password;
    }

    public function setPassword(Password $password) {
        $this->password = $password;
        return $this;
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidEmail;

/**
 * Email object value.
 */
class Email {

    private $email;

    public function __construct(string $email) {
        if (!$this->isValid($email)) {
            throw new InvalidEmail();
        }

        $this->email = $email;
    }

    private function isValid(string $email) {
        return (isEmpty($email) || !isWellFormed($email)) ? false : true;
    }

    private function isEmpty(string $email) {
        return empty($email) ? true : false;
    }

    private function isWellFormed(string $email) {
        return !filter_var($email, FILTER_VALIDATE_EMAIL) ? false : true;
    }

    public function __toString() {
        return $this->email;
    }

}

<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidPassword;

/**
 * Password object value.
 */
class Password {

    private const MIN_LENGTH = 8;

    private $password;


    public function __construct(string $password) {
        if (!$this->isValid($password)) {
            throw new InvalidPassword();
        }

        $this->password = $password;
    }

    private function isValid(string $password) {
        return (isEmpty($password) || isTooShort($password)) ? false : true;
    }

    private function isEmpty(string $password) {
        return empty($password) ? true : false;
    }

    private function isTooShort(string $password) {
        return strlen($password) < self::MIN_LENGTH ? true : false;
    }

    public function __toString() {
        return $this->password;
    }

}
<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidEmail extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The email address is not valid.';
        $code = 123402;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidPassword extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The password is not valid.';
        $code = 123401;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

abstract class InvalidData extends \LogicException {

    public function __construct(string $message, int $code = 0, \Exception $previous = null) {
        $message = 'Invalid data: ' . $message;

        parent::__construct($message, $code, $previous);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User collection, e.g. user repository.
 */
interface UserCollection {

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id);

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user);

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user);

}
<?php

namespace MyApp\Domain\Infrastructure\Repository\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

/**
 * User collection, e.g. user repository.
 */
class UserCollection implements UserCollectionInterface {

    private $userMapper;

    public function __construct(UserMapper $userMapper) {
        $this->userMapper = $userMapper;
    }

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id) {
        return $this->userMapper->fetchUserById($id);
    }

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll() {
        return $this->userMapper->fetchAllUsers();
    }

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user) {
        return $this->userMapper->userExists($user);
    }

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user) {
        return $this->userMapper->saveUser($user);
    }

}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User mapper.
 */
interface UserMapper {

    /**
     * Fetch a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id);

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user);

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user);
}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use PDO;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;

/**
 * PDO user mapper.
 */
class PdoUserMapper implements UserMapper {

    /**
     * Database connection.
     * 
     * @var PDO
     */
    private $connection;

    public function __construct(PDO $connection) {
        $this->connection = $connection;
    }

    /**
     * Fetch a user by id.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id) {
        $sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            'id' => $id,
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record === false) ? null : $this->convertRecordToUser($record);
    }

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers() {
        $sql = 'SELECT * FROM users';

        $statement = $this->connection->prepare($sql);
        $statement->execute();

        $recordset = $statement->fetchAll(PDO::FETCH_ASSOC);

        return $this->convertRecordsetToUserList($recordset);
    }

    /**
     * Check if the given user exists.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user) {
        $sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record['cnt'] > 0) ? true : false;
    }

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user) {
        $id = $user->getId();

        if (!isset($id)) {
            return $this->insertUser($user);
        }

        return $this->updateUser($user);
    }

    /**
     * Insert a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function insertUser(User $user) {
        $sql = 'INSERT INTO users (
                    username,
                    password,
                    email
                ) VALUES (
                    :username,
                    :password,
                    :email
                )';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        $user->setId($this->connection->lastInsertId());

        return $user;
    }

    /**
     * Update a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function updateUser(User $user) {
        $sql = 'UPDATE users 
                SET 
                    username = :username,
                    password = :password,
                    email = :email 
                WHERE id = :id';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':id' => $user->getId(),
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        return $user;
    }

    /**
     * Convert a record to a user.
     * 
     * @param array $record Record data.
     * @return User User.
     */
    private function convertRecordToUser(array $record) {
        $user = $this->createUser(
                    $record['id'],
                    $record['username'],
                    $record['password'],
                    $record['email']
                );

        return $user;
    }

    /**
     * Convert a recordset to a list of users.
     * 
     * @param array $recordset Recordset data.
     * @return User[] User list.
     */
    private function convertRecordsetToUserList(array $recordset) {
        $users = [];

        foreach ($recordset as $record) {
            $users[] = $this->convertRecordToUser($record);
        }

        return $users;
    }

    /**
     * Create user.
     *
     * @param int $id User id.
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(int $id, string $username, string $password, string $email) {
        $user = new User();

        $user
            ->setId($id)
            ->setUsername($username)
            ->setPassword(new Password($password))
            ->setEmail(new Email($email))
        ;

        return $user;
    }

}
a)扩展的“MyApp/UI”:

<?php

namespace MyApp\UI\Web\Controller\Users;

use Psr\Http\Message\ServerRequestInterface;
use MyApp\Domain\Model\Users\Exception\InvalidData;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
use MyApp\Domain\Service\Users\Registration as RegistrationService;

class Registration {

    private $registration;

    public function __construct(RegistrationService $registration) {
        $this->registration = $registration;
    }

    public function register(ServerRequestInterface $request) {
        $username = $request->getParsedBody()['username'];
        $password = $request->getParsedBody()['password'];
        $email = $request->getParsedBody()['email'];

        try {
            $user = $this->registration->register($username, $password, $email);
        } catch (InvalidData $exc) {
            // Write the exception message to a flash messenger, for example, 
            // in order to be read and displayed by the specific view component.
            var_dump($exc->getMessage());
        } catch (FailedRegistration $exc) {
            // Write the exception message to the flash messenger.
            var_dump($exc->getMessage());
        }

        // In the view component, if no exception messages are found in the flash messenger, display a success message.
        var_dump('Successfully registered.');
    }

}
<?php

namespace MyApp\Domain\Service\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Service\Users\Exception\UserExists;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

class Registration {

    /**
     * User collection, e.g. user repository.
     * 
     * @var UserCollectionInterface
     */
    private $userCollection;

    public function __construct(UserCollectionInterface $userCollection) {
        $this->userCollection = $userCollection;
    }

    /**
     * Register user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    public function register(string $username, string $password, string $email) {
        $user = $this->createUser($username, $password, $email);

        return $this->storeUser($user);
    }

    /**
     * Create user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(string $username, string $password, string $email) {
        // Create the object values (containing specific validation).
        $email = new Email($email);
        $password = new Password($password);

        // Create the entity (e.g. the domain object).
        $user = new User();

        $user->setUsername($username);
        $user->setEmail($email);
        $user->setPassword($password);

        return $user;
    }

    /**
     * Store user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function storeUser(User $user) {
        // Check if user already exists.
        if ($this->userCollection->exists($user)) {
            throw new UserExists();
        }

        return $this->userCollection->store($user);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;

/**
 * User entity (e.g. domain object).
 */
class User {

    private $id;
    private $username;
    private $email;
    private $password;

    public function getId() {
        return $this->id;
    }

    public function setId(int id) {
        $this->id = $id;
        return $this;
    }

    public function getUsername() {
        return $this->username;
    }

    public function setUsername(string $username) {
        $this->username = $username;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setEmail(Email $email) {
        $this->email = $email;
        return $this;
    }

    public function getPassword() {
        return $this->password;
    }

    public function setPassword(Password $password) {
        $this->password = $password;
        return $this;
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidEmail;

/**
 * Email object value.
 */
class Email {

    private $email;

    public function __construct(string $email) {
        if (!$this->isValid($email)) {
            throw new InvalidEmail();
        }

        $this->email = $email;
    }

    private function isValid(string $email) {
        return (isEmpty($email) || !isWellFormed($email)) ? false : true;
    }

    private function isEmpty(string $email) {
        return empty($email) ? true : false;
    }

    private function isWellFormed(string $email) {
        return !filter_var($email, FILTER_VALIDATE_EMAIL) ? false : true;
    }

    public function __toString() {
        return $this->email;
    }

}

<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidPassword;

/**
 * Password object value.
 */
class Password {

    private const MIN_LENGTH = 8;

    private $password;


    public function __construct(string $password) {
        if (!$this->isValid($password)) {
            throw new InvalidPassword();
        }

        $this->password = $password;
    }

    private function isValid(string $password) {
        return (isEmpty($password) || isTooShort($password)) ? false : true;
    }

    private function isEmpty(string $password) {
        return empty($password) ? true : false;
    }

    private function isTooShort(string $password) {
        return strlen($password) < self::MIN_LENGTH ? true : false;
    }

    public function __toString() {
        return $this->password;
    }

}
<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidEmail extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The email address is not valid.';
        $code = 123402;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidPassword extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The password is not valid.';
        $code = 123401;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

abstract class InvalidData extends \LogicException {

    public function __construct(string $message, int $code = 0, \Exception $previous = null) {
        $message = 'Invalid data: ' . $message;

        parent::__construct($message, $code, $previous);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User collection, e.g. user repository.
 */
interface UserCollection {

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id);

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user);

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user);

}
<?php

namespace MyApp\Domain\Infrastructure\Repository\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

/**
 * User collection, e.g. user repository.
 */
class UserCollection implements UserCollectionInterface {

    private $userMapper;

    public function __construct(UserMapper $userMapper) {
        $this->userMapper = $userMapper;
    }

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id) {
        return $this->userMapper->fetchUserById($id);
    }

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll() {
        return $this->userMapper->fetchAllUsers();
    }

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user) {
        return $this->userMapper->userExists($user);
    }

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user) {
        return $this->userMapper->saveUser($user);
    }

}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User mapper.
 */
interface UserMapper {

    /**
     * Fetch a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id);

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user);

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user);
}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use PDO;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;

/**
 * PDO user mapper.
 */
class PdoUserMapper implements UserMapper {

    /**
     * Database connection.
     * 
     * @var PDO
     */
    private $connection;

    public function __construct(PDO $connection) {
        $this->connection = $connection;
    }

    /**
     * Fetch a user by id.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id) {
        $sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            'id' => $id,
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record === false) ? null : $this->convertRecordToUser($record);
    }

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers() {
        $sql = 'SELECT * FROM users';

        $statement = $this->connection->prepare($sql);
        $statement->execute();

        $recordset = $statement->fetchAll(PDO::FETCH_ASSOC);

        return $this->convertRecordsetToUserList($recordset);
    }

    /**
     * Check if the given user exists.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user) {
        $sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record['cnt'] > 0) ? true : false;
    }

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user) {
        $id = $user->getId();

        if (!isset($id)) {
            return $this->insertUser($user);
        }

        return $this->updateUser($user);
    }

    /**
     * Insert a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function insertUser(User $user) {
        $sql = 'INSERT INTO users (
                    username,
                    password,
                    email
                ) VALUES (
                    :username,
                    :password,
                    :email
                )';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        $user->setId($this->connection->lastInsertId());

        return $user;
    }

    /**
     * Update a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function updateUser(User $user) {
        $sql = 'UPDATE users 
                SET 
                    username = :username,
                    password = :password,
                    email = :email 
                WHERE id = :id';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':id' => $user->getId(),
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        return $user;
    }

    /**
     * Convert a record to a user.
     * 
     * @param array $record Record data.
     * @return User User.
     */
    private function convertRecordToUser(array $record) {
        $user = $this->createUser(
                    $record['id'],
                    $record['username'],
                    $record['password'],
                    $record['email']
                );

        return $user;
    }

    /**
     * Convert a recordset to a list of users.
     * 
     * @param array $recordset Recordset data.
     * @return User[] User list.
     */
    private function convertRecordsetToUserList(array $recordset) {
        $users = [];

        foreach ($recordset as $record) {
            $users[] = $this->convertRecordToUser($record);
        }

        return $users;
    }

    /**
     * Create user.
     *
     * @param int $id User id.
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(int $id, string $username, string $password, string $email) {
        $user = new User();

        $user
            ->setId($id)
            ->setUsername($username)
            ->setPassword(new Password($password))
            ->setEmail(new Email($email))
        ;

        return $user;
    }

}

b)扩展的“MyApp/Domain”:

<?php

namespace MyApp\UI\Web\Controller\Users;

use Psr\Http\Message\ServerRequestInterface;
use MyApp\Domain\Model\Users\Exception\InvalidData;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
use MyApp\Domain\Service\Users\Registration as RegistrationService;

class Registration {

    private $registration;

    public function __construct(RegistrationService $registration) {
        $this->registration = $registration;
    }

    public function register(ServerRequestInterface $request) {
        $username = $request->getParsedBody()['username'];
        $password = $request->getParsedBody()['password'];
        $email = $request->getParsedBody()['email'];

        try {
            $user = $this->registration->register($username, $password, $email);
        } catch (InvalidData $exc) {
            // Write the exception message to a flash messenger, for example, 
            // in order to be read and displayed by the specific view component.
            var_dump($exc->getMessage());
        } catch (FailedRegistration $exc) {
            // Write the exception message to the flash messenger.
            var_dump($exc->getMessage());
        }

        // In the view component, if no exception messages are found in the flash messenger, display a success message.
        var_dump('Successfully registered.');
    }

}
<?php

namespace MyApp\Domain\Service\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Service\Users\Exception\UserExists;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

class Registration {

    /**
     * User collection, e.g. user repository.
     * 
     * @var UserCollectionInterface
     */
    private $userCollection;

    public function __construct(UserCollectionInterface $userCollection) {
        $this->userCollection = $userCollection;
    }

    /**
     * Register user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    public function register(string $username, string $password, string $email) {
        $user = $this->createUser($username, $password, $email);

        return $this->storeUser($user);
    }

    /**
     * Create user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(string $username, string $password, string $email) {
        // Create the object values (containing specific validation).
        $email = new Email($email);
        $password = new Password($password);

        // Create the entity (e.g. the domain object).
        $user = new User();

        $user->setUsername($username);
        $user->setEmail($email);
        $user->setPassword($password);

        return $user;
    }

    /**
     * Store user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function storeUser(User $user) {
        // Check if user already exists.
        if ($this->userCollection->exists($user)) {
            throw new UserExists();
        }

        return $this->userCollection->store($user);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;

/**
 * User entity (e.g. domain object).
 */
class User {

    private $id;
    private $username;
    private $email;
    private $password;

    public function getId() {
        return $this->id;
    }

    public function setId(int id) {
        $this->id = $id;
        return $this;
    }

    public function getUsername() {
        return $this->username;
    }

    public function setUsername(string $username) {
        $this->username = $username;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setEmail(Email $email) {
        $this->email = $email;
        return $this;
    }

    public function getPassword() {
        return $this->password;
    }

    public function setPassword(Password $password) {
        $this->password = $password;
        return $this;
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidEmail;

/**
 * Email object value.
 */
class Email {

    private $email;

    public function __construct(string $email) {
        if (!$this->isValid($email)) {
            throw new InvalidEmail();
        }

        $this->email = $email;
    }

    private function isValid(string $email) {
        return (isEmpty($email) || !isWellFormed($email)) ? false : true;
    }

    private function isEmpty(string $email) {
        return empty($email) ? true : false;
    }

    private function isWellFormed(string $email) {
        return !filter_var($email, FILTER_VALIDATE_EMAIL) ? false : true;
    }

    public function __toString() {
        return $this->email;
    }

}

<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidPassword;

/**
 * Password object value.
 */
class Password {

    private const MIN_LENGTH = 8;

    private $password;


    public function __construct(string $password) {
        if (!$this->isValid($password)) {
            throw new InvalidPassword();
        }

        $this->password = $password;
    }

    private function isValid(string $password) {
        return (isEmpty($password) || isTooShort($password)) ? false : true;
    }

    private function isEmpty(string $password) {
        return empty($password) ? true : false;
    }

    private function isTooShort(string $password) {
        return strlen($password) < self::MIN_LENGTH ? true : false;
    }

    public function __toString() {
        return $this->password;
    }

}
<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidEmail extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The email address is not valid.';
        $code = 123402;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidPassword extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The password is not valid.';
        $code = 123401;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

abstract class InvalidData extends \LogicException {

    public function __construct(string $message, int $code = 0, \Exception $previous = null) {
        $message = 'Invalid data: ' . $message;

        parent::__construct($message, $code, $previous);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User collection, e.g. user repository.
 */
interface UserCollection {

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id);

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user);

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user);

}
<?php

namespace MyApp\Domain\Infrastructure\Repository\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

/**
 * User collection, e.g. user repository.
 */
class UserCollection implements UserCollectionInterface {

    private $userMapper;

    public function __construct(UserMapper $userMapper) {
        $this->userMapper = $userMapper;
    }

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id) {
        return $this->userMapper->fetchUserById($id);
    }

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll() {
        return $this->userMapper->fetchAllUsers();
    }

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user) {
        return $this->userMapper->userExists($user);
    }

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user) {
        return $this->userMapper->saveUser($user);
    }

}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User mapper.
 */
interface UserMapper {

    /**
     * Fetch a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id);

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user);

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user);
}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use PDO;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;

/**
 * PDO user mapper.
 */
class PdoUserMapper implements UserMapper {

    /**
     * Database connection.
     * 
     * @var PDO
     */
    private $connection;

    public function __construct(PDO $connection) {
        $this->connection = $connection;
    }

    /**
     * Fetch a user by id.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id) {
        $sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            'id' => $id,
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record === false) ? null : $this->convertRecordToUser($record);
    }

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers() {
        $sql = 'SELECT * FROM users';

        $statement = $this->connection->prepare($sql);
        $statement->execute();

        $recordset = $statement->fetchAll(PDO::FETCH_ASSOC);

        return $this->convertRecordsetToUserList($recordset);
    }

    /**
     * Check if the given user exists.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user) {
        $sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record['cnt'] > 0) ? true : false;
    }

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user) {
        $id = $user->getId();

        if (!isset($id)) {
            return $this->insertUser($user);
        }

        return $this->updateUser($user);
    }

    /**
     * Insert a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function insertUser(User $user) {
        $sql = 'INSERT INTO users (
                    username,
                    password,
                    email
                ) VALUES (
                    :username,
                    :password,
                    :email
                )';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        $user->setId($this->connection->lastInsertId());

        return $user;
    }

    /**
     * Update a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function updateUser(User $user) {
        $sql = 'UPDATE users 
                SET 
                    username = :username,
                    password = :password,
                    email = :email 
                WHERE id = :id';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':id' => $user->getId(),
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        return $user;
    }

    /**
     * Convert a record to a user.
     * 
     * @param array $record Record data.
     * @return User User.
     */
    private function convertRecordToUser(array $record) {
        $user = $this->createUser(
                    $record['id'],
                    $record['username'],
                    $record['password'],
                    $record['email']
                );

        return $user;
    }

    /**
     * Convert a recordset to a list of users.
     * 
     * @param array $recordset Recordset data.
     * @return User[] User list.
     */
    private function convertRecordsetToUserList(array $recordset) {
        $users = [];

        foreach ($recordset as $record) {
            $users[] = $this->convertRecordToUser($record);
        }

        return $users;
    }

    /**
     * Create user.
     *
     * @param int $id User id.
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(int $id, string $username, string $password, string $email) {
        $user = new User();

        $user
            ->setId($id)
            ->setUsername($username)
            ->setPassword(new Password($password))
            ->setEmail(new Email($email))
        ;

        return $user;
    }

}


控制器:

<?php

namespace MyApp\UI\Web\Controller\Users;

use Psr\Http\Message\ServerRequestInterface;
use MyApp\Domain\Model\Users\Exception\InvalidData;
use MyApp\Domain\Service\Users\Exception\FailedRegistration;
use MyApp\Domain\Service\Users\Registration as RegistrationService;

class Registration {

    private $registration;

    public function __construct(RegistrationService $registration) {
        $this->registration = $registration;
    }

    public function register(ServerRequestInterface $request) {
        $username = $request->getParsedBody()['username'];
        $password = $request->getParsedBody()['password'];
        $email = $request->getParsedBody()['email'];

        try {
            $user = $this->registration->register($username, $password, $email);
        } catch (InvalidData $exc) {
            // Write the exception message to a flash messenger, for example, 
            // in order to be read and displayed by the specific view component.
            var_dump($exc->getMessage());
        } catch (FailedRegistration $exc) {
            // Write the exception message to the flash messenger.
            var_dump($exc->getMessage());
        }

        // In the view component, if no exception messages are found in the flash messenger, display a success message.
        var_dump('Successfully registered.');
    }

}
<?php

namespace MyApp\Domain\Service\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Service\Users\Exception\UserExists;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

class Registration {

    /**
     * User collection, e.g. user repository.
     * 
     * @var UserCollectionInterface
     */
    private $userCollection;

    public function __construct(UserCollectionInterface $userCollection) {
        $this->userCollection = $userCollection;
    }

    /**
     * Register user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    public function register(string $username, string $password, string $email) {
        $user = $this->createUser($username, $password, $email);

        return $this->storeUser($user);
    }

    /**
     * Create user.
     * 
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(string $username, string $password, string $email) {
        // Create the object values (containing specific validation).
        $email = new Email($email);
        $password = new Password($password);

        // Create the entity (e.g. the domain object).
        $user = new User();

        $user->setUsername($username);
        $user->setEmail($email);
        $user->setPassword($password);

        return $user;
    }

    /**
     * Store user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function storeUser(User $user) {
        // Check if user already exists.
        if ($this->userCollection->exists($user)) {
            throw new UserExists();
        }

        return $this->userCollection->store($user);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;

/**
 * User entity (e.g. domain object).
 */
class User {

    private $id;
    private $username;
    private $email;
    private $password;

    public function getId() {
        return $this->id;
    }

    public function setId(int id) {
        $this->id = $id;
        return $this;
    }

    public function getUsername() {
        return $this->username;
    }

    public function setUsername(string $username) {
        $this->username = $username;
        return $this;
    }

    public function getEmail() {
        return $this->email;
    }

    public function setEmail(Email $email) {
        $this->email = $email;
        return $this;
    }

    public function getPassword() {
        return $this->password;
    }

    public function setPassword(Password $password) {
        $this->password = $password;
        return $this;
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidEmail;

/**
 * Email object value.
 */
class Email {

    private $email;

    public function __construct(string $email) {
        if (!$this->isValid($email)) {
            throw new InvalidEmail();
        }

        $this->email = $email;
    }

    private function isValid(string $email) {
        return (isEmpty($email) || !isWellFormed($email)) ? false : true;
    }

    private function isEmpty(string $email) {
        return empty($email) ? true : false;
    }

    private function isWellFormed(string $email) {
        return !filter_var($email, FILTER_VALIDATE_EMAIL) ? false : true;
    }

    public function __toString() {
        return $this->email;
    }

}

<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\Exception\InvalidPassword;

/**
 * Password object value.
 */
class Password {

    private const MIN_LENGTH = 8;

    private $password;


    public function __construct(string $password) {
        if (!$this->isValid($password)) {
            throw new InvalidPassword();
        }

        $this->password = $password;
    }

    private function isValid(string $password) {
        return (isEmpty($password) || isTooShort($password)) ? false : true;
    }

    private function isEmpty(string $password) {
        return empty($password) ? true : false;
    }

    private function isTooShort(string $password) {
        return strlen($password) < self::MIN_LENGTH ? true : false;
    }

    public function __toString() {
        return $this->password;
    }

}
<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidEmail extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The email address is not valid.';
        $code = 123402;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

use MyApp\Domain\Model\Users\Exception\InvalidData;

class InvalidPassword extends InvalidData {

    public function __construct(\Exception $previous = null) {
        $message = 'The password is not valid.';
        $code = 123401;

        parent::__construct($message, $code, $previous);
    }

}

<?php

namespace MyApp\Domain\Model\Users\Exception;

abstract class InvalidData extends \LogicException {

    public function __construct(string $message, int $code = 0, \Exception $previous = null) {
        $message = 'Invalid data: ' . $message;

        parent::__construct($message, $code, $previous);
    }

}
<?php

namespace MyApp\Domain\Model\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User collection, e.g. user repository.
 */
interface UserCollection {

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id);

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user);

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user);

}
<?php

namespace MyApp\Domain\Infrastructure\Repository\Users;

use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;
use MyApp\Domain\Model\Users\UserCollection as UserCollectionInterface;

/**
 * User collection, e.g. user repository.
 */
class UserCollection implements UserCollectionInterface {

    private $userMapper;

    public function __construct(UserMapper $userMapper) {
        $this->userMapper = $userMapper;
    }

    /**
     * Find a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function findById(int $id) {
        return $this->userMapper->fetchUserById($id);
    }

    /**
     * Find all users.
     * 
     * @return User[] User list.
     */
    public function findAll() {
        return $this->userMapper->fetchAllUsers();
    }

    /**
     * Check if the given user exists.
     * 
     * @param User $user User
     * @return bool True if user exists, false otherwise.
     */
    public function exists(User $user) {
        return $this->userMapper->userExists($user);
    }

    /**
     * Store a user.
     * 
     * @param User $user User
     * @return User User.
     */
    public function store(User $user) {
        return $this->userMapper->saveUser($user);
    }

}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use MyApp\Domain\Model\Users\User;

/**
 * User mapper.
 */
interface UserMapper {

    /**
     * Fetch a user by id.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id);

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers();

    /**
     * Check if the given user exists.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user);

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user);
}
<?php

namespace MyApp\Domain\Infrastructure\Mapper\Users;

use PDO;
use MyApp\Domain\Model\Users\User;
use MyApp\Domain\Model\Users\Email;
use MyApp\Domain\Model\Users\Password;
use MyApp\Domain\Infrastructure\Mapper\Users\UserMapper;

/**
 * PDO user mapper.
 */
class PdoUserMapper implements UserMapper {

    /**
     * Database connection.
     * 
     * @var PDO
     */
    private $connection;

    public function __construct(PDO $connection) {
        $this->connection = $connection;
    }

    /**
     * Fetch a user by id.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param int $id User id.
     * @return User|null User.
     */
    public function fetchUserById(int $id) {
        $sql = 'SELECT * FROM users WHERE id = :id LIMIT 1';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            'id' => $id,
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record === false) ? null : $this->convertRecordToUser($record);
    }

    /**
     * Fetch all users.
     * 
     * @return User[] User list.
     */
    public function fetchAllUsers() {
        $sql = 'SELECT * FROM users';

        $statement = $this->connection->prepare($sql);
        $statement->execute();

        $recordset = $statement->fetchAll(PDO::FETCH_ASSOC);

        return $this->convertRecordsetToUserList($recordset);
    }

    /**
     * Check if the given user exists.
     * 
     * Note: PDOStatement::fetch returns FALSE if no record is found.
     * 
     * @param User $user User.
     * @return bool True if the user exists, false otherwise.
     */
    public function userExists(User $user) {
        $sql = 'SELECT COUNT(*) as cnt FROM users WHERE username = :username';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
        ]);

        $record = $statement->fetch(PDO::FETCH_ASSOC);

        return ($record['cnt'] > 0) ? true : false;
    }

    /**
     * Save a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    public function saveUser(User $user) {
        $id = $user->getId();

        if (!isset($id)) {
            return $this->insertUser($user);
        }

        return $this->updateUser($user);
    }

    /**
     * Insert a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function insertUser(User $user) {
        $sql = 'INSERT INTO users (
                    username,
                    password,
                    email
                ) VALUES (
                    :username,
                    :password,
                    :email
                )';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        $user->setId($this->connection->lastInsertId());

        return $user;
    }

    /**
     * Update a user.
     * 
     * @param User $user User.
     * @return User User.
     */
    private function updateUser(User $user) {
        $sql = 'UPDATE users 
                SET 
                    username = :username,
                    password = :password,
                    email = :email 
                WHERE id = :id';

        $statement = $this->connection->prepare($sql);
        $statement->execute([
            ':id' => $user->getId(),
            ':username' => $user->getUsername(),
            ':password' => (string) $user->getPassword(),
            ':email' => (string) $user->getEmail(),
        ]);

        return $user;
    }

    /**
     * Convert a record to a user.
     * 
     * @param array $record Record data.
     * @return User User.
     */
    private function convertRecordToUser(array $record) {
        $user = $this->createUser(
                    $record['id'],
                    $record['username'],
                    $record['password'],
                    $record['email']
                );

        return $user;
    }

    /**
     * Convert a recordset to a list of users.
     * 
     * @param array $recordset Recordset data.
     * @return User[] User list.
     */
    private function convertRecordsetToUserList(array $recordset) {
        $users = [];

        foreach ($recordset as $record) {
            $users[] = $this->convertRecordToUser($record);
        }

        return $users;
    }

    /**
     * Create user.
     *
     * @param int $id User id.
     * @param string $username Username.
     * @param string $password Password.
     * @param string $email Email.
     * @return User User.
     */
    private function createUser(int $id, string $username, string $password, string $email) {
        $user = new User();

        $user
            ->setId($id)
            ->setUsername($username)
            ->setPassword(new Password($password))
            ->setEmail(new Email($email))
        ;

        return $user;
    }

}

谢谢你这么做,这非常有用。我想知道,将验证移到使用angular的前端是否可行?输入验证似乎是一个前端工作。不过,我会在api端保留密码散列和用户输入信息。不客气。就个人而言,我会尝试在前端和后端验证用户输入。我不确定你对用户输入卫生的理解。我知道卫生会从输入中去除不需要的字符,我使用PHP的“FILTER_SANITIZE_EMAIL”和“FILTER_SANITIZE_STRING”来实现这一点。但是关于前端和后端的验证,他们是否都应该说确保电子邮件以相同的方式正确格式化?两者都穿似乎有点毫无意义。如果他们在前端突破了验证,后端还有一道屏障作为第二道防线吗?我的错。让我纠正我之前的评论:“我会尝试在前端和后端验证用户输入,但几乎不会只在客户端验证”。通过使用“几乎”,我认为,您可能正在为您工作的公司开发一个内部网,并且您肯定知道,用户是值得信赖的,并且将始终启用客户端库。尽管如此,即使这种情况也不能完全保证。有了“……后端仍然存在障碍”,您正确地认识到了使用这两种方法的原因。事实上,“两者兼而有之似乎有点毫无意义”。将特定的客户端库用于特定的输入数据,而使用另一个服务器端库用于相同数据的服务器端验证是毫无意义的。简言之,仅仅进行一次彻底的服务器端验证就足够了。