Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fixtures/api/user.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
parameters:
password_hash: '$2y$13$PiPZCpgPCMk1d0B/GPzM/.LkmW2qVtiSu61eM2iXePr.zXNfUTZNe' # The password is 'I<3BambooShoots'.

Panda\Account\Domain\Model\User:
Panda\Account\Infrastructure\Symfony\Security\User:
user_panda:
__construct: ['panda@example.com']
password: <{password_hash}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Panda\Account\Domain\Exception\AuthorizedUserNotFoundException;
use Panda\Account\Domain\Model\UserInterface;
use Panda\Account\Domain\Provider\AuthorizedUserProvider;
use Panda\Account\Infrastructure\Symfony\Security\AuthorizedUserProvider;
use Panda\AccountOHS\Domain\Provider\AuthorizedUserProviderInterface;
use PhpSpec\ObjectBehavior;
use Symfony\Bundle\SecurityBundle\Security;
Expand Down
2 changes: 1 addition & 1 deletion src/Account/Domain/Factory/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

namespace Panda\Account\Domain\Factory;

use Panda\Account\Domain\Hasher\UserPasswordHasherInterface;
use Panda\Account\Domain\Model\User;
use Panda\Account\Domain\Model\UserInterface;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

final readonly class UserFactory implements UserFactoryInterface
{
Expand Down
12 changes: 12 additions & 0 deletions src/Account/Domain/Hasher/UserPasswordHasherInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Panda\Account\Domain\Hasher;

use Panda\Account\Domain\Model\UserInterface;

interface UserPasswordHasherInterface
{
public function hashPassword(UserInterface $user, #[\SensitiveParameter] string $plainPassword): string;
}
20 changes: 3 additions & 17 deletions src/Account/Domain/Model/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@ class User implements UserInterface
{
use TimestampableTrait;

private readonly Uuid $id;
protected readonly Uuid $id;

private ?string $password = null;
protected ?string $password = null;

public function __construct(private string $email)
public function __construct(protected string $email)
{
$this->id = Uuid::v4();
}
Expand All @@ -34,16 +34,6 @@ public function setEmail(string $email): void
$this->email = $email;
}

public function getUserIdentifier(): string
{
return $this->email;
}

public function getRoles(): array
{
return ['ROLE_USER'];
}

public function getPassword(): ?string
{
return $this->password;
Expand All @@ -54,10 +44,6 @@ public function setPassword(string $password): void
$this->password = $password;
}

public function eraseCredentials(): void
{
}

public function compare(OwnerInterface $owner): bool
{
return $this->getId() === $owner->getId();
Expand Down
6 changes: 3 additions & 3 deletions src/Account/Domain/Model/UserInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

use Panda\AccountOHS\Domain\Model\Owner\OwnerInterface;
use Panda\Core\Domain\Model\TimestampableInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface as SymfonyUserInterface;

interface UserInterface extends TimestampableInterface, OwnerInterface, SymfonyUserInterface, PasswordAuthenticatedUserInterface
interface UserInterface extends TimestampableInterface, OwnerInterface
{
public function getEmail(): string;

public function setEmail(string $email): void;

public function getPassword(): ?string;

public function setPassword(string $password): void;
}
3 changes: 1 addition & 2 deletions src/Account/Domain/Repository/UserRepositoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
namespace Panda\Account\Domain\Repository;

use Panda\Account\Domain\Model\UserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
use Symfony\Component\Uid\Uuid;

interface UserRepositoryInterface extends PasswordUpgraderInterface
interface UserRepositoryInterface
{
public function save(UserInterface $user): void;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
doctrine:
orm:
mappings:
Account:
DomainAccount:
is_bundle: false
type: xml
dir: '%kernel.project_dir%/src/Account/Infrastructure/Configuration/Doctrine/mappings'
prefix: 'Panda\Account\Domain\Model'
prefix: 'Panda\Account'
alias: Account
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
xmlns:gedmo="http://gediminasm.org/schemas/orm/doctrine-extensions-mapping"
>
<entity name="Panda\Account\Domain\Model\User" table="panda_user">
<mapped-superclass name="Panda\Account\Domain\Model\User" table="panda_user">
<id name="id" column="id" type="uuid" />

<field name="email" column="email" length="180" unique="true" />
<field name="password" column="password" />

<field name="createdAt" column="created_at" type="datetime"><gedmo:timestampable on="create"/></field>
<field name="updatedAt" column="updated_at" type="datetime"><gedmo:timestampable on="update"/></field>
</entity>
</mapped-superclass>
</doctrine-mapping>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>

<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping">
<entity name="Panda\Account\Infrastructure\Symfony\Security\User" table="panda_user" />
</doctrine-mapping>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

declare(strict_types=1);

use Panda\Account\Domain\Provider\AuthorizedUserProvider;
use Panda\Account\Infrastructure\Symfony\Security\AuthorizedUserProvider;
use Panda\AccountOHS\Domain\Provider\AuthorizedUserProviderInterface;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
Expand Down
2 changes: 1 addition & 1 deletion src/Account/Infrastructure/Doctrine/Orm/UserRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use Doctrine\ORM\EntityManagerInterface;
use Panda\Account\Domain\Model\User;
use Panda\Account\Domain\Model\UserInterface;
use Panda\Account\Domain\Repository\UserRepositoryInterface;
use Panda\Account\Infrastructure\Symfony\Security\UserRepositoryInterface;
use Panda\Core\Infrastructure\Doctrine\Orm\DoctrineRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

declare(strict_types=1);

namespace Panda\Account\Domain\Provider;
namespace Panda\Account\Infrastructure\Symfony\Security;

use Panda\Account\Domain\Exception\AuthorizedUserNotFoundException;
use Panda\Account\Domain\Model\UserInterface;
use Panda\AccountOHS\Domain\Model\Owner\OwnerInterface;
use Panda\AccountOHS\Domain\Provider\AuthorizedUserProviderInterface;
use Symfony\Bundle\SecurityBundle\Security;
Expand Down
24 changes: 24 additions & 0 deletions src/Account/Infrastructure/Symfony/Security/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Panda\Account\Infrastructure\Symfony\Security;

use Panda\Account\Domain\Model\User as DomainUser;

final class User extends DomainUser implements UserInterface
{
public function getUserIdentifier(): string
{
return $this->getEmail();
}

public function getRoles(): array
{
return ['ROLE_USER'];
}

public function eraseCredentials(): void
{
}
}
13 changes: 13 additions & 0 deletions src/Account/Infrastructure/Symfony/Security/UserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Panda\Account\Infrastructure\Symfony\Security;

use Panda\Account\Domain\Model\UserInterface as DomainUserInterface;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface as SymfonyUserInterface;

interface UserInterface extends DomainUserInterface, SymfonyUserInterface, PasswordAuthenticatedUserInterface
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Panda\Account\Infrastructure\Symfony\Security;

use Panda\Account\Domain\Repository\UserRepositoryInterface as DomainUserRepositoryInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;

interface UserRepositoryInterface extends DomainUserRepositoryInterface, PasswordUpgraderInterface
{
}
4 changes: 2 additions & 2 deletions src/Core/Domain/Model/TimestampableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
*/
trait TimestampableTrait
{
private ?\DateTimeInterface $createdAt = null;
protected ?\DateTimeInterface $createdAt = null;

private ?\DateTimeInterface $updatedAt = null;
protected ?\DateTimeInterface $updatedAt = null;

public function getCreatedAt(): ?\DateTimeInterface
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ security:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: Panda\Account\Domain\Model\User
class: Panda\Account\Infrastructure\Symfony\Security\User
property: email
firewalls:
dev:
Expand Down
7 changes: 5 additions & 2 deletions src/Report/Domain/Calculator/OperationValueCalculator.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
use Panda\AccountOHS\Domain\Model\Owner\OwnerInterface;
use Panda\Exchange\Domain\Repository\ExchangeRateLogRepositoryInterface;
use Panda\Portfolio\Domain\Model\Portfolio\PortfolioInterface;
use Panda\Report\Domain\Exception\ExchangeRateLogNotFoundException;
use Panda\Trade\Domain\Model\Transaction\OperationInterface;
use Webmozart\Assert\Assert;

final readonly class OperationValueCalculator implements OperationValueCalculatorInterface
{
Expand All @@ -35,7 +35,10 @@ public function calculate(
$ticker,
$datetime,
);
Assert::notNull($exchangeRate);

if (null === $exchangeRate) {
throw new ExchangeRateLogNotFoundException(sprintf('Exchange rate log for %s datetime not found.', $datetime->format('Y-m-d H:i:s')));
}

return $operation->getQuantity() * $exchangeRate->getRate();
}
Expand Down
10 changes: 10 additions & 0 deletions src/Report/Domain/Exception/ExchangeRateLogNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Panda\Report\Domain\Exception;

final class ExchangeRateLogNotFoundException extends \Exception
{
protected $message = 'Exchange rate log not found.';
}
10 changes: 10 additions & 0 deletions src/Trade/Domain/Exception/EmptyAdjustmentsException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace Panda\Trade\Domain\Exception;

final class EmptyAdjustmentsException extends \InvalidArgumentException
{
protected $message = 'Adjustments cannot be empty.';
}
6 changes: 4 additions & 2 deletions src/Trade/Domain/Factory/TransactionFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

use Panda\AccountOHS\Domain\Model\Owner\OwnerInterface;
use Panda\AccountOHS\Domain\Provider\AuthorizedUserProviderInterface;
use Panda\Trade\Domain\Exception\EmptyAdjustmentsException;
use Panda\Trade\Domain\Model\Transaction\OperationInterface;
use Panda\Trade\Domain\Model\Transaction\Transaction;
use Panda\Trade\Domain\Model\Transaction\TransactionInterface;
use Panda\Trade\Domain\ValueObject\TransactionTypeEnum;
use Webmozart\Assert\Assert;

final class TransactionFactory implements TransactionFactoryInterface
{
Expand Down Expand Up @@ -93,7 +93,9 @@ public function createFee(
\DateTimeInterface $concludedAt,
?OwnerInterface $owner = null,
): TransactionInterface {
Assert::notEmpty($adjustments);
if ([] === $adjustments) {
throw new EmptyAdjustmentsException();
}

$transaction = new Transaction(
TransactionTypeEnum::FEE,
Expand Down
2 changes: 1 addition & 1 deletion tests/Api/ApiTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use ApiTestCase\JsonApiTestCase;
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Panda\Account\Domain\Model\UserInterface;
use Panda\Account\Infrastructure\Symfony\Security\UserInterface;
use Panda\Tests\Util\HttpMethodEnum;
use Symfony\Component\HttpFoundation\Response;

Expand Down
2 changes: 1 addition & 1 deletion tests/App/Account/Domain/Factory/UserFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
namespace Panda\Tests\App\Account\Domain\Factory;

use Panda\Account\Domain\Factory\UserFactory;
use Panda\Account\Domain\Hasher\UserPasswordHasherInterface;
use PHPUnit\Framework\TestCase;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Webmozart\Assert\Assert;

final class UserFactoryTest extends TestCase
Expand Down
29 changes: 22 additions & 7 deletions tests/Architecture/LayersSeparationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

namespace Panda\Tests\Architecture;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;
use Symfony\Component\Finder\Finder;
use Symfony\Component\Finder\SplFileInfo;
use Symfony\Component\Uid\Uuid;

final class LayersSeparationTest
{
Expand All @@ -26,19 +29,31 @@ public function test_domain_does_not_depend_on_other_layers(): Rule
->classes(...$this->applicationLayerSelectors, ...$this->infrastructureLayerSelectors);
}

public function test_domain_does_not_depend_on_doctrine(): Rule
public function test_domain_does_not_depend_on_vendor(): Rule
{
$this->findAllLayers();

return PHPat::rule()
->classes(...$this->domainLayerSelectors)
->shouldNotDependOn()
->classes(Selector::namespace('Doctrine'))
->canOnlyDependOn()
->classes(
Selector::namespace('Panda'),

// Allowed 3rd party classes
Selector::classname(Uuid::class),
Selector::classname(Collection::class),
Selector::classname(ArrayCollection::class),

// FIXME: requires too much effort to get rid of these dependencies for now
->excluding(
Selector::classname('Doctrine\Common\Collections\ArrayCollection'),
Selector::classname('Doctrine\Common\Collections\Collection'),
// PHP root namespace
Selector::classname(\BackedEnum::class),
Selector::classname(\Countable::class),
Selector::classname(\DateTimeImmutable::class),
Selector::classname(\DateTimeInterface::class),
Selector::classname(\Exception::class),
Selector::classname(\InvalidArgumentException::class),
Selector::classname(\Iterator::class),
Selector::classname(\IteratorAggregate::class),
Selector::classname(\Throwable::class),
);
}

Expand Down