Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(bridge): API-Platform bridge #25

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
Empty file added .github/workflows/daily.yml
Empty file.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
- Background worker
- [Symfony/Messenger](https://symfony.com/doc/current/messenger.html) integration
- [Mercure](https://www.mercure.rocks) integration
- [API-Platform](https://api-platform.com/) support

## Installation

Expand Down Expand Up @@ -98,6 +99,7 @@ When a task is configured, time to execute it, two approaches can be used:
* [Usage](doc/usage.md)
* [Configuration](doc/configuration.md)
* [Best practices](doc/best_practices.md)
* [API-Platform](doc/api_platform.md)
* [Tasks](doc/tasks.md)
* [Transports](doc/transport.md)
* [Lock](doc/lock.md)
Expand Down
5 changes: 5 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
"psr-4": {
"SchedulerBundle\\": "src/",
"SchedulerBundle\\Bridge\\": "src/Bridge/",
"SchedulerBundle\\Bridge\\ApiPlatform\\": "src/Bridge/ApiPlatform/",
"SchedulerBundle\\Bridge\\ApiPlatform\\Filter\\": "src/Bridge/ApiPlatform/Filter/",
"SchedulerBundle\\Bridge\\Doctrine\\": "src/Bridge/Doctrine/",
"SchedulerBundle\\Bridge\\Doctrine\\SchemaListener\\": "src/Bridge/Doctrine/SchemaListener/",
"SchedulerBundle\\Bridge\\Doctrine\\Transport\\": "src/Bridge/Doctrine/Transport/",
Expand Down Expand Up @@ -56,6 +58,8 @@
"psr-4": {
"Tests\\SchedulerBundle\\": "tests/",
"Tests\\SchedulerBundle\\Bridge\\": "tests/Bridge/",
"Tests\\SchedulerBundle\\Bridge\\ApiPlatform\\": "tests/Bridge/ApiPlatform/",
"Tests\\SchedulerBundle\\Bridge\\ApiPlatform\\Filter\\": "tests/Bridge/ApiPlatform/Filter/",
"Tests\\SchedulerBundle\\Bridge\\Doctrine\\": "tests/Bridge/Doctrine/",
"Tests\\SchedulerBundle\\Bridge\\Doctrine\\SchemaListener\\": "tests/Bridge/Doctrine/SchemaListener/",
"Tests\\SchedulerBundle\\Bridge\\Doctrine\\Transport\\": "tests/Bridge/Doctrine/Transport/",
Expand Down Expand Up @@ -118,6 +122,7 @@
"ext-pcntl": "*",
"ext-pdo": "*",
"ext-redis": "*",
"api-platform/core": "^2.6",
"doctrine/dbal": "^3.1.4",
"doctrine/orm": "^2.8",
"friendsofphp/php-cs-fixer": "^3.5",
Expand Down
3 changes: 3 additions & 0 deletions doc/api_platform.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# API-Platform

This bundle provides a bridge with API-Platform,
115 changes: 115 additions & 0 deletions src/Bridge/ApiPlatform/Filter/SearchFilter.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

declare(strict_types=1);

namespace SchedulerBundle\Bridge\ApiPlatform\Filter;

use ApiPlatform\Core\Api\FilterInterface;
use SchedulerBundle\Task\TaskInterface;
use SchedulerBundle\Task\TaskListInterface;

/**
* @author Guillaume Loulier <contact@guillaumeloulier.fr>
*/
final class SearchFilter implements FilterInterface
{
/**
* {@inheritdoc}
*/
public function getDescription(string $resourceClass): array
{
if (TaskInterface::class !== $resourceClass) {
return [];
}

return [
'expression' => [
'type' => 'string',
'required' => false,
'property' => 'expression',
'swagger' => [
'description' => 'Filter tasks using the expression',
'name' => 'expression',
'type' => 'string',
],
],
'queued' => [
'type' => 'bool',
'required' => false,
'property' => 'queued',
'swagger' => [
'description' => 'Filter tasks that are queued',
'name' => 'queued',
'type' => 'bool',
],
],
'state' => [
'type' => 'string',
'required' => false,
'property' => 'state',
'swagger' => [
'description' => 'Filter tasks with a specific state',
'name' => 'state',
'type' => 'string',
],
],
'timezone' => [
'type' => 'string',
'required' => false,
'property' => 'timezone',
'swagger' => [
'description' => 'Filter tasks scheduled using a specific timezone',
'name' => 'timezone',
'type' => 'string',
],
],
'type' => [
'type' => 'string',
'required' => false,
'property' => 'type',
'swagger' => [
'description' => 'Filter tasks depending on internal type',
'name' => 'timezone',
'type' => 'string',
],
],
];
}

public function filter(TaskListInterface $list, array $filters = []): TaskListInterface
{
if ([] === $filters) {
return $list;
}

if (0 === $list->count()) {
return $list;
}

foreach ($filters as $filter => $value) {
switch ($filter) {
case 'expression':
$list = $list->filter(static fn (TaskInterface $task): bool => $value === $task->getExpression());
break;
case 'queued':
$list = $list->filter(static fn (TaskInterface $task): bool => $task->isQueued());
break;
case 'state':
$list = $list->filter(static fn (TaskInterface $task): bool => $value === $task->getState());
break;
case 'timezone':
$list = $list->filter(static function (TaskInterface $task) use ($value): bool {
$timezone = $task->getTimezone();

return null !== $timezone && $value === $timezone->getName();
});
break;
case 'type':
$list = $list->filter(static fn (TaskInterface $task): bool => $value === $task::class);
break;
}
}

return $list;
}
}
31 changes: 31 additions & 0 deletions src/Bridge/ApiPlatform/Model/Task.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace SchedulerBundle\Bridge\ApiPlatform\Model;

use ApiPlatform\Core\Annotation\ApiFilter;
use ApiPlatform\Core\Annotation\ApiResource;
use SchedulerBundle\Bridge\ApiPlatform\Filter\SearchFilter;
use SchedulerBundle\Task\AbstractTask;
use SchedulerBundle\Task\TaskInterface;

#[ApiResource(
collectionOperations: ['get'],
itemOperations: ['get'],
routePrefix: '/tasks',
stateless: true,
)]
#[ApiFilter(SearchFilter::class)]
final class Task extends AbstractTask
{
public function __construct(private TaskInterface $wrappedTask)
{
parent::__construct(sprintf('%s.wrapped', $wrappedTask->getName()));
}

public function getWrappedTask(): TaskInterface
{
return $this->wrappedTask;
}
}
57 changes: 57 additions & 0 deletions src/Bridge/ApiPlatform/TaskDataProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

declare(strict_types=1);

namespace SchedulerBundle\Bridge\ApiPlatform;

use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use SchedulerBundle\Task\TaskInterface;
use SchedulerBundle\Transport\TransportInterface;
use Throwable;
use function sprintf;

/**
* @author Guillaume Loulier <contact@guillaumeloulier.fr>
*/
final class TaskDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
{
private TransportInterface $transport;
private LoggerInterface $logger;

public function __construct(
TransportInterface $transport,
?LoggerInterface $logger = null
) {
$this->transport = $transport;
$this->logger = $logger ?: new NullLogger();
}

/**
* {@inheritdoc}
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return $resourceClass === TaskInterface::class;
}

/**
* {@inheritdoc}
*/
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): TaskInterface
{
try {
$task = $this->transport->get($id);
} catch (Throwable $throwable) {
$this->logger->critical(sprintf('The task "%s" cannot be found', $id), [
'error' => $throwable->getMessage(),
]);

throw $throwable;
}

return $task;
}
}
66 changes: 66 additions & 0 deletions src/Bridge/ApiPlatform/TaskListDataProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace SchedulerBundle\Bridge\ApiPlatform;

use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use SchedulerBundle\Bridge\ApiPlatform\Filter\SearchFilter;
use SchedulerBundle\Task\TaskInterface;
use SchedulerBundle\Task\TaskListInterface;
use SchedulerBundle\Transport\TransportInterface;
use Throwable;
use function array_key_exists;

/**
* @author Guillaume Loulier <contact@guillaumeloulier.fr>
*/
final class TaskListDataProvider implements ContextAwareCollectionDataProviderInterface, RestrictedDataProviderInterface
{
private SearchFilter $searchFilter;
private TransportInterface $transport;
private LoggerInterface $logger;

public function __construct(
SearchFilter $searchFilter,
TransportInterface $transport,
?LoggerInterface $logger = null
) {
$this->searchFilter = $searchFilter;
$this->transport = $transport;
$this->logger = $logger ?: new NullLogger();
}

/**
* {@inheritdoc}
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return $resourceClass === TaskInterface::class;
}

/**
* {@inheritdoc}
*/
public function getCollection(string $resourceClass, string $operationName = null, array $context = []): TaskListInterface
{
try {
$list = $this->transport->list();
} catch (Throwable $throwable) {
$this->logger->critical('The list cannot be retrieved', [
'error' => $throwable->getMessage(),
]);

throw $throwable;
}

if (array_key_exists('filters', $context) && [] !== $context['filters']) {
return $this->searchFilter->filter($list, $context['filters']);
}

return $list;
}
}
4 changes: 4 additions & 0 deletions src/DependencyInjection/SchedulerBundleConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,10 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->scalarNode('api_platform')
->info('Enable the API-Platform support')
->defaultValue(false)
->end()
->end()
->end()
;
Expand Down
Loading