Merge pull request #29 from GoteoFoundation/feat/benzina-refactor
[feat] Refactor Benzina
This commit is contained in:
@@ -3,12 +3,15 @@
|
||||
namespace App\Command;
|
||||
|
||||
use App\Library\Benzina\Benzina;
|
||||
use App\Library\Benzina\Pump\PumpInterface;
|
||||
use App\Library\Benzina\Stream\PdoStream;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\ProgressBar;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
@@ -28,6 +31,14 @@ class BenzinaPumpCommand extends Command
|
||||
{
|
||||
$this->addArgument('table', InputArgument::REQUIRED);
|
||||
|
||||
$this->addOption(
|
||||
'database',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The address of the database to read from',
|
||||
'mysql://goteo:goteo@mariadb:3306/benzina'
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'batch-size',
|
||||
null,
|
||||
@@ -37,18 +48,11 @@ class BenzinaPumpCommand extends Command
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'no-progress',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'For progressive pumps, disables the override of already pumped progress'
|
||||
);
|
||||
|
||||
$this->addOption(
|
||||
'database',
|
||||
'skip-pumped',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The address of the database to read from',
|
||||
'mysql://goteo:goteo@mariadb:3306/benzina'
|
||||
'Skips feeding already pumped records in a batch',
|
||||
false
|
||||
);
|
||||
|
||||
$this->addUsage('app:benzina:pump --no-debug user');
|
||||
@@ -65,61 +69,99 @@ EOF
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ConsoleOutputInterface $output
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$batchSize = $input->getOption('batch-size');
|
||||
|
||||
$stream = new PdoStream(
|
||||
$input->getOption('database'),
|
||||
$input->getArgument('table'),
|
||||
$input->getOption('batch-size')
|
||||
$batchSize
|
||||
);
|
||||
$streamSize = $stream->size();
|
||||
$streamBatches = $streamSize / $batchSize;
|
||||
$streamSection = new SymfonyStyle($input, $output->section());
|
||||
|
||||
if ($streamSize < 1) {
|
||||
$io->writeln('No data found at the given source. Skipping execution.');
|
||||
$streamSection->writeln('No data found at the given source. Skipping execution.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$pumps = $this->benzina->getPumps($stream);
|
||||
$pumpsCount = count($pumps);
|
||||
$streamSection->writeln(sprintf('Streaming %d records (%d batches).', $streamSize, $streamBatches));
|
||||
|
||||
$pumps = $this->benzina->getPumpsFor($stream);
|
||||
$pumpsCount = \count($pumps);
|
||||
$pumpsSection = new SymfonyStyle($input, $output->section());
|
||||
|
||||
if ($pumpsCount < 1) {
|
||||
$io->writeln('No pumps available for the data. Skipping execution.');
|
||||
$pumpsSection->writeln('No pumps support the streamed sample. Skipping execution.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$io->writeln(sprintf('Found %d pumps.', $pumpsCount));
|
||||
$io->listing(\array_map(function ($pump) {
|
||||
$pumpsSection->writeln(sprintf('Streaming to %d pumps.', $pumpsCount));
|
||||
$pumpsSection->listing(\array_map(function (PumpInterface $pump) {
|
||||
return $pump::class;
|
||||
}, $pumps));
|
||||
|
||||
$io->writeln(sprintf('Processing %d records.', $streamSize));
|
||||
$progress = $io->createProgressBar();
|
||||
$progress->start($streamSize);
|
||||
$streamedBatchesSection = $output->section();
|
||||
$streamedBatchesSection->writeln("\tStreamed batches:");
|
||||
$streamedBatches = new ProgressBar($streamedBatchesSection);
|
||||
$streamedBatches->start($streamBatches);
|
||||
|
||||
$streamedRecordsSection = $output->section();
|
||||
$streamedRecordsSection->writeln("\tStreamed records:");
|
||||
$streamedRecords = new ProgressBar($streamedRecordsSection);
|
||||
$streamedRecords->start($streamSize);
|
||||
|
||||
$memUsageSection = $output->section();
|
||||
$memUsageSection->writeln("\tMemory usage:");
|
||||
$memUsage = new ProgressBar($memUsageSection);
|
||||
$memUsage->start($this->getMemoryLimit());
|
||||
|
||||
while (!$stream->eof()) {
|
||||
$data = $stream->read();
|
||||
\memory_reset_peak_usage();
|
||||
|
||||
foreach ($pumps as $pump) {
|
||||
$pump->configure([
|
||||
'progress' => !$input->getOption('no-progress'),
|
||||
$batch = $stream->read();
|
||||
|
||||
$streamBatches = $streamBatches - 1;
|
||||
$streamedBatches->advance();
|
||||
|
||||
foreach ($pumps as $key => $pump) {
|
||||
$pump->setConfig([
|
||||
'skip-pumped' => $input->getOption('skip-pumped'),
|
||||
]);
|
||||
|
||||
$pump->process($data);
|
||||
$pump->pump($batch);
|
||||
|
||||
$memUsage->setProgress(\memory_get_peak_usage(false));
|
||||
}
|
||||
|
||||
$progress->setProgress($stream->tell());
|
||||
$streamed = $stream->tell();
|
||||
$streamedRecords->setProgress($streamed);
|
||||
}
|
||||
|
||||
$stream->close();
|
||||
$progress->finish();
|
||||
|
||||
$io->writeln("\n");
|
||||
$io->success('Data processed successfully!');
|
||||
$endSection = new SymfonyStyle($input, $output->section());
|
||||
$endSection->success('Data processed successfully!');
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function getMemoryLimit(): int
|
||||
{
|
||||
$limit = \ini_get('memory_limit');
|
||||
if (preg_match('/^(\d+)(.)$/', $limit, $matches)) {
|
||||
if ($matches[2] == 'M') {
|
||||
$limit = $matches[1] * 1024 * 1024; // nnnM -> nnn MB
|
||||
} elseif ($matches[2] == 'K') {
|
||||
$limit = $matches[1] * 1024; // nnnK -> nnn KB
|
||||
}
|
||||
}
|
||||
|
||||
return $limit;
|
||||
}
|
||||
}
|
||||
|
||||
+74
-85
@@ -31,6 +31,20 @@ class Accounting
|
||||
#[ORM\Column(length: 3)]
|
||||
private ?string $currency = null;
|
||||
|
||||
/**
|
||||
* @var Collection<int, AccountingTransaction>
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\OneToMany(mappedBy: 'origin', targetEntity: AccountingTransaction::class)]
|
||||
private Collection $transactionsIssued;
|
||||
|
||||
/**
|
||||
* @var Collection<int, AccountingTransaction>
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\OneToMany(mappedBy: 'target', targetEntity: AccountingTransaction::class)]
|
||||
private Collection $transactionsReceived;
|
||||
|
||||
#[API\ApiProperty(writable: false, readable: false)]
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $ownerClass = null;
|
||||
@@ -47,14 +61,6 @@ class Accounting
|
||||
#[ORM\OneToOne(mappedBy: 'accounting', cascade: ['persist', 'remove'])]
|
||||
private ?Tipjar $tipjar = null;
|
||||
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\OneToMany(mappedBy: 'origin', targetEntity: AccountingTransaction::class)]
|
||||
private Collection $transactionsIssued;
|
||||
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\OneToMany(mappedBy: 'target', targetEntity: AccountingTransaction::class)]
|
||||
private Collection $transactionsReceived;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
/*
|
||||
@@ -83,6 +89,66 @@ class Accounting
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, AccountingTransaction>
|
||||
*/
|
||||
public function getTransactionsIssued(): Collection
|
||||
{
|
||||
return $this->transactionsIssued;
|
||||
}
|
||||
|
||||
public function addTransactionsIssued(AccountingTransaction $transactionsIssued): static
|
||||
{
|
||||
if (!$this->transactionsIssued->contains($transactionsIssued)) {
|
||||
$this->transactionsIssued->add($transactionsIssued);
|
||||
$transactionsIssued->setOrigin($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTransactionsIssued(AccountingTransaction $transactionsIssued): static
|
||||
{
|
||||
if ($this->transactionsIssued->removeElement($transactionsIssued)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($transactionsIssued->getOrigin() === $this) {
|
||||
$transactionsIssued->setOrigin(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, AccountingTransaction>
|
||||
*/
|
||||
public function getTransactionsReceived(): Collection
|
||||
{
|
||||
return $this->transactionsReceived;
|
||||
}
|
||||
|
||||
public function addTransactionsReceived(AccountingTransaction $transactionsReceived): static
|
||||
{
|
||||
if (!$this->transactionsReceived->contains($transactionsReceived)) {
|
||||
$this->transactionsReceived->add($transactionsReceived);
|
||||
$transactionsReceived->setTarget($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTransactionsReceived(AccountingTransaction $transactionsReceived): static
|
||||
{
|
||||
if ($this->transactionsReceived->removeElement($transactionsReceived)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($transactionsReceived->getTarget() === $this) {
|
||||
$transactionsReceived->setTarget(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getOwnerClass(): ?string
|
||||
{
|
||||
return $this->ownerClass;
|
||||
@@ -167,81 +233,4 @@ class Accounting
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, AccountingTransaction>
|
||||
*/
|
||||
public function getTransactions(): Collection
|
||||
{
|
||||
$transactions = [
|
||||
...$this->getTransactionsIssued()->toArray(),
|
||||
...$this->getTransactionsReceived()->toArray(),
|
||||
];
|
||||
|
||||
\usort($transactions, function ($a, $b) {
|
||||
return $a->getId() - $b->getId();
|
||||
});
|
||||
|
||||
return new ArrayCollection($transactions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, AccountingTransaction>
|
||||
*/
|
||||
public function getTransactionsIssued(): Collection
|
||||
{
|
||||
return $this->transactionsIssued;
|
||||
}
|
||||
|
||||
public function addTransactionsIssued(AccountingTransaction $transaction): static
|
||||
{
|
||||
if (!$this->transactionsIssued->contains($transaction)) {
|
||||
$this->transactionsIssued->add($transaction);
|
||||
$transaction->setOrigin($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTransactionsIssued(AccountingTransaction $transaction): static
|
||||
{
|
||||
if ($this->transactionsIssued->removeElement($transaction)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($transaction->getOrigin() === $this) {
|
||||
$transaction->setOrigin(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, AccountingTransaction>
|
||||
*/
|
||||
public function getTransactionsReceived(): Collection
|
||||
{
|
||||
return $this->transactionsReceived;
|
||||
}
|
||||
|
||||
public function addTransactionsReceived(AccountingTransaction $transaction): static
|
||||
{
|
||||
if (!$this->transactionsReceived->contains($transaction)) {
|
||||
$this->transactionsReceived->add($transaction);
|
||||
$transaction->setTarget($this);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeTransactionsReceived(AccountingTransaction $transaction): static
|
||||
{
|
||||
if ($this->transactionsReceived->removeElement($transaction)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($transaction->getTarget() === $this) {
|
||||
$transaction->setTarget(null);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,6 @@ class AccountingTransaction
|
||||
/**
|
||||
* The Accounting issuing the money of this Transaction.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\ManyToOne(inversedBy: 'transactionsIssued', cascade: ['persist'])]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Accounting $origin = null;
|
||||
@@ -45,8 +43,6 @@ class AccountingTransaction
|
||||
/**
|
||||
* The Accounting receiving the money of this Transaction.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\ManyToOne(inversedBy: 'transactionsReceived', cascade: ['persist'])]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?Accounting $target = null;
|
||||
@@ -75,8 +71,6 @@ class AccountingTransaction
|
||||
|
||||
public function setOrigin(?Accounting $origin): static
|
||||
{
|
||||
$origin->addTransactionsIssued($this);
|
||||
|
||||
$this->origin = $origin;
|
||||
|
||||
return $this;
|
||||
@@ -89,8 +83,6 @@ class AccountingTransaction
|
||||
|
||||
public function setTarget(?Accounting $target): static
|
||||
{
|
||||
$target->addTransactionsReceived($this);
|
||||
|
||||
$this->target = $target;
|
||||
|
||||
return $this;
|
||||
|
||||
@@ -10,6 +10,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
/**
|
||||
* A GatewayCharge represents a monetary payment that can be done by an issuer at checkout with the Gateway.
|
||||
*/
|
||||
#[API\ApiResource()]
|
||||
#[ORM\Entity(repositoryClass: GatewayChargeRepository::class)]
|
||||
class GatewayCharge
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[API\Get()]
|
||||
#[API\ApiFilter(filterClass: SearchFilter::class, properties: ['origin' => 'exact', 'charges.target' => 'exact'])]
|
||||
#[ORM\Entity(repositoryClass: GatewayCheckoutRepository::class)]
|
||||
#[ORM\Index(fields: ['migratedId'])]
|
||||
class GatewayCheckout
|
||||
{
|
||||
use TimestampableCreationEntity;
|
||||
@@ -75,21 +76,21 @@ class GatewayCheckout
|
||||
* A list of URLs provided by the Gateway for this checkout.\
|
||||
* e.g: Fulfill payment, API resource address.
|
||||
*
|
||||
* @var Collection<int, GatewayLink>
|
||||
* @var GatewayLink[]
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\OneToMany(mappedBy: 'checkout', targetEntity: GatewayLink::class, cascade: ['persist'])]
|
||||
private Collection $gatewayLinks;
|
||||
#[ORM\Column]
|
||||
private array $gatewayLinks = [];
|
||||
|
||||
/**
|
||||
* A list of tracking codes provided by the Gateway for this checkout.\
|
||||
* e.g: Order ID, Payment Capture ID, Checkout Session Token.
|
||||
*
|
||||
* @var Collection<int, GatewayTracking>
|
||||
* @var GatewayTracking[]
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\OneToMany(mappedBy: 'checkout', targetEntity: GatewayTracking::class, cascade: ['persist'])]
|
||||
private Collection $gatewayTrackings;
|
||||
#[ORM\Column]
|
||||
private array $gatewayTrackings = [];
|
||||
|
||||
/**
|
||||
* GatewayCheckout was migrated from an invest record in Goteo v3 platform.
|
||||
@@ -103,7 +104,7 @@ class GatewayCheckout
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $migratedReference = null;
|
||||
private ?string $migratedId = null;
|
||||
|
||||
/**
|
||||
* A free-form collection of additional data associated with this checkout operation.
|
||||
@@ -115,8 +116,6 @@ class GatewayCheckout
|
||||
{
|
||||
$this->charges = new ArrayCollection();
|
||||
$this->status = GatewayCheckoutStatus::Pending;
|
||||
$this->gatewayLinks = new ArrayCollection();
|
||||
$this->gatewayTrackings = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
@@ -185,61 +184,56 @@ class GatewayCheckout
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, GatewayLink>
|
||||
* @return GatewayLink[]
|
||||
*/
|
||||
public function getGatewayLinks(): Collection
|
||||
public function getGatewayLinks(): array
|
||||
{
|
||||
return $this->gatewayLinks;
|
||||
}
|
||||
|
||||
public function addGatewayLink(GatewayLink $link): static
|
||||
{
|
||||
if (!$this->gatewayLinks->contains($link)) {
|
||||
$this->gatewayLinks->add($link);
|
||||
$link->setCheckout($this);
|
||||
}
|
||||
$this->gatewayLinks = [...$this->gatewayLinks, $link];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeGatewayLink(GatewayLink $link): static
|
||||
{
|
||||
if ($this->gatewayLinks->removeElement($link)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($link->getCheckout() === $this) {
|
||||
$link->setCheckout(null);
|
||||
$this->gatewayLinks = \array_filter(
|
||||
$this->gatewayLinks,
|
||||
function (GatewayLink $existingLink) use ($link) {
|
||||
return $existingLink->href !== $link->href;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection<int, GatewayTracking>
|
||||
* @return GatewayTracking[]
|
||||
*/
|
||||
public function getGatewayTrackings(): Collection
|
||||
public function getGatewayTrackings(): array
|
||||
{
|
||||
return $this->gatewayTrackings;
|
||||
}
|
||||
|
||||
public function addGatewayTracking(GatewayTracking $gatewayTracking): static
|
||||
public function addGatewayTracking(GatewayTracking $tracking): static
|
||||
{
|
||||
if (!$this->gatewayTrackings->contains($gatewayTracking)) {
|
||||
$this->gatewayTrackings->add($gatewayTracking);
|
||||
$gatewayTracking->setCheckout($this);
|
||||
}
|
||||
$this->gatewayTrackings = [...$this->gatewayTrackings, $tracking];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeGatewayTracking(GatewayTracking $gatewayTracking): static
|
||||
public function removeGatewayTracking(GatewayTracking $tracking): static
|
||||
{
|
||||
if ($this->gatewayTrackings->removeElement($gatewayTracking)) {
|
||||
// set the owning side to null (unless already changed)
|
||||
if ($gatewayTracking->getCheckout() === $this) {
|
||||
$gatewayTracking->setCheckout(null);
|
||||
$this->gatewayTrackings = \array_filter(
|
||||
$this->gatewayTrackings,
|
||||
function (GatewayTracking $existingTracking) use ($tracking) {
|
||||
return $existingTracking->title !== $tracking->title
|
||||
&& $existingTracking->value !== $tracking->value;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -256,14 +250,14 @@ class GatewayCheckout
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMigratedReference(): ?string
|
||||
public function getMigratedId(): ?string
|
||||
{
|
||||
return $this->migratedReference;
|
||||
return $this->migratedId;
|
||||
}
|
||||
|
||||
public function setMigratedReference(?string $migratedReference): static
|
||||
public function setMigratedId(?string $migratedId): static
|
||||
{
|
||||
$this->migratedReference = $migratedReference;
|
||||
$this->migratedId = $migratedId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -2,114 +2,29 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use ApiPlatform\Metadata as API;
|
||||
use App\Repository\GatewayLinkRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity(repositoryClass: GatewayLinkRepository::class)]
|
||||
class GatewayLink
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
/**
|
||||
* The complete target URL.
|
||||
*/
|
||||
#[ORM\Column(type: Types::TEXT)]
|
||||
private ?string $href = null;
|
||||
public string $href;
|
||||
|
||||
/**
|
||||
* The link relation type, which serves as an ID for a link that unambiguously describes the semantics of the link.
|
||||
*
|
||||
* @see https://www.iana.org/assignments/link-relations/link-relations.xhtml
|
||||
*/
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $rel = null;
|
||||
public string $rel;
|
||||
|
||||
/**
|
||||
* The HTTP method required to make the related call.
|
||||
*/
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $method = null;
|
||||
public string $method;
|
||||
|
||||
/**
|
||||
* The type of the link indicates who is the intended user of a link.\
|
||||
* `debug` links are for developers and platform maintainers to get useful information about the checkout.\
|
||||
* `payment` links are for end-users who must visit this link to complete the checkout.
|
||||
*/
|
||||
#[ORM\Column(enumType: GatewayLinkType::class)]
|
||||
private ?GatewayLinkType $type = null;
|
||||
|
||||
#[API\ApiProperty(readable: false)]
|
||||
#[ORM\ManyToOne(inversedBy: 'links')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?GatewayCheckout $checkout = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getHref(): ?string
|
||||
{
|
||||
return $this->href;
|
||||
}
|
||||
|
||||
public function setHref(string $href): static
|
||||
{
|
||||
$this->href = $href;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRel(): ?string
|
||||
{
|
||||
return $this->rel;
|
||||
}
|
||||
|
||||
public function setRel(string $rel): static
|
||||
{
|
||||
$this->rel = $rel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMethod(): ?string
|
||||
{
|
||||
return $this->method;
|
||||
}
|
||||
|
||||
public function setMethod(string $method): static
|
||||
{
|
||||
$this->method = $method;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): ?GatewayLinkType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(GatewayLinkType $type): static
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCheckout(): ?GatewayCheckout
|
||||
{
|
||||
return $this->checkout;
|
||||
}
|
||||
|
||||
public function setCheckout(?GatewayCheckout $checkout): static
|
||||
{
|
||||
$this->checkout = $checkout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
public GatewayLinkType $type;
|
||||
}
|
||||
|
||||
@@ -2,71 +2,19 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\GatewayTrackingRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Entity(repositoryClass: GatewayTrackingRepository::class)]
|
||||
class GatewayTracking
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
/**
|
||||
* A descriptive title for the tracking number.
|
||||
*/
|
||||
#[Assert\NotBlank()]
|
||||
public string $title;
|
||||
|
||||
/**
|
||||
* The tracking number given by the Gateway.
|
||||
*/
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $value = null;
|
||||
|
||||
/**
|
||||
* A descriptive title for the tracking number.
|
||||
*/
|
||||
#[ORM\Column(length: 255)]
|
||||
private ?string $title = null;
|
||||
|
||||
#[ORM\ManyToOne(inversedBy: 'gatewayIds')]
|
||||
#[ORM\JoinColumn(nullable: false)]
|
||||
private ?GatewayCheckout $checkout = null;
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getValue(): ?string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
public function setValue(string $value): static
|
||||
{
|
||||
$this->value = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getTitle(): ?string
|
||||
{
|
||||
return $this->title;
|
||||
}
|
||||
|
||||
public function setTitle(string $title): static
|
||||
{
|
||||
$this->title = $title;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCheckout(): ?GatewayCheckout
|
||||
{
|
||||
return $this->checkout;
|
||||
}
|
||||
|
||||
public function setCheckout(?GatewayCheckout $checkout): static
|
||||
{
|
||||
$this->checkout = $checkout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
#[Assert\NotBlank()]
|
||||
public string $value;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class Project
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $migratedReference = null;
|
||||
private ?string $migratedId = null;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -148,14 +148,14 @@ class Project
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMigratedReference(): ?string
|
||||
public function getMigratedId(): ?string
|
||||
{
|
||||
return $this->migratedReference;
|
||||
return $this->migratedId;
|
||||
}
|
||||
|
||||
public function setMigratedReference(?string $migratedReference): static
|
||||
public function setMigratedId(?string $migratedId): static
|
||||
{
|
||||
$this->migratedReference = $migratedReference;
|
||||
$this->migratedId = $migratedId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
+6
-5
@@ -37,6 +37,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[UniqueEntity(fields: ['username'], message: 'This usernames already exists.')]
|
||||
#[UniqueEntity(fields: ['email'], message: 'This email address is already registered.')]
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
#[ORM\Index(fields: ['migratedId'])]
|
||||
class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUserInterface
|
||||
{
|
||||
use TimestampableCreationEntity;
|
||||
@@ -136,7 +137,7 @@ class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUs
|
||||
*/
|
||||
#[API\ApiProperty(writable: false)]
|
||||
#[ORM\Column(length: 255, nullable: true)]
|
||||
private ?string $migratedReference = null;
|
||||
private ?string $migratedId = null;
|
||||
|
||||
/**
|
||||
* The projects owned by this User.
|
||||
@@ -373,14 +374,14 @@ class User implements UserInterface, UserOwnedInterface, PasswordAuthenticatedUs
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMigratedReference(): ?string
|
||||
public function getMigratedId(): ?string
|
||||
{
|
||||
return $this->migratedReference;
|
||||
return $this->migratedId;
|
||||
}
|
||||
|
||||
public function setMigratedReference(?string $migratedReference): static
|
||||
public function setMigratedId(?string $migratedId): static
|
||||
{
|
||||
$this->migratedReference = $migratedReference;
|
||||
$this->migratedId = $migratedId;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class Benzina
|
||||
*
|
||||
* @return PumpInterface[]
|
||||
*/
|
||||
public function getPumps(StreamInterface $stream, int $sampleSize = 1): array
|
||||
public function getPumpsFor(StreamInterface $stream, int $sampleSize = 1): array
|
||||
{
|
||||
$sample = $stream->read($sampleSize);
|
||||
$stream->rewind();
|
||||
|
||||
@@ -4,10 +4,23 @@ namespace App\Library\Benzina\Pump;
|
||||
|
||||
abstract class AbstractPump implements PumpInterface
|
||||
{
|
||||
protected array $config;
|
||||
protected array $config = [];
|
||||
|
||||
public function configure(array $config = []): void
|
||||
public function setConfig(array $config = []): void
|
||||
{
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getConfig(?string $key = null, mixed $default = null): array
|
||||
{
|
||||
if ($key !== null) {
|
||||
return [
|
||||
$key => \array_key_exists($key, $this->config)
|
||||
? $this->config[$key]
|
||||
: $default,
|
||||
];
|
||||
}
|
||||
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Library\Benzina\Pump;
|
||||
|
||||
use App\Entity\Accounting;
|
||||
use App\Entity\AccountingTransaction;
|
||||
use App\Entity\GatewayCharge;
|
||||
use App\Entity\GatewayChargeType;
|
||||
use App\Entity\GatewayCheckout;
|
||||
@@ -12,7 +11,7 @@ use App\Entity\GatewayTracking;
|
||||
use App\Entity\Money;
|
||||
use App\Entity\Tipjar;
|
||||
use App\Library\Benzina\Pump\Trait\ArrayPumpTrait;
|
||||
use App\Library\Benzina\Pump\Trait\ProgressivePumpTrait;
|
||||
use App\Library\Benzina\Pump\Trait\DoctrinePumpTrait;
|
||||
use App\Library\Economy\Payment\CashGateway;
|
||||
use App\Library\Economy\Payment\CecaGateway;
|
||||
use App\Library\Economy\Payment\DropGateway;
|
||||
@@ -22,12 +21,14 @@ use App\Library\Economy\Payment\WalletGateway;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Repository\TipjarRepository;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Service\GatewayCheckoutService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class CheckoutsPump extends AbstractPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use ProgressivePumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
|
||||
public const TRACKING_TITLE_V3 = 'v3 Invest ID';
|
||||
public const TRACKING_TITLE_PAYMENT = 'v3 Invest Payment';
|
||||
public const TRACKING_TITLE_TRANSACTION = 'v3 Invest Transaction';
|
||||
@@ -74,33 +75,30 @@ class CheckoutsPump extends AbstractPump implements PumpInterface
|
||||
private ProjectRepository $projectRepository,
|
||||
private TipjarRepository $tipjarRepository,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private GatewayCheckoutService $checkoutService,
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(mixed $data): bool
|
||||
public function supports(mixed $batch): bool
|
||||
{
|
||||
if (!\is_array($data) || !\array_key_exists(0, $data)) {
|
||||
if (!\is_array($batch) || !\array_key_exists(0, $batch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasAllKeys($data[0], self::INVEST_KEYS);
|
||||
return $this->hasAllKeys($batch[0], self::INVEST_KEYS);
|
||||
}
|
||||
|
||||
public function process(mixed $data): void
|
||||
public function pump(mixed $batch): void
|
||||
{
|
||||
$users = $this->getUsers($data);
|
||||
$projects = $this->getProjects($data);
|
||||
$batch = $this->skipPumped($batch, 'id', GatewayCheckout::class, 'migratedId');
|
||||
|
||||
$tipjar = $this->getPlatformTipjar();
|
||||
|
||||
$pumped = $this->getPumped(GatewayCheckout::class, $data, ['migratedReference' => 'id']);
|
||||
$users = $this->getPumpedUsers($batch);
|
||||
$projects = $this->getPumpedProjects($batch);
|
||||
|
||||
foreach ($data as $key => $record) {
|
||||
if ($this->isPumped($record, $pumped, ['migratedReference' => 'id'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!\array_key_exists($record['project'], $projects)) {
|
||||
foreach ($batch as $key => $record) {
|
||||
if (!$record['user'] || empty($record['user'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -113,7 +111,6 @@ class CheckoutsPump extends AbstractPump implements PumpInterface
|
||||
}
|
||||
|
||||
$user = $users[$record['user']];
|
||||
$project = $projects[$record['project']];
|
||||
|
||||
$checkout = new GatewayCheckout();
|
||||
$checkout->setOrigin($user->getAccounting());
|
||||
@@ -125,19 +122,26 @@ class CheckoutsPump extends AbstractPump implements PumpInterface
|
||||
}
|
||||
|
||||
$checkout->setMigrated(true);
|
||||
$checkout->setMigratedReference($record['id']);
|
||||
$checkout->setMetadata([
|
||||
'payment' => $record['payment'],
|
||||
'transaction' => $record['transaction'],
|
||||
'preapproval' => $record['preapproval'],
|
||||
]);
|
||||
$checkout->setMigratedId($record['id']);
|
||||
|
||||
$checkout->setDateCreated(new \DateTime($record['invested']));
|
||||
$checkout->setDateUpdated(new \DateTime());
|
||||
|
||||
$charge = new GatewayCharge();
|
||||
$charge->setType($this->getChargeType($record));
|
||||
$charge->setMoney($this->getChargeMoney($record['amount'], $record['currency']));
|
||||
$charge->setTarget($project->getAccounting());
|
||||
|
||||
if (empty($record['project'])) {
|
||||
$charge->setTarget($user->getAccounting());
|
||||
}
|
||||
|
||||
if (!empty($record['project'])) {
|
||||
$project = $projects[$record['project']];
|
||||
|
||||
$charge->setTarget($project->getAccounting());
|
||||
}
|
||||
|
||||
$checkout->addCharge($charge);
|
||||
|
||||
if ($record['donate_amount'] > 0) {
|
||||
$tip = new GatewayCharge();
|
||||
@@ -145,25 +149,11 @@ class CheckoutsPump extends AbstractPump implements PumpInterface
|
||||
$tip->setMoney($this->getChargeMoney($record['donate_amount'], $record['currency']));
|
||||
$tip->setTarget($tipjar->getAccounting());
|
||||
|
||||
$this->entityManager->persist($tip);
|
||||
$checkout->addCharge($tip);
|
||||
}
|
||||
|
||||
$this->entityManager->persist($charge);
|
||||
$checkout->addCharge($charge);
|
||||
|
||||
if ($checkout->getStatus() === GatewayCheckoutStatus::Charged) {
|
||||
foreach ($checkout->getCharges() as $charge) {
|
||||
$transaction = new AccountingTransaction();
|
||||
$transaction->setMoney($charge->getMoney());
|
||||
$transaction->setOrigin($checkout->getOrigin());
|
||||
$transaction->setTarget($charge->getTarget());
|
||||
|
||||
$charge->setTransaction($transaction);
|
||||
|
||||
$this->entityManager->persist($transaction);
|
||||
$this->entityManager->persist($charge);
|
||||
}
|
||||
$checkout = $this->checkoutService->chargeCheckout($checkout);
|
||||
}
|
||||
|
||||
$this->entityManager->persist($checkout);
|
||||
@@ -173,32 +163,38 @@ class CheckoutsPump extends AbstractPump implements PumpInterface
|
||||
$this->entityManager->clear();
|
||||
}
|
||||
|
||||
private function getUsers(array $data): array
|
||||
/**
|
||||
* @return array<string, \App\Entity\User>
|
||||
*/
|
||||
private function getPumpedUsers(array $batch): array
|
||||
{
|
||||
$users = $this->userRepository->findBy(['migratedReference' => \array_map(function ($data) {
|
||||
return $data['user'];
|
||||
}, $data)]);
|
||||
$users = $this->userRepository->findBy(['migratedId' => \array_map(function ($batch) {
|
||||
return $batch['user'];
|
||||
}, $batch)]);
|
||||
|
||||
$usersByMigratedReference = [];
|
||||
$usersByMigratedId = [];
|
||||
foreach ($users as $user) {
|
||||
$usersByMigratedReference[$user->getMigratedReference()] = $user;
|
||||
$usersByMigratedId[$user->getMigratedId()] = $user;
|
||||
}
|
||||
|
||||
return $usersByMigratedReference;
|
||||
return $usersByMigratedId;
|
||||
}
|
||||
|
||||
private function getProjects(array $data): array
|
||||
/**
|
||||
* @return array<string, \App\Entity\Project>
|
||||
*/
|
||||
private function getPumpedProjects(array $batch): array
|
||||
{
|
||||
$projects = $this->projectRepository->findBy(['migratedReference' => \array_map(function ($data) {
|
||||
return $data['project'];
|
||||
}, $data)]);
|
||||
$projects = $this->projectRepository->findBy(['migratedId' => \array_map(function ($batch) {
|
||||
return $batch['project'];
|
||||
}, $batch)]);
|
||||
|
||||
$projectsByMigratedReference = [];
|
||||
$projectsByMigratedId = [];
|
||||
foreach ($projects as $project) {
|
||||
$projectsByMigratedReference[$project->getMigratedReference()] = $project;
|
||||
$projectsByMigratedId[$project->getMigratedId()] = $project;
|
||||
}
|
||||
|
||||
return $projectsByMigratedReference;
|
||||
return $projectsByMigratedId;
|
||||
}
|
||||
|
||||
private function getPlatformTipjar(): Tipjar
|
||||
@@ -277,31 +273,31 @@ class CheckoutsPump extends AbstractPump implements PumpInterface
|
||||
private function getCheckoutTrackings(array $record): array
|
||||
{
|
||||
$v3Tracking = new GatewayTracking();
|
||||
$v3Tracking->setValue($record['id']);
|
||||
$v3Tracking->setTitle(self::TRACKING_TITLE_V3);
|
||||
$v3Tracking->title = self::TRACKING_TITLE_V3;
|
||||
$v3Tracking->value = $record['id'];
|
||||
|
||||
$trackings = [$v3Tracking];
|
||||
|
||||
if (!empty($record['payment'])) {
|
||||
$payment = new GatewayTracking();
|
||||
$payment->setValue($record['payment']);
|
||||
$payment->setTitle(self::TRACKING_TITLE_PAYMENT);
|
||||
$payment->title = self::TRACKING_TITLE_PAYMENT;
|
||||
$payment->value = $record['payment'];
|
||||
|
||||
$trackings[] = $payment;
|
||||
}
|
||||
|
||||
if (!empty($record['transaction'])) {
|
||||
$transaction = new GatewayTracking();
|
||||
$transaction->setValue($record['transaction']);
|
||||
$transaction->setTitle(self::TRACKING_TITLE_TRANSACTION);
|
||||
$transaction->title = self::TRACKING_TITLE_TRANSACTION;
|
||||
$transaction->value = $record['transaction'];
|
||||
|
||||
$trackings[] = $transaction;
|
||||
}
|
||||
|
||||
if (!empty($record['preapproval'])) {
|
||||
$preapproval = new GatewayTracking();
|
||||
$preapproval->setValue($record['preapproval']);
|
||||
$preapproval->setTitle(self::TRACKING_TITLE_PREAPPROVAL);
|
||||
$preapproval->title = self::TRACKING_TITLE_PREAPPROVAL;
|
||||
$preapproval->value = $record['preapproval'];
|
||||
|
||||
$trackings[] = $preapproval;
|
||||
}
|
||||
|
||||
@@ -7,14 +7,14 @@ use App\Entity\Project;
|
||||
use App\Entity\ProjectStatus;
|
||||
use App\Entity\User;
|
||||
use App\Library\Benzina\Pump\Trait\ArrayPumpTrait;
|
||||
use App\Library\Benzina\Pump\Trait\ProgressivePumpTrait;
|
||||
use App\Library\Benzina\Pump\Trait\DoctrinePumpTrait;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class ProjectsPump extends AbstractPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use ProgressivePumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
|
||||
private const PROJECT_KEYS = [
|
||||
'id',
|
||||
@@ -100,27 +100,22 @@ class ProjectsPump extends AbstractPump implements PumpInterface
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(mixed $data): bool
|
||||
public function supports(mixed $batch): bool
|
||||
{
|
||||
if (!\is_array($data) || !\array_key_exists(0, $data)) {
|
||||
if (!\is_array($batch) || !\array_key_exists(0, $batch)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasAllKeys($data[0], self::PROJECT_KEYS);
|
||||
return $this->hasAllKeys($batch[0], self::PROJECT_KEYS);
|
||||
}
|
||||
|
||||
public function process(mixed $data): void
|
||||
public function pump(mixed $batch): void
|
||||
{
|
||||
$pumped = $this->getPumped(Project::class, $data, ['migratedReference' => 'id']);
|
||||
$owners = $this->getOwners($data);
|
||||
$batch = $this->skipPumped($batch, 'id', Project::class, 'migratedId');
|
||||
|
||||
foreach ($data as $key => $record) {
|
||||
$isPumped = $this->isPumped($record, $pumped, ['migratedReference' => 'id']);
|
||||
|
||||
if ($isPumped) {
|
||||
continue;
|
||||
}
|
||||
$owners = $this->getOwners($batch);
|
||||
|
||||
foreach ($batch as $key => $record) {
|
||||
if (!\array_key_exists($record['owner'], $owners)) {
|
||||
continue;
|
||||
}
|
||||
@@ -134,8 +129,9 @@ class ProjectsPump extends AbstractPump implements PumpInterface
|
||||
$project->setOwner($owners[$record['owner']]);
|
||||
$project->setStatus($this->getProjectStatus($record['status']));
|
||||
$project->setMigrated(true);
|
||||
$project->setMigratedReference($record['id']);
|
||||
$project->setMigratedId($record['id']);
|
||||
$project->setDateCreated(new \DateTime($record['created']));
|
||||
$project->setDateUpdated(new \DateTime());
|
||||
|
||||
$accounting = $this->getAccounting($record);
|
||||
$accounting->setProject($project);
|
||||
@@ -151,15 +147,15 @@ class ProjectsPump extends AbstractPump implements PumpInterface
|
||||
/**
|
||||
* @return User[]
|
||||
*/
|
||||
private function getOwners(array $data): array
|
||||
private function getOwners(array $record): array
|
||||
{
|
||||
$users = $this->userRepository->findBy(['migratedReference' => \array_map(function ($data) {
|
||||
return $data['owner'];
|
||||
}, $data)]);
|
||||
$users = $this->userRepository->findBy(['migratedId' => \array_map(function ($record) {
|
||||
return $record['owner'];
|
||||
}, $record)]);
|
||||
|
||||
$owners = [];
|
||||
foreach ($users as $user) {
|
||||
$owners[$user->getMigratedReference()] = $user;
|
||||
$owners[$user->getMigratedId()] = $user;
|
||||
}
|
||||
|
||||
return $owners;
|
||||
|
||||
@@ -4,24 +4,34 @@ namespace App\Library\Benzina\Pump;
|
||||
|
||||
interface PumpInterface
|
||||
{
|
||||
/**
|
||||
* Determines if the data is supported by this pump.
|
||||
*
|
||||
* @param mixed $data The streamed records, e.g. rows from an user table
|
||||
*/
|
||||
public function supports(mixed $data): bool;
|
||||
|
||||
/**
|
||||
* Sets flexible configuration values for this pump.
|
||||
*
|
||||
* @param array $config the configuration array
|
||||
* @param array $config The configuration array
|
||||
*/
|
||||
public function configure(array $config = []): void;
|
||||
public function setConfig(array $config = []): void;
|
||||
|
||||
/**
|
||||
* Process the data to be pumped.
|
||||
* Read the configuration values for this pump.
|
||||
*
|
||||
* @param mixed $data The streamed records, e.g. rows from an user table
|
||||
* @param string|null $key A configuration array key to return
|
||||
* @param mixed|null $default Any value to return in case the configuration key does not exist
|
||||
*
|
||||
* @return array The configuration array at the specified key, or all keys if null
|
||||
*/
|
||||
public function process(mixed $data): void;
|
||||
public function getConfig(?string $key = null, mixed $default = null): array;
|
||||
|
||||
/**
|
||||
* Determines if a data batch is supported by this pump.
|
||||
*
|
||||
* @param mixed $batch A sample of the streamed records, e.g. rows from an user table
|
||||
*/
|
||||
public function supports(mixed $batch): bool;
|
||||
|
||||
/**
|
||||
* Pump a data batch into a final destination.
|
||||
*
|
||||
* @param mixed $batch The streamed records, e.g. rows from an user table
|
||||
*/
|
||||
public function pump(mixed $batch): void;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina\Pump\Trait;
|
||||
|
||||
use App\Library\Benzina\Pump\PumpInterface;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
trait DoctrinePumpTrait
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
if (!$this instanceof PumpInterface) {
|
||||
throw new \Exception('DoctrinePumpTrait can only be used with PumpInterface classes');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip already pumped records from a pumping stream batch.
|
||||
*
|
||||
* @param array $batch The batch of records being pumped
|
||||
* @param array $batchKey The key in the batch records to match against the entity key
|
||||
* @param string $entityClass The class where the data is being pumped to
|
||||
* @param string $entityKey The property of the pumped to class to match against the batch key
|
||||
*
|
||||
* @return array The batch of records being pumped, sliced out of already pumped records
|
||||
*/
|
||||
public function skipPumped(
|
||||
array $batch,
|
||||
string $batchKey,
|
||||
string $entityClass,
|
||||
string $entityKey,
|
||||
): array {
|
||||
if (!$this->getConfig('skip-pumped', false)) {
|
||||
return $batch;
|
||||
}
|
||||
|
||||
$pumped = $this->entityManager
|
||||
->getRepository($entityClass)
|
||||
->findBy([$entityKey => \array_map(function (array $record) use ($batchKey) {
|
||||
return $record[$batchKey];
|
||||
}, $batch)]);
|
||||
|
||||
$pumpedKeys = [];
|
||||
foreach ($pumped as $key => $entity) {
|
||||
$entityKeyGetter = sprintf('get%s', \ucfirst($entityKey));
|
||||
|
||||
$pumpedKeys[] = $entity->$entityKeyGetter();
|
||||
}
|
||||
|
||||
return \array_filter($batch, function (array $record) use ($batchKey, $pumpedKeys) {
|
||||
return !\in_array($record[$batchKey], $pumpedKeys);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Library\Benzina\Pump\Trait;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
/**
|
||||
* A progressive pump behaves with idempotency with the pumping data.
|
||||
*
|
||||
* To do so the pump must check matching, already pumped, data for the currently pumping data and skip it.
|
||||
*
|
||||
* This is an example:
|
||||
* ```
|
||||
* $alreadyPumped = $this->getPumped(Entity::class, $data, ['entityId' => 'dataId']);
|
||||
* ...
|
||||
* if ($this->isPumped($record, $alreadyPumped)) continue;
|
||||
* ```
|
||||
*/
|
||||
trait ProgressivePumpTrait
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve already pumped data from the pumping data batch.
|
||||
*
|
||||
* @param string $entityClass The class to pump the data to
|
||||
* @param array $pumpingBatch The pumping data batch
|
||||
* @param array $matchCriteria An array with the matching criteria between the entity key and the data key
|
||||
*/
|
||||
public function getPumped(
|
||||
string $entityClass,
|
||||
array $pumpingBatch,
|
||||
array $matchCriteria,
|
||||
): array {
|
||||
$repository = $this->entityManager->getRepository($entityClass);
|
||||
|
||||
$entityKey = \array_keys($matchCriteria)[0];
|
||||
$pumpingKey = $matchCriteria[$entityKey];
|
||||
|
||||
$pumped = $repository->findBy([$entityKey => \array_map(function ($data) use ($pumpingKey) {
|
||||
return $data[$pumpingKey];
|
||||
}, $pumpingBatch)]);
|
||||
|
||||
$pumpedByKey = [];
|
||||
foreach ($pumped as $pumped) {
|
||||
$getter = sprintf('get%s', ucfirst($entityKey));
|
||||
$pumpedByKey[$pumped->$getter()] = $pumped;
|
||||
}
|
||||
|
||||
return $pumpedByKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a pumping data record is already in a pumped data batch.
|
||||
*
|
||||
* @param array $pumpingRecord The data record to be pumped
|
||||
* @param array $pumpedBatch The pumped data batch
|
||||
* @param array $matchCriteria An array with the matching criteria between the entity key and the data key
|
||||
*/
|
||||
public function isPumped(
|
||||
array $pumpingRecord,
|
||||
array $pumpedBatch,
|
||||
array $matchCriteria,
|
||||
): bool {
|
||||
if (
|
||||
isset($this->config)
|
||||
&& $this->config['progress'] === false
|
||||
&& \array_key_exists('progress', $this->config)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$entityKey = \array_keys($matchCriteria)[0];
|
||||
$pumpingKey = $matchCriteria[$entityKey];
|
||||
|
||||
return \array_key_exists($pumpingRecord[$pumpingKey], $pumpedBatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the pumped record in a pumped data batch from a pumping data record.
|
||||
*
|
||||
* @param array $pumpingRecord The data record being pumped
|
||||
* @param array $pumpedBatch The pumped data batch
|
||||
* @param array $matchCriteria An array with the matching criteria between the entity key and the data key
|
||||
*/
|
||||
public function getPumpedRecord(
|
||||
array $pumpingRecord,
|
||||
array $pumpedBatch,
|
||||
array $matchCriteria,
|
||||
): mixed {
|
||||
$entityKey = \array_keys($matchCriteria)[0];
|
||||
$pumpingKey = $matchCriteria[$entityKey];
|
||||
|
||||
if ($this->isPumped($pumpingRecord, $pumpedBatch, $matchCriteria)) {
|
||||
return $pumpedBatch[$pumpingRecord[$pumpingKey]];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,13 @@ namespace App\Library\Benzina\Pump;
|
||||
use App\Entity\Accounting;
|
||||
use App\Entity\User;
|
||||
use App\Library\Benzina\Pump\Trait\ArrayPumpTrait;
|
||||
use App\Library\Benzina\Pump\Trait\ProgressivePumpTrait;
|
||||
use App\Library\Benzina\Pump\Trait\DoctrinePumpTrait;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
class UsersPump extends AbstractPump implements PumpInterface
|
||||
{
|
||||
use ArrayPumpTrait;
|
||||
use ProgressivePumpTrait;
|
||||
use DoctrinePumpTrait;
|
||||
|
||||
public const USER_KEYS = [
|
||||
'id',
|
||||
@@ -55,24 +55,20 @@ class UsersPump extends AbstractPump implements PumpInterface
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(mixed $data): bool
|
||||
public function supports(mixed $batch): bool
|
||||
{
|
||||
if (!\is_array($data) || !\is_array($data[0])) {
|
||||
if (!\is_array($batch) || !\is_array($batch[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->hasAllKeys($data[0], self::USER_KEYS);
|
||||
return $this->hasAllKeys($batch[0], self::USER_KEYS);
|
||||
}
|
||||
|
||||
public function process(mixed $data): void
|
||||
public function pump(mixed $batch): void
|
||||
{
|
||||
$pumped = $this->getPumped(User::class, $data, ['migratedReference' => 'id']);
|
||||
|
||||
foreach ($data as $key => $record) {
|
||||
if ($this->isPumped($record, $pumped, ['migratedReference' => 'id'])) {
|
||||
continue;
|
||||
}
|
||||
$batch = $this->skipPumped($batch, 'id', User::class, 'migratedId');
|
||||
|
||||
foreach ($batch as $key => $record) {
|
||||
$user = new User();
|
||||
$user->setAccounting(new Accounting());
|
||||
$user->setUsername($this->getUsername($record));
|
||||
@@ -82,7 +78,7 @@ class UsersPump extends AbstractPump implements PumpInterface
|
||||
$user->setName($record['name']);
|
||||
$user->setActive(false);
|
||||
$user->setMigrated(true);
|
||||
$user->setMigratedReference($record['id']);
|
||||
$user->setMigratedId($record['id']);
|
||||
|
||||
$accounting = new Accounting();
|
||||
$accounting->setUser($user);
|
||||
@@ -113,12 +109,12 @@ class UsersPump extends AbstractPump implements PumpInterface
|
||||
return $username;
|
||||
}
|
||||
|
||||
private function getUsername(array $data): string
|
||||
private function getUsername(array $record): string
|
||||
{
|
||||
$username = $this->normalizeUsername($data['id']);
|
||||
$username = $this->normalizeUsername($record['id']);
|
||||
|
||||
if (!$username) {
|
||||
$username = $this->normalizeUsername($data['email']);
|
||||
$username = $this->normalizeUsername($record['email']);
|
||||
}
|
||||
|
||||
return $username;
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\GatewayLink;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<GatewayLink>
|
||||
*/
|
||||
class GatewayLinkRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, GatewayLink::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return GatewayLink[] Returns an array of GatewayLink objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('g.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?GatewayLink
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Repository;
|
||||
|
||||
use App\Entity\GatewayTracking;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<GatewayTracking>
|
||||
*/
|
||||
class GatewayTrackingRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, GatewayTracking::class);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @return GatewayTracking[] Returns an array of GatewayTracking objects
|
||||
// */
|
||||
// public function findByExampleField($value): array
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->orderBy('g.id', 'ASC')
|
||||
// ->setMaxResults(10)
|
||||
// ->getQuery()
|
||||
// ->getResult()
|
||||
// ;
|
||||
// }
|
||||
|
||||
// public function findOneBySomeField($value): ?GatewayTracking
|
||||
// {
|
||||
// return $this->createQueryBuilder('g')
|
||||
// ->andWhere('g.exampleField = :val')
|
||||
// ->setParameter('val', $value)
|
||||
// ->getQuery()
|
||||
// ->getOneOrNullResult()
|
||||
// ;
|
||||
// }
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use App\Entity\AccountingTransaction;
|
||||
use App\Entity\GatewayCharge;
|
||||
use App\Entity\GatewayCheckout;
|
||||
use App\Entity\GatewayCheckoutStatus;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
|
||||
class GatewayCheckoutService
|
||||
@@ -17,10 +16,14 @@ class GatewayCheckoutService
|
||||
|
||||
public function __construct(
|
||||
private RouterInterface $router,
|
||||
private EntityManagerInterface $entityManager,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a suitable value for redirection params to external gateways.
|
||||
*
|
||||
* @return string Absolute URL to the gateways redirection handler
|
||||
*/
|
||||
public function generateRedirectUrl(
|
||||
GatewayCheckout $checkout,
|
||||
string $type = self::RESPONSE_TYPE_SUCCESS,
|
||||
@@ -38,17 +41,55 @@ class GatewayCheckoutService
|
||||
);
|
||||
}
|
||||
|
||||
public function getGatewayReference(GatewayCheckout $checkout, GatewayCharge $charge): string
|
||||
/**
|
||||
* Generates a full tracking code for a given charge in a checkout.
|
||||
*
|
||||
* @return string A tracking code suitable for external gateway data matching
|
||||
*/
|
||||
public function generateTracking(GatewayCheckout $checkout, GatewayCharge $charge): string
|
||||
{
|
||||
return sprintf(
|
||||
'AO%d-CO%d-CH%d-AT%d',
|
||||
return \sprintf(
|
||||
'%s-%s',
|
||||
$this->generateCheckoutTracking($checkout),
|
||||
$this->generateChargeTracking($charge)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a partial tracking code for any given checkout.
|
||||
*
|
||||
* @return string A tracking code suitable for external gateway data matching
|
||||
*/
|
||||
public function generateCheckoutTracking(GatewayCheckout $checkout): string
|
||||
{
|
||||
return \sprintf(
|
||||
'AO%d-CO%d',
|
||||
$checkout->getOrigin()->getId(),
|
||||
$checkout->getId(),
|
||||
$checkout->getId()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a partial tracking code for any given charge.
|
||||
*
|
||||
* @return string A tracking code suitable for external gateway data matching
|
||||
*/
|
||||
public function generateChargeTracking(GatewayCharge $charge): string
|
||||
{
|
||||
return \sprintf(
|
||||
'CH%d-AT%d',
|
||||
$charge->getId(),
|
||||
$charge->getTarget()->getId()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a successful GatewayCheckout and generates the transactions for each charge.
|
||||
*
|
||||
* @param GatewayCheckout $checkout The GatewayCheckout to be updated
|
||||
*
|
||||
* @return GatewayCheckout The updated GatewayCheckout with updated charges and generated transactions
|
||||
*/
|
||||
public function chargeCheckout(GatewayCheckout $checkout): GatewayCheckout
|
||||
{
|
||||
$checkout->setStatus(GatewayCheckoutStatus::Charged);
|
||||
@@ -59,15 +100,9 @@ class GatewayCheckoutService
|
||||
$transaction->setOrigin($checkout->getOrigin());
|
||||
$transaction->setTarget($charge->getTarget());
|
||||
|
||||
$this->entityManager->persist($transaction);
|
||||
|
||||
$charge->setTransaction($transaction);
|
||||
|
||||
$this->entityManager->persist($charge);
|
||||
}
|
||||
|
||||
$this->entityManager->flush();
|
||||
|
||||
return $checkout;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Accounting;
|
||||
use App\Entity\AccountingTransaction;
|
||||
use App\Entity\Money;
|
||||
use App\Entity\Tipjar;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
@@ -23,114 +21,42 @@ class AccountingTest extends KernelTestCase
|
||||
$this->entityManager = static::getContainer()->get(EntityManagerInterface::class);
|
||||
}
|
||||
|
||||
public function testAccountingGetsUpdatedByPersistingTransactions()
|
||||
public function testAccountingGetsUpdatedByCascade()
|
||||
{
|
||||
$tipjarA = new Tipjar();
|
||||
$tipjarA->setName('TEST_TIPJAR_A');
|
||||
|
||||
$accA = new Accounting();
|
||||
$accA->setTipjar($tipjarA);
|
||||
$accountingA = new Accounting();
|
||||
$accountingA->setTipjar($tipjarA);
|
||||
|
||||
$this->entityManager->persist($tipjarA);
|
||||
$this->entityManager->flush();
|
||||
|
||||
/** @var Accounting */
|
||||
$accountingA = $this->entityManager->getRepository(Tipjar::class)
|
||||
->findOneBy(['name' => 'TEST_TIPJAR_A'])
|
||||
->getAccounting();
|
||||
|
||||
$this->assertNotNull($accountingA->getId());
|
||||
$this->assertEquals(Tipjar::class, $accountingA->getOwnerClass());
|
||||
$this->assertEquals('TEST_TIPJAR_A', $accountingA->getTipjar()->getName());
|
||||
|
||||
$tipjarB = new Tipjar();
|
||||
$tipjarB->setName('TEST_TIPJAR_B');
|
||||
|
||||
$accB = new Accounting();
|
||||
$accB->setTipjar($tipjarB);
|
||||
$accountingB = new Accounting();
|
||||
$accountingB->setTipjar($tipjarB);
|
||||
|
||||
$trx = new AccountingTransaction();
|
||||
$trx->setMoney(new Money(120, 'EUR'));
|
||||
$trx->setOrigin($accA);
|
||||
$trx->setTarget($accB);
|
||||
|
||||
$this->entityManager->persist($trx);
|
||||
$this->entityManager->persist($tipjarB);
|
||||
$this->entityManager->flush();
|
||||
|
||||
/** @var Accounting */
|
||||
$accA = $this->entityManager->getRepository(Tipjar::class)
|
||||
->findOneBy(['name' => 'TEST_TIPJAR_A'])
|
||||
->getAccounting();
|
||||
|
||||
/** @var Accounting */
|
||||
$accB = $this->entityManager->getRepository(Tipjar::class)
|
||||
$accountingB = $this->entityManager->getRepository(Tipjar::class)
|
||||
->findOneBy(['name' => 'TEST_TIPJAR_B'])
|
||||
->getAccounting();
|
||||
|
||||
$this->assertNotNull($accA->getId());
|
||||
$this->assertNotNull($accB->getId());
|
||||
|
||||
$this->assertCount(1, $accA->getTransactionsIssued());
|
||||
$this->assertCount(0, $accA->getTransactionsReceived());
|
||||
|
||||
$this->assertCount(0, $accB->getTransactionsIssued());
|
||||
$this->assertCount(1, $accB->getTransactionsReceived());
|
||||
|
||||
$this->assertEquals(120, $accA->getTransactionsIssued()[0]->getMoney()->amount);
|
||||
$this->assertEquals('EUR', $accA->getTransactionsIssued()[0]->getMoney()->currency);
|
||||
|
||||
$this->assertEquals(120, $accB->getTransactionsReceived()[0]->getMoney()->amount);
|
||||
$this->assertEquals('EUR', $accB->getTransactionsReceived()[0]->getMoney()->currency);
|
||||
}
|
||||
|
||||
public function testAccountingSortsMixedTransactions()
|
||||
{
|
||||
$tipjarA = new Tipjar();
|
||||
$tipjarA->setName('TEST_TIPJAR_A');
|
||||
|
||||
$accA = new Accounting();
|
||||
$accA->setTipjar($tipjarA);
|
||||
|
||||
$tipjarB = new Tipjar();
|
||||
$tipjarB->setName('TEST_TIPJAR_B');
|
||||
|
||||
$accB = new Accounting();
|
||||
$accB->setTipjar($tipjarB);
|
||||
|
||||
$trx1 = new AccountingTransaction();
|
||||
$trx1->setMoney(new Money(1, 'EUR'));
|
||||
$trx1->setOrigin($accA);
|
||||
$trx1->setTarget($accB);
|
||||
|
||||
$trx2 = new AccountingTransaction();
|
||||
$trx2->setMoney(new Money(2, 'USD'));
|
||||
$trx2->setOrigin($accB);
|
||||
$trx2->setTarget($accA);
|
||||
|
||||
$this->entityManager->persist($trx1);
|
||||
$this->entityManager->persist($trx2);
|
||||
$this->entityManager->flush();
|
||||
|
||||
/** @var Accounting */
|
||||
$accA = $this->entityManager->getRepository(Tipjar::class)
|
||||
->findOneBy(['name' => 'TEST_TIPJAR_A'])
|
||||
->getAccounting();
|
||||
|
||||
/** @var Accounting */
|
||||
$accB = $this->entityManager->getRepository(Tipjar::class)
|
||||
->findOneBy(['name' => 'TEST_TIPJAR_B'])
|
||||
->getAccounting();
|
||||
|
||||
$this->assertNotNull($accA->getId());
|
||||
$this->assertNotNull($accB->getId());
|
||||
|
||||
$this->assertCount(2, $accA->getTransactions());
|
||||
$this->assertCount(2, $accB->getTransactions());
|
||||
|
||||
$this->assertEquals(1, $accA->getTransactions()[0]->getMoney()->amount);
|
||||
$this->assertEquals('EUR', $accA->getTransactions()[0]->getMoney()->currency);
|
||||
|
||||
$this->assertEquals(1, $accB->getTransactions()[0]->getMoney()->amount);
|
||||
$this->assertEquals('EUR', $accB->getTransactions()[0]->getMoney()->currency);
|
||||
|
||||
$this->assertEquals(2, $accA->getTransactions()[1]->getMoney()->amount);
|
||||
$this->assertEquals('USD', $accA->getTransactions()[1]->getMoney()->currency);
|
||||
|
||||
$this->assertEquals(2, $accB->getTransactions()[1]->getMoney()->amount);
|
||||
$this->assertEquals('USD', $accB->getTransactions()[1]->getMoney()->currency);
|
||||
|
||||
$this->assertEquals($accA, $accA->getTransactions()[0]->getOrigin());
|
||||
$this->assertEquals($accA, $accA->getTransactions()[1]->getTarget());
|
||||
|
||||
$this->assertEquals($accB, $accB->getTransactions()[1]->getOrigin());
|
||||
$this->assertEquals($accB, $accB->getTransactions()[0]->getTarget());
|
||||
$this->assertNotNull($accountingB->getId());
|
||||
$this->assertEquals(Tipjar::class, $accountingB->getOwnerClass());
|
||||
$this->assertEquals('TEST_TIPJAR_B', $accountingB->getTipjar()->getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class UsersPumpTest extends KernelTestCase
|
||||
|
||||
$this->assertCount(0, $usersPrePumping);
|
||||
|
||||
$this->pump->process([$testUser]);
|
||||
$this->pump->pump([$testUser]);
|
||||
|
||||
$usersPostPumping = $this->entityManager->getRepository(User::class)
|
||||
->findAll();
|
||||
@@ -108,6 +108,6 @@ class UsersPumpTest extends KernelTestCase
|
||||
$this->assertNotEquals($testUser['confirmed'], $user->isEmailConfirmed());
|
||||
|
||||
$this->assertTrue($user->isMigrated());
|
||||
$this->assertEquals($testUser['id'], $user->getMigratedReference());
|
||||
$this->assertEquals($testUser['id'], $user->getMigratedId());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user