├── .env.dist ├── .github └── FUNDING.yml ├── .gitignore ├── .php_cs.dist ├── LICENSE ├── README.md ├── app ├── app.php ├── config │ ├── commands.php │ ├── executors.php │ ├── jobqueue.php │ ├── jobserver_config.lua.dist │ ├── local.php.dist │ └── settings.php ├── jobserver.lua ├── jobserver_instance.lua └── monitor.lua ├── composer.json ├── composer.lock ├── docker-compose.override.yml.dist ├── docker-compose.override.yml.full.dist ├── docker-compose.yml ├── docker └── worker │ ├── Dockerfile │ └── dev.ini ├── jobserver ├── phpunit.xml.dist ├── res ├── grafana │ ├── provisioning │ │ ├── dashboards │ │ │ ├── dashboards.yaml │ │ │ └── jobserver.json │ │ └── datasources │ │ │ └── datasources.yaml │ └── screenshot.png ├── haproxy │ └── haproxy.cfg ├── logrotate.d │ └── logs.tpl ├── prometheus │ ├── alert.rules │ ├── alertmanager.yml.dist │ └── prometheus.yml └── systemd │ ├── jobserver-worker.service.tpl │ └── jobserver-worker.tpl ├── src ├── Di │ ├── Config.php │ ├── Container.php │ ├── ContainerBuilder.php │ ├── Handlers.php │ ├── JobQueue.php │ ├── Logging.php │ └── Options.php ├── JobQueue │ └── Executor │ │ ├── CallbackResolver │ │ └── ContainerCallbackResolver.php │ │ └── ExecutorFactory.php └── UseCase │ └── Greet │ ├── GreetCommand.php │ ├── GreetHandler.php │ └── GreetHandlerFactory.php ├── tests └── Unit │ ├── Di │ ├── ContainerBuilderTest.php │ └── ContainerTest.php │ └── UseCase │ └── Greet │ └── GreetHandlerTest.php └── var └── log └── .gitkeep /.env.dist: -------------------------------------------------------------------------------- 1 | # Default values for any environment variables referenced in the Compose file 2 | # https://docs.docker.com/compose/environment-variables/#setting-environment-variables-with-docker-compose-run 3 | 4 | TARANTOOL_USER_NAME=jobserver 5 | TARANTOOL_USER_PASSWORD=jobserver 6 | TARANTOOL_MONITOR_HOST=0.0.0.0 7 | TARANTOOL_MONITOR_PORT=8080 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [rybakit] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /app/config/jobserver_config.lua 2 | /app/config/local.php 3 | /res/prometheus/alertmanager.yml 4 | /var/ 5 | /vendor/ 6 | /.env 7 | /.php_cs 8 | /docker-compose.override.yml 9 | /phpunit.xml 10 | -------------------------------------------------------------------------------- /.php_cs.dist: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ->exclude('docker') 6 | ->exclude('res') 7 | ->exclude('var') 8 | ; 9 | 10 | return PhpCsFixer\Config::create() 11 | ->setFinder($finder) 12 | ->setRiskyAllowed(true) 13 | ->setRules([ 14 | '@Symfony' => true, 15 | '@Symfony:risky' => true, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'no_useless_else' => true, 18 | 'no_useless_return' => true, 19 | 'ordered_imports' => true, 20 | 'phpdoc_order' => true, 21 | 'php_unit_strict' => true, 22 | 'strict_comparison' => true, 23 | ]) 24 | ->setCacheFile(__DIR__.'/var/.php_cs.cache') 25 | ; 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2020 Eugene Leonovich 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JobServer 2 | 3 | JobServer is a skeleton repository used for creating and processing background jobs 4 | backed by [Tarantool](http://tarantool.org/). It contains configuration files and folders 5 | you will need for quick start from scratch. 6 | 7 | 8 | ## Installation 9 | 10 | The recommended way to create a new application is through [Composer](http://getcomposer.org): 11 | 12 | ```sh 13 | composer create-project tarantool/jobserver -s dev 14 | ``` 15 | 16 | 17 | ## Quick start 18 | 19 | First, create your own `docker-compose.override.yml` file by copying 20 | [docker-compose.override.yml.dist](docker-compose.override.yml.dist) (or 21 | [docker-compose.override.yml.full.dist](docker-compose.override.yml.full.dist) if you want to test the full setup 22 | including a Tarantool cluster with automatic failover and monitoring tools) and customize to your needs. 23 | Do the same for [.env.dist](.env.dist) and all `*.dist` files located in [app/config](app/config) and [res](res). 24 | 25 | Then, browse to the project directory and execute this command: 26 | 27 | ```sh 28 | docker-compose up -d 29 | ``` 30 | 31 | After the command has completed successfully, you'll have a running server ready to execute jobs. 32 | If you open a log file in follow mode (`tail -f var/log/workers.log`), you'll see something like the following: 33 | 34 | ```sh 35 | [2017-11-19 00:00:23] default:worker.DEBUG: Idling... [] [] 36 | [2017-11-19 00:00:24] default:worker.DEBUG: Idling... [] [] 37 | [2017-11-19 00:00:25] default:worker.DEBUG: Idling... [] [] 38 | ``` 39 | 40 | Let's now try to add a task to the queue. This repository comes with a [demo job](src/UseCase/Greet/GreetHandler.php) 41 | that writes a greeting to the log. By running the following command: 42 | 43 | 44 | ```sh 45 | docker-compose exec worker ./jobserver queue:put default -H tarantool \ 46 | '{"payload": {"service": "greet", "args": {"name": "foobar"}}}' 47 | ``` 48 | 49 | we add a task to the `default` queue with a job payload, 50 | where `greet` is a job name and `foobar` is an argument passing to a job callable. 51 | 52 | Now in the log you will see that the job is executed: 53 | 54 | ```sh 55 | [2017-11-19 00:00:32] jobserver.INFO: HELLO FOOBAR [] [] 56 | [2017-11-19 00:00:33] default:worker.DEBUG: Idling... [] [] 57 | [2017-11-19 00:00:34] default:worker.INFO: Task #0 was successfully processed. {"payload":{"args":{"name":"foobar"},"service":"greet"}} [] 58 | ``` 59 | 60 | Also, you can run the job directly in the console, bypassing the queue: 61 | 62 | ```sh 63 | docker-compose exec worker ./jobserver -vvv handler:greet foobar 64 | ``` 65 | 66 | To be able to run a job from the console, you need to write an adapter for the symfony command 67 | and register it in [app/config/commands.php](app/config/commands.php). This is how the adapter 68 | looks like for GreetHandler: [GreetCommand](src/UseCase/Greet/GreetCommand.php). 69 | 70 | To see a list of all registered commands, run: 71 | 72 | ```sh 73 | docker-compose exec worker ./jobserver 74 | ``` 75 | 76 | 77 | ## Tarantool 78 | 79 | To use a web interface for Tarantool, open your browser and access 80 | the [http://localhost:8001](http://localhost:8001/) address (make sure that 81 | Docker containers are running). 82 | 83 | To get into Tarantool console as admin on a running Docker container, execute: 84 | 85 | ```sh 86 | docker-compose exec tarantool tarantoolctl connect /var/run/tarantool/tarantool.sock 87 | ``` 88 | 89 | On a server: 90 | 91 | ```sh 92 | sudo tarantoolctl enter jobserver_instance 93 | ``` 94 | 95 | On the server as a job queue user: 96 | 97 | ```sh 98 | sudo tarantoolctl connect $TNT_JOBQUEUE_USER:$TNT_JOBQUEUE_PASSWORD@$TNT_JOBQUEUE_HOST:3301 99 | ``` 100 | 101 | 102 | ## Monitoring 103 | 104 | Open your browser and access: 105 | 106 | * [Prometheus](http://localhost:9090/) 107 | * [Alert manager](http://localhost:9093/) 108 | * [Grafana](http://localhost:3000/) 109 | 110 | ![Grafana](/res/grafana/screenshot.png) 111 | 112 | > *Make sure to use a copy of [docker-compose.override.yml.full.dist](docker-compose.override.yml.full.dist) 113 | > to have all monitoring containers running.* 114 | 115 | 116 | ## Testing 117 | 118 | ```sh 119 | docker-compose exec worker vendor/bin/phpunit 120 | ``` 121 | 122 | 123 | ## Debugging 124 | 125 | To debug a job runner, first, stop the worker container 126 | 127 | ```sh 128 | docker-compose stop worker 129 | ``` 130 | 131 | Then, start listening for php debug connections and then execute: 132 | 133 | ```sh 134 | LOCAL_IP= docker-compose run --rm worker bash -c ' \ 135 | TNT_JOBQUEUE_PASSWORD=jobserver \ 136 | vendor/bin/jobqueue run default \ 137 | --config app/config/jobqueue.php \ 138 | --executors-config app/config/executors.php \ 139 | --user jobserver \ 140 | --host tarantool \ 141 | ' 142 | ``` 143 | 144 | > *Check [this manual](https://confluence.jetbrains.com/display/PhpStorm/Simultaneous+debugging+sessions+with+PhpStorm) 145 | > to learn how to debug multiple processes (for example, the runner and background jobs) 146 | > simultaneously in PhpStorm.* 147 | 148 | 149 | ## License 150 | 151 | The library is released under the MIT License. See the bundled [LICENSE](LICENSE) file for details. 152 | -------------------------------------------------------------------------------- /app/app.php: -------------------------------------------------------------------------------- 1 | build(); 20 | } 21 | -------------------------------------------------------------------------------- /app/config/commands.php: -------------------------------------------------------------------------------- 1 | getGreetHandler()), 11 | ]; 12 | }; 13 | -------------------------------------------------------------------------------- /app/config/executors.php: -------------------------------------------------------------------------------- 1 | getJobQueueExecutor(); 4 | -------------------------------------------------------------------------------- /app/config/jobqueue.php: -------------------------------------------------------------------------------- 1 | getJobQueueConfigFactory(); 4 | -------------------------------------------------------------------------------- /app/config/jobserver_config.lua.dist: -------------------------------------------------------------------------------- 1 | local os = require('os') 2 | 3 | return { 4 | user = os.getenv('TARANTOOL_USER_NAME'), 5 | password = os.getenv('TARANTOOL_USER_PASSWORD'), 6 | monitor = { 7 | host = os.getenv('TARANTOOL_MONITOR_HOST'), 8 | port = os.getenv('TARANTOOL_MONITOR_PORT'), 9 | scrape_interval = 15 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/config/local.php.dist: -------------------------------------------------------------------------------- 1 | setEnv('dev') 9 | ->setDebug(true) 10 | ->set(Options::GREET_YELL, true) 11 | ; 12 | }; 13 | -------------------------------------------------------------------------------- /app/config/settings.php: -------------------------------------------------------------------------------- 1 | false, 8 | Options::LOGGER_FILE => "$rootDir/var/log/workers.log", 9 | ]; 10 | }; 11 | -------------------------------------------------------------------------------- /app/jobserver.lua: -------------------------------------------------------------------------------- 1 | queue = require('queue') 2 | 3 | local function start(config) 4 | box.once('jobserver:v1.0', function() 5 | local tube = queue.create_tube('default', 'fifottl', {if_not_exists = true}) 6 | -- temporary disabled as seems like this method is not yet released 7 | -- tube:grant(config.user) 8 | 9 | -- tube:put({recurrence = 60, payload = {service = 'greet', args = {name = 'Foobar'}}}, {ttr = 300}) 10 | end) 11 | end 12 | 13 | local function stop() 14 | end 15 | 16 | return { 17 | start = start, 18 | stop = stop, 19 | queue = queue 20 | } 21 | -------------------------------------------------------------------------------- /app/jobserver_instance.lua: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tarantool 2 | 3 | box.cfg { 4 | listen = 3301, 5 | log_level = 5 6 | } 7 | 8 | if jobserver ~= nil then 9 | -- hot code reload using tarantoolctl or dofile() 10 | 11 | -- unload old application 12 | jobserver.stop() 13 | -- clear cache for loaded modules and dependencies 14 | package.loaded['jobserver'] = nil 15 | end 16 | 17 | local config = require('jobserver_config') 18 | 19 | -- ensure a user exists 20 | if not box.schema.user.exists(config.user) then 21 | box.schema.user.create(config.user, {password = config.password}) 22 | box.schema.user.grant(config.user, 'read,write,execute', 'universe', nil) 23 | end 24 | 25 | -- load a new version of app and all dependencies 26 | jobserver = require('jobserver') 27 | jobserver.start(config) 28 | 29 | -- start monitoring 30 | if config.monitor then 31 | require('monitor').monitor( 32 | config.monitor.host, 33 | config.monitor.port, 34 | jobserver.queue, 35 | config.monitor.scrape_interval 36 | ) 37 | end 38 | -------------------------------------------------------------------------------- /app/monitor.lua: -------------------------------------------------------------------------------- 1 | local prom = require('prometheus') 2 | local http = require('http.server') 3 | local fiber = require('fiber') 4 | 5 | local function worker(queue, scrape_interval) 6 | local metrics = {} 7 | 8 | for tube in pairs(queue.stats()) do 9 | metrics[tube] = { 10 | task_ready = prom.gauge( 11 | "jobserver_" .. tube .. "_task_ready", 12 | "Number of ready tasks" 13 | ), 14 | task_taken = prom.gauge( 15 | "jobserver_" .. tube .. "_task_taken", 16 | "Number of taken tasks" 17 | ), 18 | task_done = prom.gauge( 19 | "jobserver_" .. tube .. "_task_done", 20 | "Number of done tasks" 21 | ), 22 | task_buried = prom.gauge( 23 | "jobserver_" .. tube .. "_task_buried", 24 | "Number of buried tasks" 25 | ), 26 | task_delayed = prom.gauge( 27 | "jobserver_" .. tube .. "_task_delayed", 28 | "Number of delayed tasks" 29 | ), 30 | task_total = prom.gauge( 31 | "jobserver_" .. tube .. "_task_total", 32 | "Total number of tasks" 33 | ) 34 | } 35 | end 36 | 37 | local stats 38 | while true do 39 | stats = queue.stats() 40 | 41 | for tube, metric in pairs(metrics) do 42 | metric.task_ready:set(stats[tube].tasks.ready, {tube}) 43 | metric.task_taken:set(stats[tube].tasks.taken, {tube}) 44 | metric.task_done:set(stats[tube].tasks.done, {tube}) 45 | metric.task_buried:set(stats[tube].tasks.buried, {tube}) 46 | metric.task_delayed:set(stats[tube].tasks.delayed, {tube}) 47 | metric.task_total:set(stats[tube].tasks.total, {tube}) 48 | end 49 | 50 | fiber.sleep(scrape_interval) 51 | end 52 | end 53 | 54 | local function monitor(host, port, queue, scrape_interval) 55 | local httpd = http.new(host, port) 56 | 57 | httpd:route({ path = '/metrics' }, prom.collect_http) 58 | httpd:start() 59 | 60 | fiber.create(worker, queue, scrape_interval) 61 | end 62 | 63 | return { 64 | monitor = monitor 65 | } 66 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tarantool/jobserver", 3 | "description": "A skeleton application for creating and processing background jobs.", 4 | "type": "project", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Eugene Leonovich", 9 | "email": "gen.work@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "php": "^7.1", 14 | "ocramius/package-versions": "^1.3", 15 | "rybakit/arguments-resolver": "^0.5.1", 16 | "symfony/console": "^3.4", 17 | "tarantool/jobqueue": "dev-listener" 18 | }, 19 | "require-dev": { 20 | "friendsofphp/php-cs-fixer": "^2.10", 21 | "phpunit/phpunit": "^6.5" 22 | }, 23 | "replace": { 24 | "symfony/polyfill-php70": "*" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "App\\": "src/" 29 | }, 30 | "files": [ 31 | "app/app.php" 32 | ] 33 | }, 34 | "autoload-dev" : { 35 | "psr-4": { 36 | "App\\Tests\\": "tests/" 37 | } 38 | }, 39 | "config": { 40 | "optimize-autoloader": true, 41 | "preferred-install": { 42 | "*": "dist" 43 | }, 44 | "sort-packages": true 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "cc024fe54cf12bd38e9ac25c27840b25", 8 | "packages": [ 9 | { 10 | "name": "amphp/amp", 11 | "version": "v2.0.6", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/amphp/amp.git", 15 | "reference": "4a742beb59615f36ed998e2dc210e36576e44c44" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/amphp/amp/zipball/4a742beb59615f36ed998e2dc210e36576e44c44", 20 | "reference": "4a742beb59615f36ed998e2dc210e36576e44c44", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=7" 25 | }, 26 | "require-dev": { 27 | "amphp/phpunit-util": "^1", 28 | "friendsofphp/php-cs-fixer": "^2.3", 29 | "phpstan/phpstan": "^0.8.5", 30 | "phpunit/phpunit": "^6.0.9", 31 | "react/promise": "^2" 32 | }, 33 | "type": "library", 34 | "extra": { 35 | "branch-alias": { 36 | "dev-master": "2.0.x-dev" 37 | } 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "Amp\\": "lib" 42 | }, 43 | "files": [ 44 | "lib/functions.php", 45 | "lib/Internal/functions.php" 46 | ] 47 | }, 48 | "notification-url": "https://packagist.org/downloads/", 49 | "license": [ 50 | "MIT" 51 | ], 52 | "authors": [ 53 | { 54 | "name": "Bob Weinand", 55 | "email": "bobwei9@hotmail.com" 56 | }, 57 | { 58 | "name": "Niklas Keller", 59 | "email": "me@kelunik.com" 60 | }, 61 | { 62 | "name": "Daniel Lowrey", 63 | "email": "rdlowrey@php.net" 64 | }, 65 | { 66 | "name": "Aaron Piotrowski", 67 | "email": "aaron@trowski.com" 68 | } 69 | ], 70 | "description": "A non-blocking concurrency framework for PHP applications.", 71 | "homepage": "http://amphp.org/amp", 72 | "keywords": [ 73 | "async", 74 | "asynchronous", 75 | "awaitable", 76 | "concurrency", 77 | "event", 78 | "event-loop", 79 | "future", 80 | "non-blocking", 81 | "promise" 82 | ], 83 | "time": "2018-01-27T19:18:05+00:00" 84 | }, 85 | { 86 | "name": "amphp/byte-stream", 87 | "version": "v1.3.1", 88 | "source": { 89 | "type": "git", 90 | "url": "https://github.com/amphp/byte-stream.git", 91 | "reference": "1b75b122e6f069e7d102eef065dc192119d99ca7" 92 | }, 93 | "dist": { 94 | "type": "zip", 95 | "url": "https://api.github.com/repos/amphp/byte-stream/zipball/1b75b122e6f069e7d102eef065dc192119d99ca7", 96 | "reference": "1b75b122e6f069e7d102eef065dc192119d99ca7", 97 | "shasum": "" 98 | }, 99 | "require": { 100 | "amphp/amp": "^2" 101 | }, 102 | "require-dev": { 103 | "amphp/phpunit-util": "^1", 104 | "friendsofphp/php-cs-fixer": "^2.3", 105 | "phpunit/phpunit": "^6" 106 | }, 107 | "type": "library", 108 | "autoload": { 109 | "psr-4": { 110 | "Amp\\ByteStream\\": "lib" 111 | }, 112 | "files": [ 113 | "lib/functions.php" 114 | ] 115 | }, 116 | "notification-url": "https://packagist.org/downloads/", 117 | "license": [ 118 | "MIT" 119 | ], 120 | "authors": [ 121 | { 122 | "name": "Niklas Keller", 123 | "email": "me@kelunik.com" 124 | }, 125 | { 126 | "name": "Aaron Piotrowski", 127 | "email": "aaron@trowski.com" 128 | } 129 | ], 130 | "description": "A stream abstraction to make working with non-blocking I/O simple.", 131 | "homepage": "http://amphp.org/byte-stream", 132 | "keywords": [ 133 | "amp", 134 | "amphp", 135 | "async", 136 | "io", 137 | "non-blocking", 138 | "stream" 139 | ], 140 | "time": "2018-04-04T05:33:09+00:00" 141 | }, 142 | { 143 | "name": "amphp/parallel", 144 | "version": "v0.1.8", 145 | "source": { 146 | "type": "git", 147 | "url": "https://github.com/amphp/parallel.git", 148 | "reference": "25b9a2a75c4a6ba4815775267822066abab1054a" 149 | }, 150 | "dist": { 151 | "type": "zip", 152 | "url": "https://api.github.com/repos/amphp/parallel/zipball/25b9a2a75c4a6ba4815775267822066abab1054a", 153 | "reference": "25b9a2a75c4a6ba4815775267822066abab1054a", 154 | "shasum": "" 155 | }, 156 | "require": { 157 | "amphp/amp": "^2", 158 | "amphp/byte-stream": "^1.1", 159 | "amphp/parser": "^1", 160 | "amphp/process": "^0.2" 161 | }, 162 | "require-dev": { 163 | "amphp/phpunit-util": "^1", 164 | "friendsofphp/php-cs-fixer": "^2.3", 165 | "phpunit/phpunit": "^6" 166 | }, 167 | "suggest": { 168 | "ext-pcntl": "Required for fork contexts", 169 | "ext-pthreads": "Required for thread contexts", 170 | "ext-sysvsem": "Required for fork synchronization", 171 | "ext-sysvshm": "Required for fork contexts" 172 | }, 173 | "type": "library", 174 | "extra": { 175 | "branch-alias": { 176 | "dev-master": "0.1.x-dev" 177 | } 178 | }, 179 | "autoload": { 180 | "psr-4": { 181 | "Amp\\Parallel\\": "lib" 182 | }, 183 | "files": [ 184 | "lib/Worker/functions.php" 185 | ] 186 | }, 187 | "notification-url": "https://packagist.org/downloads/", 188 | "license": [ 189 | "MIT" 190 | ], 191 | "authors": [ 192 | { 193 | "name": "Stephen Coakley", 194 | "email": "me@stephencoakley.com" 195 | }, 196 | { 197 | "name": "Aaron Piotrowski", 198 | "email": "aaron@trowski.com" 199 | } 200 | ], 201 | "description": "Parallel processing component for Amp.", 202 | "homepage": "https://github.com/amphp/parallel", 203 | "keywords": [ 204 | "async", 205 | "asynchronous", 206 | "concurrent", 207 | "multi-processing", 208 | "multi-threading" 209 | ], 210 | "time": "2017-07-18T03:59:56+00:00" 211 | }, 212 | { 213 | "name": "amphp/parser", 214 | "version": "v1.0.0", 215 | "source": { 216 | "type": "git", 217 | "url": "https://github.com/amphp/parser.git", 218 | "reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1" 219 | }, 220 | "dist": { 221 | "type": "zip", 222 | "url": "https://api.github.com/repos/amphp/parser/zipball/f83e68f03d5b8e8e0365b8792985a7f341c57ae1", 223 | "reference": "f83e68f03d5b8e8e0365b8792985a7f341c57ae1", 224 | "shasum": "" 225 | }, 226 | "require": { 227 | "php": ">=7" 228 | }, 229 | "require-dev": { 230 | "friendsofphp/php-cs-fixer": "^2.3", 231 | "phpunit/phpunit": "^6" 232 | }, 233 | "type": "library", 234 | "autoload": { 235 | "psr-4": { 236 | "Amp\\Parser\\": "lib" 237 | } 238 | }, 239 | "notification-url": "https://packagist.org/downloads/", 240 | "license": [ 241 | "MIT" 242 | ], 243 | "authors": [ 244 | { 245 | "name": "Niklas Keller", 246 | "email": "me@kelunik.com" 247 | }, 248 | { 249 | "name": "Aaron Piotrowski", 250 | "email": "aaron@trowski.com" 251 | } 252 | ], 253 | "description": "A generator parser to make streaming parsers simple.", 254 | "homepage": "https://github.com/amphp/parser", 255 | "keywords": [ 256 | "async", 257 | "non-blocking", 258 | "parser", 259 | "stream" 260 | ], 261 | "time": "2017-06-06T05:29:10+00:00" 262 | }, 263 | { 264 | "name": "amphp/process", 265 | "version": "v0.2.1", 266 | "source": { 267 | "type": "git", 268 | "url": "https://github.com/amphp/process.git", 269 | "reference": "5aa6040fcf5c98bfb4f4a8e68305cb6cd6a3d37a" 270 | }, 271 | "dist": { 272 | "type": "zip", 273 | "url": "https://api.github.com/repos/amphp/process/zipball/5aa6040fcf5c98bfb4f4a8e68305cb6cd6a3d37a", 274 | "reference": "5aa6040fcf5c98bfb4f4a8e68305cb6cd6a3d37a", 275 | "shasum": "" 276 | }, 277 | "require": { 278 | "amphp/amp": "^2", 279 | "amphp/byte-stream": "^1" 280 | }, 281 | "require-dev": { 282 | "amphp/phpunit-util": "^1", 283 | "friendsofphp/php-cs-fixer": "^2.3", 284 | "kelunik/fqn-check": "^0.1.3", 285 | "phpunit/phpunit": "^6" 286 | }, 287 | "type": "library", 288 | "autoload": { 289 | "psr-4": { 290 | "Amp\\Process\\": "lib" 291 | } 292 | }, 293 | "notification-url": "https://packagist.org/downloads/", 294 | "license": [ 295 | "MIT" 296 | ], 297 | "authors": [ 298 | { 299 | "name": "Bob Weinand", 300 | "email": "bobwei9@hotmail.com" 301 | }, 302 | { 303 | "name": "Aaron Piotrowski", 304 | "email": "aaron@trowski.com" 305 | } 306 | ], 307 | "description": "Asynchronous process manager", 308 | "homepage": "https://github.com/amphp/process", 309 | "time": "2017-07-18T03:37:19+00:00" 310 | }, 311 | { 312 | "name": "monolog/monolog", 313 | "version": "1.23.0", 314 | "source": { 315 | "type": "git", 316 | "url": "https://github.com/Seldaek/monolog.git", 317 | "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" 318 | }, 319 | "dist": { 320 | "type": "zip", 321 | "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", 322 | "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", 323 | "shasum": "" 324 | }, 325 | "require": { 326 | "php": ">=5.3.0", 327 | "psr/log": "~1.0" 328 | }, 329 | "provide": { 330 | "psr/log-implementation": "1.0.0" 331 | }, 332 | "require-dev": { 333 | "aws/aws-sdk-php": "^2.4.9 || ^3.0", 334 | "doctrine/couchdb": "~1.0@dev", 335 | "graylog2/gelf-php": "~1.0", 336 | "jakub-onderka/php-parallel-lint": "0.9", 337 | "php-amqplib/php-amqplib": "~2.4", 338 | "php-console/php-console": "^3.1.3", 339 | "phpunit/phpunit": "~4.5", 340 | "phpunit/phpunit-mock-objects": "2.3.0", 341 | "ruflin/elastica": ">=0.90 <3.0", 342 | "sentry/sentry": "^0.13", 343 | "swiftmailer/swiftmailer": "^5.3|^6.0" 344 | }, 345 | "suggest": { 346 | "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", 347 | "doctrine/couchdb": "Allow sending log messages to a CouchDB server", 348 | "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", 349 | "ext-mongo": "Allow sending log messages to a MongoDB server", 350 | "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", 351 | "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", 352 | "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", 353 | "php-console/php-console": "Allow sending log messages to Google Chrome", 354 | "rollbar/rollbar": "Allow sending log messages to Rollbar", 355 | "ruflin/elastica": "Allow sending log messages to an Elastic Search server", 356 | "sentry/sentry": "Allow sending log messages to a Sentry server" 357 | }, 358 | "type": "library", 359 | "extra": { 360 | "branch-alias": { 361 | "dev-master": "2.0.x-dev" 362 | } 363 | }, 364 | "autoload": { 365 | "psr-4": { 366 | "Monolog\\": "src/Monolog" 367 | } 368 | }, 369 | "notification-url": "https://packagist.org/downloads/", 370 | "license": [ 371 | "MIT" 372 | ], 373 | "authors": [ 374 | { 375 | "name": "Jordi Boggiano", 376 | "email": "j.boggiano@seld.be", 377 | "homepage": "http://seld.be" 378 | } 379 | ], 380 | "description": "Sends your logs to files, sockets, inboxes, databases and various web services", 381 | "homepage": "http://github.com/Seldaek/monolog", 382 | "keywords": [ 383 | "log", 384 | "logging", 385 | "psr-3" 386 | ], 387 | "time": "2017-06-19T01:22:40+00:00" 388 | }, 389 | { 390 | "name": "ocramius/package-versions", 391 | "version": "1.3.0", 392 | "source": { 393 | "type": "git", 394 | "url": "https://github.com/Ocramius/PackageVersions.git", 395 | "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" 396 | }, 397 | "dist": { 398 | "type": "zip", 399 | "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/4489d5002c49d55576fa0ba786f42dbb009be46f", 400 | "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f", 401 | "shasum": "" 402 | }, 403 | "require": { 404 | "composer-plugin-api": "^1.0.0", 405 | "php": "^7.1.0" 406 | }, 407 | "require-dev": { 408 | "composer/composer": "^1.6.3", 409 | "ext-zip": "*", 410 | "infection/infection": "^0.7.1", 411 | "phpunit/phpunit": "^7.0.0" 412 | }, 413 | "type": "composer-plugin", 414 | "extra": { 415 | "class": "PackageVersions\\Installer", 416 | "branch-alias": { 417 | "dev-master": "2.0.x-dev" 418 | } 419 | }, 420 | "autoload": { 421 | "psr-4": { 422 | "PackageVersions\\": "src/PackageVersions" 423 | } 424 | }, 425 | "notification-url": "https://packagist.org/downloads/", 426 | "license": [ 427 | "MIT" 428 | ], 429 | "authors": [ 430 | { 431 | "name": "Marco Pivetta", 432 | "email": "ocramius@gmail.com" 433 | } 434 | ], 435 | "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", 436 | "time": "2018-02-05T13:05:30+00:00" 437 | }, 438 | { 439 | "name": "psr/log", 440 | "version": "1.0.2", 441 | "source": { 442 | "type": "git", 443 | "url": "https://github.com/php-fig/log.git", 444 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" 445 | }, 446 | "dist": { 447 | "type": "zip", 448 | "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 449 | "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", 450 | "shasum": "" 451 | }, 452 | "require": { 453 | "php": ">=5.3.0" 454 | }, 455 | "type": "library", 456 | "extra": { 457 | "branch-alias": { 458 | "dev-master": "1.0.x-dev" 459 | } 460 | }, 461 | "autoload": { 462 | "psr-4": { 463 | "Psr\\Log\\": "Psr/Log/" 464 | } 465 | }, 466 | "notification-url": "https://packagist.org/downloads/", 467 | "license": [ 468 | "MIT" 469 | ], 470 | "authors": [ 471 | { 472 | "name": "PHP-FIG", 473 | "homepage": "http://www.php-fig.org/" 474 | } 475 | ], 476 | "description": "Common interface for logging libraries", 477 | "homepage": "https://github.com/php-fig/log", 478 | "keywords": [ 479 | "log", 480 | "psr", 481 | "psr-3" 482 | ], 483 | "time": "2016-10-10T12:19:37+00:00" 484 | }, 485 | { 486 | "name": "rybakit/arguments-resolver", 487 | "version": "v0.5.1", 488 | "source": { 489 | "type": "git", 490 | "url": "https://github.com/rybakit/arguments-resolver.git", 491 | "reference": "5cb9f193794b62e42c0c7cf5c77d9366a4a4a236" 492 | }, 493 | "dist": { 494 | "type": "zip", 495 | "url": "https://api.github.com/repos/rybakit/arguments-resolver/zipball/5cb9f193794b62e42c0c7cf5c77d9366a4a4a236", 496 | "reference": "5cb9f193794b62e42c0c7cf5c77d9366a4a4a236", 497 | "shasum": "" 498 | }, 499 | "require": { 500 | "php": "^5.4|^7.0" 501 | }, 502 | "type": "library", 503 | "extra": { 504 | "branch-alias": { 505 | "dev-master": "1.0.x-dev" 506 | } 507 | }, 508 | "autoload": { 509 | "psr-4": { 510 | "ArgumentsResolver\\": "src/" 511 | } 512 | }, 513 | "notification-url": "https://packagist.org/downloads/", 514 | "license": [ 515 | "MIT" 516 | ], 517 | "authors": [ 518 | { 519 | "name": "Eugene Leonovich", 520 | "email": "gen.work@gmail.com" 521 | } 522 | ], 523 | "description": "ArgumentsResolver allows you to determine the arguments to pass to a function or method.", 524 | "homepage": "https://github.com/rybakit/arguments-resolver", 525 | "keywords": [ 526 | "arguments", 527 | "callable", 528 | "function", 529 | "injection", 530 | "invoke", 531 | "method", 532 | "parameters", 533 | "reflection", 534 | "resolver" 535 | ], 536 | "time": "2017-01-03T12:20:50+00:00" 537 | }, 538 | { 539 | "name": "rybakit/msgpack", 540 | "version": "v0.2.2", 541 | "source": { 542 | "type": "git", 543 | "url": "https://github.com/rybakit/msgpack.php.git", 544 | "reference": "62ef0f629dbbe855ffed38d1b4c4d1130b6195a9" 545 | }, 546 | "dist": { 547 | "type": "zip", 548 | "url": "https://api.github.com/repos/rybakit/msgpack.php/zipball/62ef0f629dbbe855ffed38d1b4c4d1130b6195a9", 549 | "reference": "62ef0f629dbbe855ffed38d1b4c4d1130b6195a9", 550 | "shasum": "" 551 | }, 552 | "require": { 553 | "php": "^5.4|^7.0" 554 | }, 555 | "type": "library", 556 | "autoload": { 557 | "psr-4": { 558 | "MessagePack\\": "src/" 559 | } 560 | }, 561 | "notification-url": "https://packagist.org/downloads/", 562 | "license": [ 563 | "MIT" 564 | ], 565 | "authors": [ 566 | { 567 | "name": "Eugene Leonovich", 568 | "email": "gen.work@gmail.com" 569 | } 570 | ], 571 | "description": "A pure PHP implementation of the MessagePack serialization format.", 572 | "keywords": [ 573 | "messagepack", 574 | "msgpack", 575 | "pure", 576 | "streaming" 577 | ], 578 | "time": "2017-03-11T15:48:37+00:00" 579 | }, 580 | { 581 | "name": "symfony/console", 582 | "version": "v3.4.8", 583 | "source": { 584 | "type": "git", 585 | "url": "https://github.com/symfony/console.git", 586 | "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf" 587 | }, 588 | "dist": { 589 | "type": "zip", 590 | "url": "https://api.github.com/repos/symfony/console/zipball/d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", 591 | "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", 592 | "shasum": "" 593 | }, 594 | "require": { 595 | "php": "^5.5.9|>=7.0.8", 596 | "symfony/debug": "~2.8|~3.0|~4.0", 597 | "symfony/polyfill-mbstring": "~1.0" 598 | }, 599 | "conflict": { 600 | "symfony/dependency-injection": "<3.4", 601 | "symfony/process": "<3.3" 602 | }, 603 | "require-dev": { 604 | "psr/log": "~1.0", 605 | "symfony/config": "~3.3|~4.0", 606 | "symfony/dependency-injection": "~3.4|~4.0", 607 | "symfony/event-dispatcher": "~2.8|~3.0|~4.0", 608 | "symfony/lock": "~3.4|~4.0", 609 | "symfony/process": "~3.3|~4.0" 610 | }, 611 | "suggest": { 612 | "psr/log": "For using the console logger", 613 | "symfony/event-dispatcher": "", 614 | "symfony/lock": "", 615 | "symfony/process": "" 616 | }, 617 | "type": "library", 618 | "extra": { 619 | "branch-alias": { 620 | "dev-master": "3.4-dev" 621 | } 622 | }, 623 | "autoload": { 624 | "psr-4": { 625 | "Symfony\\Component\\Console\\": "" 626 | }, 627 | "exclude-from-classmap": [ 628 | "/Tests/" 629 | ] 630 | }, 631 | "notification-url": "https://packagist.org/downloads/", 632 | "license": [ 633 | "MIT" 634 | ], 635 | "authors": [ 636 | { 637 | "name": "Fabien Potencier", 638 | "email": "fabien@symfony.com" 639 | }, 640 | { 641 | "name": "Symfony Community", 642 | "homepage": "https://symfony.com/contributors" 643 | } 644 | ], 645 | "description": "Symfony Console Component", 646 | "homepage": "https://symfony.com", 647 | "time": "2018-04-03T05:22:50+00:00" 648 | }, 649 | { 650 | "name": "symfony/debug", 651 | "version": "v4.0.8", 652 | "source": { 653 | "type": "git", 654 | "url": "https://github.com/symfony/debug.git", 655 | "reference": "5961d02d48828671f5d8a7805e06579d692f6ede" 656 | }, 657 | "dist": { 658 | "type": "zip", 659 | "url": "https://api.github.com/repos/symfony/debug/zipball/5961d02d48828671f5d8a7805e06579d692f6ede", 660 | "reference": "5961d02d48828671f5d8a7805e06579d692f6ede", 661 | "shasum": "" 662 | }, 663 | "require": { 664 | "php": "^7.1.3", 665 | "psr/log": "~1.0" 666 | }, 667 | "conflict": { 668 | "symfony/http-kernel": "<3.4" 669 | }, 670 | "require-dev": { 671 | "symfony/http-kernel": "~3.4|~4.0" 672 | }, 673 | "type": "library", 674 | "extra": { 675 | "branch-alias": { 676 | "dev-master": "4.0-dev" 677 | } 678 | }, 679 | "autoload": { 680 | "psr-4": { 681 | "Symfony\\Component\\Debug\\": "" 682 | }, 683 | "exclude-from-classmap": [ 684 | "/Tests/" 685 | ] 686 | }, 687 | "notification-url": "https://packagist.org/downloads/", 688 | "license": [ 689 | "MIT" 690 | ], 691 | "authors": [ 692 | { 693 | "name": "Fabien Potencier", 694 | "email": "fabien@symfony.com" 695 | }, 696 | { 697 | "name": "Symfony Community", 698 | "homepage": "https://symfony.com/contributors" 699 | } 700 | ], 701 | "description": "Symfony Debug Component", 702 | "homepage": "https://symfony.com", 703 | "time": "2018-04-03T05:24:00+00:00" 704 | }, 705 | { 706 | "name": "symfony/event-dispatcher", 707 | "version": "v3.4.8", 708 | "source": { 709 | "type": "git", 710 | "url": "https://github.com/symfony/event-dispatcher.git", 711 | "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8" 712 | }, 713 | "dist": { 714 | "type": "zip", 715 | "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/fdd5abcebd1061ec647089c6c41a07ed60af09f8", 716 | "reference": "fdd5abcebd1061ec647089c6c41a07ed60af09f8", 717 | "shasum": "" 718 | }, 719 | "require": { 720 | "php": "^5.5.9|>=7.0.8" 721 | }, 722 | "conflict": { 723 | "symfony/dependency-injection": "<3.3" 724 | }, 725 | "require-dev": { 726 | "psr/log": "~1.0", 727 | "symfony/config": "~2.8|~3.0|~4.0", 728 | "symfony/dependency-injection": "~3.3|~4.0", 729 | "symfony/expression-language": "~2.8|~3.0|~4.0", 730 | "symfony/stopwatch": "~2.8|~3.0|~4.0" 731 | }, 732 | "suggest": { 733 | "symfony/dependency-injection": "", 734 | "symfony/http-kernel": "" 735 | }, 736 | "type": "library", 737 | "extra": { 738 | "branch-alias": { 739 | "dev-master": "3.4-dev" 740 | } 741 | }, 742 | "autoload": { 743 | "psr-4": { 744 | "Symfony\\Component\\EventDispatcher\\": "" 745 | }, 746 | "exclude-from-classmap": [ 747 | "/Tests/" 748 | ] 749 | }, 750 | "notification-url": "https://packagist.org/downloads/", 751 | "license": [ 752 | "MIT" 753 | ], 754 | "authors": [ 755 | { 756 | "name": "Fabien Potencier", 757 | "email": "fabien@symfony.com" 758 | }, 759 | { 760 | "name": "Symfony Community", 761 | "homepage": "https://symfony.com/contributors" 762 | } 763 | ], 764 | "description": "Symfony EventDispatcher Component", 765 | "homepage": "https://symfony.com", 766 | "time": "2018-04-06T07:35:25+00:00" 767 | }, 768 | { 769 | "name": "symfony/polyfill-mbstring", 770 | "version": "v1.7.0", 771 | "source": { 772 | "type": "git", 773 | "url": "https://github.com/symfony/polyfill-mbstring.git", 774 | "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" 775 | }, 776 | "dist": { 777 | "type": "zip", 778 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", 779 | "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", 780 | "shasum": "" 781 | }, 782 | "require": { 783 | "php": ">=5.3.3" 784 | }, 785 | "suggest": { 786 | "ext-mbstring": "For best performance" 787 | }, 788 | "type": "library", 789 | "extra": { 790 | "branch-alias": { 791 | "dev-master": "1.7-dev" 792 | } 793 | }, 794 | "autoload": { 795 | "psr-4": { 796 | "Symfony\\Polyfill\\Mbstring\\": "" 797 | }, 798 | "files": [ 799 | "bootstrap.php" 800 | ] 801 | }, 802 | "notification-url": "https://packagist.org/downloads/", 803 | "license": [ 804 | "MIT" 805 | ], 806 | "authors": [ 807 | { 808 | "name": "Nicolas Grekas", 809 | "email": "p@tchwork.com" 810 | }, 811 | { 812 | "name": "Symfony Community", 813 | "homepage": "https://symfony.com/contributors" 814 | } 815 | ], 816 | "description": "Symfony polyfill for the Mbstring extension", 817 | "homepage": "https://symfony.com", 818 | "keywords": [ 819 | "compatibility", 820 | "mbstring", 821 | "polyfill", 822 | "portable", 823 | "shim" 824 | ], 825 | "time": "2018-01-30T19:27:44+00:00" 826 | }, 827 | { 828 | "name": "symfony/process", 829 | "version": "v3.4.8", 830 | "source": { 831 | "type": "git", 832 | "url": "https://github.com/symfony/process.git", 833 | "reference": "4b7d64e852886319e93ddfdecff0d744ab87658b" 834 | }, 835 | "dist": { 836 | "type": "zip", 837 | "url": "https://api.github.com/repos/symfony/process/zipball/4b7d64e852886319e93ddfdecff0d744ab87658b", 838 | "reference": "4b7d64e852886319e93ddfdecff0d744ab87658b", 839 | "shasum": "" 840 | }, 841 | "require": { 842 | "php": "^5.5.9|>=7.0.8" 843 | }, 844 | "type": "library", 845 | "extra": { 846 | "branch-alias": { 847 | "dev-master": "3.4-dev" 848 | } 849 | }, 850 | "autoload": { 851 | "psr-4": { 852 | "Symfony\\Component\\Process\\": "" 853 | }, 854 | "exclude-from-classmap": [ 855 | "/Tests/" 856 | ] 857 | }, 858 | "notification-url": "https://packagist.org/downloads/", 859 | "license": [ 860 | "MIT" 861 | ], 862 | "authors": [ 863 | { 864 | "name": "Fabien Potencier", 865 | "email": "fabien@symfony.com" 866 | }, 867 | { 868 | "name": "Symfony Community", 869 | "homepage": "https://symfony.com/contributors" 870 | } 871 | ], 872 | "description": "Symfony Process Component", 873 | "homepage": "https://symfony.com", 874 | "time": "2018-04-03T05:22:50+00:00" 875 | }, 876 | { 877 | "name": "tarantool/client", 878 | "version": "v0.4.1", 879 | "source": { 880 | "type": "git", 881 | "url": "https://github.com/tarantool-php/client.git", 882 | "reference": "3431c4435940cbfefc1c0ad2986fdf87b6d80669" 883 | }, 884 | "dist": { 885 | "type": "zip", 886 | "url": "https://api.github.com/repos/tarantool-php/client/zipball/3431c4435940cbfefc1c0ad2986fdf87b6d80669", 887 | "reference": "3431c4435940cbfefc1c0ad2986fdf87b6d80669", 888 | "shasum": "" 889 | }, 890 | "require": { 891 | "php": "^5.6|^7.0" 892 | }, 893 | "require-dev": { 894 | "ext-msgpack": "@dev", 895 | "rybakit/msgpack": "@dev" 896 | }, 897 | "suggest": { 898 | "ext-msgpack": "For using PeclPacker.", 899 | "rybakit/msgpack": "For using PurePacker." 900 | }, 901 | "type": "library", 902 | "autoload": { 903 | "psr-4": { 904 | "Tarantool\\Client\\": "src/" 905 | } 906 | }, 907 | "notification-url": "https://packagist.org/downloads/", 908 | "license": [ 909 | "MIT" 910 | ], 911 | "authors": [ 912 | { 913 | "name": "Eugene Leonovich", 914 | "email": "gen.work@gmail.com" 915 | } 916 | ], 917 | "description": "PHP client for Tarantool.", 918 | "keywords": [ 919 | "client", 920 | "nosql", 921 | "pure", 922 | "tarantool" 923 | ], 924 | "time": "2018-02-21T15:54:05+00:00" 925 | }, 926 | { 927 | "name": "tarantool/jobqueue", 928 | "version": "dev-listener", 929 | "source": { 930 | "type": "git", 931 | "url": "https://github.com/tarantool-php/jobqueue.git", 932 | "reference": "f7898243a7e14fcd641fba05ad41ef7d8d5b2ee6" 933 | }, 934 | "dist": { 935 | "type": "zip", 936 | "url": "https://api.github.com/repos/tarantool-php/jobqueue/zipball/f7898243a7e14fcd641fba05ad41ef7d8d5b2ee6", 937 | "reference": "f7898243a7e14fcd641fba05ad41ef7d8d5b2ee6", 938 | "shasum": "" 939 | }, 940 | "require": { 941 | "amphp/parallel": "^0.1.7", 942 | "monolog/monolog": "^1.22", 943 | "php": "^7.1", 944 | "psr/log": "^1.0", 945 | "rybakit/msgpack": "^0.2.2", 946 | "symfony/console": "^3.2", 947 | "symfony/event-dispatcher": "^3.3", 948 | "symfony/process": "^3.2", 949 | "tarantool/client": "^0.4.1", 950 | "tarantool/queue": "^0.6.0" 951 | }, 952 | "require-dev": { 953 | "phpunit/phpunit": "^6.0", 954 | "pimple/pimple": "^3.2", 955 | "psr/container": "^1.0", 956 | "rybakit/arguments-resolver": "^0.5.0" 957 | }, 958 | "suggest": { 959 | "psr/container": "For using CallbackExecutor\\ContainerCallbackResolver.", 960 | "rybakit/arguments-resolver": "For using CallbackExecutor.", 961 | "symfony/process": "For using ProcessExecutor." 962 | }, 963 | "bin": [ 964 | "jobqueue" 965 | ], 966 | "type": "library", 967 | "autoload": { 968 | "psr-4": { 969 | "Tarantool\\JobQueue\\": "src/" 970 | } 971 | }, 972 | "notification-url": "https://packagist.org/downloads/", 973 | "license": [ 974 | "MIT" 975 | ], 976 | "authors": [ 977 | { 978 | "name": "Eugene Leonovich", 979 | "email": "gen.work@gmail.com" 980 | } 981 | ], 982 | "description": "A job queue backed by Tarantool.", 983 | "keywords": [ 984 | "delayed", 985 | "job", 986 | "nosql", 987 | "priority", 988 | "queue", 989 | "schedule", 990 | "tarantool", 991 | "task", 992 | "ttl", 993 | "ttr", 994 | "worker" 995 | ], 996 | "time": "2018-04-10T15:12:38+00:00" 997 | }, 998 | { 999 | "name": "tarantool/queue", 1000 | "version": "v0.6.1", 1001 | "source": { 1002 | "type": "git", 1003 | "url": "https://github.com/tarantool-php/queue.git", 1004 | "reference": "0955b72e0ffebd1e0aeacdd7727b71920350c410" 1005 | }, 1006 | "dist": { 1007 | "type": "zip", 1008 | "url": "https://api.github.com/repos/tarantool-php/queue/zipball/0955b72e0ffebd1e0aeacdd7727b71920350c410", 1009 | "reference": "0955b72e0ffebd1e0aeacdd7727b71920350c410", 1010 | "shasum": "" 1011 | }, 1012 | "require": { 1013 | "php": "^5.4|^7.0" 1014 | }, 1015 | "require-dev": { 1016 | "rybakit/msgpack": "@dev", 1017 | "tarantool/client": "@dev" 1018 | }, 1019 | "suggest": { 1020 | "ext-tarantool": "For using pecl extension", 1021 | "tarantool/client": "For using pure client" 1022 | }, 1023 | "type": "library", 1024 | "autoload": { 1025 | "psr-4": { 1026 | "Tarantool\\Queue\\": "src/" 1027 | } 1028 | }, 1029 | "notification-url": "https://packagist.org/downloads/", 1030 | "license": [ 1031 | "MIT" 1032 | ], 1033 | "authors": [ 1034 | { 1035 | "name": "Eugene Leonovich", 1036 | "email": "gen.work@gmail.com" 1037 | } 1038 | ], 1039 | "description": "PHP bindings for Tarantool Queue.", 1040 | "keywords": [ 1041 | "delayed", 1042 | "job", 1043 | "nosql", 1044 | "priority", 1045 | "queue", 1046 | "schedule", 1047 | "tarantool", 1048 | "task", 1049 | "ttl", 1050 | "ttr", 1051 | "worker" 1052 | ], 1053 | "time": "2018-02-06T12:23:02+00:00" 1054 | } 1055 | ], 1056 | "packages-dev": [ 1057 | { 1058 | "name": "composer/semver", 1059 | "version": "1.4.2", 1060 | "source": { 1061 | "type": "git", 1062 | "url": "https://github.com/composer/semver.git", 1063 | "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" 1064 | }, 1065 | "dist": { 1066 | "type": "zip", 1067 | "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", 1068 | "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", 1069 | "shasum": "" 1070 | }, 1071 | "require": { 1072 | "php": "^5.3.2 || ^7.0" 1073 | }, 1074 | "require-dev": { 1075 | "phpunit/phpunit": "^4.5 || ^5.0.5", 1076 | "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" 1077 | }, 1078 | "type": "library", 1079 | "extra": { 1080 | "branch-alias": { 1081 | "dev-master": "1.x-dev" 1082 | } 1083 | }, 1084 | "autoload": { 1085 | "psr-4": { 1086 | "Composer\\Semver\\": "src" 1087 | } 1088 | }, 1089 | "notification-url": "https://packagist.org/downloads/", 1090 | "license": [ 1091 | "MIT" 1092 | ], 1093 | "authors": [ 1094 | { 1095 | "name": "Nils Adermann", 1096 | "email": "naderman@naderman.de", 1097 | "homepage": "http://www.naderman.de" 1098 | }, 1099 | { 1100 | "name": "Jordi Boggiano", 1101 | "email": "j.boggiano@seld.be", 1102 | "homepage": "http://seld.be" 1103 | }, 1104 | { 1105 | "name": "Rob Bast", 1106 | "email": "rob.bast@gmail.com", 1107 | "homepage": "http://robbast.nl" 1108 | } 1109 | ], 1110 | "description": "Semver library that offers utilities, version constraint parsing and validation.", 1111 | "keywords": [ 1112 | "semantic", 1113 | "semver", 1114 | "validation", 1115 | "versioning" 1116 | ], 1117 | "time": "2016-08-30T16:08:34+00:00" 1118 | }, 1119 | { 1120 | "name": "doctrine/annotations", 1121 | "version": "v1.6.0", 1122 | "source": { 1123 | "type": "git", 1124 | "url": "https://github.com/doctrine/annotations.git", 1125 | "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" 1126 | }, 1127 | "dist": { 1128 | "type": "zip", 1129 | "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", 1130 | "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", 1131 | "shasum": "" 1132 | }, 1133 | "require": { 1134 | "doctrine/lexer": "1.*", 1135 | "php": "^7.1" 1136 | }, 1137 | "require-dev": { 1138 | "doctrine/cache": "1.*", 1139 | "phpunit/phpunit": "^6.4" 1140 | }, 1141 | "type": "library", 1142 | "extra": { 1143 | "branch-alias": { 1144 | "dev-master": "1.6.x-dev" 1145 | } 1146 | }, 1147 | "autoload": { 1148 | "psr-4": { 1149 | "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" 1150 | } 1151 | }, 1152 | "notification-url": "https://packagist.org/downloads/", 1153 | "license": [ 1154 | "MIT" 1155 | ], 1156 | "authors": [ 1157 | { 1158 | "name": "Roman Borschel", 1159 | "email": "roman@code-factory.org" 1160 | }, 1161 | { 1162 | "name": "Benjamin Eberlei", 1163 | "email": "kontakt@beberlei.de" 1164 | }, 1165 | { 1166 | "name": "Guilherme Blanco", 1167 | "email": "guilhermeblanco@gmail.com" 1168 | }, 1169 | { 1170 | "name": "Jonathan Wage", 1171 | "email": "jonwage@gmail.com" 1172 | }, 1173 | { 1174 | "name": "Johannes Schmitt", 1175 | "email": "schmittjoh@gmail.com" 1176 | } 1177 | ], 1178 | "description": "Docblock Annotations Parser", 1179 | "homepage": "http://www.doctrine-project.org", 1180 | "keywords": [ 1181 | "annotations", 1182 | "docblock", 1183 | "parser" 1184 | ], 1185 | "time": "2017-12-06T07:11:42+00:00" 1186 | }, 1187 | { 1188 | "name": "doctrine/instantiator", 1189 | "version": "1.1.0", 1190 | "source": { 1191 | "type": "git", 1192 | "url": "https://github.com/doctrine/instantiator.git", 1193 | "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" 1194 | }, 1195 | "dist": { 1196 | "type": "zip", 1197 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", 1198 | "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", 1199 | "shasum": "" 1200 | }, 1201 | "require": { 1202 | "php": "^7.1" 1203 | }, 1204 | "require-dev": { 1205 | "athletic/athletic": "~0.1.8", 1206 | "ext-pdo": "*", 1207 | "ext-phar": "*", 1208 | "phpunit/phpunit": "^6.2.3", 1209 | "squizlabs/php_codesniffer": "^3.0.2" 1210 | }, 1211 | "type": "library", 1212 | "extra": { 1213 | "branch-alias": { 1214 | "dev-master": "1.2.x-dev" 1215 | } 1216 | }, 1217 | "autoload": { 1218 | "psr-4": { 1219 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 1220 | } 1221 | }, 1222 | "notification-url": "https://packagist.org/downloads/", 1223 | "license": [ 1224 | "MIT" 1225 | ], 1226 | "authors": [ 1227 | { 1228 | "name": "Marco Pivetta", 1229 | "email": "ocramius@gmail.com", 1230 | "homepage": "http://ocramius.github.com/" 1231 | } 1232 | ], 1233 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 1234 | "homepage": "https://github.com/doctrine/instantiator", 1235 | "keywords": [ 1236 | "constructor", 1237 | "instantiate" 1238 | ], 1239 | "time": "2017-07-22T11:58:36+00:00" 1240 | }, 1241 | { 1242 | "name": "doctrine/lexer", 1243 | "version": "v1.0.1", 1244 | "source": { 1245 | "type": "git", 1246 | "url": "https://github.com/doctrine/lexer.git", 1247 | "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" 1248 | }, 1249 | "dist": { 1250 | "type": "zip", 1251 | "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", 1252 | "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", 1253 | "shasum": "" 1254 | }, 1255 | "require": { 1256 | "php": ">=5.3.2" 1257 | }, 1258 | "type": "library", 1259 | "extra": { 1260 | "branch-alias": { 1261 | "dev-master": "1.0.x-dev" 1262 | } 1263 | }, 1264 | "autoload": { 1265 | "psr-0": { 1266 | "Doctrine\\Common\\Lexer\\": "lib/" 1267 | } 1268 | }, 1269 | "notification-url": "https://packagist.org/downloads/", 1270 | "license": [ 1271 | "MIT" 1272 | ], 1273 | "authors": [ 1274 | { 1275 | "name": "Roman Borschel", 1276 | "email": "roman@code-factory.org" 1277 | }, 1278 | { 1279 | "name": "Guilherme Blanco", 1280 | "email": "guilhermeblanco@gmail.com" 1281 | }, 1282 | { 1283 | "name": "Johannes Schmitt", 1284 | "email": "schmittjoh@gmail.com" 1285 | } 1286 | ], 1287 | "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", 1288 | "homepage": "http://www.doctrine-project.org", 1289 | "keywords": [ 1290 | "lexer", 1291 | "parser" 1292 | ], 1293 | "time": "2014-09-09T13:34:57+00:00" 1294 | }, 1295 | { 1296 | "name": "friendsofphp/php-cs-fixer", 1297 | "version": "v2.11.1", 1298 | "source": { 1299 | "type": "git", 1300 | "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", 1301 | "reference": "ad94441c17b8ef096e517acccdbf3238af8a2da8" 1302 | }, 1303 | "dist": { 1304 | "type": "zip", 1305 | "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ad94441c17b8ef096e517acccdbf3238af8a2da8", 1306 | "reference": "ad94441c17b8ef096e517acccdbf3238af8a2da8", 1307 | "shasum": "" 1308 | }, 1309 | "require": { 1310 | "composer/semver": "^1.4", 1311 | "doctrine/annotations": "^1.2", 1312 | "ext-json": "*", 1313 | "ext-tokenizer": "*", 1314 | "php": "^5.6 || >=7.0 <7.3", 1315 | "php-cs-fixer/diff": "^1.3", 1316 | "symfony/console": "^3.2 || ^4.0", 1317 | "symfony/event-dispatcher": "^3.0 || ^4.0", 1318 | "symfony/filesystem": "^3.0 || ^4.0", 1319 | "symfony/finder": "^3.0 || ^4.0", 1320 | "symfony/options-resolver": "^3.0 || ^4.0", 1321 | "symfony/polyfill-php70": "^1.0", 1322 | "symfony/polyfill-php72": "^1.4", 1323 | "symfony/process": "^3.0 || ^4.0", 1324 | "symfony/stopwatch": "^3.0 || ^4.0" 1325 | }, 1326 | "conflict": { 1327 | "hhvm": "*" 1328 | }, 1329 | "require-dev": { 1330 | "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", 1331 | "justinrainbow/json-schema": "^5.0", 1332 | "keradus/cli-executor": "^1.0", 1333 | "mikey179/vfsstream": "^1.6", 1334 | "php-coveralls/php-coveralls": "^2.0", 1335 | "php-cs-fixer/accessible-object": "^1.0", 1336 | "phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0", 1337 | "phpunitgoodpractices/traits": "^1.3.1", 1338 | "symfony/phpunit-bridge": "^3.2.2 || ^4.0" 1339 | }, 1340 | "suggest": { 1341 | "ext-mbstring": "For handling non-UTF8 characters in cache signature.", 1342 | "symfony/polyfill-mbstring": "When enabling `ext-mbstring` is not possible." 1343 | }, 1344 | "bin": [ 1345 | "php-cs-fixer" 1346 | ], 1347 | "type": "application", 1348 | "extra": { 1349 | "branch-alias": { 1350 | "dev-master": "2.11-dev" 1351 | } 1352 | }, 1353 | "autoload": { 1354 | "psr-4": { 1355 | "PhpCsFixer\\": "src/" 1356 | }, 1357 | "classmap": [ 1358 | "tests/Test/AbstractFixerTestCase.php", 1359 | "tests/Test/AbstractIntegrationCaseFactory.php", 1360 | "tests/Test/AbstractIntegrationTestCase.php", 1361 | "tests/Test/Assert/AssertTokensTrait.php", 1362 | "tests/Test/Constraint/SameStringsConstraint.php", 1363 | "tests/Test/Constraint/SameStringsConstraintForV5.php", 1364 | "tests/Test/Constraint/SameStringsConstraintForV7.php", 1365 | "tests/Test/IntegrationCase.php", 1366 | "tests/Test/IntegrationCaseFactory.php", 1367 | "tests/Test/IntegrationCaseFactoryInterface.php", 1368 | "tests/Test/InternalIntegrationCaseFactory.php", 1369 | "tests/TestCase.php" 1370 | ] 1371 | }, 1372 | "notification-url": "https://packagist.org/downloads/", 1373 | "license": [ 1374 | "MIT" 1375 | ], 1376 | "authors": [ 1377 | { 1378 | "name": "Dariusz Rumiński", 1379 | "email": "dariusz.ruminski@gmail.com" 1380 | }, 1381 | { 1382 | "name": "Fabien Potencier", 1383 | "email": "fabien@symfony.com" 1384 | } 1385 | ], 1386 | "description": "A tool to automatically fix PHP code style", 1387 | "time": "2018-03-21T17:41:26+00:00" 1388 | }, 1389 | { 1390 | "name": "myclabs/deep-copy", 1391 | "version": "1.7.0", 1392 | "source": { 1393 | "type": "git", 1394 | "url": "https://github.com/myclabs/DeepCopy.git", 1395 | "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" 1396 | }, 1397 | "dist": { 1398 | "type": "zip", 1399 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", 1400 | "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", 1401 | "shasum": "" 1402 | }, 1403 | "require": { 1404 | "php": "^5.6 || ^7.0" 1405 | }, 1406 | "require-dev": { 1407 | "doctrine/collections": "^1.0", 1408 | "doctrine/common": "^2.6", 1409 | "phpunit/phpunit": "^4.1" 1410 | }, 1411 | "type": "library", 1412 | "autoload": { 1413 | "psr-4": { 1414 | "DeepCopy\\": "src/DeepCopy/" 1415 | }, 1416 | "files": [ 1417 | "src/DeepCopy/deep_copy.php" 1418 | ] 1419 | }, 1420 | "notification-url": "https://packagist.org/downloads/", 1421 | "license": [ 1422 | "MIT" 1423 | ], 1424 | "description": "Create deep copies (clones) of your objects", 1425 | "keywords": [ 1426 | "clone", 1427 | "copy", 1428 | "duplicate", 1429 | "object", 1430 | "object graph" 1431 | ], 1432 | "time": "2017-10-19T19:58:43+00:00" 1433 | }, 1434 | { 1435 | "name": "phar-io/manifest", 1436 | "version": "1.0.1", 1437 | "source": { 1438 | "type": "git", 1439 | "url": "https://github.com/phar-io/manifest.git", 1440 | "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0" 1441 | }, 1442 | "dist": { 1443 | "type": "zip", 1444 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/2df402786ab5368a0169091f61a7c1e0eb6852d0", 1445 | "reference": "2df402786ab5368a0169091f61a7c1e0eb6852d0", 1446 | "shasum": "" 1447 | }, 1448 | "require": { 1449 | "ext-dom": "*", 1450 | "ext-phar": "*", 1451 | "phar-io/version": "^1.0.1", 1452 | "php": "^5.6 || ^7.0" 1453 | }, 1454 | "type": "library", 1455 | "extra": { 1456 | "branch-alias": { 1457 | "dev-master": "1.0.x-dev" 1458 | } 1459 | }, 1460 | "autoload": { 1461 | "classmap": [ 1462 | "src/" 1463 | ] 1464 | }, 1465 | "notification-url": "https://packagist.org/downloads/", 1466 | "license": [ 1467 | "BSD-3-Clause" 1468 | ], 1469 | "authors": [ 1470 | { 1471 | "name": "Arne Blankerts", 1472 | "email": "arne@blankerts.de", 1473 | "role": "Developer" 1474 | }, 1475 | { 1476 | "name": "Sebastian Heuer", 1477 | "email": "sebastian@phpeople.de", 1478 | "role": "Developer" 1479 | }, 1480 | { 1481 | "name": "Sebastian Bergmann", 1482 | "email": "sebastian@phpunit.de", 1483 | "role": "Developer" 1484 | } 1485 | ], 1486 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 1487 | "time": "2017-03-05T18:14:27+00:00" 1488 | }, 1489 | { 1490 | "name": "phar-io/version", 1491 | "version": "1.0.1", 1492 | "source": { 1493 | "type": "git", 1494 | "url": "https://github.com/phar-io/version.git", 1495 | "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df" 1496 | }, 1497 | "dist": { 1498 | "type": "zip", 1499 | "url": "https://api.github.com/repos/phar-io/version/zipball/a70c0ced4be299a63d32fa96d9281d03e94041df", 1500 | "reference": "a70c0ced4be299a63d32fa96d9281d03e94041df", 1501 | "shasum": "" 1502 | }, 1503 | "require": { 1504 | "php": "^5.6 || ^7.0" 1505 | }, 1506 | "type": "library", 1507 | "autoload": { 1508 | "classmap": [ 1509 | "src/" 1510 | ] 1511 | }, 1512 | "notification-url": "https://packagist.org/downloads/", 1513 | "license": [ 1514 | "BSD-3-Clause" 1515 | ], 1516 | "authors": [ 1517 | { 1518 | "name": "Arne Blankerts", 1519 | "email": "arne@blankerts.de", 1520 | "role": "Developer" 1521 | }, 1522 | { 1523 | "name": "Sebastian Heuer", 1524 | "email": "sebastian@phpeople.de", 1525 | "role": "Developer" 1526 | }, 1527 | { 1528 | "name": "Sebastian Bergmann", 1529 | "email": "sebastian@phpunit.de", 1530 | "role": "Developer" 1531 | } 1532 | ], 1533 | "description": "Library for handling version information and constraints", 1534 | "time": "2017-03-05T17:38:23+00:00" 1535 | }, 1536 | { 1537 | "name": "php-cs-fixer/diff", 1538 | "version": "v1.3.0", 1539 | "source": { 1540 | "type": "git", 1541 | "url": "https://github.com/PHP-CS-Fixer/diff.git", 1542 | "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756" 1543 | }, 1544 | "dist": { 1545 | "type": "zip", 1546 | "url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/78bb099e9c16361126c86ce82ec4405ebab8e756", 1547 | "reference": "78bb099e9c16361126c86ce82ec4405ebab8e756", 1548 | "shasum": "" 1549 | }, 1550 | "require": { 1551 | "php": "^5.6 || ^7.0" 1552 | }, 1553 | "require-dev": { 1554 | "phpunit/phpunit": "^5.7.23 || ^6.4.3", 1555 | "symfony/process": "^3.3" 1556 | }, 1557 | "type": "library", 1558 | "autoload": { 1559 | "classmap": [ 1560 | "src/" 1561 | ] 1562 | }, 1563 | "notification-url": "https://packagist.org/downloads/", 1564 | "license": [ 1565 | "BSD-3-Clause" 1566 | ], 1567 | "authors": [ 1568 | { 1569 | "name": "Kore Nordmann", 1570 | "email": "mail@kore-nordmann.de" 1571 | }, 1572 | { 1573 | "name": "Sebastian Bergmann", 1574 | "email": "sebastian@phpunit.de" 1575 | }, 1576 | { 1577 | "name": "SpacePossum" 1578 | } 1579 | ], 1580 | "description": "sebastian/diff v2 backport support for PHP5.6", 1581 | "homepage": "https://github.com/PHP-CS-Fixer", 1582 | "keywords": [ 1583 | "diff" 1584 | ], 1585 | "time": "2018-02-15T16:58:55+00:00" 1586 | }, 1587 | { 1588 | "name": "phpdocumentor/reflection-common", 1589 | "version": "1.0.1", 1590 | "source": { 1591 | "type": "git", 1592 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 1593 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6" 1594 | }, 1595 | "dist": { 1596 | "type": "zip", 1597 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 1598 | "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6", 1599 | "shasum": "" 1600 | }, 1601 | "require": { 1602 | "php": ">=5.5" 1603 | }, 1604 | "require-dev": { 1605 | "phpunit/phpunit": "^4.6" 1606 | }, 1607 | "type": "library", 1608 | "extra": { 1609 | "branch-alias": { 1610 | "dev-master": "1.0.x-dev" 1611 | } 1612 | }, 1613 | "autoload": { 1614 | "psr-4": { 1615 | "phpDocumentor\\Reflection\\": [ 1616 | "src" 1617 | ] 1618 | } 1619 | }, 1620 | "notification-url": "https://packagist.org/downloads/", 1621 | "license": [ 1622 | "MIT" 1623 | ], 1624 | "authors": [ 1625 | { 1626 | "name": "Jaap van Otterdijk", 1627 | "email": "opensource@ijaap.nl" 1628 | } 1629 | ], 1630 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 1631 | "homepage": "http://www.phpdoc.org", 1632 | "keywords": [ 1633 | "FQSEN", 1634 | "phpDocumentor", 1635 | "phpdoc", 1636 | "reflection", 1637 | "static analysis" 1638 | ], 1639 | "time": "2017-09-11T18:02:19+00:00" 1640 | }, 1641 | { 1642 | "name": "phpdocumentor/reflection-docblock", 1643 | "version": "4.3.0", 1644 | "source": { 1645 | "type": "git", 1646 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 1647 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08" 1648 | }, 1649 | "dist": { 1650 | "type": "zip", 1651 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", 1652 | "reference": "94fd0001232e47129dd3504189fa1c7225010d08", 1653 | "shasum": "" 1654 | }, 1655 | "require": { 1656 | "php": "^7.0", 1657 | "phpdocumentor/reflection-common": "^1.0.0", 1658 | "phpdocumentor/type-resolver": "^0.4.0", 1659 | "webmozart/assert": "^1.0" 1660 | }, 1661 | "require-dev": { 1662 | "doctrine/instantiator": "~1.0.5", 1663 | "mockery/mockery": "^1.0", 1664 | "phpunit/phpunit": "^6.4" 1665 | }, 1666 | "type": "library", 1667 | "extra": { 1668 | "branch-alias": { 1669 | "dev-master": "4.x-dev" 1670 | } 1671 | }, 1672 | "autoload": { 1673 | "psr-4": { 1674 | "phpDocumentor\\Reflection\\": [ 1675 | "src/" 1676 | ] 1677 | } 1678 | }, 1679 | "notification-url": "https://packagist.org/downloads/", 1680 | "license": [ 1681 | "MIT" 1682 | ], 1683 | "authors": [ 1684 | { 1685 | "name": "Mike van Riel", 1686 | "email": "me@mikevanriel.com" 1687 | } 1688 | ], 1689 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 1690 | "time": "2017-11-30T07:14:17+00:00" 1691 | }, 1692 | { 1693 | "name": "phpdocumentor/type-resolver", 1694 | "version": "0.4.0", 1695 | "source": { 1696 | "type": "git", 1697 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 1698 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7" 1699 | }, 1700 | "dist": { 1701 | "type": "zip", 1702 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7", 1703 | "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7", 1704 | "shasum": "" 1705 | }, 1706 | "require": { 1707 | "php": "^5.5 || ^7.0", 1708 | "phpdocumentor/reflection-common": "^1.0" 1709 | }, 1710 | "require-dev": { 1711 | "mockery/mockery": "^0.9.4", 1712 | "phpunit/phpunit": "^5.2||^4.8.24" 1713 | }, 1714 | "type": "library", 1715 | "extra": { 1716 | "branch-alias": { 1717 | "dev-master": "1.0.x-dev" 1718 | } 1719 | }, 1720 | "autoload": { 1721 | "psr-4": { 1722 | "phpDocumentor\\Reflection\\": [ 1723 | "src/" 1724 | ] 1725 | } 1726 | }, 1727 | "notification-url": "https://packagist.org/downloads/", 1728 | "license": [ 1729 | "MIT" 1730 | ], 1731 | "authors": [ 1732 | { 1733 | "name": "Mike van Riel", 1734 | "email": "me@mikevanriel.com" 1735 | } 1736 | ], 1737 | "time": "2017-07-14T14:27:02+00:00" 1738 | }, 1739 | { 1740 | "name": "phpspec/prophecy", 1741 | "version": "1.7.5", 1742 | "source": { 1743 | "type": "git", 1744 | "url": "https://github.com/phpspec/prophecy.git", 1745 | "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401" 1746 | }, 1747 | "dist": { 1748 | "type": "zip", 1749 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401", 1750 | "reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401", 1751 | "shasum": "" 1752 | }, 1753 | "require": { 1754 | "doctrine/instantiator": "^1.0.2", 1755 | "php": "^5.3|^7.0", 1756 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", 1757 | "sebastian/comparator": "^1.1|^2.0", 1758 | "sebastian/recursion-context": "^1.0|^2.0|^3.0" 1759 | }, 1760 | "require-dev": { 1761 | "phpspec/phpspec": "^2.5|^3.2", 1762 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" 1763 | }, 1764 | "type": "library", 1765 | "extra": { 1766 | "branch-alias": { 1767 | "dev-master": "1.7.x-dev" 1768 | } 1769 | }, 1770 | "autoload": { 1771 | "psr-0": { 1772 | "Prophecy\\": "src/" 1773 | } 1774 | }, 1775 | "notification-url": "https://packagist.org/downloads/", 1776 | "license": [ 1777 | "MIT" 1778 | ], 1779 | "authors": [ 1780 | { 1781 | "name": "Konstantin Kudryashov", 1782 | "email": "ever.zet@gmail.com", 1783 | "homepage": "http://everzet.com" 1784 | }, 1785 | { 1786 | "name": "Marcello Duarte", 1787 | "email": "marcello.duarte@gmail.com" 1788 | } 1789 | ], 1790 | "description": "Highly opinionated mocking framework for PHP 5.3+", 1791 | "homepage": "https://github.com/phpspec/prophecy", 1792 | "keywords": [ 1793 | "Double", 1794 | "Dummy", 1795 | "fake", 1796 | "mock", 1797 | "spy", 1798 | "stub" 1799 | ], 1800 | "time": "2018-02-19T10:16:54+00:00" 1801 | }, 1802 | { 1803 | "name": "phpunit/php-code-coverage", 1804 | "version": "5.3.2", 1805 | "source": { 1806 | "type": "git", 1807 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 1808 | "reference": "c89677919c5dd6d3b3852f230a663118762218ac" 1809 | }, 1810 | "dist": { 1811 | "type": "zip", 1812 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c89677919c5dd6d3b3852f230a663118762218ac", 1813 | "reference": "c89677919c5dd6d3b3852f230a663118762218ac", 1814 | "shasum": "" 1815 | }, 1816 | "require": { 1817 | "ext-dom": "*", 1818 | "ext-xmlwriter": "*", 1819 | "php": "^7.0", 1820 | "phpunit/php-file-iterator": "^1.4.2", 1821 | "phpunit/php-text-template": "^1.2.1", 1822 | "phpunit/php-token-stream": "^2.0.1", 1823 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 1824 | "sebastian/environment": "^3.0", 1825 | "sebastian/version": "^2.0.1", 1826 | "theseer/tokenizer": "^1.1" 1827 | }, 1828 | "require-dev": { 1829 | "phpunit/phpunit": "^6.0" 1830 | }, 1831 | "suggest": { 1832 | "ext-xdebug": "^2.5.5" 1833 | }, 1834 | "type": "library", 1835 | "extra": { 1836 | "branch-alias": { 1837 | "dev-master": "5.3.x-dev" 1838 | } 1839 | }, 1840 | "autoload": { 1841 | "classmap": [ 1842 | "src/" 1843 | ] 1844 | }, 1845 | "notification-url": "https://packagist.org/downloads/", 1846 | "license": [ 1847 | "BSD-3-Clause" 1848 | ], 1849 | "authors": [ 1850 | { 1851 | "name": "Sebastian Bergmann", 1852 | "email": "sebastian@phpunit.de", 1853 | "role": "lead" 1854 | } 1855 | ], 1856 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 1857 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 1858 | "keywords": [ 1859 | "coverage", 1860 | "testing", 1861 | "xunit" 1862 | ], 1863 | "time": "2018-04-06T15:36:58+00:00" 1864 | }, 1865 | { 1866 | "name": "phpunit/php-file-iterator", 1867 | "version": "1.4.5", 1868 | "source": { 1869 | "type": "git", 1870 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 1871 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4" 1872 | }, 1873 | "dist": { 1874 | "type": "zip", 1875 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/730b01bc3e867237eaac355e06a36b85dd93a8b4", 1876 | "reference": "730b01bc3e867237eaac355e06a36b85dd93a8b4", 1877 | "shasum": "" 1878 | }, 1879 | "require": { 1880 | "php": ">=5.3.3" 1881 | }, 1882 | "type": "library", 1883 | "extra": { 1884 | "branch-alias": { 1885 | "dev-master": "1.4.x-dev" 1886 | } 1887 | }, 1888 | "autoload": { 1889 | "classmap": [ 1890 | "src/" 1891 | ] 1892 | }, 1893 | "notification-url": "https://packagist.org/downloads/", 1894 | "license": [ 1895 | "BSD-3-Clause" 1896 | ], 1897 | "authors": [ 1898 | { 1899 | "name": "Sebastian Bergmann", 1900 | "email": "sb@sebastian-bergmann.de", 1901 | "role": "lead" 1902 | } 1903 | ], 1904 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 1905 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 1906 | "keywords": [ 1907 | "filesystem", 1908 | "iterator" 1909 | ], 1910 | "time": "2017-11-27T13:52:08+00:00" 1911 | }, 1912 | { 1913 | "name": "phpunit/php-text-template", 1914 | "version": "1.2.1", 1915 | "source": { 1916 | "type": "git", 1917 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 1918 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 1919 | }, 1920 | "dist": { 1921 | "type": "zip", 1922 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1923 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1924 | "shasum": "" 1925 | }, 1926 | "require": { 1927 | "php": ">=5.3.3" 1928 | }, 1929 | "type": "library", 1930 | "autoload": { 1931 | "classmap": [ 1932 | "src/" 1933 | ] 1934 | }, 1935 | "notification-url": "https://packagist.org/downloads/", 1936 | "license": [ 1937 | "BSD-3-Clause" 1938 | ], 1939 | "authors": [ 1940 | { 1941 | "name": "Sebastian Bergmann", 1942 | "email": "sebastian@phpunit.de", 1943 | "role": "lead" 1944 | } 1945 | ], 1946 | "description": "Simple template engine.", 1947 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1948 | "keywords": [ 1949 | "template" 1950 | ], 1951 | "time": "2015-06-21T13:50:34+00:00" 1952 | }, 1953 | { 1954 | "name": "phpunit/php-timer", 1955 | "version": "1.0.9", 1956 | "source": { 1957 | "type": "git", 1958 | "url": "https://github.com/sebastianbergmann/php-timer.git", 1959 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" 1960 | }, 1961 | "dist": { 1962 | "type": "zip", 1963 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 1964 | "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", 1965 | "shasum": "" 1966 | }, 1967 | "require": { 1968 | "php": "^5.3.3 || ^7.0" 1969 | }, 1970 | "require-dev": { 1971 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" 1972 | }, 1973 | "type": "library", 1974 | "extra": { 1975 | "branch-alias": { 1976 | "dev-master": "1.0-dev" 1977 | } 1978 | }, 1979 | "autoload": { 1980 | "classmap": [ 1981 | "src/" 1982 | ] 1983 | }, 1984 | "notification-url": "https://packagist.org/downloads/", 1985 | "license": [ 1986 | "BSD-3-Clause" 1987 | ], 1988 | "authors": [ 1989 | { 1990 | "name": "Sebastian Bergmann", 1991 | "email": "sb@sebastian-bergmann.de", 1992 | "role": "lead" 1993 | } 1994 | ], 1995 | "description": "Utility class for timing", 1996 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 1997 | "keywords": [ 1998 | "timer" 1999 | ], 2000 | "time": "2017-02-26T11:10:40+00:00" 2001 | }, 2002 | { 2003 | "name": "phpunit/php-token-stream", 2004 | "version": "2.0.2", 2005 | "source": { 2006 | "type": "git", 2007 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 2008 | "reference": "791198a2c6254db10131eecfe8c06670700904db" 2009 | }, 2010 | "dist": { 2011 | "type": "zip", 2012 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/791198a2c6254db10131eecfe8c06670700904db", 2013 | "reference": "791198a2c6254db10131eecfe8c06670700904db", 2014 | "shasum": "" 2015 | }, 2016 | "require": { 2017 | "ext-tokenizer": "*", 2018 | "php": "^7.0" 2019 | }, 2020 | "require-dev": { 2021 | "phpunit/phpunit": "^6.2.4" 2022 | }, 2023 | "type": "library", 2024 | "extra": { 2025 | "branch-alias": { 2026 | "dev-master": "2.0-dev" 2027 | } 2028 | }, 2029 | "autoload": { 2030 | "classmap": [ 2031 | "src/" 2032 | ] 2033 | }, 2034 | "notification-url": "https://packagist.org/downloads/", 2035 | "license": [ 2036 | "BSD-3-Clause" 2037 | ], 2038 | "authors": [ 2039 | { 2040 | "name": "Sebastian Bergmann", 2041 | "email": "sebastian@phpunit.de" 2042 | } 2043 | ], 2044 | "description": "Wrapper around PHP's tokenizer extension.", 2045 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 2046 | "keywords": [ 2047 | "tokenizer" 2048 | ], 2049 | "time": "2017-11-27T05:48:46+00:00" 2050 | }, 2051 | { 2052 | "name": "phpunit/phpunit", 2053 | "version": "6.5.8", 2054 | "source": { 2055 | "type": "git", 2056 | "url": "https://github.com/sebastianbergmann/phpunit.git", 2057 | "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b" 2058 | }, 2059 | "dist": { 2060 | "type": "zip", 2061 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b", 2062 | "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b", 2063 | "shasum": "" 2064 | }, 2065 | "require": { 2066 | "ext-dom": "*", 2067 | "ext-json": "*", 2068 | "ext-libxml": "*", 2069 | "ext-mbstring": "*", 2070 | "ext-xml": "*", 2071 | "myclabs/deep-copy": "^1.6.1", 2072 | "phar-io/manifest": "^1.0.1", 2073 | "phar-io/version": "^1.0", 2074 | "php": "^7.0", 2075 | "phpspec/prophecy": "^1.7", 2076 | "phpunit/php-code-coverage": "^5.3", 2077 | "phpunit/php-file-iterator": "^1.4.3", 2078 | "phpunit/php-text-template": "^1.2.1", 2079 | "phpunit/php-timer": "^1.0.9", 2080 | "phpunit/phpunit-mock-objects": "^5.0.5", 2081 | "sebastian/comparator": "^2.1", 2082 | "sebastian/diff": "^2.0", 2083 | "sebastian/environment": "^3.1", 2084 | "sebastian/exporter": "^3.1", 2085 | "sebastian/global-state": "^2.0", 2086 | "sebastian/object-enumerator": "^3.0.3", 2087 | "sebastian/resource-operations": "^1.0", 2088 | "sebastian/version": "^2.0.1" 2089 | }, 2090 | "conflict": { 2091 | "phpdocumentor/reflection-docblock": "3.0.2", 2092 | "phpunit/dbunit": "<3.0" 2093 | }, 2094 | "require-dev": { 2095 | "ext-pdo": "*" 2096 | }, 2097 | "suggest": { 2098 | "ext-xdebug": "*", 2099 | "phpunit/php-invoker": "^1.1" 2100 | }, 2101 | "bin": [ 2102 | "phpunit" 2103 | ], 2104 | "type": "library", 2105 | "extra": { 2106 | "branch-alias": { 2107 | "dev-master": "6.5.x-dev" 2108 | } 2109 | }, 2110 | "autoload": { 2111 | "classmap": [ 2112 | "src/" 2113 | ] 2114 | }, 2115 | "notification-url": "https://packagist.org/downloads/", 2116 | "license": [ 2117 | "BSD-3-Clause" 2118 | ], 2119 | "authors": [ 2120 | { 2121 | "name": "Sebastian Bergmann", 2122 | "email": "sebastian@phpunit.de", 2123 | "role": "lead" 2124 | } 2125 | ], 2126 | "description": "The PHP Unit Testing framework.", 2127 | "homepage": "https://phpunit.de/", 2128 | "keywords": [ 2129 | "phpunit", 2130 | "testing", 2131 | "xunit" 2132 | ], 2133 | "time": "2018-04-10T11:38:34+00:00" 2134 | }, 2135 | { 2136 | "name": "phpunit/phpunit-mock-objects", 2137 | "version": "5.0.6", 2138 | "source": { 2139 | "type": "git", 2140 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 2141 | "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" 2142 | }, 2143 | "dist": { 2144 | "type": "zip", 2145 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", 2146 | "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", 2147 | "shasum": "" 2148 | }, 2149 | "require": { 2150 | "doctrine/instantiator": "^1.0.5", 2151 | "php": "^7.0", 2152 | "phpunit/php-text-template": "^1.2.1", 2153 | "sebastian/exporter": "^3.1" 2154 | }, 2155 | "conflict": { 2156 | "phpunit/phpunit": "<6.0" 2157 | }, 2158 | "require-dev": { 2159 | "phpunit/phpunit": "^6.5" 2160 | }, 2161 | "suggest": { 2162 | "ext-soap": "*" 2163 | }, 2164 | "type": "library", 2165 | "extra": { 2166 | "branch-alias": { 2167 | "dev-master": "5.0.x-dev" 2168 | } 2169 | }, 2170 | "autoload": { 2171 | "classmap": [ 2172 | "src/" 2173 | ] 2174 | }, 2175 | "notification-url": "https://packagist.org/downloads/", 2176 | "license": [ 2177 | "BSD-3-Clause" 2178 | ], 2179 | "authors": [ 2180 | { 2181 | "name": "Sebastian Bergmann", 2182 | "email": "sebastian@phpunit.de", 2183 | "role": "lead" 2184 | } 2185 | ], 2186 | "description": "Mock Object library for PHPUnit", 2187 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 2188 | "keywords": [ 2189 | "mock", 2190 | "xunit" 2191 | ], 2192 | "time": "2018-01-06T05:45:45+00:00" 2193 | }, 2194 | { 2195 | "name": "sebastian/code-unit-reverse-lookup", 2196 | "version": "1.0.1", 2197 | "source": { 2198 | "type": "git", 2199 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 2200 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 2201 | }, 2202 | "dist": { 2203 | "type": "zip", 2204 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 2205 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 2206 | "shasum": "" 2207 | }, 2208 | "require": { 2209 | "php": "^5.6 || ^7.0" 2210 | }, 2211 | "require-dev": { 2212 | "phpunit/phpunit": "^5.7 || ^6.0" 2213 | }, 2214 | "type": "library", 2215 | "extra": { 2216 | "branch-alias": { 2217 | "dev-master": "1.0.x-dev" 2218 | } 2219 | }, 2220 | "autoload": { 2221 | "classmap": [ 2222 | "src/" 2223 | ] 2224 | }, 2225 | "notification-url": "https://packagist.org/downloads/", 2226 | "license": [ 2227 | "BSD-3-Clause" 2228 | ], 2229 | "authors": [ 2230 | { 2231 | "name": "Sebastian Bergmann", 2232 | "email": "sebastian@phpunit.de" 2233 | } 2234 | ], 2235 | "description": "Looks up which function or method a line of code belongs to", 2236 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 2237 | "time": "2017-03-04T06:30:41+00:00" 2238 | }, 2239 | { 2240 | "name": "sebastian/comparator", 2241 | "version": "2.1.3", 2242 | "source": { 2243 | "type": "git", 2244 | "url": "https://github.com/sebastianbergmann/comparator.git", 2245 | "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9" 2246 | }, 2247 | "dist": { 2248 | "type": "zip", 2249 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/34369daee48eafb2651bea869b4b15d75ccc35f9", 2250 | "reference": "34369daee48eafb2651bea869b4b15d75ccc35f9", 2251 | "shasum": "" 2252 | }, 2253 | "require": { 2254 | "php": "^7.0", 2255 | "sebastian/diff": "^2.0 || ^3.0", 2256 | "sebastian/exporter": "^3.1" 2257 | }, 2258 | "require-dev": { 2259 | "phpunit/phpunit": "^6.4" 2260 | }, 2261 | "type": "library", 2262 | "extra": { 2263 | "branch-alias": { 2264 | "dev-master": "2.1.x-dev" 2265 | } 2266 | }, 2267 | "autoload": { 2268 | "classmap": [ 2269 | "src/" 2270 | ] 2271 | }, 2272 | "notification-url": "https://packagist.org/downloads/", 2273 | "license": [ 2274 | "BSD-3-Clause" 2275 | ], 2276 | "authors": [ 2277 | { 2278 | "name": "Jeff Welch", 2279 | "email": "whatthejeff@gmail.com" 2280 | }, 2281 | { 2282 | "name": "Volker Dusch", 2283 | "email": "github@wallbash.com" 2284 | }, 2285 | { 2286 | "name": "Bernhard Schussek", 2287 | "email": "bschussek@2bepublished.at" 2288 | }, 2289 | { 2290 | "name": "Sebastian Bergmann", 2291 | "email": "sebastian@phpunit.de" 2292 | } 2293 | ], 2294 | "description": "Provides the functionality to compare PHP values for equality", 2295 | "homepage": "https://github.com/sebastianbergmann/comparator", 2296 | "keywords": [ 2297 | "comparator", 2298 | "compare", 2299 | "equality" 2300 | ], 2301 | "time": "2018-02-01T13:46:46+00:00" 2302 | }, 2303 | { 2304 | "name": "sebastian/diff", 2305 | "version": "2.0.1", 2306 | "source": { 2307 | "type": "git", 2308 | "url": "https://github.com/sebastianbergmann/diff.git", 2309 | "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd" 2310 | }, 2311 | "dist": { 2312 | "type": "zip", 2313 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", 2314 | "reference": "347c1d8b49c5c3ee30c7040ea6fc446790e6bddd", 2315 | "shasum": "" 2316 | }, 2317 | "require": { 2318 | "php": "^7.0" 2319 | }, 2320 | "require-dev": { 2321 | "phpunit/phpunit": "^6.2" 2322 | }, 2323 | "type": "library", 2324 | "extra": { 2325 | "branch-alias": { 2326 | "dev-master": "2.0-dev" 2327 | } 2328 | }, 2329 | "autoload": { 2330 | "classmap": [ 2331 | "src/" 2332 | ] 2333 | }, 2334 | "notification-url": "https://packagist.org/downloads/", 2335 | "license": [ 2336 | "BSD-3-Clause" 2337 | ], 2338 | "authors": [ 2339 | { 2340 | "name": "Kore Nordmann", 2341 | "email": "mail@kore-nordmann.de" 2342 | }, 2343 | { 2344 | "name": "Sebastian Bergmann", 2345 | "email": "sebastian@phpunit.de" 2346 | } 2347 | ], 2348 | "description": "Diff implementation", 2349 | "homepage": "https://github.com/sebastianbergmann/diff", 2350 | "keywords": [ 2351 | "diff" 2352 | ], 2353 | "time": "2017-08-03T08:09:46+00:00" 2354 | }, 2355 | { 2356 | "name": "sebastian/environment", 2357 | "version": "3.1.0", 2358 | "source": { 2359 | "type": "git", 2360 | "url": "https://github.com/sebastianbergmann/environment.git", 2361 | "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5" 2362 | }, 2363 | "dist": { 2364 | "type": "zip", 2365 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/cd0871b3975fb7fc44d11314fd1ee20925fce4f5", 2366 | "reference": "cd0871b3975fb7fc44d11314fd1ee20925fce4f5", 2367 | "shasum": "" 2368 | }, 2369 | "require": { 2370 | "php": "^7.0" 2371 | }, 2372 | "require-dev": { 2373 | "phpunit/phpunit": "^6.1" 2374 | }, 2375 | "type": "library", 2376 | "extra": { 2377 | "branch-alias": { 2378 | "dev-master": "3.1.x-dev" 2379 | } 2380 | }, 2381 | "autoload": { 2382 | "classmap": [ 2383 | "src/" 2384 | ] 2385 | }, 2386 | "notification-url": "https://packagist.org/downloads/", 2387 | "license": [ 2388 | "BSD-3-Clause" 2389 | ], 2390 | "authors": [ 2391 | { 2392 | "name": "Sebastian Bergmann", 2393 | "email": "sebastian@phpunit.de" 2394 | } 2395 | ], 2396 | "description": "Provides functionality to handle HHVM/PHP environments", 2397 | "homepage": "http://www.github.com/sebastianbergmann/environment", 2398 | "keywords": [ 2399 | "Xdebug", 2400 | "environment", 2401 | "hhvm" 2402 | ], 2403 | "time": "2017-07-01T08:51:00+00:00" 2404 | }, 2405 | { 2406 | "name": "sebastian/exporter", 2407 | "version": "3.1.0", 2408 | "source": { 2409 | "type": "git", 2410 | "url": "https://github.com/sebastianbergmann/exporter.git", 2411 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937" 2412 | }, 2413 | "dist": { 2414 | "type": "zip", 2415 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937", 2416 | "reference": "234199f4528de6d12aaa58b612e98f7d36adb937", 2417 | "shasum": "" 2418 | }, 2419 | "require": { 2420 | "php": "^7.0", 2421 | "sebastian/recursion-context": "^3.0" 2422 | }, 2423 | "require-dev": { 2424 | "ext-mbstring": "*", 2425 | "phpunit/phpunit": "^6.0" 2426 | }, 2427 | "type": "library", 2428 | "extra": { 2429 | "branch-alias": { 2430 | "dev-master": "3.1.x-dev" 2431 | } 2432 | }, 2433 | "autoload": { 2434 | "classmap": [ 2435 | "src/" 2436 | ] 2437 | }, 2438 | "notification-url": "https://packagist.org/downloads/", 2439 | "license": [ 2440 | "BSD-3-Clause" 2441 | ], 2442 | "authors": [ 2443 | { 2444 | "name": "Jeff Welch", 2445 | "email": "whatthejeff@gmail.com" 2446 | }, 2447 | { 2448 | "name": "Volker Dusch", 2449 | "email": "github@wallbash.com" 2450 | }, 2451 | { 2452 | "name": "Bernhard Schussek", 2453 | "email": "bschussek@2bepublished.at" 2454 | }, 2455 | { 2456 | "name": "Sebastian Bergmann", 2457 | "email": "sebastian@phpunit.de" 2458 | }, 2459 | { 2460 | "name": "Adam Harvey", 2461 | "email": "aharvey@php.net" 2462 | } 2463 | ], 2464 | "description": "Provides the functionality to export PHP variables for visualization", 2465 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 2466 | "keywords": [ 2467 | "export", 2468 | "exporter" 2469 | ], 2470 | "time": "2017-04-03T13:19:02+00:00" 2471 | }, 2472 | { 2473 | "name": "sebastian/global-state", 2474 | "version": "2.0.0", 2475 | "source": { 2476 | "type": "git", 2477 | "url": "https://github.com/sebastianbergmann/global-state.git", 2478 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 2479 | }, 2480 | "dist": { 2481 | "type": "zip", 2482 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 2483 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 2484 | "shasum": "" 2485 | }, 2486 | "require": { 2487 | "php": "^7.0" 2488 | }, 2489 | "require-dev": { 2490 | "phpunit/phpunit": "^6.0" 2491 | }, 2492 | "suggest": { 2493 | "ext-uopz": "*" 2494 | }, 2495 | "type": "library", 2496 | "extra": { 2497 | "branch-alias": { 2498 | "dev-master": "2.0-dev" 2499 | } 2500 | }, 2501 | "autoload": { 2502 | "classmap": [ 2503 | "src/" 2504 | ] 2505 | }, 2506 | "notification-url": "https://packagist.org/downloads/", 2507 | "license": [ 2508 | "BSD-3-Clause" 2509 | ], 2510 | "authors": [ 2511 | { 2512 | "name": "Sebastian Bergmann", 2513 | "email": "sebastian@phpunit.de" 2514 | } 2515 | ], 2516 | "description": "Snapshotting of global state", 2517 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 2518 | "keywords": [ 2519 | "global state" 2520 | ], 2521 | "time": "2017-04-27T15:39:26+00:00" 2522 | }, 2523 | { 2524 | "name": "sebastian/object-enumerator", 2525 | "version": "3.0.3", 2526 | "source": { 2527 | "type": "git", 2528 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 2529 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" 2530 | }, 2531 | "dist": { 2532 | "type": "zip", 2533 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", 2534 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", 2535 | "shasum": "" 2536 | }, 2537 | "require": { 2538 | "php": "^7.0", 2539 | "sebastian/object-reflector": "^1.1.1", 2540 | "sebastian/recursion-context": "^3.0" 2541 | }, 2542 | "require-dev": { 2543 | "phpunit/phpunit": "^6.0" 2544 | }, 2545 | "type": "library", 2546 | "extra": { 2547 | "branch-alias": { 2548 | "dev-master": "3.0.x-dev" 2549 | } 2550 | }, 2551 | "autoload": { 2552 | "classmap": [ 2553 | "src/" 2554 | ] 2555 | }, 2556 | "notification-url": "https://packagist.org/downloads/", 2557 | "license": [ 2558 | "BSD-3-Clause" 2559 | ], 2560 | "authors": [ 2561 | { 2562 | "name": "Sebastian Bergmann", 2563 | "email": "sebastian@phpunit.de" 2564 | } 2565 | ], 2566 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 2567 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 2568 | "time": "2017-08-03T12:35:26+00:00" 2569 | }, 2570 | { 2571 | "name": "sebastian/object-reflector", 2572 | "version": "1.1.1", 2573 | "source": { 2574 | "type": "git", 2575 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 2576 | "reference": "773f97c67f28de00d397be301821b06708fca0be" 2577 | }, 2578 | "dist": { 2579 | "type": "zip", 2580 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", 2581 | "reference": "773f97c67f28de00d397be301821b06708fca0be", 2582 | "shasum": "" 2583 | }, 2584 | "require": { 2585 | "php": "^7.0" 2586 | }, 2587 | "require-dev": { 2588 | "phpunit/phpunit": "^6.0" 2589 | }, 2590 | "type": "library", 2591 | "extra": { 2592 | "branch-alias": { 2593 | "dev-master": "1.1-dev" 2594 | } 2595 | }, 2596 | "autoload": { 2597 | "classmap": [ 2598 | "src/" 2599 | ] 2600 | }, 2601 | "notification-url": "https://packagist.org/downloads/", 2602 | "license": [ 2603 | "BSD-3-Clause" 2604 | ], 2605 | "authors": [ 2606 | { 2607 | "name": "Sebastian Bergmann", 2608 | "email": "sebastian@phpunit.de" 2609 | } 2610 | ], 2611 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 2612 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 2613 | "time": "2017-03-29T09:07:27+00:00" 2614 | }, 2615 | { 2616 | "name": "sebastian/recursion-context", 2617 | "version": "3.0.0", 2618 | "source": { 2619 | "type": "git", 2620 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 2621 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" 2622 | }, 2623 | "dist": { 2624 | "type": "zip", 2625 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 2626 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 2627 | "shasum": "" 2628 | }, 2629 | "require": { 2630 | "php": "^7.0" 2631 | }, 2632 | "require-dev": { 2633 | "phpunit/phpunit": "^6.0" 2634 | }, 2635 | "type": "library", 2636 | "extra": { 2637 | "branch-alias": { 2638 | "dev-master": "3.0.x-dev" 2639 | } 2640 | }, 2641 | "autoload": { 2642 | "classmap": [ 2643 | "src/" 2644 | ] 2645 | }, 2646 | "notification-url": "https://packagist.org/downloads/", 2647 | "license": [ 2648 | "BSD-3-Clause" 2649 | ], 2650 | "authors": [ 2651 | { 2652 | "name": "Jeff Welch", 2653 | "email": "whatthejeff@gmail.com" 2654 | }, 2655 | { 2656 | "name": "Sebastian Bergmann", 2657 | "email": "sebastian@phpunit.de" 2658 | }, 2659 | { 2660 | "name": "Adam Harvey", 2661 | "email": "aharvey@php.net" 2662 | } 2663 | ], 2664 | "description": "Provides functionality to recursively process PHP variables", 2665 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 2666 | "time": "2017-03-03T06:23:57+00:00" 2667 | }, 2668 | { 2669 | "name": "sebastian/resource-operations", 2670 | "version": "1.0.0", 2671 | "source": { 2672 | "type": "git", 2673 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 2674 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" 2675 | }, 2676 | "dist": { 2677 | "type": "zip", 2678 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 2679 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 2680 | "shasum": "" 2681 | }, 2682 | "require": { 2683 | "php": ">=5.6.0" 2684 | }, 2685 | "type": "library", 2686 | "extra": { 2687 | "branch-alias": { 2688 | "dev-master": "1.0.x-dev" 2689 | } 2690 | }, 2691 | "autoload": { 2692 | "classmap": [ 2693 | "src/" 2694 | ] 2695 | }, 2696 | "notification-url": "https://packagist.org/downloads/", 2697 | "license": [ 2698 | "BSD-3-Clause" 2699 | ], 2700 | "authors": [ 2701 | { 2702 | "name": "Sebastian Bergmann", 2703 | "email": "sebastian@phpunit.de" 2704 | } 2705 | ], 2706 | "description": "Provides a list of PHP built-in functions that operate on resources", 2707 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 2708 | "time": "2015-07-28T20:34:47+00:00" 2709 | }, 2710 | { 2711 | "name": "sebastian/version", 2712 | "version": "2.0.1", 2713 | "source": { 2714 | "type": "git", 2715 | "url": "https://github.com/sebastianbergmann/version.git", 2716 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 2717 | }, 2718 | "dist": { 2719 | "type": "zip", 2720 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 2721 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 2722 | "shasum": "" 2723 | }, 2724 | "require": { 2725 | "php": ">=5.6" 2726 | }, 2727 | "type": "library", 2728 | "extra": { 2729 | "branch-alias": { 2730 | "dev-master": "2.0.x-dev" 2731 | } 2732 | }, 2733 | "autoload": { 2734 | "classmap": [ 2735 | "src/" 2736 | ] 2737 | }, 2738 | "notification-url": "https://packagist.org/downloads/", 2739 | "license": [ 2740 | "BSD-3-Clause" 2741 | ], 2742 | "authors": [ 2743 | { 2744 | "name": "Sebastian Bergmann", 2745 | "email": "sebastian@phpunit.de", 2746 | "role": "lead" 2747 | } 2748 | ], 2749 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 2750 | "homepage": "https://github.com/sebastianbergmann/version", 2751 | "time": "2016-10-03T07:35:21+00:00" 2752 | }, 2753 | { 2754 | "name": "symfony/filesystem", 2755 | "version": "v4.0.8", 2756 | "source": { 2757 | "type": "git", 2758 | "url": "https://github.com/symfony/filesystem.git", 2759 | "reference": "5d2d655b2c72fc4d9bf7e9bf14f72a447b940f21" 2760 | }, 2761 | "dist": { 2762 | "type": "zip", 2763 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/5d2d655b2c72fc4d9bf7e9bf14f72a447b940f21", 2764 | "reference": "5d2d655b2c72fc4d9bf7e9bf14f72a447b940f21", 2765 | "shasum": "" 2766 | }, 2767 | "require": { 2768 | "php": "^7.1.3" 2769 | }, 2770 | "type": "library", 2771 | "extra": { 2772 | "branch-alias": { 2773 | "dev-master": "4.0-dev" 2774 | } 2775 | }, 2776 | "autoload": { 2777 | "psr-4": { 2778 | "Symfony\\Component\\Filesystem\\": "" 2779 | }, 2780 | "exclude-from-classmap": [ 2781 | "/Tests/" 2782 | ] 2783 | }, 2784 | "notification-url": "https://packagist.org/downloads/", 2785 | "license": [ 2786 | "MIT" 2787 | ], 2788 | "authors": [ 2789 | { 2790 | "name": "Fabien Potencier", 2791 | "email": "fabien@symfony.com" 2792 | }, 2793 | { 2794 | "name": "Symfony Community", 2795 | "homepage": "https://symfony.com/contributors" 2796 | } 2797 | ], 2798 | "description": "Symfony Filesystem Component", 2799 | "homepage": "https://symfony.com", 2800 | "time": "2018-02-22T10:50:29+00:00" 2801 | }, 2802 | { 2803 | "name": "symfony/finder", 2804 | "version": "v4.0.8", 2805 | "source": { 2806 | "type": "git", 2807 | "url": "https://github.com/symfony/finder.git", 2808 | "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49" 2809 | }, 2810 | "dist": { 2811 | "type": "zip", 2812 | "url": "https://api.github.com/repos/symfony/finder/zipball/ca27c02b7a3fef4828c998c2ff9ba7aae1641c49", 2813 | "reference": "ca27c02b7a3fef4828c998c2ff9ba7aae1641c49", 2814 | "shasum": "" 2815 | }, 2816 | "require": { 2817 | "php": "^7.1.3" 2818 | }, 2819 | "type": "library", 2820 | "extra": { 2821 | "branch-alias": { 2822 | "dev-master": "4.0-dev" 2823 | } 2824 | }, 2825 | "autoload": { 2826 | "psr-4": { 2827 | "Symfony\\Component\\Finder\\": "" 2828 | }, 2829 | "exclude-from-classmap": [ 2830 | "/Tests/" 2831 | ] 2832 | }, 2833 | "notification-url": "https://packagist.org/downloads/", 2834 | "license": [ 2835 | "MIT" 2836 | ], 2837 | "authors": [ 2838 | { 2839 | "name": "Fabien Potencier", 2840 | "email": "fabien@symfony.com" 2841 | }, 2842 | { 2843 | "name": "Symfony Community", 2844 | "homepage": "https://symfony.com/contributors" 2845 | } 2846 | ], 2847 | "description": "Symfony Finder Component", 2848 | "homepage": "https://symfony.com", 2849 | "time": "2018-04-04T05:10:37+00:00" 2850 | }, 2851 | { 2852 | "name": "symfony/options-resolver", 2853 | "version": "v4.0.8", 2854 | "source": { 2855 | "type": "git", 2856 | "url": "https://github.com/symfony/options-resolver.git", 2857 | "reference": "371532a2cfe932f7a3766dd4c45364566def1dd0" 2858 | }, 2859 | "dist": { 2860 | "type": "zip", 2861 | "url": "https://api.github.com/repos/symfony/options-resolver/zipball/371532a2cfe932f7a3766dd4c45364566def1dd0", 2862 | "reference": "371532a2cfe932f7a3766dd4c45364566def1dd0", 2863 | "shasum": "" 2864 | }, 2865 | "require": { 2866 | "php": "^7.1.3" 2867 | }, 2868 | "type": "library", 2869 | "extra": { 2870 | "branch-alias": { 2871 | "dev-master": "4.0-dev" 2872 | } 2873 | }, 2874 | "autoload": { 2875 | "psr-4": { 2876 | "Symfony\\Component\\OptionsResolver\\": "" 2877 | }, 2878 | "exclude-from-classmap": [ 2879 | "/Tests/" 2880 | ] 2881 | }, 2882 | "notification-url": "https://packagist.org/downloads/", 2883 | "license": [ 2884 | "MIT" 2885 | ], 2886 | "authors": [ 2887 | { 2888 | "name": "Fabien Potencier", 2889 | "email": "fabien@symfony.com" 2890 | }, 2891 | { 2892 | "name": "Symfony Community", 2893 | "homepage": "https://symfony.com/contributors" 2894 | } 2895 | ], 2896 | "description": "Symfony OptionsResolver Component", 2897 | "homepage": "https://symfony.com", 2898 | "keywords": [ 2899 | "config", 2900 | "configuration", 2901 | "options" 2902 | ], 2903 | "time": "2018-01-18T22:19:33+00:00" 2904 | }, 2905 | { 2906 | "name": "symfony/polyfill-php72", 2907 | "version": "v1.7.0", 2908 | "source": { 2909 | "type": "git", 2910 | "url": "https://github.com/symfony/polyfill-php72.git", 2911 | "reference": "8eca20c8a369e069d4f4c2ac9895144112867422" 2912 | }, 2913 | "dist": { 2914 | "type": "zip", 2915 | "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/8eca20c8a369e069d4f4c2ac9895144112867422", 2916 | "reference": "8eca20c8a369e069d4f4c2ac9895144112867422", 2917 | "shasum": "" 2918 | }, 2919 | "require": { 2920 | "php": ">=5.3.3" 2921 | }, 2922 | "type": "library", 2923 | "extra": { 2924 | "branch-alias": { 2925 | "dev-master": "1.7-dev" 2926 | } 2927 | }, 2928 | "autoload": { 2929 | "psr-4": { 2930 | "Symfony\\Polyfill\\Php72\\": "" 2931 | }, 2932 | "files": [ 2933 | "bootstrap.php" 2934 | ] 2935 | }, 2936 | "notification-url": "https://packagist.org/downloads/", 2937 | "license": [ 2938 | "MIT" 2939 | ], 2940 | "authors": [ 2941 | { 2942 | "name": "Nicolas Grekas", 2943 | "email": "p@tchwork.com" 2944 | }, 2945 | { 2946 | "name": "Symfony Community", 2947 | "homepage": "https://symfony.com/contributors" 2948 | } 2949 | ], 2950 | "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", 2951 | "homepage": "https://symfony.com", 2952 | "keywords": [ 2953 | "compatibility", 2954 | "polyfill", 2955 | "portable", 2956 | "shim" 2957 | ], 2958 | "time": "2018-01-31T17:43:24+00:00" 2959 | }, 2960 | { 2961 | "name": "symfony/stopwatch", 2962 | "version": "v4.0.8", 2963 | "source": { 2964 | "type": "git", 2965 | "url": "https://github.com/symfony/stopwatch.git", 2966 | "reference": "6795ffa2f8eebedac77f045aa62c0c10b2763042" 2967 | }, 2968 | "dist": { 2969 | "type": "zip", 2970 | "url": "https://api.github.com/repos/symfony/stopwatch/zipball/6795ffa2f8eebedac77f045aa62c0c10b2763042", 2971 | "reference": "6795ffa2f8eebedac77f045aa62c0c10b2763042", 2972 | "shasum": "" 2973 | }, 2974 | "require": { 2975 | "php": "^7.1.3" 2976 | }, 2977 | "type": "library", 2978 | "extra": { 2979 | "branch-alias": { 2980 | "dev-master": "4.0-dev" 2981 | } 2982 | }, 2983 | "autoload": { 2984 | "psr-4": { 2985 | "Symfony\\Component\\Stopwatch\\": "" 2986 | }, 2987 | "exclude-from-classmap": [ 2988 | "/Tests/" 2989 | ] 2990 | }, 2991 | "notification-url": "https://packagist.org/downloads/", 2992 | "license": [ 2993 | "MIT" 2994 | ], 2995 | "authors": [ 2996 | { 2997 | "name": "Fabien Potencier", 2998 | "email": "fabien@symfony.com" 2999 | }, 3000 | { 3001 | "name": "Symfony Community", 3002 | "homepage": "https://symfony.com/contributors" 3003 | } 3004 | ], 3005 | "description": "Symfony Stopwatch Component", 3006 | "homepage": "https://symfony.com", 3007 | "time": "2018-02-19T16:50:22+00:00" 3008 | }, 3009 | { 3010 | "name": "theseer/tokenizer", 3011 | "version": "1.1.0", 3012 | "source": { 3013 | "type": "git", 3014 | "url": "https://github.com/theseer/tokenizer.git", 3015 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" 3016 | }, 3017 | "dist": { 3018 | "type": "zip", 3019 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", 3020 | "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", 3021 | "shasum": "" 3022 | }, 3023 | "require": { 3024 | "ext-dom": "*", 3025 | "ext-tokenizer": "*", 3026 | "ext-xmlwriter": "*", 3027 | "php": "^7.0" 3028 | }, 3029 | "type": "library", 3030 | "autoload": { 3031 | "classmap": [ 3032 | "src/" 3033 | ] 3034 | }, 3035 | "notification-url": "https://packagist.org/downloads/", 3036 | "license": [ 3037 | "BSD-3-Clause" 3038 | ], 3039 | "authors": [ 3040 | { 3041 | "name": "Arne Blankerts", 3042 | "email": "arne@blankerts.de", 3043 | "role": "Developer" 3044 | } 3045 | ], 3046 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 3047 | "time": "2017-04-07T12:08:54+00:00" 3048 | }, 3049 | { 3050 | "name": "webmozart/assert", 3051 | "version": "1.3.0", 3052 | "source": { 3053 | "type": "git", 3054 | "url": "https://github.com/webmozart/assert.git", 3055 | "reference": "0df1908962e7a3071564e857d86874dad1ef204a" 3056 | }, 3057 | "dist": { 3058 | "type": "zip", 3059 | "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", 3060 | "reference": "0df1908962e7a3071564e857d86874dad1ef204a", 3061 | "shasum": "" 3062 | }, 3063 | "require": { 3064 | "php": "^5.3.3 || ^7.0" 3065 | }, 3066 | "require-dev": { 3067 | "phpunit/phpunit": "^4.6", 3068 | "sebastian/version": "^1.0.1" 3069 | }, 3070 | "type": "library", 3071 | "extra": { 3072 | "branch-alias": { 3073 | "dev-master": "1.3-dev" 3074 | } 3075 | }, 3076 | "autoload": { 3077 | "psr-4": { 3078 | "Webmozart\\Assert\\": "src/" 3079 | } 3080 | }, 3081 | "notification-url": "https://packagist.org/downloads/", 3082 | "license": [ 3083 | "MIT" 3084 | ], 3085 | "authors": [ 3086 | { 3087 | "name": "Bernhard Schussek", 3088 | "email": "bschussek@gmail.com" 3089 | } 3090 | ], 3091 | "description": "Assertions to validate method input/output with nice error messages.", 3092 | "keywords": [ 3093 | "assert", 3094 | "check", 3095 | "validate" 3096 | ], 3097 | "time": "2018-01-29T19:49:41+00:00" 3098 | } 3099 | ], 3100 | "aliases": [], 3101 | "minimum-stability": "stable", 3102 | "stability-flags": { 3103 | "tarantool/jobqueue": 20 3104 | }, 3105 | "prefer-stable": false, 3106 | "prefer-lowest": false, 3107 | "platform": { 3108 | "php": "^7.1" 3109 | }, 3110 | "platform-dev": [] 3111 | } 3112 | -------------------------------------------------------------------------------- /docker-compose.override.yml.dist: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | tarantool: 5 | ports: 6 | - "8080:8080" 7 | worker: 8 | volumes: 9 | - ${SSH_AUTH_SOCK}:${SSH_AUTH_SOCK} 10 | - ./docker/worker/dev.ini:/usr/local/etc/php/conf.d/99-dev.ini 11 | environment: 12 | - SSH_AUTH_SOCK=${SSH_AUTH_SOCK} 13 | - TNT_JOBQUEUE_USER=${TARANTOOL_USER_NAME} 14 | - TNT_JOBQUEUE_PASSWORD=${TARANTOOL_USER_PASSWORD} 15 | - XDEBUG_CONFIG=remote_host=${LOCAL_IP} 16 | - PHP_IDE_CONFIG=serverName=jobserver.local 17 | tarantool_admin: 18 | container_name: jobserver_tarantool_admin 19 | image: quay.io/basis-company/tarantool-admin 20 | restart: always 21 | ports: 22 | - "8001:80" 23 | depends_on: 24 | - tarantool 25 | environment: 26 | # https://github.com/basis-company/tarantool-admin#configure-using-env 27 | - TARANTOOL_TCP_NODELAY=1 28 | - TARANTOOL_DATABASE_QUERY=1 29 | - TARANTOOL_CONNECTIONS=${TARANTOOL_USER_NAME}:${TARANTOOL_USER_PASSWORD}@tarantool:3301 30 | -------------------------------------------------------------------------------- /docker-compose.override.yml.full.dist: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | tarantool: 5 | container_name: jobserver_tarantool 6 | image: haproxy:1-alpine 7 | restart: always 8 | volumes: 9 | - ./res/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg 10 | command: "" 11 | ports: 12 | - "3301:3301" 13 | - "8080:8080" 14 | environment: [] 15 | depends_on: 16 | - tarantool1 17 | - tarantool2 18 | tarantool1: 19 | container_name: jobserver_tarantool1 20 | image: tarantool/tarantool:1 21 | restart: always 22 | volumes: 23 | - ./app/config/jobserver_config.lua:/opt/tarantool/jobserver_config.lua 24 | - ./app/jobserver.lua:/usr/share/tarantool/jobserver.lua 25 | - ./app/monitor.lua:/usr/share/tarantool/monitor.lua 26 | - ./app/jobserver_instance.lua:/opt/tarantool/jobserver_instance.lua 27 | command: tarantool /opt/tarantool/jobserver_instance.lua 28 | ports: 29 | - "3302:3301" 30 | environment: 31 | - TARANTOOL_REPLICATION_SOURCE=tarantool1,tarantool2 32 | - TARANTOOL_USER_NAME 33 | - TARANTOOL_USER_PASSWORD 34 | - TARANTOOL_MONITOR_HOST 35 | - TARANTOOL_MONITOR_PORT 36 | tarantool2: 37 | container_name: jobserver_tarantool2 38 | image: tarantool/tarantool:1 39 | restart: always 40 | environment: 41 | - TARANTOOL_REPLICATION_SOURCE=tarantool1,tarantool2 42 | - TARANTOOL_USER_NAME 43 | - TARANTOOL_USER_PASSWORD 44 | - TARANTOOL_MONITOR_HOST 45 | - TARANTOOL_MONITOR_PORT 46 | volumes: 47 | - ./app/config/jobserver_config.lua:/opt/tarantool/jobserver_config.lua 48 | - ./app/jobserver.lua:/usr/share/tarantool/jobserver.lua 49 | - ./app/monitor.lua:/usr/share/tarantool/monitor.lua 50 | - ./app/jobserver_instance.lua:/opt/tarantool/jobserver_instance.lua 51 | command: tarantool /opt/tarantool/jobserver_instance.lua 52 | ports: 53 | - "3303:3301" 54 | worker: 55 | volumes: 56 | - ${SSH_AUTH_SOCK}:${SSH_AUTH_SOCK} 57 | - ./docker/worker/dev.ini:/usr/local/etc/php/conf.d/99-dev.ini 58 | environment: 59 | - SSH_AUTH_SOCK=${SSH_AUTH_SOCK} 60 | - TNT_JOBQUEUE_USER=${TARANTOOL_USER_NAME} 61 | - TNT_JOBQUEUE_PASSWORD=${TARANTOOL_USER_PASSWORD} 62 | - XDEBUG_CONFIG=remote_host=${LOCAL_IP} 63 | - PHP_IDE_CONFIG=serverName=jobserver.local 64 | tarantool_admin: 65 | container_name: jobserver_tarantool_admin 66 | image: quay.io/basis-company/tarantool-admin 67 | restart: always 68 | ports: 69 | - "8001:80" 70 | depends_on: 71 | - tarantool 72 | environment: 73 | # https://github.com/basis-company/tarantool-admin#configure-using-env 74 | - TARANTOOL_TCP_NODELAY=1 75 | - TARANTOOL_DATABASE_QUERY=1 76 | - TARANTOOL_CONNECTIONS=${TARANTOOL_USER_NAME}:${TARANTOOL_USER_PASSWORD}@tarantool:3301,${TARANTOOL_USER_NAME}:${TARANTOOL_USER_PASSWORD}@tarantool1:3301,${TARANTOOL_USER_NAME}:${TARANTOOL_USER_PASSWORD}@tarantool2:3301 77 | prometheus: 78 | container_name: jobserver_prometheus 79 | image: prom/prometheus:latest 80 | restart: always 81 | volumes: 82 | - ./res/prometheus/alert.rules:/etc/prometheus/alert.rules 83 | - ./res/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml 84 | ports: 85 | - "9090:9090" 86 | command: 87 | - '--config.file=/etc/prometheus/prometheus.yml' 88 | - '--log.level=debug' 89 | alertmanager: 90 | container_name: jobserver_alertmanager 91 | image: prom/alertmanager:latest 92 | restart: always 93 | volumes: 94 | - ./res/prometheus/alertmanager.yml:/etc/alertmanager/config.yml 95 | ports: 96 | - "9093:9093" 97 | command: 98 | - '--config.file=/etc/alertmanager/config.yml' 99 | - '--log.level=debug' 100 | grafana: 101 | container_name: jobserver_grafana 102 | image: grafana/grafana:latest 103 | restart: always 104 | volumes: 105 | - ./res/grafana/provisioning:/etc/grafana/provisioning 106 | - ./var/grafana:/var/lib/grafana 107 | ports: 108 | - "3000:3000" 109 | user: "0" 110 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | 3 | services: 4 | tarantool: 5 | container_name: jobserver_tarantool 6 | image: tarantool/tarantool:1 7 | restart: always 8 | volumes: 9 | - ./app/config/jobserver_config.lua:/opt/tarantool/jobserver_config.lua 10 | - ./app/jobserver.lua:/usr/share/tarantool/jobserver.lua 11 | - ./app/monitor.lua:/usr/share/tarantool/monitor.lua 12 | - ./app/jobserver_instance.lua:/opt/tarantool/jobserver_instance.lua 13 | command: tarantool /opt/tarantool/jobserver_instance.lua 14 | ports: 15 | - "3301:3301" 16 | environment: 17 | - TARANTOOL_USER_NAME 18 | - TARANTOOL_USER_PASSWORD 19 | - TARANTOOL_MONITOR_HOST 20 | - TARANTOOL_MONITOR_PORT 21 | worker: 22 | container_name: jobserver_worker 23 | restart: always 24 | build: ./docker/worker 25 | image: jobserver:worker 26 | working_dir: /opt/jobserver 27 | command: vendor/bin/jobqueue run default --host tarantool --config app/config/jobqueue.php --executors-config app/config/executors.php 28 | volumes: 29 | - .:/opt/jobserver 30 | depends_on: 31 | - tarantool 32 | -------------------------------------------------------------------------------- /docker/worker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7-alpine 2 | 3 | RUN echo http://dl-5.alpinelinux.org/alpine/edge/community/ >> /etc/apk/repositories 4 | 5 | RUN apk add --update --no-cache libintl icu icu-dev zlib-dev autoconf g++ make bash && \ 6 | docker-php-ext-install opcache intl zip 7 | 8 | # DEV packages 9 | RUN apk add --update --no-cache git openssh 10 | RUN pecl install xdebug && docker-php-ext-enable xdebug 11 | RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer && \ 12 | curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig && \ 13 | php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" && \ 14 | php /tmp/composer-setup.php --install-dir=/usr/local/bin --filename=composer && \ 15 | rm /tmp/composer-setup.php 16 | 17 | RUN rm -rf /var/cache/apk/* 18 | -------------------------------------------------------------------------------- /docker/worker/dev.ini: -------------------------------------------------------------------------------- 1 | html_errors = Off 2 | display_errors = On 3 | display_startup_errors = On 4 | error_reporting = E_ALL 5 | 6 | xdebug.remote_autostart = On 7 | xdebug.remote_enable = On 8 | xdebug.remote_host = 127.0.0.1 9 | xdebug.file_link_format = phpstorm://open?%f:%l 10 | -------------------------------------------------------------------------------- /jobserver: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add((new KickCommand())->setName('queue:kick')); 19 | $app->add((new PutCommand())->setName('queue:put')); 20 | $app->add((new StatsCommand())->setName('queue:stats')); 21 | $app->add((new TruncateCommand())->setName('queue:truncate')); 22 | 23 | $getCommands = require __DIR__.'/app/config/commands.php'; 24 | $app->addCommands($getCommands(container())); 25 | 26 | $app->run(); 27 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | tests/Unit 25 | 26 | 27 | tests/Integration 28 | 29 | 30 | 31 | 32 | 33 | src 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /res/grafana/provisioning/dashboards/dashboards.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | providers: 4 | - name: JobServer 5 | type: file 6 | options: 7 | path: /etc/grafana/provisioning/dashboards/jobserver.json 8 | -------------------------------------------------------------------------------- /res/grafana/provisioning/dashboards/jobserver.json: -------------------------------------------------------------------------------- 1 | { 2 | "__requires": [ 3 | { 4 | "type": "grafana", 5 | "id": "grafana", 6 | "name": "Grafana", 7 | "version": "5.1.2" 8 | }, 9 | { 10 | "type": "panel", 11 | "id": "graph", 12 | "name": "Graph", 13 | "version": "5.0.0" 14 | }, 15 | { 16 | "type": "datasource", 17 | "id": "prometheus", 18 | "name": "Prometheus", 19 | "version": "5.0.0" 20 | }, 21 | { 22 | "type": "panel", 23 | "id": "singlestat", 24 | "name": "Singlestat", 25 | "version": "5.0.0" 26 | } 27 | ], 28 | "annotations": { 29 | "list": [ 30 | { 31 | "builtIn": 1, 32 | "datasource": "-- Grafana --", 33 | "enable": true, 34 | "hide": true, 35 | "iconColor": "rgba(0, 211, 255, 1)", 36 | "name": "Annotations & Alerts", 37 | "type": "dashboard" 38 | } 39 | ] 40 | }, 41 | "editable": true, 42 | "gnetId": null, 43 | "graphTooltip": 0, 44 | "id": null, 45 | "iteration": 1526989245482, 46 | "links": [], 47 | "panels": [ 48 | { 49 | "cacheTimeout": null, 50 | "colorBackground": false, 51 | "colorValue": false, 52 | "colors": [ 53 | "#299c46", 54 | "rgba(237, 129, 40, 0.89)", 55 | "#d44a3a" 56 | ], 57 | "datasource": "Prometheus", 58 | "format": "none", 59 | "gauge": { 60 | "maxValue": 100, 61 | "minValue": 0, 62 | "show": false, 63 | "thresholdLabels": false, 64 | "thresholdMarkers": true 65 | }, 66 | "gridPos": { 67 | "h": 2, 68 | "w": 4, 69 | "x": 2, 70 | "y": 0 71 | }, 72 | "id": 10, 73 | "interval": null, 74 | "links": [], 75 | "mappingType": 1, 76 | "mappingTypes": [ 77 | { 78 | "name": "value to text", 79 | "value": 1 80 | }, 81 | { 82 | "name": "range to text", 83 | "value": 2 84 | } 85 | ], 86 | "maxDataPoints": 100, 87 | "nullPointMode": "connected", 88 | "nullText": null, 89 | "postfix": "", 90 | "postfixFontSize": "50%", 91 | "prefix": "", 92 | "prefixFontSize": "50%", 93 | "rangeMaps": [ 94 | { 95 | "from": "null", 96 | "text": "N/A", 97 | "to": "null" 98 | } 99 | ], 100 | "sparkline": { 101 | "fillColor": "rgba(31, 118, 189, 0.18)", 102 | "full": false, 103 | "lineColor": "rgb(31, 120, 193)", 104 | "show": true 105 | }, 106 | "tableColumn": "", 107 | "targets": [ 108 | { 109 | "expr": "up{instance=\"$server\"}", 110 | "format": "time_series", 111 | "intervalFactor": 1, 112 | "refId": "A" 113 | } 114 | ], 115 | "thresholds": "0,1", 116 | "title": "Status", 117 | "type": "singlestat", 118 | "valueFontSize": "50%", 119 | "valueMaps": [ 120 | { 121 | "op": "=", 122 | "text": "N/A", 123 | "value": "null" 124 | }, 125 | { 126 | "op": "=", 127 | "text": "Up", 128 | "value": "1" 129 | }, 130 | { 131 | "op": "=", 132 | "text": "Down", 133 | "value": "0" 134 | } 135 | ], 136 | "valueName": "current" 137 | }, 138 | { 139 | "cacheTimeout": null, 140 | "colorBackground": false, 141 | "colorValue": false, 142 | "colors": [ 143 | "#299c46", 144 | "rgba(237, 129, 40, 0.89)", 145 | "#d44a3a" 146 | ], 147 | "datasource": "Prometheus", 148 | "format": "none", 149 | "gauge": { 150 | "maxValue": 100, 151 | "minValue": 0, 152 | "show": false, 153 | "thresholdLabels": false, 154 | "thresholdMarkers": true 155 | }, 156 | "gridPos": { 157 | "h": 2, 158 | "w": 4, 159 | "x": 6, 160 | "y": 0 161 | }, 162 | "id": 6, 163 | "interval": null, 164 | "links": [], 165 | "mappingType": 1, 166 | "mappingTypes": [ 167 | { 168 | "name": "value to text", 169 | "value": 1 170 | }, 171 | { 172 | "name": "range to text", 173 | "value": 2 174 | } 175 | ], 176 | "maxDataPoints": 100, 177 | "nullPointMode": "connected", 178 | "nullText": null, 179 | "postfix": "", 180 | "postfixFontSize": "50%", 181 | "prefix": "", 182 | "prefixFontSize": "50%", 183 | "rangeMaps": [ 184 | { 185 | "from": "null", 186 | "text": "N/A", 187 | "to": "null" 188 | } 189 | ], 190 | "sparkline": { 191 | "fillColor": "rgba(31, 118, 189, 0.18)", 192 | "full": false, 193 | "lineColor": "rgb(31, 120, 193)", 194 | "show": false 195 | }, 196 | "tableColumn": "", 197 | "targets": [ 198 | { 199 | "expr": "jobserver_default_task_buried{instance=\"$server\"}", 200 | "format": "time_series", 201 | "intervalFactor": 1, 202 | "refId": "A" 203 | } 204 | ], 205 | "thresholds": "", 206 | "title": "Failed", 207 | "type": "singlestat", 208 | "valueFontSize": "50%", 209 | "valueMaps": [ 210 | { 211 | "op": "=", 212 | "text": "N/A", 213 | "value": "null" 214 | } 215 | ], 216 | "valueName": "current" 217 | }, 218 | { 219 | "cacheTimeout": null, 220 | "colorBackground": false, 221 | "colorValue": false, 222 | "colors": [ 223 | "#299c46", 224 | "rgba(237, 129, 40, 0.89)", 225 | "#d44a3a" 226 | ], 227 | "datasource": "Prometheus", 228 | "format": "none", 229 | "gauge": { 230 | "maxValue": 100, 231 | "minValue": 0, 232 | "show": false, 233 | "thresholdLabels": false, 234 | "thresholdMarkers": true 235 | }, 236 | "gridPos": { 237 | "h": 2, 238 | "w": 4, 239 | "x": 10, 240 | "y": 0 241 | }, 242 | "id": 12, 243 | "interval": null, 244 | "links": [], 245 | "mappingType": 1, 246 | "mappingTypes": [ 247 | { 248 | "name": "value to text", 249 | "value": 1 250 | }, 251 | { 252 | "name": "range to text", 253 | "value": 2 254 | } 255 | ], 256 | "maxDataPoints": 100, 257 | "nullPointMode": "connected", 258 | "nullText": null, 259 | "postfix": "", 260 | "postfixFontSize": "50%", 261 | "prefix": "", 262 | "prefixFontSize": "50%", 263 | "rangeMaps": [ 264 | { 265 | "from": "null", 266 | "text": "N/A", 267 | "to": "null" 268 | } 269 | ], 270 | "sparkline": { 271 | "fillColor": "rgba(31, 118, 189, 0.18)", 272 | "full": false, 273 | "lineColor": "rgb(31, 120, 193)", 274 | "show": false 275 | }, 276 | "tableColumn": "", 277 | "targets": [ 278 | { 279 | "expr": "jobserver_default_task_delayed{instance=\"$server\"}", 280 | "format": "time_series", 281 | "intervalFactor": 1, 282 | "refId": "A" 283 | } 284 | ], 285 | "thresholds": "", 286 | "title": "Delayed", 287 | "type": "singlestat", 288 | "valueFontSize": "50%", 289 | "valueMaps": [ 290 | { 291 | "op": "=", 292 | "text": "N/A", 293 | "value": "null" 294 | } 295 | ], 296 | "valueName": "current" 297 | }, 298 | { 299 | "cacheTimeout": null, 300 | "colorBackground": false, 301 | "colorValue": false, 302 | "colors": [ 303 | "#299c46", 304 | "rgba(237, 129, 40, 0.89)", 305 | "#d44a3a" 306 | ], 307 | "datasource": "Prometheus", 308 | "format": "none", 309 | "gauge": { 310 | "maxValue": 100, 311 | "minValue": 0, 312 | "show": false, 313 | "thresholdLabels": false, 314 | "thresholdMarkers": true 315 | }, 316 | "gridPos": { 317 | "h": 2, 318 | "w": 4, 319 | "x": 14, 320 | "y": 0 321 | }, 322 | "id": 4, 323 | "interval": null, 324 | "links": [], 325 | "mappingType": 1, 326 | "mappingTypes": [ 327 | { 328 | "name": "value to text", 329 | "value": 1 330 | }, 331 | { 332 | "name": "range to text", 333 | "value": 2 334 | } 335 | ], 336 | "maxDataPoints": 100, 337 | "nullPointMode": "connected", 338 | "nullText": null, 339 | "postfix": "", 340 | "postfixFontSize": "50%", 341 | "prefix": "", 342 | "prefixFontSize": "50%", 343 | "rangeMaps": [ 344 | { 345 | "from": "null", 346 | "text": "N/A", 347 | "to": "null" 348 | } 349 | ], 350 | "sparkline": { 351 | "fillColor": "rgba(31, 118, 189, 0.18)", 352 | "full": false, 353 | "lineColor": "rgb(31, 120, 193)", 354 | "show": false 355 | }, 356 | "tableColumn": "", 357 | "targets": [ 358 | { 359 | "expr": "jobserver_default_task_total{instance=\"$server\"}", 360 | "format": "time_series", 361 | "intervalFactor": 1, 362 | "refId": "A" 363 | } 364 | ], 365 | "thresholds": "", 366 | "title": "Total", 367 | "type": "singlestat", 368 | "valueFontSize": "50%", 369 | "valueMaps": [ 370 | { 371 | "op": "=", 372 | "text": "N/A", 373 | "value": "null" 374 | } 375 | ], 376 | "valueName": "current" 377 | }, 378 | { 379 | "cacheTimeout": null, 380 | "colorBackground": false, 381 | "colorValue": false, 382 | "colors": [ 383 | "#299c46", 384 | "rgba(237, 129, 40, 0.89)", 385 | "#d44a3a" 386 | ], 387 | "datasource": "Prometheus", 388 | "format": "none", 389 | "gauge": { 390 | "maxValue": 100, 391 | "minValue": 0, 392 | "show": false, 393 | "thresholdLabels": false, 394 | "thresholdMarkers": true 395 | }, 396 | "gridPos": { 397 | "h": 2, 398 | "w": 4, 399 | "x": 18, 400 | "y": 0 401 | }, 402 | "id": 8, 403 | "interval": null, 404 | "links": [], 405 | "mappingType": 1, 406 | "mappingTypes": [ 407 | { 408 | "name": "value to text", 409 | "value": 1 410 | }, 411 | { 412 | "name": "range to text", 413 | "value": 2 414 | } 415 | ], 416 | "maxDataPoints": 100, 417 | "nullPointMode": "connected", 418 | "nullText": null, 419 | "postfix": "", 420 | "postfixFontSize": "50%", 421 | "prefix": "", 422 | "prefixFontSize": "50%", 423 | "rangeMaps": [ 424 | { 425 | "from": "null", 426 | "text": "N/A", 427 | "to": "null" 428 | } 429 | ], 430 | "sparkline": { 431 | "fillColor": "rgba(31, 118, 189, 0.18)", 432 | "full": false, 433 | "lineColor": "rgb(31, 120, 193)", 434 | "show": false 435 | }, 436 | "tableColumn": "", 437 | "targets": [ 438 | { 439 | "expr": "jobserver_default_task_done{instance=\"$server\"}", 440 | "format": "time_series", 441 | "intervalFactor": 1, 442 | "legendFormat": "", 443 | "refId": "A" 444 | } 445 | ], 446 | "thresholds": "", 447 | "title": "Processed", 448 | "type": "singlestat", 449 | "valueFontSize": "50%", 450 | "valueMaps": [ 451 | { 452 | "op": "=", 453 | "text": "N/A", 454 | "value": "null" 455 | } 456 | ], 457 | "valueName": "current" 458 | }, 459 | { 460 | "aliasColors": {}, 461 | "bars": false, 462 | "dashLength": 10, 463 | "dashes": false, 464 | "datasource": "Prometheus", 465 | "fill": 1, 466 | "gridPos": { 467 | "h": 9, 468 | "w": 24, 469 | "x": 0, 470 | "y": 2 471 | }, 472 | "id": 2, 473 | "legend": { 474 | "avg": false, 475 | "current": false, 476 | "max": false, 477 | "min": false, 478 | "show": true, 479 | "total": false, 480 | "values": false 481 | }, 482 | "lines": true, 483 | "linewidth": 1, 484 | "links": [], 485 | "nullPointMode": "null", 486 | "percentage": false, 487 | "pointradius": 5, 488 | "points": false, 489 | "renderer": "flot", 490 | "seriesOverrides": [], 491 | "spaceLength": 10, 492 | "stack": false, 493 | "steppedLine": false, 494 | "targets": [ 495 | { 496 | "expr": "jobserver_default_task_ready{instance=\"$server\"}", 497 | "format": "time_series", 498 | "intervalFactor": 1, 499 | "legendFormat": "Pending", 500 | "refId": "A" 501 | }, 502 | { 503 | "expr": "jobserver_default_task_taken{instance=\"$server\"}", 504 | "format": "time_series", 505 | "intervalFactor": 1, 506 | "legendFormat": "Taken", 507 | "refId": "B" 508 | } 509 | ], 510 | "thresholds": [], 511 | "timeFrom": null, 512 | "timeShift": null, 513 | "title": "", 514 | "tooltip": { 515 | "shared": true, 516 | "sort": 0, 517 | "value_type": "individual" 518 | }, 519 | "transparent": true, 520 | "type": "graph", 521 | "xaxis": { 522 | "buckets": null, 523 | "mode": "time", 524 | "name": null, 525 | "show": true, 526 | "values": [] 527 | }, 528 | "yaxes": [ 529 | { 530 | "format": "short", 531 | "label": null, 532 | "logBase": 1, 533 | "max": null, 534 | "min": null, 535 | "show": true 536 | }, 537 | { 538 | "format": "short", 539 | "label": null, 540 | "logBase": 1, 541 | "max": null, 542 | "min": null, 543 | "show": true 544 | } 545 | ], 546 | "yaxis": { 547 | "align": false, 548 | "alignLevel": null 549 | } 550 | } 551 | ], 552 | "schemaVersion": 16, 553 | "style": "dark", 554 | "tags": [], 555 | "templating": { 556 | "list": [ 557 | { 558 | "allValue": null, 559 | "current": {}, 560 | "datasource": "Prometheus", 561 | "hide": 0, 562 | "includeAll": false, 563 | "label": "Server", 564 | "multi": false, 565 | "name": "server", 566 | "options": [], 567 | "query": "label_values(jobserver_default_task_total,instance)", 568 | "refresh": 1, 569 | "regex": "", 570 | "sort": 1, 571 | "tagValuesQuery": "", 572 | "tags": [], 573 | "tagsQuery": "", 574 | "type": "query", 575 | "useTags": false 576 | } 577 | ] 578 | }, 579 | "time": { 580 | "from": "now-6h", 581 | "to": "now" 582 | }, 583 | "timepicker": { 584 | "refresh_intervals": [ 585 | "5s", 586 | "10s", 587 | "30s", 588 | "1m", 589 | "5m", 590 | "15m", 591 | "30m", 592 | "1h", 593 | "2h", 594 | "1d" 595 | ], 596 | "time_options": [ 597 | "5m", 598 | "15m", 599 | "1h", 600 | "6h", 601 | "12h", 602 | "24h", 603 | "2d", 604 | "7d", 605 | "30d" 606 | ] 607 | }, 608 | "timezone": "", 609 | "title": "JobServer", 610 | "uid": "X_4Cnsnik", 611 | "version": 1 612 | } 613 | -------------------------------------------------------------------------------- /res/grafana/provisioning/datasources/datasources.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: Prometheus 5 | type: prometheus 6 | access: proxy 7 | url: http://prometheus:9090 8 | -------------------------------------------------------------------------------- /res/grafana/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool-php/jobserver/fb3ecd25e879f854321620c7a394135f9a8b8240/res/grafana/screenshot.png -------------------------------------------------------------------------------- /res/haproxy/haproxy.cfg: -------------------------------------------------------------------------------- 1 | # https://www.haproxy.com/blog/emulating-activepassing-application-clustering-with-haproxy/ 2 | 3 | defaults 4 | mode tcp 5 | timeout client 20s 6 | timeout server 20s 7 | timeout connect 4s 8 | 9 | frontend ft_tnt 10 | bind 0.0.0.0:3301 name tarantool 11 | default_backend bk_tnt 12 | 13 | backend bk_tnt 14 | stick-table type ip size 1 nopurge 15 | stick on dst 16 | server s1 tarantool1:3301 check 17 | server s2 tarantool2:3301 check backup 18 | 19 | frontend ft_tnt_metrics 20 | bind 0.0.0.0:8080 name tnt_metrics 21 | default_backend bk_tnt_metrics 22 | 23 | backend bk_tnt_metrics 24 | stick-table type ip size 1 nopurge 25 | stick on dst 26 | server s1 tarantool1:8080 check 27 | server s2 tarantool2:8080 check backup 28 | -------------------------------------------------------------------------------- /res/logrotate.d/logs.tpl: -------------------------------------------------------------------------------- 1 | ${APP_LOG_DIR}/*.log { 2 | daily 3 | missingok 4 | rotate 4 5 | size 1M 6 | compress 7 | delaycompress 8 | notifempty 9 | copytruncate 10 | } 11 | -------------------------------------------------------------------------------- /res/prometheus/alert.rules: -------------------------------------------------------------------------------- 1 | groups: 2 | - name: jobserver 3 | 4 | rules: 5 | - alert: lots_of_pending_tasks 6 | expr: sum(jobserver_default_task_ready) > 20 7 | for: 15s 8 | 9 | - alert: task_fails 10 | expr: deriv(jobserver_default_task_buried[1m]) > 0 11 | for: 10s 12 | labels: 13 | severity: major 14 | -------------------------------------------------------------------------------- /res/prometheus/alertmanager.yml.dist: -------------------------------------------------------------------------------- 1 | # The root route on which each incoming alert enters. 2 | route: 3 | # The root route must not have any matchers as it is the entry point for 4 | # all alerts. It needs to have a receiver configured so alerts that do not 5 | # match any of the sub-routes are sent to someone. 6 | receiver: 'slack' 7 | 8 | receivers: 9 | - name: 'slack' 10 | slack_configs: 11 | # https://api.slack.com/incoming-webhooks 12 | - api_url: 13 | channel: 14 | username: alertmanager 15 | -------------------------------------------------------------------------------- /res/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | evaluation_interval: 15s 4 | 5 | external_labels: 6 | monitor: 'jobserver-monitor' 7 | 8 | scrape_configs: 9 | - job_name: 'jobserver' 10 | scrape_interval: 15s 11 | static_configs: 12 | - targets: ['tarantool:8080'] 13 | 14 | alerting: 15 | alertmanagers: 16 | - scheme: http 17 | static_configs: 18 | - targets: ['alertmanager:9093'] 19 | 20 | rule_files: 21 | - 'alert.rules' 22 | -------------------------------------------------------------------------------- /res/systemd/jobserver-worker.service.tpl: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Job Server Worker (${APP_TNT_QUEUE}) 3 | 4 | [Service] 5 | EnvironmentFile=/etc/default/jobserver-worker 6 | ExecStart=${APP_RELEASE_DIR}/vendor/bin/jobqueue run "${APP_TNT_QUEUE}" --host "${TNT_JOBQUEUE_HOST}" --config ${APP_RELEASE_DIR}/app/config/jobqueue.php --executors-config ${APP_RELEASE_DIR}/app/config/executors.php 7 | Restart=always 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /res/systemd/jobserver-worker.tpl: -------------------------------------------------------------------------------- 1 | # copy this file to /etc/default/jobserver-worker 2 | # and adjust variable values 3 | 4 | TNT_JOBQUEUE_HOST=${APP_TNT_HOST} 5 | TNT_JOBQUEUE_USER=${APP_TNT_USER} 6 | TNT_JOBQUEUE_PASSWORD=${APP_TNT_PASSWORD} 7 | -------------------------------------------------------------------------------- /src/Di/Config.php: -------------------------------------------------------------------------------- 1 | env = $env; 14 | 15 | return $this; 16 | } 17 | 18 | public function getEnv(): string 19 | { 20 | return $this->env; 21 | } 22 | 23 | private function setDebug(bool $debug): self 24 | { 25 | $this->debug = $debug; 26 | 27 | return $this; 28 | } 29 | 30 | public function isDebug(): bool 31 | { 32 | return $this->debug; 33 | } 34 | 35 | private function setOptions(array $options): self 36 | { 37 | $this->options = $options; 38 | 39 | return $this; 40 | } 41 | 42 | public function getOptions(): array 43 | { 44 | return $this->options; 45 | } 46 | 47 | private function set(string $option, $value): self 48 | { 49 | $this->options[$option] = $value; 50 | 51 | return $this; 52 | } 53 | 54 | public function get(string $option) 55 | { 56 | if (array_key_exists($option, $this->options)) { 57 | return $this->options[$option]; 58 | } 59 | 60 | throw new \InvalidArgumentException(sprintf('Unknown option "%s".', $option)); 61 | } 62 | 63 | public function tryGet(string $option, $default = null) 64 | { 65 | return array_key_exists($option, $this->options) 66 | ? $this->options[$option] 67 | : $default; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Di/Container.php: -------------------------------------------------------------------------------- 1 | setOptions($options); 15 | $this->setEnv($env); 16 | $this->setDebug($debug); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Di/ContainerBuilder.php: -------------------------------------------------------------------------------- 1 | setEnv('dev'); 17 | $this->setDebug(false); 18 | $this->setOptions($options); 19 | } 20 | 21 | public function build(): Container 22 | { 23 | return new Container($this->options, $this->env, $this->debug); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Di/Handlers.php: -------------------------------------------------------------------------------- 1 | setLogFile($this->get(Options::LOGGER_FILE)) 19 | ->setLogLevel($this->isDebug() ? LogLevel::DEBUG : LogLevel::INFO) 20 | ->setConnectionOptions(['tcp_nodelay' => true]) 21 | ; 22 | } 23 | 24 | public function getJobQueueExecutor(): Executor 25 | { 26 | static $executor; 27 | 28 | return $executor ?? $executor = (new ExecutorFactory())($this); 29 | } 30 | 31 | public function getJobQueueAutowiredArgs(): array 32 | { 33 | return [ 34 | 'logger' => $this->getLogger(), 35 | ]; 36 | } 37 | 38 | abstract public function get(string $option); 39 | 40 | abstract public function getLogger(): LoggerInterface; 41 | 42 | abstract public function isDebug(): bool; 43 | } 44 | -------------------------------------------------------------------------------- /src/Di/Logging.php: -------------------------------------------------------------------------------- 1 | get(Options::LOGGER_FILE), 18 | $this->isDebug() ? Logger::DEBUG : Logger::INFO 19 | ), 20 | ]); 21 | } 22 | 23 | abstract public function get(string $option); 24 | 25 | abstract public function isDebug(): bool; 26 | } 27 | -------------------------------------------------------------------------------- /src/Di/Options.php: -------------------------------------------------------------------------------- 1 | container = $container; 17 | } 18 | 19 | public function resolve($payload): callable 20 | { 21 | if (empty($payload[JobOptions::PAYLOAD_SERVICE])) { 22 | throw BadPayloadException::missingOrEmptyKeyValue($payload, JobOptions::PAYLOAD_SERVICE, 'string', __CLASS__); 23 | } 24 | 25 | $handlerName = $payload[JobOptions::PAYLOAD_SERVICE]; 26 | $getter = 'get'.self::camelize($handlerName).'Handler'; 27 | 28 | if (method_exists($this->container, $getter)) { 29 | return $this->container->$getter(); 30 | } 31 | 32 | throw new \UnexpectedValueException(sprintf('Unknown handler "%s".', $handlerName)); 33 | } 34 | 35 | private static function camelize(string $value): string 36 | { 37 | return strtr(ucwords(strtr($value, ['_' => ' ', '.' => '_ ', '\\' => '_ '])), [' ' => '']); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/JobQueue/Executor/ExecutorFactory.php: -------------------------------------------------------------------------------- 1 | getJobQueueAutowiredArgs() 19 | ); 20 | 21 | return new ExecutorChain([ 22 | $callbackExecutor, 23 | new ProcessExecutor(), 24 | ]); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/UseCase/Greet/GreetCommand.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 20 | } 21 | 22 | protected function configure(): void 23 | { 24 | $this 25 | ->setName('handler:greet') 26 | ->setDescription('Greet someone') 27 | ->addArgument('name', InputArgument::REQUIRED, 'Who do you want to greet?') 28 | ; 29 | } 30 | 31 | protected function execute(InputInterface $input, OutputInterface $output): void 32 | { 33 | ($this->handler)( 34 | $input->getArgument('name'), 35 | new ConsoleLogger($output) 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/UseCase/Greet/GreetHandler.php: -------------------------------------------------------------------------------- 1 | yell = $yell; 14 | } 15 | 16 | public function __invoke(string $name, Logger $logger): void 17 | { 18 | $text = "Hello $name"; 19 | 20 | if ($this->yell) { 21 | $text = strtoupper($text); 22 | } 23 | 24 | $logger->info($text); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/UseCase/Greet/GreetHandlerFactory.php: -------------------------------------------------------------------------------- 1 | get(Options::GREET_YELL) 14 | ); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/Unit/Di/ContainerBuilderTest.php: -------------------------------------------------------------------------------- 1 | 'bar']; 14 | $builder = new ContainerBuilder($options); 15 | 16 | self::assertSame($options['foo'], $builder->get('foo')); 17 | } 18 | 19 | public function testSetGetEnv(): void 20 | { 21 | $builder = new ContainerBuilder(); 22 | $env = 'test'; 23 | 24 | self::assertNotSame($env, $builder->getEnv()); 25 | $builder->setEnv($env); 26 | self::assertSame($env, $builder->getEnv()); 27 | } 28 | 29 | public function testSetGetDebug(): void 30 | { 31 | $builder = new ContainerBuilder(); 32 | 33 | self::assertNotTrue($builder->isDebug()); 34 | $builder->setDebug(true); 35 | self::assertTrue($builder->isDebug()); 36 | $builder->setDebug(false); 37 | self::assertFalse($builder->isDebug()); 38 | } 39 | 40 | public function testSetOptions(): void 41 | { 42 | $builder = new ContainerBuilder(); 43 | $builder->setOptions(['foo' => 'bar']); 44 | 45 | self::assertSame('bar', $builder->get('foo')); 46 | } 47 | 48 | public function testGetOptions(): void 49 | { 50 | $options = ['foo' => 'bar', 'baz' => 'qux']; 51 | $builder = new ContainerBuilder($options); 52 | 53 | self::assertSame($options, $builder->getOptions()); 54 | } 55 | 56 | public function testSet(): void 57 | { 58 | $builder = new ContainerBuilder(); 59 | $builder->set('foo', 'bar'); 60 | 61 | self::assertSame('bar', $builder->get('foo')); 62 | } 63 | 64 | /** 65 | * @expectedException \InvalidArgumentException 66 | * @expectedExceptionMessage Unknown option "foo". 67 | */ 68 | public function testGetMissingOption(): void 69 | { 70 | $builder = new ContainerBuilder(); 71 | 72 | $builder->get('foo'); 73 | } 74 | 75 | public function testTryGet(): void 76 | { 77 | $builder = new ContainerBuilder(); 78 | 79 | self::assertSame(42, $builder->tryGet('foo', 42)); 80 | } 81 | 82 | public function testBuild(): void 83 | { 84 | $builder = new ContainerBuilder(['foo' => 'bar']); 85 | $builder->setEnv('baz'); 86 | $builder->setDebug(true); 87 | 88 | $container = $builder->build(); 89 | 90 | self::assertInstanceOf(Container::class, $container); 91 | self::assertSame('bar', $container->get('foo')); 92 | self::assertSame('baz', $container->getEnv()); 93 | self::assertTrue($container->isDebug()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /tests/Unit/Di/ContainerTest.php: -------------------------------------------------------------------------------- 1 | 'bar'], 'test', true); 13 | 14 | self::assertSame('bar', $container->get('foo')); 15 | } 16 | 17 | public function testGetEnv(): void 18 | { 19 | $container = new Container([], 'test', true); 20 | 21 | self::assertSame('test', $container->getEnv()); 22 | } 23 | 24 | public function testIsDebug(): void 25 | { 26 | $container = new Container([], 'test', true); 27 | 28 | self::assertTrue($container->isDebug()); 29 | } 30 | 31 | public function testGetOptions(): void 32 | { 33 | $options = ['foo' => 'bar', 'baz' => 'qux']; 34 | $container = new Container($options, 'test', true); 35 | 36 | self::assertSame($options, $container->getOptions()); 37 | } 38 | 39 | /** 40 | * @expectedException \InvalidArgumentException 41 | * @expectedExceptionMessage Unknown option "foo". 42 | */ 43 | public function testGetMissingOption(): void 44 | { 45 | $container = new Container([], 'test', true); 46 | 47 | $container->get('foo'); 48 | } 49 | 50 | public function testTryGet(): void 51 | { 52 | $container = new Container([], 'test', true); 53 | 54 | self::assertSame(42, $container->tryGet('foo', 42)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/Unit/UseCase/Greet/GreetHandlerTest.php: -------------------------------------------------------------------------------- 1 | createMock(Logger::class); 14 | $logger->expects(self::once())->method('info') 15 | ->with('Hello World'); 16 | 17 | $handler = new GreetHandler(false); 18 | $handler('World', $logger); 19 | } 20 | 21 | public function testInvokeWithYell(): void 22 | { 23 | $logger = $this->createMock(Logger::class); 24 | $logger->expects(self::once())->method('info') 25 | ->with('HELLO WORLD'); 26 | 27 | $handler = new GreetHandler(true); 28 | $handler('World', $logger); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /var/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tarantool-php/jobserver/fb3ecd25e879f854321620c7a394135f9a8b8240/var/log/.gitkeep --------------------------------------------------------------------------------