├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── migrations ├── 2017_01_01_000001_create_jobs_table.php └── 2017_01_01_000002_jobs_add_uniqueable.php ├── phpunit.xml ├── src ├── Connectors │ ├── DatabaseConnector.php │ └── RedisConnector.php ├── DatabaseQueue.php ├── Jobs │ └── Uniqueable.php ├── LaravelUQueueServiceProvider.php ├── LuaScripts.php ├── LumenUQueueServiceProvider.php └── RedisQueue.php └── tests ├── LaravelTest.php ├── LumenTest.php ├── PackageTest.php ├── SimpleJob.php └── UniqueableJob.php /.gitignore: -------------------------------------------------------------------------------- 1 | # Composer files 2 | composer.phar 3 | composer.lock 4 | vendor/ 5 | 6 | # Local configs 7 | config/autoload/*.local.php 8 | 9 | # Binary gettext files 10 | *.mo 11 | 12 | # Data 13 | data/logs/ 14 | data/cache/ 15 | data/sessions/ 16 | data/tmp/ 17 | temp/ 18 | 19 | .idea/ 20 | coverage.xml 21 | 22 | .phpunit.result.cache 23 | 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | services: 4 | - docker 5 | 6 | language: php 7 | 8 | php: 9 | - 7.1 10 | - 7.2 11 | - 7.3 12 | - 7.4 13 | 14 | env: 15 | - ILLUMINATE_VERSION=5 16 | - ILLUMINATE_VERSION=6 17 | - ILLUMINATE_VERSION=7 18 | - ILLUMINATE_VERSION=8 19 | 20 | jobs: 21 | exclude: 22 | - env: ILLUMINATE_VERSION=6 23 | php: 7.1 24 | - env: ILLUMINATE_VERSION=7 25 | php: 7.1 26 | - env: ILLUMINATE_VERSION=7 # https://github.com/composer/composer/issues/7051 27 | php: 7.2 28 | - env: ILLUMINATE_VERSION=8 29 | php: 7.1 30 | - env: ILLUMINATE_VERSION=8 31 | php: 7.2 32 | 33 | before_install: 34 | - docker pull postgres:latest 35 | - docker run --name postgres -e POSTGRES_PASSWORD=postgres -d -p 54320:5432 postgres:latest 36 | - docker pull mysql:latest 37 | - docker run --name mysql -e MYSQL_DATABASE=mysql -e MYSQL_USER=mysql -e MYSQL_PASSWORD=mysql -e MYSQL_ROOT_PASSWORD=mysql -d -p 33060:3306 mysql:latest mysqld --default-authentication-plugin=mysql_native_password 38 | - docker pull redis:latest 39 | - docker run --name redis -d -p 63790:6379 redis:latest 40 | - mkdir -p /tmp/coverage 41 | - export PHP_VERSION=`php -r "echo PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION;"` 42 | - export COMPOSER_MEMORY_LIMIT=-1 43 | 44 | install: 45 | - travis_retry composer self-update 46 | - composer global require hirak/prestissimo 47 | - travis_wait composer require "laravel/laravel:^${ILLUMINATE_VERSION}.0" "laravel/lumen:^${ILLUMINATE_VERSION}.0" --no-interaction --prefer-source 48 | 49 | script: 50 | - vendor/bin/phpunit --coverage-php /tmp/coverage/laravel.cov tests/LaravelTest.php 51 | - vendor/bin/phpunit --coverage-php /tmp/coverage/lumen.cov tests/LumenTest.php 52 | 53 | after_success: 54 | - vendor/bin/phpcov merge /tmp/coverage --clover coverage.xml 55 | - bash <(curl -s https://codecov.io/bash) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # illuminate-uqueue 2 | Provides support for uniqueable queues for Laravel/Lumen 5.5+. 3 | 4 | # Travis CI 5 | [![Build Status](https://travis-ci.org/mingalevme/illuminate-uqueue.svg?branch=master)](https://travis-ci.org/mingalevme/illuminate-uqueue) 6 | 7 | # Codecov 8 | [![codecov](https://codecov.io/gh/mingalevme/illuminate-uqueue/branch/master/graph/badge.svg)](https://codecov.io/gh/mingalevme/illuminate-uqueue) 9 | 10 | # Supported drivers: 11 | - Database 12 | - Redis (Based on Sorted Sets) 13 | 14 | # Installation 15 | 16 | 1. ```composer require mingalevme/illuminate-uqueue``` 17 | 18 | 2. In Laravel 5.5+, the service provider and facade will automatically get registered. 19 | 20 | For older versions of the framework or Lumen, follow the steps below: 21 | 22 | Register the appropriate service provider ```\Mingalevme\Illuminate\UQueue\LaravelUQueueServiceProvider::class``` or ```\Mingalevme\Illuminate\UQueue\LumenUQueueServiceProvider::class```. 23 | 24 | 3. If you plan to use the database as a driver you should add the migration (change the table name if necessary): 25 | 26 | ```php 27 | string('unique_id')->nullable(); 44 | $table->unique(['queue', 'unique_id'], 'jobs_queue_unique_id_unique'); 45 | }); 46 | } 47 | } 48 | 49 | /** 50 | * Reverse the migrations. 51 | * 52 | * @return void 53 | */ 54 | public function down() 55 | { 56 | if (Schema::hasColumn('jobs', 'unique_id')) { 57 | Schema::table('jobs', function (Blueprint $table) { 58 | $table->dropUnique('jobs_queue_unique_id_unique'); 59 | $table->dropColumn('unique_id'); 60 | }); 61 | } 62 | } 63 | } 64 | 65 | ``` 66 | 67 | 4. Create a job that implements the interface ```\Mingalevme\Illuminate\UQueue\Jobs\Uniqueable```: 68 | 69 | ```php 70 | data = $data; 84 | } 85 | 86 | public function uniqueable() 87 | { 88 | return md5(json_encode($this->data)); 89 | } 90 | 91 | public function handle() 92 | { 93 | // ... 94 | } 95 | } 96 | ``` 97 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mingalevme/illuminate-uqueue", 3 | "description": "Laravel/Lumen uniqueable queues (Database, Redis)", 4 | "keywords": [ 5 | "laravel", 6 | "lumen", 7 | "queue", 8 | "uniqueable" 9 | ], 10 | "homepage": "https://github.com/mingalevme/illuminate-uqueue", 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Mingalev Mikhail", 15 | "email": "mingalevme@gmail.com" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=7.1", 20 | "ext-json": "*", 21 | "illuminate/queue": ">=5.8" 22 | }, 23 | "require-dev": { 24 | "phpunit/phpunit": ">=7.0", 25 | "mockery/mockery": "^1.0", 26 | "predis/predis": ">=1.0", 27 | "doctrine/dbal": "^2.8", 28 | "phpunit/phpcov": ">=5.0" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Mingalevme\\Illuminate\\UQueue\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Mingalevme\\Tests\\Illuminate\\UQueue\\": "tests/" 38 | }, 39 | "classmap": [ 40 | "migrations" 41 | ] 42 | }, 43 | "extra": { 44 | "laravel": { 45 | "providers": [ 46 | "Mingalevme\\Illuminate\\UQueue\\LaravelUQueueServiceProvider" 47 | ] 48 | } 49 | }, 50 | "minimum-stability": "dev" 51 | } 52 | -------------------------------------------------------------------------------- /migrations/2017_01_01_000001_create_jobs_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 19 | $table->string('queue')->index(); 20 | $table->longText('payload'); 21 | $table->unsignedTinyInteger('attempts'); 22 | $table->unsignedInteger('reserved_at')->nullable(); 23 | $table->unsignedInteger('available_at'); 24 | $table->unsignedInteger('created_at'); 25 | }); 26 | } 27 | } 28 | 29 | /** 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists(self::TABLE_NAME); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /migrations/2017_01_01_000002_jobs_add_uniqueable.php: -------------------------------------------------------------------------------- 1 | string(self::COLUMN_NAME)->nullable(); 20 | $table->unique(['queue', self::COLUMN_NAME], self::INDEX_NAME); 21 | }); 22 | } 23 | } 24 | 25 | /** 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | if (Schema::hasColumn(CreateJobsTable::TABLE_NAME, self::COLUMN_NAME)) { 31 | Schema::table(CreateJobsTable::TABLE_NAME, function (Blueprint $table) { 32 | $table->dropColumn(self::COLUMN_NAME); 33 | }); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 36 | 37 | -------------------------------------------------------------------------------- /src/Connectors/DatabaseConnector.php: -------------------------------------------------------------------------------- 1 | connections->connection($config['connection'] ?? null), 19 | $config['table'], 20 | $config['queue'], 21 | $config['retry_after'] ?? 60 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Connectors/RedisConnector.php: -------------------------------------------------------------------------------- 1 | redis, $config['queue'], 20 | $config['connection'] ?? $this->connection, 21 | $config['retry_after'] ?? 60, 22 | $config['block_for'] ?? null 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/DatabaseQueue.php: -------------------------------------------------------------------------------- 1 | uniqueable(); 25 | } 26 | 27 | return $payload; 28 | } 29 | 30 | /** 31 | * Create an array to insert for the given job. 32 | * 33 | * @param string|null $queue 34 | * @param array $payload 35 | * @param int $availableAt 36 | * @param int $attempts 37 | * @return array 38 | */ 39 | protected function buildDatabaseRecord($queue, $payload, $availableAt, $attempts = 0) 40 | { 41 | $record = parent::buildDatabaseRecord($queue, $this->jsonize($payload), $availableAt, $attempts); 42 | 43 | if (isset($payload['unique_id'])) { 44 | $record['unique_id'] = $payload['unique_id']; 45 | } else { 46 | $record['unique_id'] = \Illuminate\Support\Str::random(32); 47 | } 48 | 49 | return $record; 50 | } 51 | 52 | /** 53 | * Push a new job onto the queue. 54 | * 55 | * @param string $job 56 | * @param mixed $data 57 | * @param string $queue 58 | * @return mixed 59 | */ 60 | public function push($job, $data = '', $queue = null) 61 | { 62 | return $this->pushToDatabase($queue, $this->createPayloadArray($job, $data)); 63 | } 64 | 65 | /** 66 | * Push a raw payload onto the queue. 67 | * 68 | * @param string $payload 69 | * @param string $queue 70 | * @param array $options 71 | * @return mixed 72 | */ 73 | public function pushRaw($payload, $queue = null, array $options = []) 74 | { 75 | return $this->pushToDatabase($queue, json_decode($payload, true)); 76 | } 77 | 78 | /** 79 | * Push a new job onto the queue after a delay. 80 | * 81 | * @param \DateTimeInterface|\DateInterval|int $delay 82 | * @param string $job 83 | * @param mixed $data 84 | * @param string $queue 85 | * @return void 86 | */ 87 | public function later($delay, $job, $data = '', $queue = null) 88 | { 89 | return $this->pushToDatabase($queue, $this->createPayloadArray($job, $data), $delay); 90 | } 91 | 92 | /** 93 | * Release a reserved job back onto the queue. 94 | * 95 | * @param string $queue 96 | * @param \Illuminate\Queue\Jobs\DatabaseJobRecord $job 97 | * @param int $delay 98 | * @return mixed 99 | */ 100 | public function release($queue, $job, $delay) 101 | { 102 | return $this->pushToDatabase($queue, json_decode($job->payload, true), $delay, $job->attempts); 103 | } 104 | 105 | /** 106 | * Push a raw payload to the database with a given delay. 107 | * 108 | * @param string $queue 109 | * @param array $payload 110 | * @param \DateTime|int $delay 111 | * @param int $attempts 112 | * @return mixed 113 | */ 114 | protected function pushToDatabase($queue, $payload, $delay = 0, $attempts = 0) 115 | { 116 | $uniqueId = isset($payload['unique_id']) 117 | ? $payload['unique_id'] 118 | : null; 119 | 120 | while (true) { 121 | try { 122 | return $this->database->table($this->table)->insertGetId($this->buildDatabaseRecord( 123 | $this->getQueue($queue), $payload, $this->availableAt($delay), $attempts 124 | )); 125 | } catch (QueryException $e) { 126 | $this->handleQueryException($e); 127 | } 128 | 129 | $query = $this->database->table($this->table) 130 | ->where('unique_id', $uniqueId) 131 | ->where('queue', $this->getQueue($queue)); 132 | 133 | if (count($results = $query->get(['id']))) { 134 | return $results[0]->id; 135 | } 136 | } 137 | } 138 | 139 | protected function handleQueryException(QueryException $e) 140 | { 141 | $driver = $this->database->getDriverName(); 142 | $ecode = intval($e->getCode()); 143 | 144 | if ($driver === 'pgsql' && in_array($ecode, [23505])) { 145 | // pass 146 | } elseif ($driver === 'sqlite' && in_array($ecode, [23000, 19, 2067])) { 147 | // pass 148 | } elseif ($driver === 'mysql' && in_array($ecode, [23000, 1062])) { 149 | // pass 150 | } elseif ($driver === 'sqlsrv' && in_array($ecode, [2601, 2627])) { 151 | // pass 152 | } else { 153 | throw $e; 154 | } 155 | } 156 | 157 | /** 158 | * Create a json string from the given array. 159 | * 160 | * @param string $job 161 | * @param mixed $data 162 | * @return string 163 | * 164 | * @throws \Illuminate\Queue\InvalidPayloadException 165 | */ 166 | protected function jsonize($data) 167 | { 168 | $json = json_encode($data); 169 | 170 | if (\JSON_ERROR_NONE !== json_last_error()) { 171 | throw new InvalidPayloadException( 172 | 'Unable to JSON encode payload. Error code: '.json_last_error() 173 | ); 174 | } 175 | 176 | return $json; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/Jobs/Uniqueable.php: -------------------------------------------------------------------------------- 1 | app->bind(IlluminateRedisConnector::class, UQueueRedisConnector::class); 16 | $this->app->bind(IlluminateDatabaseConnector::class, UQueueDatabaseConnector::class); 17 | parent::register(); 18 | } 19 | 20 | /** 21 | * Register the Redis queue connector. 22 | * 23 | * @param \Illuminate\Queue\QueueManager $manager 24 | * @return void 25 | */ 26 | protected function registerRedisConnector($manager) 27 | { 28 | $manager->addConnector('redis', function () { 29 | return new UQueueRedisConnector($this->app['redis']); 30 | }); 31 | } 32 | 33 | /** 34 | * Register the database queue connector. 35 | * 36 | * @param \Illuminate\Queue\QueueManager $manager 37 | * @return void 38 | */ 39 | protected function registerDatabaseConnector($manager) 40 | { 41 | $manager->addConnector('database', function () { 42 | return new UQueueDatabaseConnector($this->app['db']); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/LuaScripts.php: -------------------------------------------------------------------------------- 1 | 0 do 103 | -- Check that no one has reserved the job 104 | if (redis.call('zrem', KEYS[1], jobs[1]) > 0) then 105 | -- Increment the attempt count and place job on the reserved queue... 106 | local reserved = cjson.decode(jobs[1]) 107 | reserved['attempts'] = reserved['attempts'] + 1 108 | reserved = cjson.encode(reserved) 109 | redis.call('zadd', KEYS[2], ARGV[1], reserved) 110 | redis.call('lpop', KEYS[3]) 111 | return {jobs[1], reserved} 112 | end 113 | jobs = redis.call('zrange', KEYS[1], 0, 0) 114 | end 115 | 116 | return {false, false} 117 | LUA; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/LumenUQueueServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->availableBindings['queue']); 21 | unset($this->app->availableBindings['queue.connection']); 22 | unset($this->app->availableBindings['Illuminate\Contracts\Queue\Factory']); 23 | unset($this->app->availableBindings['Illuminate\Contracts\Queue\Queue']); 24 | 25 | $this->app->alias('queue', 'queue.connection'); 26 | $this->app->alias('queue', 'Illuminate\Contracts\Queue\Factory'); 27 | $this->app->alias('queue', 'Illuminate\Contracts\Queue\Queue'); 28 | 29 | $this->app->alias('redis', 'Illuminate\Contracts\Redis\Factory'); 30 | 31 | $this->app->bind(IlluminateRedisConnector::class, UQueueRedisConnector::class); 32 | $this->app->bind(IlluminateDatabaseConnector::class, UQueueDatabaseConnector::class); 33 | 34 | $this->app->configure('queue'); 35 | 36 | parent::registerManager(); 37 | } 38 | 39 | /** 40 | * Register the Redis queue connector. 41 | * 42 | * @param \Illuminate\Queue\QueueManager $manager 43 | * @return void 44 | */ 45 | protected function registerRedisConnector($manager) 46 | { 47 | $manager->addConnector('redis', function () { 48 | return new UQueueRedisConnector($this->app['redis']); 49 | }); 50 | } 51 | 52 | /** 53 | * Register the database queue connector. 54 | * 55 | * @param \Illuminate\Queue\QueueManager $manager 56 | * @return void 57 | */ 58 | protected function registerDatabaseConnector($manager) 59 | { 60 | $manager->addConnector('database', function () { 61 | return new UQueueDatabaseConnector($this->app['db']); 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/RedisQueue.php: -------------------------------------------------------------------------------- 1 | getQueue($queue); 18 | 19 | return $this->getConnection()->eval( 20 | LuaScripts::size(), 3, $queue, $queue.':delayed', $queue.':reserved' 21 | ); 22 | } 23 | 24 | /** 25 | * Push a raw payload onto the queue. 26 | * 27 | * @param string $payload 28 | * @param string|null $queue 29 | * @param array $options 30 | * @return mixed 31 | */ 32 | public function pushRaw($payload, $queue = null, array $options = []) 33 | { 34 | $queue = $this->getQueue($queue); 35 | 36 | $this->getConnection()->eval( 37 | LuaScripts::push(), 2, 38 | $queue, // KEY1 39 | $queue.':notify', // KEY2 40 | microtime(true), // ARGV1 41 | $payload // ARGV2 42 | ); 43 | 44 | return json_decode($payload, true)['id'] ?? null; 45 | } 46 | 47 | /** 48 | * Create a payload string from the given job and data. 49 | * 50 | * @param string $job 51 | * @param string $queue 52 | * @param mixed $data 53 | * @return array 54 | */ 55 | protected function createPayloadArray($job, $queue, $data = '') 56 | { 57 | $payload = parent::createPayloadArray($job, $queue, $data); 58 | 59 | if (is_object($job) && $job instanceof Uniqueable) { 60 | $payload['id'] = $job->uniqueable(); 61 | if (!empty($payload['uuid'])) { 62 | $payload['uuid'] = $this->uuid($payload['id']); 63 | } 64 | } 65 | 66 | return $payload; 67 | } 68 | 69 | /** 70 | * Migrate the delayed jobs that are ready to the regular queue. 71 | * 72 | * @param string $from 73 | * @param string $to 74 | * @return array 75 | */ 76 | public function migrateExpiredJobs($from, $to) 77 | { 78 | return $this->getConnection()->eval( 79 | LuaScripts::migrateExpiredJobs(), 3, 80 | $from, // KEY1 81 | $to, // KEY2 82 | $to.':notify', // KEY3 83 | $this->currentTime() // ARGV1 84 | ); 85 | } 86 | 87 | /** 88 | * Retrieve the next job from the queue. 89 | * 90 | * @param string $queue 91 | * @param bool $block 92 | * @return array 93 | */ 94 | protected function retrieveNextJob($queue, $block = true) 95 | { 96 | $nextJob = $this->getConnection()->eval( 97 | LuaScripts::pop(), 3, 98 | $queue, // KEYS1 99 | $queue.':reserved', // KEYS2 100 | $queue.':notify', // KEYS3 101 | $this->availableAt($this->retryAfter) // ARGV1 102 | ); 103 | 104 | if (empty($nextJob)) { 105 | return [null, null]; 106 | } 107 | 108 | [$job, $reserved] = $nextJob; 109 | 110 | if (! $job && ! is_null($this->blockFor) && $block && 111 | $this->getConnection()->blpop([$queue.':notify'], $this->blockFor)) { 112 | return $this->retrieveNextJob($queue, false); 113 | } 114 | 115 | return [$job, $reserved]; 116 | } 117 | 118 | protected function uuid(string $seed): string 119 | { 120 | $data = substr(sha1($seed), -16); 121 | 122 | $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100 123 | $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10 124 | 125 | return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4)); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/LaravelTest.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 24 | $app->register(RedisServiceProvider::class); 25 | $app->register(LaravelUQueueServiceProvider::class); 26 | return $app; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/LumenTest.php: -------------------------------------------------------------------------------- 1 | bind('path.storage', function () { 27 | return '/tmp'; 28 | }); 29 | 30 | $app->alias('db', 'Illuminate\Database\ConnectionResolverInterface'); 31 | 32 | $app->singleton( 33 | \Illuminate\Contracts\Debug\ExceptionHandler::class, 34 | \Laravel\Lumen\Exceptions\Handler::class 35 | ); 36 | $app->singleton( 37 | \Illuminate\Contracts\Console\Kernel::class, 38 | \Laravel\Lumen\Console\Kernel::class 39 | ); 40 | 41 | $app->withFacades(); 42 | 43 | $app->register(RedisServiceProvider::class); 44 | 45 | $app->register(LumenUQueueServiceProvider::class); 46 | 47 | $app->configure('database'); 48 | 49 | return $app; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/PackageTest.php: -------------------------------------------------------------------------------- 1 | migrateDown(); 17 | } 18 | parent::tearDown(); 19 | } 20 | 21 | protected function migrateDown(): void 22 | { 23 | (new \JobsAddUniqueable())->down(); 24 | (new \CreateJobsTable())->down(); 25 | } 26 | 27 | protected function migrateUp(): void 28 | { 29 | (new \CreateJobsTable())->up(); 30 | (new \JobsAddUniqueable())->up(); 31 | } 32 | 33 | protected function setUpSqlite(): void 34 | { 35 | config(['database.default' => 'sqlite']); 36 | config(['database.connections.sqlite.database' => ':memory:']); 37 | config(['queue.default' => 'database']); 38 | 39 | DB::setDefaultConnection('sqlite'); 40 | 41 | $this->migrateDown(); 42 | $this->migrateUp(); 43 | 44 | Queue::setDefaultDriver('database'); 45 | } 46 | 47 | protected function setUpPgsql(): void 48 | { 49 | config(['database.default' => 'pgsql']); 50 | config(['database.connections.pgsql.database' => 'postgres']); 51 | config(['database.connections.pgsql.username' => 'postgres']); 52 | config(['database.connections.pgsql.password' => 'postgres']); 53 | config(['database.connections.pgsql.port' => '54320']); 54 | config(['queue.default' => 'database']); 55 | 56 | DB::setDefaultConnection('pgsql'); 57 | 58 | $this->migrateDown(); 59 | $this->migrateUp(); 60 | 61 | Queue::setDefaultDriver('database'); 62 | } 63 | 64 | protected function setUpMysql(): void 65 | { 66 | config(['database.default' => 'mysql']); 67 | config(['database.connections.mysql.database' => 'mysql']); 68 | config(['database.connections.mysql.username' => 'mysql']); 69 | config(['database.connections.mysql.password' => 'mysql']); 70 | config(['database.connections.mysql.port' => '33060']); 71 | config(['database.connections.mysql.version' => 8]); 72 | config(['database.connections.mysql.modes' => [ 73 | 'ONLY_FULL_GROUP_BY', 74 | 'STRICT_TRANS_TABLES', 75 | 'NO_ZERO_IN_DATE', 76 | 'NO_ZERO_DATE', 77 | 'ERROR_FOR_DIVISION_BY_ZERO', 78 | 'NO_ENGINE_SUBSTITUTION', 79 | ]]); 80 | 81 | config(['queue.default' => 'database']); 82 | 83 | DB::setDefaultConnection('mysql'); 84 | 85 | $this->migrateDown(); 86 | $this->migrateUp(); 87 | 88 | Queue::setDefaultDriver('database'); 89 | } 90 | 91 | public function testSimpleDatabaseJobSqlite() 92 | { 93 | $this->setUpSqlite(); 94 | 95 | $id1 = Queue::push(new SimpleJob(['foo' => 'bar'])); 96 | $id2 = Queue::push(new SimpleJob(['foo' => 'bar'])); 97 | 98 | $this->assertNotNull($id1); 99 | $this->assertNotNull($id2); 100 | $this->assertNotSame((string) $id1, (string) $id2); 101 | 102 | $this->assertCount(2, DB::select('SELECT * FROM jobs')); 103 | 104 | SimpleJob::$test = null; 105 | 106 | /** @var DatabaseJob $job */ 107 | $job = Queue::pop(); 108 | 109 | $job->fire(); 110 | 111 | $this->assertTrue(SimpleJob::$test); 112 | } 113 | 114 | public function testUniqueableDatabaseJobSqlite() 115 | { 116 | $this->setUpSqlite(); 117 | 118 | $id1 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 119 | $id2 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 120 | 121 | $this->assertNotNull($id1); 122 | $this->assertNotNull($id2); 123 | $this->assertSame((string) $id1, (string) $id2); 124 | 125 | $this->assertCount(1, DB::select('SELECT * FROM jobs')); 126 | 127 | $id3 = Queue::push(new UniqueableJob(['foo2' => 'bar2'])); 128 | 129 | $this->assertNotSame($id1, $id3); 130 | 131 | $this->assertCount(2, DB::select('SELECT * FROM jobs')); 132 | 133 | UniqueableJob::$test = null; 134 | 135 | /** @var DatabaseJob $job */ 136 | $job = Queue::pop(); 137 | 138 | $job->fire(); 139 | 140 | $this->assertTrue(UniqueableJob::$test); 141 | } 142 | 143 | public function testSimpleDatabaseJobPgsql() 144 | { 145 | $this->setUpPgsql(); 146 | 147 | $id1 = Queue::push(new SimpleJob(['foo' => 'bar'])); 148 | $id2 = Queue::push(new SimpleJob(['foo' => 'bar'])); 149 | 150 | $this->assertNotNull($id1); 151 | $this->assertNotNull($id2); 152 | $this->assertNotSame((string) $id1, (string) $id2); 153 | 154 | $this->assertCount(2, DB::select('SELECT * FROM jobs')); 155 | 156 | SimpleJob::$test = null; 157 | 158 | /** @var DatabaseJob $job */ 159 | $job = Queue::pop(); 160 | 161 | $job->fire(); 162 | 163 | $this->assertTrue(SimpleJob::$test); 164 | } 165 | 166 | public function testUniqueableDatabaseJobPgsql() 167 | { 168 | $this->setUpPgsql(); 169 | 170 | $id1 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 171 | $id2 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 172 | 173 | $this->assertNotNull($id1); 174 | $this->assertNotNull($id2); 175 | $this->assertSame((string) $id1, (string) $id2); 176 | 177 | $this->assertCount(1, DB::select('SELECT * FROM jobs')); 178 | 179 | $id3 = Queue::push(new UniqueableJob(['foo2' => 'bar2'])); 180 | 181 | $this->assertNotSame($id1, $id3); 182 | 183 | $this->assertCount(2, DB::select('SELECT * FROM jobs')); 184 | 185 | UniqueableJob::$test = null; 186 | 187 | /** @var DatabaseJob $job */ 188 | $job = Queue::pop(); 189 | 190 | $job->fire(); 191 | 192 | $this->assertTrue(UniqueableJob::$test); 193 | } 194 | 195 | public function testSimpleDatabaseJobMysql() 196 | { 197 | $this->setUpMysql(); 198 | 199 | $id1 = Queue::push(new SimpleJob(['foo' => 'bar'])); 200 | $id2 = Queue::push(new SimpleJob(['foo' => 'bar'])); 201 | 202 | $this->assertNotNull($id1); 203 | $this->assertNotNull($id2); 204 | $this->assertNotSame((string) $id1, (string) $id2); 205 | 206 | $this->assertCount(2, DB::select('SELECT * FROM jobs')); 207 | 208 | SimpleJob::$test = null; 209 | 210 | /** @var DatabaseJob $job */ 211 | $job = Queue::pop(); 212 | 213 | $job->fire(); 214 | 215 | $this->assertTrue(SimpleJob::$test); 216 | } 217 | 218 | public function testUniqueableDatabaseJobMysql() 219 | { 220 | $this->setUpMysql(); 221 | 222 | $id1 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 223 | $id2 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 224 | 225 | $this->assertNotNull($id1); 226 | $this->assertNotNull($id2); 227 | $this->assertSame((string) $id1, (string) $id2); 228 | 229 | $this->assertCount(1, DB::select('SELECT * FROM jobs')); 230 | 231 | $id3 = Queue::push(new UniqueableJob(['foo2' => 'bar2'])); 232 | 233 | $this->assertNotSame($id1, $id3); 234 | 235 | $this->assertCount(2, DB::select('SELECT * FROM jobs')); 236 | 237 | UniqueableJob::$test = null; 238 | 239 | /** @var DatabaseJob $job */ 240 | $job = Queue::pop(); 241 | 242 | $job->fire(); 243 | 244 | $this->assertTrue(UniqueableJob::$test); 245 | } 246 | 247 | public function testSimpleRedisJob() 248 | { 249 | Queue::setDefaultDriver('redis'); 250 | 251 | Redis::connection()->command('DEL', ['queues:default']); 252 | 253 | $id1 = Queue::push(new SimpleJob(['foo' => 'bar'])); 254 | $id2 = Queue::push(new SimpleJob(['foo' => 'bar'])); 255 | 256 | $this->assertNotNull($id1); 257 | $this->assertNotNull($id2); 258 | $this->assertNotSame($id1, $id2); 259 | 260 | $this->assertCount(2, Redis::connection()->command('ZRANGE', ['queues:default', 0, -1])); 261 | 262 | SimpleJob::$test = null; 263 | 264 | /** @var RedisJob $job */ 265 | $job = Queue::pop(); 266 | 267 | $job->fire(); 268 | 269 | $this->assertTrue(SimpleJob::$test); 270 | } 271 | 272 | public function testUniqueableRedisJob() 273 | { 274 | Queue::setDefaultDriver('redis'); 275 | 276 | Redis::connection()->command('DEL', ['queues:default']); 277 | 278 | $this->assertCount(0, Redis::connection()->command('ZRANGE', ['queues:default', 0, -1])); 279 | 280 | $id1 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 281 | $id2 = Queue::push(new UniqueableJob(['foo' => 'bar'])); 282 | 283 | $this->assertNotNull($id1); 284 | $this->assertNotNull($id2); 285 | $this->assertSame($id1, $id2); 286 | 287 | $this->assertCount(1, Redis::connection()->command('ZRANGE', ['queues:default', 0, -1])); 288 | 289 | $id3 = Queue::push(new UniqueableJob(['foo2' => 'bar2'])); 290 | 291 | $this->assertNotSame($id1, $id3); 292 | 293 | $this->assertCount(2, Redis::connection()->command('ZRANGE', ['queues:default', 0, -1])); 294 | 295 | UniqueableJob::$test = null; 296 | 297 | /** @var RedisJob $job */ 298 | $job = Queue::pop(); 299 | 300 | $job->fire(); 301 | 302 | $this->assertTrue(UniqueableJob::$test); 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /tests/SimpleJob.php: -------------------------------------------------------------------------------- 1 | data = $data; 22 | } 23 | 24 | public function handle() 25 | { 26 | static::$test = true; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/UniqueableJob.php: -------------------------------------------------------------------------------- 1 | data = $data; 23 | } 24 | 25 | public function uniqueable() 26 | { 27 | return md5(json_encode($this->data)); 28 | } 29 | 30 | public function handle() 31 | { 32 | static::$test = true; 33 | } 34 | } 35 | --------------------------------------------------------------------------------