├── 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 JobRunShell} 9 | * to be used within the {@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 | --------------------------------------------------------------------------------