├── Job.php ├── Queue.php ├── README.md ├── RedisQueue.php ├── SqlQueue.php ├── composer.json └── console └── controllers └── QueueController.php /Job.php: -------------------------------------------------------------------------------- 1 | queueObject = $queueObject; 18 | $this->payload = $payload; 19 | $this->queueName = $queueName; 20 | } 21 | 22 | public function run() 23 | { 24 | $this->resolveAndRun(json_decode($this->payload, true)); 25 | } 26 | 27 | public function getQueueObject() 28 | { 29 | return $this->queueObject; 30 | } 31 | 32 | protected function resolveAndRun(array $payload) 33 | { 34 | list($class, $method) = $this->resolveJob($payload['job']); 35 | $instance = Yii::createObject([ 36 | 'class' => $class 37 | ]); 38 | $instance->{$method}($this, $payload['data']); 39 | } 40 | 41 | protected function resolveJob($job) 42 | { 43 | $segments = explode('@', $job); 44 | return count($segments) > 1 ? $segments : array($segments[0], 'run'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Queue.php: -------------------------------------------------------------------------------- 1 | queuePrefix . ':' . $name; 33 | } 34 | 35 | /** 36 | * Push job to the queue 37 | * 38 | * @param string $job Fully qualified class name of the job 39 | * @param mixed $data Data for the job 40 | * @param string|null $queue Queue name 41 | * @return string ID of the job 42 | */ 43 | public function push($job, $data = null, $queue = null, $options = []) 44 | { 45 | return $this->pushInternal($this->createPayload($job, $data), $queue, $options); 46 | } 47 | 48 | /** 49 | * Get job from the queue 50 | * 51 | * @param string|null $queue Queue name 52 | * @return mixed 53 | */ 54 | public function pop($queue = null) 55 | { 56 | return $this->popInternal($queue); 57 | } 58 | 59 | /** 60 | * Create job array 61 | * 62 | * @param string $job Fully qualified class name of the job 63 | * @param mixed $data Data for the job 64 | * @return array 65 | */ 66 | protected function createPayload($job, $data) 67 | { 68 | $payload = [ 69 | 'job' => $job, 70 | 'data' => $data 71 | ]; 72 | $payload = $this->setMeta($payload, 'id', $this->getRandomId()); 73 | 74 | return $payload; 75 | } 76 | 77 | /** 78 | * Set additional meta on a payload string. 79 | * 80 | * @param string $payload 81 | * @param string $key 82 | * @param string $value 83 | * @return string 84 | */ 85 | protected function setMeta($payload, $key, $value) 86 | { 87 | $payload[$key] = $value; 88 | 89 | return json_encode($payload); 90 | } 91 | 92 | /** 93 | * Get random ID. 94 | * 95 | * @return string 96 | */ 97 | protected function getRandomId() 98 | { 99 | return Yii::$app->security->generateRandomString(); 100 | } 101 | 102 | /** 103 | * Get prefixed queue name 104 | * 105 | * @param $queue Queue name 106 | * @return string 107 | */ 108 | protected function getQueue($queue) 109 | { 110 | return $this->buildPrefix($queue); 111 | } 112 | 113 | /** 114 | * Class-specific realisation of adding the job to the queue 115 | * 116 | * @param array $payload Job data 117 | * @param string|null $queue Queue name 118 | * @param array $options 119 | * 120 | * @return mixed 121 | */ 122 | abstract protected function pushInternal($payload, $queue = null, $options = []); 123 | 124 | /** 125 | * Class-specific realisation of getting the job to the queue 126 | * 127 | * @param string|null $queue Queue name 128 | * 129 | * @return mixed 130 | */ 131 | abstract protected function popInternal($queue = null); 132 | } 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Queue component for Yii2 2 | ==================== 3 | This component provides simple queue wrapper 4 | 5 | I recommend [yii2-asynctask](https://github.com/wayhood/yii2-asynctask). 6 | 7 | Requirements 8 | ------------ 9 | 10 | [Redis](http://redis.io) 11 | [yii2-redis](https://github.com/yiisoft/yii2-redis) 12 | 13 | Installation 14 | ------------ 15 | 16 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 17 | 18 | Either run 19 | 20 | ``` 21 | php composer.phar require --prefer-dist wayhood/yii2-queue "*" 22 | ``` 23 | 24 | or add 25 | 26 | ``` 27 | "wayhood/yii2-queue": "*" 28 | ``` 29 | 30 | to the require section of your `composer.json` file. 31 | 32 | 33 | Usage 34 | ----- 35 | 36 | To use this extension, simply add the following code in your application configuration: 37 | 38 | ```php 39 | return [ 40 | //.... 41 | 'components' => [ 42 | 'queue' => [ 43 | 'class' => 'wh\queue\RedisQueue', 44 | ], 45 | 'redis' => [ 46 | 'class' => 'yii\redis\Connection', 47 | 'hostname' => 'localhost', 48 | 'port' => 6379, 49 | 'database' => 0 50 | ], 51 | ], 52 | ]; 53 | ``` 54 | 55 | 56 | 57 | First create a Job process class 58 | 59 | ```php 60 | namespace console\jobs; 61 | 62 | class MyJob 63 | { 64 | public function run($job, $data) 65 | { 66 | //process $data; 67 | var_dump($data); 68 | } 69 | } 70 | ``` 71 | 72 | and than just push job to queue 73 | 74 | ```php 75 | // Push job to the default queue and execute "run" method 76 | Yii::$app->queue->push('\console\jobs\MyJob', ['a', 'b', 'c']); 77 | 78 | // or push it and execute any other method 79 | Yii::$app->queue->push('\console\jobs\MyJob@myMethod', ['a', 'b', 'c']); 80 | 81 | // or push it to some specific queue 82 | Yii::$app->queue->push('\console\jobs\MyJob', ['a', 'b', 'c'], 'myQueue'); 83 | 84 | // or both 85 | Yii::$app->queue->push('\console\jobs\MyJob@myMethod', ['a', 'b', 'c'], 'myQueue'); 86 | 87 | ``` 88 | 89 | Map console controller in your app config 90 | 91 | ```php 92 | return [ 93 | ... 94 | 'controllerMap' => [ 95 | 'queue' => 'wh\queue\console\controllers\QueueController' 96 | ], 97 | ... 98 | ]; 99 | ``` 100 | 101 | Examples of using queue controller: 102 | 103 | ``` 104 | # Process a job from default queue and than exit the process 105 | ./yii queue/work 106 | 107 | # continuously process jobs from default queue 108 | ./yii queue/listen 109 | 110 | # process a job from specific queue and than exit the process 111 | ./yii queue/work myQueue 112 | 113 | # continuously process jobs from specific queue 114 | ./yii queue/listen myQueue 115 | ``` 116 | -------------------------------------------------------------------------------- /RedisQueue.php: -------------------------------------------------------------------------------- 1 | redis)) { 25 | $this->redis = Yii::$app->get($this->redis); 26 | } elseif (is_array($this->redis)) { 27 | $this->redis = Yii::createObject($this->redis); 28 | } 29 | if (!$this->redis instanceof Connection) { 30 | throw new InvalidConfigException("Queue::redis must be either a Redis connection instance or the application component ID of a Redis connection."); 31 | } 32 | } 33 | 34 | protected function pushInternal($payload, $queue = null, $options = []) 35 | { 36 | $this->redis->rpush($this->getQueue($queue), $payload); 37 | $payload = json_decode($payload, true); 38 | 39 | return $payload['id']; 40 | } 41 | 42 | 43 | public function popInternal($queue = null) 44 | { 45 | $payload = $this->redis->lpop($this->getQueue($queue)); 46 | if ($payload) { 47 | //$this->redis->zadd($queue.':reserved', $this->getTime() + 60, $job); 48 | return new Job($this, $payload, $queue); 49 | } 50 | 51 | return null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /SqlQueue.php: -------------------------------------------------------------------------------- 1 | connection)) { 28 | $this->connection = Yii::$app->get($this->connection); 29 | } elseif (is_array($this->connection)) { 30 | if (!isset($this->connection['class'])) { 31 | $this->connection['class'] = Connection::className(); 32 | } 33 | $this->connection = Yii::createObject($this->connection); 34 | } 35 | 36 | if (!$this->connection instanceof Connection) { 37 | throw new InvalidConfigException("Queue::connection must be application component ID of a SQL connection."); 38 | } 39 | 40 | if (!$this->hasTable()) { 41 | $this->createTable(); 42 | } 43 | } 44 | 45 | private function hasTable() 46 | { 47 | $schema=$this->connection->schema->getTableSchema($this->getTableName(), true); 48 | if ($schema==null) { 49 | return false; 50 | } 51 | if ($schema->columns['id']->comment!=='1.0.0') { 52 | $this->dropTable(); 53 | return false; 54 | } 55 | return true; 56 | } 57 | 58 | private function createTable() 59 | { 60 | $this->connection->createCommand()->createTable($this->getTableName(), [ 61 | 'id' => 'pk COMMENT "1.0.0"', 62 | 'queue' => 'string(255)', 63 | 'run_at' => 'timestamp default CURRENT_TIMESTAMP NOT NULL', 64 | 'payload' => 'text', 65 | ])->execute(); 66 | $this->connection->schema->refresh(); 67 | } 68 | 69 | public function dropTable() 70 | { 71 | $this->connection->createCommand()->dropTable($this->getTableName())->execute(); 72 | } 73 | 74 | private function getTableName() 75 | { 76 | return $this->default.'_queue'; 77 | } 78 | 79 | protected function pushInternal($payload, $queue = null, $options = []) 80 | { 81 | if (isset($options['run_at']) && ($options['run_at'] instanceof \DateTime)) { 82 | $run_at=$options['run_at']; 83 | } else { 84 | $run_at=new \DateTime; 85 | } 86 | 87 | $this->connection->schema->insert($this->getTableName(), [ 88 | 'queue' => $this->getQueue($queue), 89 | 'payload' => $payload, 90 | 'run_at' => new Expression('FROM_UNIXTIME(:unixtime)', [ 91 | ':unixtime' => $run_at->format('U') 92 | ]) 93 | ]); 94 | 95 | $payload = json_decode($payload, true); 96 | 97 | 98 | return $payload['id']; 99 | } 100 | 101 | protected function getQueueInternal($queue = null) 102 | { 103 | return ($queue ?: $this->default); 104 | } 105 | 106 | 107 | private function getQuery($queue) 108 | { 109 | if ($this->_query) { 110 | return $this->_query; 111 | } 112 | 113 | $this->_query=new Query; 114 | $this->_query->select('id, payload') 115 | ->from($this->getTableName()) 116 | ->where(array('queue'=>$queue)) 117 | ->andWhere('run_at <= NOW()') 118 | ->limit(1); 119 | 120 | return $this->_query; 121 | } 122 | private function deleteQueue($id) 123 | { 124 | $this->connection->createCommand()->delete($this->getTableName(), 'id=:id', [':id'=>$id])->execute(); 125 | } 126 | 127 | public function popInternal($queue = null) 128 | { 129 | $row=$this->getQuery($this->getQueue($queue))->one($this->connection); 130 | if ($row) { 131 | $this->deleteQueue($row['id']); 132 | return new Job($this, $row['payload'], $queue); 133 | } 134 | return null; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wayhood/yii2-queue", 3 | "description": "yii2 queue component", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","queue"], 6 | "license": "BSD-4-Clause", 7 | "authors": [ 8 | { 9 | "name": "Song Yeung", 10 | "email": "netyum@163.com" 11 | } 12 | ], 13 | "require": { 14 | "yiisoft/yii2": "*", 15 | "yiisoft/yii2-redis": "*" 16 | }, 17 | "autoload": { 18 | "psr-4": { 19 | "wh\\queue\\": "" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /console/controllers/QueueController.php: -------------------------------------------------------------------------------- 1 | process($queueName, $queueObjectName); 30 | } 31 | 32 | /** 33 | * Continuously process jobs 34 | * 35 | * @param string $queueName 36 | * @param string $queueObjectName 37 | * @throws \Exception 38 | */ 39 | public function actionListen($queueName = null, $queueObjectName = 'queue') 40 | { 41 | while (true) { 42 | if ($this->timeout !==null) { 43 | if ($this->timeoutprocess($queueName, $queueObjectName)) { 48 | sleep($this->sleep); 49 | } 50 | 51 | } 52 | } 53 | 54 | protected function process($queueName, $queueObjectName) 55 | { 56 | $queue = Yii::$app->{$queueObjectName}; 57 | $job = $queue->pop($queueName); 58 | 59 | if ($job) { 60 | try { 61 | $job->run(); 62 | return true; 63 | } catch (\Exception $e) { 64 | if ($queue->debug) { 65 | var_dump($e); 66 | } 67 | 68 | Yii::error($e->getMessage(), __METHOD__); 69 | } 70 | } 71 | return false; 72 | } 73 | 74 | public function beforeAction($action) 75 | { 76 | if (!parent::beforeAction($action)) { 77 | return false; 78 | } 79 | 80 | if (getenv('QUEUE_TIMEOUT')) { 81 | $this->timeout=(int)getenv('QUEUE_TIMEOUT')+time(); 82 | } 83 | if (getenv('QUEUE_SLEEP')) { 84 | $this->sleep=(int)getenv('QUEUE_SLEEP'); 85 | } 86 | return true; 87 | } 88 | } 89 | --------------------------------------------------------------------------------