├── .dockerignore ├── .editorconfig ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.markdown ├── LICENSE ├── README.md ├── composer.json ├── config ├── schema-mysql.sql └── schema-pgsql.sql ├── docs ├── callbacks.md ├── defining-jobs.md ├── index.md ├── installation.md ├── job-options.md ├── pop-order.md ├── simple-usage.md └── supported-systems.md ├── example.php ├── phpunit.xml.dist ├── src └── josegonzalez │ └── Queuesadilla │ ├── Engine │ ├── Base.php │ ├── BeanstalkEngine.php │ ├── EngineInterface.php │ ├── IronEngine.php │ ├── MemoryEngine.php │ ├── MysqlEngine.php │ ├── NullEngine.php │ ├── PdoEngine.php │ ├── PostgresEngine.php │ ├── PredisEngine.php │ ├── RedisEngine.php │ └── SynchronousEngine.php │ ├── Event │ ├── Event.php │ ├── EventListenerInterface.php │ ├── EventManagerTrait.php │ └── MultiEventListener.php │ ├── Job │ ├── Base.php │ └── BeanstalkJob.php │ ├── Queue.php │ ├── Utility │ ├── DsnParserTrait.php │ ├── LoggerTrait.php │ ├── Pheanstalk.php │ └── SettingTrait.php │ └── Worker │ ├── Base.php │ ├── Listener │ ├── DummyListener.php │ └── StatsListener.php │ ├── SequentialWorker.php │ └── TestWorker.php └── tests ├── bootstrap.php └── josegonzalez └── Queuesadilla ├── Engine ├── AbstractPdoEngineTest.php ├── BeanstalkEngineTest.php ├── IronEngineTest.php ├── MemoryEngineTest.php ├── MysqlEngineTest.php ├── NullEngineTest.php ├── PostgresEngineTest.php ├── PredisEngineTest.php ├── RedisEngineTest.php └── SynchronousEngineTest.php ├── Event └── EventTest.php ├── FixtureData.php ├── Job └── BaseTest.php ├── QueueTest.php ├── TestCase.php ├── Utility ├── DsnParserTraitTest.php └── LoggerTraitTest.php └── Worker ├── Listener └── StatsListenerTest.php ├── SequentialWorkerTest.php └── TestWorkerTest.php /.dockerignore: -------------------------------------------------------------------------------- 1 | .dockerignore 2 | Dockerfile 3 | vendor/* 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | root = true 5 | 6 | [*] 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | # Spaces in coffee 13 | [**.coffee] 14 | indent_style = space 15 | indent_size = 2 16 | 17 | [**.js] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | # Tabs in less 22 | [**.less] 23 | indent_style = tab 24 | indent_size = 2 25 | 26 | [**.css] 27 | indent_style = tab 28 | indent_size = 2 29 | 30 | [**.php] 31 | indent_style = space 32 | indent_size = 4 33 | 34 | [**.html] 35 | indent_style = tab 36 | indent_size = 2 37 | 38 | [Makefile] 39 | indent_style = tab 40 | indent_size = 4 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor 2 | composer.lock 3 | .DS_Store 4 | build 5 | 6 | setup.sh -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | services: 4 | - mysql 5 | - postgres 6 | # - rabbitmq 7 | - redis-server 8 | 9 | php: 10 | - 7.2 11 | - 7.3 12 | 13 | env: 14 | global: 15 | - BEANSTALK_URL="beanstalk://127.0.0.1:11300?queue=default&timeout=1" 16 | - MEMORY_URL="memory:///?queue=default&timeout=1" 17 | - MYSQL_URL="mysql://travis@127.0.0.1:3306/database_name?queue=default&timeout=1" 18 | - NULL_URL="null:///?queue=default&timeout=1" 19 | - REDIS_URL="redis://travis@127.0.0.1:6379/0?queue=default&timeout=1" 20 | - MEMORY_URL="synchronous:///?queue=default&timeout=1" 21 | - POSTGRES_URL="pgsql://postgres@127.0.0.1:5432/database_name?queue=default" 22 | 23 | before_script: 24 | - sudo apt-get update 25 | # beanstalkd 26 | - sudo apt-get install -qq --force-yes beanstalkd 27 | 28 | # mysql 29 | - mysql -e 'CREATE DATABASE database_name;' 30 | - mysql database_name < config/schema-mysql.sql 31 | 32 | # postgres 33 | - psql -c "CREATE DATABASE database_name;" -U postgres; 34 | - psql -d database_name -f config/schema-pgsql.sql -U postgres; 35 | 36 | # extensions 37 | - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 38 | 39 | - composer self-update 40 | - composer install 41 | 42 | script: 43 | - vendor/bin/phpcs --standard=psr2 src/ 44 | - vendor/bin/phpunit 45 | - vendor/bin/phpmd src/ text cleancode,codesize,controversial,design,naming,unusedcode 46 | 47 | after_success: 48 | - php vendor/bin/php-coveralls -v 49 | -------------------------------------------------------------------------------- /CONTRIBUTING.markdown: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Queuesadilla loves to welcome your contributions. There are several ways to help out: 4 | 5 | * Create a ticket in GitHub, if you have found a bug 6 | * Write testcases for open bug tickets 7 | * Write patches for open bug/feature tickets, preferably with testcases included 8 | * Contribute to the [documentation](https://github.com/josegonzalez/php-queuesadilla/tree/gh-pages) 9 | 10 | There are a few guidelines that we need contributors to follow so that we have a 11 | chance of keeping on top of things. 12 | 13 | ## Getting Started 14 | 15 | * Make sure you have a [GitHub account](https://github.com/signup/free) 16 | * Submit a ticket for your issue, assuming one does not already exist. 17 | * Clearly describe the issue including steps to reproduce when it is a bug. 18 | * Make sure you fill in the earliest version that you know has the issue. 19 | * Fork the repository on GitHub. 20 | 21 | ## Making Changes 22 | 23 | * Create a topic branch from where you want to base your work. 24 | * This is usually the develop branch 25 | * To quickly create a topic branch based on master; `git branch 26 | master/my_contribution master` then checkout the new branch with `git 27 | checkout master/my_contribution`. Better avoid working directly on the 28 | `master` branch, to avoid conflicts if you pull in updates from origin. 29 | * Make commits of logical units. 30 | * Check for unnecessary whitespace with `git diff --check` before committing. 31 | * Use descriptive commit messages and reference the #ticket number 32 | * Core testcases should continue to pass. You can run tests locally or enable 33 | [travis-ci](https://travis-ci.org/) for your fork, so all tests and codesniffs 34 | will be executed. 35 | 36 | ## Which branch to base the work 37 | 38 | * Bugfix branches will be based on develop branch. 39 | * New features that are backwards compatible will be based on develop branch 40 | * New features or other non-BC changes will go in the next major release branch. 41 | 42 | ## Submitting Changes 43 | 44 | * Push your changes to a topic branch in your fork of the repository. 45 | * Submit a pull request to the repository with the correct target branch. 46 | 47 | ## Testcases and codesniffer 48 | 49 | Queuesadilla tests requires [PHPUnit](http://www.phpunit.de/manual/current/en/installation.html) 50 | 3.5 or higher. To run the testcases locally use the following command: 51 | 52 | phpunit --coverage-text 53 | 54 | To run the sniffs for PSR2 coding standards 55 | 56 | phpcs --standard=psr2 src/ 57 | 58 | # Additional Resources 59 | 60 | * [Bug tracker](https://github.com/josegonzalez/php-queuesadilla/issues) 61 | * [General GitHub documentation](https://help.github.com/) 62 | * [GitHub pull request documentation](https://help.github.com/send-pull-requests/) 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Jose Diaz-Gonzalez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/josegonzalez/php-queuesadilla/master.svg?style=flat-square)](https://travis-ci.org/josegonzalez/php-queuesadilla) 2 | [![Coverage Status](https://img.shields.io/coveralls/josegonzalez/php-queuesadilla/master.svg?style=flat-square)](https://coveralls.io/r/josegonzalez/php-queuesadilla?branch=master) 3 | [![Total Downloads](https://img.shields.io/packagist/dt/josegonzalez/queuesadilla.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/queuesadilla) 4 | [![Latest Stable Version](https://img.shields.io/packagist/v/josegonzalez/queuesadilla.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/queuesadilla) 5 | [![Gratipay](https://img.shields.io/gratipay/josegonzalez.svg?style=flat-square)](https://gratipay.com/~josegonzalez/) 6 | 7 | # Queuesadilla 8 | 9 | A job/worker system built to support various queuing systems. 10 | 11 | ## Requirements 12 | 13 | - PHP 7.2+ 14 | 15 | ## Installation 16 | 17 | _[Using [Composer](http://getcomposer.org/)]_ 18 | 19 | Add the plugin to your project's `composer.json` - something like this: 20 | 21 | ```composer 22 | { 23 | "require": { 24 | "josegonzalez/queuesadilla": "dev-master" 25 | } 26 | } 27 | ``` 28 | 29 | ## Usage 30 | 31 | - [Installation](/docs/installation.md) 32 | - [Supported Systems](/docs/supported-systems.md) 33 | - [Simple Usage](/docs/simple-usage.md) 34 | - [Defining Jobs](/docs/defining-jobs.md) 35 | - [Job Options](/docs/job-options.md) 36 | - [Available Callbacks](/docs/callbacks.md) 37 | 38 | ## Tests 39 | 40 | Tests are run via `phpunit` and depend upon multiple datastores. You may also run tests using the included `Dockerfile`: 41 | 42 | ```shell 43 | docker build . 44 | ``` 45 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "josegonzalez/queuesadilla", 3 | "type": "library", 4 | "description": "Queue background jobs to any backend system", 5 | "keywords": ["php", "queue"], 6 | "homepage": "https://github.com/josegonzalez/php-queuesadilla", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Jose Diaz-Gonzalez", 11 | "email": "queuesadilla@josegonzalez.com", 12 | "homepage": "http://josediazgonzalez.com", 13 | "role": "Maintainer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=7.2.0", 18 | "psr/log": "~1.0", 19 | "league/event": "~2.0", 20 | "ext-pcntl": "*" 21 | }, 22 | "require-dev": { 23 | "phpunit/phpunit": "~8.0", 24 | "iron-io/iron_mq": "dev-master", 25 | "monolog/monolog": "*", 26 | "pda/pheanstalk": "~3.0", 27 | "phpmd/phpmd": "dev-master", 28 | "predis/predis": "~1.0", 29 | "php-coveralls/php-coveralls": "~2.0", 30 | "squizlabs/php_codesniffer": "dev-master" 31 | }, 32 | "suggest": { 33 | "pda/pheanstalk": "Adds support for Beanstalk background workers", 34 | "iron-io/iron_mq": "Adds support for IronMQ background workers", 35 | "monolog/monolog": "Adds support for logging" 36 | }, 37 | "autoload": { 38 | "psr-0": { 39 | "josegonzalez\\Queuesadilla": ["src", "tests"] 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config/schema-mysql.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS `jobs` ( 2 | `id` mediumint(20) NOT NULL AUTO_INCREMENT, 3 | `queue` char(32) NOT NULL DEFAULT 'default', 4 | `data` mediumtext NOT NULL, 5 | `priority` int(1) NOT NULL DEFAULT '0', 6 | `expires_at` datetime DEFAULT NULL, 7 | `delay_until` datetime DEFAULT NULL, 8 | `locked` tinyint(1) NOT NULL DEFAULT '0', 9 | `attempts`int(11) DEFAULT NULL, 10 | `created_at` datetime DEFAULT NULL, 11 | PRIMARY KEY (`id`), 12 | KEY `queue` (`queue`,`locked`) 13 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -------------------------------------------------------------------------------- /config/schema-pgsql.sql: -------------------------------------------------------------------------------- 1 | SET standard_conforming_strings = 'off'; 2 | SET backslash_quote = 'on'; 3 | 4 | DROP TABLE IF EXISTS jobs; 5 | CREATE TABLE jobs ( 6 | id SERIAL PRIMARY KEY, 7 | queue char(32) NOT NULL DEFAULT 'default', 8 | data text NOT NULL, 9 | priority smallint NOT NULL DEFAULT '0', 10 | expires_at timestamp DEFAULT NULL, 11 | delay_until timestamp DEFAULT NULL, 12 | locked smallint NOT NULL DEFAULT '0', 13 | attempts smallint DEFAULT '0', 14 | created_at timestamp DEFAULT NULL 15 | ); 16 | CREATE INDEX queue ON JOBS (queue, locked); -------------------------------------------------------------------------------- /docs/callbacks.md: -------------------------------------------------------------------------------- 1 | ## Callbacks 2 | 3 | Queuesadilla allows developers to hook into various events in the queue/work cycle using the [league/event](http://event.thephpleague.com/1.0/) package. To do so, you can attach a listener to the object as follows: 4 | 5 | ```php 6 | attachListener($event, function (AbstractEvent $event) { 11 | // Your Work Here 12 | }); 13 | 14 | // Using a class 15 | $object->attachListener(new ClassBasedListener); 16 | ?> 17 | ``` 18 | 19 | The `AbstractEvent $event` object that is passed in will always be an instance of the `josegonzalez\Queuesadilla\Event\Event` class, which has the ability the following helper methods: 20 | 21 | - `name()`: Returns the current event name 22 | - `subject()`: Returns the subject of the event, normally the object that dispatched said event 23 | - `data()`: Returns any data the emitted event may contain 24 | 25 | You can otherwise interact with the events as you normally would with `league/event` package. Please consult those [docs](http://event.thephpleague.com/1.0/) for more information. 26 | 27 | ### Creating Class Based Listeners 28 | 29 | There are two ways to create class-based listeners. The simplest way would be to create a class that extends `League\Event\AbstractEvent`: 30 | 31 | ```php 32 | 44 | ``` 45 | 46 | You can also create a listener that can bind to multiple events by extending the `josegonzalez\Queuesadilla\Event\MultiEventListener` class: 47 | 48 | ```php 49 | 'aFunction', 59 | 'another.event' => 'anotherFunction', 60 | ]; 61 | } 62 | 63 | public function aFunction(AbstractEvent $event) 64 | { 65 | // do your worst here 66 | } 67 | 68 | public function anotherFunction(AbstractEvent $event) 69 | { 70 | // do your worst here 71 | } 72 | } 73 | ?> 74 | ``` 75 | 76 | ### Available Events 77 | 78 | #### Queuing 79 | 80 | When queuing a job, you have the ability to hook into the following events: 81 | 82 | - `Queue.afterEnqueue`: Available data includes an `item` array - the data being enqueued - as well as a boolean `success` value that contains whether the job was enqueued. Note that the `item` array will only contain a job `id` if the job was successfully enqueued. 83 | 84 | #### Worker 85 | 86 | When processing jobs via a worker, you have the ability to hook into the following events: 87 | 88 | - `Worker.connectionFailed`: Dispatched when the configured backend could not connect to the backend. 89 | - `Worker.maxIterations`: Dispatched when the configured max number of iterations has been hit. 90 | - `Worker.maxRuntime`: Dispatched when the configured max runtime is reached. 91 | - `Worker.job.seen`: Dispatched after `Engine::pop()` has returned. Will contain an `item` key in it's data, which may be populated with item data if the `pop()` was successful. 92 | - `Worker.job.empty`: Dispatched if the `item` produced from `Engine::pop()` is empty. 93 | - `Worker.job.invalid`: Dispatched if the `item` produced from `Engine::pop()` contains an invalid callable. 94 | - `Worker.job.start`: Dispatched before a job is performed. Data contains a `job` key. 95 | - `Worker.job.exception`: Dispatched if performing the job resulted in any kind of exception. Data contains both a `job` and `exception` key. 96 | - `Worker.job.success`: Dispatched if performing the job was successful. Data contains a `job` key. 97 | - `Worker.job.failure`: Dispatched if performing the job failed. Data contains a `job` key. 98 | -------------------------------------------------------------------------------- /docs/defining-jobs.md: -------------------------------------------------------------------------------- 1 | ## Creating Jobs 2 | 3 | Jobs are simply PHP callables. Job callables should be available to the worker processes via includes, autoloading, etc. 4 | 5 | Job callables receive a `Job` instance (not to be confused with your own own jobs), which is a wrapper around the metadata for a given job. The two useful methods of this `Job` instance are: 6 | 7 | - `attempts()`: Contains the number of attempts a job has has left before being discarded. This value may be incorrect, depending upon the semantics of your chosen backend 8 | - `data($key = null, $default = null)`: Returns the job payload. If the first argument is passed, then the method will return only that key in the job payload if it exists. You can also fallback to a `$default` value if said key does not exist in the payload 9 | 10 | ### Bare Functions 11 | 12 | > Note: When queuing up a function-based job, the job must be available in the namespace that you instantiate the Worker class in. If you are unsure, include a fully-qualified namespace. 13 | 14 | You can create global functions for job workers: 15 | 16 | ```php 17 | data('id'); 20 | $message = $job->data('message'); 21 | 22 | $post = Post::get($id); 23 | $post->message = $message; 24 | $post->save(); 25 | } 26 | 27 | // Queue up the job 28 | $queue->push('\some_job', [ 29 | 'id' => 7, 30 | 'message' => 'hi2u' 31 | ]); 32 | ?> 33 | ``` 34 | 35 | ### Static Functions 36 | 37 | > Note: When queuing up a class-based job, the job must be available in the namespace that you instantiate the Worker class in. If you are unsure, include a fully-qualified namespace. 38 | 39 | Static functions are also an option. State may or may not be cleared, so keep this in mind: 40 | 41 | ```php 42 | data('id'); 46 | $message = $job->data('message'); 47 | 48 | $post = Post::get($id); 49 | $post->message = $message; 50 | $post->save(); 51 | } 52 | } 53 | // Queue up the job 54 | $queue->push('\SomeClass::staticMethod', [ 55 | 'id' => 7, 56 | 'message' => 'hi2u' 57 | ]); 58 | ?> 59 | ``` 60 | 61 | ### Object Instances 62 | 63 | > Note: When queuing up a object-based job, the job must be available in the namespace that you instantiate the Worker class in. If you are unsure, include a fully-qualified namespace. 64 | 65 | We can also create completely new instances of a class that will execute a job. 66 | 67 | ```php 68 | data('id'); 72 | $message = $job->data('message'); 73 | 74 | $post = Post::get($id); 75 | $post->message = $message; 76 | $post->save(); 77 | } 78 | } 79 | // Queue up the job 80 | $queue->push(['SomeClass', 'instanceMethod'], [ 81 | 'id' => 7, 82 | 'message' => 'hi2u', 83 | ]); 84 | ?> 85 | ``` 86 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://img.shields.io/travis/josegonzalez/php-queuesadilla/master.svg?style=flat-square)](https://travis-ci.org/josegonzalez/php-queuesadilla) [![Coverage Status](https://img.shields.io/coveralls/josegonzalez/php-queuesadilla/master.svg?style=flat-square)](https://coveralls.io/r/josegonzalez/php-queuesadilla?branch=master) [![Total Downloads](https://img.shields.io/packagist/dt/josegonzalez/queuesadilla.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/queuesadilla) [![Latest Stable Version](https://img.shields.io/packagist/v/josegonzalez/queuesadilla.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/queuesadilla) [![Gratipay](https://img.shields.io/gratipay/josegonzalez.svg?style=flat-square)](https://gratipay.com/~josegonzalez/) 2 | 3 | # Queuesadilla 4 | 5 | A job/worker system built to support various queuing systems. 6 | 7 | ## Table Of Contents 8 | 9 | - [Installation](/php-queuesadilla/installation) 10 | - [Supported Systems](/php-queuesadilla/supported-systems) 11 | - [Simple Usage](/php-queuesadilla/simple-usage) 12 | - [Defining Jobs](/php-queuesadilla/defining-jobs) 13 | - [Job Options](/php-queuesadilla/job-options) 14 | - [Available Callbacks](/php-queuesadilla/callbacks) 15 | - [Pop order of jobs](/php-queuesadilla/pop-order) 16 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | > Queuesadilla requires PHP 5.5+ 4 | 5 | _[Using [Composer](http://getcomposer.org/)]_ 6 | 7 | Add the plugin to your project's `composer.json` - something like this: 8 | 9 | ```composer 10 | { 11 | "require": { 12 | "josegonzalez/queuesadilla": "dev-master" 13 | } 14 | } 15 | ``` 16 | -------------------------------------------------------------------------------- /docs/job-options.md: -------------------------------------------------------------------------------- 1 | ### Job Options 2 | 3 | Queuing options are configured either at Engine creation or when queuing a job. Options declared when queuing a job take precedence over those at Engine instantiation. All queueing systems support the following options unless otherwise specified: 4 | 5 | - `queue`: Name of a queue to place a job on. All queues are dynamic, and need not be declared beforehand. 6 | - `attempts`: Max number of attempts a job can be performed until it is marked as dead. 7 | - `attempts_delay`: Number of seconds any additional attempts are delayed by. 8 | - `priority`: Jobs with smaller priority values will be scheduled before jobs with larger priorities. Not available with the `MemoryEngine` or `SynchronousEngine`. Job priorities are constants, and there are 5 priorities: 9 | - Job::LOW 10 | - Job::NORMAL 11 | - Job::MEDIUM 12 | - Job::HIGH 13 | - Job::CRITICAL 14 | - `delay`: Seconds to wait before putting the job in the ready queue. The job will be in the "delayed" state during this time. Not available with the `RedisEngine` or `PredisEngine`. 15 | - `time_to_run`: Max amount of time (in seconds) a job can take to run before it is released to the general queue. Not available with the `MysqlEngine` 16 | - `expires_in`: Max amount of time a job may be in the queue until it is discarded. Not available with the `RedisEngine` or `PredisEngine`. 17 | -------------------------------------------------------------------------------- /docs/pop-order.md: -------------------------------------------------------------------------------- 1 | ## Pop order 2 | 3 | By default, jobs are retrieved using the `priority` column value ascending, you can set job priority 4 | using 5 | 6 | ``` 7 | $engine->push($data, ['queue' => 'your-queue-name', 'priority' => 4]) # lower values for higher priority 8 | ``` 9 | 10 | if you want to retrieve jobs 11 | using FIFO, change the pop option to 12 | 13 | ``` 14 | $engine->pop([ 15 | 'queue' => 'default', 16 | 'pop_order' => PdoEngine::POP_ORDER_FIFO, 17 | ] 18 | ``` 19 | -------------------------------------------------------------------------------- /docs/simple-usage.md: -------------------------------------------------------------------------------- 1 | ## Usage: 2 | 3 | > The example here uses the MysqlEngine, though you are free to use any of the [stable backends listed here](/php-queuesadilla/supported-systems) 4 | 5 | Before queuing a job, you should have a engine to store the jobs as well as an instance of the `Queue` class: 6 | 7 | ```php 8 | 15 | ``` 16 | 17 | Next, you will want to create a dummy job: 18 | 19 | ```php 20 | data()); 24 | } 25 | ?> 26 | ``` 27 | 28 | At this point, you can use the `$queue` instance to queue up jobs: 29 | 30 | ```php 31 | push('some_job', [ 33 | 'id' => 7, 34 | 'message' => 'hi' 35 | ]); 36 | ?> 37 | ``` 38 | 39 | Once a job has been pushed, you can construct a worker and execute all jobs in the queue: 40 | 41 | ```php 42 | 5, 'maxRuntime' => 300]); 52 | $worker->work(); 53 | ?> 54 | ``` 55 | 56 | For a more complete example, please see the [example](https://github.com/josegonzalez/php-queuesadilla/blob/master/example.php). 57 | -------------------------------------------------------------------------------- /docs/supported-systems.md: -------------------------------------------------------------------------------- 1 | ### Available Systems 2 | 3 | Queuesadilla supports the following engine engines: 4 | 5 | | Engine | Stability | Notes | 6 | |---------------------|-------------|-----------------------------------------| 7 | | `BeanstalkEngine` | **Stable** | BeanstalkD | 8 | | `MemoryEngine` | **Stable** | In-memory | 9 | | `MysqlEngine` | **Stable** | Backed by PDO/MySQL | 10 | | `PdoEngine` | **Stable** | | 11 | | `PostgresEngine` | **Stable** | Backed by PDO/Postgres | 12 | | `PredisEngine` | **Stable** | Redis-based, requires `nrk/predis` | 13 | | `RedisEngine` | **Stable** | Redis-based, requires `ext-redis` | 14 | | `SynchronousEngine` | **Stable** | Synchronous, in-memory | 15 | | `NullEngine` | **Stable** | Always returns "true" for any operation | 16 | | `IronEngine` | *Unstable* | Needs unit tests | 17 | | `AzureEngine` | Planned | | 18 | | `RabbitMQEngine` | Planned | | 19 | | `SqsEngine` | Planned | | 20 | -------------------------------------------------------------------------------- /example.php: -------------------------------------------------------------------------------- 1 | data()); 44 | } 45 | 46 | public static function run($job) 47 | { 48 | $logger = new Logger('MyJob', [new ErrorLogHandler]); 49 | $logger->info($job->data('message')); 50 | $sleep = $job->data('sleep'); 51 | if (!empty($sleep)) { 52 | $logger->info("Sleeping for " . $job->data('sleep') . " seconds"); 53 | sleep($job->data('sleep')); 54 | } 55 | } 56 | } 57 | 58 | $_type = envDefault('ENGINE_CLASS', 'Memory'); 59 | $_worker = envDefault('WORKER_CLASS', 'Sequential'); 60 | 61 | $EngineClass = "josegonzalez\\Queuesadilla\\Engine\\" . $_type . 'Engine'; 62 | $WorkerClass = "josegonzalez\\Queuesadilla\\Worker\\" . $_worker . "Worker"; 63 | 64 | // Setup a few loggers 65 | $logger = new Logger('Test', [new ErrorLogHandler]); 66 | $callbackLogger = new Logger('Queue.afterEnqueue', [new ErrorLogHandler]); 67 | $dummyLogger = new Logger('Worker.DummyListener', [new ErrorLogHandler]); 68 | 69 | // Instantiate necessary classes 70 | $engine = new $EngineClass($logger); 71 | $queue = new Queue($engine); 72 | $worker = new $WorkerClass($engine, $logger, ['maxIterations' => 5]); 73 | 74 | // Add some callbacks 75 | $queue->attachListener('Queue.afterEnqueue', function (AbstractEvent $event) use ($callbackLogger) { 76 | $data = $event->data(); 77 | $item = $data['item']; 78 | $success = $data['success']; 79 | if ($data['success']) { 80 | $callbackLogger->info(sprintf("Job enqueued: %s", json_encode($item['class']))); 81 | return; 82 | } 83 | $callbackLogger->info(sprintf("Job failed to be enqueued: %s", json_encode($item['class']))); 84 | }); 85 | $worker->attachListener(new DummyListener($dummyLogger)); 86 | 87 | // Push some jobs onto the queue 88 | $queue->push('MyJob::run', ['sleep' => 3, 'message' => 'hi', 'raise' => false]); 89 | $queue->push('raise', ['sleep' => 0, 'message' => 'hi2', 'raise' => true]); 90 | $queue->push(['MyJob', 'perform'], ['sleep' => 1, 'message' => 'hi2u2', 'raise' => false]); 91 | 92 | // Work 93 | $worker->work(); 94 | debug($worker->stats()); 95 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | tests/josegonzalez/Queuesadilla 15 | ./tests/josegonzalez/Queuesadilla/TestCase.php 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | src 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/Base.php: -------------------------------------------------------------------------------- 1 | parseDsn($url)); 34 | } elseif (is_string($config)) { 35 | $config = $this->parseDsn($config); 36 | } 37 | 38 | $this->setLogger($logger); 39 | $this->config($this->baseConfig); 40 | $this->config($config); 41 | 42 | return $this; 43 | } 44 | 45 | public function getJobClass() 46 | { 47 | return '\\josegonzalez\\Queuesadilla\\Job\\Base'; 48 | } 49 | 50 | public function connection() 51 | { 52 | if ($this->connection === null) { 53 | $this->connect(); 54 | } 55 | 56 | return $this->connection; 57 | } 58 | 59 | public function lastJobId() 60 | { 61 | return $this->lastJobId; 62 | } 63 | 64 | public function acknowledge($item) 65 | { 66 | if (!is_array($item)) { 67 | return false; 68 | } 69 | 70 | return !empty($item['id']) && !empty($item['queue']); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/BeanstalkEngine.php: -------------------------------------------------------------------------------- 1 | null, 17 | 'expires_in' => null, 18 | 'port' => 11300, 19 | 'priority' => 0, 20 | 'queue' => 'default', 21 | 'host' => '127.0.0.1', 22 | 'time_to_run' => 60, 23 | 'timeout' => 1, 24 | ]; 25 | 26 | /** 27 | * {@inheritDoc} 28 | */ 29 | public function getJobClass() 30 | { 31 | return '\\josegonzalez\\Queuesadilla\\Job\\BeanstalkJob'; 32 | } 33 | 34 | /** 35 | * {@inheritDoc} 36 | */ 37 | public function connect() 38 | { 39 | $this->connection = new Pheanstalk( 40 | $this->config('host'), 41 | $this->config('port'), 42 | $this->config('timeout') 43 | ); 44 | 45 | return $this->connection->getConnection()->isServiceListening(); 46 | } 47 | 48 | /** 49 | * {@inheritDoc} 50 | */ 51 | public function acknowledge($item) 52 | { 53 | if (!parent::acknowledge($item)) { 54 | return false; 55 | } 56 | if (empty($item['job'])) { 57 | return false; 58 | } 59 | 60 | $response = $this->connection()->deleteJob($item['job']); 61 | 62 | return $response->getResponseName() == Response::RESPONSE_DELETED; 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | public function reject($item) 69 | { 70 | return $this->acknowledge($item); 71 | } 72 | 73 | /** 74 | * {@inheritDoc} 75 | */ 76 | public function pop($options = []) 77 | { 78 | $queue = $this->setting($options, 'queue'); 79 | $this->connection()->useTube($queue); 80 | $job = $this->connection()->reserve(0); 81 | if (!$job) { 82 | return null; 83 | } 84 | 85 | $item = json_decode($job->getData(), true); 86 | $item['job'] = $job; 87 | $item['id'] = $job->getId(); 88 | 89 | $datetime = new DateTime; 90 | if (!empty($item['options']['expires_at']) && $datetime > $item['options']['expires_at']) { 91 | return null; 92 | } 93 | 94 | return $item; 95 | } 96 | 97 | /** 98 | * {@inheritDoc} 99 | */ 100 | public function push($item, $options = []) 101 | { 102 | if (!is_array($options)) { 103 | $options = ['queue' => $options]; 104 | } 105 | 106 | $queue = $this->setting($options, 'queue'); 107 | $expiresIn = $this->setting($options, 'expires_in'); 108 | $delay = $this->setting($options, 'delay', PheanstalkInterface::DEFAULT_DELAY); 109 | $priority = $this->setting($options, 'priority', PheanstalkInterface::DEFAULT_PRIORITY); 110 | $timeToRun = $this->setting($options, 'time_to_run', PheanstalkInterface::DEFAULT_TTR); 111 | 112 | $options = []; 113 | if ($expiresIn !== null) { 114 | $datetime = new DateTime; 115 | $options['expires_at'] = $datetime->add(new DateInterval(sprintf('PT%sS', $expiresIn))); 116 | unset($options['expires_in']); 117 | } 118 | 119 | unset($options['queue']); 120 | $this->connection()->useTube($queue); 121 | try { 122 | $item['options'] = $options; 123 | $this->lastJobId = $this->connection()->put( 124 | json_encode($item), 125 | $priority, 126 | $delay, 127 | $timeToRun 128 | ); 129 | } catch (Exception $e) { 130 | // TODO: Proper logging 131 | return false; 132 | } 133 | 134 | return true; 135 | } 136 | 137 | /** 138 | * {@inheritDoc} 139 | */ 140 | public function queues() 141 | { 142 | return $this->connection()->listTubes(); 143 | } 144 | 145 | /** 146 | * {@inheritDoc} 147 | */ 148 | public function release($item, $options = []) 149 | { 150 | $queue = $this->setting($options, 'queue'); 151 | $delay = $this->setting($options, 'delay', PheanstalkInterface::DEFAULT_DELAY); 152 | $priority = $this->setting($options, 'priority', PheanstalkInterface::DEFAULT_PRIORITY); 153 | 154 | $this->connection()->useTube($queue); 155 | $response = $this->connection()->releaseJob($item['job'], $priority, $delay); 156 | 157 | return $response->getResponseName() == Response::RESPONSE_RELEASED; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/EngineInterface.php: -------------------------------------------------------------------------------- 1 | 1, 12 | 'delay' => null, 13 | 'expires_in' => null, 14 | 'host' => 'mq-aws-us-east-1.iron.io', 15 | 'port' => 443, 16 | 'project_id' => null, 17 | 'protocol' => 'https', 18 | 'queue' => 'default', 19 | 'token' => null, 20 | 'time_to_run' => 60, 21 | ]; 22 | 23 | protected $ironSettings = [ 24 | 'api_version', 25 | 'host', 26 | 'port', 27 | 'project_id', 28 | 'protocol', 29 | 'token', 30 | ]; 31 | 32 | /** 33 | * {@inheritDoc} 34 | */ 35 | public function connect() 36 | { 37 | $settings = []; 38 | foreach ($this->ironSettings as $key) { 39 | $settings[$key] = $this->config($key); 40 | } 41 | 42 | $this->connection = new IronMQ($settings); 43 | 44 | return (bool)$this->connection; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function acknowledge($item) 51 | { 52 | if (!parent::acknowledge($item)) { 53 | return false; 54 | } 55 | 56 | return $this->connection()->deleteMessage($item['queue'], $item['id']); 57 | } 58 | 59 | /** 60 | * {@inheritDoc} 61 | */ 62 | public function reject($item) 63 | { 64 | return $this->acknowledge($item); 65 | } 66 | 67 | /** 68 | * {@inheritDoc} 69 | */ 70 | public function pop($options = []) 71 | { 72 | $queue = $this->setting($options, 'queue'); 73 | $item = $this->connection()->getMessage($queue); 74 | if (!$item) { 75 | return null; 76 | } 77 | 78 | $data = json_decode($item->body, true); 79 | 80 | return [ 81 | 'id' => $item->id, 82 | 'class' => $data['class'], 83 | 'args' => $data['args'], 84 | ]; 85 | } 86 | 87 | /** 88 | * {@inheritDoc} 89 | */ 90 | public function push($class, $args = [], $options = []) 91 | { 92 | $queue = $this->setting($options, 'queue'); 93 | 94 | $item = json_encode(compact('class', 'args', 'queue')); 95 | 96 | return $this->connection()->postMessage($queue, $item, [ 97 | "timeout" => $this->config('time_to_run'), 98 | "delay" => $this->config('delay'), 99 | "expires_in" => $this->config('expires_in'), 100 | ]); 101 | } 102 | 103 | /** 104 | * {@inheritDoc} 105 | */ 106 | public function queues() 107 | { 108 | return $this->connection()->getQueues(); 109 | } 110 | 111 | /** 112 | * {@inheritDoc} 113 | */ 114 | public function release($item, $options = []) 115 | { 116 | $queue = $this->setting($options, 'queue'); 117 | 118 | return $this->connection()->postMessage($queue, $item, [ 119 | "timeout" => $this->config('time_to_run'), 120 | "delay" => $this->config('delay'), 121 | "expires_in" => $this->config('expires_in'), 122 | ]); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/MemoryEngine.php: -------------------------------------------------------------------------------- 1 | null, 13 | 'expires_in' => null, 14 | 'queue' => 'default', 15 | ]; 16 | 17 | protected $queues = [ 18 | 'default' => [], 19 | ]; 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function connect() 25 | { 26 | return $this->connection = true; 27 | } 28 | 29 | /** 30 | * {@inheritDoc} 31 | */ 32 | public function acknowledge($item) 33 | { 34 | if (!parent::acknowledge($item)) { 35 | return false; 36 | } 37 | 38 | $queue = $item['queue']; 39 | if (!isset($this->queues[$queue])) { 40 | return false; 41 | } 42 | 43 | $deleted = false; 44 | foreach ($this->queues[$queue] as $i => $queueItem) { 45 | if ($queueItem['id'] === $item['id']) { 46 | unset($this->queues[$queue][$i]); 47 | $deleted = true; 48 | break; 49 | } 50 | } 51 | 52 | return $deleted; 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | public function reject($item) 59 | { 60 | return $this->acknowledge($item); 61 | } 62 | 63 | /** 64 | * {@inheritDoc} 65 | */ 66 | public function pop($options = []) 67 | { 68 | $queue = $this->setting($options, 'queue'); 69 | $this->requireQueue($options); 70 | 71 | $itemId = null; 72 | $item = null; 73 | while ($item === null) { 74 | $item = array_shift($this->queues[$queue]); 75 | if (!$item) { 76 | return null; 77 | } 78 | 79 | if ($itemId === $item['id']) { 80 | array_push($this->queues[$queue], $item); 81 | 82 | return null; 83 | } 84 | 85 | if ($itemId === null) { 86 | $itemId = $item['id']; 87 | } 88 | 89 | if (empty($item['options'])) { 90 | break; 91 | } 92 | 93 | if ($this->shouldDelay($item)) { 94 | $this->queues[$queue][] = $item; 95 | $item = null; 96 | continue; 97 | } 98 | 99 | if ($this->shouldExpire($item)) { 100 | $item = null; 101 | continue; 102 | } 103 | } 104 | 105 | return $item; 106 | } 107 | 108 | /** 109 | * {@inheritDoc} 110 | */ 111 | public function push($item, $options = []) 112 | { 113 | if (!is_array($options)) { 114 | $options = ['queue' => $options]; 115 | } 116 | 117 | $queue = $this->setting($options, 'queue'); 118 | $delay = $this->setting($options, 'delay'); 119 | $expiresIn = $this->setting($options, 'expires_in'); 120 | $this->requireQueue($options); 121 | 122 | if ($delay !== null) { 123 | $datetime = new DateTime; 124 | $options['delay_until'] = $datetime->add(new DateInterval(sprintf('PT%sS', $delay))); 125 | unset($options['delay']); 126 | } 127 | 128 | if ($expiresIn !== null) { 129 | $datetime = new DateTime; 130 | $options['expires_at'] = $datetime->add(new DateInterval(sprintf('PT%sS', $expiresIn))); 131 | unset($options['expires_in']); 132 | } 133 | 134 | unset($options['queue']); 135 | $oldCount = count($this->queues[$queue]); 136 | $item['options'] = $options; 137 | $newCount = array_push($this->queues[$queue], $item); 138 | 139 | if ($newCount === ($oldCount + 1)) { 140 | $this->lastJobId = $item['id']; 141 | } 142 | 143 | return $newCount === ($oldCount + 1); 144 | } 145 | 146 | /** 147 | * {@inheritDoc} 148 | */ 149 | public function queues() 150 | { 151 | return array_keys($this->queues); 152 | } 153 | 154 | /** 155 | * {@inheritDoc} 156 | */ 157 | public function release($item, $options = []) 158 | { 159 | $queue = $this->setting($options, 'queue'); 160 | $this->requireQueue($options); 161 | 162 | return array_push($this->queues[$queue], $item) !== count($this->queues[$queue]); 163 | } 164 | 165 | protected function requireQueue($options) 166 | { 167 | $queue = $this->setting($options, 'queue'); 168 | if (!isset($this->queues[$queue])) { 169 | $this->queues[$queue] = []; 170 | } 171 | } 172 | 173 | protected function shouldDelay($item) 174 | { 175 | $datetime = new DateTime; 176 | if (!empty($item['options']['delay_until']) && $datetime < $item['options']['delay_until']) { 177 | return true; 178 | } 179 | 180 | return false; 181 | } 182 | 183 | protected function shouldExpire($item) 184 | { 185 | $datetime = new DateTime; 186 | if (!empty($item['options']['expires_at']) && $datetime > $item['options']['expires_at']) { 187 | return true; 188 | } 189 | 190 | return false; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/MysqlEngine.php: -------------------------------------------------------------------------------- 1 | null, 29 | 'database' => 'database_name', 30 | 'expires_in' => null, 31 | 'user' => null, 32 | 'pass' => null, 33 | 'persistent' => true, 34 | 'port' => 3306, 35 | 'priority' => 0, 36 | 'queue' => 'default', 37 | 'attempts' => 0, 38 | 'attempts_delay' => 600, 39 | 'host' => '127.0.0.1', 40 | 'table' => 'jobs', 41 | ]; 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | public function connect() 47 | { 48 | $config = $this->settings; 49 | if (empty($config['flags'])) { 50 | $config['flags'] = []; 51 | } 52 | 53 | $flags = [ 54 | PDO::ATTR_PERSISTENT => $config['persistent'], 55 | PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true, 56 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 57 | ] + $config['flags']; 58 | 59 | $dsn = "mysql:host={$config['host']};port={$config['port']};dbname={$config['database']}"; 60 | 61 | try { 62 | $this->connection = new PDO( 63 | $dsn, 64 | $config['user'], 65 | $config['pass'], 66 | $flags 67 | ); 68 | } catch (PDOException $e) { 69 | $this->logger()->error($e->getMessage()); 70 | $this->connection = null; 71 | } 72 | 73 | return (bool)$this->connection; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/NullEngine.php: -------------------------------------------------------------------------------- 1 | connection = $this->return; 17 | } 18 | 19 | /** 20 | * {@inheritDoc} 21 | */ 22 | public function acknowledge($item) 23 | { 24 | if (!parent::acknowledge($item)) { 25 | return false; 26 | } 27 | 28 | return $this->return; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function reject($item) 35 | { 36 | return $this->acknowledge($item); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function pop($options = []) 43 | { 44 | return $this->return; 45 | } 46 | 47 | /** 48 | * {@inheritDoc} 49 | */ 50 | public function push($item, $options = []) 51 | { 52 | $this->lastJobId = $this->return; 53 | 54 | return $this->return; 55 | } 56 | 57 | /** 58 | * {@inheritDoc} 59 | */ 60 | public function queues() 61 | { 62 | return []; 63 | } 64 | 65 | /** 66 | * {@inheritDoc} 67 | */ 68 | public function release($item, $options = []) 69 | { 70 | return $this->return; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/PdoEngine.php: -------------------------------------------------------------------------------- 1 | quoteIdentifier($this->config('table'))); 53 | 54 | $sth = $this->connection()->prepare($sql); 55 | $sth->bindParam(1, $item['id'], PDO::PARAM_INT); 56 | $sth->execute(); 57 | 58 | return $sth->rowCount() == 1; 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public function reject($item) 65 | { 66 | return $this->acknowledge($item); 67 | } 68 | 69 | /** 70 | * {@inheritDoc} 71 | */ 72 | public function pop($options = []) 73 | { 74 | $queue = $this->setting($options, 'queue'); 75 | 76 | $this->cleanup($queue); 77 | 78 | $selectSql = $this->generatePopSelectSql($options); 79 | $updateSql = sprintf('UPDATE %s SET locked = 1 WHERE id = ?', $this->quoteIdentifier($this->config('table'))); 80 | 81 | $dtFormatted = $this->formattedDateNow(); 82 | 83 | try { 84 | $sth = $this->connection()->prepare($selectSql); 85 | $sth->bindParam(1, $queue, PDO::PARAM_STR); 86 | $sth->bindParam(2, $dtFormatted, PDO::PARAM_STR); 87 | $sth->bindParam(3, $dtFormatted, PDO::PARAM_STR); 88 | 89 | $this->connection()->beginTransaction(); 90 | $sth->execute(); 91 | $result = $sth->fetch(PDO::FETCH_ASSOC); 92 | 93 | if (!empty($result)) { 94 | $sth = $this->connection()->prepare($updateSql); 95 | $sth->bindParam(1, $result['id'], PDO::PARAM_INT); 96 | $sth->execute(); 97 | $this->connection()->commit(); 98 | if ($sth->rowCount() == 1) { 99 | $data = json_decode($result['data'], true); 100 | 101 | return [ 102 | 'id' => $result['id'], 103 | 'class' => $data['class'], 104 | 'args' => $data['args'], 105 | 'queue' => $queue, 106 | 'options' => $data['options'], 107 | 'attempts' => (int)$result['attempts'] 108 | ]; 109 | } 110 | } 111 | $this->connection()->commit(); 112 | } catch (PDOException $e) { 113 | $this->logger()->error($e->getMessage()); 114 | $this->connection()->rollBack(); 115 | } 116 | 117 | return null; 118 | } 119 | 120 | /** 121 | * {@inheritDoc} 122 | */ 123 | public function push($item, $options = []) 124 | { 125 | if (!is_array($options)) { 126 | $options = ['queue' => $options]; 127 | } 128 | 129 | $delay = $this->setting($options, 'delay'); 130 | $expiresIn = $this->setting($options, 'expires_in'); 131 | $queue = $this->setting($options, 'queue'); 132 | $priority = $this->setting($options, 'priority'); 133 | $attempts = $this->setting($options, 'attempts'); 134 | $attemptsDelay = $this->setting($options, 'attempts_delay'); 135 | 136 | $delayUntil = null; 137 | if ($delay !== null) { 138 | $datetime = new DateTime; 139 | $delayUntil = $datetime->add(new DateInterval(sprintf('PT%sS', $delay)))->format('Y-m-d H:i:s'); 140 | } 141 | 142 | $expiresAt = null; 143 | if ($expiresIn !== null) { 144 | $datetime = new DateTime; 145 | $expiresAt = $datetime->add(new DateInterval(sprintf('PT%sS', $expiresIn)))->format('Y-m-d H:i:s'); 146 | } 147 | 148 | unset($options['queue']); 149 | unset($options['attempts']); 150 | $item['options'] = $options; 151 | $item['options']['attempts_delay'] = $attemptsDelay; 152 | $data = json_encode($item); 153 | 154 | $dtFormatted = $this->formattedDateNow(); 155 | $sql = 'INSERT INTO %s (%s, %s, %s, %s, %s, %s, %s) VALUES (?, ?, ?, ?, ?, ?, ?)'; 156 | $sql = sprintf( 157 | $sql, 158 | $this->quoteIdentifier($this->config('table')), 159 | $this->quoteIdentifier('data'), 160 | $this->quoteIdentifier('queue'), 161 | $this->quoteIdentifier('priority'), 162 | $this->quoteIdentifier('expires_at'), 163 | $this->quoteIdentifier('delay_until'), 164 | $this->quoteIdentifier('attempts'), 165 | $this->quoteIdentifier('created_at') 166 | ); 167 | $sth = $this->connection()->prepare($sql); 168 | $sth->bindParam(1, $data, PDO::PARAM_STR); 169 | $sth->bindParam(2, $queue, PDO::PARAM_STR); 170 | $sth->bindParam(3, $priority, PDO::PARAM_INT); 171 | $sth->bindParam(4, $expiresAt, PDO::PARAM_STR); 172 | $sth->bindParam(5, $delayUntil, PDO::PARAM_STR); 173 | $sth->bindParam(6, $attempts, PDO::PARAM_INT); 174 | $sth->bindParam(7, $dtFormatted, PDO::PARAM_STR); 175 | $sth->execute(); 176 | 177 | if ($sth->rowCount() == 1) { 178 | $this->lastJobId = $this->connection()->lastInsertId(); 179 | } 180 | 181 | return $sth->rowCount() == 1; 182 | } 183 | 184 | /** 185 | * {@inheritDoc} 186 | */ 187 | public function queues() 188 | { 189 | $sql = implode(" ", [ 190 | sprintf( 191 | 'SELECT %s FROM %s', 192 | $this->quoteIdentifier('queue'), 193 | $this->quoteIdentifier($this->config('table')) 194 | ), 195 | sprintf('GROUP BY %s', $this->quoteIdentifier('queue')), 196 | ]); 197 | $sth = $this->connection()->prepare($sql); 198 | $sth->execute(); 199 | $results = $sth->fetchAll(PDO::FETCH_ASSOC); 200 | 201 | if (empty($results)) { 202 | return []; 203 | } 204 | 205 | return array_map(function ($result) { 206 | return trim($result['queue']); 207 | }, $results); 208 | } 209 | 210 | /** 211 | * {@inheritDoc} 212 | */ 213 | public function release($item, $options = []) 214 | { 215 | if (isset($item['attempts']) && $item['attempts'] === 0) { 216 | return $this->reject($item); 217 | } 218 | 219 | $fields = [ 220 | [ 221 | 'type' => PDO::PARAM_INT, 222 | 'key' => 'locked', 223 | 'value' => 0, 224 | ], 225 | ]; 226 | 227 | if (isset($item['delay'])) { 228 | $dateInterval = new DateInterval(sprintf('PT%sS', $item['delay'])); 229 | $datetime = new DateTime; 230 | $delayUntil = $datetime->add($dateInterval) 231 | ->format('Y-m-d H:i:s'); 232 | $fields[] = [ 233 | 'type' => PDO::PARAM_STR, 234 | 'key' => 'delay_until', 235 | 'value' => $delayUntil, 236 | ]; 237 | } 238 | if (isset($item['attempts']) && $item['attempts'] > 0) { 239 | $fields[] = [ 240 | 'type' => PDO::PARAM_INT, 241 | 'key' => 'attempts', 242 | 'value' => (int)$item['attempts'], 243 | ]; 244 | } 245 | $updateSql = []; 246 | foreach ($fields as $config) { 247 | $updateSql[] = sprintf('%1$s = :%1$s', $config['key']); 248 | } 249 | $sql = sprintf( 250 | 'UPDATE %s SET %s WHERE id = :id', 251 | $this->config('table'), 252 | implode(', ', $updateSql) 253 | ); 254 | $sth = $this->connection()->prepare($sql); 255 | foreach ($fields as $config) { 256 | $sth->bindValue(sprintf(':%s', $config['key']), $config['value'], $config['type']); 257 | } 258 | $sth->bindValue(':id', (int)$item['id'], PDO::PARAM_INT); 259 | $sth->execute(); 260 | 261 | return $sth->rowCount() == 1; 262 | } 263 | 264 | /** 265 | * Quotes a database identifier (a column name, table name, etc..) to 266 | * be used safely in queries without the risk of using reserved words 267 | * 268 | * Method taken from CakePHP 3.2.10 269 | * 270 | * @param string $identifier The identifier to quote. 271 | * @return string 272 | */ 273 | public function quoteIdentifier($identifier) 274 | { 275 | $identifier = trim($identifier); 276 | 277 | if ($identifier === '*') { 278 | return '*'; 279 | } 280 | 281 | if ($identifier === '') { 282 | return ''; 283 | } 284 | 285 | // string 286 | if (preg_match('/^[\w-]+$/', $identifier)) { 287 | return $this->startQuote . $identifier . $this->endQuote; 288 | } 289 | 290 | // string.string 291 | if (preg_match('/^[\w-]+\.[^ \*]*$/', $identifier)) { 292 | $items = explode('.', $identifier); 293 | 294 | return $this->startQuote . implode($this->endQuote . '.' . $this->startQuote, $items) . $this->endQuote; 295 | } 296 | 297 | // string.* 298 | if (preg_match('/^[\w-]+\.\*$/', $identifier)) { 299 | return $this->startQuote . str_replace('.*', $this->endQuote . '.*', $identifier); 300 | } 301 | 302 | return $identifier; 303 | } 304 | 305 | /** 306 | * Check if expired jobs are present in the database and reject them 307 | * 308 | * @param string $queue name of the queue 309 | * @return void 310 | */ 311 | protected function cleanup($queue) 312 | { 313 | $sql = implode(" ", [ 314 | sprintf( 315 | 'SELECT id FROM %s', 316 | $this->quoteIdentifier($this->config('table')) 317 | ), 318 | sprintf('WHERE %s = ?', $this->quoteIdentifier('queue')), 319 | 'AND expires_at < ?' 320 | ]); 321 | 322 | $datetime = new DateTime; 323 | $dtFormatted = $datetime->format('Y-m-d H:i:s'); 324 | 325 | try { 326 | $sth = $this->connection()->prepare($sql); 327 | $sth->bindParam(1, $queue, PDO::PARAM_STR); 328 | $sth->bindParam(2, $dtFormatted, PDO::PARAM_STR); 329 | 330 | $sth->execute(); 331 | $result = $sth->fetch(PDO::FETCH_ASSOC); 332 | 333 | if (!empty($result)) { 334 | $this->reject([ 335 | 'id' => $result['id'], 336 | 'queue' => $queue 337 | ]); 338 | } 339 | } catch (PDOException $e) { 340 | $this->logger()->error($e->getMessage()); 341 | } 342 | } 343 | 344 | /** 345 | * Generate the sql query to pop a job based on 346 | * priority (default) or FIFO if `$options['pop_order']` is `self::POP_ORDER_FIFO` 347 | * 348 | * @param array $options 349 | * @return string 350 | */ 351 | protected function generatePopSelectSql($options) 352 | { 353 | $queryParts = [ 354 | sprintf( 355 | 'SELECT id, %s, attempts FROM %s', 356 | $this->quoteIdentifier('data'), 357 | $this->quoteIdentifier($this->config('table')) 358 | ), 359 | sprintf('WHERE %s = ? AND %s != 1', $this->quoteIdentifier('queue'), $this->quoteIdentifier('locked')), 360 | 'AND (expires_at IS NULL OR expires_at > ?)', 361 | 'AND (delay_until IS NULL OR delay_until < ?)', 362 | ]; 363 | 364 | $queryParts[] = $this->generatePopOrderSql($options); 365 | 366 | $queryParts[] = 'LIMIT 1 FOR UPDATE'; 367 | 368 | return implode(" ", $queryParts); 369 | } 370 | 371 | /** 372 | * Generate ORDER sql 373 | * 374 | * @param array $options 375 | * @return string 376 | * @throws \InvalidArgumentException if the "pop_order" option is not valid or null 377 | */ 378 | protected function generatePopOrderSql($options = []) 379 | { 380 | $orderPart = 'ORDER BY priority ASC'; 381 | if (isset($options['pop_order']) && $options['pop_order'] === self::POP_ORDER_FIFO) { 382 | $orderPart = 'ORDER BY created_at ASC'; 383 | } 384 | 385 | return $orderPart; 386 | } 387 | 388 | /** 389 | * Get formatted date 390 | * 391 | * @return string 392 | */ 393 | protected function formattedDateNow() 394 | { 395 | $datetime = new DateTime; 396 | 397 | return $datetime->format('Y-m-d H:i:s'); 398 | } 399 | } 400 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/PostgresEngine.php: -------------------------------------------------------------------------------- 1 | null, 29 | 'database' => 'database_name', 30 | 'expires_in' => null, 31 | 'user' => null, 32 | 'pass' => null, 33 | 'persistent' => true, 34 | 'port' => 5432, 35 | 'priority' => 0, 36 | 'queue' => 'default', 37 | 'attempts' => 0, 38 | 'attempts_delay' => 600, 39 | 'host' => '127.0.0.1', 40 | 'table' => 'jobs', 41 | ]; 42 | 43 | /** 44 | * {@inheritDoc} 45 | */ 46 | public function connect() 47 | { 48 | $config = $this->settings; 49 | if (empty($config['flags'])) { 50 | $config['flags'] = []; 51 | } 52 | 53 | $flags = [ 54 | PDO::ATTR_PERSISTENT => $config['persistent'], 55 | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION 56 | ] + $config['flags']; 57 | 58 | $dsn = "pgsql:host={$config['host']};port={$config['port']};dbname={$config['database']}"; 59 | 60 | try { 61 | $this->connection = new PDO( 62 | $dsn, 63 | $config['user'], 64 | $config['pass'], 65 | $flags 66 | ); 67 | } catch (PDOException $e) { 68 | $this->logger()->error($e->getMessage()); 69 | $this->connection = null; 70 | } 71 | 72 | return (bool)$this->connection; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/PredisEngine.php: -------------------------------------------------------------------------------- 1 | connection()->evalSha( 31 | $scriptSha, 32 | 3, 33 | $item['queue'], 34 | rand(), 35 | $item['id'] 36 | ); 37 | } 38 | 39 | protected function redisInstance() 40 | { 41 | return new Client([ 42 | 'host' => $this->config('host'), 43 | 'port' => $this->config('port'), 44 | 'timeout' => (int)$this->config('timeout'), 45 | ]); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/RedisEngine.php: -------------------------------------------------------------------------------- 1 | null, 15 | 'pass' => false, 16 | 'persistent' => true, 17 | 'port' => 6379, 18 | 'queue' => 'default', 19 | 'host' => '127.0.0.1', 20 | 'timeout' => 0, 21 | ]; 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | public function connect() 27 | { 28 | $return = false; 29 | $connectMethod = 'connect'; 30 | if ($this->config('persistent') && $this->persistentEnabled) { 31 | $connectMethod = 'pconnect'; 32 | } 33 | 34 | try { 35 | $this->connection = $this->redisInstance(); 36 | if ($this->connection) { 37 | $return = $this->connection->$connectMethod( 38 | $this->config('host'), 39 | $this->config('port'), 40 | (int)$this->config('timeout') 41 | ); 42 | } 43 | } catch (Exception $e) { 44 | return false; 45 | } 46 | 47 | if ($return && $this->config('database') !== null) { 48 | $return = $this->connection->select((int)$this->config('database')); 49 | } 50 | 51 | if ($return && $this->config('pass')) { 52 | $return = $this->connection->auth($this->config('pass')); 53 | } 54 | 55 | return $return; 56 | } 57 | 58 | /** 59 | * {@inheritDoc} 60 | */ 61 | public function acknowledge($item) 62 | { 63 | if (!parent::acknowledge($item)) { 64 | return false; 65 | } 66 | 67 | $script = $this->getRemoveScript(); 68 | $exists = $this->ensureRemoveScript(); 69 | if (!$exists) { 70 | return false; 71 | } 72 | 73 | return $this->evalSha(sha1($script), $item); 74 | } 75 | 76 | /** 77 | * {@inheritDoc} 78 | */ 79 | public function reject($item) 80 | { 81 | return $this->acknowledge($item); 82 | } 83 | 84 | /** 85 | * {@inheritDoc} 86 | */ 87 | public function pop($options = []) 88 | { 89 | $queue = $this->setting($options, 'queue'); 90 | $item = $this->connection()->lpop('queue:' . $queue); 91 | if (!$item) { 92 | return null; 93 | } 94 | 95 | return json_decode($item, true); 96 | } 97 | 98 | /** 99 | * {@inheritDoc} 100 | */ 101 | public function push($item, $options = []) 102 | { 103 | if (!is_array($options)) { 104 | $options = ['queue' => $options]; 105 | } 106 | 107 | $queue = $this->setting($options, 'queue'); 108 | $this->requireQueue($options); 109 | 110 | unset($options['queue']); 111 | $item['options'] = $options; 112 | $success = (bool)$this->connection()->rpush('queue:' . $queue, json_encode($item)); 113 | if ($success) { 114 | $this->lastJobId = $item['id']; 115 | } 116 | 117 | return $success; 118 | } 119 | 120 | /** 121 | * {@inheritDoc} 122 | */ 123 | public function queues() 124 | { 125 | return $this->connection()->smembers('queues'); 126 | } 127 | 128 | /** 129 | * {@inheritDoc} 130 | */ 131 | public function release($item, $options = []) 132 | { 133 | if (!is_array($item) || !isset($item['id'])) { 134 | return false; 135 | } 136 | 137 | $queue = $this->setting($options, 'queue'); 138 | $this->requireQueue($options); 139 | 140 | if (isset($item['attempts']) && $item['attempts'] === 0) { 141 | return $this->reject($item); 142 | } 143 | 144 | return $this->connection()->rpush('queue:' . $queue, json_encode($item)); 145 | } 146 | 147 | protected function ensureRemoveScript() 148 | { 149 | $script = $this->getRemoveScript(); 150 | $exists = $this->connection()->script('exists', sha1($script)); 151 | if (!empty($exists[0])) { 152 | return $exists[0]; 153 | } 154 | 155 | return $this->connection()->script('load', $script); 156 | } 157 | 158 | protected function evalSha($scriptSha, $item) 159 | { 160 | return (bool)$this->connection()->evalSha($scriptSha, [ 161 | $item['queue'], 162 | rand(), 163 | $item['id'], 164 | ], 3); 165 | } 166 | 167 | protected function getRemoveScript() 168 | { 169 | $script = <<setting($options, 'queue'); 216 | $this->connection()->sadd('queues', $queue); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Engine/SynchronousEngine.php: -------------------------------------------------------------------------------- 1 | getWorker(); 17 | 18 | return $worker->work(); 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | protected function getWorker() 25 | { 26 | return new SequentialWorker($this, $this->logger, ['maxIterations' => 1]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Event/Event.php: -------------------------------------------------------------------------------- 1 | name = $name; 46 | $this->data = $data; 47 | $this->subject = $subject; 48 | 49 | return $this; 50 | } 51 | 52 | /** 53 | * Dynamically returns the name and subject if accessed directly 54 | * 55 | * @param string $attribute Attribute name. 56 | * @return mixed 57 | */ 58 | public function __get($attribute) 59 | { 60 | if ($attribute === 'name' || $attribute === 'subject') { 61 | return $this->{$attribute}(); 62 | } 63 | } 64 | 65 | /** 66 | * Returns the name of this event. This is usually used as the event identifier 67 | * 68 | * @return string 69 | */ 70 | public function getName() 71 | { 72 | return $this->name(); 73 | } 74 | 75 | /** 76 | * Returns the name of this event. This is usually used as the event identifier 77 | * 78 | * @return string 79 | */ 80 | public function name() 81 | { 82 | return $this->name; 83 | } 84 | 85 | /** 86 | * Returns the subject of this event 87 | * 88 | * @return string 89 | */ 90 | public function subject() 91 | { 92 | return $this->subject; 93 | } 94 | 95 | /** 96 | * Check if the event is stopped 97 | * 98 | * @return bool True if the event is stopped 99 | */ 100 | public function isStopped() 101 | { 102 | return $this->isPropagationStopped(); 103 | } 104 | 105 | /** 106 | * Access the event data/payload. 107 | * 108 | * @return array 109 | */ 110 | public function data() 111 | { 112 | return (array)$this->data; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Event/EventListenerInterface.php: -------------------------------------------------------------------------------- 1 | 'sendEmail', 23 | * 'Article.afterBuy' => 'decrementInventory', 24 | * 'User.onRegister' => array('callable' => 'logRegistration', 'priority' => 20, 'passParams' => true) 25 | * ); 26 | * } 27 | * }}} 28 | * 29 | * @return array associative array or event key names pointing to the function 30 | * that should be called in the object when the respective event is fired 31 | */ 32 | public function implementedEvents(); 33 | } 34 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Event/EventManagerTrait.php: -------------------------------------------------------------------------------- 1 | eventManager = $eventManager; 41 | } elseif (empty($this->eventManager)) { 42 | $this->eventManager = new EventManager(); 43 | } 44 | 45 | return $this->eventManager; 46 | } 47 | 48 | /** 49 | * Wrapper for creating and dispatching events. 50 | * 51 | * Returns a dispatched event. 52 | * 53 | * @param string $name Name of the event. 54 | * @param array $data Any value you wish to be transported with this event to 55 | * it can be read by listeners. 56 | * 57 | * @param object $subject The object that this event applies to 58 | * ($this by default). 59 | * 60 | * @return \League\Event\AbstractEvent 61 | */ 62 | public function dispatchEvent($name, $data = null, $subject = null) 63 | { 64 | if ($subject === null) { 65 | $subject = $this; 66 | } 67 | 68 | $event = new $this->eventClass($name, $subject, $data); 69 | $this->eventManager()->emit($event); 70 | 71 | return $event; 72 | } 73 | 74 | public function attachListener($name, $listener = null, array $options = []) 75 | { 76 | if (!$listener) { 77 | if ($name instanceof MultiEventListener) { 78 | foreach ($name->implementedEvents() as $event => $method) { 79 | $method; 80 | $this->attachListener($event, $name, $options); 81 | } 82 | 83 | return; 84 | } 85 | throw new \InvalidArgumentException('Invalid listener for event'); 86 | } 87 | $options += ['priority' => 0]; 88 | $this->eventManager()->addListener($name, $listener, $options['priority']); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Event/MultiEventListener.php: -------------------------------------------------------------------------------- 1 | implementedEvents(); 15 | if (empty($events)) { 16 | return; 17 | } 18 | if (!isset($events[$event->getName()])) { 19 | return; 20 | } 21 | 22 | $handler = $events[$event->getName()]; 23 | if (!method_exists($this, $handler)) { 24 | return; 25 | } 26 | 27 | return $this->$handler($event); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Job/Base.php: -------------------------------------------------------------------------------- 1 | engine = $engine; 22 | $this->item = $item; 23 | 24 | return $this; 25 | } 26 | 27 | public function attempts() 28 | { 29 | if (array_key_exists('attempts', $this->item)) { 30 | return $this->item['attempts']; 31 | } 32 | 33 | return $this->item['attempts'] = 0; 34 | } 35 | 36 | public function data($key = null, $default = null) 37 | { 38 | if ($key === null) { 39 | return $this->item['args'][0]; 40 | } 41 | 42 | if (array_key_exists($key, $this->item['args'][0])) { 43 | return $this->item['args'][0][$key]; 44 | } 45 | 46 | return $default; 47 | } 48 | 49 | public function acknowledge() 50 | { 51 | return $this->engine->acknowledge($this->item); 52 | } 53 | 54 | public function reject() 55 | { 56 | return $this->engine->reject($this->item); 57 | } 58 | 59 | public function item() 60 | { 61 | return $this->item; 62 | } 63 | 64 | public function release($delay = 0) 65 | { 66 | if (!isset($this->item['attempts'])) { 67 | $this->item['attempts'] = 0; 68 | } 69 | if ($this->item['attempts'] > 0) { 70 | $this->item['attempts'] -= 1; 71 | } 72 | 73 | $this->item['delay'] = $delay; 74 | if (isset($this->item['options']['attempts_delay'])) { 75 | $this->item['delay'] = $this->item['options']['attempts_delay']; 76 | } 77 | 78 | return $this->engine->release($this->item); 79 | } 80 | 81 | public function __toString() 82 | { 83 | return json_encode($this); 84 | } 85 | 86 | public function jsonSerialize(): mixed 87 | { 88 | return $this->item; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Job/BeanstalkJob.php: -------------------------------------------------------------------------------- 1 | engine->connection()->statsJob($this->item['id']); 15 | if ($stats !== null) { 16 | return (int)$stats['reserves']; 17 | } 18 | 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Queue.php: -------------------------------------------------------------------------------- 1 | engine = $engine; 14 | 15 | return $this; 16 | } 17 | 18 | /** 19 | * Push a single job onto the queue. 20 | * 21 | * @param string $callable a job callable 22 | * @param array $args an array of data to set for the job 23 | * @param array $options an array of options for publishing the job 24 | * 25 | * @return boolean the result of the push 26 | **/ 27 | public function push($callable, $args = [], $options = []) 28 | { 29 | $queue = $this->engine->setting($options, 'queue'); 30 | $item = [ 31 | 'queue' => $queue, 32 | 'class' => $callable, 33 | 'args' => [$args], 34 | 'id' => md5(uniqid('', true)), 35 | 'queue_time' => microtime(true), 36 | ]; 37 | $success = $this->engine->push($item, $options); 38 | 39 | unset($item['id']); 40 | if ($success) { 41 | $item['id'] = $this->engine->lastJobId(); 42 | } 43 | 44 | $item['args'] = $args; 45 | $this->dispatchEvent('Queue.afterEnqueue', [ 46 | 'item' => $item, 47 | 'success' => $success, 48 | ]); 49 | 50 | return $success; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Utility/DsnParserTrait.php: -------------------------------------------------------------------------------- 1 | true, 41 | 'false' => false, 42 | 'null' => null, 43 | ]; 44 | 45 | parse_str($query, $queryArgs); 46 | foreach ($queryArgs as $key => $value) { 47 | if (isset($stringMap[$value])) { 48 | $queryArgs[$key] = $stringMap[$value]; 49 | } 50 | } 51 | 52 | $parsed = $queryArgs + $parsed; 53 | 54 | return $parsed; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Utility/LoggerTrait.php: -------------------------------------------------------------------------------- 1 | logger = $logger; 20 | } 21 | 22 | public function logger() 23 | { 24 | return $this->logger; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Utility/Pheanstalk.php: -------------------------------------------------------------------------------- 1 | protectedMethodCall($this, [new ReleaseCommand($job, $priority, $delay)]); 18 | } 19 | 20 | public function deleteJob($job) 21 | { 22 | return $this->protectedMethodCall($this, [new DeleteCommand($job)]); 23 | } 24 | 25 | public function protectedMethodCall($object, $parameters) 26 | { 27 | $reflection = new ReflectionClass(get_class($object)); 28 | $method = $reflection->getMethod('_dispatch'); 29 | $method->setAccessible(true); 30 | 31 | return $method->invokeArgs($object, $parameters); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Utility/SettingTrait.php: -------------------------------------------------------------------------------- 1 | settings = array_merge($this->settings, $key); 13 | $key = null; 14 | } 15 | 16 | if ($key === null) { 17 | return $this->settings; 18 | } 19 | 20 | if ($value === null) { 21 | if (isset($this->settings[$key])) { 22 | return $this->settings[$key]; 23 | } 24 | 25 | return null; 26 | } 27 | 28 | return $this->settings[$key] = $value; 29 | } 30 | 31 | public function setting($settings, $key, $default = null) 32 | { 33 | if (!is_array($settings)) { 34 | $settings = ['queue' => $settings]; 35 | } 36 | 37 | $settings = array_merge($this->settings, $settings); 38 | 39 | if (isset($settings[$key])) { 40 | return $settings[$key]; 41 | } 42 | 43 | return $default; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Worker/Base.php: -------------------------------------------------------------------------------- 1 | 1, 34 | 'maxIterations' => null, 35 | 'maxRuntime' => null, 36 | 'queue' => 'default', 37 | ], $params); 38 | 39 | $this->engine = $engine; 40 | $this->queue = $params['queue']; 41 | $this->maxIterations = $params['maxIterations']; 42 | $this->interval = $params['interval']; 43 | $this->iterations = 0; 44 | $this->maxRuntime = $params['maxRuntime']; 45 | $this->runtime = 0; 46 | $this->name = get_class($this->engine) . ' Worker'; 47 | $this->setLogger($logger); 48 | 49 | $this->StatsListener = new StatsListener; 50 | $this->attachListener($this->StatsListener); 51 | $this->attachListener($this); 52 | register_shutdown_function([&$this, 'shutdownHandler']); 53 | 54 | return $this; 55 | } 56 | 57 | public function implementedEvents() 58 | { 59 | return []; 60 | } 61 | 62 | public function stats() 63 | { 64 | return $this->StatsListener->stats; 65 | } 66 | 67 | public function shutdownHandler($signo = null) 68 | { 69 | $this->logger->info("Shutting down"); 70 | 71 | $signals = [ 72 | SIGQUIT => "SIGQUIT", 73 | SIGTERM => "SIGTERM", 74 | SIGINT => "SIGINT", 75 | SIGUSR1 => "SIGUSR1", 76 | ]; 77 | 78 | if ($signo !== null) { 79 | $signal = $signals[$signo]; 80 | $this->logger->info(sprintf("Received received %s... Shutting down", $signal)); 81 | } 82 | $this->disconnect(); 83 | 84 | $this->logger->info(sprintf( 85 | "Worker shutting down after running %d iterations in %ds", 86 | $this->iterations, 87 | $this->runtime 88 | )); 89 | 90 | return true; 91 | } 92 | 93 | abstract public function work(); 94 | 95 | abstract protected function disconnect(); 96 | } 97 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Worker/Listener/DummyListener.php: -------------------------------------------------------------------------------- 1 | setLogger($logger); 18 | } 19 | 20 | public function implementedEvents() 21 | { 22 | return [ 23 | 'Worker.connectionFailed' => 'perform', 24 | 'Worker.maxIterations' => 'perform', 25 | 'Worker.maxRuntime' => 'perform', 26 | 'Worker.job.seen' => 'perform', 27 | 'Worker.job.empty' => 'perform', 28 | 'Worker.job.invalid' => 'perform', 29 | 'Worker.job.start' => 'perform', 30 | 'Worker.job.exception' => 'perform', 31 | 'Worker.job.success' => 'perform', 32 | 'Worker.job.failure' => 'perform', 33 | ]; 34 | } 35 | 36 | public function perform(AbstractEvent $event) 37 | { 38 | $data = $event->data(); 39 | $job = ''; 40 | if (!empty($data['job'])) { 41 | $job = $data['job']; 42 | } 43 | 44 | $this->logger()->info(sprintf("%s: %s\n", $event->name(), json_encode($job))); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Worker/Listener/StatsListener.php: -------------------------------------------------------------------------------- 1 | stats = [ 18 | 'connectionFailed' => 0, 19 | 'maxIterations' => 0, 20 | 'maxRuntime' => 0, 21 | 'seen' => 0, 22 | 'empty' => 0, 23 | 'exception' => 0, 24 | 'invalid' => 0, 25 | 'success' => 0, 26 | 'failure' => 0, 27 | ]; 28 | } 29 | 30 | public function implementedEvents() 31 | { 32 | return [ 33 | 'Worker.connectionFailed' => 'connectionFailed', 34 | 'Worker.maxIterations' => 'maxIterations', 35 | 'Worker.maxRuntime' => 'maxRuntime', 36 | 'Worker.job.seen' => 'jobSeen', 37 | 'Worker.job.empty' => 'jobEmpty', 38 | 'Worker.job.invalid' => 'jobInvalid', 39 | 'Worker.job.start' => 'jobStart', 40 | 'Worker.job.exception' => 'jobException', 41 | 'Worker.job.success' => 'jobSuccess', 42 | 'Worker.job.failure' => 'jobFailure', 43 | ]; 44 | } 45 | 46 | public function connectionFailed() 47 | { 48 | $this->stats['connectionFailed'] += 1; 49 | } 50 | 51 | public function maxIterations() 52 | { 53 | $this->stats['maxIterations'] += 1; 54 | } 55 | public function maxRuntime() 56 | { 57 | $this->stats['maxRuntime'] += 1; 58 | } 59 | public function jobSeen() 60 | { 61 | $this->stats['seen'] += 1; 62 | } 63 | public function jobEmpty() 64 | { 65 | $this->stats['empty'] += 1; 66 | } 67 | public function jobInvalid() 68 | { 69 | $this->stats['invalid'] += 1; 70 | } 71 | public function jobException() 72 | { 73 | $this->stats['exception'] += 1; 74 | } 75 | public function jobSuccess() 76 | { 77 | $this->stats['success'] += 1; 78 | } 79 | public function jobFailure() 80 | { 81 | $this->stats['failure'] += 1; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Worker/SequentialWorker.php: -------------------------------------------------------------------------------- 1 | running = true; 26 | } 27 | 28 | /** 29 | * events this class listens to 30 | * 31 | * @return array 32 | */ 33 | public function implementedEvents() 34 | { 35 | return [ 36 | 'Worker.job.empty' => 'jobEmpty', 37 | 'Worker.job.exception' => 'jobException', 38 | 'Worker.job.success' => 'jobSuccess', 39 | ]; 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | * @SuppressWarnings(PHPMD.CyclomaticComplexity) 45 | */ 46 | public function work() 47 | { 48 | if (!$this->connect()) { 49 | $this->logger()->alert(sprintf('Worker unable to connect, exiting')); 50 | $this->dispatchEvent('Worker.job.connectionFailed'); 51 | 52 | return false; 53 | } 54 | 55 | $jobClass = $this->engine->getJobClass(); 56 | $time = microtime(true); 57 | while ($this->running) { 58 | if (is_int($this->maxRuntime) && $this->runtime >= $this->maxRuntime) { 59 | $this->logger()->debug('Max runtime reached, exiting'); 60 | $this->dispatchEvent('Worker.maxRuntime'); 61 | break; 62 | } elseif (is_int($this->maxIterations) && $this->iterations >= $this->maxIterations) { 63 | $this->logger()->debug('Max iterations reached, exiting'); 64 | $this->dispatchEvent('Worker.maxIterations'); 65 | break; 66 | } 67 | 68 | $this->runtime += microtime(true) - $time; 69 | $time = microtime(true); 70 | $this->iterations++; 71 | $item = $this->engine->pop($this->queue); 72 | $this->dispatchEvent('Worker.job.seen', ['item' => $item]); 73 | if (empty($item)) { 74 | $this->dispatchEvent('Worker.job.empty'); 75 | sleep($this->interval); 76 | continue; 77 | } 78 | 79 | $success = false; 80 | $job = new $jobClass($item, $this->engine); 81 | if (!is_callable($item['class'])) { 82 | $this->logger()->alert('Invalid callable for job. Rejecting job from queue.'); 83 | $job->reject(); 84 | $this->dispatchEvent('Worker.job.invalid', ['job' => $job]); 85 | continue; 86 | } 87 | 88 | $this->dispatchEvent('Worker.job.start', ['job' => $job]); 89 | 90 | try { 91 | $success = $this->perform($item, $job); 92 | } catch (Exception $e) { 93 | $this->dispatchEvent('Worker.job.exception', [ 94 | 'job' => $job, 95 | 'exception' => $e, 96 | ]); 97 | } 98 | 99 | if ($success) { 100 | $job->acknowledge(); 101 | $this->dispatchEvent('Worker.job.success', ['job' => $job]); 102 | continue; 103 | } 104 | 105 | $this->logger()->info('Failed. Releasing job to queue'); 106 | $job->release(); 107 | $this->dispatchEvent('Worker.job.failure', ['job' => $job]); 108 | } 109 | 110 | return true; 111 | } 112 | 113 | public function connect() 114 | { 115 | $maxIterations = $this->maxIterations ? sprintf(', max iterations %s', $this->maxIterations) : ''; 116 | $this->logger()->info(sprintf('Starting worker%s', $maxIterations)); 117 | 118 | return (bool)$this->engine->connection(); 119 | } 120 | 121 | public function perform($item, $job) 122 | { 123 | if (!is_callable($item['class'])) { 124 | return false; 125 | } 126 | 127 | $success = false; 128 | if (is_array($item['class']) && count($item['class']) == 2) { 129 | $className = $item['class'][0]; 130 | $methodName = $item['class'][1]; 131 | $instance = new $className; 132 | $success = $instance->$methodName($job); 133 | } elseif (is_string($item['class'])) { 134 | $success = call_user_func($item['class'], $job); 135 | } 136 | 137 | if ($success !== false) { 138 | $success = true; 139 | } 140 | 141 | return $success; 142 | } 143 | 144 | protected function disconnect() 145 | { 146 | } 147 | 148 | public function signalHandler($signo = null) 149 | { 150 | $signals = [ 151 | SIGQUIT => "SIGQUIT", 152 | SIGTERM => "SIGTERM", 153 | SIGINT => "SIGINT", 154 | SIGUSR1 => "SIGUSR1", 155 | ]; 156 | 157 | if ($signo !== null) { 158 | $signal = $signals[$signo]; 159 | $this->logger->info(sprintf("Received %s... Shutting down", $signal)); 160 | } 161 | 162 | switch ($signo) { 163 | case SIGQUIT: 164 | $this->logger()->debug('SIG: Caught SIGQUIT'); 165 | $this->running = false; 166 | break; 167 | case SIGTERM: 168 | $this->logger()->debug('SIG: Caught SIGTERM'); 169 | $this->running = false; 170 | break; 171 | case SIGINT: 172 | $this->logger()->debug('SIG: Caught CTRL+C'); 173 | $this->running = false; 174 | break; 175 | case SIGUSR1: 176 | $this->logger()->debug('SIG: Caught SIGUSR1'); 177 | $this->running = false; 178 | break; 179 | default: 180 | $this->logger()->debug('SIG:received other signal'); 181 | break; 182 | } 183 | 184 | return true; 185 | } 186 | 187 | public function shutdownHandler($signo = null) 188 | { 189 | $this->disconnect(); 190 | 191 | $this->logger->info(sprintf( 192 | "Worker shutting down after running %d iterations in %ds", 193 | $this->iterations, 194 | $this->runtime 195 | )); 196 | 197 | return true; 198 | } 199 | 200 | /** 201 | * Event triggered on Worker.job.empty 202 | * 203 | * @param Event $event 204 | * @return void 205 | */ 206 | public function jobEmpty(Event $event) 207 | { 208 | $event; 209 | $this->logger()->debug('No job!'); 210 | } 211 | 212 | /** 213 | * Event triggered on Worker.job.exception 214 | * 215 | * @param Event $event 216 | * @return void 217 | */ 218 | public function jobException(Event $event) 219 | { 220 | $data = $event->data(); 221 | $this->logger()->alert(sprintf('Exception: "%s"', $data['exception']->getMessage())); 222 | } 223 | 224 | /** 225 | * Event triggered on Worker.job.success 226 | * 227 | * @param Event $event 228 | * @return void 229 | */ 230 | public function jobSuccess(Event $event) 231 | { 232 | $event; 233 | $this->logger()->debug('Success. Acknowledging job on queue.'); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /src/josegonzalez/Queuesadilla/Worker/TestWorker.php: -------------------------------------------------------------------------------- 1 | engine->pop(); 15 | } 16 | 17 | /** 18 | * {@inheritDoc} 19 | */ 20 | protected function disconnect() 21 | { 22 | return true; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | config = ['url' => $this->url]; 22 | $this->Logger = new NullLogger; 23 | $this->Engine = $this->mockEngine(); 24 | $this->Fixtures = new FixtureData; 25 | $this->clearEngine(); 26 | $this->expandFixtureData(); 27 | } 28 | 29 | /** 30 | * Tear Down 31 | * 32 | * @return void 33 | */ 34 | public function tearDown() : void 35 | { 36 | $this->clearEngine(); 37 | unset($this->Engine); 38 | } 39 | 40 | /** 41 | * Used to truncate the jobs table and to reset the auto increment value. 42 | * 43 | * @return void 44 | */ 45 | abstract protected function clearEngine(); 46 | 47 | /** 48 | * @covers josegonzalez\Queuesadilla\Engine\Base::__construct 49 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::__construct 50 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::__construct 51 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::__construct 52 | */ 53 | public function testConstruct() 54 | { 55 | if ($this->Engine->connection() === null) { 56 | $this->markTestSkipped('No connection to database available'); 57 | } 58 | $Engine = new $this->engineClass($this->Logger, [ 59 | 'user' => 'invalid' 60 | ]); 61 | $this->assertNull($Engine->connection()); 62 | 63 | $Engine = new $this->engineClass($this->Logger, $this->url); 64 | $this->assertNotNull($Engine->connection()); 65 | 66 | $Engine = new $this->engineClass($this->Logger, $this->config); 67 | $this->assertNotNull($Engine->connection()); 68 | } 69 | 70 | /** 71 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::connect 72 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::connect 73 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::connect 74 | */ 75 | public function testConnect() 76 | { 77 | if ($this->Engine->connection() === null) { 78 | $this->markTestSkipped('No connection to database available'); 79 | } 80 | $engine = $this->mockEngine(null, [ 81 | 'database' => 'invalid', 82 | 'user' => 'invalid' 83 | ]); 84 | 85 | $this->assertFalse($engine->connect()); 86 | 87 | $this->assertTrue($this->Engine->connect()); 88 | } 89 | 90 | /** 91 | * @covers josegonzalez\Queuesadilla\Engine\Base::connection 92 | */ 93 | public function testConnection() 94 | { 95 | $engine = $this->mockEngine(); 96 | $connection = $engine->connection(); 97 | $this->assertInstanceOf('PDO', $connection); 98 | } 99 | 100 | /** 101 | * @covers josegonzalez\Queuesadilla\Engine\Base::getJobClass 102 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::getJobClass 103 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::getJobClass 104 | */ 105 | public function testGetJobClass() 106 | { 107 | if ($this->Engine->connection() === null) { 108 | $this->markTestSkipped('No connection to database available'); 109 | } 110 | $this->assertEquals('\\josegonzalez\\Queuesadilla\\Job\\Base', $this->Engine->getJobClass()); 111 | } 112 | 113 | /** 114 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::acknowledge 115 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::acknowledge 116 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::acknowledge 117 | */ 118 | public function testAcknowledge() 119 | { 120 | if ($this->Engine->connection() === null) { 121 | $this->markTestSkipped('No connection to database available'); 122 | } 123 | $this->assertFalse($this->Engine->acknowledge(null)); 124 | $this->assertFalse($this->Engine->acknowledge(false)); 125 | $this->assertFalse($this->Engine->acknowledge(1)); 126 | $this->assertFalse($this->Engine->acknowledge('string')); 127 | $this->assertFalse($this->Engine->acknowledge(['key' => 'value'])); 128 | $this->assertFalse($this->Engine->acknowledge($this->Fixtures->default['first'])); 129 | 130 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 131 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 132 | $this->assertTrue($this->Engine->acknowledge($this->Fixtures->default['first'])); 133 | } 134 | 135 | /** 136 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::reject 137 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::reject 138 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::reject 139 | */ 140 | public function testReject() 141 | { 142 | if ($this->Engine->connection() === null) { 143 | $this->markTestSkipped('No connection to database available'); 144 | } 145 | $this->assertFalse($this->Engine->reject(null)); 146 | $this->assertFalse($this->Engine->reject(false)); 147 | $this->assertFalse($this->Engine->reject(1)); 148 | $this->assertFalse($this->Engine->reject('string')); 149 | $this->assertFalse($this->Engine->reject(['key' => 'value'])); 150 | $this->assertFalse($this->Engine->reject($this->Fixtures->default['first'])); 151 | 152 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 153 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 154 | $this->assertTrue($this->Engine->reject($this->Fixtures->default['first'])); 155 | } 156 | 157 | /** 158 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::pop 159 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::pop 160 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::pop 161 | */ 162 | public function testPop() 163 | { 164 | if ($this->Engine->connection() === null) { 165 | $this->markTestSkipped('No connection to database available'); 166 | } 167 | $this->assertNull($this->Engine->pop('default')); 168 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], ['queue' => 'default', 'priority' => 4])); 169 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], ['queue' => 'default', 'priority' => 1])); 170 | $this->assertTrue($this->Engine->push($this->Fixtures->default['third'], ['queue' => 'default', 'priority' => 3])); 171 | $msg = 'We should have returned the second job, as it has the lowest priority'; 172 | $secondJobFixture = $this->Fixtures->default['second']; 173 | $secondJobFixture['options']['priority'] = 1; 174 | $this->assertEquals($secondJobFixture, $this->Engine->pop('default'), $msg); 175 | } 176 | 177 | /** 178 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::pop 179 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::generatePopSelectSql 180 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::generatePopOrderSql 181 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::formattedDateNow 182 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::pop 183 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::pop 184 | */ 185 | public function testPopFIFO() 186 | { 187 | if ($this->Engine->connection() === null) { 188 | $this->markTestSkipped('No connection to database available'); 189 | } 190 | $this->assertNull($this->Engine->pop('default')); 191 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], ['queue' => 'default', 'priority' => 4])); 192 | sleep(1); 193 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], ['queue' => 'default', 'priority' => 1])); 194 | sleep(1); 195 | $this->assertTrue($this->Engine->push($this->Fixtures->default['third'], ['queue' => 'default', 'priority' => 3])); 196 | $msg = 'We should have returned the first job, as it has the lowest id (FIFO)'; 197 | $firstJobFixture = $this->Fixtures->default['first']; 198 | $firstJobFixture['options']['priority'] = 4; 199 | $this->assertEquals($firstJobFixture, $this->Engine->pop([ 200 | 'queue' => 'default', 201 | 'pop_order' => PdoEngine::POP_ORDER_FIFO, 202 | ]), $msg); 203 | } 204 | 205 | /** 206 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::pop 207 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::pop 208 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::pop 209 | */ 210 | public function testPopInvalid() 211 | { 212 | if ($this->Engine->connection() === null) { 213 | $this->markTestSkipped('No connection to database available'); 214 | } 215 | $this->assertNull($this->Engine->pop('default')); 216 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], ['queue' => 'default', 'priority' => 4])); 217 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], ['queue' => 'default', 'priority' => 1])); 218 | $this->assertTrue($this->Engine->push($this->Fixtures->default['third'], ['queue' => 'default', 'priority' => 3])); 219 | $msg = 'We should have returned the second job, as it has the lowest priority'; 220 | $secondJobFixture = $this->Fixtures->default['second']; 221 | $secondJobFixture['options']['priority'] = 1; 222 | $this->assertEquals($secondJobFixture, $this->Engine->pop('default'), $msg); 223 | } 224 | 225 | /** 226 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::push 227 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::push 228 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::push 229 | */ 230 | public function testPush() 231 | { 232 | if ($this->Engine->connection() === null) { 233 | $this->markTestSkipped('No connection to database available'); 234 | } 235 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 236 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], [ 237 | 'delay' => 30 238 | ])); 239 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'], [ 240 | 'expires_in' => 1, 241 | ])); 242 | $this->assertTrue($this->Engine->push($this->Fixtures->default['fourth'], 'default')); 243 | sleep(2); 244 | 245 | $pop1 = $this->Engine->pop(); 246 | $pop2 = $this->Engine->pop(); 247 | $pop3 = $this->Engine->pop(); 248 | $pop4 = $this->Engine->pop(); 249 | 250 | $this->assertNotEmpty($pop1['id']); 251 | $this->assertEquals($this->Fixtures->default['first']['id'], $pop1['id']); 252 | $this->assertNull($pop1['class']); 253 | $this->assertEmpty($pop1['args']); 254 | $this->assertEquals($this->Fixtures->default['fourth']['id'], $pop2['id']); 255 | $this->assertEquals('yet_another_function', $pop2['class']); 256 | $this->assertNull($pop3); 257 | $this->assertNull($pop4); 258 | } 259 | 260 | /** 261 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::release 262 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::release 263 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::release 264 | */ 265 | public function testRelease() 266 | { 267 | if ($this->Engine->connection() === null) { 268 | $this->markTestSkipped('No connection to database available'); 269 | } 270 | $this->assertFalse($this->Engine->release(null, 'default')); 271 | 272 | $this->Engine->push($this->Fixtures->default['first'], 'default'); 273 | $item = $this->Engine->pop(); 274 | $this->assertTrue($this->Engine->release($item)); 275 | $sth = $this->execute($this->Engine->connection(), 'SELECT * FROM jobs WHERE id = ' . $this->Fixtures->default['first']['id']); 276 | $this->assertTrue($sth->rowCount() == 0); 277 | 278 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], [ 279 | 'attempts' => 10 280 | ])); 281 | 282 | $item2 = $this->Engine->pop(); 283 | $item2['attempts'] = 9; 284 | $item2['delay'] = $item2['options']['attempts_delay']; 285 | $this->assertTrue($this->Engine->release($item2)); 286 | 287 | $date = new \DateTime(); 288 | $date->modify('+10 minutes'); 289 | $sth = $this->execute($this->Engine->connection(), 'SELECT * FROM jobs WHERE id = ' . $item2['id']); 290 | $results = $sth->fetch(PDO::FETCH_ASSOC); 291 | $inTenMinutes = $date->format('Y-m-d H:i:s'); 292 | 293 | $this->assertEquals($inTenMinutes, $results['delay_until']); 294 | $this->assertEquals(9, $results['attempts']); 295 | } 296 | 297 | /** 298 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::queues 299 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::queues 300 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::queues 301 | */ 302 | public function testQueues() 303 | { 304 | if ($this->Engine->connection() === null) { 305 | $this->markTestSkipped('No connection to database available'); 306 | } 307 | $this->assertEquals([], $this->Engine->queues()); 308 | $this->Engine->push($this->Fixtures->default['first']); 309 | $this->assertEquals(['default'], $this->Engine->queues()); 310 | 311 | $this->Engine->push($this->Fixtures->other['second'], ['queue' => 'other']); 312 | $queues = $this->Engine->queues(); 313 | sort($queues); 314 | $this->assertEquals(['default', 'other'], $queues); 315 | 316 | $this->Engine->pop(); 317 | $this->Engine->pop(); 318 | $queues = $this->Engine->queues(); 319 | sort($queues); 320 | $this->assertEquals(['default', 'other'], $queues); 321 | } 322 | 323 | /** 324 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::cleanup 325 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::cleanup 326 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::cleanup 327 | */ 328 | public function testCleanup() 329 | { 330 | if ($this->Engine->connection() === null) { 331 | $this->markTestSkipped('No connection to database available'); 332 | } 333 | 334 | $this->Engine->push($this->Fixtures->default['first'], [ 335 | 'queue' => 'default', 336 | 'expires_in' => 2 337 | ]); 338 | $pop1 = $this->Engine->pop(); 339 | $this->assertEquals($pop1['id'], 1); 340 | 341 | $this->Engine->push($this->Fixtures->default['first'], [ 342 | 'queue' => 'default', 343 | 'expires_in' => 1 344 | ]); 345 | sleep(2); 346 | $pop2 = $this->Engine->pop(); 347 | $this->assertEquals($pop2, null); 348 | } 349 | 350 | protected function execute($connection, $sql) 351 | { 352 | if ($connection === null) { 353 | return; 354 | } 355 | 356 | $sql = trim($sql); 357 | try { 358 | $query = $connection->prepare($sql, []); 359 | $query->setFetchMode(PDO::FETCH_LAZY); 360 | if (!$query->execute([])) { 361 | $query->closeCursor(); 362 | 363 | return false; 364 | } 365 | if (!$query->columnCount()) { 366 | $query->closeCursor(); 367 | if (!$query->rowCount()) { 368 | return true; 369 | } 370 | } 371 | 372 | return $query; 373 | } catch (PDOException $e) { 374 | $e->queryString = $sql; 375 | if (isset($query->queryString)) { 376 | $e->queryString = $query->queryString; 377 | } 378 | throw $e; 379 | } 380 | } 381 | 382 | protected function expandFixtureData() 383 | { 384 | foreach ($this->Fixtures->default as &$default) { 385 | $default['options']['attempts_delay'] = 600; 386 | } 387 | foreach ($this->Fixtures->other as &$other) { 388 | $other['options']['attempts_delay'] = 600; 389 | } 390 | } 391 | 392 | protected function mockEngine($methods = null, $config = null) 393 | { 394 | if ($config === null) { 395 | $config = $this->config; 396 | } 397 | 398 | return $this->getMockBuilder($this->engineClass) 399 | ->setMethods($methods) 400 | ->setConstructorArgs([$this->Logger, $config]) 401 | ->getMock(); 402 | } 403 | } 404 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/BeanstalkEngineTest.php: -------------------------------------------------------------------------------- 1 | url = getenv('BEANSTALK_URL'); 16 | $this->config = ['url' => $this->url]; 17 | $this->Logger = new NullLogger; 18 | $this->engineClass = 'josegonzalez\Queuesadilla\Engine\BeanstalkEngine'; 19 | $this->Engine = $this->mockEngine(); 20 | $this->Fixtures = new FixtureData; 21 | $this->clearEngine(); 22 | } 23 | 24 | public function tearDown() : void 25 | { 26 | $this->clearEngine(); 27 | unset($this->Engine); 28 | } 29 | 30 | /** 31 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::__construct 32 | */ 33 | public function testConstruct() 34 | { 35 | $Engine = new BeanstalkEngine($this->Logger, []); 36 | $this->assertNotNull($Engine->connection()); 37 | 38 | $Engine = new BeanstalkEngine($this->Logger, $this->url); 39 | $this->assertNotNull($Engine->connection()); 40 | 41 | $Engine = new BeanstalkEngine($this->Logger, $this->config); 42 | $this->assertNotNull($Engine->connection()); 43 | } 44 | 45 | /** 46 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::connect 47 | */ 48 | public function testConnect() 49 | { 50 | $this->assertTrue($this->Engine->connect()); 51 | } 52 | 53 | /** 54 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::getJobClass 55 | */ 56 | public function testGetJobClass() 57 | { 58 | $this->assertEquals('\\josegonzalez\\Queuesadilla\\Job\\BeanstalkJob', $this->Engine->getJobClass()); 59 | } 60 | 61 | /** 62 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::acknowledge 63 | * @covers josegonzalez\Queuesadilla\Utility\Pheanstalk::deleteJob 64 | * @covers josegonzalez\Queuesadilla\Utility\Pheanstalk::protectedMethodCall 65 | */ 66 | public function testAcknowledge() 67 | { 68 | $this->assertFalse($this->Engine->acknowledge(null)); 69 | $this->assertFalse($this->Engine->acknowledge(false)); 70 | $this->assertFalse($this->Engine->acknowledge(1)); 71 | $this->assertFalse($this->Engine->acknowledge('string')); 72 | $this->assertFalse($this->Engine->acknowledge(['key' => 'value'])); 73 | $this->assertFalse($this->Engine->acknowledge($this->Fixtures->default['first'])); 74 | 75 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 76 | $job = new \Pheanstalk\Job($this->Engine->lastJobId(), ['queue' => 'default']); 77 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 78 | 79 | $data = $this->Fixtures->default['first']; 80 | $data['id'] = $job->getId(); 81 | $data['job'] = $job; 82 | $this->assertTrue($this->Engine->acknowledge($data)); 83 | } 84 | 85 | /** 86 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::reject 87 | * @covers josegonzalez\Queuesadilla\Utility\Pheanstalk::deleteJob 88 | * @covers josegonzalez\Queuesadilla\Utility\Pheanstalk::protectedMethodCall 89 | */ 90 | public function testReject() 91 | { 92 | $this->assertFalse($this->Engine->reject(null)); 93 | $this->assertFalse($this->Engine->reject(false)); 94 | $this->assertFalse($this->Engine->reject(1)); 95 | $this->assertFalse($this->Engine->reject('string')); 96 | $this->assertFalse($this->Engine->reject(['key' => 'value'])); 97 | $this->assertFalse($this->Engine->reject($this->Fixtures->default['first'])); 98 | 99 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 100 | $job = new \Pheanstalk\Job($this->Engine->lastJobId(), ['queue' => 'default']); 101 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 102 | 103 | $data = $this->Fixtures->default['first']; 104 | $data['id'] = $job->getId(); 105 | $data['job'] = $job; 106 | $this->assertTrue($this->Engine->reject($data)); 107 | } 108 | 109 | /** 110 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::pop 111 | */ 112 | public function testPop() 113 | { 114 | $this->assertNull($this->Engine->pop('default')); 115 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 116 | 117 | $item = $this->Engine->pop('default'); 118 | $this->assertIsArray($item); 119 | $this->assertArrayHasKey('class', $item); 120 | $this->assertArrayHasKey('args', $item); 121 | $this->assertArrayHasKey('job', $item); 122 | $this->assertInstanceOf('\Pheanstalk\Job', $item['job']); 123 | } 124 | 125 | /** 126 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::push 127 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::pop 128 | */ 129 | public function testPush() 130 | { 131 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 132 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], [ 133 | 'delay' => 30, 134 | ])); 135 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'], [ 136 | 'expires_in' => 1, 137 | ])); 138 | $this->assertTrue($this->Engine->push($this->Fixtures->default['fourth'], 'default')); 139 | 140 | sleep(2); 141 | 142 | $pop1 = $this->Engine->pop(); 143 | $pop2 = $this->Engine->pop(); 144 | $pop3 = $this->Engine->pop(); 145 | $pop4 = $this->Engine->pop(); 146 | 147 | $this->assertNotEmpty($pop1['id']); 148 | $this->assertNull($pop1['class']); 149 | $this->assertEmpty($pop1['args']); 150 | $this->assertNull($pop2); 151 | $this->assertEquals('yet_another_function', $pop3['class']); 152 | $this->assertNull($pop4); 153 | } 154 | 155 | /** 156 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::release 157 | * @covers josegonzalez\Queuesadilla\Utility\Pheanstalk::protectedMethodCall 158 | * @covers josegonzalez\Queuesadilla\Utility\Pheanstalk::releaseJob 159 | */ 160 | public function testRelease() 161 | { 162 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 163 | 164 | $item = $this->Engine->pop('default'); 165 | $this->assertInstanceOf('\Pheanstalk\Job', $item['job']); 166 | $this->assertTrue($this->Engine->release($item, 'default')); 167 | } 168 | 169 | /** 170 | * @covers josegonzalez\Queuesadilla\Engine\BeanstalkEngine::queues 171 | */ 172 | public function testQueues() 173 | { 174 | $this->assertEquals(['default'], $this->Engine->queues()); 175 | $this->Engine->push($this->Fixtures->default['first']); 176 | $this->assertEquals(['default'], $this->Engine->queues()); 177 | 178 | $this->Engine->push($this->Fixtures->other['second'], ['queue' => 'other']); 179 | $queues = $this->Engine->queues(); 180 | sort($queues); 181 | $this->assertEquals(['default', 'other'], $queues); 182 | 183 | $this->Engine->pop(); 184 | $this->Engine->pop(); 185 | $queues = $this->Engine->queues(); 186 | sort($queues); 187 | $this->assertEquals(['default', 'other'], $queues); 188 | } 189 | 190 | protected function clearEngine() 191 | { 192 | foreach ($this->Engine->queues() as $queue) { 193 | $this->Engine->connection()->useTube($queue); 194 | try { 195 | $job = $this->Engine->connection()->peekReady($queue); 196 | } catch (ServerException $e) { 197 | continue; 198 | } 199 | 200 | while (!empty($job)) { 201 | $this->Engine->connection()->deleteJob($job); 202 | try { 203 | $job = $this->Engine->connection()->peekReady($queue); 204 | } catch (ServerException $e) { 205 | break; 206 | } 207 | } 208 | } 209 | } 210 | 211 | protected function mockEngine($methods = null, $config = null) 212 | { 213 | if ($config === null) { 214 | $config = $this->config; 215 | } 216 | 217 | return $this->getMockBuilder($this->engineClass) 218 | ->setMethods($methods) 219 | ->setConstructorArgs([$this->Logger, $config]) 220 | ->getMock(); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/IronEngineTest.php: -------------------------------------------------------------------------------- 1 | Engine = new IronEngine(); 14 | } 15 | 16 | public function tearDown() : void 17 | { 18 | // unset($this->Engine); 19 | } 20 | 21 | public function testExample() 22 | { 23 | $this->markTestIncomplete('No tests written.'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/MemoryEngineTest.php: -------------------------------------------------------------------------------- 1 | url = getenv('MEMORY_URL'); 15 | $this->config = ['url' => $this->url]; 16 | $this->Logger = new NullLogger; 17 | $this->Engine = new MemoryEngine($this->Logger, $this->config); 18 | $this->Fixtures = new FixtureData; 19 | } 20 | 21 | public function tearDown() : void 22 | { 23 | unset($this->Engine); 24 | } 25 | 26 | /** 27 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::__construct 28 | */ 29 | public function testConstruct() 30 | { 31 | $Engine = new MemoryEngine($this->Logger, []); 32 | $this->assertNotNull($Engine->connection()); 33 | 34 | $Engine = new MemoryEngine($this->Logger, $this->url); 35 | $this->assertNotNull($Engine->connection()); 36 | 37 | $Engine = new MemoryEngine($this->Logger, $this->config); 38 | $this->assertNotNull($Engine->connection()); 39 | } 40 | 41 | /** 42 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::connect 43 | */ 44 | public function testConnect() 45 | { 46 | $this->assertTrue($this->Engine->connect()); 47 | } 48 | 49 | /** 50 | * @covers josegonzalez\Queuesadilla\Engine\Base::getJobClass 51 | */ 52 | public function testGetJobClass() 53 | { 54 | $this->assertEquals('\\josegonzalez\\Queuesadilla\\Job\\Base', $this->Engine->getJobClass()); 55 | } 56 | 57 | /** 58 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::acknowledge 59 | */ 60 | public function testAcknowledge() 61 | { 62 | $this->assertFalse($this->Engine->acknowledge(null)); 63 | $this->assertFalse($this->Engine->acknowledge(false)); 64 | $this->assertFalse($this->Engine->acknowledge(1)); 65 | $this->assertFalse($this->Engine->acknowledge('string')); 66 | $this->assertFalse($this->Engine->acknowledge(['key' => 'value'])); 67 | $this->assertFalse($this->Engine->acknowledge($this->Fixtures->default['first'])); 68 | 69 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 70 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 71 | $this->assertTrue($this->Engine->acknowledge($this->Fixtures->default['first'])); 72 | } 73 | 74 | /** 75 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::reject 76 | */ 77 | public function testReject() 78 | { 79 | $this->assertFalse($this->Engine->reject(null)); 80 | $this->assertFalse($this->Engine->reject(false)); 81 | $this->assertFalse($this->Engine->reject(1)); 82 | $this->assertFalse($this->Engine->reject('string')); 83 | $this->assertFalse($this->Engine->reject(['key' => 'value'])); 84 | $this->assertFalse($this->Engine->reject($this->Fixtures->default['first'])); 85 | 86 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 87 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 88 | $this->assertTrue($this->Engine->reject($this->Fixtures->default['first'])); 89 | } 90 | 91 | /** 92 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::pop 93 | */ 94 | public function testPop() 95 | { 96 | $this->assertNull($this->Engine->pop('default')); 97 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 98 | $this->assertEquals($this->Fixtures->default['first'], $this->Engine->pop('default')); 99 | } 100 | 101 | /** 102 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::push 103 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::pop 104 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::shouldDelay 105 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::shouldExpire 106 | */ 107 | public function testPush() 108 | { 109 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 110 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], [ 111 | 'delay' => 30, 112 | ])); 113 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'], [ 114 | 'expires_in' => 1, 115 | ])); 116 | $this->assertTrue($this->Engine->push($this->Fixtures->default['fourth'], 'default')); 117 | 118 | sleep(2); 119 | 120 | $pop1 = $this->Engine->pop(); 121 | $pop2 = $this->Engine->pop(); 122 | $pop3 = $this->Engine->pop(); 123 | $pop4 = $this->Engine->pop(); 124 | 125 | $this->assertNotEmpty($pop1['id']); 126 | $this->assertNull($pop1['class']); 127 | $this->assertEmpty($pop1['args']); 128 | $this->assertEquals('yet_another_function', $pop2['class']); 129 | $this->assertNull($pop3); 130 | $this->assertNull($pop4); 131 | } 132 | 133 | /** 134 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::release 135 | */ 136 | public function testRelease() 137 | { 138 | $this->assertFalse($this->Engine->release(null, 'default')); 139 | } 140 | 141 | /** 142 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::queues 143 | * @covers josegonzalez\Queuesadilla\Engine\MemoryEngine::requireQueue 144 | */ 145 | public function testQueues() 146 | { 147 | $this->assertEquals(['default'], $this->Engine->queues()); 148 | $this->Engine->push($this->Fixtures->default['first']); 149 | $this->assertEquals(['default'], $this->Engine->queues()); 150 | 151 | $this->Engine->push($this->Fixtures->other['second'], ['queue' => 'other']); 152 | $queues = $this->Engine->queues(); 153 | sort($queues); 154 | $this->assertEquals(['default', 'other'], $queues); 155 | 156 | $this->Engine->pop(); 157 | $this->Engine->pop(); 158 | $queues = $this->Engine->queues(); 159 | sort($queues); 160 | $this->assertEquals(['default', 'other'], $queues); 161 | } 162 | 163 | protected function mockEngine($methods = null, $config = null) 164 | { 165 | if ($config === null) { 166 | $config = $this->config; 167 | } 168 | 169 | return $this->getMockBuilder($this->engineClass) 170 | ->setMethods($methods) 171 | ->setConstructorArgs([$this->Logger, $config]) 172 | ->getMock(); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/MysqlEngineTest.php: -------------------------------------------------------------------------------- 1 | url = getenv('MYSQL_URL'); 15 | $this->engineClass = 'josegonzalez\Queuesadilla\Engine\MysqlEngine'; 16 | parent::setUp(); 17 | } 18 | 19 | protected function clearEngine() 20 | { 21 | $this->execute($this->Engine->connection(), 'TRUNCATE TABLE jobs'); 22 | } 23 | 24 | /** 25 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::quoteIdentifier 26 | * @covers josegonzalez\Queuesadilla\Engine\MysqlEngine::quoteIdentifier 27 | */ 28 | public function testQuoteIdentifier() 29 | { 30 | $this->assertEquals('*', $this->Engine->quoteIdentifier('*')); 31 | $this->assertEquals('', $this->Engine->quoteIdentifier('')); 32 | $this->assertEquals('`my_field`', $this->Engine->quoteIdentifier('my_field')); 33 | $this->assertEquals('`my_table`.`my_field`', $this->Engine->quoteIdentifier('my_table.my_field')); 34 | $this->assertEquals('`my_table`.*', $this->Engine->quoteIdentifier('my_table.*')); 35 | $this->assertEquals('`my_field`', $this->Engine->quoteIdentifier('`my_field`')); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/NullEngineTest.php: -------------------------------------------------------------------------------- 1 | url = getenv('NULL_URL'); 15 | $this->config = ['url' => $this->url]; 16 | $this->Logger = new NullLogger; 17 | $this->engineClass = 'josegonzalez\Queuesadilla\Engine\NullEngine'; 18 | $this->Engine = $this->mockEngine(); 19 | $this->Fixtures = new FixtureData; 20 | } 21 | 22 | public function tearDown() : void 23 | { 24 | unset($this->Engine); 25 | } 26 | 27 | /** 28 | * @covers josegonzalez\Queuesadilla\Engine\Base::__construct 29 | * @covers josegonzalez\Queuesadilla\Engine\Base::connection 30 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::__construct 31 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::connection 32 | */ 33 | public function testConstruct() 34 | { 35 | $Engine = new NullEngine($this->Logger, []); 36 | $this->assertNotNull($Engine->connection()); 37 | 38 | $Engine = new NullEngine($this->Logger, $this->url); 39 | $this->assertNotNull($Engine->connection()); 40 | 41 | $Engine = new NullEngine($this->Logger, $this->config); 42 | $this->assertNotNull($Engine->connection()); 43 | } 44 | 45 | /** 46 | * @covers josegonzalez\Queuesadilla\Engine\Base::connect 47 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::connect 48 | */ 49 | public function testConnect() 50 | { 51 | $this->assertTrue($this->Engine->connect()); 52 | 53 | $this->Engine->return = false; 54 | $this->assertFalse($this->Engine->connect()); 55 | } 56 | 57 | /** 58 | * @covers josegonzalez\Queuesadilla\Engine\Base::getJobClass 59 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::getJobClass 60 | */ 61 | public function testGetJobClass() 62 | { 63 | $this->assertEquals('\\josegonzalez\\Queuesadilla\\Job\\Base', $this->Engine->getJobClass()); 64 | } 65 | 66 | /** 67 | * @covers josegonzalez\Queuesadilla\Engine\Base::lastJobId 68 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::lastJobId 69 | */ 70 | public function testLastJobId() 71 | { 72 | $this->assertNull($this->Engine->lastJobId()); 73 | $this->assertTrue($this->Engine->push(null, 'default')); 74 | $this->assertTrue($this->Engine->lastJobId()); 75 | } 76 | 77 | /** 78 | * @covers josegonzalez\Queuesadilla\Engine\Base::acknowledge 79 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::acknowledge 80 | */ 81 | public function testAcknowledge() 82 | { 83 | $this->assertFalse($this->Engine->acknowledge(null)); 84 | $this->assertFalse($this->Engine->acknowledge(false)); 85 | $this->assertFalse($this->Engine->acknowledge(1)); 86 | $this->assertFalse($this->Engine->acknowledge('string')); 87 | $this->assertFalse($this->Engine->acknowledge(['key' => 'value'])); 88 | 89 | $this->assertTrue($this->Engine->acknowledge($this->Fixtures->default['first'])); 90 | $this->Engine->return = false; 91 | $this->assertFalse($this->Engine->acknowledge(null)); 92 | } 93 | 94 | /** 95 | * @covers josegonzalez\Queuesadilla\Engine\Base::reject 96 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::reject 97 | */ 98 | public function testReject() 99 | { 100 | $this->assertFalse($this->Engine->reject(null)); 101 | $this->assertFalse($this->Engine->reject(false)); 102 | $this->assertFalse($this->Engine->reject(1)); 103 | $this->assertFalse($this->Engine->reject('string')); 104 | $this->assertFalse($this->Engine->reject(['key' => 'value'])); 105 | 106 | $this->assertTrue($this->Engine->reject($this->Fixtures->default['first'])); 107 | $this->Engine->return = false; 108 | $this->assertFalse($this->Engine->reject(null)); 109 | } 110 | 111 | /** 112 | * @covers josegonzalez\Queuesadilla\Engine\Base::config 113 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::config 114 | */ 115 | public function testConfig() 116 | { 117 | $this->assertEquals([ 118 | 'queue' => 'default', 119 | 'timeout' => '1', 120 | 'scheme' => 'null', 121 | 'database' => false, 122 | ], $this->Engine->config()); 123 | $this->assertEquals([ 124 | 'queue' => 'other', 125 | 'timeout' => '1', 126 | 'scheme' => 'null', 127 | 'database' => false, 128 | ], $this->Engine->config(['queue' => 'other'])); 129 | $this->assertEquals('other', $this->Engine->config('queue')); 130 | $this->assertEquals('another', $this->Engine->config('queue', 'another')); 131 | $this->assertEquals(null, $this->Engine->config('random')); 132 | } 133 | 134 | /** 135 | * @covers josegonzalez\Queuesadilla\Engine\Base::setting 136 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::setting 137 | */ 138 | public function testSetting() 139 | { 140 | $this->assertEquals('string_to_array', $this->Engine->setting('string_to_array', 'queue')); 141 | $this->assertEquals('non_default', $this->Engine->setting(['queue' => 'non_default'], 'queue')); 142 | $this->assertEquals('default', $this->Engine->setting([], 'queue')); 143 | $this->assertEquals('other', $this->Engine->setting([], 'other', 'other')); 144 | } 145 | 146 | /** 147 | * @covers josegonzalez\Queuesadilla\Engine\Base::pop 148 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::pop 149 | */ 150 | public function testPop() 151 | { 152 | $this->assertTrue($this->Engine->pop('default')); 153 | 154 | $this->Engine->return = false; 155 | $this->assertFalse($this->Engine->pop('default')); 156 | } 157 | 158 | /** 159 | * @covers josegonzalez\Queuesadilla\Engine\Base::push 160 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::push 161 | */ 162 | public function testPush() 163 | { 164 | $this->assertTrue($this->Engine->push(null, 'default')); 165 | 166 | $this->Engine->return = false; 167 | $this->assertFalse($this->Engine->connect(null, 'default')); 168 | } 169 | 170 | /** 171 | * @covers josegonzalez\Queuesadilla\Engine\Base::release 172 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::release 173 | */ 174 | public function testRelease() 175 | { 176 | $this->assertTrue($this->Engine->release(null, 'default')); 177 | 178 | $this->Engine->return = false; 179 | $this->assertFalse($this->Engine->release(null, 'default')); 180 | } 181 | 182 | /** 183 | * @covers josegonzalez\Queuesadilla\Engine\Base::queues 184 | * @covers josegonzalez\Queuesadilla\Engine\NullEngine::queues 185 | */ 186 | public function testQueues() 187 | { 188 | $this->assertEquals([], $this->Engine->queues()); 189 | } 190 | 191 | protected function mockEngine($methods = null, $config = null) 192 | { 193 | if ($config === null) { 194 | $config = $this->config; 195 | } 196 | 197 | return $this->getMockBuilder($this->engineClass) 198 | ->setMethods($methods) 199 | ->setConstructorArgs([$this->Logger, $config]) 200 | ->getMock(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/PostgresEngineTest.php: -------------------------------------------------------------------------------- 1 | url = getenv('POSTGRES_URL'); 15 | $this->engineClass = 'josegonzalez\Queuesadilla\Engine\PostgresEngine'; 16 | parent::setUp(); 17 | } 18 | 19 | protected function clearEngine() 20 | { 21 | $this->execute($this->Engine->connection(), 'TRUNCATE TABLE jobs'); 22 | $this->execute($this->Engine->connection(), 'ALTER SEQUENCE jobs_id_seq RESTART WITH 1'); 23 | } 24 | 25 | /** 26 | * @covers josegonzalez\Queuesadilla\Engine\PdoEngine::quoteIdentifier 27 | * @covers josegonzalez\Queuesadilla\Engine\PostgresEngine::quoteIdentifier 28 | */ 29 | public function testQuoteIdentifier() 30 | { 31 | $this->assertEquals('*', $this->Engine->quoteIdentifier('*')); 32 | $this->assertEquals('', $this->Engine->quoteIdentifier('')); 33 | $this->assertEquals('"my_field"', $this->Engine->quoteIdentifier('my_field')); 34 | $this->assertEquals('"my_table"."my_field"', $this->Engine->quoteIdentifier('my_table.my_field')); 35 | $this->assertEquals('"my_table".*', $this->Engine->quoteIdentifier('my_table.*')); 36 | $this->assertEquals('"my_field"', $this->Engine->quoteIdentifier('"my_field"')); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/PredisEngineTest.php: -------------------------------------------------------------------------------- 1 | url = getenv('REDIS_URL'); 16 | $this->config = ['url' => $this->url]; 17 | $this->Logger = new NullLogger; 18 | $this->engineClass = 'josegonzalez\Queuesadilla\Engine\PredisEngine'; 19 | $this->Engine = $this->mockEngine(); 20 | $this->Fixtures = new FixtureData; 21 | $this->clearEngine(); 22 | } 23 | 24 | public function tearDown() : void 25 | { 26 | $this->clearEngine(); 27 | unset($this->Engine); 28 | } 29 | 30 | /** 31 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::__construct 32 | */ 33 | public function testConstruct() 34 | { 35 | $Engine = new PredisEngine($this->Logger, []); 36 | $this->assertNotNull($Engine->connection()); 37 | 38 | $Engine = new PredisEngine($this->Logger, $this->url); 39 | $this->assertNotNull($Engine->connection()); 40 | 41 | $Engine = new PredisEngine($this->Logger, $this->config); 42 | $this->assertNotNull($Engine->connection()); 43 | } 44 | 45 | /** 46 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::connect 47 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::redisInstance 48 | */ 49 | public function testConnect() 50 | { 51 | $this->assertTrue($this->Engine->connect()); 52 | 53 | $config = $this->config; 54 | $Engine = $this->mockEngine(null, $config); 55 | $Engine->config('database', 1); 56 | $this->assertTrue($Engine->connect()); 57 | 58 | $config = $this->config; 59 | $Engine = $this->mockEngine(null, $config); 60 | $Engine->config('persistent', false); 61 | $this->assertTrue($Engine->connect()); 62 | } 63 | 64 | /** 65 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::connect 66 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::redisInstance 67 | */ 68 | public function testConnectAuth() 69 | { 70 | $this->markTestIncomplete( 71 | 'Predis does not return false on invalid connection' 72 | ); 73 | 74 | $config = $this->config; 75 | $Engine = $this->mockEngine(null, $config); 76 | $Engine->config('pass', 'some_password'); 77 | $this->assertFalse($Engine->connect()); 78 | } 79 | 80 | /** 81 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::connect 82 | */ 83 | public function testConnectionException() 84 | { 85 | $Engine = $this->mockEngine(['redisInstance']); 86 | $Engine->expects($this->once()) 87 | ->method('redisInstance') 88 | ->will($this->throwException(new RedisException)); 89 | 90 | $this->assertFalse($Engine->connect()); 91 | } 92 | 93 | /** 94 | * @covers josegonzalez\Queuesadilla\Engine\Base::getJobClass 95 | */ 96 | public function testGetJobClass() 97 | { 98 | $this->assertEquals('\\josegonzalez\\Queuesadilla\\Job\\Base', $this->Engine->getJobClass()); 99 | } 100 | 101 | /** 102 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::acknowledge 103 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::evalSha 104 | */ 105 | public function testAcknowledge() 106 | { 107 | $this->assertFalse($this->Engine->acknowledge(null)); 108 | $this->assertFalse($this->Engine->acknowledge(false)); 109 | $this->assertFalse($this->Engine->acknowledge(1)); 110 | $this->assertFalse($this->Engine->acknowledge('string')); 111 | $this->assertFalse($this->Engine->acknowledge(['key' => 'value'])); 112 | $this->assertFalse($this->Engine->acknowledge($this->Fixtures->default['first'])); 113 | 114 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 115 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 116 | $this->assertTrue($this->Engine->acknowledge($this->Fixtures->default['first'])); 117 | } 118 | 119 | /** 120 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::reject 121 | */ 122 | public function testReject() 123 | { 124 | $this->assertFalse($this->Engine->reject(null)); 125 | $this->assertFalse($this->Engine->reject(false)); 126 | $this->assertFalse($this->Engine->reject(1)); 127 | $this->assertFalse($this->Engine->reject('string')); 128 | $this->assertFalse($this->Engine->reject(['key' => 'value'])); 129 | $this->assertFalse($this->Engine->reject($this->Fixtures->default['first'])); 130 | 131 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 132 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 133 | $this->assertTrue($this->Engine->reject($this->Fixtures->default['first'])); 134 | } 135 | 136 | /** 137 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::pop 138 | */ 139 | public function testPop() 140 | { 141 | $this->assertNull($this->Engine->pop('default')); 142 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 143 | $this->assertEquals($this->Fixtures->default['first'], $this->Engine->pop('default')); 144 | } 145 | 146 | /** 147 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::push 148 | */ 149 | public function testPush() 150 | { 151 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 152 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], [ 153 | 'delay' => 30, 154 | ])); 155 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'], [ 156 | 'expires_in' => 1, 157 | ])); 158 | $this->assertTrue($this->Engine->push($this->Fixtures->default['fourth'], 'default')); 159 | 160 | sleep(2); 161 | 162 | $pop1 = $this->Engine->pop(); 163 | $pop2 = $this->Engine->pop(); 164 | $pop3 = $this->Engine->pop(); 165 | $pop4 = $this->Engine->pop(); 166 | 167 | $this->assertNull($pop1['class']); 168 | $this->assertEmpty($pop1['args']); 169 | 170 | $this->markTestIncomplete( 171 | 'PredisEngine does not yet implement delay or expires_in (tbd sorted sets)' 172 | ); 173 | 174 | $this->assertEquals('yet_another_function', $pop2['class']); 175 | $this->assertNull($pop3); 176 | $this->assertNull($pop4); 177 | } 178 | 179 | /** 180 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::pop 181 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::release 182 | */ 183 | public function testRelease() 184 | { 185 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 186 | $this->assertEquals($this->Fixtures->default['first'], $this->Engine->pop('default')); 187 | 188 | $this->assertFalse($this->Engine->release(null, 'default')); 189 | 190 | $this->assertEquals(false, $this->Engine->release($this->Fixtures->default['second'], 'default')); 191 | $this->assertEquals(null, $this->Engine->pop('default')); 192 | 193 | $this->assertEquals(1, $this->Engine->release($this->Fixtures->default['fifth'], 'default')); 194 | $this->assertEquals($this->Fixtures->default['fifth'], $this->Engine->pop('default')); 195 | } 196 | 197 | /** 198 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::queues 199 | * @covers josegonzalez\Queuesadilla\Engine\PredisEngine::requireQueue 200 | */ 201 | public function testQueues() 202 | { 203 | $this->assertEquals([], $this->Engine->queues()); 204 | $this->Engine->push($this->Fixtures->default['first']); 205 | $this->assertEquals(['default'], $this->Engine->queues()); 206 | 207 | $this->Engine->push($this->Fixtures->other['second'], ['queue' => 'other']); 208 | $queues = $this->Engine->queues(); 209 | sort($queues); 210 | $this->assertEquals(['default', 'other'], $queues); 211 | 212 | $this->Engine->pop(); 213 | $this->Engine->pop(); 214 | $queues = $this->Engine->queues(); 215 | sort($queues); 216 | $this->assertEquals(['default', 'other'], $queues); 217 | } 218 | 219 | protected function clearEngine() 220 | { 221 | $this->Engine->connection()->flushdb(); 222 | $this->Engine->connection()->script('flush'); 223 | } 224 | 225 | protected function mockEngine($methods = null, $config = null) 226 | { 227 | if ($config === null) { 228 | $config = $this->config; 229 | } 230 | 231 | return $this->getMockBuilder($this->engineClass) 232 | ->setMethods($methods) 233 | ->setConstructorArgs([$this->Logger, $config]) 234 | ->getMock(); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/RedisEngineTest.php: -------------------------------------------------------------------------------- 1 | markTestSkipped('Redis extension is not installed or configured properly.'); 17 | } 18 | 19 | $this->url = getenv('REDIS_URL'); 20 | $this->config = ['url' => $this->url]; 21 | $this->Logger = new NullLogger; 22 | $this->engineClass = 'josegonzalez\Queuesadilla\Engine\RedisEngine'; 23 | $this->Engine = $this->mockEngine(); 24 | $this->Fixtures = new FixtureData; 25 | $this->clearEngine(); 26 | } 27 | 28 | public function tearDown() : void 29 | { 30 | $this->clearEngine(); 31 | unset($this->Engine); 32 | } 33 | 34 | /** 35 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::__construct 36 | */ 37 | public function testConstruct() 38 | { 39 | $Engine = new RedisEngine($this->Logger, []); 40 | $this->assertNotNull($Engine->connection()); 41 | 42 | $Engine = new RedisEngine($this->Logger, $this->url); 43 | $this->assertNotNull($Engine->connection()); 44 | 45 | $Engine = new RedisEngine($this->Logger, $this->config); 46 | $this->assertNotNull($Engine->connection()); 47 | } 48 | 49 | /** 50 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::connect 51 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::redisInstance 52 | */ 53 | public function testConnect() 54 | { 55 | $this->assertTrue($this->Engine->connect()); 56 | 57 | $config = $this->config; 58 | $Engine = $this->mockEngine(null, $config); 59 | $Engine->config('database', 1); 60 | $this->assertTrue($Engine->connect()); 61 | 62 | $config = $this->config; 63 | $Engine = $this->mockEngine(null, $config); 64 | $Engine->config('persistent', false); 65 | $this->assertTrue($Engine->connect()); 66 | } 67 | 68 | /** 69 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::connect 70 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::redisInstance 71 | */ 72 | public function testConnectAuth() 73 | { 74 | $config = $this->config; 75 | $Engine = $this->mockEngine(null, $config); 76 | $Engine->config('pass', 'some_password'); 77 | $this->assertFalse($Engine->connect()); 78 | } 79 | 80 | /** 81 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::connect 82 | */ 83 | public function testConnectionException() 84 | { 85 | $Engine = $this->mockEngine(['redisInstance']); 86 | $Engine->expects($this->once()) 87 | ->method('redisInstance') 88 | ->will($this->throwException(new RedisException)); 89 | 90 | $this->assertFalse($Engine->connect()); 91 | } 92 | 93 | /** 94 | * @covers josegonzalez\Queuesadilla\Engine\Base::getJobClass 95 | */ 96 | public function testGetJobClass() 97 | { 98 | $this->assertEquals('\\josegonzalez\\Queuesadilla\\Job\\Base', $this->Engine->getJobClass()); 99 | } 100 | 101 | /** 102 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::acknowledge 103 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::evalSha 104 | */ 105 | public function testAcknowledge() 106 | { 107 | $this->assertFalse($this->Engine->acknowledge(null)); 108 | $this->assertFalse($this->Engine->acknowledge(false)); 109 | $this->assertFalse($this->Engine->acknowledge(1)); 110 | $this->assertFalse($this->Engine->acknowledge('string')); 111 | $this->assertFalse($this->Engine->acknowledge(['key' => 'value'])); 112 | $this->assertFalse($this->Engine->acknowledge($this->Fixtures->default['first'])); 113 | 114 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 115 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 116 | $this->assertTrue($this->Engine->acknowledge($this->Fixtures->default['first'])); 117 | } 118 | 119 | /** 120 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::reject 121 | */ 122 | public function testReject() 123 | { 124 | $this->assertFalse($this->Engine->reject(null)); 125 | $this->assertFalse($this->Engine->reject(false)); 126 | $this->assertFalse($this->Engine->reject(1)); 127 | $this->assertFalse($this->Engine->reject('string')); 128 | $this->assertFalse($this->Engine->reject(['key' => 'value'])); 129 | $this->assertFalse($this->Engine->reject($this->Fixtures->default['first'])); 130 | 131 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'])); 132 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'])); 133 | $this->assertTrue($this->Engine->reject($this->Fixtures->default['first'])); 134 | } 135 | 136 | /** 137 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::pop 138 | */ 139 | public function testPop() 140 | { 141 | $this->assertNull($this->Engine->pop('default')); 142 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 143 | $this->assertEquals($this->Fixtures->default['first'], $this->Engine->pop('default')); 144 | } 145 | 146 | /** 147 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::push 148 | */ 149 | public function testPush() 150 | { 151 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 152 | $this->assertTrue($this->Engine->push($this->Fixtures->default['second'], [ 153 | 'delay' => 30, 154 | ])); 155 | $this->assertTrue($this->Engine->push($this->Fixtures->other['third'], [ 156 | 'expires_in' => 1, 157 | ])); 158 | $this->assertTrue($this->Engine->push($this->Fixtures->default['fourth'], 'default')); 159 | 160 | sleep(2); 161 | 162 | $pop1 = $this->Engine->pop(); 163 | $pop2 = $this->Engine->pop(); 164 | $pop3 = $this->Engine->pop(); 165 | $pop4 = $this->Engine->pop(); 166 | 167 | $this->assertNull($pop1['class']); 168 | $this->assertEmpty($pop1['args']); 169 | 170 | $this->markTestIncomplete( 171 | 'RedisEngine does not yet implement delay or expires_in (tbd sorted sets)' 172 | ); 173 | 174 | $this->assertEquals('yet_another_function', $pop2['class']); 175 | $this->assertNull($pop3); 176 | $this->assertNull($pop4); 177 | } 178 | 179 | /** 180 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::pop 181 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::release 182 | */ 183 | public function testRelease() 184 | { 185 | $this->assertTrue($this->Engine->push($this->Fixtures->default['first'], 'default')); 186 | $this->assertEquals($this->Fixtures->default['first'], $this->Engine->pop('default')); 187 | 188 | $this->assertFalse($this->Engine->release(null, 'default')); 189 | 190 | $this->assertEquals(false, $this->Engine->release($this->Fixtures->default['second'], 'default')); 191 | $this->assertEquals(null, $this->Engine->pop('default')); 192 | 193 | $this->assertEquals(1, $this->Engine->release($this->Fixtures->default['fifth'], 'default')); 194 | $this->assertEquals($this->Fixtures->default['fifth'], $this->Engine->pop('default')); 195 | } 196 | /** 197 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::queues 198 | * @covers josegonzalez\Queuesadilla\Engine\RedisEngine::requireQueue 199 | */ 200 | public function testQueues() 201 | { 202 | $this->assertEquals([], $this->Engine->queues()); 203 | $this->Engine->push($this->Fixtures->default['first']); 204 | $this->assertEquals(['default'], $this->Engine->queues()); 205 | 206 | $this->Engine->push($this->Fixtures->other['second'], ['queue' => 'other']); 207 | $queues = $this->Engine->queues(); 208 | sort($queues); 209 | $this->assertEquals(['default', 'other'], $queues); 210 | 211 | $this->Engine->pop(); 212 | $this->Engine->pop(); 213 | $queues = $this->Engine->queues(); 214 | sort($queues); 215 | $this->assertEquals(['default', 'other'], $queues); 216 | } 217 | 218 | protected function clearEngine() 219 | { 220 | $this->Engine->connection()->flushdb(); 221 | $this->Engine->connection()->script('flush'); 222 | } 223 | 224 | protected function mockEngine($methods = null, $config = null) 225 | { 226 | if ($config === null) { 227 | $config = $this->config; 228 | } 229 | 230 | return $this->getMockBuilder($this->engineClass) 231 | ->setMethods($methods) 232 | ->setConstructorArgs([$this->Logger, $config]) 233 | ->getMock(); 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Engine/SynchronousEngineTest.php: -------------------------------------------------------------------------------- 1 | url = getenv('SYNCHRONOUS_URL'); 19 | $this->config = ['url' => $this->url]; 20 | $this->Logger = new NullLogger; 21 | $this->engineClass = 'josegonzalez\Queuesadilla\Engine\SynchronousEngine'; 22 | $this->Engine = $this->mockEngine(['getWorker']); 23 | $this->Engine->expects($this->any()) 24 | ->method('getWorker') 25 | ->will($this->returnValue(new TestWorker($this->Engine))); 26 | $this->Fixtures = new FixtureData; 27 | } 28 | 29 | public function tearDown() : void 30 | { 31 | unset($this->Engine); 32 | } 33 | 34 | /** 35 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::__construct 36 | */ 37 | public function testConstruct() 38 | { 39 | $Engine = new SynchronousEngine($this->Logger, []); 40 | $this->assertNotNull($Engine->connection()); 41 | 42 | $Engine = new SynchronousEngine($this->Logger, $this->url); 43 | $this->assertNotNull($Engine->connection()); 44 | 45 | $Engine = new SynchronousEngine($this->Logger, $this->config); 46 | $this->assertNotNull($Engine->connection()); 47 | } 48 | 49 | /** 50 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::connect 51 | */ 52 | public function testConnect() 53 | { 54 | $this->assertTrue($this->Engine->connect()); 55 | } 56 | 57 | /** 58 | * @covers josegonzalez\Queuesadilla\Engine\Base::getJobClass 59 | */ 60 | public function testGetJobClass() 61 | { 62 | $this->assertEquals('\\josegonzalez\\Queuesadilla\\Job\\Base', $this->Engine->getJobClass()); 63 | } 64 | 65 | /** 66 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::acknowledge 67 | */ 68 | public function testAcknowledge() 69 | { 70 | $Engine = $this->mockEngine(); 71 | $this->assertFalse($Engine->acknowledge(null)); 72 | $this->assertFalse($Engine->acknowledge(false)); 73 | $this->assertFalse($Engine->acknowledge(1)); 74 | $this->assertFalse($Engine->acknowledge('string')); 75 | $this->assertFalse($Engine->acknowledge(['key' => 'value'])); 76 | $this->assertFalse($Engine->acknowledge($this->Fixtures->default['first'])); 77 | 78 | $this->assertTrue($Engine->push($this->Fixtures->default['first'])); 79 | $this->assertTrue($Engine->push($this->Fixtures->other['third'])); 80 | $this->assertFalse($Engine->acknowledge($this->Fixtures->default['first'])); 81 | } 82 | 83 | /** 84 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::reject 85 | */ 86 | public function testReject() 87 | { 88 | $Engine = $this->mockEngine(); 89 | $this->assertFalse($Engine->reject(null)); 90 | $this->assertFalse($Engine->reject(false)); 91 | $this->assertFalse($Engine->reject(1)); 92 | $this->assertFalse($Engine->reject('string')); 93 | $this->assertFalse($Engine->reject(['key' => 'value'])); 94 | $this->assertFalse($Engine->reject($this->Fixtures->default['first'])); 95 | 96 | $this->assertTrue($Engine->push($this->Fixtures->default['first'])); 97 | $this->assertTrue($Engine->push($this->Fixtures->other['third'])); 98 | $this->assertFalse($Engine->reject($this->Fixtures->default['first'])); 99 | } 100 | 101 | /** 102 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::push 103 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::pop 104 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::shouldDelay 105 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::shouldExpire 106 | */ 107 | public function testPush() 108 | { 109 | $Engine = $this->mockEngine(); 110 | $this->assertTrue($Engine->push($this->Fixtures->default['first'], 'default')); 111 | $this->assertTrue($Engine->push($this->Fixtures->default['second'], [ 112 | 'delay' => 30, 113 | ])); 114 | $this->assertTrue($Engine->push($this->Fixtures->other['third'], [ 115 | 'expires_in' => 1, 116 | ])); 117 | $this->assertTrue($Engine->push($this->Fixtures->default['fourth'], 'default')); 118 | 119 | sleep(2); 120 | 121 | $pop1 = $this->Engine->pop(); 122 | $pop2 = $this->Engine->pop(); 123 | $pop3 = $this->Engine->pop(); 124 | $pop4 = $this->Engine->pop(); 125 | 126 | $this->assertNull($pop1); 127 | $this->assertNull($pop2); 128 | $this->assertNull($pop3); 129 | $this->assertNull($pop4); 130 | } 131 | 132 | /** 133 | * @covers josegonzalez\Queuesadilla\Engine\SynchronousEngine::getWorker 134 | */ 135 | public function testGetWorker() 136 | { 137 | $Engine = new SynchronousEngine; 138 | $this->assertInstanceOf( 139 | '\josegonzalez\Queuesadilla\Worker\SequentialWorker', 140 | $this->protectedMethodCall($Engine, 'getWorker') 141 | ); 142 | } 143 | 144 | protected function mockEngine($methods = null, $config = null) 145 | { 146 | if ($config === null) { 147 | $config = $this->config; 148 | } 149 | 150 | return $this->getMockBuilder($this->engineClass) 151 | ->setMethods($methods) 152 | ->setConstructorArgs([$this->Logger, $config]) 153 | ->getMock(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Event/EventTest.php: -------------------------------------------------------------------------------- 1 | object = new stdClass(); 13 | $this->Event = new Event('test', $this->object, []); 14 | } 15 | 16 | public function tearDown() : void 17 | { 18 | unset($this->object); 19 | unset($this->Event); 20 | } 21 | 22 | /** 23 | * @covers josegonzalez\Queuesadilla\Event\Event::__construct 24 | */ 25 | public function testConstruct() 26 | { 27 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Event\Event', $this->Event); 28 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Event\Event', new Event('name')); 29 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Event\Event', new Event('name', $this->object)); 30 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Event\Event', new Event('name', $this->object, [])); 31 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Event\Event', new Event('name', $this->object, null)); 32 | } 33 | 34 | public function testGetter() 35 | { 36 | $this->assertEquals('test', $this->Event->name); 37 | $this->assertEquals($this->object, $this->Event->subject); 38 | $this->assertNull($this->Event->invalidAttribute); 39 | } 40 | 41 | /** 42 | * @covers josegonzalez\Queuesadilla\Event\Event::__get 43 | * @covers josegonzalez\Queuesadilla\Event\Event::getName 44 | * @covers josegonzalez\Queuesadilla\Event\Event::name 45 | */ 46 | public function testGetName() 47 | { 48 | $this->assertEquals('test', $this->Event->name); 49 | $this->assertEquals('test', $this->Event->getName()); 50 | $this->assertEquals('test', $this->Event->name()); 51 | } 52 | 53 | /** 54 | * @covers josegonzalez\Queuesadilla\Event\Event::__get 55 | * @covers josegonzalez\Queuesadilla\Event\Event::subject 56 | */ 57 | public function testGetSubject() 58 | { 59 | $this->assertEquals($this->object, $this->Event->subject); 60 | $this->assertEquals($this->object, $this->Event->subject()); 61 | } 62 | 63 | /** 64 | * @covers josegonzalez\Queuesadilla\Event\Event::isStopped 65 | */ 66 | public function testIsStopped() 67 | { 68 | $this->assertFalse($this->Event->isStopped()); 69 | $this->Event->stopPropagation(); 70 | $this->assertTrue($this->Event->isStopped()); 71 | } 72 | 73 | /** 74 | * @covers josegonzalez\Queuesadilla\Event\Event::data 75 | */ 76 | public function testData() 77 | { 78 | $this->assertIsArray($this->Event->data()); 79 | $this->assertIsArray($this->Event->data); 80 | $this->assertEquals([], $this->Event->data); 81 | 82 | $this->Event->data = ['test' => 'passed']; 83 | $this->assertEquals(['test' => 'passed'], $this->Event->data); 84 | 85 | $Event = new Event('test'); 86 | $this->assertIsArray($Event->data()); 87 | $this->assertNull($Event->data); 88 | 89 | $Event->data = ['test' => 'passed']; 90 | $this->assertIsArray($Event->data()); 91 | $this->assertIsArray($Event->data); 92 | $this->assertEquals(['test' => 'passed'], $Event->data); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/FixtureData.php: -------------------------------------------------------------------------------- 1 | ['id' => '1', 'class' => null, 'args' => [], 'options' => [], 'queue' => 'default', 'attempts' => 0], 9 | 'second' => ['id' => '2', 'class' => 'some_function', 'args' => [], 'options' => [], 'queue' => 'default', 'attempts' => 0], 10 | 'third' => ['id' => '3', 'class' => 'another_function', 'args' => [], 'options' => [], 'queue' => 'default', 'attempts' => 0], 11 | 'fourth' => ['id' => '4', 'class' => 'yet_another_function', 'args' => [], 'options' => [], 'queue' => 'default', 'attempts' => 0], 12 | 'fifth' => ['id' => '5', 'class' => 'some_function', 'args' => [], 'options' => [], 'queue' => 'default', 'attempts' => 1], 13 | ]; 14 | 15 | public $other = [ 16 | 'first' => ['id' => '1', 'class' => null, 'args' => [], 'options' => [], 'queue' => 'other', 'attempts' => 0], 17 | 'second' => ['id' => '2', 'class' => 'some_function', 'args' => [], 'options' => [], 'queue' => 'other', 'attempts' => 0], 18 | 'third' => ['id' => '3', 'class' => 'another_function', 'args' => [], 'options' => [], 'queue' => 'other', 'attempts' => 0], 19 | 'fourth' => ['id' => '4', 'class' => 'yet_another_function', 'args' => [], 'options' => [], 'queue' => 'other', 'attempts' => 0], 20 | ]; 21 | } 22 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Job/BaseTest.php: -------------------------------------------------------------------------------- 1 | 1, 18 | 'delay' => 0, 19 | 'class' => 'Foo', 20 | 'queue' => 'default', 21 | 'args' => [ 22 | [ 23 | 'foo' => 'bar', 24 | 'baz' => 'qux', 25 | ] 26 | ], 27 | ], 28 | [ 29 | 'id' => 2, 30 | 'attempts' => 0, 31 | 'delay' => 0, 32 | 'class' => 'Foo', 33 | 'queue' => 'default', 34 | 'args' => [ 35 | [ 36 | 'foo' => 'bar', 37 | 'baz' => 'qux', 38 | ] 39 | ], 40 | ], 41 | [ 42 | 'id' => 3, 43 | 'attempts' => 1, 44 | 'delay' => 0, 45 | 'class' => 'Foo', 46 | 'queue' => 'default', 47 | 'args' => [ 48 | [ 49 | 'foo' => 'bar', 50 | 'baz' => 'qux', 51 | ] 52 | ], 53 | ], 54 | ]; 55 | 56 | $this->Logger = new NullLogger; 57 | $this->Engine = new NullEngine($this->Logger, $config); 58 | $this->Jobs = [ 59 | new Base($items[0], $this->Engine), 60 | new Base($items[1], $this->Engine), 61 | new Base($items[2], $this->Engine), 62 | ]; 63 | } 64 | 65 | public function tearDown() : void 66 | { 67 | unset($this->Engine); 68 | unset($this->Jobs); 69 | } 70 | 71 | /** 72 | * @covers josegonzalez\Queuesadilla\Job\Base::__construct 73 | */ 74 | public function testConstruct() 75 | { 76 | $data = [ 77 | 'delay' => 0, 78 | 'class' => 'Foo', 79 | 'args' => [ 80 | [ 81 | 'foo' => 'bar', 82 | 'baz' => 'qux', 83 | ] 84 | ], 85 | ]; 86 | 87 | $job = new Base($data, $this->Engine); 88 | $this->assertEquals($data, $job->item()); 89 | } 90 | 91 | /** 92 | * @covers josegonzalez\Queuesadilla\Job\Base::attempts 93 | */ 94 | public function testAttempts() 95 | { 96 | $this->assertEquals(0, $this->Jobs[0]->attempts()); 97 | $this->assertEquals(0, $this->Jobs[1]->attempts()); 98 | $this->assertEquals(1, $this->Jobs[2]->attempts()); 99 | } 100 | 101 | /** 102 | * @covers josegonzalez\Queuesadilla\Job\Base::data 103 | */ 104 | public function testData() 105 | { 106 | $this->assertNull($this->Jobs[0]->data('unset_variable')); 107 | $this->assertTrue($this->Jobs[0]->data('unset_variable', true)); 108 | $this->assertEquals('bar', $this->Jobs[0]->data('foo')); 109 | $this->assertEquals('qux', $this->Jobs[0]->data('baz')); 110 | $this->assertEquals([ 111 | 'foo' => 'bar', 112 | 'baz' => 'qux', 113 | ], $this->Jobs[0]->data()); 114 | } 115 | 116 | /** 117 | * @covers josegonzalez\Queuesadilla\Job\Base::item 118 | */ 119 | public function testItem() 120 | { 121 | $this->assertEquals([ 122 | 'id' => 1, 123 | 'delay' => 0, 124 | 'class' => 'Foo', 125 | 'queue' => 'default', 126 | 'args' => [ 127 | [ 128 | 'foo' => 'bar', 129 | 'baz' => 'qux', 130 | ] 131 | ], 132 | ], $this->Jobs[0]->item()); 133 | 134 | $this->assertEquals([ 135 | 'id' => 2, 136 | 'attempts' => 0, 137 | 'delay' => 0, 138 | 'class' => 'Foo', 139 | 'queue' => 'default', 140 | 'args' => [ 141 | [ 142 | 'foo' => 'bar', 143 | 'baz' => 'qux', 144 | ] 145 | ], 146 | ], $this->Jobs[1]->item()); 147 | 148 | $this->assertEquals([ 149 | 'id' => 3, 150 | 'attempts' => 1, 151 | 'delay' => 0, 152 | 'class' => 'Foo', 153 | 'queue' => 'default', 154 | 'args' => [ 155 | [ 156 | 'foo' => 'bar', 157 | 'baz' => 'qux', 158 | ] 159 | ], 160 | ], $this->Jobs[2]->item()); 161 | } 162 | 163 | /** 164 | * @covers josegonzalez\Queuesadilla\Job\Base::acknowledge 165 | */ 166 | public function testAcknowledge() 167 | { 168 | $this->Engine->return = true; 169 | $this->assertTrue($this->Jobs[0]->acknowledge()); 170 | 171 | $this->Engine->return = false; 172 | $this->assertFalse($this->Jobs[0]->acknowledge()); 173 | } 174 | 175 | /** 176 | * @covers josegonzalez\Queuesadilla\Job\Base::reject 177 | */ 178 | public function testReject() 179 | { 180 | $this->Engine->return = true; 181 | $this->assertTrue($this->Jobs[0]->reject()); 182 | 183 | $this->Engine->return = false; 184 | $this->assertFalse($this->Jobs[0]->reject()); 185 | } 186 | 187 | /** 188 | * @covers josegonzalez\Queuesadilla\Job\Base::release 189 | */ 190 | public function testRelease() 191 | { 192 | $this->Engine->return = true; 193 | $this->assertTrue($this->Jobs[0]->release(10)); 194 | $this->assertEquals([ 195 | 'id' => 1, 196 | 'attempts' => 0, 197 | 'delay' => 10, 198 | 'class' => 'Foo', 199 | 'queue' => 'default', 200 | 'args' => [ 201 | [ 202 | 'foo' => 'bar', 203 | 'baz' => 'qux', 204 | ] 205 | ], 206 | ], $this->Jobs[0]->item()); 207 | 208 | $this->Engine->return = false; 209 | $this->assertFalse($this->Jobs[1]->release()); 210 | $this->assertEquals([ 211 | 'id' => 2, 212 | 'attempts' => 0, 213 | 'delay' => 0, 214 | 'class' => 'Foo', 215 | 'queue' => 'default', 216 | 'args' => [ 217 | [ 218 | 'foo' => 'bar', 219 | 'baz' => 'qux', 220 | ] 221 | ], 222 | ], $this->Jobs[1]->item()); 223 | 224 | $this->assertFalse($this->Jobs[2]->release()); 225 | $this->assertEquals([ 226 | 'id' => 3, 227 | 'attempts' => 0, 228 | 'delay' => 0, 229 | 'class' => 'Foo', 230 | 'queue' => 'default', 231 | 'args' => [ 232 | [ 233 | 'foo' => 'bar', 234 | 'baz' => 'qux', 235 | ] 236 | ], 237 | ], $this->Jobs[2]->item()); 238 | } 239 | 240 | /** 241 | * @covers josegonzalez\Queuesadilla\Job\Base::__toString 242 | */ 243 | public function testToString() 244 | { 245 | $this->assertEquals('{"id":1,"delay":0,"class":"Foo","queue":"default","args":[{"foo":"bar","baz":"qux"}]}', (string)$this->Jobs[0]); 246 | $this->assertEquals('{"id":2,"attempts":0,"delay":0,"class":"Foo","queue":"default","args":[{"foo":"bar","baz":"qux"}]}', (string)$this->Jobs[1]); 247 | $this->assertEquals('{"id":3,"attempts":1,"delay":0,"class":"Foo","queue":"default","args":[{"foo":"bar","baz":"qux"}]}', (string)$this->Jobs[2]); 248 | } 249 | 250 | /** 251 | * @covers josegonzalez\Queuesadilla\Job\Base::jsonSerialize 252 | */ 253 | public function testJsonSerialize() 254 | { 255 | $this->assertEquals('{"id":1,"delay":0,"class":"Foo","queue":"default","args":[{"foo":"bar","baz":"qux"}]}', json_encode($this->Jobs[0])); 256 | $this->assertEquals('{"id":2,"attempts":0,"delay":0,"class":"Foo","queue":"default","args":[{"foo":"bar","baz":"qux"}]}', json_encode($this->Jobs[1])); 257 | $this->assertEquals('{"id":3,"attempts":1,"delay":0,"class":"Foo","queue":"default","args":[{"foo":"bar","baz":"qux"}]}', json_encode($this->Jobs[2])); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/QueueTest.php: -------------------------------------------------------------------------------- 1 | Engine = new NullEngine; 14 | $this->Queue = new Queue($this->Engine); 15 | } 16 | 17 | /** 18 | * @covers josegonzalez\Queuesadilla\Queue::__construct 19 | */ 20 | public function testConstruct() 21 | { 22 | $Queue = new Queue($this->Engine); 23 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Queue', $Queue); 24 | } 25 | /** 26 | * @covers josegonzalez\Queuesadilla\Queue::push 27 | */ 28 | public function testPush() 29 | { 30 | $this->assertTrue($this->Queue->push([])); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/TestCase.php: -------------------------------------------------------------------------------- 1 | getMethod($methodName); 13 | $method->setAccessible(true); 14 | 15 | return $method->invokeArgs($object, $parameters); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Utility/DsnParserTraitTest.php: -------------------------------------------------------------------------------- 1 | getObjectForTrait('josegonzalez\Queuesadilla\Utility\DsnParserTrait'); 17 | $this->assertEquals([], $subject->parseDsn('')); 18 | 19 | $this->assertEquals([], $subject->parseDsn(':')); 20 | 21 | $dsn = 'test://user:pass@host:1'; 22 | $expected = [ 23 | 'host' => 'host', 24 | 'port' => 1, 25 | 'scheme' => 'test', 26 | 'user' => 'user', 27 | 'pass' => 'pass' 28 | ]; 29 | $this->assertEquals($expected, $subject->parseDsn($dsn)); 30 | 31 | $dsn = 'mysql://localhost:3306/database'; 32 | $expected = [ 33 | 'host' => 'localhost', 34 | 'database' => 'database', 35 | 'port' => 3306, 36 | 'scheme' => 'mysql', 37 | ]; 38 | $this->assertEquals($expected, $subject->parseDsn($dsn)); 39 | 40 | $dsn = 'mysql://user:password@localhost:3306/database'; 41 | $expected = [ 42 | 'host' => 'localhost', 43 | 'pass' => 'password', 44 | 'database' => 'database', 45 | 'port' => 3306, 46 | 'scheme' => 'mysql', 47 | 'user' => 'user', 48 | ]; 49 | $this->assertEquals($expected, $subject->parseDsn($dsn)); 50 | 51 | $dsn = 'sqlite:///memory:'; 52 | $expected = [ 53 | 'database' => 'memory:', 54 | 'scheme' => 'sqlite', 55 | ]; 56 | $this->assertEquals($expected, $subject->parseDsn($dsn)); 57 | 58 | $dsn = 'sqlite:///?database=memory:'; 59 | $expected = [ 60 | 'database' => 'memory:', 61 | 'scheme' => 'sqlite', 62 | ]; 63 | $this->assertEquals($expected, $subject->parseDsn($dsn)); 64 | 65 | $dsn = 'sqlserver://sa:Password12!@.\SQL2012SP1/cakephp?MultipleActiveResultSets=false'; 66 | $expected = [ 67 | 'host' => '.\SQL2012SP1', 68 | 'MultipleActiveResultSets' => false, 69 | 'pass' => 'Password12!', 70 | 'database' => 'cakephp', 71 | 'scheme' => 'sqlserver', 72 | 'user' => 'sa', 73 | ]; 74 | $this->assertEquals($expected, $subject->parseDsn($dsn)); 75 | } 76 | 77 | /** 78 | * @covers josegonzalez\Queuesadilla\Utility\DsnParserTrait::parseDsn 79 | */ 80 | public function testParseDsnThrowsException() 81 | { 82 | $this->expectException(InvalidArgumentException::class); 83 | $subject = $this->getObjectForTrait('josegonzalez\Queuesadilla\Utility\DsnParserTrait'); 84 | $this->assertEquals([], $subject->parseDsn(['not-empty'])); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Utility/LoggerTraitTest.php: -------------------------------------------------------------------------------- 1 | getObjectForTrait('josegonzalez\Queuesadilla\Utility\LoggerTrait'); 18 | $this->assertInstanceOf('Psr\Log\NullLogger', $subject->setLogger(null)); 19 | 20 | $logger = new Logger('test'); 21 | $subject = $this->getObjectForTrait('josegonzalez\Queuesadilla\Utility\LoggerTrait'); 22 | $this->assertEquals($logger, $subject->setLogger($logger)); 23 | $this->assertInstanceOf('Monolog\Logger', $subject->setLogger($logger)); 24 | 25 | $logger = new NullLogger; 26 | $subject = $this->getObjectForTrait('josegonzalez\Queuesadilla\Utility\LoggerTrait'); 27 | $this->assertEquals($logger, $subject->setLogger($logger)); 28 | $this->assertInstanceOf('Psr\Log\NullLogger', $subject->setLogger($logger)); 29 | } 30 | 31 | /** 32 | * @covers josegonzalez\Queuesadilla\Utility\LoggerTrait::logger 33 | */ 34 | public function testLogger() 35 | { 36 | $subject = $this->getObjectForTrait('josegonzalez\Queuesadilla\Utility\LoggerTrait'); 37 | $this->assertNull($subject->logger()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Worker/Listener/StatsListenerTest.php: -------------------------------------------------------------------------------- 1 | StatsListener = new StatsListener; 13 | } 14 | 15 | public function tearDown() : void 16 | { 17 | unset($this->StatsListener); 18 | } 19 | 20 | /** 21 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::__construct 22 | */ 23 | public function testConstruct() 24 | { 25 | $StatsListener = new StatsListener; 26 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Worker\Listener\StatsListener', $StatsListener); 27 | } 28 | 29 | /** 30 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::implementedEvents 31 | */ 32 | public function testImplementedEvents() 33 | { 34 | $this->assertEquals([ 35 | 'Worker.connectionFailed' => 'connectionFailed', 36 | 'Worker.maxIterations' => 'maxIterations', 37 | 'Worker.maxRuntime' => 'maxRuntime', 38 | 'Worker.job.seen' => 'jobSeen', 39 | 'Worker.job.empty' => 'jobEmpty', 40 | 'Worker.job.invalid' => 'jobInvalid', 41 | 'Worker.job.start' => 'jobStart', 42 | 'Worker.job.exception' => 'jobException', 43 | 'Worker.job.success' => 'jobSuccess', 44 | 'Worker.job.failure' => 'jobFailure', 45 | ], $this->StatsListener->implementedEvents()); 46 | } 47 | 48 | /** 49 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::connectionFailed 50 | */ 51 | public function testConnectionFailed() 52 | { 53 | $this->assertEquals([ 54 | 'connectionFailed' => 0, 55 | 'maxIterations' => 0, 56 | 'maxRuntime' => 0, 57 | 'seen' => 0, 58 | 'empty' => 0, 59 | 'exception' => 0, 60 | 'invalid' => 0, 61 | 'success' => 0, 62 | 'failure' => 0, 63 | ], $this->StatsListener->stats); 64 | 65 | $this->StatsListener->connectionFailed(); 66 | 67 | $this->assertEquals([ 68 | 'connectionFailed' => 1, 69 | 'maxIterations' => 0, 70 | 'maxRuntime' => 0, 71 | 'seen' => 0, 72 | 'empty' => 0, 73 | 'exception' => 0, 74 | 'invalid' => 0, 75 | 'success' => 0, 76 | 'failure' => 0, 77 | ], $this->StatsListener->stats); 78 | } 79 | 80 | /** 81 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::maxIterations 82 | */ 83 | public function testMaxIterations() 84 | { 85 | $this->assertEquals([ 86 | 'connectionFailed' => 0, 87 | 'maxIterations' => 0, 88 | 'maxRuntime' => 0, 89 | 'seen' => 0, 90 | 'empty' => 0, 91 | 'exception' => 0, 92 | 'invalid' => 0, 93 | 'success' => 0, 94 | 'failure' => 0, 95 | ], $this->StatsListener->stats); 96 | 97 | $this->StatsListener->maxIterations(); 98 | 99 | $this->assertEquals([ 100 | 'connectionFailed' => 0, 101 | 'maxIterations' => 1, 102 | 'maxRuntime' => 0, 103 | 'seen' => 0, 104 | 'empty' => 0, 105 | 'exception' => 0, 106 | 'invalid' => 0, 107 | 'success' => 0, 108 | 'failure' => 0, 109 | ], $this->StatsListener->stats); 110 | } 111 | 112 | /** 113 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::maxRuntime 114 | */ 115 | public function testMaxRuntime() 116 | { 117 | $this->assertEquals([ 118 | 'connectionFailed' => 0, 119 | 'maxIterations' => 0, 120 | 'maxRuntime' => 0, 121 | 'seen' => 0, 122 | 'empty' => 0, 123 | 'exception' => 0, 124 | 'invalid' => 0, 125 | 'success' => 0, 126 | 'failure' => 0, 127 | ], $this->StatsListener->stats); 128 | 129 | $this->StatsListener->maxRuntime(); 130 | 131 | $this->assertEquals([ 132 | 'connectionFailed' => 0, 133 | 'maxIterations' => 0, 134 | 'maxRuntime' => 1, 135 | 'seen' => 0, 136 | 'empty' => 0, 137 | 'exception' => 0, 138 | 'invalid' => 0, 139 | 'success' => 0, 140 | 'failure' => 0, 141 | ], $this->StatsListener->stats); 142 | } 143 | 144 | /** 145 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::jobSeen 146 | */ 147 | public function testJobSeen() 148 | { 149 | $this->assertEquals([ 150 | 'connectionFailed' => 0, 151 | 'maxIterations' => 0, 152 | 'maxRuntime' => 0, 153 | 'seen' => 0, 154 | 'empty' => 0, 155 | 'exception' => 0, 156 | 'invalid' => 0, 157 | 'success' => 0, 158 | 'failure' => 0, 159 | ], $this->StatsListener->stats); 160 | 161 | $this->StatsListener->jobSeen(); 162 | 163 | $this->assertEquals([ 164 | 'connectionFailed' => 0, 165 | 'maxIterations' => 0, 166 | 'maxRuntime' => 0, 167 | 'seen' => 1, 168 | 'empty' => 0, 169 | 'exception' => 0, 170 | 'invalid' => 0, 171 | 'success' => 0, 172 | 'failure' => 0, 173 | ], $this->StatsListener->stats); 174 | } 175 | 176 | /** 177 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::jobEmpty 178 | */ 179 | public function testJobEmpty() 180 | { 181 | $this->assertEquals([ 182 | 'connectionFailed' => 0, 183 | 'maxIterations' => 0, 184 | 'maxRuntime' => 0, 185 | 'seen' => 0, 186 | 'empty' => 0, 187 | 'exception' => 0, 188 | 'invalid' => 0, 189 | 'success' => 0, 190 | 'failure' => 0, 191 | ], $this->StatsListener->stats); 192 | 193 | $this->StatsListener->jobEmpty(); 194 | 195 | $this->assertEquals([ 196 | 'connectionFailed' => 0, 197 | 'maxIterations' => 0, 198 | 'maxRuntime' => 0, 199 | 'seen' => 0, 200 | 'empty' => 1, 201 | 'exception' => 0, 202 | 'invalid' => 0, 203 | 'success' => 0, 204 | 'failure' => 0, 205 | ], $this->StatsListener->stats); 206 | } 207 | 208 | /** 209 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::jobException 210 | */ 211 | public function testJobException() 212 | { 213 | $this->assertEquals([ 214 | 'connectionFailed' => 0, 215 | 'maxIterations' => 0, 216 | 'maxRuntime' => 0, 217 | 'seen' => 0, 218 | 'empty' => 0, 219 | 'exception' => 0, 220 | 'invalid' => 0, 221 | 'success' => 0, 222 | 'failure' => 0, 223 | ], $this->StatsListener->stats); 224 | 225 | $this->StatsListener->jobException(); 226 | 227 | $this->assertEquals([ 228 | 'connectionFailed' => 0, 229 | 'maxIterations' => 0, 230 | 'maxRuntime' => 0, 231 | 'seen' => 0, 232 | 'empty' => 0, 233 | 'exception' => 1, 234 | 'invalid' => 0, 235 | 'success' => 0, 236 | 'failure' => 0, 237 | ], $this->StatsListener->stats); 238 | } 239 | 240 | /** 241 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::jobInvalid 242 | */ 243 | public function testJobInvalid() 244 | { 245 | $this->assertEquals([ 246 | 'connectionFailed' => 0, 247 | 'maxIterations' => 0, 248 | 'maxRuntime' => 0, 249 | 'seen' => 0, 250 | 'empty' => 0, 251 | 'exception' => 0, 252 | 'invalid' => 0, 253 | 'success' => 0, 254 | 'failure' => 0, 255 | ], $this->StatsListener->stats); 256 | 257 | $this->StatsListener->jobInvalid(); 258 | 259 | $this->assertEquals([ 260 | 'connectionFailed' => 0, 261 | 'maxIterations' => 0, 262 | 'maxRuntime' => 0, 263 | 'seen' => 0, 264 | 'empty' => 0, 265 | 'exception' => 0, 266 | 'invalid' => 1, 267 | 'success' => 0, 268 | 'failure' => 0, 269 | ], $this->StatsListener->stats); 270 | } 271 | 272 | /** 273 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::jobSuccess 274 | */ 275 | public function testJobSuccess() 276 | { 277 | $this->assertEquals([ 278 | 'connectionFailed' => 0, 279 | 'maxIterations' => 0, 280 | 'maxRuntime' => 0, 281 | 'seen' => 0, 282 | 'empty' => 0, 283 | 'exception' => 0, 284 | 'invalid' => 0, 285 | 'success' => 0, 286 | 'failure' => 0, 287 | ], $this->StatsListener->stats); 288 | 289 | $this->StatsListener->jobSuccess(); 290 | 291 | $this->assertEquals([ 292 | 'connectionFailed' => 0, 293 | 'maxIterations' => 0, 294 | 'maxRuntime' => 0, 295 | 'seen' => 0, 296 | 'empty' => 0, 297 | 'exception' => 0, 298 | 'invalid' => 0, 299 | 'success' => 1, 300 | 'failure' => 0, 301 | ], $this->StatsListener->stats); 302 | } 303 | 304 | /** 305 | * @covers josegonzalez\Queuesadilla\Worker\Listener\StatsListener::jobFailure 306 | */ 307 | public function testJobFailure() 308 | { 309 | $this->assertEquals([ 310 | 'connectionFailed' => 0, 311 | 'maxIterations' => 0, 312 | 'maxRuntime' => 0, 313 | 'seen' => 0, 314 | 'empty' => 0, 315 | 'exception' => 0, 316 | 'invalid' => 0, 317 | 'success' => 0, 318 | 'failure' => 0, 319 | ], $this->StatsListener->stats); 320 | 321 | $this->StatsListener->jobFailure(); 322 | 323 | $this->assertEquals([ 324 | 'connectionFailed' => 0, 325 | 'maxIterations' => 0, 326 | 'maxRuntime' => 0, 327 | 'seen' => 0, 328 | 'empty' => 0, 329 | 'exception' => 0, 330 | 'invalid' => 0, 331 | 'success' => 0, 332 | 'failure' => 1, 333 | ], $this->StatsListener->stats); 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Worker/SequentialWorkerTest.php: -------------------------------------------------------------------------------- 1 | data('return'); 45 | } 46 | public function performStatic() 47 | { 48 | return true; 49 | } 50 | } 51 | 52 | class SequentialWorkerTest extends TestCase 53 | { 54 | public function setUp() : void 55 | { 56 | $this->Engine = new NullEngine; 57 | $this->Worker = new SequentialWorker($this->Engine); 58 | $this->Item = [ 59 | 'class' => ['josegonzalez\Queuesadilla\MyJob', 'perform'], 60 | 'args' => [['return' => true]], 61 | ]; 62 | $this->ItemFail = [ 63 | 'class' => ['josegonzalez\Queuesadilla\MyJob', 'performFail'], 64 | 'args' => [['return' => true]], 65 | ]; 66 | $this->ItemException = [ 67 | 'class' => ['josegonzalez\Queuesadilla\MyJob', 'performException'], 68 | 'args' => [['return' => true]], 69 | ]; 70 | $this->Job = new BaseJob($this->Item, $this->Engine); 71 | } 72 | 73 | public function tearDown() : void 74 | { 75 | unset($this->Engine); 76 | unset($this->Worker); 77 | } 78 | 79 | /** 80 | * @covers josegonzalez\Queuesadilla\Worker\Base::__construct 81 | * @covers josegonzalez\Queuesadilla\Worker\SequentialWorker::__construct 82 | */ 83 | public function testConstruct() 84 | { 85 | $Worker = new SequentialWorker($this->Engine); 86 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Worker\Base', $Worker); 87 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Worker\SequentialWorker', $Worker); 88 | $this->assertInstanceOf('\Psr\Log\LoggerInterface', $Worker->logger()); 89 | $this->assertInstanceOf('\Psr\Log\NullLogger', $Worker->logger()); 90 | } 91 | 92 | /** 93 | * @covers josegonzalez\Queuesadilla\Worker\SequentialWorker::work 94 | */ 95 | public function testWork() 96 | { 97 | $Engine = new NullEngine; 98 | $Engine->return = false; 99 | $Worker = new SequentialWorker($Engine); 100 | $this->assertFalse($Worker->work()); 101 | 102 | $Engine = $this->getMockBuilder('josegonzalez\Queuesadilla\Engine\NullEngine') 103 | ->setMethods(['pop']) 104 | ->getMock(); 105 | $Engine->expects($this->at(0)) 106 | ->method('pop') 107 | ->will($this->returnValue(true)); 108 | $Engine->expects($this->at(1)) 109 | ->method('pop') 110 | ->will($this->returnValue($this->Item)); 111 | $Engine->expects($this->at(2)) 112 | ->method('pop') 113 | ->will($this->returnValue($this->ItemFail)); 114 | $Engine->expects($this->at(3)) 115 | ->method('pop') 116 | ->will($this->returnValue($this->ItemException)); 117 | $Engine->expects($this->at(4)) 118 | ->method('pop') 119 | ->will($this->returnValue(false)); 120 | $Worker = new SequentialWorker($Engine, null, ['maxIterations' => 5]); 121 | $this->assertTrue($Worker->work()); 122 | $this->assertEquals([ 123 | 'seen' => 5, 124 | 'empty' => 1, 125 | 'exception' => 1, 126 | 'invalid' => 1, 127 | 'success' => 1, 128 | 'failure' => 2, 129 | 'connectionFailed' => 0, 130 | 'maxIterations' => 1, 131 | 'maxRuntime' => 0, 132 | ], $Worker->stats()); 133 | $Worker = new SequentialWorker($Engine, null, ['maxRuntime' => 5]); 134 | $this->assertTrue($Worker->work()); 135 | $this->assertEquals(1, $Worker->stats()['maxRuntime']); 136 | } 137 | 138 | /** 139 | * @covers josegonzalez\Queuesadilla\Worker\SequentialWorker::connect 140 | */ 141 | public function testConnect() 142 | { 143 | $this->assertTrue($this->Worker->connect()); 144 | 145 | $Engine = new NullEngine; 146 | $Engine->return = false; 147 | $Worker = new SequentialWorker($Engine); 148 | $this->assertFalse($Worker->connect()); 149 | } 150 | 151 | /** 152 | * @covers josegonzalez\Queuesadilla\Worker\SequentialWorker::perform 153 | */ 154 | public function testPerform() 155 | { 156 | $this->assertFalse($this->Worker->perform([ 157 | 'class' => 'josegonzalez\Queuesadilla\nonexistent_method' 158 | ], null)); 159 | $this->assertFalse($this->Worker->perform([ 160 | 'class' => 'josegonzalez\Queuesadilla\fail_method' 161 | ], null)); 162 | $this->assertTrue($this->Worker->perform([ 163 | 'class' => 'josegonzalez\Queuesadilla\null_method' 164 | ], null)); 165 | $this->assertTrue($this->Worker->perform([ 166 | 'class' => 'josegonzalez\Queuesadilla\true_method' 167 | ], null)); 168 | $this->assertFalse($this->Worker->perform([ 169 | 'class' => ['josegonzalez\Queuesadilla\MyJob', 'performFail'] 170 | ], null)); 171 | $this->assertTrue($this->Worker->perform([ 172 | 'class' => ['josegonzalez\Queuesadilla\MyJob', 'performTrue'] 173 | ], null)); 174 | $this->assertTrue($this->Worker->perform([ 175 | 'class' => ['josegonzalez\Queuesadilla\MyJob', 'performNull'] 176 | ], null)); 177 | $this->assertTrue($this->Worker->perform([ 178 | 'class' => ['josegonzalez\Queuesadilla\MyJob', 'perform'] 179 | ], $this->Job)); 180 | } 181 | 182 | /** 183 | * @covers josegonzalez\Queuesadilla\Worker\SequentialWorker::disconnect 184 | */ 185 | public function testDisconnect() 186 | { 187 | $this->assertNull($this->protectedMethodCall($this->Worker, 'disconnect')); 188 | } 189 | 190 | /** 191 | * @covers josegonzalez\Queuesadilla\Worker\SequentialWorker::signalHandler 192 | */ 193 | public function testSignalHandler() 194 | { 195 | $this->assertEquals(true, $this->Worker->signalHandler()); 196 | $this->assertEquals(true, $this->Worker->signalHandler(SIGQUIT)); 197 | $this->assertEquals(true, $this->Worker->signalHandler(SIGTERM)); 198 | $this->assertEquals(true, $this->Worker->signalHandler(SIGINT)); 199 | $this->assertEquals(true, $this->Worker->signalHandler(SIGUSR1)); 200 | } 201 | 202 | /** 203 | * @covers josegonzalez\Queuesadilla\Worker\SequentialWorker::shutdownHandler 204 | */ 205 | public function testShutdownHandler() 206 | { 207 | $this->assertEquals(true, $this->Worker->shutdownHandler()); 208 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGQUIT)); 209 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGTERM)); 210 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGINT)); 211 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGUSR1)); 212 | } 213 | 214 | /** 215 | * tests Worker.job.empty logs to debug 216 | * 217 | * @return void 218 | */ 219 | public function testJobEmptyEvent() 220 | { 221 | $Logger = $this->getMockBuilder(NullLogger::class) 222 | ->setMethods(['debug']) 223 | ->getMock(); 224 | $Logger 225 | ->expects($this->at(0)) 226 | ->method('debug') 227 | ->with('No job!'); 228 | 229 | $Engine = $this->getMockBuilder(NullEngine::class) 230 | ->setMethods(['pop']) 231 | ->getMock(); 232 | $Engine->expects($this->at(0)) 233 | ->method('pop') 234 | ->will($this->returnValue(false)); 235 | 236 | $Worker = new SequentialWorker($Engine, $Logger, ['maxIterations' => 1]); 237 | $this->assertTrue($Worker->work()); 238 | } 239 | 240 | /** 241 | * tests Worker.job.exception logs to alert 242 | * 243 | * @return void 244 | */ 245 | public function testJobExceptionEvent() 246 | { 247 | $Logger = $this->getMockBuilder(NullLogger::class) 248 | ->setMethods(['alert']) 249 | ->getMock(); 250 | $Logger 251 | ->expects($this->at(0)) 252 | ->method('alert') 253 | ->with('Exception: "Exception"'); 254 | 255 | $Engine = $this->getMockBuilder(NullEngine::class) 256 | ->setMethods(['pop']) 257 | ->getMock(); 258 | $Engine->expects($this->at(0)) 259 | ->method('pop') 260 | ->will($this->returnValue($this->ItemException)); 261 | 262 | $Worker = new SequentialWorker($Engine, $Logger, ['maxIterations' => 1]); 263 | $this->assertTrue($Worker->work()); 264 | } 265 | 266 | /** 267 | * tests Worker.job.success logs to debug 268 | * 269 | * @return void 270 | */ 271 | public function testJobSuccessEvent() 272 | { 273 | $Logger = $this->getMockBuilder(NullLogger::class) 274 | ->setMethods(['debug']) 275 | ->getMock(); 276 | $Logger 277 | ->expects($this->at(0)) 278 | ->method('debug') 279 | ->with('Success. Acknowledging job on queue.'); 280 | 281 | $Engine = $this->getMockBuilder(NullEngine::class) 282 | ->setMethods(['pop']) 283 | ->getMock(); 284 | $Engine->expects($this->at(0)) 285 | ->method('pop') 286 | ->will($this->returnValue($this->Item)); 287 | 288 | $Worker = new SequentialWorker($Engine, $Logger, ['maxIterations' => 1]); 289 | $this->assertTrue($Worker->work()); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /tests/josegonzalez/Queuesadilla/Worker/TestWorkerTest.php: -------------------------------------------------------------------------------- 1 | Engine = new NullEngine; 15 | $this->Worker = new TestWorker($this->Engine); 16 | } 17 | 18 | public function tearDown() : void 19 | { 20 | unset($this->Engine); 21 | unset($this->Worker); 22 | } 23 | 24 | /** 25 | * @covers josegonzalez\Queuesadilla\Worker\Base::__construct 26 | * @covers josegonzalez\Queuesadilla\Worker\TestWorker::__construct 27 | */ 28 | public function testConstruct() 29 | { 30 | $this->assertInstanceOf('\josegonzalez\Queuesadilla\Worker\Base', $this->Worker); 31 | $this->assertInstanceOf('\Psr\Log\LoggerInterface', $this->Worker->logger()); 32 | $this->assertInstanceOf('\Psr\Log\NullLogger', $this->Worker->logger()); 33 | } 34 | 35 | /** 36 | * @covers josegonzalez\Queuesadilla\Worker\Base::work 37 | * @covers josegonzalez\Queuesadilla\Worker\TestWorker::work 38 | */ 39 | public function testWork() 40 | { 41 | $this->assertTrue($this->Worker->work()); 42 | $this->assertTrue($this->Worker->work()); 43 | $this->assertTrue($this->Worker->work()); 44 | } 45 | 46 | /** 47 | * @covers josegonzalez\Queuesadilla\Worker\Base::stats 48 | */ 49 | public function testStats() 50 | { 51 | $this->assertEquals([ 52 | 'seen' => 0, 53 | 'empty' => 0, 54 | 'exception' => 0, 55 | 'invalid' => 0, 56 | 'success' => 0, 57 | 'failure' => 0, 58 | 'connectionFailed' => 0, 59 | 'maxIterations' => 0, 60 | 'maxRuntime' => 0, 61 | ], $this->Worker->stats()); 62 | } 63 | 64 | /** 65 | * @covers josegonzalez\Queuesadilla\Worker\Base::shutdownHandler 66 | */ 67 | public function testShutdownHandler() 68 | { 69 | $this->assertEquals(true, $this->Worker->shutdownHandler()); 70 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGQUIT)); 71 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGTERM)); 72 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGINT)); 73 | $this->assertEquals(true, $this->Worker->shutdownHandler(SIGUSR1)); 74 | } 75 | 76 | /** 77 | * @covers josegonzalez\Queuesadilla\Worker\Base::disconnect 78 | * @covers josegonzalez\Queuesadilla\Worker\TestWorker::disconnect 79 | */ 80 | public function testDisconnect() 81 | { 82 | $this->assertTrue($this->protectedMethodCall($this->Worker, 'disconnect')); 83 | } 84 | } 85 | --------------------------------------------------------------------------------