generated from chicory/php-env
#14 User model and repository
This commit is contained in:
parent
88f5b94c1f
commit
8e85a38f00
24
src/Domain/Entity/User.php
Normal file
24
src/Domain/Entity/User.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Entity;
|
||||
|
||||
use Runx\Domain\Types\DateTime;
|
||||
use Runx\Domain\Types\Email;
|
||||
use Runx\Domain\Types\Name;
|
||||
use Runx\Domain\Types\Password;
|
||||
use Runx\Domain\Types\Username;
|
||||
|
||||
class User
|
||||
{
|
||||
public function __construct(
|
||||
public readonly Username $username,
|
||||
public Email $email,
|
||||
public Name $name,
|
||||
public ?Password $password = null,
|
||||
public ?DateTime $createdAt = null,
|
||||
) {
|
||||
$this->createdAt = $createdAt ?? new DateTime();
|
||||
}
|
||||
}
|
7
src/Domain/Exception/DomainException.php
Normal file
7
src/Domain/Exception/DomainException.php
Normal file
@ -0,0 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Exception;
|
||||
|
||||
class DomainException extends \Exception {}
|
11
src/Domain/Exception/EntityNotFoundException.php
Normal file
11
src/Domain/Exception/EntityNotFoundException.php
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Exception;
|
||||
|
||||
class EntityNotFoundException extends DomainException
|
||||
{
|
||||
/** @var int */
|
||||
protected $code = 404;
|
||||
}
|
18
src/Domain/Exception/InvalidArgumentException.php
Normal file
18
src/Domain/Exception/InvalidArgumentException.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Exception;
|
||||
|
||||
class InvalidArgumentException extends DomainException
|
||||
{
|
||||
public const MESSAGE = 'Invalid value for: %s';
|
||||
|
||||
/** @var int */
|
||||
protected $code = 400;
|
||||
|
||||
public function __construct(string $argName, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct(sprintf(self::MESSAGE, $argName), previous: $previous);
|
||||
}
|
||||
}
|
18
src/Domain/Exception/UserNotFoundException.php
Normal file
18
src/Domain/Exception/UserNotFoundException.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Exception;
|
||||
|
||||
class UserNotFoundException extends EntityNotFoundException
|
||||
{
|
||||
private const MESSAGE = 'User "%s" not found';
|
||||
|
||||
public function __construct(string $uid, ?\Throwable $previous = null)
|
||||
{
|
||||
parent::__construct(
|
||||
message: sprintf(self::MESSAGE, $uid),
|
||||
previous: $previous,
|
||||
);
|
||||
}
|
||||
}
|
16
src/Domain/Repository/UserRepositoryInterface.php
Normal file
16
src/Domain/Repository/UserRepositoryInterface.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Repository;
|
||||
|
||||
use Runx\Domain\Entity\User;
|
||||
use Runx\Domain\Exception\UserNotFoundException;
|
||||
|
||||
interface UserRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @throws UserNotFoundException
|
||||
*/
|
||||
public function get(string $username): User;
|
||||
}
|
20
src/Domain/Types/DateTime.php
Normal file
20
src/Domain/Types/DateTime.php
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Types;
|
||||
|
||||
class DateTime extends \DateTimeImmutable implements \Stringable
|
||||
{
|
||||
public const DATE_FORMAT = 'd.m.Y';
|
||||
|
||||
public function getDate(): string
|
||||
{
|
||||
return $this->format(self::DATE_FORMAT);
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->format(\DateTimeInterface::ISO8601_EXPANDED);
|
||||
}
|
||||
}
|
31
src/Domain/Types/Email.php
Normal file
31
src/Domain/Types/Email.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Types;
|
||||
|
||||
use Runx\Domain\Exception\InvalidArgumentException;
|
||||
|
||||
class Email implements \Stringable
|
||||
{
|
||||
private string $email;
|
||||
|
||||
public function __construct(string $email)
|
||||
{
|
||||
$this->email = $this->validate($email);
|
||||
}
|
||||
|
||||
public function validate(string $email): string
|
||||
{
|
||||
if (false === filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new InvalidArgumentException('email');
|
||||
}
|
||||
|
||||
return $this->email = $email;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->email;
|
||||
}
|
||||
}
|
34
src/Domain/Types/Name.php
Normal file
34
src/Domain/Types/Name.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Types;
|
||||
|
||||
use Runx\Domain\Exception\InvalidArgumentException;
|
||||
|
||||
class Name implements \Stringable
|
||||
{
|
||||
public const MIN_LENGTH = 2;
|
||||
public const MAX_LENGTH = 64;
|
||||
|
||||
private string $name;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = htmlspecialchars($this->validate($name), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
public function validate(string $name): string
|
||||
{
|
||||
if (mb_strlen($name) < self::MIN_LENGTH || mb_strlen($name) > self::MAX_LENGTH) {
|
||||
throw new InvalidArgumentException('name');
|
||||
}
|
||||
|
||||
return $this->name = $name;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
38
src/Domain/Types/Password.php
Normal file
38
src/Domain/Types/Password.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Types;
|
||||
|
||||
use Runx\Domain\Exception\InvalidArgumentException;
|
||||
|
||||
class Password implements \Stringable
|
||||
{
|
||||
public const MIN_LENGTH = 8;
|
||||
public const MAX_LENGTH = 128;
|
||||
|
||||
private string $password;
|
||||
|
||||
public function __construct(?string $password = null)
|
||||
{
|
||||
if (null === $password) {
|
||||
$password = uniqid();
|
||||
}
|
||||
|
||||
$this->password = $this->validate($password);
|
||||
}
|
||||
|
||||
public function validate(string $password): string
|
||||
{
|
||||
if (mb_strlen($password) < self::MIN_LENGTH || mb_strlen($password) > self::MAX_LENGTH) {
|
||||
throw new InvalidArgumentException('password');
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
}
|
31
src/Domain/Types/Username.php
Normal file
31
src/Domain/Types/Username.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Domain\Types;
|
||||
|
||||
use Runx\Domain\Exception\InvalidArgumentException;
|
||||
|
||||
class Username implements \Stringable
|
||||
{
|
||||
private string $username;
|
||||
|
||||
public function __construct(string $username)
|
||||
{
|
||||
$this->username = $this->validate($username);
|
||||
}
|
||||
|
||||
public function validate(string $username): string
|
||||
{
|
||||
if (!preg_match('/^[a-z_][a-z0-9_-]{1,31}$/', $username)) {
|
||||
throw new InvalidArgumentException('username');
|
||||
}
|
||||
|
||||
return $this->username = $username;
|
||||
}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
}
|
@ -27,6 +27,14 @@ return function (): App {
|
||||
$dependencies = require __DIR__ . '/dependencies.php';
|
||||
$dependencies($containerBuilder);
|
||||
|
||||
/**
|
||||
* Bind repositories.
|
||||
*
|
||||
* @var callable(ContainerBuilder<Container>):void $repositories
|
||||
* */
|
||||
$repositories = require __DIR__ . '/repositories.php';
|
||||
$repositories($containerBuilder);
|
||||
|
||||
$container = $containerBuilder->build();
|
||||
|
||||
/** @var App<ContainerInterface> $app */
|
||||
|
13
src/Infrastructure/Boot/repositories.php
Normal file
13
src/Infrastructure/Boot/repositories.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use DI\ContainerBuilder;
|
||||
use Runx\Domain\Repository\UserRepositoryInterface;
|
||||
use Runx\Infrastructure\Repository\LdapUserRepository;
|
||||
|
||||
return function (ContainerBuilder $builder): void {
|
||||
$builder->addDefinitions([
|
||||
UserRepositoryInterface::class => \DI\get(LdapUserRepository::class),
|
||||
]);
|
||||
};
|
@ -3,6 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use Runx\Infrastructure\Controller\PagesController;
|
||||
use Runx\Infrastructure\Controller\UserController;
|
||||
use Slim\App;
|
||||
|
||||
return function (App $app): void {
|
||||
@ -10,4 +11,7 @@ return function (App $app): void {
|
||||
$app->get('/', [PagesController::class, 'index']);
|
||||
$app->get('/donate', [PagesController::class, 'donate']);
|
||||
$app->get('/template', [PagesController::class, 'template']);
|
||||
|
||||
// User
|
||||
$app->get('/user/{uid}', [UserController::class, 'read']);
|
||||
};
|
||||
|
@ -5,6 +5,7 @@ declare(strict_types=1);
|
||||
namespace Runx\Infrastructure\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Runx\Domain\Repository\UserRepositoryInterface;
|
||||
|
||||
final class PagesController extends AbstractController
|
||||
{
|
||||
@ -13,8 +14,12 @@ final class PagesController extends AbstractController
|
||||
return $this->render('pages/index.twig');
|
||||
}
|
||||
|
||||
public function donate(): ResponseInterface
|
||||
public function donate(UserRepositoryInterface $userRepository): ResponseInterface
|
||||
{
|
||||
$user = $userRepository->get('chicory');
|
||||
|
||||
var_dump($user);
|
||||
|
||||
return $this->render('pages/donate.twig');
|
||||
}
|
||||
|
||||
|
22
src/Infrastructure/Controller/UserController.php
Normal file
22
src/Infrastructure/Controller/UserController.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Infrastructure\Controller;
|
||||
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Runx\Domain\Repository\UserRepositoryInterface;
|
||||
use Runx\Infrastructure\Formatter\UserFormatter;
|
||||
|
||||
final class UserController extends AbstractController
|
||||
{
|
||||
public function read(
|
||||
string $uid,
|
||||
UserRepositoryInterface $userRepository,
|
||||
UserFormatter $formatter,
|
||||
): ResponseInterface {
|
||||
$user = $userRepository->get($uid);
|
||||
|
||||
return $this->render('user/read.twig', ['user' => $formatter->format($user)]);
|
||||
}
|
||||
}
|
23
src/Infrastructure/Formatter/UserFormatter.php
Normal file
23
src/Infrastructure/Formatter/UserFormatter.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Infrastructure\Formatter;
|
||||
|
||||
use Runx\Domain\Entity\User;
|
||||
|
||||
final class UserFormatter
|
||||
{
|
||||
/**
|
||||
* @return array<string,mixed>
|
||||
*/
|
||||
public function format(User $user): array
|
||||
{
|
||||
return [
|
||||
'username' => (string) $user->username,
|
||||
'email' => (string) $user->email,
|
||||
'name' => (string) $user->name,
|
||||
'createdAt' => $user->createdAt ? $user->createdAt->getDate() : null,
|
||||
];
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ use Fig\Http\Message\StatusCodeInterface;
|
||||
use Psr\Http\Message\ResponseFactoryInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Runx\Domain\Exception\DomainException;
|
||||
use Runx\Infrastructure\Config\ErrorHandlingConfig;
|
||||
use Runx\Infrastructure\Service\RenderServiceInterface;
|
||||
use Slim\Exception\HttpSpecializedException;
|
||||
@ -55,21 +56,25 @@ class ErrorHandler extends SlimErrorHandler
|
||||
{
|
||||
return match (true) {
|
||||
$this->exception instanceof HttpSpecializedException => $this->exception->getCode(),
|
||||
$this->exception instanceof DomainException => $this->exception->getCode(),
|
||||
default => StatusCodeInterface::STATUS_INTERNAL_SERVER_ERROR,
|
||||
};
|
||||
}
|
||||
|
||||
protected function getTitle(): string
|
||||
{
|
||||
return $this->exception instanceof HttpSpecializedException
|
||||
? $this->exception->getTitle()
|
||||
: self::DEFAULT_MESSAGE;
|
||||
return match (true) {
|
||||
$this->exception instanceof HttpSpecializedException => $this->exception->getTitle(),
|
||||
$this->exception instanceof DomainException => $this->exception->getMessage(),
|
||||
default => self::DEFAULT_MESSAGE,
|
||||
};
|
||||
}
|
||||
|
||||
protected function getMessage(): string
|
||||
{
|
||||
return match (true) {
|
||||
$this->exception instanceof HttpSpecializedException => $this->exception->getMessage(),
|
||||
$this->exception instanceof DomainException => $this->exception->getMessage(),
|
||||
$this->config->displayErrorDetails => $this->exception->getMessage(),
|
||||
default => self::DEFAULT_MESSAGE,
|
||||
};
|
||||
|
55
src/Infrastructure/Repository/LdapUserRepository.php
Normal file
55
src/Infrastructure/Repository/LdapUserRepository.php
Normal file
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Runx\Infrastructure\Repository;
|
||||
|
||||
use Laminas\Ldap\Ldap;
|
||||
use Runx\Domain\Entity\User;
|
||||
use Runx\Domain\Exception\UserNotFoundException;
|
||||
use Runx\Domain\Repository\UserRepositoryInterface;
|
||||
use Runx\Domain\Types\DateTime;
|
||||
use Runx\Domain\Types\Email;
|
||||
use Runx\Domain\Types\Name;
|
||||
use Runx\Domain\Types\Username;
|
||||
use Runx\Infrastructure\Config\Config;
|
||||
use Runx\Infrastructure\Config\LdapConfig;
|
||||
|
||||
class LdapUserRepository implements UserRepositoryInterface
|
||||
{
|
||||
private readonly LdapConfig $config;
|
||||
|
||||
public function __construct(
|
||||
private readonly Ldap $ldap,
|
||||
Config $config,
|
||||
) {
|
||||
$this->config = $config->ldap;
|
||||
}
|
||||
|
||||
public function get(string $username): User
|
||||
{
|
||||
/** @var array<string,array<int,mixed>>|null */
|
||||
$rawEntity = $this->ldap->getEntry(sprintf('uid=%s,%s', $username, $this->config->baseDn));
|
||||
|
||||
if (null == $rawEntity) {
|
||||
throw new UserNotFoundException($username);
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
$username = $rawEntity['uid'][0];
|
||||
/** @var string */
|
||||
$string = $rawEntity['mail'][0];
|
||||
/** @var string */
|
||||
$name = $rawEntity['cn'][0];
|
||||
/** @var string */
|
||||
$createdAt = $rawEntity['createTimestamp'][0];
|
||||
|
||||
return new User(
|
||||
username: new Username($username),
|
||||
email: new Email($string),
|
||||
name: new Name($name),
|
||||
password: null,
|
||||
createdAt: new DateTime($createdAt),
|
||||
);
|
||||
}
|
||||
}
|
14
templates/user/read.twig
Normal file
14
templates/user/read.twig
Normal file
@ -0,0 +1,14 @@
|
||||
{% extends "layouts/base.twig" %}
|
||||
{% block title %}{{ user.name }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="page content single">
|
||||
<article>
|
||||
<div>
|
||||
<h1>{{ user.name }}</h1><br>
|
||||
<b>Username:</b> {{ user.username }}<br>
|
||||
<b>Created at:</b> {{ user.createdAt }}<br>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
{% endblock %}
|
Loading…
Reference in New Issue
Block a user