feat: ajout de la gestion des jobs avec création, récupération et filtrage via l'API, incluant des entités et des mappers pour les échecs et les jobs
This commit is contained in:
parent
d7088b14c2
commit
d7ccc1e603
34
src/Domain/Shared/Application/Query/ListJobsQuery.php
Normal file
34
src/Domain/Shared/Application/Query/ListJobsQuery.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Shared\Application\Query;
|
||||
|
||||
use App\Domain\Shared\Domain\Contract\QueryInterface;
|
||||
use App\Domain\Shared\Domain\Model\JobStatus;
|
||||
|
||||
readonly class ListJobsQuery implements QueryInterface
|
||||
{
|
||||
public function __construct(
|
||||
public ?JobStatus $status = null,
|
||||
public ?string $type = null,
|
||||
public ?\DateTimeImmutable $createdAfter = null,
|
||||
public ?\DateTimeImmutable $createdBefore = null,
|
||||
public ?int $page = 1,
|
||||
public ?int $limit = 20,
|
||||
public ?string $sortBy = 'createdAt',
|
||||
public ?string $sortOrder = 'DESC'
|
||||
) {
|
||||
if ($this->page < 1) {
|
||||
throw new \InvalidArgumentException('Page must be greater than 0');
|
||||
}
|
||||
if ($this->limit < 1) {
|
||||
throw new \InvalidArgumentException('Limit must be greater than 0');
|
||||
}
|
||||
}
|
||||
|
||||
public function getOffset(): int
|
||||
{
|
||||
return ($this->page - 1) * $this->limit;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Shared\Application\QueryHandler;
|
||||
|
||||
use App\Domain\Shared\Application\Query\ListJobsQuery;
|
||||
use App\Domain\Shared\Application\Response\JobListResponse;
|
||||
use App\Domain\Shared\Domain\Contract\QueryHandlerInterface;
|
||||
use App\Domain\Shared\Domain\Contract\QueryInterface;
|
||||
use App\Domain\Shared\Domain\Contract\ResponseInterface;
|
||||
use App\Domain\Shared\Domain\Contract\JobRepositoryInterface;
|
||||
|
||||
readonly class ListJobsQueryHandler implements QueryHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private JobRepositoryInterface $jobRepository
|
||||
) {}
|
||||
|
||||
public function handle(QueryInterface $query): ResponseInterface
|
||||
{
|
||||
if (!$query instanceof ListJobsQuery) {
|
||||
throw new \InvalidArgumentException(sprintf(
|
||||
'Query must be instance of %s, %s given',
|
||||
ListJobsQuery::class,
|
||||
get_class($query)
|
||||
));
|
||||
}
|
||||
|
||||
$criteria = [
|
||||
'status' => $query->status,
|
||||
'type' => $query->type,
|
||||
'createdAfter' => $query->createdAfter,
|
||||
'createdBefore' => $query->createdBefore,
|
||||
'sortBy' => $query->sortBy,
|
||||
'sortOrder' => $query->sortOrder,
|
||||
'offset' => $query->getOffset(),
|
||||
'limit' => $query->limit
|
||||
];
|
||||
|
||||
$jobs = $this->jobRepository->findByCriteria($criteria);
|
||||
$total = $this->jobRepository->countByCriteria($criteria);
|
||||
|
||||
return JobListResponse::fromJobs(
|
||||
jobs: $jobs,
|
||||
total: $total,
|
||||
page: $query->page,
|
||||
limit: $query->limit
|
||||
);
|
||||
}
|
||||
}
|
||||
33
src/Domain/Shared/Application/Response/JobListResponse.php
Normal file
33
src/Domain/Shared/Application/Response/JobListResponse.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Shared\Application\Response;
|
||||
|
||||
use App\Domain\Shared\Domain\Contract\ResponseInterface;
|
||||
use App\Domain\Shared\Domain\Model\Job;
|
||||
|
||||
readonly class JobListResponse implements ResponseInterface
|
||||
{
|
||||
/**
|
||||
* @param Job[] $items
|
||||
*/
|
||||
public function __construct(
|
||||
public array $items,
|
||||
public int $total,
|
||||
public int $page,
|
||||
public int $limit,
|
||||
public int $pages
|
||||
) {}
|
||||
|
||||
public static function fromJobs(array $jobs, int $total, int $page, int $limit): self
|
||||
{
|
||||
return new self(
|
||||
items: $jobs,
|
||||
total: $total,
|
||||
page: $page,
|
||||
limit: $limit,
|
||||
pages: (int) ceil($total / $limit)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,5 @@ interface FailedJobRepositoryInterface
|
||||
public function delete(string $id): void;
|
||||
public function findAll(): array;
|
||||
public function findByJobType(string $jobType): array;
|
||||
public function findByJobId(string $jobId): array;
|
||||
public function findRetryableJobs(): array;
|
||||
}
|
||||
}
|
||||
@@ -14,4 +14,29 @@ interface JobRepositoryInterface
|
||||
public function findPendingJobs(): array;
|
||||
public function findInProgressJobs(): array;
|
||||
public function findFailedJobs(): array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* status?: ?JobStatus,
|
||||
* type?: ?string,
|
||||
* createdAfter?: ?\DateTimeImmutable,
|
||||
* createdBefore?: ?\DateTimeImmutable,
|
||||
* sortBy?: string,
|
||||
* sortOrder?: string,
|
||||
* offset?: int,
|
||||
* limit?: int
|
||||
* } $criteria
|
||||
* @return Job[]
|
||||
*/
|
||||
public function findByCriteria(array $criteria): array;
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* status?: ?JobStatus,
|
||||
* type?: ?string,
|
||||
* createdAfter?: ?\DateTimeImmutable,
|
||||
* createdBefore?: ?\DateTimeImmutable
|
||||
* } $criteria
|
||||
*/
|
||||
public function countByCriteria(array $criteria): int;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\ApiPlatform\Resource;
|
||||
|
||||
use ApiPlatform\Metadata\ApiFilter;
|
||||
use ApiPlatform\Metadata\ApiProperty;
|
||||
use ApiPlatform\Metadata\ApiResource;
|
||||
use ApiPlatform\Metadata\GetCollection;
|
||||
use App\Domain\Shared\Domain\Model\JobStatus;
|
||||
use App\Domain\Shared\Infrastructure\ApiPlatform\State\Provider\GetJobListStateProvider;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ApiResource(
|
||||
shortName: 'Job',
|
||||
operations: [
|
||||
new GetCollection(
|
||||
uriTemplate: '/jobs',
|
||||
provider: GetJobListStateProvider::class,
|
||||
output: GetJobListResource::class,
|
||||
description: 'Liste des jobs',
|
||||
openapiContext: [
|
||||
'parameters' => [
|
||||
[
|
||||
'name' => 'status',
|
||||
'in' => 'query',
|
||||
'description' => 'Filtrer par status',
|
||||
'required' => false,
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'enum' => ['pending', 'in_progress', 'completed', 'failed'],
|
||||
'example' => 'pending'
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'type',
|
||||
'in' => 'query',
|
||||
'description' => 'Filtrer par type de job (ex: scraping_job)',
|
||||
'required' => false,
|
||||
'schema' => ['type' => 'string']
|
||||
],
|
||||
[
|
||||
'name' => 'createdAfter',
|
||||
'in' => 'query',
|
||||
'description' => 'Date de création minimum (format ISO8601)',
|
||||
'required' => false,
|
||||
'schema' => ['type' => 'string', 'format' => 'date-time']
|
||||
],
|
||||
[
|
||||
'name' => 'createdBefore',
|
||||
'in' => 'query',
|
||||
'description' => 'Date de création maximum (format ISO8601)',
|
||||
'required' => false,
|
||||
'schema' => ['type' => 'string', 'format' => 'date-time']
|
||||
],
|
||||
[
|
||||
'name' => 'page',
|
||||
'in' => 'query',
|
||||
'description' => 'Numéro de la page',
|
||||
'required' => false,
|
||||
'schema' => ['type' => 'integer', 'default' => 1, 'minimum' => 1]
|
||||
],
|
||||
[
|
||||
'name' => 'limit',
|
||||
'in' => 'query',
|
||||
'description' => 'Nombre d\'éléments par page',
|
||||
'required' => false,
|
||||
'schema' => ['type' => 'integer', 'default' => 20, 'minimum' => 1]
|
||||
],
|
||||
[
|
||||
'name' => 'sortBy',
|
||||
'in' => 'query',
|
||||
'description' => 'Champ de tri',
|
||||
'required' => false,
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'enum' => ['createdAt', 'type', 'status'],
|
||||
'default' => 'createdAt'
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'sortOrder',
|
||||
'in' => 'query',
|
||||
'description' => 'Ordre de tri',
|
||||
'required' => false,
|
||||
'schema' => [
|
||||
'type' => 'string',
|
||||
'enum' => ['ASC', 'DESC'],
|
||||
'default' => 'DESC'
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
]
|
||||
)]
|
||||
class GetJobListResource
|
||||
{
|
||||
public function __construct(
|
||||
#[ApiProperty(
|
||||
identifier: true,
|
||||
description: 'Identifiant unique du job'
|
||||
)]
|
||||
public readonly string $id,
|
||||
|
||||
#[ApiProperty(description: 'Type du job (ex: scraping_job)')]
|
||||
#[Assert\NotBlank]
|
||||
public readonly string $type,
|
||||
|
||||
#[ApiProperty(
|
||||
description: 'Status du job',
|
||||
openapiContext: ['enum' => ['pending', 'in_progress', 'completed', 'failed', 'cancelled']]
|
||||
)]
|
||||
#[Assert\NotBlank]
|
||||
public readonly string $status,
|
||||
|
||||
#[ApiProperty(description: 'Date de création du job')]
|
||||
#[Assert\NotNull]
|
||||
public readonly \DateTimeImmutable $createdAt,
|
||||
|
||||
#[ApiProperty(description: 'Date de début d\'exécution du job')]
|
||||
public readonly ?\DateTimeImmutable $startedAt = null,
|
||||
|
||||
#[ApiProperty(description: 'Date de fin d\'exécution du job')]
|
||||
public readonly ?\DateTimeImmutable $completedAt = null,
|
||||
|
||||
#[ApiProperty(description: 'Raison de l\'échec si le job a échoué')]
|
||||
public readonly ?string $failureReason = null,
|
||||
|
||||
#[ApiProperty(description: 'Nombre de tentatives effectuées')]
|
||||
#[Assert\GreaterThanOrEqual(0)]
|
||||
public readonly int $attempts = 0,
|
||||
|
||||
#[ApiProperty(description: 'Nombre maximum de tentatives autorisées')]
|
||||
#[Assert\GreaterThan(0)]
|
||||
public readonly int $maxAttempts = 3,
|
||||
|
||||
#[ApiProperty(description: 'Données contextuelles du job')]
|
||||
public readonly array $context = []
|
||||
) {}
|
||||
|
||||
public static function fromJob(\App\Domain\Shared\Domain\Model\Job $job): self
|
||||
{
|
||||
return new self(
|
||||
id: $job->id,
|
||||
type: $job->type,
|
||||
status: $job->status->value,
|
||||
createdAt: $job->createdAt,
|
||||
startedAt: $job->startedAt,
|
||||
completedAt: $job->completedAt,
|
||||
failureReason: $job->failureReason,
|
||||
attempts: $job->attempts,
|
||||
maxAttempts: $job->maxAttempts,
|
||||
context: $job->context
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\ApiPlatform\State\Provider;
|
||||
|
||||
use ApiPlatform\Metadata\Operation;
|
||||
use ApiPlatform\State\ProviderInterface;
|
||||
use App\Domain\Shared\Application\Query\ListJobsQuery;
|
||||
use App\Domain\Shared\Application\QueryHandler\ListJobsQueryHandler;
|
||||
use App\Domain\Shared\Domain\Model\JobStatus;
|
||||
use App\Domain\Shared\Infrastructure\ApiPlatform\Resource\GetJobListResource;
|
||||
|
||||
readonly class GetJobListStateProvider implements ProviderInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ListJobsQueryHandler $handler
|
||||
) {}
|
||||
|
||||
public function provide(Operation $operation, array $uriVariables = [], array $context = []): array
|
||||
{
|
||||
$filters = $context['filters'] ?? [];
|
||||
|
||||
$query = new ListJobsQuery(
|
||||
status: isset($filters['status']) ? JobStatus::from($filters['status']) : null,
|
||||
type: $filters['type'] ?? null,
|
||||
createdAfter: isset($filters['createdAfter']) ? new \DateTimeImmutable($filters['createdAfter']) : null,
|
||||
createdBefore: isset($filters['createdBefore']) ? new \DateTimeImmutable($filters['createdBefore']) : null,
|
||||
page: (int) ($filters['page'] ?? 1),
|
||||
limit: (int) ($filters['limit'] ?? 20),
|
||||
sortBy: $filters['sortBy'] ?? 'createdAt',
|
||||
sortOrder: $filters['sortOrder'] ?? 'DESC'
|
||||
);
|
||||
|
||||
$response = $this->handler->handle($query);
|
||||
|
||||
return [
|
||||
'items' => array_map(
|
||||
fn($job) => GetJobListResource::fromJob($job),
|
||||
$response->items
|
||||
),
|
||||
'total' => $response->total,
|
||||
'page' => $response->page,
|
||||
'limit' => $response->limit,
|
||||
'pages' => $response->pages
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\Persistence\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'failed_job')]
|
||||
class FailedJobEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
private string $id;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $type;
|
||||
|
||||
#[ORM\Column(type: 'text')]
|
||||
private string $failureReason;
|
||||
|
||||
#[ORM\Column]
|
||||
private \DateTimeImmutable $failedAt;
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $context = [];
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(string $id): self
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFailureReason(): string
|
||||
{
|
||||
return $this->failureReason;
|
||||
}
|
||||
|
||||
public function setFailureReason(string $failureReason): self
|
||||
{
|
||||
$this->failureReason = $failureReason;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFailedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->failedAt;
|
||||
}
|
||||
|
||||
public function setFailedAt(\DateTimeImmutable $failedAt): self
|
||||
{
|
||||
$this->failedAt = $failedAt;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContext(): array
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function setContext(array $context): self
|
||||
{
|
||||
$this->context = $context;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\Persistence\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
#[ORM\Entity]
|
||||
#[ORM\Table(name: 'job')]
|
||||
class JobEntity
|
||||
{
|
||||
#[ORM\Id]
|
||||
#[ORM\Column]
|
||||
private string $id;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $type;
|
||||
|
||||
#[ORM\Column(type: 'string')]
|
||||
private string $status;
|
||||
|
||||
#[ORM\Column]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $startedAt = null;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $completedAt = null;
|
||||
|
||||
#[ORM\Column(type: 'text', nullable: true)]
|
||||
private ?string $failureReason = null;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $attempts = 0;
|
||||
|
||||
#[ORM\Column(type: 'integer')]
|
||||
private int $maxAttempts = 3;
|
||||
|
||||
#[ORM\Column(type: 'json')]
|
||||
private array $context = [];
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setId(string $id): self
|
||||
{
|
||||
$this->id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): self
|
||||
{
|
||||
$this->type = $type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
public function setStatus(string $status): self
|
||||
{
|
||||
$this->status = $status;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function setCreatedAt(\DateTimeImmutable $createdAt): self
|
||||
{
|
||||
$this->createdAt = $createdAt;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStartedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->startedAt;
|
||||
}
|
||||
|
||||
public function setStartedAt(?\DateTimeImmutable $startedAt): self
|
||||
{
|
||||
$this->startedAt = $startedAt;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getCompletedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->completedAt;
|
||||
}
|
||||
|
||||
public function setCompletedAt(?\DateTimeImmutable $completedAt): self
|
||||
{
|
||||
$this->completedAt = $completedAt;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFailureReason(): ?string
|
||||
{
|
||||
return $this->failureReason;
|
||||
}
|
||||
|
||||
public function setFailureReason(?string $failureReason): self
|
||||
{
|
||||
$this->failureReason = $failureReason;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAttempts(): int
|
||||
{
|
||||
return $this->attempts;
|
||||
}
|
||||
|
||||
public function setAttempts(int $attempts): self
|
||||
{
|
||||
$this->attempts = $attempts;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMaxAttempts(): int
|
||||
{
|
||||
return $this->maxAttempts;
|
||||
}
|
||||
|
||||
public function setMaxAttempts(int $maxAttempts): self
|
||||
{
|
||||
$this->maxAttempts = $maxAttempts;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContext(): array
|
||||
{
|
||||
return $this->context;
|
||||
}
|
||||
|
||||
public function setContext(array $context): self
|
||||
{
|
||||
$this->context = $context;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\Persistence\Mapper;
|
||||
|
||||
use App\Domain\Shared\Domain\Model\FailedJob;
|
||||
use App\Domain\Shared\Infrastructure\Persistence\Entity\FailedJobEntity;
|
||||
|
||||
readonly class FailedJobMapper
|
||||
{
|
||||
public function toEntity(FailedJob $job): FailedJobEntity
|
||||
{
|
||||
$entity = new FailedJobEntity();
|
||||
$entity->setId($job->id)
|
||||
->setType($job->jobType)
|
||||
->setFailureReason($job->failureReason)
|
||||
->setFailedAt($job->failedAt)
|
||||
->setContext($job->context);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function toDomain(FailedJobEntity $entity): FailedJob
|
||||
{
|
||||
return new FailedJob(
|
||||
id: $entity->getId(),
|
||||
jobId: $entity->getId(), // On utilise le même ID car on n'a pas de référence au job original
|
||||
jobType: $entity->getType(),
|
||||
failureReason: $entity->getFailureReason(),
|
||||
context: $entity->getContext(),
|
||||
failedAt: $entity->getFailedAt(),
|
||||
attempt: 1 // Par défaut car on n'a pas cette info dans l'entité
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\Persistence\Mapper;
|
||||
|
||||
use App\Domain\Scraping\Domain\Model\ScrapingJob;
|
||||
use App\Domain\Shared\Domain\Model\Job;
|
||||
use App\Domain\Shared\Domain\Model\JobStatus;
|
||||
use App\Domain\Shared\Infrastructure\Persistence\Entity\JobEntity;
|
||||
|
||||
readonly class JobMapper
|
||||
{
|
||||
public function toEntity(Job $job): JobEntity
|
||||
{
|
||||
$entity = new JobEntity();
|
||||
$entity->setId($job->id)
|
||||
->setType($job->type)
|
||||
->setStatus($job->status->value)
|
||||
->setCreatedAt($job->createdAt)
|
||||
->setStartedAt($job->startedAt)
|
||||
->setCompletedAt($job->completedAt)
|
||||
->setFailureReason($job->failureReason)
|
||||
->setAttempts($job->attempts)
|
||||
->setMaxAttempts($job->maxAttempts)
|
||||
->setContext($job->context);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
public function toDomain(JobEntity $entity): Job
|
||||
{
|
||||
$job = match($entity->getType()) {
|
||||
'scraping_job' => new ScrapingJob(
|
||||
$entity->getId(),
|
||||
$entity->getContext()['mangaId'],
|
||||
$entity->getContext()['chapterNumber'],
|
||||
$entity->getContext()['sourceId']
|
||||
),
|
||||
default => throw new \RuntimeException(sprintf('Unknown job type: %s', $entity->getType()))
|
||||
};
|
||||
|
||||
$job->status = JobStatus::from($entity->getStatus());
|
||||
$job->createdAt = $entity->getCreatedAt();
|
||||
$job->startedAt = $entity->getStartedAt();
|
||||
$job->completedAt = $entity->getCompletedAt();
|
||||
$job->failureReason = $entity->getFailureReason();
|
||||
$job->attempts = $entity->getAttempts();
|
||||
$job->maxAttempts = $entity->getMaxAttempts();
|
||||
$job->context = $entity->getContext();
|
||||
|
||||
return $job;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\Persistence\Repository;
|
||||
|
||||
use App\Domain\Shared\Domain\Contract\FailedJobRepositoryInterface;
|
||||
use App\Domain\Shared\Domain\Model\FailedJob;
|
||||
use App\Domain\Shared\Domain\Model\Job;
|
||||
use App\Domain\Shared\Infrastructure\Persistence\Entity\FailedJobEntity;
|
||||
use App\Domain\Shared\Infrastructure\Persistence\Mapper\FailedJobMapper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
readonly class DoctrineFailedJobRepository implements FailedJobRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private FailedJobMapper $mapper
|
||||
) {
|
||||
}
|
||||
|
||||
public function save(FailedJob $job): void
|
||||
{
|
||||
$entity = $this->mapper->toEntity($job);
|
||||
$this->entityManager->persist($entity);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
|
||||
public function get(string $id): FailedJob
|
||||
{
|
||||
$job = $this->findById($id);
|
||||
|
||||
if (null === $job) {
|
||||
throw new \RuntimeException(sprintf('Failed job with id %s not found', $id));
|
||||
}
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
public function delete(string $id): void
|
||||
{
|
||||
$entity = $this->entityManager->find(FailedJobEntity::class, $id);
|
||||
|
||||
if ($entity) {
|
||||
$this->entityManager->remove($entity);
|
||||
$this->entityManager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
public function findAll(): array
|
||||
{
|
||||
$entities = $this->entityManager->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(FailedJobEntity::class, 'j')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return array_map(fn(FailedJobEntity $entity) => $this->mapper->toDomain($entity), $entities);
|
||||
}
|
||||
|
||||
public function findById(string $id): ?FailedJob
|
||||
{
|
||||
$entity = $this->entityManager->find(FailedJobEntity::class, $id);
|
||||
|
||||
if (null === $entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->mapper->toDomain($entity);
|
||||
}
|
||||
|
||||
public function findByJobType(string $type): array
|
||||
{
|
||||
return $this->findByType($type);
|
||||
}
|
||||
|
||||
public function findRetryableJobs(): array
|
||||
{
|
||||
$entities = $this->entityManager->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(FailedJobEntity::class, 'j')
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return array_map(
|
||||
fn(FailedJobEntity $entity) => $this->mapper->toDomain($entity),
|
||||
array_filter(
|
||||
$entities,
|
||||
fn(FailedJobEntity $entity) => $this->mapper->toDomain($entity)->attempt < 3
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private function findByType(string $type): array
|
||||
{
|
||||
$entities = $this->entityManager->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(FailedJobEntity::class, 'j')
|
||||
->where('j.type = :type')
|
||||
->setParameter('type', $type)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return array_map(fn(FailedJobEntity $entity) => $this->mapper->toDomain($entity), $entities);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Shared\Infrastructure\Persistence\Repository;
|
||||
|
||||
use App\Domain\Shared\Domain\Contract\JobRepositoryInterface;
|
||||
use App\Domain\Shared\Domain\Exception\JobNotFoundException;
|
||||
use App\Domain\Shared\Domain\Model\Job;
|
||||
use App\Domain\Shared\Domain\Model\JobStatus;
|
||||
use App\Domain\Shared\Infrastructure\Persistence\Entity\JobEntity;
|
||||
use App\Domain\Shared\Infrastructure\Persistence\Mapper\JobMapper;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
readonly class DoctrineJobRepository implements JobRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private JobMapper $mapper
|
||||
) {
|
||||
}
|
||||
|
||||
public function save(Job $job): void
|
||||
{
|
||||
dump('save', $job);
|
||||
/** @var JobEntity|null $existingJobEntity */
|
||||
$existingJobEntity = $this->entityManager->find(JobEntity::class, $job->id);
|
||||
|
||||
if ($existingJobEntity) {
|
||||
dump('existingJobEntity', $existingJobEntity);
|
||||
$existingJobEntity->setStatus($job->status->value);
|
||||
$existingJobEntity->setStartedAt($job->startedAt);
|
||||
$existingJobEntity->setCompletedAt($job->completedAt);
|
||||
$existingJobEntity->setFailureReason($job->failureReason);
|
||||
$existingJobEntity->setAttempts($job->attempts);
|
||||
$existingJobEntity->setContext($job->context);
|
||||
$this->entityManager->persist($existingJobEntity);
|
||||
dump('updated', $existingJobEntity);
|
||||
} else {
|
||||
$entity = $this->mapper->toEntity($job);
|
||||
$this->entityManager->persist($entity);
|
||||
dump('created', $entity);
|
||||
}
|
||||
$this->entityManager->flush();
|
||||
dump('flushed');
|
||||
}
|
||||
|
||||
public function get(string $id): Job
|
||||
{
|
||||
$job = $this->findById($id);
|
||||
|
||||
if (null === $job) {
|
||||
throw JobNotFoundException::withId($id);
|
||||
}
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
public function findById(string $id): ?Job
|
||||
{
|
||||
$entity = $this->entityManager->find(JobEntity::class, $id);
|
||||
|
||||
if (null === $entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->mapper->toDomain($entity);
|
||||
}
|
||||
|
||||
public function findByStatus(JobStatus $status): array
|
||||
{
|
||||
$entities = $this->entityManager->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(JobEntity::class, 'j')
|
||||
->where('j.status = :status')
|
||||
->setParameter('status', $status->value)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return array_map(fn(JobEntity $entity) => $this->mapper->toDomain($entity), $entities);
|
||||
}
|
||||
|
||||
public function findPendingJobs(): array
|
||||
{
|
||||
return $this->findByStatus(JobStatus::PENDING);
|
||||
}
|
||||
|
||||
public function findInProgressJobs(): array
|
||||
{
|
||||
return $this->findByStatus(JobStatus::IN_PROGRESS);
|
||||
}
|
||||
|
||||
public function findFailedJobs(): array
|
||||
{
|
||||
return $this->findByStatus(JobStatus::FAILED);
|
||||
}
|
||||
|
||||
public function findByType(string $type): array
|
||||
{
|
||||
$entities = $this->entityManager->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(JobEntity::class, 'j')
|
||||
->where('j.type = :type')
|
||||
->setParameter('type', $type)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
return array_map(fn(JobEntity $entity) => $this->mapper->toDomain($entity), $entities);
|
||||
}
|
||||
|
||||
public function findByCriteria(array $criteria): array
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->select('j')
|
||||
->from(JobEntity::class, 'j');
|
||||
|
||||
if (isset($criteria['status'])) {
|
||||
$qb->andWhere('j.status = :status')
|
||||
->setParameter('status', $criteria['status']->value);
|
||||
}
|
||||
|
||||
if (isset($criteria['type'])) {
|
||||
$qb->andWhere('j.type = :type')
|
||||
->setParameter('type', $criteria['type']);
|
||||
}
|
||||
|
||||
if (isset($criteria['createdAfter'])) {
|
||||
$qb->andWhere('j.createdAt >= :createdAfter')
|
||||
->setParameter('createdAfter', $criteria['createdAfter']);
|
||||
}
|
||||
|
||||
if (isset($criteria['createdBefore'])) {
|
||||
$qb->andWhere('j.createdAt <= :createdBefore')
|
||||
->setParameter('createdBefore', $criteria['createdBefore']);
|
||||
}
|
||||
|
||||
if (isset($criteria['sortBy'])) {
|
||||
$qb->orderBy('j.' . $criteria['sortBy'], $criteria['sortOrder'] ?? 'ASC');
|
||||
}
|
||||
|
||||
if (isset($criteria['offset'])) {
|
||||
$qb->setFirstResult($criteria['offset']);
|
||||
}
|
||||
|
||||
if (isset($criteria['limit'])) {
|
||||
$qb->setMaxResults($criteria['limit']);
|
||||
}
|
||||
|
||||
$entities = $qb->getQuery()->getResult();
|
||||
|
||||
return array_map(fn(JobEntity $entity) => $this->mapper->toDomain($entity), $entities);
|
||||
}
|
||||
|
||||
public function countByCriteria(array $criteria): int
|
||||
{
|
||||
$qb = $this->entityManager->createQueryBuilder()
|
||||
->select('COUNT(j.id)')
|
||||
->from(JobEntity::class, 'j');
|
||||
|
||||
if (isset($criteria['status'])) {
|
||||
$qb->andWhere('j.status = :status')
|
||||
->setParameter('status', $criteria['status']->value);
|
||||
}
|
||||
|
||||
if (isset($criteria['type'])) {
|
||||
$qb->andWhere('j.type = :type')
|
||||
->setParameter('type', $criteria['type']);
|
||||
}
|
||||
|
||||
if (isset($criteria['createdAfter'])) {
|
||||
$qb->andWhere('j.createdAt >= :createdAfter')
|
||||
->setParameter('createdAfter', $criteria['createdAfter']);
|
||||
}
|
||||
|
||||
if (isset($criteria['createdBefore'])) {
|
||||
$qb->andWhere('j.createdAt <= :createdBefore')
|
||||
->setParameter('createdBefore', $criteria['createdBefore']);
|
||||
}
|
||||
|
||||
return (int) $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user