<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Application\UseCase\Command;

class ResetPassword
{
    public function __construct(string $passwordResetToken, string $newEmail)
    {
    }
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\Identity\Application\UseCase\CommandHandler;

use App\Kernel\Identity\Application\UseCase\Command\ResetPassword;

interface ResetPasswordHandler
{
    public function handle(ResetPassword $command): void;
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Application\UseCase\CommandHandler;

use App\Kernel\MoneyTransferring\Application\UseCase\Command\BookTransaction;

final class BookTransactionConcreteHandler implements BookTransactionHandler
{
    public function handle(BookTransaction $command): int
    {
        // Orchestrate domain to do stuff.
        // E.g. retrieve entity from repository, call method on entity & etc.
    }
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User\Entity;

class User
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Application\UseCase\Command;

class InitiatePasswordReset
{
    public function __construct(string $email)
    {
    }
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\UserInterface\Web\Controller;

use App\Kernel\Identity\Application\UseCase\Command\RegisterUser;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class RegisterUserController
{
    private $commandBus;

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

    public function __invoke(Request $request): Response
    {
        $this->commandBus->handle(
            new RegisterUser(
                $request->request->get('email'),
                $request->request->get('password')
            ),
        );
    }
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Domain\Model\Transaction\Entity;

class Transaction
{

}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Infrastructure\Persistance\InMemory;

use App\Kernel\Identity\Domain\Model\Entity\User;
use App\Kernel\Identity\Domain\Model\UserRepository;
use App\Kernel\Identity\Domain\Model\ValueObject\UserId;

/**
 * User repository In-memory implementation used for test environments
 */
final class UserInMemoryRepository implements UserRepository
{
    public function add(User $user): void
    {
    }

    public function get(UserId $userId): User
    {
    }
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Application\UseCase\CommandHandler;

use App\Kernel\Identity\Application\UseCase\Command\InitiatePasswordReset;

class InitiatePasswordResetConcreteHandler
{
    public function handle(InitiatePasswordReset $command): void
    {
    }
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User;

interface PasswordHasher
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\SharedKernel\Domain\Service;

interface EventDispatcher
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User\Event;

use App\Kernel\Identity\Domain\Model\User\ValueObject\UserId;
use App\SharedKernel\Domain\ValueObject\Email;

final class UserRegistered
{
    public function __construct(UserId $userId, Email $email)
    {
    }
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\Identity\Application\UseCase\CommandHandler;

use App\Kernel\Identity\Application\UseCase\Command\RegisterUser;

interface RegisterUserHandler
{
    public function handle(RegisterUser $command): void;
}
<file_sep><?php

declare(strict_types=1);

namespace App\SharedKernel\Application\Mailer;

interface Mailer
{

}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Domain\Model\Transaction\ValueObject;

class TransactionId
{

}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User\ValueObject;

final class UserId
{
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Application\UseCase\Command;

class BookTransaction
{
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Domain\Model\Transaction;

interface TransactionRepository
{

}
<file_sep><?php

declare(strict_types=1);

namespace App\SharedKernel\Domain\ValueObject;

final class Money
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Infrastructure\Persistance\Doctrine\ORM;

use App\Kernel\Identity\Domain\Model\Entity\User;
use App\Kernel\Identity\Domain\Model\UserRepository;
use App\Kernel\Identity\Domain\Model\ValueObject\UserId;

final class UserDoctrineOrmRepository implements UserRepository
{
    public function add(User $user): void
    {
    }

    public function get(UserId $userId): User
    {
    }
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Application\UseCase\CommandHandler;

use App\Kernel\MoneyTransferring\Application\UseCase\Command\BookTransaction;

interface BookTransactionHandler
{
    public function handle(BookTransaction $command): int;
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Application\UseCase\CommandHandler;

use App\Kernel\Identity\Application\UseCase\Command\ResetPassword;

class ResetPasswordConcreteHandler
{
    public function handle(ResetPassword $command): void
    {
    }
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User;

final class BcryptPasswordHasher implements PasswordHasher
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User\Exception;

use RuntimeException;

class UserNotFound extends RuntimeException
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\SharedKernel\Domain\ValueObject;

final class Email
{
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Domain\Model\Transaction\Exception;

use RuntimeException;

/**
 * Throw when trying to book transaction with negative amount
 */
class NegativeAmountError extends RuntimeException
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\SharedKernel\Application\Mailer;

final class NullMailer implements Mailer
{
}
<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Domain\Model\Transaction\Event;

class TransactionBooked
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\SharedKernel\Infrastructure\Mailer\SwiftMailer;

use App\SharedKernel\Application\Mailer\Mailer;

final class SwiftMailer implements Mailer
{
    public function __construct(/* Swift_Mailer $mailer */)
    {
    }
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\UserInterface\Console\Command;

use App\Kernel\Identity\Application\UseCase\Command\RegisterUser;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class RegisterUserCommand extends Command
{
    private $commandBus;

    public function __construct($commandBus)
    {
        parent::__construct();

        $this->commandBus = $commandBus;
    }
    
    public function execute(InputInterface $input, OutputInterface $output)
    {
        $this->commandBus->handle(
            new RegisterUser(
                $input->getArgument('email'),
                $input->getArgument('password')
            ),
        );
    }
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Application\UseCase\Command;

class RegisterUser
{
    public function __construct(string $email, string $password)
    {
    }
}
<file_sep># Skeleton

Skeleton acts as an application architecture example that uses:

  - DDD
  - Hexagonal / Layers
  - CQRS

## About the structure

* ***Kernel (aka `Core`)*** - code that represents whole application. `Kernel` consists of bounded contexts that solve single problem (e.g. `Identity`).
* ***SharedKernel*** - code that is shared across all bounded contexts.

### Bounded contexts in depth

Each module consists of 4 layers:
  - Domain
  - Infrastructure
  - Application
  - UserInterface

#### Domain layer

Domain layer is responsible for defining and maintaining business rules of the application. This layer contains classes like entities, repositories (usually interfaces), domain events, value objects and domain services.

#### Infrastructure layer

This layer consist most vendor specific implementations of the interfaces defined by Domain and Application layers (e.g. `UserRepository` interface is implemented using Doctrine ORM or `Mailer` interface is implemented using SwiftMailer library). 

#### Application layer

Application layer acts as a middle-man between infrastructure and domain. For example, controller accepts HTTP request (Infrastructure layer), converts that request into use-case (e.g. `RegisterUser`) command and dispatches it for command handler (Application layer) to take care of it by orchestrating entities, domain services (Domain layer).
This layer can also consist code that is needed by the application itself, but does not necessarily fall into Domain (e.g. `Mailer`, `Logger`).

#### UserInterface layer

UserInterface layer is the part through which user (be it actual user or 3rd party system) enters the application, for example Web API or Console commands. 
Services in this layer are mostly implemented using frameworks & libraries, similar to Infrastructure layer, that's why it commonly referred as primary infrastructure layer.

## Reference

Example code in this repository _tries_ to follow architecture defined in this [schema](https://herbertograca.files.wordpress.com/2018/11/070-explicit-architecture-svg.png?w=1100). 

## Code example

See `/src` directory of this repository for minimal example.<file_sep><?php declare(strict_types=1);

namespace App\Kernel\MoneyTransferring\Infrastructure\Persistance\Doctrine\ORM;

use App\Kernel\MoneyTransferring\Domain\Model\Transaction\TransactionRepository;

final class TransactionDoctrineOrmRepository implements TransactionRepository
{
}
<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User\ValueObject;

final class Password
{
}
<file_sep># Identity bounded context

Each bounded context contains brief description of what it does.

It could also describe abbreviations and/or terms used in the bounded context or provide high level documentation.<file_sep><?php

declare(strict_types=1);

namespace App\Kernel\Identity\Domain\Model\User;

use App\Kernel\Identity\Domain\Model\User\Entity\User;
use App\Kernel\Identity\Domain\Model\User\ValueObject\UserId;

interface UserRepository
{
    public function add(User $user): void;

    public function get(UserId $userId): User;
}
