├── pkg
├── bundle
│ ├── Tests
│ │ ├── Functional
│ │ │ ├── App
│ │ │ │ ├── config
│ │ │ │ │ ├── routing.yml
│ │ │ │ │ └── config.yml
│ │ │ │ ├── console.php
│ │ │ │ └── AppKernel.php
│ │ │ ├── SchedulerTest.php
│ │ │ ├── RemoteSchedulerTest.php
│ │ │ ├── SchedulerCommandTest.php
│ │ │ ├── ManagementCommandTest.php
│ │ │ └── WebTestCase.php
│ │ ├── QuartzBundleTest.php
│ │ ├── DependencyInjection
│ │ │ ├── QuartzExtensionTest.php
│ │ │ └── ConfigurationTest.php
│ │ └── Command
│ │ │ ├── ManagementCommandTest.php
│ │ │ └── SchedulerCommandTest.php
│ ├── .gitignore
│ ├── .travis.yml
│ ├── QuartzBundle.php
│ ├── composer.json
│ ├── phpunit.xml.dist
│ ├── DependencyInjection
│ │ ├── Configuration.php
│ │ └── QuartzExtension.php
│ ├── LICENSE
│ └── Command
│ │ ├── SchedulerCommand.php
│ │ └── ManagementCommand.php
├── quartz
│ ├── .gitignore
│ ├── Core
│ │ ├── Model.php
│ │ ├── ScheduleBuilder.php
│ │ ├── SchedulerException.php
│ │ ├── IntervalUnit.php
│ │ ├── JobPersistenceException.php
│ │ ├── SchedulerFactory.php
│ │ ├── CompletedExecutionInstruction.php
│ │ ├── Job.php
│ │ ├── SimpleJobFactory.php
│ │ ├── ObjectAlreadyExistsException.php
│ │ ├── JobFactory.php
│ │ ├── Calendar.php
│ │ ├── Key.php
│ │ ├── JobDetail.php
│ │ └── DateBuilder.php
│ ├── Scheduler
│ │ ├── JobRunShell.php
│ │ ├── StdJobRunShellFactory.php
│ │ ├── JobRunShellFactory.php
│ │ └── StdJobRunShell.php
│ ├── Events
│ │ ├── KeyEvent.php
│ │ ├── GroupsEvent.php
│ │ ├── TriggerEvent.php
│ │ ├── JobDetailEvent.php
│ │ ├── TickEvent.php
│ │ ├── JobExecutionContextEvent.php
│ │ └── ErrorEvent.php
│ ├── phpunit.xml.dist
│ ├── composer.json
│ ├── LICENSE
│ ├── examples
│ │ ├── scheduler.php
│ │ ├── cron-trigger.php
│ │ ├── simple-trigger.php
│ │ ├── calendar-interval-trigger.php
│ │ └── daily-interval-trigger.php
│ ├── Tests
│ │ ├── Core
│ │ │ ├── KeyTest.php
│ │ │ ├── SimpleJobFactoryTest.php
│ │ │ └── DateBuilderTest.php
│ │ ├── JobDetail
│ │ │ └── JobDetailTest.php
│ │ ├── Calendar
│ │ │ ├── CronCalendarTest.php
│ │ │ ├── HolidayCalendarTest.php
│ │ │ ├── DailyCalendarTest.php
│ │ │ ├── MonthlyCalendarTest.php
│ │ │ ├── BaseCalendarTest.php
│ │ │ └── WeeklyCalendarTest.php
│ │ └── Triggers
│ │ │ └── SimpleTriggerTest.php
│ ├── ModelClassFactory.php
│ ├── JobDetail
│ │ └── JobDetail.php
│ └── Calendar
│ │ ├── HolidayCalendar.php
│ │ ├── BaseCalendar.php
│ │ └── MonthlyCalendar.php
├── app
│ ├── etc
│ │ ├── packages
│ │ │ ├── test
│ │ │ │ └── framework.yaml
│ │ │ ├── quartz.yaml
│ │ │ ├── enqueue.yaml
│ │ │ ├── framework.yaml
│ │ │ └── app.yaml
│ │ ├── bundles.php
│ │ └── container.yaml
│ ├── .gitignore
│ ├── .travis.yml
│ ├── .env.dist
│ ├── phpunit.xml.dist
│ ├── bin
│ │ └── console
│ ├── LICENSE
│ ├── Makefile
│ ├── composer.json
│ ├── docker-compose.yml
│ └── src
│ │ └── Kernel.php
└── bridge
│ ├── Scheduler
│ ├── RemoteTransport.php
│ ├── EnqueueJobRunShell.php
│ ├── JobRunShellProcessor.php
│ ├── SchedulerFactory.php
│ └── RpcProtocol.php
│ ├── .travis.yml
│ ├── Yadm
│ ├── ModelHydrator.php
│ ├── FiredTriggerStorage.php
│ ├── PausedTriggerStorage.php
│ ├── CalendarStorage.php
│ ├── JobStorage.php
│ ├── StoreResource.php
│ ├── TriggerStorage.php
│ ├── BundleStoreResource.php
│ └── SimpleStoreResource.php
│ ├── phpunit.xml.dist
│ ├── Enqueue
│ ├── EnqueueRemoteTransport.php
│ ├── EnqueueResponseJob.php
│ └── EnqueueRemoteTransportProcessor.php
│ ├── composer.json
│ ├── Swoole
│ └── CheckMasterProcessSubscriber.php
│ ├── LICENSE
│ ├── Tests
│ ├── Enqueue
│ │ ├── EnqueueRemoteTransportTest.php
│ │ └── EnqueueResponseJobTest.php
│ ├── Swoole
│ │ └── CheckMasterProcessSubscriberTest.php
│ ├── Scheduler
│ │ ├── RemoteSchedulerTest.php
│ │ └── JobRunShellProcessorTest.php
│ └── DI
│ │ └── QuartzConfigurationTest.php
│ ├── DI
│ ├── QuartzJobCompilerPass.php
│ ├── RemoteSchedulerExtension.php
│ └── QuartzConfiguration.php
│ ├── SignalSubscriber.php
│ └── LoggerSubscriber.php
├── .gitignore
├── docker
├── entrypoiny.sh
├── cli.ini
├── xdebug.ini
└── Dockerfile
├── bin
├── splitsh-lite
├── test
├── dev
├── subtree-split
└── release
├── docker-compose.yml
├── LICENSE
├── phpunit.xml.dist
├── .travis.yml
└── composer.json
/pkg/bundle/Tests/Functional/App/config/routing.yml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/pkg/quartz/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | vendor
3 | composer.lock
4 | bin/phpunit
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | vendor
3 | composer.lock
4 | bin/phpunit
5 | bin/console
6 |
--------------------------------------------------------------------------------
/docker/entrypoiny.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | while true; do sleep 1; done
4 |
--------------------------------------------------------------------------------
/bin/splitsh-lite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/php-quartz/quartz-dev/HEAD/bin/splitsh-lite
--------------------------------------------------------------------------------
/pkg/quartz/Core/Model.php:
--------------------------------------------------------------------------------
1 | symfony/framework-bundle ###
2 | .env
3 | /var/
4 | /vendor/
5 | /web/bundles/
6 | ###< symfony/framework-bundle ###
7 |
--------------------------------------------------------------------------------
/docker/cli.ini:
--------------------------------------------------------------------------------
1 | error_reporting=E_ALL
2 | display_errors=on
3 | memory_limit = 2G
4 | max_execution_time=0
5 | date.timezone=UTC
6 | variables_order="EGPCS"
7 |
--------------------------------------------------------------------------------
/pkg/app/etc/packages/quartz.yaml:
--------------------------------------------------------------------------------
1 | quartz:
2 | scheduler:
3 | yadm_simple_store:
4 | uri: 'mongodb://%env(MONGODB_HOST)%:%env(MONGODB_PORT)%'
5 | dbName: '%env(MONGODB_DB)%'
6 |
--------------------------------------------------------------------------------
/docker/xdebug.ini:
--------------------------------------------------------------------------------
1 | zend_extension=xdebug.so
2 | xdebug.profiler_enable = Off
3 | xdebug.profiler_enable_trigger = Off
4 | xdebug.max_nesting_level = 5000
5 | xdebug.remote_enable = On
6 | xdebug.remote_host = 172.10.0.1
--------------------------------------------------------------------------------
/pkg/quartz/Core/ScheduleBuilder.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
5 | 'Enqueue\Bundle\EnqueueBundle' => ['all' => true],
6 | 'Quartz\Bundle\QuartzBundle' => ['all' => true],
7 | ];
8 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/SchedulerException.php:
--------------------------------------------------------------------------------
1 | {@link Scheduler}.
6 | */
7 | class SchedulerException extends \Exception
8 | {
9 | }
10 |
--------------------------------------------------------------------------------
/pkg/app/etc/container.yaml:
--------------------------------------------------------------------------------
1 | # Put parameters here that don't need to change on each machine where the app is deployed
2 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
3 | parameters:
4 |
5 | services:
6 |
--------------------------------------------------------------------------------
/pkg/bridge/Scheduler/RemoteTransport.php:
--------------------------------------------------------------------------------
1 |
8 | * Returns a client-usable handle to a Scheduler.
9 | *
10 | *
11 | * @return Scheduler
12 | */
13 | public function getScheduler();
14 | }
15 |
--------------------------------------------------------------------------------
/pkg/app/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | git:
4 | depth: 1
5 |
6 | language: php
7 |
8 | php:
9 | - '7.2'
10 |
11 | cache:
12 | directories:
13 | - $HOME/.composer/cache
14 |
15 | install:
16 | - composer self-update
17 | - composer install --ignore-platform-reqs --prefer-source
18 |
19 | script:
20 | - vendor/bin/phpunit
21 |
--------------------------------------------------------------------------------
/pkg/bridge/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | git:
4 | depth: 1
5 |
6 | language: php
7 |
8 | php:
9 | - '7.2'
10 |
11 | cache:
12 | directories:
13 | - $HOME/.composer/cache
14 |
15 | install:
16 | - composer self-update
17 | - composer install --ignore-platform-reqs --prefer-source
18 |
19 | script:
20 | - vendor/bin/phpunit
21 |
--------------------------------------------------------------------------------
/pkg/bundle/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | git:
4 | depth: 1
5 |
6 | language: php
7 |
8 | php:
9 | - '7.2'
10 |
11 | cache:
12 | directories:
13 | - $HOME/.composer/cache
14 |
15 | install:
16 | - composer self-update
17 | - composer install --prefer-source --ignore-platform-reqs
18 |
19 | script:
20 | - vendor/bin/phpunit
21 |
--------------------------------------------------------------------------------
/pkg/quartz/Scheduler/JobRunShell.php:
--------------------------------------------------------------------------------
1 | run(new ArgvInput());
12 |
--------------------------------------------------------------------------------
/docker/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM formapro/nginx-php-fpm:7.3-latest-all-exts
2 |
3 | COPY ./cli.ini /etc/php/7.3/cli/conf.d/1-dev_cli.ini
4 | COPY ./entrypoiny.sh /usr/local/bin/entrypoint.sh
5 | RUN chmod u+x /usr/local/bin/entrypoint.sh
6 | RUN apt-get update && apt-get -y --no-install-recommends --no-install-suggests install netcat
7 |
8 | RUN mkdir -p /app
9 | WORKDIR /app
10 |
11 | CMD /usr/local/bin/entrypoint.sh
12 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Functional/SchedulerTest.php:
--------------------------------------------------------------------------------
1 | get('test_quartz.scheduler');
12 |
13 | $this->assertInstanceOf(StdScheduler::class, $scheduler);
14 | }
15 | }
--------------------------------------------------------------------------------
/pkg/app/etc/packages/enqueue.yaml:
--------------------------------------------------------------------------------
1 | enqueue:
2 | default:
3 | transport:
4 | dsn: 'amqp:'
5 | connection_factory_class: 'Enqueue\AmqpBunny\AmqpConnectionFactory'
6 | host: '%env(RABBITMQ_HOST)%'
7 | port: '%env(RABBITMQ_PORT)%'
8 | user: '%env(RABBITMQ_USER)%'
9 | pass: '%env(RABBITMQ_PASS)%'
10 | vhost: '%env(RABBITMQ_VHOST)%'
11 | client:
12 | app_name: 'quartz'
13 |
--------------------------------------------------------------------------------
/pkg/bundle/QuartzBundle.php:
--------------------------------------------------------------------------------
1 | addCompilerPass(new QuartzJobCompilerPass());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Functional/RemoteSchedulerTest.php:
--------------------------------------------------------------------------------
1 | get('test_quartz.remote.scheduler');
12 |
13 | $this->assertInstanceOf(RemoteScheduler::class, $scheduler);
14 | }
15 | }
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Functional/SchedulerCommandTest.php:
--------------------------------------------------------------------------------
1 | get('test_quartz.cli.scheduler');
12 |
13 | $this->assertInstanceOf(SchedulerCommand::class, $scheduler);
14 | }
15 | }
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Functional/ManagementCommandTest.php:
--------------------------------------------------------------------------------
1 | get('test_quartz.cli.management');
12 |
13 | $this->assertInstanceOf(ManagementCommand::class, $scheduler);
14 | }
15 | }
--------------------------------------------------------------------------------
/pkg/quartz/Events/KeyEvent.php:
--------------------------------------------------------------------------------
1 | key = $key;
19 | }
20 |
21 | /**
22 | * @return Key
23 | */
24 | public function getKey(): Key
25 | {
26 | return $this->key;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/app/etc/packages/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: '%env(APP_SECRET)%'
3 | #default_locale: en
4 | #csrf_protection: null
5 | #http_method_override: true
6 | #trusted_hosts: null
7 | # https://symfony.com/doc/current/reference/configuration/framework.html#handler-id
8 | #session:
9 | # handler_id: session.handler.native_file
10 | # save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
11 | #esi: ~
12 | #fragments: ~
13 | php_errors:
14 | log: true
15 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/CompletedExecutionInstruction.php:
--------------------------------------------------------------------------------
1 | hydrate($values, build_object($class, $values));
20 | }
21 | }
--------------------------------------------------------------------------------
/pkg/quartz/Events/GroupsEvent.php:
--------------------------------------------------------------------------------
1 | groups = $groups;
17 | }
18 |
19 | /**
20 | * @return string[]|null
21 | */
22 | public function getGroups(): array
23 | {
24 | return $this->groups;
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/quartz/Events/TriggerEvent.php:
--------------------------------------------------------------------------------
1 | trigger = $trigger;
19 | }
20 |
21 | /**
22 | * @return Trigger
23 | */
24 | public function getTrigger()
25 | {
26 | return $this->trigger;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/quartz/Events/JobDetailEvent.php:
--------------------------------------------------------------------------------
1 | jobDetail = $jobDetail;
19 | }
20 |
21 | /**
22 | * @return JobDetail
23 | */
24 | public function getJobDetail()
25 | {
26 | return $this->jobDetail;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/pkg/quartz/Events/TickEvent.php:
--------------------------------------------------------------------------------
1 | interrupted = false;
14 | }
15 |
16 | /**
17 | * @return boolean
18 | */
19 | public function isInterrupted()
20 | {
21 | return $this->interrupted;
22 | }
23 |
24 | /**
25 | * @param boolean $interrupt
26 | */
27 | public function setInterrupted($interrupt)
28 | {
29 | $this->interrupted = (bool) $interrupt;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/quartz/Scheduler/StdJobRunShellFactory.php:
--------------------------------------------------------------------------------
1 | jobRunShell = $jobRunShell;
19 | }
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function createJobRunShell(Trigger $trigger)
25 | {
26 | return $this->jobRunShell;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/bin/test:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | FORCE_EXIT=false
4 |
5 | function waitForService()
6 | {
7 | ATTEMPTS=0
8 | until nc -z $1 $2; do
9 | printf "wait for service %s:%s\n" $1 $2
10 | ((ATTEMPTS++))
11 | if [ $ATTEMPTS -ge $3 ]; then
12 | printf "service is not running %s:%s\n" $1 $2
13 | exit 1
14 | fi
15 | if [ "$FORCE_EXIT" = true ]; then
16 | exit;
17 | fi
18 |
19 | sleep 1
20 | done
21 |
22 | printf "service is online %s:%s\n" $1 $2
23 | }
24 |
25 | trap "FORCE_EXIT=true" SIGTERM SIGINT
26 |
27 | waitForService mongo 27017 50
28 |
29 | bin/phpunit "$@"
30 |
--------------------------------------------------------------------------------
/pkg/app/.env.dist:
--------------------------------------------------------------------------------
1 | # This file is a "template" of which env vars needs to be defined in your configuration or in an .env file
2 | # Set variables here that may be different on each deployment target of the app, e.g. development, staging, production.
3 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
4 |
5 | ###> symfony/framework-bundle ###
6 | APP_ENV=dev
7 | APP_DEBUG=1
8 | APP_SECRET=c2e19906f05378569f0f5357d3245bdd
9 | RABBITMQ_HOST=localhost
10 | RABBITMQ_PORT=5672
11 | RABBITMQ_USER=guest
12 | RABBITMQ_PASS=guest
13 | RABBITMQ_VHOST=/
14 | MONGODB_HOST=localhost
15 | MONGODB_PORT=27017
16 | MONGODB_DB=quartz
17 | ###< symfony/framework-bundle ###
18 |
--------------------------------------------------------------------------------
/pkg/quartz/Scheduler/JobRunShellFactory.php:
--------------------------------------------------------------------------------
1 |
8 | * Responsible for creating the instances of {@link QuartzScheduler} instance.
10 | *
11 | */
12 | interface JobRunShellFactory
13 | {
14 | /**
15 | *
16 | * Called by the {@link org.quartz.core.QuartzSchedulerThread}
17 | * to obtain instances of {@link JobRunShell}.
18 | *
19 | *
20 | * @param Trigger $trigger
21 | *
22 | * @return JobRunShell
23 | */
24 | public function createJobRunShell(Trigger $trigger); // orig TriggerFiredBundle
25 | }
26 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | app:
4 | build: { context: docker, dockerfile: Dockerfile }
5 | working_dir: '/app'
6 | restart: 'no'
7 | depends_on:
8 | - 'mongo'
9 | volumes:
10 | - './:/app:cached'
11 | # - './docker/xdebug.ini:/etc/php/7.0/mods-available/xdebug.ini'
12 | environment:
13 | PHP_IDE_CONFIG: 'serverName=quartz.dev'
14 | XDEBUG_CONFIG: 'idekey=PHPSTORM'
15 | MONGODB_HOST: 'mongo'
16 | MONGODB_PORT: '27017'
17 | MONGODB_DB: 'quartz'
18 |
19 | mongo:
20 | image: 'mongo:3'
21 | restart: 'on-failure'
22 | ports:
23 | - '27017:27017'
24 |
25 | generate-changelog:
26 | image: enqueue/generate-changelog:latest
27 | volumes:
28 | - './:/app:cached'
29 |
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/FiredTriggerStorage.php:
--------------------------------------------------------------------------------
1 | 1]),
21 | ];
22 | }
23 |
24 | public function getCreateCollectionOptions(): array
25 | {
26 | return [];
27 | }
28 | }
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/PausedTriggerStorage.php:
--------------------------------------------------------------------------------
1 | 1]),
21 | ];
22 | }
23 |
24 | public function getCreateCollectionOptions(): array
25 | {
26 | return [];
27 | }
28 | }
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/CalendarStorage.php:
--------------------------------------------------------------------------------
1 | 1], ['unique' => true]),
21 | ];
22 | }
23 |
24 | public function getCreateCollectionOptions(): array
25 | {
26 | return [];
27 | }
28 | }
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/JobStorage.php:
--------------------------------------------------------------------------------
1 | 1, 'group' => 1], ['unique' => true]),
21 | new Index(['group' => 1]),
22 | ];
23 | }
24 |
25 | public function getCreateCollectionOptions(): array
26 | {
27 | return [];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/StoreResource.php:
--------------------------------------------------------------------------------
1 |
8 | * Called by the {@link Scheduler} when a {@link Trigger}
9 | * fires that is associated with the Job.
10 | *
11 | *
12 | *
13 | * The implementation may wish to set a
14 | * {@link JobExecutionContext#setResult(Object) result} object on the
15 | * {@link JobExecutionContext} before this method exits. The result itself
16 | * is meaningless to Quartz, but may be informative to
17 | * {@link JobListener}s or
18 | * {@link TriggerListener}s that are watching the job's
19 | * execution.
20 | *
21 | *
22 | * @param JobExecutionContext $context
23 | *
24 | */
25 | public function execute(JobExecutionContext $context);
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/app/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 | ./tests
18 |
19 |
20 |
21 |
22 |
23 | .
24 |
25 | ./vendor
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Functional/WebTestCase.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
23 | static::$container = static::$kernel->getContainer();
24 | }
25 |
26 | /**
27 | * @return string
28 | */
29 | public static function getKernelClass()
30 | {
31 | include_once __DIR__.'/App/AppKernel.php';
32 |
33 | return AppKernel::class;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/bridge/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 | ./Tests
18 |
19 |
20 |
21 |
22 |
23 | .
24 |
25 | ./vendor
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/pkg/quartz/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 | ./Tests
18 |
19 |
20 |
21 |
22 |
23 | .
24 |
25 | ./vendor
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/pkg/app/etc/packages/app.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | # default configuration for services in *this* file
3 | _defaults:
4 | # automatically injects dependencies in your services
5 | autowire: true
6 | # automatically registers your services as commands, event subscribers, etc.
7 | autoconfigure: true
8 | # this means you cannot fetch services directly from the container via $container->get()
9 | # if you need to do this, you can override this setting on individual services
10 | public: false
11 |
12 | # makes classes in src/ available to be used as services
13 | # this creates a service per class whose id is the fully-qualified class name
14 | Quartz\App\:
15 | resource: '../../src/*'
16 | # you can exclude directories or files
17 | # but if a service is unused, it's removed anyway
18 | exclude: '../../src/{Entity,Repository,Tests}'
19 |
--------------------------------------------------------------------------------
/pkg/bridge/Enqueue/EnqueueRemoteTransport.php:
--------------------------------------------------------------------------------
1 | producer = $producer;
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function request(array $parameters)
30 | {
31 | $responseMessage = $this->producer->sendCommand(self::COMMAND, $parameters, true)->receive();
32 |
33 | return JSON::decode($responseMessage->getBody());
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/bundle/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-quartz/bundle",
3 | "type": "symfony-bundle",
4 | "description": "Quartz Remote Scheduler Bundle",
5 | "keywords": ["job", "time", "task", "time scheduler", "quartz", "chrono"],
6 | "license": "MIT",
7 | "require": {
8 | "php": "^7.2",
9 | "symfony/framework-bundle": "^3|^4",
10 | "symfony/console": "^3|^4",
11 | "enqueue/enqueue-bundle": "^0.9",
12 | "php-quartz/bridge": "^0.2"
13 | },
14 | "require-dev": {
15 | "phpunit/phpunit": "^5.5",
16 | "symfony/browser-kit": "^3|^4",
17 | "enqueue/null": "^0.9"
18 | },
19 | "autoload": {
20 | "psr-4": { "Quartz\\Bundle\\": "" },
21 | "exclude-from-classmap": [
22 | "/Tests/"
23 | ]
24 | },
25 | "extra": {
26 | "branch-alias": {
27 | "dev-master": "0.2.x-dev"
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/bridge/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-quartz/bridge",
3 | "type": "library",
4 | "minimum-stability": "dev",
5 | "description": "Job Time Scheduler Library",
6 | "keywords": ["job", "time", "task", "time scheduler", "quartz", "chrono"],
7 | "license": "MIT",
8 | "require": {
9 | "php": "^7.2",
10 | "symfony/framework-bundle": "^3|^4",
11 | "enqueue/enqueue": "^0.9",
12 | "makasim/yadm": "^0.5.7",
13 | "makasim/values": "^0.5.2",
14 | "queue-interop/queue-interop": "^0.7|^0.8",
15 | "php-quartz/quartz": "^0.2"
16 | },
17 | "require-dev": {
18 | "phpunit/phpunit": "^5.5"
19 | },
20 | "autoload": {
21 | "psr-4": { "Quartz\\Bridge\\": "" },
22 | "exclude-from-classmap": [
23 | "/Tests/"
24 | ]
25 | },
26 | "extra": {
27 | "branch-alias": {
28 | "dev-master": "0.2.x-dev"
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/QuartzBundleTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Bundle::class, new QuartzBundle());
16 | }
17 |
18 | public function testShouldRegisterExpectedCompilerPasses()
19 | {
20 | $container = $this->createMock(ContainerBuilder::class);
21 | $container
22 | ->expects($this->at(0))
23 | ->method('addCompilerPass')
24 | ->with($this->isInstanceOf(QuartzJobCompilerPass::class))
25 | ;
26 |
27 | $bundle = new QuartzBundle();
28 | $bundle->build($container);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/pkg/bundle/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 | ./Tests
18 |
19 |
20 |
21 |
22 |
23 | .
24 |
25 | ./vendor
26 | ./Resources
27 | ./Tests
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/pkg/bridge/Scheduler/EnqueueJobRunShell.php:
--------------------------------------------------------------------------------
1 | producer = $producer;
24 | }
25 |
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function initialize(StdScheduler $scheduler)
30 | {
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function execute(Trigger $trigger)
37 | {
38 | $this->producer->sendCommand(self::COMMAND, [
39 | 'fireInstanceId' => $trigger->getFireInstanceId(),
40 | ], false);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/pkg/quartz/Events/JobExecutionContextEvent.php:
--------------------------------------------------------------------------------
1 | context = $context;
24 | $this->vetoed = false;
25 | }
26 |
27 | /**
28 | * @return JobExecutionContext
29 | */
30 | public function getContext()
31 | {
32 | return $this->context;
33 | }
34 |
35 | /**
36 | * @return boolean
37 | */
38 | public function isVetoed()
39 | {
40 | return $this->vetoed;
41 | }
42 |
43 | /**
44 | * @param boolean $vetoed
45 | */
46 | public function setVetoed($vetoed)
47 | {
48 | $this->vetoed = (bool) $vetoed;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/quartz/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-quartz/quartz",
3 | "type": "library",
4 | "minimum-stability": "dev",
5 | "description": "Job Time Scheduler Library",
6 | "keywords": ["job", "time", "task", "time scheduler", "quartz", "chrono"],
7 | "license": "MIT",
8 | "require": {
9 | "php": "^7.2",
10 | "ramsey/uuid": "^2|^3.5",
11 | "g4/cron": "^0.1",
12 | "symfony/event-dispatcher": "^3.4|^4"
13 | },
14 | "require-dev": {
15 | "makasim/yadm": "^0.5.7",
16 | "makasim/values": "^0.5.2",
17 | "phpunit/phpunit": "^5.5"
18 | },
19 | "suggest": {
20 | "php-quartz/bridge": "Provides a Yadm storage, Enqueue integration and other extension."
21 | },
22 | "autoload": {
23 | "psr-4": { "Quartz\\": "" },
24 | "exclude-from-classmap": [
25 | "/Tests/"
26 | ]
27 | },
28 | "extra": {
29 | "branch-alias": {
30 | "dev-master": "0.2.x-dev"
31 | }
32 | },
33 | "config": {
34 | "bin-dir": "bin"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/bridge/Swoole/CheckMasterProcessSubscriber.php:
--------------------------------------------------------------------------------
1 | setInterrupted(true);
24 | }
25 | }
26 |
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public static function getSubscribedEvents()
31 | {
32 | return [
33 | Event::SCHEDULER_TICK => 'checkMasterProcessor',
34 | ];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/app/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | load(__DIR__.'/../.env');
22 | }
23 |
24 | $input = new ArgvInput();
25 | $env = $input->getParameterOption(['--env', '-e'], getenv('APP_ENV') ?: 'dev');
26 | $debug = getenv('APP_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']);
27 |
28 | if ($debug && class_exists(Debug::class)) {
29 | Debug::enable();
30 | }
31 |
32 | $kernel = new Kernel($env, $debug);
33 | $application = new Application($kernel);
34 | $application->run($input);
35 |
--------------------------------------------------------------------------------
/pkg/bundle/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
17 |
18 | $rootNode->children()
19 | ->variableNode('remote_scheduler')
20 | ->defaultValue([])
21 | ->treatNullLike([])
22 | ->treatTrueLike([])
23 | ->info('Remote scheduler configuration')
24 | ->end()
25 | ->variableNode('scheduler')
26 | ->defaultValue(false)
27 | ->treatNullLike([])
28 | ->treatTrueLike([])
29 | ->info('Scheduler configuration')
30 | ->end()
31 | ->end()
32 | ;
33 |
34 | return $tb;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Forma-Pro
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/TriggerStorage.php:
--------------------------------------------------------------------------------
1 | 1, 'group' => 1], ['unique' => true]),
21 | new Index(['group' => 1]),
22 | new Index(['jobName' => 1, 'jobGroup' => 1]),
23 | new Index(['jobGroup' => 1]),
24 | new Index(['calendarName' => 1]),
25 | new Index(['state' => 1]),
26 | new Index(['nextFireTime.unix' => 1]),
27 | ];
28 | }
29 |
30 | public function getCreateCollectionOptions(): array
31 | {
32 | return [];
33 | }
34 | }
--------------------------------------------------------------------------------
/bin/dev:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -x
4 | set -e
5 |
6 | while getopts "busdtef" OPTION; do
7 | case $OPTION in
8 | b)
9 | COMPOSE_PROJECT_NAME=quartz docker-compose build
10 | ;;
11 | u)
12 | COMPOSE_PROJECT_NAME=quartz docker-compose up
13 | ;;
14 | s)
15 | COMPOSE_PROJECT_NAME=quartz docker-compose stop
16 | ;;
17 | d)
18 | COMPOSE_PROJECT_NAME=quartz docker-compose down
19 | ;;
20 | e)
21 | docker exec -it quartz_app_1 /bin/bash
22 | ;;
23 | f)
24 | ./bin/php-cs-fixer fix
25 | ;;
26 | t)
27 | COMPOSE_PROJECT_NAME=quartz docker-compose run --workdir="/app" --rm app ./bin/test "$2"
28 | ;;
29 | c)
30 | COMPOSE_PROJECT_NAME=quartz docker-compose run -e CHANGELOG_GITHUB_TOKEN=${CHANGELOG_GITHUB_TOKEN:-""} --workdir="/app" --rm generate-changelog github_changelog_generator --future-release "$2" --simple-list
31 | ;;
32 | \?)
33 | echo "Invalid option: -$OPTARG" >&2
34 | exit 1
35 | ;;
36 | :)
37 | echo "Option -$OPTARG requires an argument." >&2
38 | exit 1
39 | ;;
40 | esac
41 | done
42 |
--------------------------------------------------------------------------------
/pkg/app/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Forma-Pro
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/pkg/bridge/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Forma-Pro
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/pkg/bundle/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Forma-Pro
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/pkg/quartz/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 | Copyright (c) 2017 Forma-Pro
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is furnished
9 | to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in all
12 | copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/pkg/bundle/Command/SchedulerCommand.php:
--------------------------------------------------------------------------------
1 | scheduler = $scheduler;
24 | $this->setDescription('Quartz scheduler');
25 | }
26 |
27 | protected function execute(InputInterface $input, OutputInterface $output)
28 | {
29 | $this->scheduler->getEventDispatcher()->addSubscriber(new LoggerSubscriber(new ConsoleLogger($output)));
30 | $this->scheduler->getEventDispatcher()->addSubscriber(new SignalSubscriber());
31 |
32 | $this->scheduler->start();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/SimpleJobFactory.php:
--------------------------------------------------------------------------------
1 | Job,
14 | * ]
15 | *
16 | * @param Job[] $jobs
17 | */
18 | public function __construct(array $jobs = [])
19 | {
20 | $this->jobs = $jobs;
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function newJob(JobDetail $jobDetail)
27 | {
28 | $job = null;
29 | if (isset($this->jobs[$jobDetail->getJobClass()])) {
30 | $job = $this->jobs[$jobDetail->getJobClass()];
31 | } elseif (class_exists($jobDetail->getJobClass())) {
32 | $class = $jobDetail->getJobClass();
33 | $job = new $class;
34 | }
35 |
36 | if (false == $job instanceof Job) {
37 | throw new SchedulerException(sprintf('Required instance of "%s", but got: "%s"',
38 | Job::class, is_object($job) ? get_class($job) : gettype($job)));
39 | }
40 |
41 | return $job;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/quartz/examples/scheduler.php:
--------------------------------------------------------------------------------
1 | sprintf('mongodb://%s:%s', getenv('MONGODB_HOST'), getenv('MONGODB_PORT')),
17 | 'dbName' => getenv('MONGODB_DB')
18 | ];
19 |
20 | $store = new YadmStore(new SimpleStoreResource($config));
21 | $store->clearAllSchedulingData();
22 |
23 | class MyJob implements Job
24 | {
25 | public function execute(JobExecutionContext $context)
26 | {
27 | echo sprintf('Now: %s | Scheduled: %s'.PHP_EOL, date('H:i:s'), $context->getTrigger()->getScheduledFireTime()->format('H:i:s'));
28 | }
29 | }
30 |
31 | $scheduler = new StdScheduler($store, new StdJobRunShellFactory(new StdJobRunShell()), new SimpleJobFactory(), new EventDispatcher());
32 | $scheduler->start();
33 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Functional/App/config/config.yml:
--------------------------------------------------------------------------------
1 | parameters:
2 | locale: 'en'
3 | secret: 'ThisTokenIsNotSoSecretChangeIt'
4 |
5 |
6 | framework:
7 | #esi: ~
8 | #translator: { fallback: "%locale%" }
9 | test: ~
10 | assets: false
11 | templating: false
12 | session:
13 | storage_id: session.storage.mock_file
14 | secret: '%secret%'
15 | router: { resource: '%kernel.root_dir%/config/routing.yml' }
16 | default_locale: '%locale%'
17 |
18 | enqueue:
19 | default:
20 | transport: 'null:'
21 | client:
22 | traceable_producer: true
23 |
24 | quartz:
25 | remote_scheduler: ~
26 | scheduler:
27 | yadm_simple_store:
28 | uri: 'mongodb://mongo:27017/quartz'
29 |
30 | services:
31 | test_quartz.cli.management:
32 | public: true
33 | alias: 'quartz.cli.management'
34 |
35 | test_quartz.remote.scheduler:
36 | public: true
37 | alias: 'quartz.remote.scheduler'
38 |
39 | test_quartz.cli.scheduler:
40 | public: true
41 | alias: 'quartz.cli.scheduler'
42 |
43 | test_quartz.scheduler:
44 | public: true
45 | alias: 'quartz.scheduler'
--------------------------------------------------------------------------------
/pkg/bridge/Enqueue/EnqueueResponseJob.php:
--------------------------------------------------------------------------------
1 | producer = $producer;
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function execute(JobExecutionContext $context)
27 | {
28 | $data = $context->getMergedJobDataMap();
29 |
30 | if (false == empty($data['topic'])) {
31 | $this->producer->sendEvent($data['topic'], $data);
32 | } elseif (false == empty($data['command'])) {
33 | $this->producer->sendCommand($data['command'], $data);
34 | } else {
35 | $context->getTrigger()->setErrorMessage('There is no enqueue topic or command');
36 | $context->setUnscheduleFiringTrigger();
37 |
38 | return;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/app/Makefile:
--------------------------------------------------------------------------------
1 | ifndef APP_ENV
2 | include .env
3 | endif
4 |
5 | ###> symfony/framework-bundle ###
6 | cache-clear:
7 | @test -f bin/console && bin/console cache:clear --no-warmup || rm -rf var/cache/*
8 | .PHONY: cache-clear
9 |
10 | cache-warmup: cache-clear
11 | @test -f bin/console && bin/console cache:warmup || echo "cannot warmup the cache (needs symfony/console)"
12 | .PHONY: cache-warmup
13 |
14 | CONSOLE=bin/console
15 | sf_console:
16 | @test -f $(CONSOLE) || printf "Run \033[32mcomposer require cli\033[39m to install the Symfony console.\n"
17 | @exit
18 |
19 | serve_as_sf: sf_console
20 | @test -f $(CONSOLE) && $(CONSOLE)|grep server:start > /dev/null || ${MAKE} serve_as_php
21 | @$(CONSOLE) server:start || exit 1
22 |
23 | @printf "Quit the server with \033[32;49mbin/console server:stop.\033[39m\n"
24 |
25 | serve_as_php:
26 | @printf "\033[32;49mServer listening on http://127.0.0.1:8000\033[39m\n";
27 | @printf "Quit the server with CTRL-C.\n"
28 | @printf "Run \033[32mcomposer require symfony/web-server-bundle\033[39m for a better web server\n"
29 | php -S 127.0.0.1:8000 -t web
30 |
31 | serve:
32 | @${MAKE} serve_as_sf
33 | .PHONY: sf_console serve serve_as_sf serve_as_php
34 | ###< symfony/framework-bundle ###
35 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
17 | pkg/quartz/Tests
18 |
19 |
20 | pkg/bridge/Tests
21 |
22 |
23 | pkg/bundle/Tests
24 |
25 |
26 | pkg/app/tests
27 |
28 |
29 |
30 |
31 |
32 | .
33 |
34 | ./vendor
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/bin/subtree-split:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 | set -x
5 |
6 | CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD`
7 |
8 | function split()
9 | {
10 | # split_new_repo $1 $2
11 |
12 |
13 | SHA1=`./bin/splitsh-lite --prefix=$1`
14 | git push $2 "$SHA1:$CURRENT_BRANCH"
15 | }
16 |
17 | function split_new_repo()
18 | {
19 | TMP_DIR="/tmp/quartz-repo"
20 | REMOTE_URL=`git remote get-url $2`
21 |
22 | rm -rf $TMP_DIR;
23 | mkdir $TMP_DIR;
24 |
25 | (
26 | cd $TMP_DIR;
27 | git clone $REMOTE_URL .;
28 | git checkout -b master;
29 | touch foo;
30 | git add foo;
31 | git commit -m "foo";
32 | git push origin master;
33 | );
34 |
35 | SHA1=`./bin/splitsh-lite --prefix=$1`
36 | git fetch $2
37 | git push $2 "$SHA1:$CURRENT_BRANCH" -f
38 | }
39 |
40 |
41 | function remote()
42 | {
43 | git remote add $1 $2 || true
44 | }
45 |
46 | remote quartz git@github.com:php-quartz/quartz.git
47 | remote bridge git@github.com:php-quartz/bridge.git
48 | remote bundle git@github.com:php-quartz/bundle.git
49 | remote app git@github.com:php-quartz/app.git
50 |
51 | split 'pkg/quartz' quartz
52 | split 'pkg/bridge' bridge
53 | split 'pkg/bundle' bundle
54 | split 'pkg/app' app
55 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | git:
2 | depth: 10
3 |
4 | language: php
5 |
6 | env:
7 | DOCKER_VERSION: '17.05.0~ce-0~ubuntu-trusty'
8 | DOCKER_COMPOSE_VERSION: '1.13.0'
9 |
10 | matrix:
11 | include:
12 | - php: 7.2
13 | sudo: required
14 | services: docker
15 |
16 | cache:
17 | directories:
18 | - $HOME/.composer/cache
19 |
20 | install:
21 | - sudo apt-get update
22 | # list docker-engine versions
23 | - apt-cache madison docker-ce
24 | # upgrade docker-engine to specific version
25 | - sudo apt-get -o Dpkg::Options::="--force-confnew" install -y docker-ce=${DOCKER_VERSION}
26 |
27 | # reinstall docker-compose at specific version
28 | - sudo rm -f /usr/local/bin/docker-compose
29 | - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
30 | - chmod +x docker-compose
31 | - sudo mv docker-compose /usr/local/bin
32 |
33 | - docker --version
34 | - docker-compose --version
35 |
36 | - rm $HOME/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini;
37 | - echo "memory_limit=2048M" >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini
38 | - composer install --ignore-platform-reqs
39 | - bin/dev -b
40 |
41 | script:
42 | - bin/dev -t
43 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/ObjectAlreadyExistsException.php:
--------------------------------------------------------------------------------
1 | {@link JobDetail},{@link Trigger}
7 | * or {@link Calendar}) in a {@link Scheduler}
8 | * failed, because one with the same name & group already exists.
9 | */
10 | class ObjectAlreadyExistsException extends JobPersistenceException
11 | {
12 | /**
13 | * @param object $object
14 | *
15 | * @return ObjectAlreadyExistsException
16 | */
17 | public static function create($object)
18 | {
19 | if ($object instanceof JobDetail) {
20 | $message = sprintf(
21 | 'Unable to store Job : "%s", because one already exists with this identification.', $object->getKey()
22 | );
23 | } elseif ($object instanceof Trigger) {
24 | $message = sprintf(
25 | 'Unable to store Trigger with name: "%s" and group: "%s", because one already exists with this identification.'
26 | , $object->getKey()->getName(), $object->getKey()->getGroup()
27 | );
28 | } else {
29 | $message = (string) $object;
30 | }
31 |
32 | return new static($message);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-quartz/quartz-dev",
3 | "type": "project",
4 | "minimum-stability": "dev",
5 | "description": "PHP Job Time Scheduler Project",
6 | "keywords": ["job", "time", "task", "time scheduler", "quartz", "chrono"],
7 | "license": "MIT",
8 | "config": {
9 | "bin-dir": "bin",
10 | "platform": {
11 | "php": "7.2",
12 | "ext-mongodb": "1.4"
13 | },
14 | "preferred-install": {
15 | "*": "dist"
16 | },
17 | "sort-packages": true
18 | },
19 | "require": {
20 | "php": "^7.2",
21 | "php-quartz/quartz": "*@dev",
22 | "php-quartz/bridge": "*@dev",
23 | "php-quartz/bundle": "*@dev",
24 | "php-quartz/app": "*@dev",
25 | "symfony/dotenv": "^3.3|^4",
26 | "phpunit/phpunit": "^5.5",
27 | "symfony/browser-kit": "^3.4|^4"
28 | },
29 | "repositories": [
30 | {
31 | "type": "path",
32 | "url": "pkg/quartz"
33 | },
34 | {
35 | "type": "path",
36 | "url": "pkg/bridge"
37 | },
38 | {
39 | "type": "path",
40 | "url": "pkg/bundle"
41 | },
42 | {
43 | "type": "path",
44 | "url": "pkg/app"
45 | }
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Functional/App/AppKernel.php:
--------------------------------------------------------------------------------
1 | load(__DIR__.'/config/config.yml');
46 | }
47 |
48 | protected function getContainerClass()
49 | {
50 | return parent::getContainerClass().'BundleDefault';
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/JobFactory.php:
--------------------------------------------------------------------------------
1 |
6 | * A JobFactory is responsible for producing instances of Job
7 | * classes.
8 | *
9 | *
10 | *
11 | * This interface may be of use to those wishing to have their application
12 | * produce Job instances via some special mechanism, such as to
13 | * give the opportunity for dependency injection.
14 | *
15 | */
16 | interface JobFactory
17 | {
18 |
19 | /**
20 | * Called by the scheduler at the time of the trigger firing, in order to
21 | * produce a Job instance on which to call execute.
22 | *
23 | *
24 | * It should be extremely rare for this method to throw an exception -
25 | * basically only the case where there is no way at all to instantiate
26 | * and prepare the Job for execution. When the exception is thrown, the
27 | * Scheduler will move all triggers associated with the Job into the
28 | * Trigger.STATE_ERROR state, which will require human
29 | * intervention (e.g. an application restart after fixing whatever
30 | * configuration problem led to the issue wih instantiating the Job.
31 | *
32 | *
33 | * @param JobDetail $jobDetail
34 | *
35 | * @return Job
36 | */
37 | public function newJob(JobDetail $jobDetail);
38 | }
39 |
--------------------------------------------------------------------------------
/bin/release:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -e
4 |
5 | if (( "$#" != 1 ))
6 | then
7 | echo "Tag has to be provided"
8 | exit 1
9 | fi
10 |
11 | ./bin/dev -c $1 && git add CHANGELOG.md && git commit -m "Release $1" -S && git push origin master
12 |
13 | ./bin/subtree-split
14 |
15 | CURRENT_BRANCH=`git rev-parse --abbrev-ref HEAD`
16 |
17 | for REMOTE in origin quartz bridge bundle app
18 | do
19 | echo ""
20 | echo ""
21 | echo "Releasing $REMOTE";
22 |
23 | TMP_DIR="/tmp/quartz-repo"
24 | REMOTE_URL=`git remote get-url $REMOTE`
25 |
26 | rm -rf $TMP_DIR;
27 | mkdir $TMP_DIR;
28 |
29 | (
30 | cd $TMP_DIR;
31 | git clone $REMOTE_URL . --depth=200
32 | git checkout $CURRENT_BRANCH;
33 | # gsort comes with coreutils packages. brew install coreutils
34 | LAST_RELEASE=$(git tag -l [0-9].* | gsort -V | tail -n1 )
35 |
36 | echo "Last release $LAST_RELEASE";
37 |
38 | CHANGES_SINCE_LAST_RELEASE=$(git log "$LAST_RELEASE"...master)
39 | CHANGES_SINCE_LAST_RELEASE="$CHANGES_SINCE_LAST_RELEASE" | xargs echo -n
40 | if [[ ! -z "$CHANGES_SINCE_LAST_RELEASE" ]]; then
41 | echo "There are changes since last release. Releasing $1";
42 |
43 | git tag $1 -s -m "Release $1"
44 | git push origin --tags
45 | else
46 | echo "No change since last release.";
47 | fi
48 | )
49 | done
50 |
--------------------------------------------------------------------------------
/pkg/quartz/examples/cron-trigger.php:
--------------------------------------------------------------------------------
1 | sprintf('mongodb://%s:%s', getenv('MONGODB_HOST'), getenv('MONGODB_PORT')),
20 | 'dbName' => getenv('MONGODB_DB')
21 | ];
22 |
23 | class MyJob implements Job
24 | {
25 | public function execute(JobExecutionContext $context)
26 | {
27 | echo sprintf('Now: %s | Scheduled: %s'.PHP_EOL, date('H:i:s'), $context->getTrigger()->getScheduledFireTime()->format('H:i:s'));
28 | }
29 | }
30 |
31 | $job = JobBuilder::newJob(MyJob::class)->build();
32 |
33 | $trigger = TriggerBuilder::newTrigger()
34 | ->forJobDetail($job)
35 | ->endAt(new \DateTime('+1 minute'))
36 | ->withSchedule(CronScheduleBuilder::cronSchedule('*/5 * * * * *'))
37 | ->build();
38 |
39 | $store = new YadmStore(new SimpleStoreResource($config));
40 | $store->clearAllSchedulingData();
41 |
42 | $scheduler = new StdScheduler($store, new StdJobRunShellFactory(new StdJobRunShell()), new SimpleJobFactory(), new EventDispatcher());
43 | $scheduler->scheduleJob($job, $trigger);
44 |
--------------------------------------------------------------------------------
/pkg/quartz/examples/simple-trigger.php:
--------------------------------------------------------------------------------
1 | sprintf('mongodb://%s:%s', getenv('MONGODB_HOST'), getenv('MONGODB_PORT')),
20 | 'dbName' => getenv('MONGODB_DB')
21 | ];
22 |
23 | class MyJob implements Job
24 | {
25 | public function execute(JobExecutionContext $context)
26 | {
27 | echo sprintf('Now: %s | Scheduled: %s'.PHP_EOL, date('H:i:s'), $context->getTrigger()->getScheduledFireTime()->format('H:i:s'));
28 | }
29 | }
30 |
31 | $job = JobBuilder::newJob(MyJob::class)->build();
32 |
33 | $trigger = TriggerBuilder::newTrigger()
34 | ->forJobDetail($job)
35 | ->endAt(new \DateTime('+1 minutes'))
36 | ->withSchedule(SimpleScheduleBuilder::repeatSecondlyForever(5))
37 | ->build();
38 |
39 | $store = new YadmStore(new SimpleStoreResource($config));
40 | $store->clearAllSchedulingData();
41 |
42 | $scheduler = new StdScheduler($store, new StdJobRunShellFactory(new StdJobRunShell()), new SimpleJobFactory(), new EventDispatcher());
43 | $scheduler->scheduleJob($job, $trigger);
44 |
--------------------------------------------------------------------------------
/pkg/quartz/examples/calendar-interval-trigger.php:
--------------------------------------------------------------------------------
1 | sprintf('mongodb://%s:%s', getenv('MONGODB_HOST'), getenv('MONGODB_PORT')),
20 | 'dbName' => getenv('MONGODB_DB')
21 | ];
22 |
23 | class MyJob implements Job
24 | {
25 | public function execute(JobExecutionContext $context)
26 | {
27 | echo sprintf('Now: %s | Scheduled: %s'.PHP_EOL, date('H:i:s'), $context->getTrigger()->getScheduledFireTime()->format('H:i:s'));
28 | }
29 | }
30 |
31 | $job = JobBuilder::newJob(MyJob::class)->build();
32 |
33 | $trigger = TriggerBuilder::newTrigger()
34 | ->forJobDetail($job)
35 | ->endAt(new \DateTime('+2 minutes'))
36 | ->withSchedule(CalendarIntervalScheduleBuilder::calendarIntervalSchedule()->withIntervalInSeconds(10))
37 | ->build();
38 |
39 | $store = new YadmStore(new SimpleStoreResource($config));
40 | $store->clearAllSchedulingData();
41 |
42 | $scheduler = new StdScheduler($store, new StdJobRunShellFactory(new StdJobRunShell()), new SimpleJobFactory(), new EventDispatcher());
43 | $scheduler->scheduleJob($trigger, $job);
44 |
--------------------------------------------------------------------------------
/pkg/bridge/Tests/Enqueue/EnqueueRemoteTransportTest.php:
--------------------------------------------------------------------------------
1 | createMock(ProducerInterface::class);
17 |
18 | $this->assertInstanceOf(RemoteTransport::class, new EnqueueRemoteTransport($producer));
19 | }
20 |
21 | public function testShouldSendCommandAndReturnResponse()
22 | {
23 | $message = new NullMessage();
24 | $message->setBody(JSON::encode(['key2' => 'value2']));
25 |
26 | $promise = $this->createMock(Promise::class);
27 | $promise
28 | ->expects($this->once())
29 | ->method('receive')
30 | ->willReturn($message)
31 | ;
32 |
33 | $producer = $this->createMock(ProducerInterface::class);
34 | $producer
35 | ->expects($this->once())
36 | ->method('sendCommand')
37 | ->with(EnqueueRemoteTransport::COMMAND, ['key' => 'value'], $this->isTrue())
38 | ->willReturn($promise)
39 | ;
40 |
41 | $transport = new EnqueueRemoteTransport($producer);
42 | $response = $transport->request(['key' => 'value']);
43 |
44 | $this->assertSame(['key2' => 'value2'], $response);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Core/KeyTest.php:
--------------------------------------------------------------------------------
1 | assertSame('name', $key->getName());
14 | $this->assertSame('DEFAULT', $key->getGroup());
15 | }
16 |
17 | public function testCouldBeConstructedWithNameAndGroup()
18 | {
19 | $key = new Key('name', 'group');
20 |
21 | $this->assertSame('name', $key->getName());
22 | $this->assertSame('group', $key->getGroup());
23 | }
24 |
25 | public function testShouldThrowExceptionIfNameIsEmpty()
26 | {
27 | $this->expectException(\InvalidArgumentException::class);
28 | $this->expectExceptionMessage('Name cannot be empty');
29 |
30 | new Key('');
31 | }
32 |
33 | public function testCouldCompareKeys()
34 | {
35 | $this->assertTrue((new Key('name', 'group'))->equals(new Key('name', 'group')));
36 | $this->assertFalse((new Key('name1', 'group'))->equals(new Key('name', 'group')));
37 | }
38 |
39 | public function testCouldCastObjectToString()
40 | {
41 | $this->assertSame('group.name', (string) new Key('name', 'group'));
42 | }
43 |
44 | public function testShouldGenerateUniqueNames()
45 | {
46 | $name1 = Key::createUniqueName();
47 | $name2 = Key::createUniqueName();
48 |
49 | $this->assertNotEmpty($name1);
50 | $this->assertNotEmpty($name2);
51 | $this->assertNotEquals($name1, $name2);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/pkg/bridge/DI/QuartzJobCompilerPass.php:
--------------------------------------------------------------------------------
1 | alias = $alias;
22 | }
23 |
24 | /**
25 | * {@inheritdoc}
26 | */
27 | public function process(ContainerBuilder $container)
28 | {
29 | $jobFactory = $container->getDefinition($this->format('job_factory'));
30 | $tags = $container->findTaggedServiceIds($this->format('job'));
31 |
32 | $jobs = [];
33 | foreach ($tags as $serviceId => $tagAttributes) {
34 | foreach ($tagAttributes as $tagAttribute) {
35 | if (false == empty($tagAttribute['alias'])) {
36 | $jobName = $tagAttribute['alias'];
37 | } else {
38 | $jobName = $container->getDefinition($serviceId)->getClass();
39 | }
40 |
41 | $jobs[$jobName] = new Reference($serviceId);
42 | }
43 | }
44 |
45 | $jobFactory->replaceArgument(0, $jobs);
46 | }
47 |
48 | /**
49 | * @param string $service
50 | *
51 | * @return string
52 | */
53 | private function format($service)
54 | {
55 | return $this->alias.'.'.$service;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/quartz/examples/daily-interval-trigger.php:
--------------------------------------------------------------------------------
1 | sprintf('mongodb://%s:%s', getenv('MONGODB_HOST'), getenv('MONGODB_PORT')),
21 | 'dbName' => getenv('MONGODB_DB')
22 | ];
23 |
24 | class MyJob implements Job
25 | {
26 | public function execute(JobExecutionContext $context)
27 | {
28 | echo sprintf('Now: %s | Scheduled: %s'.PHP_EOL, date('H:i:s'), $context->getTrigger()->getScheduledFireTime()->format('H:i:s'));
29 | }
30 | }
31 |
32 | $job = JobBuilder::newJob(MyJob::class)->build();
33 |
34 | $trigger = TriggerBuilder::newTrigger()
35 | ->forJobDetail($job)
36 | ->endAt(new \DateTime('+1 day'))
37 | ->withSchedule(DailyTimeIntervalScheduleBuilder::dailyTimeIntervalSchedule()->withIntervalInSeconds(10))
38 | ->build();
39 |
40 | $store = new YadmStore(new SimpleStoreResource($config));
41 | $store->clearAllSchedulingData();
42 |
43 | $scheduler = new StdScheduler($store, new StdJobRunShellFactory(new StdJobRunShell()), new SimpleJobFactory(), new EventDispatcher());
44 | $scheduler->scheduleJob($trigger, $job);
45 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/JobDetail/JobDetailTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(JobDetailInterface::class, new JobDetail());
14 | }
15 |
16 | public function testCouldGetSetKey()
17 | {
18 | $job = new JobDetail();
19 | $job->setKey($key = new Key('name', 'group'));
20 |
21 | $this->assertTrue($key->equals($job->getKey()));
22 | }
23 |
24 | public function testCouldGetSetDescription()
25 | {
26 | $job = new JobDetail();
27 | $job->setDescription('the description');
28 |
29 | $this->assertSame('the description', $job->getDescription());
30 | }
31 |
32 | public function testCouldGetSetDurable()
33 | {
34 | $job = new JobDetail();
35 |
36 | $job->setDurable(true);
37 | $this->assertTrue($job->isDurable());
38 |
39 | $job->setDurable(false);
40 | $this->assertFalse($job->isDurable());
41 | }
42 |
43 | public function testCouldGetSetJobClass()
44 | {
45 | $job = new JobDetail();
46 | $job->setJobClass('the job class');
47 |
48 | $this->assertSame('the job class', $job->getJobClass());
49 | }
50 |
51 | public function testCouldGetSetJobDataMap()
52 | {
53 | $job = new JobDetail();
54 | $job->setJobDataMap(['key' => 'value']);
55 |
56 | $this->assertSame(['key' => 'value'], $job->getJobDataMap());
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/quartz/Events/ErrorEvent.php:
--------------------------------------------------------------------------------
1 | message = $message;
36 | $this->errorCount = $errorCount;
37 | $this->exception = $exception;
38 | $this->interrupted = false;
39 | }
40 |
41 | /**
42 | * @return string
43 | */
44 | public function getMessage()
45 | {
46 | return $this->message;
47 | }
48 |
49 | /**
50 | * @return \Exception
51 | */
52 | public function getException()
53 | {
54 | return $this->exception;
55 | }
56 |
57 | /**
58 | * @return int
59 | */
60 | public function getErrorCount()
61 | {
62 | return $this->errorCount;
63 | }
64 |
65 | /**
66 | * @return boolean
67 | */
68 | public function isInterrupted()
69 | {
70 | return $this->interrupted;
71 | }
72 |
73 | /**
74 | * @param boolean $interrupted
75 | */
76 | public function setInterrupted($interrupted)
77 | {
78 | $this->interrupted = (bool) $interrupted;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/Calendar.php:
--------------------------------------------------------------------------------
1 |
8 | * Set a new base calendar or remove the existing one.
9 | *
10 | *
11 | * @param Calendar $baseCalendar
12 | */
13 | public function setBaseCalendar(Calendar $baseCalendar);
14 |
15 | /**
16 | *
17 | * Get the base calendar. Will be null, if not set.
18 | *
19 | *
20 | * @return Calendar
21 | */
22 | public function getBaseCalendar();
23 |
24 | /**
25 | *
26 | * Determine whether the given time (in milliseconds) is 'included' by the
27 | * Calendar.
28 | *
29 | *
30 | * @param int $timeStamp
31 | *
32 | * @return bool
33 | */
34 | public function isTimeIncluded($timeStamp);
35 |
36 | /**
37 | *
38 | * Determine the next time (in milliseconds) that is 'included' by the
39 | * Calendar after the given time.
40 | *
41 | *
42 | * @param int $timeStamp
43 | *
44 | * @return int timestamp
45 | */
46 | public function getNextIncludedTime($timeStamp);
47 |
48 | /**
49 | *
50 | * Return the description given to the Calendar instance by
51 | * its creator (if any).
52 | *
53 | *
54 | * @return string|null if no description was set.
55 | */
56 | public function getDescription();
57 |
58 | /**
59 | *
60 | * Set a description for the Calendar instance - may be
61 | * useful for remembering/displaying the purpose of the calendar, though
62 | * the description has no meaning to Quartz.
63 | *
64 | *
65 | * @param string $description
66 | */
67 | public function setDescription($description);
68 | }
69 |
--------------------------------------------------------------------------------
/pkg/app/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "php-quartz/app",
3 | "type": "project",
4 | "license": "MIT",
5 | "description": "Job Time Scheduler App",
6 | "keywords": ["job", "time", "task", "time scheduler", "quartz", "chrono"],
7 | "require": {
8 | "php": "^7.2",
9 | "symfony/console": "^3|^4",
10 | "symfony/flex": "^1.0",
11 | "symfony/framework-bundle": "^3|^4",
12 | "symfony/yaml": "^3|^4",
13 | "makasim/yadm": "^0.5.7",
14 | "makasim/values": "^0.5.2",
15 | "enqueue/amqp-lib": "^0.9",
16 | "php-quartz/bundle": "^0.2"
17 | },
18 | "require-dev": {
19 | "symfony/dotenv": "^3|^4",
20 | "phpunit/phpunit": "^5.5",
21 | "enqueue/null": "^0.9"
22 | },
23 | "config": {
24 | "preferred-install": {
25 | "*": "dist"
26 | },
27 | "sort-packages": true
28 | },
29 | "autoload": {
30 | "psr-4": {
31 | "Quartz\\App\\": "src/"
32 | }
33 | },
34 | "autoload-dev": {
35 | "psr-4": {
36 | "Quartz\\App\\Tests\\": "tests/"
37 | }
38 | },
39 | "scripts": {
40 | "auto-scripts": {
41 | "make cache-warmup": "script"
42 | },
43 | "post-install-cmd": [
44 | "@auto-scripts"
45 | ],
46 | "post-update-cmd": [
47 | "@auto-scripts"
48 | ]
49 | },
50 | "conflict": {
51 | "symfony/symfony": "*",
52 | "symfony/twig-bundle": "<3.3",
53 | "symfony/debug": "<3.3"
54 | },
55 | "extra": {
56 | "symfony": {
57 | "id": "01BJQZD7YBT9JB0NGH2G58754G",
58 | "allow-contrib": false
59 | },
60 | "branch-alias": {
61 | "dev-master": "0.2.x-dev"
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/bridge/DI/RemoteSchedulerExtension.php:
--------------------------------------------------------------------------------
1 | alias = $alias;
25 | }
26 |
27 | /**
28 | * {@inheritdoc}
29 | */
30 | public function getAlias()
31 | {
32 | return $this->alias;
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | public function load(array $configs, ContainerBuilder $container)
39 | {
40 | $container->register($this->format('remote.transport'), EnqueueRemoteTransport::class)
41 | ->setArguments([new Reference(ProducerInterface::class)])
42 | ;
43 |
44 | $container->register($this->format('remote.rpc_protocol'), RpcProtocol::class)
45 | ->setPublic(false)
46 | ;
47 |
48 | $container->register($this->format('remote.scheduler'), RemoteScheduler::class)
49 | ->setArguments([
50 | new Reference($this->format('remote.transport')),
51 | new Reference($this->format('remote.rpc_protocol')),
52 | ])
53 | ;
54 | }
55 |
56 | /**
57 | * @param string $service
58 | *
59 | * @return string
60 | */
61 | private function format($service)
62 | {
63 | return $this->alias.'.'.$service;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/pkg/app/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3'
2 | services:
3 | scheduler:
4 | image: 'formapro/nginx-php-fpm:latest-all-exts'
5 | working_dir: '/app'
6 | entrypoint: 'bin/console quartz:scheduler -vvv'
7 | restart: 'on-failure'
8 | depends_on:
9 | - 'mongo'
10 | - 'rabbitmq'
11 | volumes:
12 | - './:/app:cached'
13 | # - './docker/xdebug.ini:/etc/php/7.0/mods-available/xdebug.ini'
14 | environment:
15 | PHP_IDE_CONFIG: 'serverName=quartz.dev'
16 | XDEBUG_CONFIG: 'idekey=PHPSTORM'
17 | MONGODB_HOST: 'mongo'
18 | MONGODB_PORT: '27017'
19 | MONGODB_DB: 'quartz'
20 | RABBITMQ_HOST: 'rabbitmq'
21 | RABBITMQ_PORT: '5672'
22 | RABBITMQ_USER: 'guest'
23 | RABBITMQ_PASS: 'guest'
24 | RABBITMQ_VHOST: 'quartz'
25 |
26 | worker:
27 | image: 'formapro/nginx-php-fpm:latest-all-exts'
28 | working_dir: '/app'
29 | entrypoint: 'bin/console enqueue:consume -vvv'
30 | restart: 'on-failure'
31 | depends_on:
32 | - 'mongo'
33 | - 'rabbitmq'
34 | volumes:
35 | - './:/app:cached'
36 | # - './docker/xdebug.ini:/etc/php/7.0/mods-available/xdebug.ini'
37 | environment:
38 | PHP_IDE_CONFIG: 'serverName=quartz.dev'
39 | XDEBUG_CONFIG: 'idekey=PHPSTORM'
40 | MONGODB_HOST: 'mongo'
41 | MONGODB_PORT: '27017'
42 | MONGODB_DB: 'quartz'
43 | RABBITMQ_HOST: 'rabbitmq'
44 | RABBITMQ_PORT: '5672'
45 | RABBITMQ_USER: 'guest'
46 | RABBITMQ_PASS: 'guest'
47 | RABBITMQ_VHOST: 'quartz'
48 |
49 | mongo:
50 | image: 'mongo:3'
51 | restart: 'no'
52 | ports:
53 | - '27017:27017'
54 |
55 | rabbitmq:
56 | image: 'enqueue/rabbitmq:latest'
57 | restart: 'no'
58 | ports:
59 | - "15672:15672"
60 | environment:
61 | RABBITMQ_DEFAULT_USER: 'guest'
62 | RABBITMQ_DEFAULT_PASS: 'guest'
63 | RABBITMQ_DEFAULT_VHOST: 'quartz'
64 |
--------------------------------------------------------------------------------
/pkg/bridge/SignalSubscriber.php:
--------------------------------------------------------------------------------
1 | interruptConsumption = false;
19 | }
20 |
21 | public function registerHandleSignalCallback()
22 | {
23 | if (false == extension_loaded('pcntl')) {
24 | throw new SchedulerException('The pcntl extension is required in order to catch signals.');
25 | }
26 |
27 | pcntl_async_signals(true);
28 |
29 | pcntl_signal(SIGTERM, [$this, 'handleSignal']);
30 | pcntl_signal(SIGQUIT, [$this, 'handleSignal']);
31 | pcntl_signal(SIGINT, [$this, 'handleSignal']);
32 | }
33 |
34 | public function handleSignalDispatch(TickEvent $event)
35 | {
36 | if ($this->interruptConsumption) {
37 | $event->setInterrupted(true);
38 | }
39 | }
40 |
41 | /**
42 | * @param int $signal
43 | */
44 | public function handleSignal($signal)
45 | {
46 | switch ($signal) {
47 | case SIGTERM: // 15 : supervisor default stop
48 | case SIGQUIT: // 3 : kill -s QUIT
49 | case SIGINT: // 2 : ctrl+c
50 | $this->interruptConsumption = true;
51 | break;
52 | default:
53 | break;
54 | }
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public static function getSubscribedEvents()
61 | {
62 | return [
63 | Event::SCHEDULER_STARTING => 'registerHandleSignalCallback',
64 | Event::SCHEDULER_TICK => 'handleSignalDispatch',
65 | ];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/pkg/bundle/Command/ManagementCommand.php:
--------------------------------------------------------------------------------
1 | scheduler = $scheduler;
26 | }
27 |
28 | /**
29 | * {@inheritdoc}
30 | */
31 | protected function configure()
32 | {
33 | $this
34 | ->addOption('clear-all', null, InputOption::VALUE_NONE, 'Clears (deletes!) all scheduling data - all Jobs, Triggers, Calendars.')
35 | ->addOption('create-indexes', null, InputOption::VALUE_NONE, 'Creates all required storage indexes')
36 | ;
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | protected function execute(InputInterface $input, OutputInterface $output)
43 | {
44 | if ($input->getOption('clear-all')) {
45 | $helper = $this->getHelper('question');
46 | $question = new ConfirmationQuestion('You are just about to delete all storage data. Are you sure? ', false, '/^(y|j)/i');
47 |
48 | if ($helper->ask($input, $output, $question)) {
49 | $this->scheduler->clear();
50 | }
51 | }
52 |
53 | if ($input->getOption('create-indexes')) {
54 | $output->writeln('Creating storage indexes');
55 | $this->scheduler->getStore()->createIndexes(); // TODO: is not part of interface :(
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/pkg/bridge/Tests/Swoole/CheckMasterProcessSubscriberTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(EventSubscriberInterface::class, new CheckMasterProcessSubscriber());
16 | }
17 |
18 | public function testShouldReturnExpectedSubscribedEvents()
19 | {
20 | $expectedEvents = [
21 | Event::SCHEDULER_TICK => 'checkMasterProcessor',
22 | ];
23 |
24 | $this->assertSame($expectedEvents, CheckMasterProcessSubscriber::getSubscribedEvents());
25 | }
26 |
27 | public function testShouldThrowExceptionIfMasterProcessPidEnvIsNotSet()
28 | {
29 | $this->expectException(SchedulerException::class);
30 | $this->expectExceptionMessage('The extension rely on MASTER_PROCESS_PID env var set but it is not set.');
31 |
32 | $s = new CheckMasterProcessSubscriber();
33 | $s->checkMasterProcessor(new TickEvent());
34 | }
35 |
36 | public function testShouldSetInterruptedIfMasterProcessIsNotRunning()
37 | {
38 | putenv('MASTER_PROCESS_PID=-12345');
39 |
40 | $s = new CheckMasterProcessSubscriber();
41 | $s->checkMasterProcessor($event = new TickEvent());
42 |
43 | $this->assertTrue($event->isInterrupted());
44 | }
45 |
46 | public function testShouldNotSetInterruptedIfMasterProcessIsRunning()
47 | {
48 | putenv('MASTER_PROCESS_PID='.posix_getpid());
49 |
50 | $s = new CheckMasterProcessSubscriber();
51 | $s->checkMasterProcessor($event = new TickEvent());
52 |
53 | $this->assertFalse($event->isInterrupted());
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/bundle/DependencyInjection/QuartzExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration(new Configuration(), $configs);
21 |
22 | if (is_array($config['scheduler'])) {
23 | $schedulerExt = new QuartzSchedulerExtension($this->getAlias());
24 | $schedulerExt->load([$config['scheduler']], $container);
25 |
26 | $container->setAlias($this->format('event_dispatcher'), 'event_dispatcher');
27 |
28 | $container->register($this->format('cli.scheduler'), SchedulerCommand::class)
29 | ->setArguments([new Reference($this->format('scheduler'))])
30 | ->addTag('console.command')
31 | ;
32 |
33 | $container->register($this->format('cli.management'), ManagementCommand::class)
34 | ->setArguments([new Reference($this->format('scheduler'))])
35 | ->addTag('console.command')
36 | ;
37 | }
38 |
39 | if (is_array($config['remote_scheduler'])) {
40 | $remoteExt = new RemoteSchedulerExtension($this->getAlias());
41 | $remoteExt->load([$config['remote_scheduler']], $container);
42 | }
43 | }
44 |
45 | /**
46 | * @param string $service
47 | *
48 | * @return string
49 | */
50 | private function format($service)
51 | {
52 | return $this->getAlias().'.'.$service;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Calendar/CronCalendarTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Calendar::class, new CronCalendar());
13 | }
14 |
15 | public function testShouldReturnTrueFalseIfTimeIncludedOrNot()
16 | {
17 | $cal = new CronCalendar();
18 | // exclude all but business hours (8AM - 5PM) every
19 | $cal->setCronExpression('* * 0-7,18-23 ? * *');
20 |
21 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-12 00:00:00')));
22 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-12 07:59:59')));
23 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-12 08:00:00')));
24 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-12 17:59:59')));
25 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-13 18:00:00')));
26 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-13 23:59:59')));
27 | }
28 |
29 | public function testShouldReturnNextInvalidTimeAfter()
30 | {
31 | $cal = new CronCalendar();
32 | $cal->setCronExpression('0 */1 * * * *');
33 |
34 | $invalidTime = $cal->getNextInvalidTimeAfter(new \DateTime('2012-12-12 12:00:59'));
35 |
36 | $this->assertEquals(new \DateTime('2012-12-12 12:01:01'), $invalidTime);
37 | }
38 |
39 | public function testShouldReturnNextIncludedTime()
40 | {
41 | $cal = new CronCalendar();
42 | $cal->setCronExpression('0 */1 * * * *');
43 |
44 | $invalidTime = $cal->getNextIncludedTime(strtotime('2012-12-12 12:00:59'));
45 | $this->assertEquals(strtotime('2012-12-12 12:01:01'), $invalidTime);
46 |
47 | $invalidTime = $cal->getNextIncludedTime(strtotime('2012-12-12 12:01:10'));
48 | $this->assertEquals(strtotime('2012-12-12 12:01:11'), $invalidTime);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/pkg/bridge/Scheduler/JobRunShellProcessor.php:
--------------------------------------------------------------------------------
1 | store = $store;
33 | $this->runShell = $runShell;
34 | }
35 |
36 | public function process(Message $message, Context $context): Result
37 | {
38 | $data = JSON::decode($message->getBody());
39 |
40 | if (false == isset($data['fireInstanceId'])) {
41 | return Result::reject('fire instance id is empty');
42 | }
43 |
44 | if (false == $trigger = $this->store->retrieveFireTrigger($data['fireInstanceId'])) {
45 | return Result::reject(sprintf('There is not trigger with fire instance id: "%s"', $data['fireInstanceId']));
46 | }
47 |
48 | $this->runShell->execute($trigger);
49 |
50 | return Result::ack();
51 | }
52 |
53 | public static function getSubscribedCommand(): array
54 | {
55 | return [
56 | 'command' => EnqueueJobRunShell::COMMAND,
57 | 'queue' => EnqueueJobRunShell::COMMAND,
58 | 'prefix_queue' => false,
59 | 'exclusive' => true,
60 | ];
61 | }
62 |
63 | public static function getSubscribedQueues(): array
64 | {
65 | return [EnqueueJobRunShell::COMMAND];
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/pkg/bridge/Enqueue/EnqueueRemoteTransportProcessor.php:
--------------------------------------------------------------------------------
1 | scheduler = $scheduler;
33 | $this->rpcProtocol = $rpcProtocol;
34 | }
35 |
36 | public function process(Message $message, Context $context): Result
37 | {
38 | try {
39 | $request = $this->rpcProtocol->decodeRequest(JSON::decode($message->getBody()));
40 |
41 | $result = call_user_func_array([$this->scheduler, $request['method']], $request['args']);
42 | $result = $this->rpcProtocol->encodeValue($result);
43 | } catch (\Exception $e) {
44 | $result = $this->rpcProtocol->encodeValue($e);
45 | }
46 |
47 | return Result::reply($context->createMessage(JSON::encode($result)));
48 | }
49 |
50 | public static function getSubscribedCommand(): array
51 | {
52 | return [
53 | 'command' => EnqueueRemoteTransport::COMMAND,
54 | 'queue' => EnqueueRemoteTransport::COMMAND,
55 | 'prefix_queue' => false,
56 | 'exclusive' => true,
57 | ];
58 | }
59 |
60 | public static function getSubscribedQueues(): array
61 | {
62 | return [EnqueueRemoteTransport::COMMAND];
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/pkg/quartz/ModelClassFactory.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | final class Kernel extends BaseKernel
15 | {
16 | use MicroKernelTrait;
17 |
18 | private const CONFIG_EXTS = '.{php,xml,yaml,yml}';
19 |
20 | public function getCacheDir(): string
21 | {
22 | return dirname(__DIR__).'/var/cache/'.$this->environment;
23 | }
24 |
25 | public function getLogDir(): string
26 | {
27 | return dirname(__DIR__).'/var/logs';
28 | }
29 |
30 | public function registerBundles(): iterable
31 | {
32 | $contents = require dirname(__DIR__).'/etc/bundles.php';
33 | foreach ($contents as $class => $envs) {
34 | if (isset($envs['all']) || isset($envs[$this->environment])) {
35 | yield new $class();
36 | }
37 | }
38 | }
39 |
40 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
41 | {
42 | $confDir = dirname(__DIR__).'/etc';
43 | $loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
44 | if (is_dir($confDir.'/packages/'.$this->environment)) {
45 | $loader->load($confDir.'/packages/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
46 | }
47 | $loader->load($confDir.'/container'.self::CONFIG_EXTS, 'glob');
48 | }
49 |
50 | protected function configureRoutes(RouteCollectionBuilder $routes): void
51 | {
52 | $confDir = dirname(__DIR__).'/etc';
53 | if (is_dir($confDir.'/routing/')) {
54 | $routes->import($confDir.'/routing/*'.self::CONFIG_EXTS, '/', 'glob');
55 | }
56 | if (is_dir($confDir.'/routing/'.$this->environment)) {
57 | $routes->import($confDir.'/routing/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
58 | }
59 | $routes->import($confDir.'/routing'.self::CONFIG_EXTS, '/', 'glob');
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/DependencyInjection/QuartzExtensionTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Extension::class, new QuartzExtension());
13 | }
14 |
15 | public function testCouldBeConstructedWithoutAnyArguments()
16 | {
17 | new QuartzExtension();
18 | }
19 |
20 | public function testShouldNotLoadSchedulerServicesIfDisabled()
21 | {
22 | $container = new ContainerBuilder();
23 |
24 | $ext = new QuartzExtension();
25 | $ext->load([[
26 | 'scheduler' => false,
27 | ]], $container);
28 |
29 | $this->assertNotContains('quartz.scheduler', $container->getServiceIds());
30 | }
31 |
32 | public function testShouldLoadSchedulerServicesIfEnabled()
33 | {
34 | $container = new ContainerBuilder();
35 |
36 | $ext = new QuartzExtension();
37 | $ext->load([[
38 | 'scheduler' => [
39 | 'yadm_simple_store' => [],
40 | ],
41 | ]], $container);
42 |
43 | $this->assertContains('quartz.scheduler', $container->getServiceIds());
44 | }
45 |
46 | public function testShouldNotLoadRemoteSchedulerServicesIfDisabled()
47 | {
48 | $container = new ContainerBuilder();
49 |
50 | $ext = new QuartzExtension();
51 | $ext->load([[
52 | 'remote_scheduler' => false,
53 | ]], $container);
54 |
55 | $this->assertNotContains('test_quartz.remote.scheduler', $container->getServiceIds());
56 | }
57 |
58 | public function testShouldLoadRemoteSchedulerServicesIfEnabled()
59 | {
60 | $container = new ContainerBuilder();
61 |
62 | $ext = new QuartzExtension();
63 | $ext->load([[
64 | 'remote_scheduler' => null,
65 | ]], $container);
66 |
67 | $this->assertContains('quartz.remote.scheduler', $container->getServiceIds());
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/pkg/bridge/DI/QuartzConfiguration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
14 |
15 | $rootNode->children()
16 | ->arrayNode('yadm_simple_store')->children()
17 | ->scalarNode('uri')->defaultValue('mongodb://localhost:27017')->end()
18 | ->variableNode('uriOptions')->defaultValue([])->end()
19 | ->variableNode('driverOptions')->defaultValue([])->end()
20 | ->scalarNode('sessionId')->defaultValue('quartz')->end()
21 | ->scalarNode('dbName')->defaultValue(null)->end()
22 | ->scalarNode('managementLockCol')->defaultValue('quartz_management_lock')->end()
23 | ->scalarNode('calendarCol')->defaultValue('quartz_calendar')->end()
24 | ->scalarNode('triggerCol')->defaultValue('quartz_trigger')->end()
25 | ->scalarNode('firedTriggerCol')->defaultValue('quartz_fired_trigger')->end()
26 | ->scalarNode('jobCol')->defaultValue('quartz_job')->end()
27 | ->scalarNode('pausedTriggerCol')->defaultValue('quartz_paused_trigger')->end()
28 | ->end()->end()
29 | ->arrayNode('yadm_bundle_store')->children()
30 | ->scalarNode('sessionId')->defaultValue('quartz')->end()
31 | ->scalarNode('managementLockCol')->defaultValue('quartz_management_lock')->end()
32 | ->scalarNode('calendarStorage')->defaultValue('quartz_calendar')->end()
33 | ->scalarNode('triggerStorage')->defaultValue('quartz_trigger')->end()
34 | ->scalarNode('firedTriggerStorage')->defaultValue('quartz_fired_trigger')->end()
35 | ->scalarNode('jobStorage')->defaultValue('quartz_job')->end()
36 | ->scalarNode('pausedTriggerStorage')->defaultValue('quartz_paused_trigger')->end()
37 | ->end()->end()
38 | ->integerNode('misfireThreshold')->min(10)->defaultValue(60)->end()
39 | ;
40 |
41 | return $tb;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/DependencyInjection/ConfigurationTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(ConfigurationInterface::class, new Configuration());
15 | }
16 |
17 | public function testByDefaultShouldEnableRemoteSchedulerAndDisableScheduler()
18 | {
19 | $configuration = new Configuration([]);
20 |
21 | $processor = new Processor();
22 | $config = $processor->processConfiguration($configuration, [[]]);
23 |
24 | $expectedConfig = [
25 | 'remote_scheduler' => [],
26 | 'scheduler' => false,
27 | ];
28 |
29 | $this->assertSame($expectedConfig, $config);
30 | }
31 |
32 | public function testShouldThreadNullAsEmptyArray()
33 | {
34 | $configuration = new Configuration([]);
35 |
36 | $processor = new Processor();
37 | $config = $processor->processConfiguration($configuration, [[
38 | 'remote_scheduler' => null,
39 | 'scheduler' => null,
40 | ]]);
41 |
42 | $expectedConfig = [
43 | 'remote_scheduler' => [],
44 | 'scheduler' => [],
45 | ];
46 |
47 | $this->assertSame($expectedConfig, $config);
48 | }
49 |
50 | public function testShouldPassAnyVariables()
51 | {
52 | $configuration = new Configuration([]);
53 |
54 | $processor = new Processor();
55 | $config = $processor->processConfiguration($configuration, [[
56 | 'remote_scheduler' => [
57 | 'key1' => 'value1',
58 | ],
59 | 'scheduler' => [
60 | 'key2' => 'value2',
61 | ],
62 | ]]);
63 |
64 | $expectedConfig = [
65 | 'remote_scheduler' => [
66 | 'key1' => 'value1',
67 | ],
68 | 'scheduler' => [
69 | 'key2' => 'value2',
70 | ],
71 | ];
72 |
73 | $this->assertSame($expectedConfig, $config);
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Calendar/HolidayCalendarTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Calendar::class, new HolidayCalendar());
13 | }
14 |
15 | public function testCouldAddExcludedDateAndSetTimeToMidnight()
16 | {
17 | $cal = new HolidayCalendar();
18 | $cal->addExcludedDate(new \DateTime('2012-12-12 12:12:12'));
19 |
20 | $excludedDates = $cal->getExcludedDates();
21 |
22 | $this->assertCount(1, $excludedDates);
23 | $this->assertEquals(new \DateTime('2012-12-12 00:00:00'), $excludedDates[0]);
24 | }
25 |
26 | public function testCouldRemoveExcludedDate()
27 | {
28 | $cal = new HolidayCalendar();
29 | $cal->addExcludedDate(new \DateTime('2012-12-12 12:12:12'));
30 | $cal->addExcludedDate(new \DateTime('2012-12-13 12:12:12'));
31 |
32 | $this->assertCount(2, $cal->getExcludedDates());
33 |
34 | $cal->removeExcludedDate(new \DateTime('2012-12-13 23:01:02'));
35 | $excludedDates = $cal->getExcludedDates();
36 |
37 | $this->assertCount(1, $cal->getExcludedDates());
38 | $this->assertEquals(new \DateTime('2012-12-12 00:00:00'), $excludedDates[0]);
39 | }
40 |
41 | public function testShouldReturnTrueFalseIfTimeIncludedOrNot()
42 | {
43 | $cal = new HolidayCalendar();
44 | $cal->addExcludedDate(new \DateTime('2012-12-12 12:12:12'));
45 |
46 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-11 13:12:12')));
47 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-12 13:12:12')));
48 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-13 13:12:12')));
49 | }
50 |
51 | public function testShouldReturnNextIncludedTime()
52 | {
53 | $cal = new HolidayCalendar();
54 | $cal->addExcludedDate(new \DateTime('2012-12-10 12:12:12'));
55 | $cal->addExcludedDate(new \DateTime('2012-12-11 12:12:12'));
56 | $cal->addExcludedDate(new \DateTime('2012-12-12 12:12:12'));
57 | $cal->addExcludedDate(new \DateTime('2012-12-13 12:12:12'));
58 |
59 | $nextIncludedTime = $cal->getNextIncludedTime(strtotime('2012-12-11 13:12:12'));
60 |
61 | $this->assertSame(strtotime('2012-12-14 00:00:00'), $nextIncludedTime);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/Key.php:
--------------------------------------------------------------------------------
1 | setInstance(self::INSTANCE);
27 |
28 | $this->setName($name);
29 | $this->setGroup(empty($group) ? self::DEFAULT_GROUP : $group);
30 | }
31 |
32 | /**
33 | * @param string $instance
34 | */
35 | protected function setInstance($instance)
36 | {
37 | $this->setValue('instance', $instance);
38 | }
39 |
40 | /**
41 | * @return string
42 | */
43 | public function getName()
44 | {
45 | return $this->getValue('name');
46 | }
47 |
48 | /**
49 | * @param string $name
50 | */
51 | private function setName($name)
52 | {
53 | $this->setValue('name', $name);
54 | }
55 |
56 | /**
57 | * @return string
58 | */
59 | public function getGroup()
60 | {
61 | return $this->getValue('group');
62 | }
63 |
64 | /**
65 | * @param string $group
66 | */
67 | private function setGroup($group)
68 | {
69 | $this->setValue('group', $group);
70 | }
71 |
72 | /**
73 | * @param string|null $group
74 | *
75 | * @return string
76 | */
77 | public static function createUniqueName($group = null)
78 | {
79 | $group = empty($group) ? self::DEFAULT_GROUP : $group;
80 | $group = Uuid::uuid3(self::GROUP_NS, $group)->toString();
81 | $name = Uuid::uuid4()->toString();
82 |
83 | return $group.'-'.$name;
84 | }
85 |
86 | /**
87 | * @param Key $key
88 | *
89 | * @return bool
90 | */
91 | public function equals(Key $key)
92 | {
93 | return ((string) $this) == ((string) $key);
94 | }
95 |
96 | /**
97 | * @return string
98 | */
99 | function __toString()
100 | {
101 | return $this->getGroup().'.'.$this->getName();
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Core/SimpleJobFactoryTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(JobFactory::class, new SimpleJobFactory());
17 | }
18 |
19 | public function testShouldReturnInstanceOfJobWhichSetInConstructor()
20 | {
21 | $job = $this->createMock(Job::class);
22 |
23 | $factory = new SimpleJobFactory([
24 | 'job-name' => $job,
25 | ]);
26 |
27 | $jobDetail = new JobDetail();
28 | $jobDetail->setJobClass('job-name');
29 |
30 | $this->assertSame($job, $factory->newJob($jobDetail));
31 | }
32 |
33 | public function testShouldCreateAndReturnNewInstanceJobDetailsClass()
34 | {
35 | $factory = new SimpleJobFactory([]);
36 |
37 | $jobDetail = new JobDetail();
38 | $jobDetail->setJobClass(SimpleJobFactoryTestJob::class);
39 |
40 | $job = $factory->newJob($jobDetail);
41 |
42 | $this->assertInstanceOf(SimpleJobFactoryTestJob::class, $job);
43 | }
44 |
45 | public function testShouldThrowSchedulerExceptionIfClassWasNotFound()
46 | {
47 | $factory = new SimpleJobFactory([]);
48 |
49 | $jobDetail = new JobDetail();
50 | $jobDetail->setJobClass('ClassDoesNotExists');
51 |
52 | $this->expectException(SchedulerException::class);
53 | $this->expectExceptionMessage('Required instance of "Quartz\Core\Job", but got: "NULL"');
54 |
55 | $factory->newJob($jobDetail);
56 | }
57 |
58 | public function testShouldThrowSchedulerExceptionIfInstanceOfObjectIsNotJobInterface()
59 | {
60 | $factory = new SimpleJobFactory([
61 | 'job-name' => new \stdClass(),
62 | ]);
63 |
64 | $jobDetail = new JobDetail();
65 | $jobDetail->setJobClass('job-name');
66 |
67 | $this->expectException(SchedulerException::class);
68 | $this->expectExceptionMessage('Required instance of "Quartz\Core\Job", but got: "stdClass"');
69 |
70 | $factory->newJob($jobDetail);
71 | }
72 | }
73 |
74 | class SimpleJobFactoryTestJob implements Job
75 | {
76 | public function execute(JobExecutionContext $context)
77 | {
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Command/ManagementCommandTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Command::class, new ManagementCommand($this->createSchedulerMock()));
18 | }
19 |
20 | public function testShouldClearAll()
21 | {
22 | $scheduler = $this->createSchedulerMock();
23 | $scheduler
24 | ->expects($this->once())
25 | ->method('clear')
26 | ;
27 |
28 | $command = new ManagementCommand($scheduler);
29 | $command->setHelperSet(new HelperSet(['question' => new QuestionHelper()]));
30 |
31 | $tester = new CommandTester($command);
32 | $tester->setInputs(['y']);
33 | $tester->execute([
34 | '--clear-all' => true,
35 | ]);
36 |
37 | $this->assertContains('You are just about to delete all storage data. Are you sure', $tester->getDisplay());
38 | }
39 |
40 | public function testShouldCreateStoreIndexes()
41 | {
42 | $store = $this->createStoreMock();
43 | $store
44 | ->expects($this->once())
45 | ->method('createIndexes')
46 | ;
47 |
48 | $scheduler = $this->createSchedulerMock();
49 | $scheduler
50 | ->expects($this->once())
51 | ->method('getStore')
52 | ->willReturn($store)
53 | ;
54 |
55 | $command = new ManagementCommand($scheduler);
56 |
57 | $tester = new CommandTester($command);
58 | $tester->execute([
59 | '--create-indexes' => true,
60 | ]);
61 |
62 | $this->assertContains('Creating storage indexes', $tester->getDisplay());
63 | }
64 |
65 | /**
66 | * @return \PHPUnit_Framework_MockObject_MockObject|StdScheduler
67 | */
68 | private function createSchedulerMock()
69 | {
70 | return $this->createMock(StdScheduler::class);
71 | }
72 |
73 | /**
74 | * @return \PHPUnit_Framework_MockObject_MockObject|YadmStore
75 | */
76 | private function createStoreMock()
77 | {
78 | return $this->createMock(YadmStore::class);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/bridge/Tests/Scheduler/RemoteSchedulerTest.php:
--------------------------------------------------------------------------------
1 | createMock(RemoteTransport::class);
23 | $transport
24 | ->expects($this->once())
25 | ->method('request')
26 | ->with($this->identicalTo($request))
27 | ->willReturn(['key' => 'value'])
28 | ;
29 |
30 | $rpcProto = $this->createMock(RpcProtocol::class);
31 | $rpcProto
32 | ->expects($this->once())
33 | ->method('encodeRequest')
34 | ->with('scheduleJob', [$trigger, $job])
35 | ->willReturn($request)
36 | ;
37 | $rpcProto
38 | ->expects($this->once())
39 | ->method('decodeValue')
40 | ->with(['key' => 'value'])
41 | ->willReturn($response)
42 | ;
43 |
44 | $scheduler = new RemoteScheduler($transport, $rpcProto);
45 |
46 | $result = $scheduler->scheduleJob($trigger, $job);
47 |
48 | $this->assertSame($response, $result);
49 | }
50 |
51 | public function testShouldThrowExceptionIfExceptionReceived()
52 | {
53 | $trigger = new SimpleTrigger();
54 | $job = new JobDetail();
55 |
56 | $e = new SchedulerException('message');
57 |
58 | $transport = $this->createMock(RemoteTransport::class);
59 | $transport
60 | ->expects($this->once())
61 | ->method('request')
62 | ;
63 |
64 | $rpcProto = $this->createMock(RpcProtocol::class);
65 | $rpcProto
66 | ->expects($this->once())
67 | ->method('encodeRequest')
68 | ->willReturn([])
69 | ;
70 | $rpcProto
71 | ->expects($this->once())
72 | ->method('decodeValue')
73 | ->willReturn($e)
74 | ;
75 |
76 | $scheduler = new RemoteScheduler($transport, $rpcProto);
77 |
78 | $this->expectException(SchedulerException::class);
79 | $this->expectExceptionMessage('message');
80 |
81 | $scheduler->scheduleJob($trigger, $job);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/JobDetail.php:
--------------------------------------------------------------------------------
1 | Job instance. JobDetails are
6 | * to be created/defined with {@link JobBuilder}.
7 | *
8 | *
9 | * Quartz does not store an actual instance of a Job class, but
10 | * instead allows you to define an instance of one, through the use of a JobDetail.
11 | *
12 | *
13 | *
14 | * Jobs have a name and group associated with them, which
15 | * should uniquely identify them within a single {@link Scheduler}.
16 | *
17 | *
18 | *
19 | * Triggers are the 'mechanism' by which Jobs
20 | * are scheduled. Many Triggers can point to the same Job,
21 | * but a single Trigger can only point to one Job.
22 | *
23 | *
24 | * @see JobBuilder
25 | * @see Job
26 | * @see JobDataMap
27 | * @see Trigger
28 | */
29 | interface JobDetail
30 | {
31 | /**
32 | * @return Key
33 | */
34 | public function getKey();
35 |
36 | /**
37 | *
38 | * Return the description given to the Job instance by its
39 | * creator (if any).
40 | *
41 | *
42 | * @return string|null if no description was set.
43 | */
44 | public function getDescription();
45 |
46 | /**
47 | *
48 | * Get the instance of Job that will be executed.
49 | *
50 | *
51 | * @return string
52 | */
53 | public function getJobClass();
54 |
55 | /**
56 | *
57 | * Get the JobDataMap that is associated with the Job.
58 | *
59 | *
60 | * @return array
61 | */
62 | public function getJobDataMap();
63 |
64 | /**
65 | *
66 | * Whether or not the Job should remain stored after it is
67 | * orphaned (no {@link Trigger}s point to it).
68 | *
69 | *
70 | *
71 | * If not explicitly set, the default value is false.
72 | *
73 | *
74 | * @return boolean true if the Job should remain persisted after being orphaned.
75 | */
76 | public function isDurable();
77 |
78 | /**
79 | *
80 | * Instructs the Scheduler whether or not the Job
81 | * should be re-executed if a 'recovery' or 'fail-over' situation is
82 | * encountered.
83 | *
84 | *
85 | *
86 | * If not explicitly set, the default value is false.
87 | *
88 | *
89 | * @see JobExecutionContext#isRecovering()
90 | *
91 | * @return bool
92 | */
93 | public function requestsRecovery();
94 | }
95 |
--------------------------------------------------------------------------------
/pkg/bridge/Tests/DI/QuartzConfigurationTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(ConfigurationInterface::class, new QuartzConfiguration());
14 | }
15 |
16 | public function testShouldReturnDefaultConfig()
17 | {
18 | $configuration = new QuartzConfiguration();
19 |
20 | $processor = new Processor();
21 | $config = $processor->processConfiguration($configuration, [[
22 | 'yadm_simple_store' => [],
23 | ]]);
24 |
25 | $expectedConfig = [
26 | 'yadm_simple_store' => [
27 | 'uri' => 'mongodb://localhost:27017',
28 | 'uriOptions' => [],
29 | 'driverOptions' => [],
30 | 'sessionId' => 'quartz',
31 | 'dbName' => null,
32 | 'managementLockCol' => 'quartz_management_lock',
33 | 'calendarCol' => 'quartz_calendar',
34 | 'triggerCol' => 'quartz_trigger',
35 | 'firedTriggerCol' => 'quartz_fired_trigger',
36 | 'jobCol' => 'quartz_job',
37 | 'pausedTriggerCol' => 'quartz_paused_trigger',
38 | ],
39 | 'misfireThreshold' => 60,
40 | ];
41 |
42 | $this->assertSame($expectedConfig, $config);
43 | }
44 |
45 | public function testCouldSetConfigurationOptions()
46 | {
47 | $configuration = new QuartzConfiguration();
48 |
49 | $processor = new Processor();
50 | $config = $processor->processConfiguration($configuration, [[
51 | 'yadm_simple_store' => [
52 | 'uri' => 'the-uri',
53 | ],
54 | 'misfireThreshold' => 120,
55 | ]]);
56 |
57 | $expectedConfig = [
58 | 'yadm_simple_store' => [
59 | 'uri' => 'the-uri',
60 | 'uriOptions' => [],
61 | 'driverOptions' => [],
62 | 'sessionId' => 'quartz',
63 | 'dbName' => null,
64 | 'managementLockCol' => 'quartz_management_lock',
65 | 'calendarCol' => 'quartz_calendar',
66 | 'triggerCol' => 'quartz_trigger',
67 | 'firedTriggerCol' => 'quartz_fired_trigger',
68 | 'jobCol' => 'quartz_job',
69 | 'pausedTriggerCol' => 'quartz_paused_trigger',
70 | ],
71 | 'misfireThreshold' => 120,
72 | ];
73 |
74 | $this->assertSame($expectedConfig, $config);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Calendar/DailyCalendarTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Calendar::class, new DailyCalendar());
13 | }
14 |
15 | public function testShouldReturnFalseWhenTimeIsIncluded()
16 | {
17 | $cal = new DailyCalendar();
18 | $cal->setTimeRange(12, 12, 12, 13, 13, 13);
19 |
20 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-12 12:12:11')));
21 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-12 12:12:12')));
22 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-12 13:13:13')));
23 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-12 13:13:14')));
24 | }
25 |
26 | public function testShouldReturnTrueWhenTimeIsIncludedIfInvertTimeRangeIsSet()
27 | {
28 | $cal = new DailyCalendar();
29 | $cal->setTimeRange(12, 12, 12, 13, 13, 13);
30 | $cal->setInvertTimeRange(true);
31 |
32 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-12 12:12:11')));
33 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-12 12:12:12')));
34 | $this->assertTrue($cal->isTimeIncluded(strtotime('2012-12-12 13:13:13')));
35 | $this->assertFalse($cal->isTimeIncluded(strtotime('2012-12-12 13:13:14')));
36 | }
37 |
38 | public function testShouldReturnNextIncludedTime()
39 | {
40 | $cal = new DailyCalendar();
41 | $cal->setTimeRange(12, 12, 12, 13, 13, 13);
42 |
43 | $nextIncludedTime = $cal->getNextIncludedTime(strtotime('2012-12-12 13:00:00'));
44 |
45 | $this->assertInternalType('int', $nextIncludedTime);
46 |
47 | $this->assertEquals(strtotime('2012-12-12 13:13:14'), $nextIncludedTime);
48 | }
49 |
50 | public function testShouldReturnNextIncludedTimeWhenTimeIsIncludedIfInvertTimeRangeIsSet()
51 | {
52 | $cal = new DailyCalendar();
53 | $cal->setTimeRange(12, 12, 12, 13, 13, 13);
54 | $cal->setInvertTimeRange(true);
55 |
56 | $nextIncludedTime = $cal->getNextIncludedTime(strtotime('2012-12-12 13:00:00'));
57 |
58 | $this->assertInternalType('int', $nextIncludedTime);
59 |
60 | $this->assertEquals(new \DateTime('2012-12-12 13:00:01'), \DateTime::createFromFormat('U', $nextIncludedTime));
61 | }
62 |
63 | public function testShouldThrowExceptionIfEndTimeIsBeforeStartTime()
64 | {
65 | $cal = new DailyCalendar();
66 |
67 | $this->expectException(\InvalidArgumentException::class);
68 | $this->expectExceptionMessage('Invalid time range: 12:12:12 - 12:12:11');
69 |
70 | $cal->setTimeRange(12, 12, 12, 12, 12, 11);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/BundleStoreResource.php:
--------------------------------------------------------------------------------
1 | options = array_replace([
29 | 'sessionId' => 'quartz',
30 | 'managementLockCol' => 'quartz_management_lock',
31 | 'calendarStorage' => 'quartz_calendar',
32 | 'triggerStorage' => 'quartz_trigger',
33 | 'firedTriggerStorage' => 'quartz_fired_trigger',
34 | 'jobStorage' => 'quartz_job',
35 | 'pausedTriggerStorage' => 'quartz_paused_trigger',
36 | ], $options);
37 |
38 | $this->clientProvider = $clientProvider;
39 | $this->collectionFactory = $collectionFactory;
40 | $this->registry = $registry;
41 | }
42 |
43 | public function getClient(): Client
44 | {
45 | return $this->getClientProvider()->getClient();
46 | }
47 |
48 | public function getClientProvider(): ClientProvider
49 | {
50 | return $this->clientProvider;
51 | }
52 |
53 | public function getCollectionFactory(): CollectionFactory
54 | {
55 | return $this->collectionFactory;
56 | }
57 |
58 | public function getManagementLock(): PessimisticLock
59 | {
60 | if (false == $this->managementLock) {
61 | $collection = $this->getCollectionFactory()->create($this->options['managementLockCol']);
62 |
63 | $this->managementLock = new PessimisticLock($collection, $this->options['sessionId']);
64 | }
65 |
66 | return $this->managementLock;
67 | }
68 |
69 | public function getCalendarStorage(): CalendarStorage
70 | {
71 | return $this->registry->getStorage($this->options['calendarStorage']);
72 | }
73 |
74 | public function getTriggerStorage(): TriggerStorage
75 | {
76 | return $this->registry->getStorage($this->options['triggerStorage']);
77 | }
78 |
79 | public function getFiredTriggerStorage(): FiredTriggerStorage
80 | {
81 | return $this->registry->getStorage($this->options['firedTriggerStorage']);
82 | }
83 |
84 | public function getJobStorage(): JobStorage
85 | {
86 | return $this->registry->getStorage($this->options['jobStorage']);
87 | }
88 |
89 | public function getPausedTriggerStorage(): PausedTriggerStorage
90 | {
91 | return $this->registry->getStorage($this->options['pausedTriggerStorage']);
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/pkg/quartz/JobDetail/JobDetail.php:
--------------------------------------------------------------------------------
1 | setInstance(self::INSTANCE);
23 | }
24 |
25 | /**
26 | * @param string $instance
27 | */
28 | protected function setInstance($instance)
29 | {
30 | $this->setValue('instance', $instance);
31 | }
32 |
33 | /**
34 | * {@inheritdoc}
35 | */
36 | public function getKey()
37 | {
38 | if (null == $this->key) {
39 | $this->key = new Key($this->getValue('name'), $this->getValue('group'));
40 | }
41 |
42 | return $this->key;
43 | }
44 |
45 | /**
46 | * @param Key $key
47 | */
48 | public function setKey(Key $key)
49 | {
50 | $this->key = $key;
51 |
52 | $this->setValue('name', $key->getName());
53 | $this->setValue('group', $key->getGroup());
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | public function getDescription()
60 | {
61 | return $this->getValue('description');
62 | }
63 |
64 | /**
65 | * @param string $description
66 | */
67 | public function setDescription($description)
68 | {
69 | $this->setValue('description', $description);
70 | }
71 |
72 | /**
73 | * {@inheritdoc}
74 | */
75 | public function isDurable()
76 | {
77 | return (bool) $this->getValue('durable');
78 | }
79 |
80 | /**
81 | * @param bool $durable
82 | */
83 | public function setDurable($durable)
84 | {
85 | $this->setValue('durable', (bool) $durable);
86 | }
87 |
88 | /**
89 | * {@inheritdoc}
90 | */
91 | public function getJobClass()
92 | {
93 | return $this->getValue('jobClass');
94 | }
95 |
96 | /**
97 | * @param string $class
98 | */
99 | public function setJobClass($class)
100 | {
101 | $this->setValue('jobClass', $class);
102 | }
103 |
104 | /**
105 | * {@inheritdoc}
106 | */
107 | public function getJobDataMap()
108 | {
109 | return $this->getValue('jobDataMap', []);
110 | }
111 |
112 | /**
113 | * @param array $jobDataMap
114 | */
115 | public function setJobDataMap(array $jobDataMap)
116 | {
117 | $this->setValue('jobDataMap', $jobDataMap);
118 | }
119 |
120 | /**
121 | * TODO: is not implemented :(
122 | *
123 | * {@inheritdoc}
124 | */
125 | public function requestsRecovery()
126 | {
127 | return $this->getValue('requestsRecovery');
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/pkg/bridge/Tests/Enqueue/EnqueueResponseJobTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Job::class, new EnqueueResponseJob($this->createProducerMock()));
18 | }
19 |
20 | public function testShouldUnscheduleTrigger()
21 | {
22 | $producer = $this->createProducerMock();
23 | $producer
24 | ->expects($this->never())
25 | ->method('sendEvent')
26 | ;
27 | $producer
28 | ->expects($this->never())
29 | ->method('sendCommand')
30 | ;
31 |
32 | $job = new EnqueueResponseJob($producer);
33 |
34 | $context = new JobExecutionContext($this->createMock(Scheduler::class), new SimpleTrigger(), new JobDetail());
35 |
36 | $job->execute($context);
37 |
38 | $this->assertSame('There is no enqueue topic or command', $context->getTrigger()->getErrorMessage());
39 | $this->assertTrue($context->isUnscheduleFiringTrigger());
40 | }
41 |
42 | public function testShouldSendEvent()
43 | {
44 | $producer = $this->createProducerMock();
45 | $producer
46 | ->expects($this->once())
47 | ->method('sendEvent')
48 | ->with('the-topic', ['topic' => 'the-topic'])
49 | ;
50 |
51 | $job = new EnqueueResponseJob($producer);
52 | $trigger = new SimpleTrigger();
53 | $trigger->setJobDataMap(['topic' => 'the-topic']);
54 |
55 | $context = new JobExecutionContext($this->createMock(Scheduler::class), $trigger, new JobDetail());
56 |
57 | $job->execute($context);
58 |
59 | $this->assertFalse($context->isUnscheduleFiringTrigger());
60 | }
61 |
62 | public function testShouldSendCommand()
63 | {
64 | $producer = $this->createProducerMock();
65 | $producer
66 | ->expects($this->once())
67 | ->method('sendCommand')
68 | ->with('the-command', ['command' => 'the-command'])
69 | ;
70 |
71 | $job = new EnqueueResponseJob($producer);
72 | $trigger = new SimpleTrigger();
73 | $trigger->setJobDataMap(['command' => 'the-command']);
74 |
75 | $context = new JobExecutionContext($this->createMock(Scheduler::class), $trigger, new JobDetail());
76 |
77 | $job->execute($context);
78 |
79 | $this->assertFalse($context->isUnscheduleFiringTrigger());
80 | }
81 |
82 | /**
83 | * @return \PHPUnit_Framework_MockObject_MockObject|ProducerInterface
84 | */
85 | private function createProducerMock()
86 | {
87 | return $this->createMock(ProducerInterface::class);
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/pkg/bundle/Tests/Command/SchedulerCommandTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Command::class, new SchedulerCommand($this->createSchedulerMock()));
18 | }
19 |
20 | public function testShouldStartScheduler()
21 | {
22 | $dispatcher = $this->createEventDispatcherMock();
23 |
24 | $scheduler = $this->createSchedulerMock();
25 | $scheduler
26 | ->expects($this->any())
27 | ->method('getEventDispatcher')
28 | ->willReturn($dispatcher)
29 | ;
30 | $scheduler
31 | ->expects($this->once())
32 | ->method('start')
33 | ;
34 |
35 | $command = new SchedulerCommand($scheduler);
36 |
37 | $tester = new CommandTester($command);
38 | $tester->execute([]);
39 |
40 | $this->assertEmpty($tester->getDisplay());
41 | }
42 |
43 | public function testShouldAddLoggerSubscriber()
44 | {
45 | $dispatcher = $this->createEventDispatcherMock();
46 | $dispatcher
47 | ->expects($this->at(0))
48 | ->method('addSubscriber')
49 | ->with($this->isInstanceOf(LoggerSubscriber::class))
50 | ;
51 |
52 | $scheduler = $this->createSchedulerMock();
53 | $scheduler
54 | ->expects($this->any())
55 | ->method('getEventDispatcher')
56 | ->willReturn($dispatcher)
57 | ;
58 |
59 | $command = new SchedulerCommand($scheduler);
60 |
61 | $tester = new CommandTester($command);
62 | $tester->execute([]);
63 |
64 | $this->assertEmpty($tester->getDisplay());
65 | }
66 |
67 | public function testShouldAddSignalSubscriber()
68 | {
69 | $dispatcher = $this->createEventDispatcherMock();
70 | $dispatcher
71 | ->expects($this->at(1))
72 | ->method('addSubscriber')
73 | ->with($this->isInstanceOf(SignalSubscriber::class))
74 | ;
75 |
76 | $scheduler = $this->createSchedulerMock();
77 | $scheduler
78 | ->expects($this->any())
79 | ->method('getEventDispatcher')
80 | ->willReturn($dispatcher)
81 | ;
82 |
83 | $command = new SchedulerCommand($scheduler);
84 |
85 | $tester = new CommandTester($command);
86 | $tester->execute([]);
87 |
88 | $this->assertEmpty($tester->getDisplay());
89 | }
90 |
91 | /**
92 | * @return \PHPUnit_Framework_MockObject_MockObject|StdScheduler
93 | */
94 | private function createSchedulerMock()
95 | {
96 | return $this->createMock(StdScheduler::class);
97 | }
98 |
99 | /**
100 | * @return \PHPUnit_Framework_MockObject_MockObject|EventDispatcherInterface
101 | */
102 | private function createEventDispatcherMock()
103 | {
104 | return $this->createMock(EventDispatcherInterface::class);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Calendar/MonthlyCalendarTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Calendar::class, new MonthlyCalendar());
13 | }
14 |
15 | public function testOnSetDaysExcludedShouldThrowExceptionIfIsNotDayOfMonth()
16 | {
17 | $cal = new MonthlyCalendar();
18 |
19 | $this->expectException(\InvalidArgumentException::class);
20 | $this->expectExceptionMessage('Invalid day of month (must be >= 1 and <= 31).');
21 |
22 | $cal->setDaysExcluded([32]);
23 | }
24 |
25 | public function testShouldSetExcludedDaysOfMonth()
26 | {
27 | $cal = new MonthlyCalendar();
28 |
29 | $cal->setDaysExcluded([1, 5, 10]);
30 |
31 | $this->assertSame([1, 5, 10], $cal->getDaysExcluded());
32 | }
33 |
34 | public function testOnSetDayExcludedShouldThrowExceptionIfArgumentIsNotDayOfMonth()
35 | {
36 | $cal = new MonthlyCalendar();
37 |
38 | $this->expectException(\InvalidArgumentException::class);
39 | $this->expectExceptionMessage('Invalid day of month (must be >= 1 and <= 31).');
40 |
41 | $cal->setDayExcluded(32, true);
42 | }
43 |
44 | public function testShouldSetExcludedDayOfMonth()
45 | {
46 | $cal = new MonthlyCalendar();
47 |
48 | $cal->setDayExcluded(3, true);
49 | $cal->setDayExcluded(5, true);
50 |
51 | $this->assertSame([3, 5], $cal->getDaysExcluded());
52 | }
53 |
54 | public function testShouldUnsetExcludedDayOfMonth()
55 | {
56 | $cal = new MonthlyCalendar();
57 |
58 | $cal->setDayExcluded(3, true);
59 | $cal->setDayExcluded(5, true);
60 | $cal->setDayExcluded(10, true);
61 |
62 | $this->assertSame([3, 5, 10], $cal->getDaysExcluded());
63 |
64 | //unset
65 | $cal->setDayExcluded(5, false);
66 | $this->assertSame([3, 10], $cal->getDaysExcluded());
67 | }
68 |
69 | public function testShouldReturnTrueWhenAllDaysAreExcluded()
70 | {
71 | $cal = new MonthlyCalendar();
72 |
73 | $this->assertFalse($cal->areAllDaysExcluded());
74 |
75 | $cal->setDaysExcluded(range(1, 31));
76 |
77 | $this->assertTrue($cal->areAllDaysExcluded());
78 | }
79 |
80 | public function testShouldReturnTrueWhenTimeIsIncluded()
81 | {
82 | $cal = new MonthlyCalendar();
83 |
84 | $cal->setDaysExcluded([13]);
85 |
86 | $included = new \DateTime('2012-12-12 12:12:12');
87 | $excluded = new \DateTime('2012-12-13 12:12:12');
88 |
89 | // included
90 | $this->assertTrue($cal->isTimeIncluded((int) $included->format('U')));
91 |
92 | // excluded
93 | $this->assertFalse($cal->isTimeIncluded((int) $excluded->format('U')));
94 | }
95 |
96 | public function testShouldReturnNextIncludedTime()
97 | {
98 | $cal = new MonthlyCalendar();
99 |
100 | $cal->setDaysExcluded([11, 12, 13, 14, 15]);
101 |
102 | $date = new \DateTime('2012-12-11 12:12:12');
103 |
104 | $nextIncludedTime = $cal->getNextIncludedTime((int) $date->format('U'));
105 |
106 | $this->assertInternalType('int', $nextIncludedTime);
107 |
108 | $this->assertEquals(new \DateTime('2012-12-16 00:00:00'), \DateTime::createFromFormat('U', $nextIncludedTime));
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/pkg/quartz/Calendar/HolidayCalendar.php:
--------------------------------------------------------------------------------
1 |
8 | * This implementation of the Calendar stores a list of holidays (full days
9 | * that are excluded from scheduling).
10 | *
11 | *
12 | *
13 | * The implementation DOES take the year into consideration, so if you want to
14 | * exclude July 4th for the next 10 years, you need to add 10 entries to the
15 | * exclude list.
16 | *
17 | */
18 | class HolidayCalendar extends BaseCalendar
19 | {
20 | const INSTANCE = 'holiday';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function __construct(Calendar $baseCalendar = null, \DateTimeZone $timeZone = null)
26 | {
27 | parent::__construct(self::INSTANCE, $baseCalendar, $timeZone);
28 | }
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | public function isTimeIncluded($timeStamp)
34 | {
35 | if (parent::isTimeIncluded($timeStamp) == false) {
36 | return false;
37 | }
38 |
39 | $lookFor = $this->getStartOfDayDateTime($timeStamp);
40 |
41 | $dates = $this->getValue('excludedDates');
42 |
43 | return false == isset($dates[$lookFor->format('U')]);
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function getNextIncludedTime($timeStamp)
50 | {
51 | // Call base calendar implementation first
52 | $baseTime = parent::getNextIncludedTime($timeStamp);
53 | if ($baseTime > 0 && $baseTime > $timeStamp) {
54 | $timeStamp = $baseTime;
55 | }
56 |
57 | // Get timestamp for 00:00:00
58 | $day = $this->getStartOfDayDateTime($timeStamp);
59 |
60 | while (false == $this->isTimeIncluded((int) $day->format('U'))) {
61 | $day->add(new \DateInterval('P1D'));
62 | }
63 |
64 | return (int) $day->format('U');
65 | }
66 |
67 | /**
68 | *
69 | * Add the given Date to the list of excluded days. Only the month, day and
70 | * year of the returned dates are significant.
71 | *
72 | *
73 | * @param \DateTime $excludedDate
74 | */
75 | public function addExcludedDate(\DateTime $excludedDate)
76 | {
77 | $date = $this->getStartOfDayDateTime($excludedDate->format('U'));
78 |
79 | $dates = $this->getValue('excludedDates');
80 | $dates[$date->format('U')] = true;
81 |
82 | $this->setValue('excludedDates', $dates);
83 | }
84 |
85 | /**
86 | * @param \DateTime $dateToRemove
87 | */
88 | public function removeExcludedDate(\DateTime $dateToRemove)
89 | {
90 | $date = $this->getStartOfDayDateTime($dateToRemove->format('U'));
91 |
92 | $dates = $this->getValue('excludedDates');
93 | unset($dates[$date->format('U')]);
94 |
95 | $this->setValue('excludedDates', $dates);
96 | }
97 |
98 | /**
99 | *
100 | * Returns a list of Dates representing the excluded
101 | * days. Only the month, day and year of the returned dates are
102 | * significant.
103 | *
104 | */
105 | public function getExcludedDates()
106 | {
107 | $dates = [];
108 | foreach ($this->getValue('excludedDates') as $date => $v) {
109 | $d = \DateTime::createFromFormat('U', $date);
110 |
111 | if ($tz = $this->getTimeZone()) {
112 | $d->setTimezone($tz);
113 | }
114 |
115 | $dates[] = $d;
116 | }
117 |
118 | return $dates;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/pkg/bridge/LoggerSubscriber.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
23 | }
24 |
25 | public function schedulerStarting()
26 | {
27 | $this->debug('Scheduler starting');
28 | }
29 |
30 | public function schedulerStarted()
31 | {
32 | $this->debug('Scheduler started');
33 | }
34 |
35 | public function schedulerShuttingdown()
36 | {
37 | $this->debug('Scheduler shutting down');
38 | }
39 |
40 | public function schedulerShutdown()
41 | {
42 | $this->debug('Scheduler shutdown');
43 | }
44 |
45 | public function jobToBeExecuted(JobExecutionContextEvent $event)
46 | {
47 | $this->debug(sprintf('Job to be executed: "%s"', (string) $event->getContext()->getJobDetail()->getKey()));
48 | }
49 |
50 | public function jobWasExecuted(JobExecutionContextEvent $event)
51 | {
52 | $this->debug(sprintf('Job was executed: "%s"', (string) $event->getContext()->getJobDetail()->getKey()));
53 |
54 | if ($e = $event->getContext()->getException()) {
55 | $this->debug(sprintf('Job has thrown exception: "%s", "%s"', get_class($e), $e->getMessage()));
56 | }
57 | }
58 |
59 | public function jobExecutionVetoed(JobExecutionContextEvent $event)
60 | {
61 | $this->debug(sprintf('Job was vetoed: "%s"', (string) $event->getContext()->getJobDetail()->getKey()));
62 | }
63 |
64 | public function triggerComplete(JobExecutionContextEvent $event)
65 | {
66 | $trigger = $event->getContext()->getTrigger();
67 |
68 | $previousFireTime = $trigger->getPreviousFireTime() ? $trigger->getPreviousFireTime()->format(DATE_ISO8601) : 'null';
69 | $scheduledFireTime = $trigger->getScheduledFireTime() ? $trigger->getScheduledFireTime()->format(DATE_ISO8601) : 'null';
70 | $nextFireTime = $trigger->getNextFireTime() ? $trigger->getNextFireTime()->format(DATE_ISO8601) : 'null';
71 |
72 | $this->debug(sprintf('Trigger execution completed: PreviousFireTime: "%s" ScheduledFireTime: "%s" NextFireTime: "%s"',
73 | $previousFireTime, $scheduledFireTime, $nextFireTime));
74 | }
75 |
76 | public function schedulerError(ErrorEvent $event)
77 | {
78 | $this->debug('Error: '.$event->getMessage());
79 |
80 | if ($event->getException()) {
81 | $this->debug($event->getException()->getMessage());
82 | }
83 | }
84 |
85 | /**
86 | * {@inheritdoc}
87 | */
88 | public static function getSubscribedEvents()
89 | {
90 | return [
91 | Event::SCHEDULER_STARTING => 'schedulerStarting',
92 | Event::SCHEDULER_STARTED => 'schedulerStarted',
93 | Event::SCHEDULER_SHUTTINGDOWN => 'schedulerShuttingdown',
94 | Event::SCHEDULER_SHUTDOWN => 'schedulerShutdown',
95 | Event::SCHEDULER_ERROR => 'schedulerError',
96 | Event::JOB_TO_BE_EXECUTED => 'jobToBeExecuted',
97 | Event::JOB_WAS_EXECUTED => 'jobWasExecuted',
98 | Event::JOB_EXECUTION_VETOED => 'jobExecutionVetoed',
99 | Event::TRIGGER_COMPLETE => 'triggerComplete',
100 | ];
101 | }
102 |
103 | private function debug($message)
104 | {
105 | $this->logger->debug(sprintf('[%s] %s', date('H:i:s'), $message));
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/pkg/quartz/Core/DateBuilder.php:
--------------------------------------------------------------------------------
1 | DateBuilder is used to conveniently create
6 | * java.util.Date instances that meet particular criteria.
7 | *
8 | * Quartz provides a builder-style API for constructing scheduling-related
9 | * entities via a Domain-Specific Language (DSL). The DSL can best be
10 | * utilized through the usage of static imports of the methods on the classes
11 | * TriggerBuilder, JobBuilder,
12 | * DateBuilder, JobKey, TriggerKey
13 | * and the various ScheduleBuilder implementations.
14 | *
15 | * Client code can then use the DSL to write code such as this:
16 | *
17 | * JobDetail job = newJob(MyJob.class)
18 | * .withIdentity("myJob")
19 | * .build();
20 | *
21 | * Trigger trigger = newTrigger()
22 | * .withIdentity(triggerKey("myTrigger", "myTriggerGroup"))
23 | * .withSchedule(simpleSchedule()
24 | * .withIntervalInHours(1)
25 | * .repeatForever())
26 | * .startAt(futureDate(10, MINUTES))
27 | * .build();
28 | *
29 | * scheduler.scheduleJob(job, trigger);
30 | *
31 | */
32 | class DateBuilder
33 | {
34 | // ISO-8601
35 | const MONDAY = 1;
36 | const TUESDAY = 2;
37 | const WEDNESDAY = 3;
38 | const THURSDAY = 4;
39 | const FRIDAY = 5;
40 | const SATURDAY = 6;
41 | const SUNDAY = 7;
42 |
43 | public static function MAX_YEAR()
44 | {
45 | static $maxYear;
46 |
47 | if (null == $maxYear) {
48 | $maxYear = ((int) date('Y')) + 100;
49 | }
50 |
51 | return $maxYear;
52 | }
53 |
54 | ////////////////////////////////////////////////////////////////////////////////////////////////////
55 |
56 | public static function validateDayOfWeek($dayOfWeek)
57 | {
58 | if ($dayOfWeek < self::MONDAY || $dayOfWeek > self::SUNDAY) {
59 | throw new \InvalidArgumentException(sprintf('Invalid day of week: "%s"', $dayOfWeek));
60 | }
61 | }
62 |
63 | public static function validateHour($hour)
64 | {
65 | if ($hour < 0 || $hour > 23) {
66 | throw new \InvalidArgumentException('Invalid hour (must be >= 0 and <= 23).');
67 | }
68 | }
69 |
70 | public static function validateMinute($minute)
71 | {
72 | if ($minute < 0 || $minute > 59) {
73 | throw new \InvalidArgumentException('Invalid minute (must be >= 0 and <= 59).');
74 | }
75 | }
76 |
77 | public static function validateSecond($second)
78 | {
79 | if ($second < 0 || $second > 59) {
80 | throw new \InvalidArgumentException('Invalid second (must be >= 0 and <= 59).');
81 | }
82 | }
83 |
84 | public static function validateDayOfMonth($day)
85 | {
86 | if ($day < 1 || $day > 31) {
87 | throw new \InvalidArgumentException('Invalid day of month (must be >= 1 and <= 31).');
88 | }
89 | }
90 |
91 | public static function validateMonth($month)
92 | {
93 | if ($month < 1 || $month > 12) {
94 | throw new \InvalidArgumentException('Invalid month (must be >= 1 and <= 12.');
95 | }
96 | }
97 |
98 | public static function validateYear($year)
99 | {
100 | if ($year < 0 || $year > self::MAX_YEAR()) {
101 | throw new \InvalidArgumentException('Invalid year (must be >= 0 and <= ' . self::MAX_YEAR());
102 | }
103 | }
104 |
105 | public static function validateIntervalUnit($intervalUnit)
106 | {
107 | if ($intervalUnit < IntervalUnit::SECOND || $intervalUnit > IntervalUnit::YEAR) {
108 | throw new \InvalidArgumentException('Invalid interval unit.');
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Calendar/BaseCalendarTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Calendar::class, new BaseCalendarImpl(''));
16 | }
17 |
18 | public function testShouldSetInstanceName()
19 | {
20 | $cal = new BaseCalendarImpl('base-calendar');
21 |
22 | $this->assertSame('base-calendar', $cal->getInstance());
23 | }
24 |
25 | public function testCouldSetGetBaseCalendar()
26 | {
27 | $baseCal = new HolidayCalendar(); // have to use real calendar
28 | $baseCal->setDescription('the description');
29 |
30 | $cal = new BaseCalendarImpl('');
31 |
32 | // set base calendar avoid object cache
33 | set_value($cal, 'baseCalendar', get_values($baseCal));
34 |
35 | $this->assertNotNull($cal->getBaseCalendar());
36 | $this->assertInstanceOf(HolidayCalendar::class, $cal->getBaseCalendar());
37 | $this->assertSame('the description', $cal->getBaseCalendar()->getDescription());
38 | }
39 |
40 | public function testCouldSetGetDescription()
41 | {
42 | $cal = new BaseCalendarImpl('');
43 | $cal->setDescription('the description');
44 |
45 | $this->assertSame('the description', $cal->getDescription());
46 | }
47 |
48 | public function testOnIsTimeIncludedShouldReturnTrueIfBaseCalendarNotSet()
49 | {
50 | $cal = new BaseCalendarImpl('');
51 |
52 | $this->assertTrue($cal->isTimeIncluded(1111));
53 | }
54 |
55 | public function testOnIsTimeIncludedShouldThrowExceptionIfTimeIsLessThenZero()
56 | {
57 | $cal = new BaseCalendarImpl('');
58 |
59 | $this->expectException(\InvalidArgumentException::class);
60 | $this->expectExceptionMessage('timeStamp must be greater 0');
61 |
62 | $cal->isTimeIncluded(-1);
63 | }
64 |
65 | public function testOnIsTimeIncludedShouldCallBaseIsTimeIncludedMethod()
66 | {
67 | $cal = new BaseCalendarImpl('');
68 | $cal->setBaseCalendar(new BaseCalendarImpl2(''));
69 |
70 | $this->assertSame(12345, $cal->isTimeIncluded(12345));
71 | }
72 |
73 | public function testOnGetNextIncludedTimeShouldReturnTimeIfBaseCalendarNotSet()
74 | {
75 | $cal = new BaseCalendarImpl('');
76 |
77 | $this->assertSame(1111, $cal->getNextIncludedTime(1111));
78 | }
79 |
80 | public function testOnGetNextIncludedTimeShouldThrowExceptionIfTimeIsLessThenZero()
81 | {
82 | $cal = new BaseCalendarImpl('');
83 |
84 | $this->expectException(\InvalidArgumentException::class);
85 | $this->expectExceptionMessage('timeStamp must be greater 0');
86 |
87 | $cal->getNextIncludedTime(-1);
88 | }
89 |
90 | public function testOnGetNextIncludedTimeShouldCallBaseIsTimeIncludedMethod()
91 | {
92 | $cal = new BaseCalendarImpl('');
93 | $cal->setBaseCalendar(new BaseCalendarImpl2(''));
94 |
95 | $this->assertSame(12345+1, $cal->getNextIncludedTime(12345));
96 | }
97 |
98 | public function testCouldGetSetTimezone()
99 | {
100 | $cal = new BaseCalendarImpl('');
101 | $cal->setTimeZone(new \DateTimeZone('Europe/Simferopol'));
102 |
103 | $this->assertNotNull($cal->getTimeZone());
104 | $this->assertInstanceOf(\DateTimeZone::class, $cal->getTimeZone());
105 | $this->assertSame('Europe/Simferopol', $cal->getTimeZone()->getName());
106 | }
107 | }
108 |
109 | class BaseCalendarImpl extends BaseCalendar
110 | {
111 | public $isTimeIncludedReturnValue;
112 |
113 | public function getInstance()
114 | {
115 | return $this->getValue('instance');
116 | }
117 | }
118 |
119 | class BaseCalendarImpl2 extends BaseCalendar
120 | {
121 | public function isTimeIncluded($timeStamp)
122 | {
123 | return $timeStamp;
124 | }
125 |
126 | public function getNextIncludedTime($timeStamp)
127 | {
128 | return $timeStamp + 1;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Calendar/WeeklyCalendarTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Calendar::class, new WeeklyCalendar());
14 | }
15 |
16 | public function testShouldReturnDefaultExcludedWeekDays()
17 | {
18 | $cal = new WeeklyCalendar();
19 |
20 | $this->assertSame([DateBuilder::SATURDAY, DateBuilder::SUNDAY], $cal->getDaysExcluded());
21 | }
22 |
23 | public function testOnSetDaysExcludedShouldThrowExceptionIfArrayKeyIsNotDayOfWeek()
24 | {
25 | $cal = new WeeklyCalendar();
26 |
27 | $this->expectException(\InvalidArgumentException::class);
28 | $this->expectExceptionMessage('Invalid day of week: "8"');
29 |
30 | $cal->setDaysExcluded([8]);
31 | }
32 |
33 | public function testShouldSetExcludedDaysOfWeek()
34 | {
35 | $cal = new WeeklyCalendar();
36 |
37 | $cal->setDaysExcluded([DateBuilder::MONDAY, DateBuilder::FRIDAY]);
38 |
39 | $this->assertSame([DateBuilder::MONDAY, DateBuilder::FRIDAY], $cal->getDaysExcluded());
40 | }
41 |
42 | public function testOnSetDayExcludedShouldThrowExceptionIfArgumentIsNotDayOfWeek()
43 | {
44 | $cal = new WeeklyCalendar();
45 |
46 | $this->expectException(\InvalidArgumentException::class);
47 | $this->expectExceptionMessage('Invalid day of week: "9"');
48 |
49 | $cal->setDayExcluded(9, true);
50 | }
51 |
52 | public function testShouldSetExcludedDayOfWeek()
53 | {
54 | $cal = new WeeklyCalendar();
55 |
56 | $cal->setDayExcluded(DateBuilder::THURSDAY, true);
57 | $cal->setDayExcluded(DateBuilder::FRIDAY, true);
58 |
59 | $this->assertSame([
60 | DateBuilder::THURSDAY,
61 | DateBuilder::FRIDAY,
62 | DateBuilder::SATURDAY,
63 | DateBuilder::SUNDAY
64 | ], $cal->getDaysExcluded());
65 | }
66 |
67 | public function testShouldUnsetExcludedDayOfWeek()
68 | {
69 | $cal = new WeeklyCalendar();
70 |
71 | $cal->setDayExcluded(DateBuilder::FRIDAY, true);
72 |
73 | $this->assertSame([
74 | DateBuilder::FRIDAY,
75 | DateBuilder::SATURDAY,
76 | DateBuilder::SUNDAY
77 | ], $cal->getDaysExcluded());
78 |
79 | // unset
80 | $cal->setDayExcluded(DateBuilder::FRIDAY, false);
81 | $cal->setDayExcluded(DateBuilder::SUNDAY, false);
82 |
83 | $this->assertSame([
84 | DateBuilder::SATURDAY,
85 | ], $cal->getDaysExcluded());
86 | }
87 |
88 | public function testShouldReturnTrueWhenAllDaysAreExcluded()
89 | {
90 | $cal = new WeeklyCalendar();
91 |
92 | $this->assertFalse($cal->areAllDaysExcluded());
93 |
94 | $cal->setDaysExcluded(range(1, 7));
95 |
96 | $this->assertTrue($cal->areAllDaysExcluded());
97 | }
98 |
99 | public function testShouldReturnTrueWhenTimeIsIncluded()
100 | {
101 | $cal = new WeeklyCalendar();
102 |
103 | $cal->setDaysExcluded([4]);
104 |
105 | $included = new \DateTime('2012-12-12 12:12:12');
106 | $excluded = new \DateTime('2012-12-13 12:12:12');
107 |
108 | // included
109 | $this->assertSame(3, (int) $included->format('N'));
110 | $this->assertTrue($cal->isTimeIncluded((int) $included->format('U')));
111 |
112 | // excluded
113 | $this->assertSame(4, (int) $excluded->format('N'));
114 | $this->assertFalse($cal->isTimeIncluded((int) $excluded->format('U')));
115 | }
116 |
117 | public function testShouldReturnNextIncludedTime()
118 | {
119 | $cal = new WeeklyCalendar();
120 |
121 | $cal->setDaysExcluded([2, 3, 4, 5]);
122 |
123 | $date = new \DateTime('2012-12-11 12:12:12');
124 |
125 | $this->assertSame(2, (int) $date->format('N'));
126 |
127 | $nextIncludedTime = $cal->getNextIncludedTime((int) $date->format('U'));
128 |
129 | $this->assertInternalType('int', $nextIncludedTime);
130 |
131 | $this->assertEquals(new \DateTime('2012-12-15 00:00:00'), \DateTime::createFromFormat('U', $nextIncludedTime));
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/pkg/bridge/Scheduler/SchedulerFactory.php:
--------------------------------------------------------------------------------
1 | config = $config;
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function getScheduler()
63 | {
64 | if (null == $this->scheduler) {
65 | $eventDispatcher = new EventDispatcher();
66 |
67 | $this->scheduler = new StdScheduler(
68 | $this->getStore(),
69 | $this->getJobRunShellFactory(),
70 | $this->getJobFactory(),
71 | $eventDispatcher
72 | );
73 | }
74 |
75 | return $this->scheduler;
76 | }
77 |
78 | public function getRemoteScheduler()
79 | {
80 | $transport = new EnqueueRemoteTransport($this->getEnqueue()->getProducerV2());
81 |
82 | return new RemoteScheduler($transport, new RpcProtocol());
83 | }
84 |
85 | public function getJobRunShellFactory()
86 | {
87 | if (null == $this->jobRunShellFactory) {
88 | $runJobShell = new EnqueueJobRunShell($this->getEnqueue()->getProducerV2());
89 | $this->jobRunShellFactory = new StdJobRunShellFactory($runJobShell);
90 | }
91 |
92 | return $this->jobRunShellFactory;
93 | }
94 |
95 | /**
96 | * @return SimpleJobFactory
97 | */
98 | public function getJobFactory()
99 | {
100 | if (null == $this->jobFactory) {
101 | $job = new EnqueueResponseJob($this->getEnqueue()->getProducerV2());
102 |
103 | $this->jobFactory = new SimpleJobFactory([
104 | EnqueueResponseJob::class => $job,
105 | ]);
106 | }
107 |
108 | return $this->jobFactory;
109 | }
110 |
111 | /**
112 | * @return JobRunShellProcessor
113 | */
114 | public function getJobRunShellProcessor()
115 | {
116 | $jobRunShell = new StdJobRunShell();
117 | $jobRunShell->initialize($this->getScheduler());
118 |
119 | return new JobRunShellProcessor($this->getStore(), $jobRunShell);
120 | }
121 |
122 | /**
123 | * @return EnqueueRemoteTransportProcessor
124 | */
125 | public function getRemoteSchedulerProcessor()
126 | {
127 | return new EnqueueRemoteTransportProcessor($this->getScheduler(), new RpcProtocol());
128 | }
129 |
130 | /**
131 | * @return YadmStore
132 | */
133 | public function getStore()
134 | {
135 | if (null == $this->store) {
136 | $config = isset($this->config['store']) ? $this->config['store'] : [];
137 | $this->store = new YadmStore(new SimpleStoreResource($config));
138 | }
139 |
140 | return $this->store;
141 | }
142 |
143 | /**
144 | * @return SimpleClient
145 | */
146 | public function getEnqueue()
147 | {
148 | if (null == $this->enqueue) {
149 | $config = isset($this->config['enqueue']) ? $this->config['enqueue'] : [];
150 | $this->enqueue = new SimpleClient($config);
151 | }
152 |
153 | return $this->enqueue;
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/pkg/quartz/Scheduler/StdJobRunShell.php:
--------------------------------------------------------------------------------
1 | scheduler = $scheduler;
21 | }
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | public function execute(Trigger $trigger)
27 | {
28 | if (false == $jobDetail = $this->scheduler->getJobDetail($trigger->getJobKey())) {
29 | $trigger->setErrorMessage(sprintf('Job was not found with key: "%s"', (string) $trigger->getJobKey()));
30 | $this->scheduler->notifyJobStoreJobComplete($trigger, null, CompletedExecutionInstruction::SET_ALL_JOB_TRIGGERS_ERROR);
31 |
32 | return;
33 | }
34 |
35 | $calendar = null;
36 | if ($trigger->getCalendarName()) {
37 | if (false == $calendar = $this->scheduler->getCalendar($trigger->getCalendarName())) {
38 | $trigger->setErrorMessage(sprintf('Calendar was not found with name: "%s"', (string) $trigger->getCalendarName()));
39 | $this->scheduler->notifyJobStoreJobComplete($trigger, $jobDetail, CompletedExecutionInstruction::SET_ALL_JOB_TRIGGERS_ERROR);
40 |
41 | return;
42 | }
43 | }
44 |
45 | try {
46 | $job = $this->scheduler->getJobFactory()->newJob($jobDetail);
47 | } catch (\Exception $e) {
48 | $trigger->setErrorMessage(sprintf('Job instance was not created: "%s"', (string) $jobDetail->getKey()));
49 | $this->scheduler->notifyJobStoreJobComplete($trigger, $jobDetail, CompletedExecutionInstruction::SET_ALL_JOB_TRIGGERS_ERROR);
50 |
51 | return;
52 | }
53 |
54 | $context = new JobExecutionContext($this->scheduler, $trigger, $jobDetail, $calendar);
55 |
56 | $now = time();
57 | $scheduledFireTime = (int) $context->getTrigger()->getScheduledFireTime()->format('U');
58 |
59 | // sleep until execution time is came up
60 | if ($scheduledFireTime > $now) {
61 | $sleepTime = $scheduledFireTime - $now;
62 |
63 | if ($sleepTime > 120) { // 2 min
64 | $trigger->setErrorMessage(sprintf('Sleep time is too long. "%d"', $sleepTime));
65 | $this->scheduler->notifyJobStoreJobComplete($trigger, $jobDetail, CompletedExecutionInstruction::NOOP);
66 | }
67 |
68 | sleep($scheduledFireTime - $now);
69 | }
70 |
71 | $startTime = microtime(true);
72 | while (true) {
73 | if ($this->scheduler->notifyTriggerListenersFired($context)) {
74 | // trigger vetoed
75 | $this->scheduler->notifyJobListenersWasVetoed($context);
76 |
77 | $instructionCode = $trigger->executionComplete($context);
78 | $this->scheduler->notifyJobStoreJobComplete($trigger, $jobDetail, $instructionCode);
79 |
80 | if (null == $trigger->getNextFireTime()) {
81 | $this->scheduler->notifySchedulerListenersFinalized($trigger);
82 | }
83 |
84 | break;
85 | }
86 |
87 | $this->scheduler->notifyJobListenersToBeExecuted($context);
88 |
89 | try {
90 | $job->execute($context);
91 | } catch (\Exception $e) {
92 | $context->setException($e);
93 | } catch (\Error $e) {
94 | $context->setException($e);
95 | }
96 |
97 | $endTime = microtime(true);
98 | $context->setJobRunTime(($endTime - $startTime) * 1000);
99 |
100 | $this->scheduler->notifyJobListenersWasExecuted($context);
101 |
102 | $instructionCode = $trigger->executionComplete($context);
103 |
104 | $this->scheduler->notifyTriggerListenersComplete($context);
105 |
106 | if ($instructionCode === CompletedExecutionInstruction::RE_EXECUTE_JOB) {
107 | $context->incrementRefireCount();
108 |
109 | continue;
110 | }
111 |
112 | $this->scheduler->notifyJobStoreJobComplete($trigger, $jobDetail, $instructionCode);
113 |
114 | break;
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/pkg/quartz/Calendar/BaseCalendar.php:
--------------------------------------------------------------------------------
1 | setInstance($instance);
19 | $this->setBaseCalendar($baseCalendar);
20 | $this->setTimeZone($timeZone);
21 | }
22 |
23 | /**
24 | * @param string $instance
25 | */
26 | protected function setInstance($instance)
27 | {
28 | $this->setValue('instance', $instance);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | public function setBaseCalendar(Calendar $baseCalendar = null)
35 | {
36 | $this->setObject('baseCalendar', $baseCalendar);
37 | }
38 |
39 | /**
40 | * {@inheritdoc}
41 | */
42 | public function getBaseCalendar()
43 | {
44 | return $this->getObject('baseCalendar', function ($values) {
45 | return ModelClassFactory::getClass($values);
46 | });
47 | }
48 |
49 | /**
50 | * {@inheritdoc}
51 | */
52 | public function getDescription()
53 | {
54 | return $this->getValue('description');
55 | }
56 |
57 | /**
58 | * {@inheritdoc}
59 | */
60 | public function setDescription($description)
61 | {
62 | $this->setValue('description', $description);
63 | }
64 |
65 | /**
66 | * {@inheritdoc}
67 | */
68 | public function isTimeIncluded($timeStamp)
69 | {
70 | if ($timeStamp <= 0) {
71 | throw new InvalidArgumentException('timeStamp must be greater 0');
72 | }
73 |
74 | if (null != $baseCalendar = $this->getBaseCalendar()) {
75 | return $baseCalendar->isTimeIncluded($timeStamp);
76 | }
77 |
78 | return true;
79 | }
80 |
81 | /**
82 | * {@inheritdoc}
83 | */
84 | public function getNextIncludedTime($timeStamp)
85 | {
86 | if ($timeStamp <= 0) {
87 | throw new InvalidArgumentException('timeStamp must be greater 0');
88 | }
89 |
90 | if (null != $baseCalendar = $this->getBaseCalendar()) {
91 | return $baseCalendar->getNextIncludedTime($timeStamp);
92 | }
93 |
94 | return $timeStamp;
95 | }
96 |
97 | /**
98 | * Returns the time zone for which this Calendar will be
99 | * resolved.
100 | *
101 | * @return \DateTimeZone This Calendar's timezone, null if Calendar should use the default
102 | */
103 | public function getTimeZone()
104 | {
105 | if ($timezone = $this->getValue('timezone')) {
106 | return new \DateTimeZone($timezone);
107 | }
108 | }
109 |
110 | /**
111 | * Sets the time zone for which this Calendar will be resolved.
112 | *
113 | * @param \DateTimeZone $timeZone The time zone to use for this Calendar, null if default should be used
114 | */
115 | public function setTimeZone(\DateTimeZone $timeZone = null)
116 | {
117 | if ($timeZone) {
118 | $value = $timeZone->getName();
119 | } else {
120 | $value = null;
121 | }
122 |
123 | $this->setValue('timezone', $value);
124 | }
125 |
126 | /**
127 | * @param int $timeStamp
128 | *
129 | * @return \DateTime
130 | */
131 | protected function createDateTime($timeStamp)
132 | {
133 | $date = \DateTime::createFromFormat('U', $timeStamp);
134 |
135 | if ($tz = $this->getTimeZone()) {
136 | $date->setTimezone($tz);
137 | }
138 |
139 | return $date;
140 | }
141 |
142 | /**
143 | * @param int $timeStamp
144 | *
145 | * @return \DateTime
146 | */
147 | protected function getStartOfDayDateTime($timeStamp)
148 | {
149 | $date = $this->createDateTime($timeStamp);
150 | $date->setTime(0, 0, 0);
151 |
152 | return $date;
153 | }
154 |
155 | /**
156 | * @param int $timeStamp
157 | *
158 | * @return \DateTime
159 | */
160 | protected function getEndOfDayDateTime($timeStamp)
161 | {
162 | $date = $this->createDateTime($timeStamp);
163 | $date->setTime(23, 59, 59);
164 |
165 | return $date;
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/pkg/bridge/Yadm/SimpleStoreResource.php:
--------------------------------------------------------------------------------
1 | options = array_replace([
33 | 'uri' => 'mongodb://localhost:27017',
34 | 'uriOptions' => [],
35 | 'driverOptions' => [],
36 | 'sessionId' => 'quartz',
37 | 'dbName' => 'quartz',
38 | 'managementLockCol' => 'managementLock',
39 | 'calendarCol' => 'calendar',
40 | 'triggerCol' => 'trigger',
41 | 'firedTriggerCol' => 'firedTrigger',
42 | 'jobCol' => 'job',
43 | 'pausedTriggerCol' => 'pausedTrigger',
44 |
45 | ], $options);
46 | }
47 |
48 | public function getClient(): Client
49 | {
50 | return $this->getClientProvider()->getClient();
51 | }
52 |
53 | public function getClientProvider(): ClientProvider
54 | {
55 | if (false == $this->clientProvider) {
56 | $this->clientProvider = new ClientProvider($this->options['uri'], $this->options['uriOptions'], $this->options['driverOptions']);
57 | }
58 |
59 | return $this->clientProvider;
60 | }
61 |
62 | public function getCollectionFactory(): CollectionFactory
63 | {
64 | if (false == $this->collectionFactory) {
65 | $this->collectionFactory = new CollectionFactory($this->getClientProvider(), $this->options['uri']);
66 | }
67 |
68 | return $this->collectionFactory;
69 | }
70 |
71 | public function getManagementLock(): PessimisticLock
72 | {
73 | if (false == $this->managementLock) {
74 | $collection = $this->getCollectionFactory()->create($this->options['managementLockCol']);
75 |
76 | $this->managementLock = new PessimisticLock($collection, $this->options['sessionId']);
77 | }
78 |
79 | return $this->managementLock;
80 | }
81 |
82 | public function getCalendarStorage(): CalendarStorage
83 | {
84 | if (false == $this->calendarStorage) {
85 | $this->calendarStorage = new CalendarStorage(
86 | $this->options['calendarCol'],
87 | $this->getCollectionFactory(),
88 | new ModelHydrator()
89 | );
90 | }
91 |
92 | return $this->calendarStorage;
93 | }
94 |
95 | public function getTriggerStorage(): TriggerStorage
96 | {
97 | if (false == $this->triggerStorage) {
98 | $this->triggerStorage = new TriggerStorage(
99 | $this->options['triggerCol'],
100 | $this->getCollectionFactory(),
101 | new ModelHydrator()
102 | );
103 | }
104 |
105 | return $this->triggerStorage;
106 | }
107 |
108 | public function getFiredTriggerStorage(): FiredTriggerStorage
109 | {
110 | if (false == $this->firedTriggerStorage) {
111 | $this->firedTriggerStorage = new FiredTriggerStorage(
112 | $this->options['firedTriggerCol'],
113 | $this->getCollectionFactory(),
114 | new ModelHydrator()
115 | );
116 | }
117 |
118 | return $this->firedTriggerStorage;
119 | }
120 |
121 | public function getJobStorage(): JobStorage
122 | {
123 | if (false == $this->jobStorage) {
124 | $this->jobStorage = new JobStorage(
125 | $this->options['jobCol'],
126 | $this->getCollectionFactory(),
127 | new ModelHydrator()
128 | );
129 | }
130 |
131 | return $this->jobStorage;
132 | }
133 |
134 | public function getPausedTriggerStorage(): PausedTriggerStorage
135 | {
136 | if (false == $this->pausedTriggerStorage) {
137 | $this->pausedTriggerStorage = new PausedTriggerStorage(
138 | $this->options['pausedTriggerCol'],
139 | $this->getCollectionFactory(),
140 | new ModelHydrator()
141 | );
142 | }
143 |
144 | return $this->pausedTriggerStorage;
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Triggers/SimpleTriggerTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Trigger::class, new SimpleTrigger());
14 | }
15 |
16 | public function testCouldSetGetRepeatCount()
17 | {
18 | $t = new SimpleTrigger();
19 | $t->setRepeatCount(123);
20 |
21 | $this->assertSame(123, $t->getRepeatCount());
22 | }
23 |
24 | public function testCouldSetGetRepeatInterval()
25 | {
26 | $t = new SimpleTrigger();
27 | $t->setRepeatInterval(123);
28 |
29 | $this->assertSame(123, $t->getRepeatInterval());
30 | }
31 |
32 | public function testOnValidateShouldThrowExceptionIfRepeatIntervalIsLessThanOne()
33 | {
34 | $t = new SimpleTrigger();
35 | $t->setRepeatInterval(0);
36 |
37 | $this->expectException(SchedulerException::class);
38 | $this->expectExceptionMessage('Trigger\'s name cannot be null');
39 |
40 | $t->validate();
41 | }
42 |
43 | public function testShouldComputeFirstFireTime()
44 | {
45 | $t = new SimpleTrigger();
46 | $t->setStartTime(new \DateTime('2012-12-12 12:00:00'));
47 | $t->setRepeatInterval(10);
48 |
49 | $this->assertEquals(new \DateTime('2012-12-12 12:00:00'), $t->computeFirstFireTime());
50 | }
51 |
52 | public function testShouldComputeFireTimeAfter()
53 | {
54 | $t = new SimpleTrigger();
55 | $t->setStartTime(new \DateTime('2012-12-12 12:00:00'));
56 | $t->setRepeatInterval(10);
57 | $t->setRepeatCount(SimpleTrigger::REPEAT_INDEFINITELY);
58 |
59 | $this->assertEquals(new \DateTime('2012-12-12 12:00:10'), $t->getFireTimeAfter(new \DateTime('2012-12-12 12:00:00')));
60 | $this->assertEquals(new \DateTime('2012-12-13 00:00:00'), $t->getFireTimeAfter(new \DateTime('2012-12-12 23:59:55')));
61 | }
62 |
63 | public function testOnFireTimeAfterShouldReturnNullIfTimesTriggeredMoreThanRepeatCount()
64 | {
65 | $t = new SimpleTrigger();
66 | $t->setStartTime(new \DateTime('2012-12-12 12:00:00'));
67 | $t->setRepeatInterval(10);
68 | $t->setTimesTriggered(5);
69 | $t->setRepeatCount(3);
70 |
71 | $this->assertNull($t->getFireTimeAfter());
72 | }
73 |
74 | public function testOnFireTimeAfterShouldReturnNullIfRepeatCountZeroAndAfterTimeAfterStartTime()
75 | {
76 | $t = new SimpleTrigger();
77 | $t->setStartTime(new \DateTime('2012-12-12 12:00:00'));
78 | $t->setRepeatInterval(10);
79 | $t->setRepeatCount(0);
80 |
81 | $this->assertNull($t->getFireTimeAfter(new \DateTime('2012-12-12 13:00:00')));
82 | }
83 |
84 | public function testOnFireTimeAfterShouldReturnStartTimeIfAfterTimeBeforeStartTime()
85 | {
86 | $t = new SimpleTrigger();
87 | $t->setStartTime(new \DateTime('2012-12-12 12:00:00'));
88 | $t->setRepeatInterval(10);
89 | $t->setRepeatCount(0);
90 |
91 | $this->assertEquals(new \DateTime('2012-12-12 12:00:00'), $t->getFireTimeAfter(new \DateTime('2012-12-12 11:00:00')));
92 | }
93 |
94 | public function testOnFireTimeAfterShouldReturnNullIfNumTimesExecutedIsMoreThanRepeatCount()
95 | {
96 | $t = new SimpleTrigger();
97 | $t->setStartTime(new \DateTime('2012-12-12 12:00:00'));
98 | $t->setRepeatInterval(10);
99 | $t->setRepeatCount(2);
100 |
101 | $this->assertNull($t->getFireTimeAfter(new \DateTime('2012-12-12 12:00:21')));
102 | }
103 |
104 | public function testOnFireTimeAfterShouldReturnNullIfCalculatedTimeIsAfterEndTime()
105 | {
106 | $t = new SimpleTrigger();
107 | $t->setStartTime(new \DateTime('2012-12-12 12:00:00'));
108 | $t->setEndTime(new \DateTime('2012-12-12 13:00:00'));
109 | $t->setRepeatInterval(10);
110 | $t->setRepeatCount(SimpleTrigger::REPEAT_INDEFINITELY);
111 |
112 | $this->assertNull($t->getFireTimeAfter(new \DateTime('2012-12-12 12:59:55')));
113 | }
114 |
115 | public function testShouldUpdateAfterMisfireWithFireNowInstruction()
116 | {
117 | $t = new SimpleTrigger();
118 | $t->setRepeatCount(0);
119 | $t->setMisfireInstruction(SimpleTrigger::MISFIRE_INSTRUCTION_FIRE_NOW);
120 |
121 | $this->assertNull($t->getNextFireTime());
122 |
123 | $t->updateAfterMisfire();
124 |
125 | $this->assertEquals(new \DateTime(), $t->getNextFireTime(), '', 5); // closer to now
126 | $this->assertSame(0, $t->getRepeatCount());
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/pkg/bridge/Tests/Scheduler/JobRunShellProcessorTest.php:
--------------------------------------------------------------------------------
1 | createJobStore(), $this->createJobRunShell());
22 |
23 | $this->assertInstanceOf(Processor::class, $processor);
24 | }
25 |
26 | public function testShouldImplementCommandSubscriberInterfaceAndReturnExpectectedSubscribedCommand()
27 | {
28 | $processor = new JobRunShellProcessor($this->createJobStore(), $this->createJobRunShell());
29 |
30 | $this->assertInstanceOf(CommandSubscriberInterface::class, $processor);
31 |
32 | $expectedConfig = [
33 | 'command' => 'quartz_job_run_shell',
34 | 'queue' => 'quartz_job_run_shell',
35 | 'prefix_queue' => false,
36 | 'exclusive' => true,
37 | ];
38 |
39 | $this->assertSame($expectedConfig, JobRunShellProcessor::getSubscribedCommand());
40 | }
41 |
42 | public function testShouldImplementQueueSubscriberInterfaceAndReturnExpectectedSubscribedCommand()
43 | {
44 | $processor = new JobRunShellProcessor($this->createJobStore(), $this->createJobRunShell());
45 |
46 | $this->assertInstanceOf(QueueSubscriberInterface::class, $processor);
47 |
48 | $this->assertSame(['quartz_job_run_shell'], JobRunShellProcessor::getSubscribedQueues());
49 | }
50 |
51 | public function testShouldRejectMessageIfJobInstanceIdIsNotSet()
52 | {
53 | $store = $this->createJobStore();
54 | $store
55 | ->expects($this->never())
56 | ->method('retrieveFireTrigger')
57 | ;
58 |
59 | $shell = $this->createJobRunShell();
60 | $shell
61 | ->expects($this->never())
62 | ->method('execute')
63 | ;
64 |
65 | $processor = new JobRunShellProcessor($store, $shell);
66 |
67 | $result = $processor->process(new NullMessage(), $this->createMock(Context::class));
68 |
69 | $this->assertInstanceOf(Result::class, $result);
70 | $this->assertSame('fire instance id is empty', $result->getReason());
71 | }
72 |
73 | public function testShouldRejectMessageIfJobInstanceWasNotFound()
74 | {
75 | $store = $this->createJobStore();
76 | $store
77 | ->expects($this->once())
78 | ->method('retrieveFireTrigger')
79 | ;
80 |
81 | $shell = $this->createJobRunShell();
82 | $shell
83 | ->expects($this->never())
84 | ->method('execute')
85 | ;
86 |
87 | $processor = new JobRunShellProcessor($store, $shell);
88 |
89 | $message = new NullMessage();
90 | $message->setBody(JSON::encode([
91 | 'fireInstanceId' => '1234',
92 | ]));
93 |
94 | $result = $processor->process($message, $this->createMock(Context::class));
95 |
96 | $this->assertInstanceOf(Result::class, $result);
97 | $this->assertSame('There is not trigger with fire instance id: "1234"', $result->getReason());
98 | }
99 |
100 | public function testShouldPassTriggerToJobRunShell()
101 | {
102 | $trigger = new SimpleTrigger();
103 |
104 | $store = $this->createJobStore();
105 | $store
106 | ->expects($this->once())
107 | ->method('retrieveFireTrigger')
108 | ->willReturn($trigger)
109 | ;
110 |
111 | $shell = $this->createJobRunShell();
112 | $shell
113 | ->expects($this->once())
114 | ->method('execute')
115 | ->with($trigger)
116 | ;
117 |
118 | $processor = new JobRunShellProcessor($store, $shell);
119 |
120 | $message = new NullMessage();
121 | $message->setBody(JSON::encode([
122 | 'fireInstanceId' => '1234',
123 | ]));
124 |
125 | $result = $processor->process($message, $this->createMock(Context::class));
126 |
127 | $this->assertInstanceOf(Result::class, $result);
128 | $this->assertSame(Result::ACK, $result->getStatus());
129 | }
130 |
131 | /**
132 | * @return \PHPUnit_Framework_MockObject_MockObject|YadmStore
133 | */
134 | private function createJobStore()
135 | {
136 | return $this->createMock(YadmStore::class);
137 | }
138 |
139 | /**
140 | * @return \PHPUnit_Framework_MockObject_MockObject|StdJobRunShell
141 | */
142 | private function createJobRunShell()
143 | {
144 | return $this->createMock(StdJobRunShell::class);
145 | }
146 | }
147 |
--------------------------------------------------------------------------------
/pkg/bridge/Scheduler/RpcProtocol.php:
--------------------------------------------------------------------------------
1 | encodeValue($arg);
16 | }
17 |
18 | return [
19 | 'method' => $method,
20 | 'args' => $encodedArgs,
21 | ];
22 | }
23 |
24 | public function decodeRequest($data)
25 | {
26 | if (false == isset($data['method'])) {
27 | throw new \InvalidArgumentException('Method property is not set');
28 | }
29 |
30 | if (false == isset($data['args'])) {
31 | throw new \InvalidArgumentException('Args property is not set');
32 | }
33 |
34 | return [
35 | 'method' => $data['method'],
36 | 'args' => $this->decodeValue($data['args']),
37 | ];
38 | }
39 |
40 | public function encodeValue($data)
41 | {
42 | if (is_scalar($data) || is_null($data)) {
43 | return $data;
44 | } elseif (is_object($data)) {
45 | if ($data instanceof Model) {
46 | return [
47 | '__values__' => get_values($data),
48 | ];
49 | } elseif ($data instanceof \Exception) {
50 | return [
51 | '__exception__' => [
52 | 'class' => get_class($data),
53 | 'message' => $data->getMessage(),
54 | 'code' => $data->getCode(),
55 | ]
56 | ];
57 | } elseif ($data instanceof \DateTime) {
58 | return [
59 | '__datetime__' => [
60 | 'iso' => $data->format(DATE_ISO8601),
61 | 'unix' => $data->format('U'),
62 | 'tz' => $data->getTimezone()->getName(),
63 | ]
64 | ];
65 | }
66 |
67 | throw new \InvalidArgumentException('Object arguments are not allowed');
68 | } elseif (is_array($data)) {
69 | $result = [];
70 |
71 | foreach ($data as $key => $value) {
72 | $result[$key] = $this->encodeValue($value);
73 | }
74 |
75 | return $result;
76 | } else {
77 | throw new \InvalidArgumentException('Invalid argument');
78 | }
79 | }
80 |
81 | public function decodeValue($data)
82 | {
83 | if (is_scalar($data) || is_null($data)) {
84 | return $data;
85 | } elseif (is_array($data)) {
86 | // values object
87 | if (isset($data['__values__'])) {
88 | $class = ModelClassFactory::getClass($data['__values__']);
89 | $rc = new \ReflectionClass($class);
90 | $object = $rc->newInstanceWithoutConstructor();
91 | set_values($object, $data['__values__']);
92 |
93 | return $object;
94 | }
95 |
96 | // datetime
97 | if (isset($data['__datetime__'])) {
98 | $time = new \DateTime('@'.$data['__datetime__']['unix']);
99 | $time->setTimezone(new \DateTimeZone($data['__datetime__']['tz']));
100 |
101 | return $time;
102 | }
103 |
104 | // exception
105 | if (isset($data['__exception__'])) {
106 | return $this->decodeException($data['__exception__']);
107 | }
108 |
109 | // just an array
110 | $result = [];
111 | foreach ($data as $key => $value) {
112 | $decValue = $this->decodeValue($value);
113 | // only top level exception is allowed
114 |
115 | if ($decValue instanceof \Exception) {
116 | throw new \InvalidArgumentException('Only top level exception is allowed');
117 | }
118 |
119 | $result[$key] = $decValue;
120 | }
121 |
122 | return $result;
123 | } else {
124 | throw new \InvalidArgumentException('Unexpected value');
125 | }
126 | }
127 |
128 | private function decodeException(array $data)
129 | {
130 | if (false == isset($data['class'])) {
131 | throw new \InvalidArgumentException('Exception class property is not set');
132 | }
133 |
134 | if (false == isset($data['message'])) {
135 | throw new \InvalidArgumentException('Exception message property is not set');
136 | }
137 |
138 | if (false == isset($data['code'])) {
139 | throw new \InvalidArgumentException('Exception code property is not set');
140 | }
141 |
142 | if (class_exists($data['class'])) {
143 | return new $data['class']($data['message'], $data['code']);
144 | } else {
145 | return new \Exception(json_encode($data));
146 | }
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/pkg/quartz/Tests/Core/DateBuilderTest.php:
--------------------------------------------------------------------------------
1 | format('Y');
13 |
14 | $this->assertSame($year+100, DateBuilder::MAX_YEAR());
15 | }
16 |
17 | public function testOnValidateDayOfWeekShouldThrowExceptionIfInvalidValue()
18 | {
19 | $this->expectException(\InvalidArgumentException::class);
20 | $this->expectExceptionMessage('Invalid day of week: "100"');
21 |
22 | DateBuilder::validateDayOfWeek(100);
23 | }
24 |
25 | public function testOnValidateDayOfWeekShouldNotThrowExceptionIfValidValue()
26 | {
27 | DateBuilder::validateDayOfWeek(DateBuilder::MONDAY);
28 | DateBuilder::validateDayOfWeek(DateBuilder::THURSDAY);
29 | DateBuilder::validateDayOfWeek(DateBuilder::WEDNESDAY);
30 | DateBuilder::validateDayOfWeek(DateBuilder::THURSDAY);
31 | DateBuilder::validateDayOfWeek(DateBuilder::FRIDAY);
32 | DateBuilder::validateDayOfWeek(DateBuilder::SATURDAY);
33 | DateBuilder::validateDayOfWeek(DateBuilder::SUNDAY);
34 | }
35 |
36 | public function testOnValidateHourShouldThrowExceptionIfInvalidValue()
37 | {
38 | $this->expectException(\InvalidArgumentException::class);
39 | $this->expectExceptionMessage('Invalid hour (must be >= 0 and <= 23).');
40 |
41 | DateBuilder::validateHour(24);
42 | }
43 |
44 | public function testOnValidateHourShouldNotThrowExceptionIfValidValue()
45 | {
46 | DateBuilder::validateHour(0);
47 | DateBuilder::validateHour(23);
48 | }
49 |
50 | public function testOnValidateMinuteShouldThrowExceptionIfInvalidValue()
51 | {
52 | $this->expectException(\InvalidArgumentException::class);
53 | $this->expectExceptionMessage('Invalid minute (must be >= 0 and <= 59).');
54 |
55 | DateBuilder::validateMinute(60);
56 | }
57 |
58 | public function testOnValidateMinuteShouldNotThrowExceptionIfValidValue()
59 | {
60 | DateBuilder::validateMinute(0);
61 | DateBuilder::validateMinute(59);
62 | }
63 |
64 | public function testOnValidateSecondShouldThrowExceptionIfInvalidValue()
65 | {
66 | $this->expectException(\InvalidArgumentException::class);
67 | $this->expectExceptionMessage('Invalid second (must be >= 0 and <= 59).');
68 |
69 | DateBuilder::validateSecond(60);
70 | }
71 |
72 | public function testOnValidateSecondShouldNotThrowExceptionIfValidValue()
73 | {
74 | DateBuilder::validateSecond(0);
75 | DateBuilder::validateSecond(59);
76 | }
77 |
78 | public function testOnValidateDayOfMonthShouldThrowExceptionIfInvalidValue()
79 | {
80 | $this->expectException(\InvalidArgumentException::class);
81 | $this->expectExceptionMessage('Invalid day of month (must be >= 1 and <= 31).');
82 |
83 | DateBuilder::validateDayOfMonth(32);
84 | }
85 |
86 | public function testOnValidateDayOfMonthShouldNotThrowExceptionIfValidValue()
87 | {
88 | DateBuilder::validateDayOfMonth(1);
89 | DateBuilder::validateDayOfMonth(31);
90 | }
91 |
92 | public function testOnValidateMonthShouldThrowExceptionIfInvalidValue()
93 | {
94 | $this->expectException(\InvalidArgumentException::class);
95 | $this->expectExceptionMessage('Invalid month (must be >= 1 and <= 12.');
96 |
97 | DateBuilder::validateMonth(13);
98 | }
99 |
100 | public function testOnValidateMonthShouldNotThrowExceptionIfValidValue()
101 | {
102 | DateBuilder::validateMonth(1);
103 | DateBuilder::validateMonth(12);
104 | }
105 |
106 | public function testOnValidateYearShouldThrowExceptionIfInvalidValue()
107 | {
108 | $year = (int) (new \DateTime())->format('Y');
109 |
110 | $this->expectException(\InvalidArgumentException::class);
111 | $this->expectExceptionMessage(sprintf('Invalid year (must be >= 0 and <= %d', $year + 100));
112 |
113 | DateBuilder::validateYear($year + 200);
114 | }
115 |
116 | public function testOnValidateYearShouldNotThrowExceptionIfValidValue()
117 | {
118 | $year = (int) (new \DateTime())->format('Y');
119 |
120 | DateBuilder::validateYear(0);
121 | DateBuilder::validateYear($year + 100);
122 | }
123 |
124 | public function testOnValidateIntervalUnitShouldThrowExceptionIfInvalidValue()
125 | {
126 | $this->expectException(\InvalidArgumentException::class);
127 | $this->expectExceptionMessage('Invalid interval unit.');
128 |
129 | DateBuilder::validateIntervalUnit(100);
130 | }
131 |
132 | public function testOnValidateIntervalUnitShouldNotThrowExceptionIfValidValue()
133 | {
134 | DateBuilder::validateIntervalUnit(IntervalUnit::SECOND);
135 | DateBuilder::validateIntervalUnit(IntervalUnit::MINUTE);
136 | DateBuilder::validateIntervalUnit(IntervalUnit::HOUR);
137 | DateBuilder::validateIntervalUnit(IntervalUnit::DAY);
138 | DateBuilder::validateIntervalUnit(IntervalUnit::WEEK);
139 | DateBuilder::validateIntervalUnit(IntervalUnit::MONTH);
140 | DateBuilder::validateIntervalUnit(IntervalUnit::YEAR);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/pkg/quartz/Calendar/MonthlyCalendar.php:
--------------------------------------------------------------------------------
1 |
9 | * This implementation of the Calendar excludes a set of days of the month. You
10 | * may use it to exclude every first day of each month for example. But you may define
11 | * any day of a month.
12 | *
13 | */
14 | class MonthlyCalendar extends BaseCalendar
15 | {
16 | const INSTANCE = 'monthly';
17 |
18 | /**
19 | * {@inheritdoc}
20 | */
21 | public function __construct(Calendar $baseCalendar = null, \DateTimeZone $timeZone = null)
22 | {
23 | parent::__construct(self::INSTANCE, $baseCalendar, $timeZone);
24 | }
25 |
26 | /**
27 | *
28 | * Get the array which defines the exclude-value of each day of month.
29 | * Only the first 31 elements of the array are relevant, with the 1 index
30 | * element representing the first day of the month.
31 | *
32 | */
33 | public function getDaysExcluded()
34 | {
35 | return $this->getValue('excludeDays', []);
36 | }
37 |
38 | /**
39 | *
40 | * Return true, if day is defined to be excluded.
41 | *
42 | *
43 | * @param int $day The day of the month (from 1 to 31) to check.
44 | *
45 | * @return bool
46 | */
47 | public function isDayExcluded($day)
48 | {
49 | DateBuilder::validateDayOfMonth($day);
50 |
51 | $days = $this->getValue('excludeDays', []);
52 |
53 | return in_array($day, $days, true);
54 | }
55 |
56 | /**
57 | *
58 | * Redefine the array of days excluded. The array must non-null and of size
59 | * greater or equal to 31. The 1 index element represents the first day of
60 | * the month.
61 | *
62 | *
63 | * @param array $days
64 | */
65 | public function setDaysExcluded(array $days)
66 | {
67 | foreach ($days as $day) {
68 | DateBuilder::validateDayOfMonth($day);
69 | }
70 |
71 | $this->setValue('excludeDays', $days);
72 | }
73 |
74 | /**
75 | *
76 | * Redefine a certain day of the month to be excluded (true) or included
77 | * (false).
78 | *
79 | *
80 | * @param int $day The day of the month (from 1 to 31) to set.
81 | * @param bool $exclude
82 | */
83 | public function setDayExcluded($day, $exclude)
84 | {
85 | DateBuilder::validateDayOfMonth($day);
86 |
87 | $days = $this->getValue('excludeDays', []);
88 |
89 | if ($exclude) {
90 | if (false === array_search($day, $days, true)) {
91 | $days[] = $day;
92 | sort($days, SORT_NUMERIC);
93 | }
94 | } else {
95 | if (false !== $index = array_search($day, $days, true)) {
96 | unset($days[$index]);
97 | $days = array_values($days);
98 | }
99 | }
100 |
101 | $this->setValue('excludeDays', $days);
102 | }
103 |
104 | /**
105 | *
106 | * Determine whether the given time (in milliseconds) is 'included' by the
107 | * Calendar.
108 | *
109 | *
110 | *
111 | * Note that this Calendar is only has full-day precision.
112 | *
113 | *
114 | * @param $timeStamp
115 | *
116 | * @return bool
117 | */
118 | public function isTimeIncluded($timeStamp)
119 | {
120 | // Test the base calendar first. Only if the base calendar not already
121 | // excludes the time/date, continue evaluating this calendar instance.
122 | if (false == parent::isTimeIncluded($timeStamp)) {
123 | return false;
124 | }
125 |
126 | $date = $this->createDateTime($timeStamp);
127 | $day = (int) $date->format('j');
128 |
129 | return false == $this->isDayExcluded($day);
130 | }
131 |
132 | /**
133 | *
134 | * Check if all days are excluded. That is no day is included.
135 | *
136 | */
137 | public function areAllDaysExcluded()
138 | {
139 | return count($this->getValue('excludeDays', [])) >= 31;
140 | }
141 |
142 | /**
143 | *
144 | * Determine the next time (in milliseconds) that is 'included' by the
145 | * Calendar after the given time. Return the original value if timeStamp is
146 | * included. Return 0 if all days are excluded.
147 | *
148 | *
149 | *
150 | * Note that this Calendar is only has full-day precision.
151 | *
152 | *
153 | * @param int $timeStamp
154 | *
155 | * @return int
156 | */
157 | public function getNextIncludedTime($timeStamp)
158 | {
159 | if ($this->areAllDaysExcluded()) {
160 | return 0;
161 | }
162 |
163 | $baseTime = parent::getNextIncludedTime($timeStamp);
164 | if ($baseTime > 0 && $baseTime > $timeStamp) {
165 | $timeStamp = $baseTime;
166 | }
167 |
168 | // Get timestamp for 00:00:00
169 | $date = $this->getStartOfDayDateTime($timeStamp);
170 | $day = (int) $date->format('j');
171 |
172 | if (false == $this->isDayExcluded($day)) {
173 | return $timeStamp; // return the original value
174 | }
175 |
176 | while ($this->isDayExcluded($day)) {
177 | $date->add(new \DateInterval('P1D'));
178 | $day = (int) $date->format('j');
179 | }
180 |
181 | return (int) $date->format('U');
182 | }
183 | }
184 |
--------------------------------------------------------------------------------