├── .gitignore ├── src ├── Queue │ ├── Traits │ │ └── UniquelyQueueable.php │ ├── Connectors │ │ ├── RedisUniqueConnector.php │ │ └── HorizonUniqueConnector.php │ ├── HorizonUniqueQueue.php │ └── RedisUniqueQueue.php └── Providers │ ├── QueueServiceProvider.php │ └── LumenQueueServiceProvider.php ├── composer.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | /.vscode -------------------------------------------------------------------------------- /src/Queue/Traits/UniquelyQueueable.php: -------------------------------------------------------------------------------- 1 | redis, $config['queue'], 22 | $config['connection'] ?? $this->connection, 23 | $config['retry_after'] ?? 60, 24 | $config['block_for'] ?? null 25 | ); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/Queue/Connectors/HorizonUniqueConnector.php: -------------------------------------------------------------------------------- 1 | redis, $config['queue'], 22 | Arr::get($config, 'connection', $this->connection), 23 | Arr::get($config, 'retry_after', 60), 24 | Arr::get($config, 'block_for', null) 25 | ); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/Providers/QueueServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->resolving('queue', function ($manager) { 20 | $manager->addConnector('unique', function () { 21 | if (defined('HORIZON_PATH')) { 22 | return new HorizonUniqueConnector($this->app['redis']); 23 | } 24 | 25 | return new RedisUniqueConnector($this->app['redis']); 26 | }); 27 | }); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mlntn/laravel-unique-queue", 3 | "type": "library", 4 | "description": "Laravel queue connection that prevents identical jobs from being queued", 5 | "keywords": ["laravel", "queue", "unique"], 6 | "homepage": "http://github.com/mlntn/laravel-unique-queue", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Jared Mellentine", 11 | "email": "jared@mellentine.com" 12 | } 13 | ], 14 | "minimum-stability": "stable", 15 | 16 | "require": { 17 | "php": "~7.1", 18 | "illuminate/queue": "~5.7||~6||~7||~8", 19 | "illuminate/redis": "~5.7||~6||~7||~8" 20 | }, 21 | "autoload": { 22 | "classmap": ["src/"] 23 | }, 24 | "extra": { 25 | "laravel": { 26 | "providers": [ 27 | "Mlntn\\Providers\\QueueServiceProvider" 28 | ] 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Providers/LumenQueueServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->resolving('queue', function (QueueManager $manager) { 28 | $manager->addConnector('unique', function () { 29 | return new RedisUniqueConnector($this->app['redis']); 30 | }); 31 | }); 32 | } 33 | } -------------------------------------------------------------------------------- /src/Queue/HorizonUniqueQueue.php: -------------------------------------------------------------------------------- 1 | getConnection(); 23 | 24 | $tracker = $this->getTrackerName($queue); 25 | 26 | $data = json_decode($payload, true); 27 | 28 | $exists = $connection->hexists($tracker, $data['uniqueIdentifier']); 29 | 30 | if ($exists) { 31 | return null; 32 | } 33 | 34 | if (parent::pushRaw($payload, $queue, $options)) { 35 | $connection->hset($tracker, $data['uniqueIdentifier'], $data['id']); 36 | } 37 | } 38 | 39 | /** 40 | * Create a payload for an object-based queue handler. 41 | * 42 | * @param mixed $job 43 | * @return array 44 | */ 45 | protected function createObjectPayload($job,$queue) 46 | { 47 | return array_merge([ 48 | 'uniqueIdentifier' => isset($job->class) 49 | ? (new $job->class)->getUniqueIdentifier() 50 | : $job->getUniqueIdentifier(), 51 | ], parent::createObjectPayload($job,$queue)); 52 | } 53 | 54 | /** 55 | * Delete a reserved job from the queue. 56 | * 57 | * @param string $queue 58 | * @param RedisJob $job 59 | * @return void 60 | */ 61 | public function deleteReserved($queue, $job) 62 | { 63 | parent::deleteReserved($queue, $job); 64 | 65 | $data = json_decode($job->getRawBody(), true); 66 | 67 | $this->getConnection()->hdel($this->getTrackerName($queue), $data['uniqueIdentifier']); 68 | } 69 | 70 | protected function getTrackerName($queue) 71 | { 72 | return $this->getQueue($queue).':tracker'; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/Queue/RedisUniqueQueue.php: -------------------------------------------------------------------------------- 1 | getConnection(); 23 | 24 | $tracker = $this->getTrackerName($queue); 25 | 26 | $data = json_decode($payload, true); 27 | 28 | $exists = $connection->hexists($tracker, $data['uniqueIdentifier']); 29 | 30 | if ($exists) { 31 | return null; 32 | } 33 | 34 | if (parent::pushRaw($payload, $queue, $options)) { 35 | $connection->hset($tracker, $data['uniqueIdentifier'], $data['id']); 36 | } 37 | } 38 | 39 | /** 40 | * Create a payload for an object-based queue handler. 41 | * 42 | * @param mixed $job 43 | * @return array 44 | */ 45 | protected function createObjectPayload($job,$queue) 46 | { 47 | return array_merge([ 48 | 'uniqueIdentifier' => isset($job->class) 49 | ? (new $job->class)->getUniqueIdentifier() 50 | : $job->getUniqueIdentifier(), 51 | ], parent::createObjectPayload($job,$queue)); 52 | } 53 | 54 | /** 55 | * Delete a reserved job from the queue. 56 | * 57 | * @param string $queue 58 | * @param RedisJob $job 59 | * @return void 60 | */ 61 | public function deleteReserved($queue, $job) 62 | { 63 | parent::deleteReserved($queue, $job); 64 | 65 | $data = json_decode($job->getRawBody(), true); 66 | 67 | $this->getConnection()->hdel($this->getTrackerName($queue), $data['uniqueIdentifier']); 68 | } 69 | 70 | protected function getTrackerName($queue) 71 | { 72 | return $this->getQueue($queue).':tracker'; 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | :warning: **DEPRECATED**: 2 | 3 | [Unique jobs have been implemented in laravel/framework](https://github.com/laravel/framework/releases/tag/v8.14.0). Please use the official implementation in Laravel 8.14.0 or higher. No further updates will be made to this repository and it will be archived in the future. 4 | 5 | # Laravel Unique Queue 6 | 7 | This redis queue driver works just like the standard Laravel redis queue driver, however, it prevents the same job from being queued multiple times. 8 | 9 | ## Requirements 10 | 11 | Needs PHP >= 7.1 to be installed. 12 | 13 | Requires `illuminate/redis` and `illuminate/queue`, both `"~5.7"`, `"~6"` or `"~7"` 14 | 15 | ## Installation 16 | 17 | ### Require via Composer 18 | ``` 19 | composer require mlntn/laravel-unique-queue 20 | ``` 21 | 22 | 23 | ### Configure 24 | Create a new connection in `config/queue.php` 25 | 26 | ``` 27 | return [ 28 | // ... 29 | 'connections' => [ 30 | 'my_unique_queue_connection_name' => [ 31 | 'driver' => 'unique', 32 | 'connection' => 'default', 33 | 'queue' => env('UNIQUE_QUEUE_NAME', 'my_unique_queue_name'), 34 | 'retry_after' => 90, 35 | ], 36 | //... 37 | ] 38 | ]; 39 | ``` 40 | 41 | ## Implementation 42 | 43 | ### Implement a uniquely-queueable job 44 | 45 | Your job should use the UniquelyQueueable trait and have the getUniqueIdentifier method: 46 | 47 | onConnection('my_unique_queue_connection_name'); 74 | 75 | 76 | ### Implement a unique-queueable listener 77 | 78 | Just like with a job the listener class should use the UniquelyQueueable trait and make sure you've set the connection and queue: 79 | 80 | register(Mlntn\Providers\LumenQueueServiceProvider::class); 110 | ``` 111 | 112 | 113 | ## Using Horizon 114 | 115 | Set up a worker configuration: 116 | ``` 117 | 'worker_name' => [ 118 | 'connection' => 'my_unique_queue_connection_name', 119 | 'queue' => ['default'], 120 | 'balance' => 'auto', 121 | 'processes' => 16, 122 | 'tries' => 3, 123 | ], 124 | ``` 125 | 126 | ## Run Queue Worker 127 | The unique queue behavior acts internally. Run queue worker as known using artisan (via cli, supervisor or another method). 128 | For more detailed information head over to https://github.com/illuminate/queue 129 | Specify the connection name used in `config/queue.php` 130 | 131 | ``` 132 | php artisan queue:work my_unique_queue_connection_name 133 | ``` 134 | --------------------------------------------------------------------------------