Initial project structure with Slim skeleton
This commit is contained in:
91
src/Application/Actions/Action.php
Normal file
91
src/Application/Actions/Action.php
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Actions;
|
||||
|
||||
use App\Domain\DomainException\DomainRecordNotFoundException;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Slim\Exception\HttpBadRequestException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
|
||||
abstract class Action
|
||||
{
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
protected Request $request;
|
||||
|
||||
protected Response $response;
|
||||
|
||||
protected array $args;
|
||||
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HttpNotFoundException
|
||||
* @throws HttpBadRequestException
|
||||
*/
|
||||
public function __invoke(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
$this->args = $args;
|
||||
|
||||
try {
|
||||
return $this->action();
|
||||
} catch (DomainRecordNotFoundException $e) {
|
||||
throw new HttpNotFoundException($this->request, $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws DomainRecordNotFoundException
|
||||
* @throws HttpBadRequestException
|
||||
*/
|
||||
abstract protected function action(): Response;
|
||||
|
||||
/**
|
||||
* @return array|object
|
||||
*/
|
||||
protected function getFormData()
|
||||
{
|
||||
return $this->request->getParsedBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @throws HttpBadRequestException
|
||||
*/
|
||||
protected function resolveArg(string $name)
|
||||
{
|
||||
if (!isset($this->args[$name])) {
|
||||
throw new HttpBadRequestException($this->request, "Could not resolve argument `{$name}`.");
|
||||
}
|
||||
|
||||
return $this->args[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array|object|null $data
|
||||
*/
|
||||
protected function respondWithData($data = null, int $statusCode = 200): Response
|
||||
{
|
||||
$payload = new ActionPayload($statusCode, $data);
|
||||
|
||||
return $this->respond($payload);
|
||||
}
|
||||
|
||||
protected function respond(ActionPayload $payload): Response
|
||||
{
|
||||
$json = json_encode($payload, JSON_PRETTY_PRINT);
|
||||
$this->response->getBody()->write($json);
|
||||
|
||||
return $this->response
|
||||
->withHeader('Content-Type', 'application/json')
|
||||
->withStatus($payload->getStatusCode());
|
||||
}
|
||||
}
|
62
src/Application/Actions/ActionError.php
Normal file
62
src/Application/Actions/ActionError.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Actions;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class ActionError implements JsonSerializable
|
||||
{
|
||||
public const BAD_REQUEST = 'BAD_REQUEST';
|
||||
public const INSUFFICIENT_PRIVILEGES = 'INSUFFICIENT_PRIVILEGES';
|
||||
public const NOT_ALLOWED = 'NOT_ALLOWED';
|
||||
public const NOT_IMPLEMENTED = 'NOT_IMPLEMENTED';
|
||||
public const RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND';
|
||||
public const SERVER_ERROR = 'SERVER_ERROR';
|
||||
public const UNAUTHENTICATED = 'UNAUTHENTICATED';
|
||||
public const VALIDATION_ERROR = 'VALIDATION_ERROR';
|
||||
public const VERIFICATION_ERROR = 'VERIFICATION_ERROR';
|
||||
|
||||
private string $type;
|
||||
|
||||
private string $description;
|
||||
|
||||
public function __construct(string $type, ?string $description)
|
||||
{
|
||||
$this->type = $type;
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function setDescription(?string $description = null): self
|
||||
{
|
||||
$this->description = $description;
|
||||
return $this;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$payload = [
|
||||
'type' => $this->type,
|
||||
'description' => $this->description,
|
||||
];
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
62
src/Application/Actions/ActionPayload.php
Normal file
62
src/Application/Actions/ActionPayload.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Actions;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class ActionPayload implements JsonSerializable
|
||||
{
|
||||
private int $statusCode;
|
||||
|
||||
/**
|
||||
* @var array|object|null
|
||||
*/
|
||||
private $data;
|
||||
|
||||
private ?ActionError $error;
|
||||
|
||||
public function __construct(
|
||||
int $statusCode = 200,
|
||||
$data = null,
|
||||
?ActionError $error = null
|
||||
) {
|
||||
$this->statusCode = $statusCode;
|
||||
$this->data = $data;
|
||||
$this->error = $error;
|
||||
}
|
||||
|
||||
public function getStatusCode(): int
|
||||
{
|
||||
return $this->statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null|object
|
||||
*/
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getError(): ?ActionError
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
$payload = [
|
||||
'statusCode' => $this->statusCode,
|
||||
];
|
||||
|
||||
if ($this->data !== null) {
|
||||
$payload['data'] = $this->data;
|
||||
} elseif ($this->error !== null) {
|
||||
$payload['error'] = $this->error;
|
||||
}
|
||||
|
||||
return $payload;
|
||||
}
|
||||
}
|
21
src/Application/Actions/User/ListUsersAction.php
Normal file
21
src/Application/Actions/User/ListUsersAction.php
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Actions\User;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
|
||||
class ListUsersAction extends UserAction
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function action(): Response
|
||||
{
|
||||
$users = $this->userRepository->findAll();
|
||||
|
||||
$this->logger->info("Users list was viewed.");
|
||||
|
||||
return $this->respondWithData($users);
|
||||
}
|
||||
}
|
19
src/Application/Actions/User/UserAction.php
Normal file
19
src/Application/Actions/User/UserAction.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Actions\User;
|
||||
|
||||
use App\Application\Actions\Action;
|
||||
use App\Domain\User\UserRepository;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
abstract class UserAction extends Action
|
||||
{
|
||||
protected UserRepository $userRepository;
|
||||
|
||||
public function __construct(LoggerInterface $logger, UserRepository $userRepository)
|
||||
{
|
||||
parent::__construct($logger);
|
||||
$this->userRepository = $userRepository;
|
||||
}
|
||||
}
|
22
src/Application/Actions/User/ViewUserAction.php
Normal file
22
src/Application/Actions/User/ViewUserAction.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Actions\User;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
|
||||
class ViewUserAction extends UserAction
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function action(): Response
|
||||
{
|
||||
$userId = (int) $this->resolveArg('id');
|
||||
$user = $this->userRepository->findUserOfId($userId);
|
||||
|
||||
$this->logger->info("User of id `${userId}` was viewed.");
|
||||
|
||||
return $this->respondWithData($user);
|
||||
}
|
||||
}
|
68
src/Application/Handlers/HttpErrorHandler.php
Normal file
68
src/Application/Handlers/HttpErrorHandler.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Handlers;
|
||||
|
||||
use App\Application\Actions\ActionError;
|
||||
use App\Application\Actions\ActionPayload;
|
||||
use Exception;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Slim\Exception\HttpBadRequestException;
|
||||
use Slim\Exception\HttpException;
|
||||
use Slim\Exception\HttpForbiddenException;
|
||||
use Slim\Exception\HttpMethodNotAllowedException;
|
||||
use Slim\Exception\HttpNotFoundException;
|
||||
use Slim\Exception\HttpNotImplementedException;
|
||||
use Slim\Exception\HttpUnauthorizedException;
|
||||
use Slim\Handlers\ErrorHandler as SlimErrorHandler;
|
||||
use Throwable;
|
||||
|
||||
class HttpErrorHandler extends SlimErrorHandler
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected function respond(): Response
|
||||
{
|
||||
$exception = $this->exception;
|
||||
$statusCode = 500;
|
||||
$error = new ActionError(
|
||||
ActionError::SERVER_ERROR,
|
||||
'An internal error has occurred while processing your request.'
|
||||
);
|
||||
|
||||
if ($exception instanceof HttpException) {
|
||||
$statusCode = $exception->getCode();
|
||||
$error->setDescription($exception->getMessage());
|
||||
|
||||
if ($exception instanceof HttpNotFoundException) {
|
||||
$error->setType(ActionError::RESOURCE_NOT_FOUND);
|
||||
} elseif ($exception instanceof HttpMethodNotAllowedException) {
|
||||
$error->setType(ActionError::NOT_ALLOWED);
|
||||
} elseif ($exception instanceof HttpUnauthorizedException) {
|
||||
$error->setType(ActionError::UNAUTHENTICATED);
|
||||
} elseif ($exception instanceof HttpForbiddenException) {
|
||||
$error->setType(ActionError::INSUFFICIENT_PRIVILEGES);
|
||||
} elseif ($exception instanceof HttpBadRequestException) {
|
||||
$error->setType(ActionError::BAD_REQUEST);
|
||||
} elseif ($exception instanceof HttpNotImplementedException) {
|
||||
$error->setType(ActionError::NOT_IMPLEMENTED);
|
||||
}
|
||||
}
|
||||
|
||||
if (!($exception instanceof HttpException)
|
||||
&& $exception instanceof Throwable
|
||||
&& $this->displayErrorDetails
|
||||
) {
|
||||
$error->setDescription($exception->getMessage());
|
||||
}
|
||||
|
||||
$payload = new ActionPayload($statusCode, null, $error);
|
||||
$encodedPayload = json_encode($payload, JSON_PRETTY_PRINT);
|
||||
|
||||
$response = $this->responseFactory->createResponse($statusCode);
|
||||
$response->getBody()->write($encodedPayload);
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
}
|
73
src/Application/Handlers/ShutdownHandler.php
Normal file
73
src/Application/Handlers/ShutdownHandler.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Handlers;
|
||||
|
||||
use App\Application\ResponseEmitter\ResponseEmitter;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\Exception\HttpInternalServerErrorException;
|
||||
|
||||
class ShutdownHandler
|
||||
{
|
||||
private Request $request;
|
||||
|
||||
private HttpErrorHandler $errorHandler;
|
||||
|
||||
private bool $displayErrorDetails;
|
||||
|
||||
public function __construct(
|
||||
Request $request,
|
||||
HttpErrorHandler $errorHandler,
|
||||
bool $displayErrorDetails
|
||||
) {
|
||||
$this->request = $request;
|
||||
$this->errorHandler = $errorHandler;
|
||||
$this->displayErrorDetails = $displayErrorDetails;
|
||||
}
|
||||
|
||||
public function __invoke()
|
||||
{
|
||||
$error = error_get_last();
|
||||
if ($error) {
|
||||
$errorFile = $error['file'];
|
||||
$errorLine = $error['line'];
|
||||
$errorMessage = $error['message'];
|
||||
$errorType = $error['type'];
|
||||
$message = 'An error while processing your request. Please try again later.';
|
||||
|
||||
if ($this->displayErrorDetails) {
|
||||
switch ($errorType) {
|
||||
case E_USER_ERROR:
|
||||
$message = "FATAL ERROR: {$errorMessage}. ";
|
||||
$message .= " on line {$errorLine} in file {$errorFile}.";
|
||||
break;
|
||||
|
||||
case E_USER_WARNING:
|
||||
$message = "WARNING: {$errorMessage}";
|
||||
break;
|
||||
|
||||
case E_USER_NOTICE:
|
||||
$message = "NOTICE: {$errorMessage}";
|
||||
break;
|
||||
|
||||
default:
|
||||
$message = "ERROR: {$errorMessage}";
|
||||
$message .= " on line {$errorLine} in file {$errorFile}.";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$exception = new HttpInternalServerErrorException($this->request, $message);
|
||||
$response = $this->errorHandler->__invoke(
|
||||
$this->request,
|
||||
$exception,
|
||||
$this->displayErrorDetails,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
$responseEmitter = new ResponseEmitter();
|
||||
$responseEmitter->emit($response);
|
||||
}
|
||||
}
|
||||
}
|
25
src/Application/Middleware/SessionMiddleware.php
Normal file
25
src/Application/Middleware/SessionMiddleware.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Middleware;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\MiddlewareInterface as Middleware;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
|
||||
class SessionMiddleware implements Middleware
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(Request $request, RequestHandler $handler): Response
|
||||
{
|
||||
if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
|
||||
session_start();
|
||||
$request = $request->withAttribute('session', $_SESSION);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
}
|
37
src/Application/ResponseEmitter/ResponseEmitter.php
Normal file
37
src/Application/ResponseEmitter/ResponseEmitter.php
Normal file
@ -0,0 +1,37 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\ResponseEmitter;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Slim\ResponseEmitter as SlimResponseEmitter;
|
||||
|
||||
class ResponseEmitter extends SlimResponseEmitter
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function emit(ResponseInterface $response): void
|
||||
{
|
||||
// This variable should be set to the allowed host from which your API can be accessed with
|
||||
$origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '';
|
||||
|
||||
$response = $response
|
||||
->withHeader('Access-Control-Allow-Credentials', 'true')
|
||||
->withHeader('Access-Control-Allow-Origin', $origin)
|
||||
->withHeader(
|
||||
'Access-Control-Allow-Headers',
|
||||
'X-Requested-With, Content-Type, Accept, Origin, Authorization',
|
||||
)
|
||||
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')
|
||||
->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
|
||||
->withAddedHeader('Cache-Control', 'post-check=0, pre-check=0')
|
||||
->withHeader('Pragma', 'no-cache');
|
||||
|
||||
if (ob_get_contents()) {
|
||||
ob_clean();
|
||||
}
|
||||
|
||||
parent::emit($response);
|
||||
}
|
||||
}
|
22
src/Application/Settings/Settings.php
Normal file
22
src/Application/Settings/Settings.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Settings;
|
||||
|
||||
class Settings implements SettingsInterface
|
||||
{
|
||||
private array $settings;
|
||||
|
||||
public function __construct(array $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key = '')
|
||||
{
|
||||
return (empty($key)) ? $this->settings : $this->settings[$key];
|
||||
}
|
||||
}
|
13
src/Application/Settings/SettingsInterface.php
Normal file
13
src/Application/Settings/SettingsInterface.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Application\Settings;
|
||||
|
||||
interface SettingsInterface
|
||||
{
|
||||
/**
|
||||
* @param string $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key = '');
|
||||
}
|
10
src/Domain/DomainException/DomainException.php
Normal file
10
src/Domain/DomainException/DomainException.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\DomainException;
|
||||
|
||||
use Exception;
|
||||
|
||||
abstract class DomainException extends Exception
|
||||
{
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\DomainException;
|
||||
|
||||
class DomainRecordNotFoundException extends DomainException
|
||||
{
|
||||
}
|
56
src/Domain/User/User.php
Normal file
56
src/Domain/User/User.php
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\User;
|
||||
|
||||
use JsonSerializable;
|
||||
|
||||
class User implements JsonSerializable
|
||||
{
|
||||
private ?int $id;
|
||||
|
||||
private string $username;
|
||||
|
||||
private string $firstName;
|
||||
|
||||
private string $lastName;
|
||||
|
||||
public function __construct(?int $id, string $username, string $firstName, string $lastName)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->username = strtolower($username);
|
||||
$this->firstName = ucfirst($firstName);
|
||||
$this->lastName = ucfirst($lastName);
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getFirstName(): string
|
||||
{
|
||||
return $this->firstName;
|
||||
}
|
||||
|
||||
public function getLastName(): string
|
||||
{
|
||||
return $this->lastName;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'username' => $this->username,
|
||||
'firstName' => $this->firstName,
|
||||
'lastName' => $this->lastName,
|
||||
];
|
||||
}
|
||||
}
|
11
src/Domain/User/UserNotFoundException.php
Normal file
11
src/Domain/User/UserNotFoundException.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\User;
|
||||
|
||||
use App\Domain\DomainException\DomainRecordNotFoundException;
|
||||
|
||||
class UserNotFoundException extends DomainRecordNotFoundException
|
||||
{
|
||||
public $message = 'The user you requested does not exist.';
|
||||
}
|
19
src/Domain/User/UserRepository.php
Normal file
19
src/Domain/User/UserRepository.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\User;
|
||||
|
||||
interface UserRepository
|
||||
{
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
public function findAll(): array;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return User
|
||||
* @throws UserNotFoundException
|
||||
*/
|
||||
public function findUserOfId(int $id): User;
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Infrastructure\Persistence\User;
|
||||
|
||||
use App\Domain\User\User;
|
||||
use App\Domain\User\UserNotFoundException;
|
||||
use App\Domain\User\UserRepository;
|
||||
|
||||
class InMemoryUserRepository implements UserRepository
|
||||
{
|
||||
/**
|
||||
* @var User[]
|
||||
*/
|
||||
private array $users;
|
||||
|
||||
/**
|
||||
* @param User[]|null $users
|
||||
*/
|
||||
public function __construct(array $users = null)
|
||||
{
|
||||
$this->users = $users ?? [
|
||||
1 => new User(1, 'bill.gates', 'Bill', 'Gates'),
|
||||
2 => new User(2, 'steve.jobs', 'Steve', 'Jobs'),
|
||||
3 => new User(3, 'mark.zuckerberg', 'Mark', 'Zuckerberg'),
|
||||
4 => new User(4, 'evan.spiegel', 'Evan', 'Spiegel'),
|
||||
5 => new User(5, 'jack.dorsey', 'Jack', 'Dorsey'),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function findAll(): array
|
||||
{
|
||||
return array_values($this->users);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function findUserOfId(int $id): User
|
||||
{
|
||||
if (!isset($this->users[$id])) {
|
||||
throw new UserNotFoundException();
|
||||
}
|
||||
|
||||
return $this->users[$id];
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user