├── .gitignore ├── .travis.yml ├── LICENSE ├── bin ├── dev ├── release ├── splitsh-lite ├── subtree-split └── test ├── composer.json ├── docker-compose.yml ├── docker ├── Dockerfile ├── cli.ini ├── entrypoiny.sh └── xdebug.ini ├── phpunit.xml.dist └── pkg ├── app ├── .env.dist ├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── bin │ └── console ├── composer.json ├── docker-compose.yml ├── etc │ ├── bundles.php │ ├── container.yaml │ └── packages │ │ ├── app.yaml │ │ ├── enqueue.yaml │ │ ├── framework.yaml │ │ ├── quartz.yaml │ │ └── test │ │ └── framework.yaml ├── phpunit.xml.dist └── src │ └── Kernel.php ├── bridge ├── .travis.yml ├── DI │ ├── QuartzConfiguration.php │ ├── QuartzExtension.php │ ├── QuartzJobCompilerPass.php │ └── RemoteSchedulerExtension.php ├── Enqueue │ ├── EnqueueRemoteTransport.php │ ├── EnqueueRemoteTransportProcessor.php │ └── EnqueueResponseJob.php ├── LICENSE ├── LoggerSubscriber.php ├── Scheduler │ ├── EnqueueJobRunShell.php │ ├── JobRunShellProcessor.php │ ├── RemoteScheduler.php │ ├── RemoteTransport.php │ ├── RpcProtocol.php │ └── SchedulerFactory.php ├── SignalSubscriber.php ├── Swoole │ └── CheckMasterProcessSubscriber.php ├── Tests │ ├── DI │ │ └── QuartzConfigurationTest.php │ ├── Enqueue │ │ ├── EnqueueRemoteTransportProcessorTest.php │ │ ├── EnqueueRemoteTransportTest.php │ │ └── EnqueueResponseJobTest.php │ ├── Scheduler │ │ ├── JobRunShellProcessorTest.php │ │ ├── RemoteSchedulerTest.php │ │ └── RpcProtocolTest.php │ ├── Swoole │ │ └── CheckMasterProcessSubscriberTest.php │ └── Yadm │ │ └── YadmStoreTest.php ├── Yadm │ ├── BundleStoreResource.php │ ├── CalendarStorage.php │ ├── FiredTriggerStorage.php │ ├── JobStorage.php │ ├── ModelHydrator.php │ ├── PausedTriggerStorage.php │ ├── SimpleStoreResource.php │ ├── StoreResource.php │ ├── TriggerStorage.php │ └── YadmStore.php ├── composer.json └── phpunit.xml.dist ├── bundle ├── .gitignore ├── .travis.yml ├── Command │ ├── ManagementCommand.php │ └── SchedulerCommand.php ├── DependencyInjection │ ├── Configuration.php │ └── QuartzExtension.php ├── LICENSE ├── QuartzBundle.php ├── Tests │ ├── Command │ │ ├── ManagementCommandTest.php │ │ └── SchedulerCommandTest.php │ ├── DependencyInjection │ │ ├── ConfigurationTest.php │ │ └── QuartzExtensionTest.php │ ├── Functional │ │ ├── App │ │ │ ├── AppKernel.php │ │ │ ├── config │ │ │ │ ├── config.yml │ │ │ │ └── routing.yml │ │ │ └── console.php │ │ ├── ManagementCommandTest.php │ │ ├── RemoteSchedulerTest.php │ │ ├── SchedulerCommandTest.php │ │ ├── SchedulerTest.php │ │ └── WebTestCase.php │ └── QuartzBundleTest.php ├── composer.json └── phpunit.xml.dist └── quartz ├── .gitignore ├── Calendar ├── BaseCalendar.php ├── CronCalendar.php ├── DailyCalendar.php ├── HolidayCalendar.php ├── MonthlyCalendar.php └── WeeklyCalendar.php ├── Core ├── Calendar.php ├── CalendarIntervalScheduleBuilder.php ├── CompletedExecutionInstruction.php ├── CronScheduleBuilder.php ├── DailyTimeIntervalScheduleBuilder.php ├── DateBuilder.php ├── IntervalUnit.php ├── Job.php ├── JobBuilder.php ├── JobDetail.php ├── JobExecutionContext.php ├── JobFactory.php ├── JobPersistenceException.php ├── Key.php ├── Model.php ├── ObjectAlreadyExistsException.php ├── ScheduleBuilder.php ├── Scheduler.php ├── SchedulerException.php ├── SchedulerFactory.php ├── SimpleJobFactory.php ├── SimpleScheduleBuilder.php ├── Trigger.php └── TriggerBuilder.php ├── Events ├── ErrorEvent.php ├── Event.php ├── GroupsEvent.php ├── JobDetailEvent.php ├── JobExecutionContextEvent.php ├── KeyEvent.php ├── TickEvent.php └── TriggerEvent.php ├── JobDetail └── JobDetail.php ├── LICENSE ├── ModelClassFactory.php ├── Scheduler ├── JobRunShell.php ├── JobRunShellFactory.php ├── JobStore.php ├── StdJobRunShell.php ├── StdJobRunShellFactory.php └── StdScheduler.php ├── Tests ├── Calendar │ ├── BaseCalendarTest.php │ ├── CronCalendarTest.php │ ├── DailyCalendarTest.php │ ├── HolidayCalendarTest.php │ ├── MonthlyCalendarTest.php │ └── WeeklyCalendarTest.php ├── Core │ ├── DateBuilderTest.php │ ├── JobExecutionContextTest.php │ ├── KeyTest.php │ └── SimpleJobFactoryTest.php ├── JobDetail │ └── JobDetailTest.php └── Triggers │ ├── AbstractTriggerTest.php │ └── SimpleTriggerTest.php ├── Triggers ├── AbstractTrigger.php ├── CalendarIntervalTrigger.php ├── CronTrigger.php ├── DailyTimeIntervalTrigger.php └── SimpleTrigger.php ├── composer.json ├── examples ├── calendar-interval-trigger.php ├── cron-trigger.php ├── daily-interval-trigger.php ├── scheduler.php └── simple-trigger.php └── phpunit.xml.dist /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor 3 | composer.lock 4 | bin/phpunit 5 | bin/console 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/splitsh-lite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-quartz/quartz-dev/95824343fec03a867d52275d0894d7e8b1b62af3/bin/splitsh-lite -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker/entrypoiny.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | while true; do sleep 1; done 4 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/app/.gitignore: -------------------------------------------------------------------------------- 1 | ###> symfony/framework-bundle ### 2 | .env 3 | /var/ 4 | /vendor/ 5 | /web/bundles/ 6 | ###< symfony/framework-bundle ### 7 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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/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/app/etc/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | 'Enqueue\Bundle\EnqueueBundle' => ['all' => true], 6 | 'Quartz\Bundle\QuartzBundle' => ['all' => true], 7 | ]; 8 | -------------------------------------------------------------------------------- /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/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/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/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/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 | -------------------------------------------------------------------------------- /pkg/app/etc/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: null 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /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/app/src/Kernel.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/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/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/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/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/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/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/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/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/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/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/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/Scheduler/RemoteTransport.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/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/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/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/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/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/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/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/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/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/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/bridge/Yadm/CalendarStorage.php: -------------------------------------------------------------------------------- 1 | 1], ['unique' => true]), 21 | ]; 22 | } 23 | 24 | public function getCreateCollectionOptions(): array 25 | { 26 | return []; 27 | } 28 | } -------------------------------------------------------------------------------- /pkg/bridge/Yadm/FiredTriggerStorage.php: -------------------------------------------------------------------------------- 1 | 1]), 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/ModelHydrator.php: -------------------------------------------------------------------------------- 1 | hydrate($values, build_object($class, $values)); 20 | } 21 | } -------------------------------------------------------------------------------- /pkg/bridge/Yadm/PausedTriggerStorage.php: -------------------------------------------------------------------------------- 1 | 1]), 21 | ]; 22 | } 23 | 24 | public function getCreateCollectionOptions(): array 25 | { 26 | return []; 27 | } 28 | } -------------------------------------------------------------------------------- /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/bridge/Yadm/StoreResource.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 | } -------------------------------------------------------------------------------- /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/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/bundle/.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | /composer.lock 3 | /composer.phar 4 | /phpunit.xml 5 | /vendor/ 6 | /.idea/ 7 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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/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/bundle/QuartzBundle.php: -------------------------------------------------------------------------------- 1 | addCompilerPass(new QuartzJobCompilerPass()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/bundle/Tests/Functional/App/config/routing.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/php-quartz/quartz-dev/95824343fec03a867d52275d0894d7e8b1b62af3/pkg/bundle/Tests/Functional/App/config/routing.yml -------------------------------------------------------------------------------- /pkg/bundle/Tests/Functional/App/console.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(new ArgvInput()); 12 | -------------------------------------------------------------------------------- /pkg/bundle/Tests/Functional/ManagementCommandTest.php: -------------------------------------------------------------------------------- 1 | get('test_quartz.cli.management'); 12 | 13 | $this->assertInstanceOf(ManagementCommand::class, $scheduler); 14 | } 15 | } -------------------------------------------------------------------------------- /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/SchedulerTest.php: -------------------------------------------------------------------------------- 1 | get('test_quartz.scheduler'); 12 | 13 | $this->assertInstanceOf(StdScheduler::class, $scheduler); 14 | } 15 | } -------------------------------------------------------------------------------- /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/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/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/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/quartz/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | vendor 3 | composer.lock 4 | bin/phpunit 5 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/quartz/Core/CompletedExecutionInstruction.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/Core/IntervalUnit.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/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/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 | -------------------------------------------------------------------------------- /pkg/quartz/Core/JobPersistenceException.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/Core/Model.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 | -------------------------------------------------------------------------------- /pkg/quartz/Core/ScheduleBuilder.php: -------------------------------------------------------------------------------- 1 | {@link Scheduler}. 6 | */ 7 | class SchedulerException extends \Exception 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /pkg/quartz/Core/SchedulerFactory.php: -------------------------------------------------------------------------------- 1 | 8 | * Returns a client-usable handle to a Scheduler. 9 | *

10 | * 11 | * @return Scheduler 12 | */ 13 | public function getScheduler(); 14 | } 15 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/quartz/ModelClassFactory.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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 | --------------------------------------------------------------------------------