zenstruck / schedule-bundle

Schedule Cron jobs (commands/callbacks/bash scripts) within your Symfony application.
MIT License
386 stars 20 forks source link

Add schedule config possibility from database #66

Open gisostallenberg opened 2 years ago

gisostallenberg commented 2 years ago

Are you open to a possibility to configure tasks (only the types that accept string arguments) using a database. I'm aware this can be created in my app, but having this out of the box would be a great addition to this nice package.

kbond commented 2 years ago

Interesting idea. My concern is with the added complexity. I'm also not sure how common such a requirement is... Are you thinking to use Doctrine ORM?

In my app, I have the task results stored in the db (which was easy to achieve) but not the definitions themselves.

I am completely open to an added event example showing how to achieve what you want. This would better scope the feature too - I've added some of the extending examples as features.

gisostallenberg commented 2 years ago

An example scheduler bundle that uses this is https://github.com/j-guyon/CommandSchedulerBundle. One of the things I like about this construction is that it allows to enable/disable command runs and configure cron times from a GUI. I think it could be in a separate package though.

kbond commented 2 years ago

What fields would we add to the Entity? Cron string for sure I think (maybe not if the user wants complete control) but what about the other properties/extensions?

I could see adding a basic base entity and subscriber:

abstract class ScheduledTask
{
    abstract public function createTask(): Task // customize to your spec

    public function isEnabled(): bool
    {
        return true; // override with your own logic
    }
}
class DoctrineScheduledTaskSubscriber implements EventSubscriberInterface
{
    public function __construct(
        private ObjectManager $om,
        private string $class, // your app's ScheduledTask class-string (from config)
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [BuildScheduleEvent::class => 'configureTasks'];
    }

    public function configureTasks(BuildScheduleEvent $event): void
    {
        foreach ($this->om->getRepository($this->class) as $entity) {
            if (!$entity->isEnabled()) {
                continue;
            }

            $event->getSchedule()->add($entity->createTask());
        }
    }
}
gisostallenberg commented 2 years ago

I thought more in a fully automated way. The entity would not need a createTask.

class DoctrineScheduledTaskSubscriber implements EventSubscriberInterface
{
    /**
     * @param class-string $class
     */
    public function __construct(
        private ObjectManager $om,
        private string $class = \Zenstruck\ScheduleBundle\Entity\ScheduledCommandTask::class
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [BuildScheduleEvent::class => 'configureTasks'];
    }

    public function configureTasks(BuildScheduleEvent $event): void
    {
        foreach ($this->om->getRepository($this->class)->findEnabled() as $scheduledCommandTask) {
            $event->getSchedule()
                ->addCommand($scheduledCommandTask->getCommand())
                ->description($scheduledCommandTask->getDescription())
                // ...
                ->cron($scheduledCommandTask->getCronExpression());
        }
    }
}

I think most entity properties of https://github.com/j-guyon/CommandSchedulerBundle are usable, so I would add:

gisostallenberg commented 2 years ago

After having played around with this some time I would add the following properties to the entity:

kbond commented 2 years ago

What's the difference between locked and disabled?

What about lastDuration and lastMemoryUsage?

kbond commented 2 years ago

Here's sort of what I'm thinking (for task definitions from db):

  1. Model\TaskDefinition interface with method createTask(): Task.
  2. Model\TaskDefinitionRepository interface with method findTaskDefinitions(): array<TaskDefinition>.
  3. Abstract (MappedSuperclass) Model\BaseTaskDefinition with the implementation you describe above.
  4. Concrete Model\ORMTaskDefinitionRepository with an ORM-specific implementation.
  5. DoctrineTaskDefinitionSubscriber similar to above. This would use the repository to load tasks and add them to the schedule.

I think (1) and (2) above are important as it allows someone to do something custom.

This leaves adding the result to the task definition. Maybe TaskDefinitionRepository has a handleResult(Result $result): void method that a subscriber sends every result to it. It would be up to the definition to match with a db task definition and save. ORMTaskDefinitionRepository could provide this implementation for BaseTaskDefinition.

How are you planning to match up task results to task definitions?

gisostallenberg commented 2 years ago

What's the difference between locked and disabled?

Locked is a system state, disabled can be set by an admin interface for example.

What about lastDuration and lastMemoryUsage?

Good one, should be added...

pribeirojtm commented 8 months ago

hello @kbond and @gisostallenberg ,

Do you have any working example on how to implement Tasks managment via database like out of the box?

Thanks

kbond commented 8 months ago

Do you have any working example on how to implement Tasks managment via database like out of the box?

I don't, no. I'd suggest looking into symfony/scheduler if you can. I know it has this ability but the docs are still a work in progress.

I'm sort of keeping this bundle in maintenance mode until Symfony 5.4 is EOL.

gisostallenberg commented 8 months ago

No, I'm sorry

pribeirojtm commented 8 months ago

Thank you. symfony/scheduler is the way for new projects