├── .gitignore ├── .travis.yml ├── src ├── Queue │ ├── Ph4DatabaseInterface.php │ ├── Console │ │ ├── stubs │ │ │ ├── failed_jobs.stub │ │ │ ├── workers.stub │ │ │ └── jobs.stub │ │ ├── TableCommand.php │ │ └── FailedTableCommand.php │ ├── Connectors │ │ └── DatabasePh4Connector.php │ ├── Misc │ │ └── WorkerTracker.php │ ├── PessimisticDatabaseQueue.php │ └── OptimisticDatabaseQueue.php └── LaravelQueuePh4ServiceProvider.php ├── tests ├── Queue │ ├── Connectors │ │ └── DatabasePh4ConnectorTest.php │ ├── DatabasePh4QueueTest.php │ ├── QueueDatabaseQueueIntegrationTest.php │ └── QueueDatabaseQueueUnitTest.php └── LaravelQueuePh4ServiceProviderTest.php ├── phpunit.xml.dist ├── LICENSE ├── composer.json ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /build 3 | /vendor 4 | .phpstorm.meta.php 5 | phpunit.xml 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | 3 | language: php 4 | 5 | php: 6 | - 7.3 7 | 8 | before_script: 9 | - COMPOSER_DISCARD_CHANGES=1 composer update --prefer-dist --no-interaction --no-suggest 10 | 11 | sudo: false 12 | 13 | script: 14 | - composer test 15 | -------------------------------------------------------------------------------- /src/Queue/Ph4DatabaseInterface.php: -------------------------------------------------------------------------------- 1 | assertTrue($rc->implementsInterface(ConnectorInterface::class)); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Queue/Console/stubs/failed_jobs.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->text('connection'); 19 | $table->text('queue'); 20 | $table->longText('payload'); 21 | $table->longText('exception'); 22 | $table->timestamp('failed_at')->useCurrent(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('{{table}}'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Queue/Console/stubs/workers.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('queue')->index(); 19 | $table->unsignedInteger('pid'); 20 | $table->dateTime('last_ping_at')->nullable(); 21 | $table->dateTime('created_at')->nullable(); 22 | $table->dateTime('updated_at')->nullable(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('{{table}}'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests/ 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | src/ 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dusan Klinec (ph4r05) 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 | -------------------------------------------------------------------------------- /src/LaravelQueuePh4ServiceProvider.php: -------------------------------------------------------------------------------- 1 | commands([ 21 | TableCommand::class, 22 | // FailedTableCommand::class, 23 | ]); 24 | } 25 | 26 | /** 27 | * Register the application's event listeners. 28 | * 29 | * @return void 30 | */ 31 | public function boot() 32 | { 33 | /** @var QueueManager $queue */ 34 | $queue = $this->app['queue']; 35 | 36 | $queue->addConnector('database_ph4', function () { 37 | return new DatabasePh4Connector($this->app['db']); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Queue/Console/stubs/jobs.stub: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('queue')->index(); 19 | $table->longText('payload'); 20 | $table->unsignedTinyInteger('attempts'); 21 | $table->unsignedInteger('reserved_at')->nullable(); 22 | $table->unsignedInteger('available_at'); 23 | $table->unsignedInteger('created_at'); 24 | $table->unsignedInteger('version')->default(0); 25 | $table->tinyInteger('delete_mark')->default(0); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('{{table}}'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ph4r05/laravel-queue-database-ph4", 3 | "description": "Laravel Database queue driver with optimistic locking", 4 | "version": "dev-master", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Dusan Klinec (ph4r05)", 9 | "email": "ph4r05@gmail.com", 10 | "homepage": "https://ph4r05.deadcode.me" 11 | } 12 | ], 13 | "require": { 14 | "php": "^7.2", 15 | "illuminate/support": "^6.2", 16 | "illuminate/database": "6.*", 17 | "illuminate/queue": "6.*" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": " ^7.0", 21 | "mockery/mockery": "~1.0", 22 | "ext-json": "*" 23 | }, 24 | "autoload": { 25 | "psr-4": { 26 | "ph4r05\\LaravelDatabasePh4\\": "src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "ph4r05\\LaravelDatabasePh4\\Tests\\": "tests/" 32 | } 33 | }, 34 | "extra": { 35 | "laravel": { 36 | "providers": [ 37 | "ph4r05\\LaravelDatabasePh4\\LaravelQueuePh4ServiceProvider" 38 | ] 39 | } 40 | }, 41 | "scripts": { 42 | "test": "vendor/bin/phpunit" 43 | }, 44 | "minimum-stability": "dev", 45 | "prefer-stable": true 46 | } 47 | -------------------------------------------------------------------------------- /src/Queue/Connectors/DatabasePh4Connector.php: -------------------------------------------------------------------------------- 1 | implementsInterface(Ph4DatabaseInterface::class)) { 25 | throw new \LogicException(sprintf('The factory_class option has to be valid class that implements "%s"', Ph4DatabaseInterface::class)); 26 | } 27 | 28 | return new $factoryClass( 29 | $this->connections->connection($config['connection'] ?? null), 30 | $config['table'], 31 | $config['queue'] ?? 'default', 32 | $config['retry_after'] ?? 60, 33 | $config 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tests/LaravelQueuePh4ServiceProviderTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($rc->isSubclassOf(ServiceProvider::class)); 20 | } 21 | 22 | public function testShouldAddRabbitMQConnectorOnBoot() 23 | { 24 | $resolverMock = $this->createMock(ConnectionResolverInterface::class); 25 | 26 | $queueMock = $this->createMock(QueueManager::class); 27 | $queueMock 28 | ->expects($this->once()) 29 | ->method('addConnector') 30 | ->with('database_ph4', $this->isInstanceOf(\Closure::class)) 31 | ->willReturnCallback(function ($driver, \Closure $resolver) use ($resolverMock) { 32 | $connector = $resolver(); 33 | 34 | $this->assertInstanceOf(DatabasePh4Connector::class, $connector); 35 | $this->assertAttributeSame($resolverMock, 'connections', $connector); 36 | }); 37 | 38 | $app = Container::getInstance(); 39 | $app['queue'] = $queueMock; 40 | $app['db'] = $resolverMock; 41 | 42 | $providerMock = new LaravelQueuePh4ServiceProvider($app); 43 | 44 | $providerMock->boot(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/Queue/DatabasePh4QueueTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($rc->implementsInterface(\Illuminate\Contracts\Queue\Queue::class)); 18 | } 19 | 20 | public function testShouldBeSubClassOfQueue() 21 | { 22 | $rc = new \ReflectionClass(OptimisticDatabaseQueue::class); 23 | 24 | $this->assertTrue($rc->isSubclassOf(\Illuminate\Queue\Queue::class)); 25 | } 26 | 27 | public function testCouldBeConstructedWithExpectedArguments() 28 | { 29 | $connectionMock = $this->createMock(Connection::class); 30 | $x = new OptimisticDatabaseQueue($connectionMock, 'jobs_ph4', 'default', 90, $this->createDummyConfig()); 31 | 32 | $this->assertNotNull($x); 33 | } 34 | 35 | private function createDummyContainer() 36 | { 37 | $logger = $this->createMock(LoggerInterface::class); 38 | 39 | $container = new Container(); 40 | $container['log'] = $logger; 41 | 42 | return $container; 43 | } 44 | 45 | /** 46 | * @return array 47 | */ 48 | private function createDummyConfig() 49 | { 50 | return [ 51 | 'driver' => 'sqlite', 52 | 'database' => ':memory:', 53 | 'table' => 'jobs_ph4', 54 | 'queue' => 'default', 55 | 'retry_after' => 5, 56 | ]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Queue/Misc/WorkerTracker.php: -------------------------------------------------------------------------------- 1 | last ping time. 26 | * 27 | * @var array 28 | */ 29 | protected $lastPingMap = []; 30 | 31 | /** 32 | * queue -> active workers list. 33 | * 34 | * @var array 35 | */ 36 | protected $workersMap = []; 37 | 38 | public function __construct() 39 | { 40 | // TODO: implement 41 | } 42 | 43 | /** 44 | * Updates the ping time for the given queue for the current worker. 45 | * Can do nothing if the last ping was too recent. 46 | * 47 | * @param $queue 48 | * @param $force bool - override caching, enforce db write 49 | */ 50 | public function tick($queue, $force = false) 51 | { 52 | // TODO: implement 53 | } 54 | 55 | /** 56 | * Creates the current worker records. Initial step when worker starts. 57 | * 58 | * @param $queues 59 | */ 60 | public function create($queues) 61 | { 62 | // TODO: implement 63 | } 64 | 65 | /** 66 | * Destroys records for the current worker. 67 | * Called when worker terminates. 68 | * 69 | * @param $queues 70 | */ 71 | public function destroy($queues) 72 | { 73 | // TODO: implement 74 | } 75 | 76 | /** 77 | * Returns number of workers active for the queue. 78 | * 79 | * @param $queue 80 | */ 81 | public function getNumWorkers($queue) 82 | { 83 | // TODO: implement 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Queue/Console/TableCommand.php: -------------------------------------------------------------------------------- 1 | files = $files; 51 | $this->composer = $composer; 52 | } 53 | 54 | /** 55 | * Execute the console command. 56 | * 57 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 58 | * 59 | * @return void 60 | */ 61 | public function handle() 62 | { 63 | $table = $this->laravel['config']['queue.connections.database_ph4.table'] ?? 'jobs_ph4'; 64 | 65 | $this->replaceMigration( 66 | $this->createBaseMigration($table), 67 | $table, 68 | Str::studly($table) 69 | ); 70 | 71 | $this->info('Migration created successfully!'); 72 | 73 | $this->composer->dumpAutoloads(); 74 | } 75 | 76 | /** 77 | * Create a base migration file for the table. 78 | * 79 | * @param string $table 80 | * 81 | * @return string 82 | */ 83 | protected function createBaseMigration($table) 84 | { 85 | return $this->laravel['migration.creator']->create( 86 | 'create_'.$table.'_table', 87 | $this->laravel->databasePath().'/migrations' 88 | ); 89 | } 90 | 91 | /** 92 | * Replace the generated migration with the job table stub. 93 | * 94 | * @param string $path 95 | * @param string $table 96 | * @param string $tableClassName 97 | * 98 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 99 | * 100 | * @return void 101 | */ 102 | protected function replaceMigration($path, $table, $tableClassName) 103 | { 104 | $stub = str_replace( 105 | ['{{table}}', '{{tableClassName}}'], 106 | [$table, $tableClassName], 107 | $this->files->get(__DIR__.'/stubs/jobs.stub') 108 | ); 109 | 110 | $this->files->put($path, $stub); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Queue/Console/FailedTableCommand.php: -------------------------------------------------------------------------------- 1 | files = $files; 51 | $this->composer = $composer; 52 | } 53 | 54 | /** 55 | * Execute the console command. 56 | * 57 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 58 | * 59 | * @return void 60 | */ 61 | public function handle() 62 | { 63 | $table = $this->laravel['config']['queue.failed.table']; 64 | 65 | $this->replaceMigration( 66 | $this->createBaseMigration($table), 67 | $table, 68 | Str::studly($table) 69 | ); 70 | 71 | $this->info('Migration created successfully!'); 72 | 73 | $this->composer->dumpAutoloads(); 74 | } 75 | 76 | /** 77 | * Create a base migration file for the table. 78 | * 79 | * @param string $table 80 | * 81 | * @return string 82 | */ 83 | protected function createBaseMigration($table = 'failed_jobs_ph4') 84 | { 85 | return $this->laravel['migration.creator']->create( 86 | 'create_'.$table.'_table', 87 | $this->laravel->databasePath().'/migrations' 88 | ); 89 | } 90 | 91 | /** 92 | * Replace the generated migration with the failed job table stub. 93 | * 94 | * @param string $path 95 | * @param string $table 96 | * @param string $tableClassName 97 | * 98 | * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException 99 | * 100 | * @return void 101 | */ 102 | protected function replaceMigration($path, $table, $tableClassName) 103 | { 104 | $stub = str_replace( 105 | ['{{table}}', '{{tableClassName}}'], 106 | [$table, $tableClassName], 107 | $this->files->get(__DIR__.'/stubs/failed_jobs.stub') 108 | ); 109 | 110 | $this->files->put($path, $stub); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Queue/PessimisticDatabaseQueue.php: -------------------------------------------------------------------------------- 1 | deleteRetry = $config['delete_retry'] ?? 5; 36 | } 37 | 38 | /** 39 | * Pop the next job off of the queue. 40 | * 41 | * @param string $queue 42 | * 43 | * @throws \Exception|\Throwable 44 | * 45 | * @return \Illuminate\Contracts\Queue\Job|null 46 | */ 47 | public function pop($queue = null) 48 | { 49 | $queue = $this->getQueue($queue); 50 | $job = $this->database->transaction(function () use ($queue) { 51 | if ($job = $this->getNextAvailableJob($queue)) { 52 | return $this->marshalJob($queue, $job); 53 | } 54 | }); 55 | 56 | return $job; 57 | } 58 | 59 | /** 60 | * Marshal the reserved job into a DatabaseJob instance. 61 | * 62 | * @param string $queue 63 | * @param \Illuminate\Queue\Jobs\DatabaseJobRecord $job 64 | * 65 | * @return \Illuminate\Queue\Jobs\DatabaseJob 66 | */ 67 | protected function marshalJob($queue, $job) 68 | { 69 | $job = $this->markJobAsReserved($job); 70 | 71 | return new DatabaseJob( 72 | $this->container, 73 | $this, 74 | $job, 75 | $this->connectionName, 76 | $queue 77 | ); 78 | } 79 | 80 | /** 81 | * Delete a reserved job from the queue. 82 | * https://github.com/laravel/framework/issues/7046. 83 | * 84 | * @param string $queue 85 | * @param string $id 86 | * 87 | * @throws \Exception|\Throwable 88 | * 89 | * @return void 90 | */ 91 | public function deleteReserved($queue, $id) 92 | { 93 | try { 94 | $this->deleteJob($id); 95 | } catch (\Throwable $e) { 96 | Log::error('Probably deadlock: '.$e->getMessage()); 97 | } 98 | } 99 | 100 | /** 101 | * @param $id 102 | * 103 | * @throws \Exception 104 | * @throws \Throwable 105 | */ 106 | protected function deleteJob($id) 107 | { 108 | if ($this->deleteRetry <= 0) { 109 | $this->database->table($this->table)->where('id', $id)->delete(); 110 | } else { 111 | $this->database->transaction(function () use ($id) { 112 | $this->database->table($this->table)->where('id', $id)->delete(); 113 | }, $this->deleteRetry); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Database Queue driver for Laravel with optimistic locking 2 | ========================================================= 3 | [![Latest Stable Version](https://poser.pugx.org/ph4r05/laravel-queue-database-ph4/v/stable?format=flat-square)](https://packagist.org/packages/ph4r05/laravel-queue-database-ph4) 4 | [![Build Status](https://img.shields.io/travis/ph4r05/laravel-queue-database-ph4.svg?style=flat-square)](https://travis-ci.org/ph4r05/laravel-queue-database-ph4) 5 | [![Total Downloads](https://poser.pugx.org/ph4r05/laravel-queue-database-ph4/downloads?format=flat-square)](https://packagist.org/packages/ph4r05/laravel-queue-database-ph4) 6 | [![StyleCI](https://styleci.io/repos/115196581/shield)](https://styleci.io/repos/115196581) 7 | [![License](https://poser.pugx.org/ph4r05/laravel-queue-database-ph4/license?format=flat-square)](https://packagist.org/packages/ph4r05/laravel-queue-database-ph4) 8 | 9 | Laravel database queue implementation using optimistic locking. 10 | Increases concurrency, eliminates deadlocks. 11 | 12 | #### Installation 13 | 14 | 1. Install this package via composer using: 15 | 16 | ``` 17 | composer require ph4r05/laravel-queue-database-ph4 18 | ``` 19 | 20 | 2. Create job table 21 | 22 | ```bash 23 | php artisan queue_ph4:table 24 | php artisan migrate 25 | ``` 26 | 27 | 3. Queue configuration 28 | 29 | ```php 30 | [ 35 | 'driver' => 'database_ph4', 36 | 'table' => 'jobs_ph4', 37 | 'queue' => 'default', 38 | 'retry_after' => 4, 39 | 'num_workers' => 1, 40 | 'window_strategy' => 1, 41 | ], 42 | ]; 43 | ``` 44 | 45 | - The `num_workers` should correspond to the number of workers processing jobs in the queue. 46 | - `window_strategy`. 47 | - 0 means worker selects one available job for processing. 48 | Smaller throughput, job ordering is preserved as with pessimistic locking. 49 | - 1 means workers will select `num_workers` next available jobs and picks one at random. 50 | Higher throughput with slight job reordering (for more info please refer to the [blog]) 51 | 52 | 53 | To use the optimistic locking you can now change your `.env`: 54 | 55 | ``` 56 | QUEUE_DRIVER=database_ph4 57 | ``` 58 | 59 | #### Usage 60 | 61 | Once you completed the configuration you can use Laravel Queue API. If you used other queue drivers you do not need to change anything else. If you do not know how to use Queue API, please refer to the official Laravel documentation: http://laravel.com/docs/queues 62 | 63 | #### Testing 64 | 65 | Run the tests with: 66 | 67 | ``` bash 68 | vendor/bin/phpunit 69 | ``` 70 | 71 | #### Blog post about optimistic locking 72 | 73 | https://ph4r05.deadcode.me/blog/2017/12/23/laravel-queues-optimization.html 74 | 75 | Benefits: 76 | 77 | - No need for explicit transactions. Single query auto-commit transactions are OK. 78 | - No DB level locking, thus no deadlocks. Works also with databases without deadlock detection (older MySQL). 79 | - Job executed exactly once (as opposed to pessimistic default DB locking) 80 | - High throughput. 81 | - Tested with MySQL, PostgreSQL, Sqlite. 82 | 83 | Cons: 84 | - Job ordering can be slightly shifted with multiple workers (reordering 0-70 in 10 000 jobs) 85 | 86 | #### Contribution 87 | 88 | You can contribute to this package by discovering bugs and opening issues. Please, add to which version of package you create pull request or issue. 89 | 90 | 91 | #### Donations 92 | 93 | Thank you for all your support! 94 | 95 | Monero: 96 | ``` 97 | 87iMuZKqgBZbxjvjJaieTqLBsW3VKtkiuRXL2arcr8eiL4eK8kFi4QbaXCXGgmNWYp5Linpd9pj5McFZ8SQevkenGuZWMCT 98 | ``` 99 | 100 | Bitcoin: 101 | ``` 102 | 17hzavLCXqavzmWEEjtX54VeCJVmbrHZQC 103 | ``` 104 | 105 | [blog]: https://ph4r05.deadcode.me/blog/2017/12/23/laravel-queues-optimization.html 106 | -------------------------------------------------------------------------------- /tests/Queue/QueueDatabaseQueueIntegrationTest.php: -------------------------------------------------------------------------------- 1 | addConnection([ 36 | 'driver' => 'sqlite', 37 | 'database' => ':memory:', 38 | ]); 39 | 40 | $db->bootEloquent(); 41 | 42 | $db->setAsGlobal(); 43 | 44 | $this->table = 'jobs'; 45 | 46 | $this->queue = new OptimisticDatabaseQueue($this->connection(), $this->table); 47 | 48 | $this->container = $this->createMock(Container::class); 49 | 50 | $this->queue->setContainer($this->container); 51 | 52 | $this->createSchema(); 53 | } 54 | 55 | /** 56 | * Setup the database schema. 57 | * 58 | * @return void 59 | */ 60 | public function createSchema() 61 | { 62 | $this->schema()->create($this->table, function (Blueprint $table) { 63 | $table->bigIncrements('id'); 64 | $table->string('queue'); 65 | $table->longText('payload'); 66 | $table->tinyInteger('attempts')->unsigned(); 67 | $table->unsignedInteger('reserved_at')->nullable(); 68 | $table->unsignedInteger('available_at'); 69 | $table->unsignedInteger('created_at'); 70 | $table->unsignedInteger('version')->default(0); 71 | $table->tinyInteger('delete_mark')->default(0); 72 | $table->index(['queue']); 73 | }); 74 | } 75 | 76 | /** 77 | * Get a database connection instance. 78 | * 79 | * @return \Illuminate\Database\Connection 80 | */ 81 | protected function connection() 82 | { 83 | return Eloquent::getConnectionResolver()->connection(); 84 | } 85 | 86 | /** 87 | * Get a schema builder instance. 88 | * 89 | * @return \Illuminate\Database\Schema\Builder 90 | */ 91 | protected function schema() 92 | { 93 | return $this->connection()->getSchemaBuilder(); 94 | } 95 | 96 | /** 97 | * Tear down the database schema. 98 | * 99 | * @return void 100 | */ 101 | protected function tearDown(): void 102 | { 103 | $this->schema()->drop('jobs'); 104 | } 105 | 106 | /** 107 | * Test that jobs that are not reserved and have an available_at value less then now, are popped. 108 | */ 109 | public function testAvailableAndUnReservedJobsArePopped() 110 | { 111 | $this->connection() 112 | ->table('jobs') 113 | ->insert([ 114 | 'id' => 1, 115 | 'queue' => $mock_queue_name = 'mock_queue_name', 116 | 'payload' => 'mock_payload', 117 | 'attempts' => 0, 118 | 'reserved_at' => null, 119 | 'available_at' => Carbon::now()->subSeconds(1)->getTimestamp(), 120 | 'created_at' => Carbon::now()->getTimestamp(), 121 | 'version' => 0, 122 | ]); 123 | 124 | $popped_job = $this->queue->pop($mock_queue_name); 125 | 126 | $this->assertNotNull($popped_job); 127 | } 128 | 129 | /** 130 | * Test that when jobs are popped, the attempts attribute is incremented. 131 | */ 132 | public function testPoppedJobsIncrementAttempts() 133 | { 134 | $job = [ 135 | 'id' => 1, 136 | 'queue' => 'mock_queue_name', 137 | 'payload' => 'mock_payload', 138 | 'attempts' => 0, 139 | 'reserved_at' => null, 140 | 'available_at' => Carbon::now()->subSeconds(1)->getTimestamp(), 141 | 'created_at' => Carbon::now()->getTimestamp(), 142 | 'version' => 0, 143 | ]; 144 | 145 | $this->connection()->table('jobs')->insert($job); 146 | 147 | $popped_job = $this->queue->pop($job['queue']); 148 | 149 | $database_record = $this->connection()->table('jobs')->find($job['id']); 150 | 151 | $this->assertEquals(1, $database_record->attempts, 'Job attempts not updated in the database!'); 152 | $this->assertEquals(1, $popped_job->attempts(), 'The "attempts" attribute of the Job object was not updated by pop!'); 153 | } 154 | 155 | /** 156 | * Test that jobs that are not reserved and have an available_at value in the future, are not popped. 157 | */ 158 | public function testUnavailableJobsAreNotPopped() 159 | { 160 | $this->connection() 161 | ->table('jobs') 162 | ->insert([ 163 | 'id' => 1, 164 | 'queue' => $mock_queue_name = 'mock_queue_name', 165 | 'payload' => 'mock_payload', 166 | 'attempts' => 0, 167 | 'reserved_at' => null, 168 | 'available_at' => Carbon::now()->addSeconds(60)->getTimestamp(), 169 | 'created_at' => Carbon::now()->getTimestamp(), 170 | 'version' => 0, 171 | ]); 172 | 173 | $popped_job = $this->queue->pop($mock_queue_name); 174 | 175 | $this->assertNull($popped_job); 176 | } 177 | 178 | /** 179 | * Test that jobs that are reserved and have expired are popped. 180 | */ 181 | public function testThatReservedAndExpiredJobsArePopped() 182 | { 183 | $this->connection() 184 | ->table('jobs') 185 | ->insert([ 186 | 'id' => 1, 187 | 'queue' => $mock_queue_name = 'mock_queue_name', 188 | 'payload' => 'mock_payload', 189 | 'attempts' => 0, 190 | 'reserved_at' => Carbon::now()->subDay()->getTimestamp(), 191 | 'available_at' => Carbon::now()->addDay()->getTimestamp(), 192 | 'created_at' => Carbon::now()->getTimestamp(), 193 | 'version' => 0, 194 | ]); 195 | 196 | $popped_job = $this->queue->pop($mock_queue_name); 197 | 198 | $this->assertNotNull($popped_job); 199 | } 200 | 201 | /** 202 | * Test that jobs that are reserved and not expired and available are not popped. 203 | */ 204 | public function testThatReservedJobsAreNotPopped() 205 | { 206 | $this->connection() 207 | ->table('jobs') 208 | ->insert([ 209 | 'id' => 1, 210 | 'queue' => $mock_queue_name = 'mock_queue_name', 211 | 'payload' => 'mock_payload', 212 | 'attempts' => 0, 213 | 'reserved_at' => Carbon::now()->addDay()->getTimestamp(), 214 | 'available_at' => Carbon::now()->subDay()->getTimestamp(), 215 | 'created_at' => Carbon::now()->getTimestamp(), 216 | 'version' => 0, 217 | ]); 218 | 219 | $popped_job = $this->queue->pop($mock_queue_name); 220 | 221 | $this->assertNull($popped_job); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Queue/OptimisticDatabaseQueue.php: -------------------------------------------------------------------------------- 1 | numWorkers = $config['num_workers'] ?? 1; 44 | $this->windowStrategy = $config['window_strategy'] ?? 1; 45 | } 46 | 47 | /** 48 | * Create an array to insert for the given job. 49 | * 50 | * @param string|null $queue 51 | * @param string $payload 52 | * @param int $availableAt 53 | * @param int $attempts 54 | * 55 | * @return array 56 | */ 57 | protected function buildDatabaseRecord($queue, $payload, $availableAt, $attempts = 0) 58 | { 59 | return [ 60 | 'queue' => $queue, 61 | 'attempts' => $attempts, 62 | 'reserved_at' => null, 63 | 'available_at' => $availableAt, 64 | 'created_at' => $this->currentTime(), 65 | 'payload' => $payload, 66 | 'version' => 0, 67 | ]; 68 | } 69 | 70 | /** 71 | * Picks job from the candidates for the processing. 72 | * 73 | * @param Collection $jobs 74 | * 75 | * @return mixed 76 | */ 77 | protected function pickJob($jobs) 78 | { 79 | $cnt = $jobs->count(); 80 | 81 | if ($cnt == 1 || $this->windowStrategy == 0) { 82 | return $jobs[0]; 83 | } 84 | 85 | // Low 2 bits encode the probabilistic 86 | // method for choosing one job out of N 87 | if (($this->windowStrategy & 3) == 1) { 88 | // Uniform pick 89 | return $jobs[mt_rand(0, $cnt - 1)]; 90 | } elseif (($this->windowStrategy & 3) == 2) { 91 | // Exp. pick 92 | for ($i = 0; $i < $cnt; $i++) { 93 | if (mt_rand(0, 2) == 0) { 94 | return $jobs[$i]; 95 | } 96 | } 97 | //return $jobs[$cnt-1]; 98 | return $jobs[0]; 99 | } 100 | 101 | return $jobs[0]; 102 | } 103 | 104 | /** 105 | * Pop the next job off of the queue. 106 | * 107 | * @param string $queue 108 | * 109 | * @throws \Exception|\Throwable 110 | * 111 | * @return \Illuminate\Contracts\Queue\Job|null 112 | */ 113 | public function pop($queue = null) 114 | { 115 | $queue = $this->getQueue($queue); 116 | 117 | // Pops one job of the queue or return null if there is no job to process. 118 | // 119 | // In order to preserve job ordering we have to pick the first available job. 120 | // Workers compete for the first available job in the queue. 121 | // 122 | // Load the first available job and try to claim it. 123 | // During the competition it may happen another worker claims the job before we do 124 | // which can be easily handled and detected with optimistic locking. 125 | // 126 | // In that case we try to load another job 127 | // because there are apparently some more jobs in the database and pop() is supposed 128 | // to return such job if there is one or return null if there are no jobs so worker 129 | // can sleep(). Thus we have to attempt to claim jobs until there are some. 130 | $job = null; 131 | $ctr = 0; 132 | 133 | $numJobs = $this->windowStrategy > 0 ? $this->numWorkers : 1; 134 | if ($this->windowStrategy > 3) { 135 | $numJobs = ceil($this->numWorkers * 0.5); 136 | } elseif ($this->windowStrategy > 7) { 137 | $numJobs = ceil($this->numWorkers * 2); 138 | } 139 | 140 | do { 141 | // Get set of first N available jobs 142 | $jobs = $this->getNextAvailableJobs($queue, $numJobs); 143 | if ($jobs->isEmpty()) { 144 | return; 145 | } 146 | 147 | // Random pick from the jobs, depending on the strategy 148 | // Reduces job preemption, worker pick randomly. 149 | $job = new DatabaseJobRecord((object) $this->pickJob($jobs)); 150 | 151 | // job is not null, try to claim it 152 | $jobClaimed = $this->marshalJob($queue, $job); 153 | if (!empty($jobClaimed)) { 154 | // job was successfully claimed, return it. 155 | return $jobClaimed; 156 | } else { 157 | $ctr += 1; 158 | } 159 | } while ($job !== null); 160 | } 161 | 162 | /** 163 | * Get the next available job for the queue. 164 | * 165 | * @param string|null $queue 166 | * @param int $limit 167 | * 168 | * @return \Illuminate\Support\Collection 169 | */ 170 | protected function getNextAvailableJobs($queue, $limit = 1) 171 | { 172 | $jobs = $this->database->table($this->table) 173 | ->where('queue', $this->getQueue($queue)) 174 | ->where(function ($query) { 175 | $this->isAvailable($query); 176 | $this->isReservedButExpired($query); 177 | }) 178 | ->orderBy('id', 'asc') 179 | ->limit($limit) 180 | ->get(); 181 | 182 | return $jobs; 183 | } 184 | 185 | /** 186 | * Marshal the reserved job into a DatabaseJob instance. 187 | * 188 | * @param string $queue 189 | * @param \Illuminate\Queue\Jobs\DatabaseJobRecord $job 190 | * 191 | * @return \Illuminate\Queue\Jobs\DatabaseJob 192 | */ 193 | protected function marshalJob($queue, $job) 194 | { 195 | $job = $this->markJobAsReserved($job); 196 | if (empty($job)) { 197 | return; 198 | } 199 | 200 | return new DatabaseJob( 201 | $this->container, 202 | $this, 203 | $job, 204 | $this->connectionName, 205 | $queue 206 | ); 207 | } 208 | 209 | /** 210 | * Marshal the reserved job into a DatabaseJob instance. 211 | * 212 | * @param \Illuminate\Queue\Jobs\DatabaseJobRecord $job 213 | * 214 | * @return DatabaseJobRecord|null 215 | */ 216 | protected function markJobAsReserved($job) 217 | { 218 | $affected = $this->database->table($this->table) 219 | ->where('id', $job->id) 220 | ->where('version', $job->version) 221 | ->update([ 222 | 'reserved_at' => $job->touch(), 223 | 'attempts' => $job->increment(), 224 | 'version' => new Expression('version + 1'), 225 | ]); 226 | 227 | return $affected ? $job : null; 228 | } 229 | 230 | /** 231 | * Delete a reserved job from the queue. 232 | * https://github.com/laravel/framework/issues/7046. 233 | * 234 | * @param string $queue 235 | * @param string $id 236 | * 237 | * @throws \Exception|\Throwable 238 | * 239 | * @return void 240 | */ 241 | public function deleteReserved($queue, $id) 242 | { 243 | $this->database->table($this->table) 244 | ->where('id', $id) 245 | ->delete(); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /tests/Queue/QueueDatabaseQueueUnitTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(OptimisticDatabaseQueue::class) 21 | ->setMethods(['currentTime']) 22 | ->setConstructorArgs([$database = m::mock('Illuminate\Database\Connection'), 'table', 'default']) 23 | ->getMock(); 24 | 25 | $queue->expects($this->any())->method('currentTime')->will($this->returnValue('time')); 26 | $database->shouldReceive('table')->with('table')->andReturn($query = m::mock('stdClass')); 27 | $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) { 28 | $this->assertEquals('default', $array['queue']); 29 | if (array_key_exists('delay', json_decode($array['payload'], true))) { 30 | $this->assertEquals(json_encode([ 31 | 'displayName' => 'foo', 32 | 'job' => 'foo', 33 | 'maxTries' => null, 34 | 'delay' => null, 35 | 'timeout' => null, 36 | 'data' => ['data'], 37 | ]), $array['payload']); 38 | } else { 39 | $this->assertEquals(json_encode([ 40 | 'displayName' => 'foo', 41 | 'job' => 'foo', 42 | 'maxTries' => null, 43 | 'timeout' => null, 44 | 'data' => ['data'], 45 | ]), $array['payload']); 46 | } 47 | $this->assertEquals(0, $array['attempts']); 48 | $this->assertNull($array['reserved_at']); 49 | $this->assertInternalType('int', $array['available_at']); 50 | }); 51 | 52 | $queue->push('foo', ['data']); 53 | } 54 | 55 | public function testDelayedPushProperlyPushesJobOntoDatabase() 56 | { 57 | $queue = $this->getMockBuilder( 58 | OptimisticDatabaseQueue::class 59 | )->setMethods( 60 | ['currentTime'] 61 | )->setConstructorArgs( 62 | [$database = m::mock('Illuminate\Database\Connection'), 'table', 'default'] 63 | )->getMock(); 64 | $queue->expects($this->any())->method('currentTime')->will($this->returnValue('time')); 65 | $database->shouldReceive('table')->with('table')->andReturn($query = m::mock('stdClass')); 66 | $query->shouldReceive('insertGetId')->once()->andReturnUsing(function ($array) { 67 | $this->assertEquals('default', $array['queue']); 68 | if (array_key_exists('delay', json_decode($array['payload'], true))) { 69 | $this->assertEquals(json_encode([ 70 | 'displayName' => 'foo', 71 | 'job' => 'foo', 72 | 'maxTries' => null, 73 | 'delay' => null, 74 | 'timeout' => null, 75 | 'data' => ['data'], 76 | ]), $array['payload']); 77 | } else { 78 | $this->assertEquals(json_encode([ 79 | 'displayName' => 'foo', 80 | 'job' => 'foo', 81 | 'maxTries' => null, 82 | 'timeout' => null, 83 | 'data' => ['data'], 84 | ]), $array['payload']); 85 | } 86 | $this->assertEquals(0, $array['attempts']); 87 | $this->assertNull($array['reserved_at']); 88 | $this->assertInternalType('int', $array['available_at']); 89 | }); 90 | 91 | $queue->later(10, 'foo', ['data']); 92 | } 93 | 94 | public function testFailureToCreatePayloadFromObject() 95 | { 96 | $this->expectException('InvalidArgumentException'); 97 | 98 | $job = new stdClass(); 99 | $job->invalid = "\xc3\x28"; 100 | 101 | $queue = $this->getMockForAbstractClass('Illuminate\Queue\Queue'); 102 | $class = new ReflectionClass('Illuminate\Queue\Queue'); 103 | 104 | $createPayload = $class->getMethod('createPayload'); 105 | $createPayload->setAccessible(true); 106 | $createPayload->invokeArgs($queue, [ 107 | $job, 108 | 'default', 109 | ]); 110 | } 111 | 112 | public function testFailureToCreatePayloadFromArray() 113 | { 114 | $this->expectException('InvalidArgumentException'); 115 | 116 | $queue = $this->getMockForAbstractClass('Illuminate\Queue\Queue'); 117 | $class = new ReflectionClass('Illuminate\Queue\Queue'); 118 | 119 | $createPayload = $class->getMethod('createPayload'); 120 | $createPayload->setAccessible(true); 121 | $createPayload->invokeArgs($queue, [ 122 | ["\xc3\x28"], 123 | 'default', 124 | ]); 125 | } 126 | 127 | public function testBulkBatchPushesOntoDatabase() 128 | { 129 | $database = m::mock('Illuminate\Database\Connection'); 130 | $queue = $this->getMockBuilder(OptimisticDatabaseQueue::class) 131 | ->setMethods(['currentTime', 'availableAt']) 132 | ->setConstructorArgs([$database, 'table', 'default']) 133 | ->getMock(); 134 | 135 | $queue->expects($this->any())->method('currentTime')->will($this->returnValue('created')); 136 | $queue->expects($this->any())->method('availableAt')->will($this->returnValue('available')); 137 | $database->shouldReceive('table')->with('table')->andReturn($query = m::mock('stdClass')); 138 | $query->shouldReceive('insert')->once()->andReturnUsing(function ($records) { 139 | if (array_key_exists('delay', json_decode($records[0]['payload'], true))) { 140 | $this->assertEquals([ 141 | [ 142 | 'queue' => 'queue', 143 | 'payload' => json_encode([ 144 | 'displayName' => 'foo', 145 | 'job' => 'foo', 146 | 'maxTries' => null, 147 | 'delay' => null, 148 | 'timeout' => null, 149 | 'data' => ['data'], 150 | ]), 151 | 'attempts' => 0, 152 | 'reserved_at' => null, 153 | 'available_at' => 'available', 154 | 'created_at' => 'created', 155 | 'version' => 0, 156 | ], 157 | [ 158 | 'queue' => 'queue', 159 | 'payload' => json_encode([ 160 | 'displayName' => 'bar', 161 | 'job' => 'bar', 162 | 'maxTries' => null, 163 | 'delay' => null, 164 | 'timeout' => null, 165 | 'data' => ['data'], 166 | ]), 167 | 'attempts' => 0, 168 | 'reserved_at' => null, 169 | 'available_at' => 'available', 170 | 'created_at' => 'created', 171 | 'version' => 0, 172 | ], 173 | ], $records); 174 | } else { 175 | $this->assertEquals([ 176 | [ 177 | 'queue' => 'queue', 178 | 'payload' => json_encode([ 179 | 'displayName' => 'foo', 180 | 'job' => 'foo', 181 | 'maxTries' => null, 182 | 'timeout' => null, 183 | 'data' => ['data'], 184 | ]), 185 | 'attempts' => 0, 186 | 'reserved_at' => null, 187 | 'available_at' => 'available', 188 | 'created_at' => 'created', 189 | 'version' => 0, 190 | ], 191 | [ 192 | 'queue' => 'queue', 193 | 'payload' => json_encode([ 194 | 'displayName' => 'bar', 195 | 'job' => 'bar', 196 | 'maxTries' => null, 197 | 'timeout' => null, 198 | 'data' => ['data'], 199 | ]), 200 | 'attempts' => 0, 201 | 'reserved_at' => null, 202 | 'available_at' => 'available', 203 | 'created_at' => 'created', 204 | 'version' => 0, 205 | ], 206 | ], $records); 207 | } 208 | }); 209 | 210 | $queue->bulk(['foo', 'bar'], ['data'], 'queue'); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /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": "cf6f43e437d263919d9a41a5b2b0c48e", 8 | "packages": [ 9 | { 10 | "name": "doctrine/inflector", 11 | "version": "1.3.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/doctrine/inflector.git", 15 | "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/doctrine/inflector/zipball/ec3a55242203ffa6a4b27c58176da97ff0a7aec1", 20 | "reference": "ec3a55242203ffa6a4b27c58176da97ff0a7aec1", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": "^7.1" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "^6.2" 28 | }, 29 | "type": "library", 30 | "extra": { 31 | "branch-alias": { 32 | "dev-master": "1.3.x-dev" 33 | } 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" 38 | } 39 | }, 40 | "notification-url": "https://packagist.org/downloads/", 41 | "license": [ 42 | "MIT" 43 | ], 44 | "authors": [ 45 | { 46 | "name": "Guilherme Blanco", 47 | "email": "guilhermeblanco@gmail.com" 48 | }, 49 | { 50 | "name": "Roman Borschel", 51 | "email": "roman@code-factory.org" 52 | }, 53 | { 54 | "name": "Benjamin Eberlei", 55 | "email": "kontakt@beberlei.de" 56 | }, 57 | { 58 | "name": "Jonathan Wage", 59 | "email": "jonwage@gmail.com" 60 | }, 61 | { 62 | "name": "Johannes Schmitt", 63 | "email": "schmittjoh@gmail.com" 64 | } 65 | ], 66 | "description": "Common String Manipulations with regard to casing and singular/plural rules.", 67 | "homepage": "http://www.doctrine-project.org", 68 | "keywords": [ 69 | "inflection", 70 | "pluralize", 71 | "singularize", 72 | "string" 73 | ], 74 | "time": "2019-10-30T19:59:35+00:00" 75 | }, 76 | { 77 | "name": "illuminate/console", 78 | "version": "v6.18.0", 79 | "source": { 80 | "type": "git", 81 | "url": "https://github.com/illuminate/console.git", 82 | "reference": "f78d0bb182611303187346e59bf8ea4ff5de8396" 83 | }, 84 | "dist": { 85 | "type": "zip", 86 | "url": "https://api.github.com/repos/illuminate/console/zipball/f78d0bb182611303187346e59bf8ea4ff5de8396", 87 | "reference": "f78d0bb182611303187346e59bf8ea4ff5de8396", 88 | "shasum": "" 89 | }, 90 | "require": { 91 | "illuminate/contracts": "^6.0", 92 | "illuminate/support": "^6.0", 93 | "php": "^7.2", 94 | "symfony/console": "^4.3.4", 95 | "symfony/process": "^4.3.4" 96 | }, 97 | "suggest": { 98 | "dragonmantank/cron-expression": "Required to use scheduling component (^2.0).", 99 | "guzzlehttp/guzzle": "Required to use the ping methods on schedules (^6.0|^7.0).", 100 | "illuminate/filesystem": "Required to use the generator command (^6.0)" 101 | }, 102 | "type": "library", 103 | "extra": { 104 | "branch-alias": { 105 | "dev-master": "6.x-dev" 106 | } 107 | }, 108 | "autoload": { 109 | "psr-4": { 110 | "Illuminate\\Console\\": "" 111 | } 112 | }, 113 | "notification-url": "https://packagist.org/downloads/", 114 | "license": [ 115 | "MIT" 116 | ], 117 | "authors": [ 118 | { 119 | "name": "Taylor Otwell", 120 | "email": "taylor@laravel.com" 121 | } 122 | ], 123 | "description": "The Illuminate Console package.", 124 | "homepage": "https://laravel.com", 125 | "time": "2020-02-15T15:36:39+00:00" 126 | }, 127 | { 128 | "name": "illuminate/container", 129 | "version": "v6.18.0", 130 | "source": { 131 | "type": "git", 132 | "url": "https://github.com/illuminate/container.git", 133 | "reference": "4078808892a7a40cbbfa5dec57c2efa1f170097d" 134 | }, 135 | "dist": { 136 | "type": "zip", 137 | "url": "https://api.github.com/repos/illuminate/container/zipball/4078808892a7a40cbbfa5dec57c2efa1f170097d", 138 | "reference": "4078808892a7a40cbbfa5dec57c2efa1f170097d", 139 | "shasum": "" 140 | }, 141 | "require": { 142 | "illuminate/contracts": "^6.0", 143 | "php": "^7.2", 144 | "psr/container": "^1.0" 145 | }, 146 | "type": "library", 147 | "extra": { 148 | "branch-alias": { 149 | "dev-master": "6.x-dev" 150 | } 151 | }, 152 | "autoload": { 153 | "psr-4": { 154 | "Illuminate\\Container\\": "" 155 | } 156 | }, 157 | "notification-url": "https://packagist.org/downloads/", 158 | "license": [ 159 | "MIT" 160 | ], 161 | "authors": [ 162 | { 163 | "name": "Taylor Otwell", 164 | "email": "taylor@laravel.com" 165 | } 166 | ], 167 | "description": "The Illuminate Container package.", 168 | "homepage": "https://laravel.com", 169 | "time": "2020-02-27T19:49:00+00:00" 170 | }, 171 | { 172 | "name": "illuminate/contracts", 173 | "version": "v6.18.0", 174 | "source": { 175 | "type": "git", 176 | "url": "https://github.com/illuminate/contracts.git", 177 | "reference": "e08ea83602d96cdfd7106a15f07878935c1a7675" 178 | }, 179 | "dist": { 180 | "type": "zip", 181 | "url": "https://api.github.com/repos/illuminate/contracts/zipball/e08ea83602d96cdfd7106a15f07878935c1a7675", 182 | "reference": "e08ea83602d96cdfd7106a15f07878935c1a7675", 183 | "shasum": "" 184 | }, 185 | "require": { 186 | "php": "^7.2", 187 | "psr/container": "^1.0", 188 | "psr/simple-cache": "^1.0" 189 | }, 190 | "type": "library", 191 | "extra": { 192 | "branch-alias": { 193 | "dev-master": "6.x-dev" 194 | } 195 | }, 196 | "autoload": { 197 | "psr-4": { 198 | "Illuminate\\Contracts\\": "" 199 | } 200 | }, 201 | "notification-url": "https://packagist.org/downloads/", 202 | "license": [ 203 | "MIT" 204 | ], 205 | "authors": [ 206 | { 207 | "name": "Taylor Otwell", 208 | "email": "taylor@laravel.com" 209 | } 210 | ], 211 | "description": "The Illuminate Contracts package.", 212 | "homepage": "https://laravel.com", 213 | "time": "2020-02-27T19:49:00+00:00" 214 | }, 215 | { 216 | "name": "illuminate/database", 217 | "version": "v6.18.0", 218 | "source": { 219 | "type": "git", 220 | "url": "https://github.com/illuminate/database.git", 221 | "reference": "5f59a50b40bce925399a43a1fe11471c76173254" 222 | }, 223 | "dist": { 224 | "type": "zip", 225 | "url": "https://api.github.com/repos/illuminate/database/zipball/5f59a50b40bce925399a43a1fe11471c76173254", 226 | "reference": "5f59a50b40bce925399a43a1fe11471c76173254", 227 | "shasum": "" 228 | }, 229 | "require": { 230 | "ext-json": "*", 231 | "illuminate/container": "^6.0", 232 | "illuminate/contracts": "^6.0", 233 | "illuminate/support": "^6.0", 234 | "php": "^7.2" 235 | }, 236 | "suggest": { 237 | "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", 238 | "fzaninotto/faker": "Required to use the eloquent factory builder (^1.4).", 239 | "illuminate/console": "Required to use the database commands (^6.0).", 240 | "illuminate/events": "Required to use the observers with Eloquent (^6.0).", 241 | "illuminate/filesystem": "Required to use the migrations (^6.0).", 242 | "illuminate/pagination": "Required to paginate the result set (^6.0)." 243 | }, 244 | "type": "library", 245 | "extra": { 246 | "branch-alias": { 247 | "dev-master": "6.x-dev" 248 | } 249 | }, 250 | "autoload": { 251 | "psr-4": { 252 | "Illuminate\\Database\\": "" 253 | } 254 | }, 255 | "notification-url": "https://packagist.org/downloads/", 256 | "license": [ 257 | "MIT" 258 | ], 259 | "authors": [ 260 | { 261 | "name": "Taylor Otwell", 262 | "email": "taylor@laravel.com" 263 | } 264 | ], 265 | "description": "The Illuminate Database package.", 266 | "homepage": "https://laravel.com", 267 | "keywords": [ 268 | "database", 269 | "laravel", 270 | "orm", 271 | "sql" 272 | ], 273 | "time": "2020-03-02T17:06:16+00:00" 274 | }, 275 | { 276 | "name": "illuminate/filesystem", 277 | "version": "v6.18.0", 278 | "source": { 279 | "type": "git", 280 | "url": "https://github.com/illuminate/filesystem.git", 281 | "reference": "799a3af9435458d56e9f3647e09e11ffc9ace896" 282 | }, 283 | "dist": { 284 | "type": "zip", 285 | "url": "https://api.github.com/repos/illuminate/filesystem/zipball/799a3af9435458d56e9f3647e09e11ffc9ace896", 286 | "reference": "799a3af9435458d56e9f3647e09e11ffc9ace896", 287 | "shasum": "" 288 | }, 289 | "require": { 290 | "illuminate/contracts": "^6.0", 291 | "illuminate/support": "^6.0", 292 | "php": "^7.2", 293 | "symfony/finder": "^4.3.4" 294 | }, 295 | "suggest": { 296 | "league/flysystem": "Required to use the Flysystem local and FTP drivers (^1.0).", 297 | "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", 298 | "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", 299 | "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", 300 | "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0)" 301 | }, 302 | "type": "library", 303 | "extra": { 304 | "branch-alias": { 305 | "dev-master": "6.x-dev" 306 | } 307 | }, 308 | "autoload": { 309 | "psr-4": { 310 | "Illuminate\\Filesystem\\": "" 311 | } 312 | }, 313 | "notification-url": "https://packagist.org/downloads/", 314 | "license": [ 315 | "MIT" 316 | ], 317 | "authors": [ 318 | { 319 | "name": "Taylor Otwell", 320 | "email": "taylor@laravel.com" 321 | } 322 | ], 323 | "description": "The Illuminate Filesystem package.", 324 | "homepage": "https://laravel.com", 325 | "time": "2020-02-25T14:02:04+00:00" 326 | }, 327 | { 328 | "name": "illuminate/pipeline", 329 | "version": "v6.18.0", 330 | "source": { 331 | "type": "git", 332 | "url": "https://github.com/illuminate/pipeline.git", 333 | "reference": "16d1cdcd2565b594a9644678acdaef70630e8d23" 334 | }, 335 | "dist": { 336 | "type": "zip", 337 | "url": "https://api.github.com/repos/illuminate/pipeline/zipball/16d1cdcd2565b594a9644678acdaef70630e8d23", 338 | "reference": "16d1cdcd2565b594a9644678acdaef70630e8d23", 339 | "shasum": "" 340 | }, 341 | "require": { 342 | "illuminate/contracts": "^6.0", 343 | "illuminate/support": "^6.0", 344 | "php": "^7.2", 345 | "symfony/debug": "^4.3" 346 | }, 347 | "type": "library", 348 | "extra": { 349 | "branch-alias": { 350 | "dev-master": "6.x-dev" 351 | } 352 | }, 353 | "autoload": { 354 | "psr-4": { 355 | "Illuminate\\Pipeline\\": "" 356 | } 357 | }, 358 | "notification-url": "https://packagist.org/downloads/", 359 | "license": [ 360 | "MIT" 361 | ], 362 | "authors": [ 363 | { 364 | "name": "Taylor Otwell", 365 | "email": "taylor@laravel.com" 366 | } 367 | ], 368 | "description": "The Illuminate Pipeline package.", 369 | "homepage": "https://laravel.com", 370 | "time": "2020-02-25T13:56:58+00:00" 371 | }, 372 | { 373 | "name": "illuminate/queue", 374 | "version": "v6.18.0", 375 | "source": { 376 | "type": "git", 377 | "url": "https://github.com/illuminate/queue.git", 378 | "reference": "e4680abd1ad1e07c4990333fcbf623d9e71f6d6b" 379 | }, 380 | "dist": { 381 | "type": "zip", 382 | "url": "https://api.github.com/repos/illuminate/queue/zipball/e4680abd1ad1e07c4990333fcbf623d9e71f6d6b", 383 | "reference": "e4680abd1ad1e07c4990333fcbf623d9e71f6d6b", 384 | "shasum": "" 385 | }, 386 | "require": { 387 | "ext-json": "*", 388 | "illuminate/console": "^6.0", 389 | "illuminate/container": "^6.0", 390 | "illuminate/contracts": "^6.0", 391 | "illuminate/database": "^6.0", 392 | "illuminate/filesystem": "^6.0", 393 | "illuminate/pipeline": "^6.0", 394 | "illuminate/support": "^6.0", 395 | "opis/closure": "^3.1", 396 | "php": "^7.2", 397 | "symfony/debug": "^4.3.4", 398 | "symfony/process": "^4.3.4" 399 | }, 400 | "suggest": { 401 | "aws/aws-sdk-php": "Required to use the SQS queue driver and DynamoDb failed job storage (^3.0).", 402 | "ext-pcntl": "Required to use all features of the queue worker.", 403 | "ext-posix": "Required to use all features of the queue worker.", 404 | "illuminate/redis": "Required to use the Redis queue driver (^6.0).", 405 | "pda/pheanstalk": "Required to use the Beanstalk queue driver (^4.0)." 406 | }, 407 | "type": "library", 408 | "extra": { 409 | "branch-alias": { 410 | "dev-master": "6.x-dev" 411 | } 412 | }, 413 | "autoload": { 414 | "psr-4": { 415 | "Illuminate\\Queue\\": "" 416 | } 417 | }, 418 | "notification-url": "https://packagist.org/downloads/", 419 | "license": [ 420 | "MIT" 421 | ], 422 | "authors": [ 423 | { 424 | "name": "Taylor Otwell", 425 | "email": "taylor@laravel.com" 426 | } 427 | ], 428 | "description": "The Illuminate Queue package.", 429 | "homepage": "https://laravel.com", 430 | "time": "2020-02-20T10:47:02+00:00" 431 | }, 432 | { 433 | "name": "illuminate/support", 434 | "version": "v6.18.0", 435 | "source": { 436 | "type": "git", 437 | "url": "https://github.com/illuminate/support.git", 438 | "reference": "844b15a40489cf74d660548d60d28e51d5966f22" 439 | }, 440 | "dist": { 441 | "type": "zip", 442 | "url": "https://api.github.com/repos/illuminate/support/zipball/844b15a40489cf74d660548d60d28e51d5966f22", 443 | "reference": "844b15a40489cf74d660548d60d28e51d5966f22", 444 | "shasum": "" 445 | }, 446 | "require": { 447 | "doctrine/inflector": "^1.1", 448 | "ext-json": "*", 449 | "ext-mbstring": "*", 450 | "illuminate/contracts": "^6.0", 451 | "nesbot/carbon": "^2.0", 452 | "php": "^7.2" 453 | }, 454 | "conflict": { 455 | "tightenco/collect": "<5.5.33" 456 | }, 457 | "suggest": { 458 | "illuminate/filesystem": "Required to use the composer class (^6.0).", 459 | "moontoast/math": "Required to use ordered UUIDs (^1.1).", 460 | "ramsey/uuid": "Required to use Str::uuid() (^3.7).", 461 | "symfony/process": "Required to use the composer class (^4.3.4).", 462 | "symfony/var-dumper": "Required to use the dd function (^4.3.4).", 463 | "vlucas/phpdotenv": "Required to use the Env class and env helper (^3.3)." 464 | }, 465 | "type": "library", 466 | "extra": { 467 | "branch-alias": { 468 | "dev-master": "6.x-dev" 469 | } 470 | }, 471 | "autoload": { 472 | "psr-4": { 473 | "Illuminate\\Support\\": "" 474 | }, 475 | "files": [ 476 | "helpers.php" 477 | ] 478 | }, 479 | "notification-url": "https://packagist.org/downloads/", 480 | "license": [ 481 | "MIT" 482 | ], 483 | "authors": [ 484 | { 485 | "name": "Taylor Otwell", 486 | "email": "taylor@laravel.com" 487 | } 488 | ], 489 | "description": "The Illuminate Support package.", 490 | "homepage": "https://laravel.com", 491 | "time": "2020-02-28T14:15:59+00:00" 492 | }, 493 | { 494 | "name": "nesbot/carbon", 495 | "version": "2.31.0", 496 | "source": { 497 | "type": "git", 498 | "url": "https://github.com/briannesbitt/Carbon.git", 499 | "reference": "bbc0ab53f41a4c6f223c18efcdbd9bc725eb5d2d" 500 | }, 501 | "dist": { 502 | "type": "zip", 503 | "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbc0ab53f41a4c6f223c18efcdbd9bc725eb5d2d", 504 | "reference": "bbc0ab53f41a4c6f223c18efcdbd9bc725eb5d2d", 505 | "shasum": "" 506 | }, 507 | "require": { 508 | "ext-json": "*", 509 | "php": "^7.1.8 || ^8.0", 510 | "symfony/translation": "^3.4 || ^4.0 || ^5.0" 511 | }, 512 | "require-dev": { 513 | "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", 514 | "kylekatarnls/multi-tester": "^1.1", 515 | "phpmd/phpmd": "^2.8", 516 | "phpstan/phpstan": "^0.11", 517 | "phpunit/phpunit": "^7.5 || ^8.0", 518 | "squizlabs/php_codesniffer": "^3.4" 519 | }, 520 | "bin": [ 521 | "bin/carbon" 522 | ], 523 | "type": "library", 524 | "extra": { 525 | "branch-alias": { 526 | "dev-master": "2.x-dev" 527 | }, 528 | "laravel": { 529 | "providers": [ 530 | "Carbon\\Laravel\\ServiceProvider" 531 | ] 532 | } 533 | }, 534 | "autoload": { 535 | "psr-4": { 536 | "Carbon\\": "src/Carbon/" 537 | } 538 | }, 539 | "notification-url": "https://packagist.org/downloads/", 540 | "license": [ 541 | "MIT" 542 | ], 543 | "authors": [ 544 | { 545 | "name": "Brian Nesbitt", 546 | "email": "brian@nesbot.com", 547 | "homepage": "http://nesbot.com" 548 | }, 549 | { 550 | "name": "kylekatarnls", 551 | "homepage": "http://github.com/kylekatarnls" 552 | } 553 | ], 554 | "description": "An API extension for DateTime that supports 281 different languages.", 555 | "homepage": "http://carbon.nesbot.com", 556 | "keywords": [ 557 | "date", 558 | "datetime", 559 | "time" 560 | ], 561 | "time": "2020-03-01T11:11:58+00:00" 562 | }, 563 | { 564 | "name": "opis/closure", 565 | "version": "3.5.1", 566 | "source": { 567 | "type": "git", 568 | "url": "https://github.com/opis/closure.git", 569 | "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969" 570 | }, 571 | "dist": { 572 | "type": "zip", 573 | "url": "https://api.github.com/repos/opis/closure/zipball/93ebc5712cdad8d5f489b500c59d122df2e53969", 574 | "reference": "93ebc5712cdad8d5f489b500c59d122df2e53969", 575 | "shasum": "" 576 | }, 577 | "require": { 578 | "php": "^5.4 || ^7.0" 579 | }, 580 | "require-dev": { 581 | "jeremeamia/superclosure": "^2.0", 582 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" 583 | }, 584 | "type": "library", 585 | "extra": { 586 | "branch-alias": { 587 | "dev-master": "3.5.x-dev" 588 | } 589 | }, 590 | "autoload": { 591 | "psr-4": { 592 | "Opis\\Closure\\": "src/" 593 | }, 594 | "files": [ 595 | "functions.php" 596 | ] 597 | }, 598 | "notification-url": "https://packagist.org/downloads/", 599 | "license": [ 600 | "MIT" 601 | ], 602 | "authors": [ 603 | { 604 | "name": "Marius Sarca", 605 | "email": "marius.sarca@gmail.com" 606 | }, 607 | { 608 | "name": "Sorin Sarca", 609 | "email": "sarca_sorin@hotmail.com" 610 | } 611 | ], 612 | "description": "A library that can be used to serialize closures (anonymous functions) and arbitrary objects.", 613 | "homepage": "https://opis.io/closure", 614 | "keywords": [ 615 | "anonymous functions", 616 | "closure", 617 | "function", 618 | "serializable", 619 | "serialization", 620 | "serialize" 621 | ], 622 | "time": "2019-11-29T22:36:02+00:00" 623 | }, 624 | { 625 | "name": "psr/container", 626 | "version": "1.0.0", 627 | "source": { 628 | "type": "git", 629 | "url": "https://github.com/php-fig/container.git", 630 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" 631 | }, 632 | "dist": { 633 | "type": "zip", 634 | "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 635 | "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", 636 | "shasum": "" 637 | }, 638 | "require": { 639 | "php": ">=5.3.0" 640 | }, 641 | "type": "library", 642 | "extra": { 643 | "branch-alias": { 644 | "dev-master": "1.0.x-dev" 645 | } 646 | }, 647 | "autoload": { 648 | "psr-4": { 649 | "Psr\\Container\\": "src/" 650 | } 651 | }, 652 | "notification-url": "https://packagist.org/downloads/", 653 | "license": [ 654 | "MIT" 655 | ], 656 | "authors": [ 657 | { 658 | "name": "PHP-FIG", 659 | "homepage": "http://www.php-fig.org/" 660 | } 661 | ], 662 | "description": "Common Container Interface (PHP FIG PSR-11)", 663 | "homepage": "https://github.com/php-fig/container", 664 | "keywords": [ 665 | "PSR-11", 666 | "container", 667 | "container-interface", 668 | "container-interop", 669 | "psr" 670 | ], 671 | "time": "2017-02-14T16:28:37+00:00" 672 | }, 673 | { 674 | "name": "psr/log", 675 | "version": "1.1.2", 676 | "source": { 677 | "type": "git", 678 | "url": "https://github.com/php-fig/log.git", 679 | "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801" 680 | }, 681 | "dist": { 682 | "type": "zip", 683 | "url": "https://api.github.com/repos/php-fig/log/zipball/446d54b4cb6bf489fc9d75f55843658e6f25d801", 684 | "reference": "446d54b4cb6bf489fc9d75f55843658e6f25d801", 685 | "shasum": "" 686 | }, 687 | "require": { 688 | "php": ">=5.3.0" 689 | }, 690 | "type": "library", 691 | "extra": { 692 | "branch-alias": { 693 | "dev-master": "1.1.x-dev" 694 | } 695 | }, 696 | "autoload": { 697 | "psr-4": { 698 | "Psr\\Log\\": "Psr/Log/" 699 | } 700 | }, 701 | "notification-url": "https://packagist.org/downloads/", 702 | "license": [ 703 | "MIT" 704 | ], 705 | "authors": [ 706 | { 707 | "name": "PHP-FIG", 708 | "homepage": "http://www.php-fig.org/" 709 | } 710 | ], 711 | "description": "Common interface for logging libraries", 712 | "homepage": "https://github.com/php-fig/log", 713 | "keywords": [ 714 | "log", 715 | "psr", 716 | "psr-3" 717 | ], 718 | "time": "2019-11-01T11:05:21+00:00" 719 | }, 720 | { 721 | "name": "psr/simple-cache", 722 | "version": "1.0.1", 723 | "source": { 724 | "type": "git", 725 | "url": "https://github.com/php-fig/simple-cache.git", 726 | "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" 727 | }, 728 | "dist": { 729 | "type": "zip", 730 | "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", 731 | "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", 732 | "shasum": "" 733 | }, 734 | "require": { 735 | "php": ">=5.3.0" 736 | }, 737 | "type": "library", 738 | "extra": { 739 | "branch-alias": { 740 | "dev-master": "1.0.x-dev" 741 | } 742 | }, 743 | "autoload": { 744 | "psr-4": { 745 | "Psr\\SimpleCache\\": "src/" 746 | } 747 | }, 748 | "notification-url": "https://packagist.org/downloads/", 749 | "license": [ 750 | "MIT" 751 | ], 752 | "authors": [ 753 | { 754 | "name": "PHP-FIG", 755 | "homepage": "http://www.php-fig.org/" 756 | } 757 | ], 758 | "description": "Common interfaces for simple caching", 759 | "keywords": [ 760 | "cache", 761 | "caching", 762 | "psr", 763 | "psr-16", 764 | "simple-cache" 765 | ], 766 | "time": "2017-10-23T01:57:42+00:00" 767 | }, 768 | { 769 | "name": "symfony/console", 770 | "version": "v4.4.5", 771 | "source": { 772 | "type": "git", 773 | "url": "https://github.com/symfony/console.git", 774 | "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9" 775 | }, 776 | "dist": { 777 | "type": "zip", 778 | "url": "https://api.github.com/repos/symfony/console/zipball/4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", 779 | "reference": "4fa15ae7be74e53f6ec8c83ed403b97e23b665e9", 780 | "shasum": "" 781 | }, 782 | "require": { 783 | "php": "^7.1.3", 784 | "symfony/polyfill-mbstring": "~1.0", 785 | "symfony/polyfill-php73": "^1.8", 786 | "symfony/service-contracts": "^1.1|^2" 787 | }, 788 | "conflict": { 789 | "symfony/dependency-injection": "<3.4", 790 | "symfony/event-dispatcher": "<4.3|>=5", 791 | "symfony/lock": "<4.4", 792 | "symfony/process": "<3.3" 793 | }, 794 | "provide": { 795 | "psr/log-implementation": "1.0" 796 | }, 797 | "require-dev": { 798 | "psr/log": "~1.0", 799 | "symfony/config": "^3.4|^4.0|^5.0", 800 | "symfony/dependency-injection": "^3.4|^4.0|^5.0", 801 | "symfony/event-dispatcher": "^4.3", 802 | "symfony/lock": "^4.4|^5.0", 803 | "symfony/process": "^3.4|^4.0|^5.0", 804 | "symfony/var-dumper": "^4.3|^5.0" 805 | }, 806 | "suggest": { 807 | "psr/log": "For using the console logger", 808 | "symfony/event-dispatcher": "", 809 | "symfony/lock": "", 810 | "symfony/process": "" 811 | }, 812 | "type": "library", 813 | "extra": { 814 | "branch-alias": { 815 | "dev-master": "4.4-dev" 816 | } 817 | }, 818 | "autoload": { 819 | "psr-4": { 820 | "Symfony\\Component\\Console\\": "" 821 | }, 822 | "exclude-from-classmap": [ 823 | "/Tests/" 824 | ] 825 | }, 826 | "notification-url": "https://packagist.org/downloads/", 827 | "license": [ 828 | "MIT" 829 | ], 830 | "authors": [ 831 | { 832 | "name": "Fabien Potencier", 833 | "email": "fabien@symfony.com" 834 | }, 835 | { 836 | "name": "Symfony Community", 837 | "homepage": "https://symfony.com/contributors" 838 | } 839 | ], 840 | "description": "Symfony Console Component", 841 | "homepage": "https://symfony.com", 842 | "time": "2020-02-24T13:10:00+00:00" 843 | }, 844 | { 845 | "name": "symfony/debug", 846 | "version": "v4.4.5", 847 | "source": { 848 | "type": "git", 849 | "url": "https://github.com/symfony/debug.git", 850 | "reference": "a980d87a659648980d89193fd8b7a7ca89d97d21" 851 | }, 852 | "dist": { 853 | "type": "zip", 854 | "url": "https://api.github.com/repos/symfony/debug/zipball/a980d87a659648980d89193fd8b7a7ca89d97d21", 855 | "reference": "a980d87a659648980d89193fd8b7a7ca89d97d21", 856 | "shasum": "" 857 | }, 858 | "require": { 859 | "php": "^7.1.3", 860 | "psr/log": "~1.0" 861 | }, 862 | "conflict": { 863 | "symfony/http-kernel": "<3.4" 864 | }, 865 | "require-dev": { 866 | "symfony/http-kernel": "^3.4|^4.0|^5.0" 867 | }, 868 | "type": "library", 869 | "extra": { 870 | "branch-alias": { 871 | "dev-master": "4.4-dev" 872 | } 873 | }, 874 | "autoload": { 875 | "psr-4": { 876 | "Symfony\\Component\\Debug\\": "" 877 | }, 878 | "exclude-from-classmap": [ 879 | "/Tests/" 880 | ] 881 | }, 882 | "notification-url": "https://packagist.org/downloads/", 883 | "license": [ 884 | "MIT" 885 | ], 886 | "authors": [ 887 | { 888 | "name": "Fabien Potencier", 889 | "email": "fabien@symfony.com" 890 | }, 891 | { 892 | "name": "Symfony Community", 893 | "homepage": "https://symfony.com/contributors" 894 | } 895 | ], 896 | "description": "Symfony Debug Component", 897 | "homepage": "https://symfony.com", 898 | "time": "2020-02-23T14:41:43+00:00" 899 | }, 900 | { 901 | "name": "symfony/finder", 902 | "version": "v4.4.5", 903 | "source": { 904 | "type": "git", 905 | "url": "https://github.com/symfony/finder.git", 906 | "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357" 907 | }, 908 | "dist": { 909 | "type": "zip", 910 | "url": "https://api.github.com/repos/symfony/finder/zipball/ea69c129aed9fdeca781d4b77eb20b62cf5d5357", 911 | "reference": "ea69c129aed9fdeca781d4b77eb20b62cf5d5357", 912 | "shasum": "" 913 | }, 914 | "require": { 915 | "php": "^7.1.3" 916 | }, 917 | "type": "library", 918 | "extra": { 919 | "branch-alias": { 920 | "dev-master": "4.4-dev" 921 | } 922 | }, 923 | "autoload": { 924 | "psr-4": { 925 | "Symfony\\Component\\Finder\\": "" 926 | }, 927 | "exclude-from-classmap": [ 928 | "/Tests/" 929 | ] 930 | }, 931 | "notification-url": "https://packagist.org/downloads/", 932 | "license": [ 933 | "MIT" 934 | ], 935 | "authors": [ 936 | { 937 | "name": "Fabien Potencier", 938 | "email": "fabien@symfony.com" 939 | }, 940 | { 941 | "name": "Symfony Community", 942 | "homepage": "https://symfony.com/contributors" 943 | } 944 | ], 945 | "description": "Symfony Finder Component", 946 | "homepage": "https://symfony.com", 947 | "time": "2020-02-14T07:42:58+00:00" 948 | }, 949 | { 950 | "name": "symfony/polyfill-mbstring", 951 | "version": "v1.14.0", 952 | "source": { 953 | "type": "git", 954 | "url": "https://github.com/symfony/polyfill-mbstring.git", 955 | "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2" 956 | }, 957 | "dist": { 958 | "type": "zip", 959 | "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/34094cfa9abe1f0f14f48f490772db7a775559f2", 960 | "reference": "34094cfa9abe1f0f14f48f490772db7a775559f2", 961 | "shasum": "" 962 | }, 963 | "require": { 964 | "php": ">=5.3.3" 965 | }, 966 | "suggest": { 967 | "ext-mbstring": "For best performance" 968 | }, 969 | "type": "library", 970 | "extra": { 971 | "branch-alias": { 972 | "dev-master": "1.14-dev" 973 | } 974 | }, 975 | "autoload": { 976 | "psr-4": { 977 | "Symfony\\Polyfill\\Mbstring\\": "" 978 | }, 979 | "files": [ 980 | "bootstrap.php" 981 | ] 982 | }, 983 | "notification-url": "https://packagist.org/downloads/", 984 | "license": [ 985 | "MIT" 986 | ], 987 | "authors": [ 988 | { 989 | "name": "Nicolas Grekas", 990 | "email": "p@tchwork.com" 991 | }, 992 | { 993 | "name": "Symfony Community", 994 | "homepage": "https://symfony.com/contributors" 995 | } 996 | ], 997 | "description": "Symfony polyfill for the Mbstring extension", 998 | "homepage": "https://symfony.com", 999 | "keywords": [ 1000 | "compatibility", 1001 | "mbstring", 1002 | "polyfill", 1003 | "portable", 1004 | "shim" 1005 | ], 1006 | "time": "2020-01-13T11:15:53+00:00" 1007 | }, 1008 | { 1009 | "name": "symfony/polyfill-php73", 1010 | "version": "v1.14.0", 1011 | "source": { 1012 | "type": "git", 1013 | "url": "https://github.com/symfony/polyfill-php73.git", 1014 | "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675" 1015 | }, 1016 | "dist": { 1017 | "type": "zip", 1018 | "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/5e66a0fa1070bf46bec4bea7962d285108edd675", 1019 | "reference": "5e66a0fa1070bf46bec4bea7962d285108edd675", 1020 | "shasum": "" 1021 | }, 1022 | "require": { 1023 | "php": ">=5.3.3" 1024 | }, 1025 | "type": "library", 1026 | "extra": { 1027 | "branch-alias": { 1028 | "dev-master": "1.14-dev" 1029 | } 1030 | }, 1031 | "autoload": { 1032 | "psr-4": { 1033 | "Symfony\\Polyfill\\Php73\\": "" 1034 | }, 1035 | "files": [ 1036 | "bootstrap.php" 1037 | ], 1038 | "classmap": [ 1039 | "Resources/stubs" 1040 | ] 1041 | }, 1042 | "notification-url": "https://packagist.org/downloads/", 1043 | "license": [ 1044 | "MIT" 1045 | ], 1046 | "authors": [ 1047 | { 1048 | "name": "Nicolas Grekas", 1049 | "email": "p@tchwork.com" 1050 | }, 1051 | { 1052 | "name": "Symfony Community", 1053 | "homepage": "https://symfony.com/contributors" 1054 | } 1055 | ], 1056 | "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", 1057 | "homepage": "https://symfony.com", 1058 | "keywords": [ 1059 | "compatibility", 1060 | "polyfill", 1061 | "portable", 1062 | "shim" 1063 | ], 1064 | "time": "2020-01-13T11:15:53+00:00" 1065 | }, 1066 | { 1067 | "name": "symfony/process", 1068 | "version": "v4.4.5", 1069 | "source": { 1070 | "type": "git", 1071 | "url": "https://github.com/symfony/process.git", 1072 | "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7" 1073 | }, 1074 | "dist": { 1075 | "type": "zip", 1076 | "url": "https://api.github.com/repos/symfony/process/zipball/bf9166bac906c9e69fb7a11d94875e7ced97bcd7", 1077 | "reference": "bf9166bac906c9e69fb7a11d94875e7ced97bcd7", 1078 | "shasum": "" 1079 | }, 1080 | "require": { 1081 | "php": "^7.1.3" 1082 | }, 1083 | "type": "library", 1084 | "extra": { 1085 | "branch-alias": { 1086 | "dev-master": "4.4-dev" 1087 | } 1088 | }, 1089 | "autoload": { 1090 | "psr-4": { 1091 | "Symfony\\Component\\Process\\": "" 1092 | }, 1093 | "exclude-from-classmap": [ 1094 | "/Tests/" 1095 | ] 1096 | }, 1097 | "notification-url": "https://packagist.org/downloads/", 1098 | "license": [ 1099 | "MIT" 1100 | ], 1101 | "authors": [ 1102 | { 1103 | "name": "Fabien Potencier", 1104 | "email": "fabien@symfony.com" 1105 | }, 1106 | { 1107 | "name": "Symfony Community", 1108 | "homepage": "https://symfony.com/contributors" 1109 | } 1110 | ], 1111 | "description": "Symfony Process Component", 1112 | "homepage": "https://symfony.com", 1113 | "time": "2020-02-07T20:06:44+00:00" 1114 | }, 1115 | { 1116 | "name": "symfony/service-contracts", 1117 | "version": "v2.0.1", 1118 | "source": { 1119 | "type": "git", 1120 | "url": "https://github.com/symfony/service-contracts.git", 1121 | "reference": "144c5e51266b281231e947b51223ba14acf1a749" 1122 | }, 1123 | "dist": { 1124 | "type": "zip", 1125 | "url": "https://api.github.com/repos/symfony/service-contracts/zipball/144c5e51266b281231e947b51223ba14acf1a749", 1126 | "reference": "144c5e51266b281231e947b51223ba14acf1a749", 1127 | "shasum": "" 1128 | }, 1129 | "require": { 1130 | "php": "^7.2.5", 1131 | "psr/container": "^1.0" 1132 | }, 1133 | "suggest": { 1134 | "symfony/service-implementation": "" 1135 | }, 1136 | "type": "library", 1137 | "extra": { 1138 | "branch-alias": { 1139 | "dev-master": "2.0-dev" 1140 | } 1141 | }, 1142 | "autoload": { 1143 | "psr-4": { 1144 | "Symfony\\Contracts\\Service\\": "" 1145 | } 1146 | }, 1147 | "notification-url": "https://packagist.org/downloads/", 1148 | "license": [ 1149 | "MIT" 1150 | ], 1151 | "authors": [ 1152 | { 1153 | "name": "Nicolas Grekas", 1154 | "email": "p@tchwork.com" 1155 | }, 1156 | { 1157 | "name": "Symfony Community", 1158 | "homepage": "https://symfony.com/contributors" 1159 | } 1160 | ], 1161 | "description": "Generic abstractions related to writing services", 1162 | "homepage": "https://symfony.com", 1163 | "keywords": [ 1164 | "abstractions", 1165 | "contracts", 1166 | "decoupling", 1167 | "interfaces", 1168 | "interoperability", 1169 | "standards" 1170 | ], 1171 | "time": "2019-11-18T17:27:11+00:00" 1172 | }, 1173 | { 1174 | "name": "symfony/translation", 1175 | "version": "v5.0.5", 1176 | "source": { 1177 | "type": "git", 1178 | "url": "https://github.com/symfony/translation.git", 1179 | "reference": "e9b93f42a1fd6aec6a0872d59ee5c8219a7d584b" 1180 | }, 1181 | "dist": { 1182 | "type": "zip", 1183 | "url": "https://api.github.com/repos/symfony/translation/zipball/e9b93f42a1fd6aec6a0872d59ee5c8219a7d584b", 1184 | "reference": "e9b93f42a1fd6aec6a0872d59ee5c8219a7d584b", 1185 | "shasum": "" 1186 | }, 1187 | "require": { 1188 | "php": "^7.2.5", 1189 | "symfony/polyfill-mbstring": "~1.0", 1190 | "symfony/translation-contracts": "^2" 1191 | }, 1192 | "conflict": { 1193 | "symfony/config": "<4.4", 1194 | "symfony/dependency-injection": "<5.0", 1195 | "symfony/http-kernel": "<5.0", 1196 | "symfony/twig-bundle": "<5.0", 1197 | "symfony/yaml": "<4.4" 1198 | }, 1199 | "provide": { 1200 | "symfony/translation-implementation": "2.0" 1201 | }, 1202 | "require-dev": { 1203 | "psr/log": "~1.0", 1204 | "symfony/config": "^4.4|^5.0", 1205 | "symfony/console": "^4.4|^5.0", 1206 | "symfony/dependency-injection": "^5.0", 1207 | "symfony/finder": "^4.4|^5.0", 1208 | "symfony/http-kernel": "^5.0", 1209 | "symfony/intl": "^4.4|^5.0", 1210 | "symfony/service-contracts": "^1.1.2|^2", 1211 | "symfony/yaml": "^4.4|^5.0" 1212 | }, 1213 | "suggest": { 1214 | "psr/log-implementation": "To use logging capability in translator", 1215 | "symfony/config": "", 1216 | "symfony/yaml": "" 1217 | }, 1218 | "type": "library", 1219 | "extra": { 1220 | "branch-alias": { 1221 | "dev-master": "5.0-dev" 1222 | } 1223 | }, 1224 | "autoload": { 1225 | "psr-4": { 1226 | "Symfony\\Component\\Translation\\": "" 1227 | }, 1228 | "exclude-from-classmap": [ 1229 | "/Tests/" 1230 | ] 1231 | }, 1232 | "notification-url": "https://packagist.org/downloads/", 1233 | "license": [ 1234 | "MIT" 1235 | ], 1236 | "authors": [ 1237 | { 1238 | "name": "Fabien Potencier", 1239 | "email": "fabien@symfony.com" 1240 | }, 1241 | { 1242 | "name": "Symfony Community", 1243 | "homepage": "https://symfony.com/contributors" 1244 | } 1245 | ], 1246 | "description": "Symfony Translation Component", 1247 | "homepage": "https://symfony.com", 1248 | "time": "2020-02-04T07:41:34+00:00" 1249 | }, 1250 | { 1251 | "name": "symfony/translation-contracts", 1252 | "version": "v2.0.1", 1253 | "source": { 1254 | "type": "git", 1255 | "url": "https://github.com/symfony/translation-contracts.git", 1256 | "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed" 1257 | }, 1258 | "dist": { 1259 | "type": "zip", 1260 | "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/8cc682ac458d75557203b2f2f14b0b92e1c744ed", 1261 | "reference": "8cc682ac458d75557203b2f2f14b0b92e1c744ed", 1262 | "shasum": "" 1263 | }, 1264 | "require": { 1265 | "php": "^7.2.5" 1266 | }, 1267 | "suggest": { 1268 | "symfony/translation-implementation": "" 1269 | }, 1270 | "type": "library", 1271 | "extra": { 1272 | "branch-alias": { 1273 | "dev-master": "2.0-dev" 1274 | } 1275 | }, 1276 | "autoload": { 1277 | "psr-4": { 1278 | "Symfony\\Contracts\\Translation\\": "" 1279 | } 1280 | }, 1281 | "notification-url": "https://packagist.org/downloads/", 1282 | "license": [ 1283 | "MIT" 1284 | ], 1285 | "authors": [ 1286 | { 1287 | "name": "Nicolas Grekas", 1288 | "email": "p@tchwork.com" 1289 | }, 1290 | { 1291 | "name": "Symfony Community", 1292 | "homepage": "https://symfony.com/contributors" 1293 | } 1294 | ], 1295 | "description": "Generic abstractions related to translation", 1296 | "homepage": "https://symfony.com", 1297 | "keywords": [ 1298 | "abstractions", 1299 | "contracts", 1300 | "decoupling", 1301 | "interfaces", 1302 | "interoperability", 1303 | "standards" 1304 | ], 1305 | "time": "2019-11-18T17:27:11+00:00" 1306 | } 1307 | ], 1308 | "packages-dev": [ 1309 | { 1310 | "name": "doctrine/instantiator", 1311 | "version": "1.3.0", 1312 | "source": { 1313 | "type": "git", 1314 | "url": "https://github.com/doctrine/instantiator.git", 1315 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1" 1316 | }, 1317 | "dist": { 1318 | "type": "zip", 1319 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/ae466f726242e637cebdd526a7d991b9433bacf1", 1320 | "reference": "ae466f726242e637cebdd526a7d991b9433bacf1", 1321 | "shasum": "" 1322 | }, 1323 | "require": { 1324 | "php": "^7.1" 1325 | }, 1326 | "require-dev": { 1327 | "doctrine/coding-standard": "^6.0", 1328 | "ext-pdo": "*", 1329 | "ext-phar": "*", 1330 | "phpbench/phpbench": "^0.13", 1331 | "phpstan/phpstan-phpunit": "^0.11", 1332 | "phpstan/phpstan-shim": "^0.11", 1333 | "phpunit/phpunit": "^7.0" 1334 | }, 1335 | "type": "library", 1336 | "extra": { 1337 | "branch-alias": { 1338 | "dev-master": "1.2.x-dev" 1339 | } 1340 | }, 1341 | "autoload": { 1342 | "psr-4": { 1343 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 1344 | } 1345 | }, 1346 | "notification-url": "https://packagist.org/downloads/", 1347 | "license": [ 1348 | "MIT" 1349 | ], 1350 | "authors": [ 1351 | { 1352 | "name": "Marco Pivetta", 1353 | "email": "ocramius@gmail.com", 1354 | "homepage": "http://ocramius.github.com/" 1355 | } 1356 | ], 1357 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 1358 | "homepage": "https://www.doctrine-project.org/projects/instantiator.html", 1359 | "keywords": [ 1360 | "constructor", 1361 | "instantiate" 1362 | ], 1363 | "time": "2019-10-21T16:45:58+00:00" 1364 | }, 1365 | { 1366 | "name": "hamcrest/hamcrest-php", 1367 | "version": "v2.0.0", 1368 | "source": { 1369 | "type": "git", 1370 | "url": "https://github.com/hamcrest/hamcrest-php.git", 1371 | "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad" 1372 | }, 1373 | "dist": { 1374 | "type": "zip", 1375 | "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad", 1376 | "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad", 1377 | "shasum": "" 1378 | }, 1379 | "require": { 1380 | "php": "^5.3|^7.0" 1381 | }, 1382 | "replace": { 1383 | "cordoval/hamcrest-php": "*", 1384 | "davedevelopment/hamcrest-php": "*", 1385 | "kodova/hamcrest-php": "*" 1386 | }, 1387 | "require-dev": { 1388 | "phpunit/php-file-iterator": "1.3.3", 1389 | "phpunit/phpunit": "~4.0", 1390 | "satooshi/php-coveralls": "^1.0" 1391 | }, 1392 | "type": "library", 1393 | "extra": { 1394 | "branch-alias": { 1395 | "dev-master": "2.0-dev" 1396 | } 1397 | }, 1398 | "autoload": { 1399 | "classmap": [ 1400 | "hamcrest" 1401 | ] 1402 | }, 1403 | "notification-url": "https://packagist.org/downloads/", 1404 | "license": [ 1405 | "BSD" 1406 | ], 1407 | "description": "This is the PHP port of Hamcrest Matchers", 1408 | "keywords": [ 1409 | "test" 1410 | ], 1411 | "time": "2016-01-20T08:20:44+00:00" 1412 | }, 1413 | { 1414 | "name": "mockery/mockery", 1415 | "version": "1.3.1", 1416 | "source": { 1417 | "type": "git", 1418 | "url": "https://github.com/mockery/mockery.git", 1419 | "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be" 1420 | }, 1421 | "dist": { 1422 | "type": "zip", 1423 | "url": "https://api.github.com/repos/mockery/mockery/zipball/f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", 1424 | "reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be", 1425 | "shasum": "" 1426 | }, 1427 | "require": { 1428 | "hamcrest/hamcrest-php": "~2.0", 1429 | "lib-pcre": ">=7.0", 1430 | "php": ">=5.6.0" 1431 | }, 1432 | "require-dev": { 1433 | "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" 1434 | }, 1435 | "type": "library", 1436 | "extra": { 1437 | "branch-alias": { 1438 | "dev-master": "1.3.x-dev" 1439 | } 1440 | }, 1441 | "autoload": { 1442 | "psr-0": { 1443 | "Mockery": "library/" 1444 | } 1445 | }, 1446 | "notification-url": "https://packagist.org/downloads/", 1447 | "license": [ 1448 | "BSD-3-Clause" 1449 | ], 1450 | "authors": [ 1451 | { 1452 | "name": "Pádraic Brady", 1453 | "email": "padraic.brady@gmail.com", 1454 | "homepage": "http://blog.astrumfutura.com" 1455 | }, 1456 | { 1457 | "name": "Dave Marshall", 1458 | "email": "dave.marshall@atstsolutions.co.uk", 1459 | "homepage": "http://davedevelopment.co.uk" 1460 | } 1461 | ], 1462 | "description": "Mockery is a simple yet flexible PHP mock object framework", 1463 | "homepage": "https://github.com/mockery/mockery", 1464 | "keywords": [ 1465 | "BDD", 1466 | "TDD", 1467 | "library", 1468 | "mock", 1469 | "mock objects", 1470 | "mockery", 1471 | "stub", 1472 | "test", 1473 | "test double", 1474 | "testing" 1475 | ], 1476 | "time": "2019-12-26T09:49:15+00:00" 1477 | }, 1478 | { 1479 | "name": "myclabs/deep-copy", 1480 | "version": "1.9.5", 1481 | "source": { 1482 | "type": "git", 1483 | "url": "https://github.com/myclabs/DeepCopy.git", 1484 | "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef" 1485 | }, 1486 | "dist": { 1487 | "type": "zip", 1488 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef", 1489 | "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef", 1490 | "shasum": "" 1491 | }, 1492 | "require": { 1493 | "php": "^7.1" 1494 | }, 1495 | "replace": { 1496 | "myclabs/deep-copy": "self.version" 1497 | }, 1498 | "require-dev": { 1499 | "doctrine/collections": "^1.0", 1500 | "doctrine/common": "^2.6", 1501 | "phpunit/phpunit": "^7.1" 1502 | }, 1503 | "type": "library", 1504 | "autoload": { 1505 | "psr-4": { 1506 | "DeepCopy\\": "src/DeepCopy/" 1507 | }, 1508 | "files": [ 1509 | "src/DeepCopy/deep_copy.php" 1510 | ] 1511 | }, 1512 | "notification-url": "https://packagist.org/downloads/", 1513 | "license": [ 1514 | "MIT" 1515 | ], 1516 | "description": "Create deep copies (clones) of your objects", 1517 | "keywords": [ 1518 | "clone", 1519 | "copy", 1520 | "duplicate", 1521 | "object", 1522 | "object graph" 1523 | ], 1524 | "time": "2020-01-17T21:11:47+00:00" 1525 | }, 1526 | { 1527 | "name": "phar-io/manifest", 1528 | "version": "1.0.3", 1529 | "source": { 1530 | "type": "git", 1531 | "url": "https://github.com/phar-io/manifest.git", 1532 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4" 1533 | }, 1534 | "dist": { 1535 | "type": "zip", 1536 | "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 1537 | "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4", 1538 | "shasum": "" 1539 | }, 1540 | "require": { 1541 | "ext-dom": "*", 1542 | "ext-phar": "*", 1543 | "phar-io/version": "^2.0", 1544 | "php": "^5.6 || ^7.0" 1545 | }, 1546 | "type": "library", 1547 | "extra": { 1548 | "branch-alias": { 1549 | "dev-master": "1.0.x-dev" 1550 | } 1551 | }, 1552 | "autoload": { 1553 | "classmap": [ 1554 | "src/" 1555 | ] 1556 | }, 1557 | "notification-url": "https://packagist.org/downloads/", 1558 | "license": [ 1559 | "BSD-3-Clause" 1560 | ], 1561 | "authors": [ 1562 | { 1563 | "name": "Arne Blankerts", 1564 | "email": "arne@blankerts.de", 1565 | "role": "Developer" 1566 | }, 1567 | { 1568 | "name": "Sebastian Heuer", 1569 | "email": "sebastian@phpeople.de", 1570 | "role": "Developer" 1571 | }, 1572 | { 1573 | "name": "Sebastian Bergmann", 1574 | "email": "sebastian@phpunit.de", 1575 | "role": "Developer" 1576 | } 1577 | ], 1578 | "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", 1579 | "time": "2018-07-08T19:23:20+00:00" 1580 | }, 1581 | { 1582 | "name": "phar-io/version", 1583 | "version": "2.0.1", 1584 | "source": { 1585 | "type": "git", 1586 | "url": "https://github.com/phar-io/version.git", 1587 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6" 1588 | }, 1589 | "dist": { 1590 | "type": "zip", 1591 | "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6", 1592 | "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6", 1593 | "shasum": "" 1594 | }, 1595 | "require": { 1596 | "php": "^5.6 || ^7.0" 1597 | }, 1598 | "type": "library", 1599 | "autoload": { 1600 | "classmap": [ 1601 | "src/" 1602 | ] 1603 | }, 1604 | "notification-url": "https://packagist.org/downloads/", 1605 | "license": [ 1606 | "BSD-3-Clause" 1607 | ], 1608 | "authors": [ 1609 | { 1610 | "name": "Arne Blankerts", 1611 | "email": "arne@blankerts.de", 1612 | "role": "Developer" 1613 | }, 1614 | { 1615 | "name": "Sebastian Heuer", 1616 | "email": "sebastian@phpeople.de", 1617 | "role": "Developer" 1618 | }, 1619 | { 1620 | "name": "Sebastian Bergmann", 1621 | "email": "sebastian@phpunit.de", 1622 | "role": "Developer" 1623 | } 1624 | ], 1625 | "description": "Library for handling version information and constraints", 1626 | "time": "2018-07-08T19:19:57+00:00" 1627 | }, 1628 | { 1629 | "name": "phpdocumentor/reflection-common", 1630 | "version": "2.0.0", 1631 | "source": { 1632 | "type": "git", 1633 | "url": "https://github.com/phpDocumentor/ReflectionCommon.git", 1634 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a" 1635 | }, 1636 | "dist": { 1637 | "type": "zip", 1638 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/63a995caa1ca9e5590304cd845c15ad6d482a62a", 1639 | "reference": "63a995caa1ca9e5590304cd845c15ad6d482a62a", 1640 | "shasum": "" 1641 | }, 1642 | "require": { 1643 | "php": ">=7.1" 1644 | }, 1645 | "require-dev": { 1646 | "phpunit/phpunit": "~6" 1647 | }, 1648 | "type": "library", 1649 | "extra": { 1650 | "branch-alias": { 1651 | "dev-master": "2.x-dev" 1652 | } 1653 | }, 1654 | "autoload": { 1655 | "psr-4": { 1656 | "phpDocumentor\\Reflection\\": "src/" 1657 | } 1658 | }, 1659 | "notification-url": "https://packagist.org/downloads/", 1660 | "license": [ 1661 | "MIT" 1662 | ], 1663 | "authors": [ 1664 | { 1665 | "name": "Jaap van Otterdijk", 1666 | "email": "opensource@ijaap.nl" 1667 | } 1668 | ], 1669 | "description": "Common reflection classes used by phpdocumentor to reflect the code structure", 1670 | "homepage": "http://www.phpdoc.org", 1671 | "keywords": [ 1672 | "FQSEN", 1673 | "phpDocumentor", 1674 | "phpdoc", 1675 | "reflection", 1676 | "static analysis" 1677 | ], 1678 | "time": "2018-08-07T13:53:10+00:00" 1679 | }, 1680 | { 1681 | "name": "phpdocumentor/reflection-docblock", 1682 | "version": "5.1.0", 1683 | "source": { 1684 | "type": "git", 1685 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 1686 | "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e" 1687 | }, 1688 | "dist": { 1689 | "type": "zip", 1690 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", 1691 | "reference": "cd72d394ca794d3466a3b2fc09d5a6c1dc86b47e", 1692 | "shasum": "" 1693 | }, 1694 | "require": { 1695 | "ext-filter": "^7.1", 1696 | "php": "^7.2", 1697 | "phpdocumentor/reflection-common": "^2.0", 1698 | "phpdocumentor/type-resolver": "^1.0", 1699 | "webmozart/assert": "^1" 1700 | }, 1701 | "require-dev": { 1702 | "doctrine/instantiator": "^1", 1703 | "mockery/mockery": "^1" 1704 | }, 1705 | "type": "library", 1706 | "extra": { 1707 | "branch-alias": { 1708 | "dev-master": "5.x-dev" 1709 | } 1710 | }, 1711 | "autoload": { 1712 | "psr-4": { 1713 | "phpDocumentor\\Reflection\\": "src" 1714 | } 1715 | }, 1716 | "notification-url": "https://packagist.org/downloads/", 1717 | "license": [ 1718 | "MIT" 1719 | ], 1720 | "authors": [ 1721 | { 1722 | "name": "Mike van Riel", 1723 | "email": "me@mikevanriel.com" 1724 | }, 1725 | { 1726 | "name": "Jaap van Otterdijk", 1727 | "email": "account@ijaap.nl" 1728 | } 1729 | ], 1730 | "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", 1731 | "time": "2020-02-22T12:28:44+00:00" 1732 | }, 1733 | { 1734 | "name": "phpdocumentor/type-resolver", 1735 | "version": "1.1.0", 1736 | "source": { 1737 | "type": "git", 1738 | "url": "https://github.com/phpDocumentor/TypeResolver.git", 1739 | "reference": "7462d5f123dfc080dfdf26897032a6513644fc95" 1740 | }, 1741 | "dist": { 1742 | "type": "zip", 1743 | "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/7462d5f123dfc080dfdf26897032a6513644fc95", 1744 | "reference": "7462d5f123dfc080dfdf26897032a6513644fc95", 1745 | "shasum": "" 1746 | }, 1747 | "require": { 1748 | "php": "^7.2", 1749 | "phpdocumentor/reflection-common": "^2.0" 1750 | }, 1751 | "require-dev": { 1752 | "ext-tokenizer": "^7.2", 1753 | "mockery/mockery": "~1" 1754 | }, 1755 | "type": "library", 1756 | "extra": { 1757 | "branch-alias": { 1758 | "dev-master": "1.x-dev" 1759 | } 1760 | }, 1761 | "autoload": { 1762 | "psr-4": { 1763 | "phpDocumentor\\Reflection\\": "src" 1764 | } 1765 | }, 1766 | "notification-url": "https://packagist.org/downloads/", 1767 | "license": [ 1768 | "MIT" 1769 | ], 1770 | "authors": [ 1771 | { 1772 | "name": "Mike van Riel", 1773 | "email": "me@mikevanriel.com" 1774 | } 1775 | ], 1776 | "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", 1777 | "time": "2020-02-18T18:59:58+00:00" 1778 | }, 1779 | { 1780 | "name": "phpspec/prophecy", 1781 | "version": "v1.10.3", 1782 | "source": { 1783 | "type": "git", 1784 | "url": "https://github.com/phpspec/prophecy.git", 1785 | "reference": "451c3cd1418cf640de218914901e51b064abb093" 1786 | }, 1787 | "dist": { 1788 | "type": "zip", 1789 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", 1790 | "reference": "451c3cd1418cf640de218914901e51b064abb093", 1791 | "shasum": "" 1792 | }, 1793 | "require": { 1794 | "doctrine/instantiator": "^1.0.2", 1795 | "php": "^5.3|^7.0", 1796 | "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", 1797 | "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", 1798 | "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" 1799 | }, 1800 | "require-dev": { 1801 | "phpspec/phpspec": "^2.5 || ^3.2", 1802 | "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" 1803 | }, 1804 | "type": "library", 1805 | "extra": { 1806 | "branch-alias": { 1807 | "dev-master": "1.10.x-dev" 1808 | } 1809 | }, 1810 | "autoload": { 1811 | "psr-4": { 1812 | "Prophecy\\": "src/Prophecy" 1813 | } 1814 | }, 1815 | "notification-url": "https://packagist.org/downloads/", 1816 | "license": [ 1817 | "MIT" 1818 | ], 1819 | "authors": [ 1820 | { 1821 | "name": "Konstantin Kudryashov", 1822 | "email": "ever.zet@gmail.com", 1823 | "homepage": "http://everzet.com" 1824 | }, 1825 | { 1826 | "name": "Marcello Duarte", 1827 | "email": "marcello.duarte@gmail.com" 1828 | } 1829 | ], 1830 | "description": "Highly opinionated mocking framework for PHP 5.3+", 1831 | "homepage": "https://github.com/phpspec/prophecy", 1832 | "keywords": [ 1833 | "Double", 1834 | "Dummy", 1835 | "fake", 1836 | "mock", 1837 | "spy", 1838 | "stub" 1839 | ], 1840 | "time": "2020-03-05T15:02:03+00:00" 1841 | }, 1842 | { 1843 | "name": "phpunit/php-code-coverage", 1844 | "version": "6.1.4", 1845 | "source": { 1846 | "type": "git", 1847 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 1848 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d" 1849 | }, 1850 | "dist": { 1851 | "type": "zip", 1852 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 1853 | "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d", 1854 | "shasum": "" 1855 | }, 1856 | "require": { 1857 | "ext-dom": "*", 1858 | "ext-xmlwriter": "*", 1859 | "php": "^7.1", 1860 | "phpunit/php-file-iterator": "^2.0", 1861 | "phpunit/php-text-template": "^1.2.1", 1862 | "phpunit/php-token-stream": "^3.0", 1863 | "sebastian/code-unit-reverse-lookup": "^1.0.1", 1864 | "sebastian/environment": "^3.1 || ^4.0", 1865 | "sebastian/version": "^2.0.1", 1866 | "theseer/tokenizer": "^1.1" 1867 | }, 1868 | "require-dev": { 1869 | "phpunit/phpunit": "^7.0" 1870 | }, 1871 | "suggest": { 1872 | "ext-xdebug": "^2.6.0" 1873 | }, 1874 | "type": "library", 1875 | "extra": { 1876 | "branch-alias": { 1877 | "dev-master": "6.1-dev" 1878 | } 1879 | }, 1880 | "autoload": { 1881 | "classmap": [ 1882 | "src/" 1883 | ] 1884 | }, 1885 | "notification-url": "https://packagist.org/downloads/", 1886 | "license": [ 1887 | "BSD-3-Clause" 1888 | ], 1889 | "authors": [ 1890 | { 1891 | "name": "Sebastian Bergmann", 1892 | "email": "sebastian@phpunit.de", 1893 | "role": "lead" 1894 | } 1895 | ], 1896 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 1897 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 1898 | "keywords": [ 1899 | "coverage", 1900 | "testing", 1901 | "xunit" 1902 | ], 1903 | "time": "2018-10-31T16:06:48+00:00" 1904 | }, 1905 | { 1906 | "name": "phpunit/php-file-iterator", 1907 | "version": "2.0.2", 1908 | "source": { 1909 | "type": "git", 1910 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 1911 | "reference": "050bedf145a257b1ff02746c31894800e5122946" 1912 | }, 1913 | "dist": { 1914 | "type": "zip", 1915 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946", 1916 | "reference": "050bedf145a257b1ff02746c31894800e5122946", 1917 | "shasum": "" 1918 | }, 1919 | "require": { 1920 | "php": "^7.1" 1921 | }, 1922 | "require-dev": { 1923 | "phpunit/phpunit": "^7.1" 1924 | }, 1925 | "type": "library", 1926 | "extra": { 1927 | "branch-alias": { 1928 | "dev-master": "2.0.x-dev" 1929 | } 1930 | }, 1931 | "autoload": { 1932 | "classmap": [ 1933 | "src/" 1934 | ] 1935 | }, 1936 | "notification-url": "https://packagist.org/downloads/", 1937 | "license": [ 1938 | "BSD-3-Clause" 1939 | ], 1940 | "authors": [ 1941 | { 1942 | "name": "Sebastian Bergmann", 1943 | "email": "sebastian@phpunit.de", 1944 | "role": "lead" 1945 | } 1946 | ], 1947 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 1948 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 1949 | "keywords": [ 1950 | "filesystem", 1951 | "iterator" 1952 | ], 1953 | "time": "2018-09-13T20:33:42+00:00" 1954 | }, 1955 | { 1956 | "name": "phpunit/php-text-template", 1957 | "version": "1.2.1", 1958 | "source": { 1959 | "type": "git", 1960 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 1961 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 1962 | }, 1963 | "dist": { 1964 | "type": "zip", 1965 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1966 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 1967 | "shasum": "" 1968 | }, 1969 | "require": { 1970 | "php": ">=5.3.3" 1971 | }, 1972 | "type": "library", 1973 | "autoload": { 1974 | "classmap": [ 1975 | "src/" 1976 | ] 1977 | }, 1978 | "notification-url": "https://packagist.org/downloads/", 1979 | "license": [ 1980 | "BSD-3-Clause" 1981 | ], 1982 | "authors": [ 1983 | { 1984 | "name": "Sebastian Bergmann", 1985 | "email": "sebastian@phpunit.de", 1986 | "role": "lead" 1987 | } 1988 | ], 1989 | "description": "Simple template engine.", 1990 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 1991 | "keywords": [ 1992 | "template" 1993 | ], 1994 | "time": "2015-06-21T13:50:34+00:00" 1995 | }, 1996 | { 1997 | "name": "phpunit/php-timer", 1998 | "version": "2.1.2", 1999 | "source": { 2000 | "type": "git", 2001 | "url": "https://github.com/sebastianbergmann/php-timer.git", 2002 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" 2003 | }, 2004 | "dist": { 2005 | "type": "zip", 2006 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", 2007 | "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", 2008 | "shasum": "" 2009 | }, 2010 | "require": { 2011 | "php": "^7.1" 2012 | }, 2013 | "require-dev": { 2014 | "phpunit/phpunit": "^7.0" 2015 | }, 2016 | "type": "library", 2017 | "extra": { 2018 | "branch-alias": { 2019 | "dev-master": "2.1-dev" 2020 | } 2021 | }, 2022 | "autoload": { 2023 | "classmap": [ 2024 | "src/" 2025 | ] 2026 | }, 2027 | "notification-url": "https://packagist.org/downloads/", 2028 | "license": [ 2029 | "BSD-3-Clause" 2030 | ], 2031 | "authors": [ 2032 | { 2033 | "name": "Sebastian Bergmann", 2034 | "email": "sebastian@phpunit.de", 2035 | "role": "lead" 2036 | } 2037 | ], 2038 | "description": "Utility class for timing", 2039 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 2040 | "keywords": [ 2041 | "timer" 2042 | ], 2043 | "time": "2019-06-07T04:22:29+00:00" 2044 | }, 2045 | { 2046 | "name": "phpunit/php-token-stream", 2047 | "version": "3.1.1", 2048 | "source": { 2049 | "type": "git", 2050 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 2051 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff" 2052 | }, 2053 | "dist": { 2054 | "type": "zip", 2055 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/995192df77f63a59e47f025390d2d1fdf8f425ff", 2056 | "reference": "995192df77f63a59e47f025390d2d1fdf8f425ff", 2057 | "shasum": "" 2058 | }, 2059 | "require": { 2060 | "ext-tokenizer": "*", 2061 | "php": "^7.1" 2062 | }, 2063 | "require-dev": { 2064 | "phpunit/phpunit": "^7.0" 2065 | }, 2066 | "type": "library", 2067 | "extra": { 2068 | "branch-alias": { 2069 | "dev-master": "3.1-dev" 2070 | } 2071 | }, 2072 | "autoload": { 2073 | "classmap": [ 2074 | "src/" 2075 | ] 2076 | }, 2077 | "notification-url": "https://packagist.org/downloads/", 2078 | "license": [ 2079 | "BSD-3-Clause" 2080 | ], 2081 | "authors": [ 2082 | { 2083 | "name": "Sebastian Bergmann", 2084 | "email": "sebastian@phpunit.de" 2085 | } 2086 | ], 2087 | "description": "Wrapper around PHP's tokenizer extension.", 2088 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 2089 | "keywords": [ 2090 | "tokenizer" 2091 | ], 2092 | "time": "2019-09-17T06:23:10+00:00" 2093 | }, 2094 | { 2095 | "name": "phpunit/phpunit", 2096 | "version": "7.5.20", 2097 | "source": { 2098 | "type": "git", 2099 | "url": "https://github.com/sebastianbergmann/phpunit.git", 2100 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c" 2101 | }, 2102 | "dist": { 2103 | "type": "zip", 2104 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9467db479d1b0487c99733bb1e7944d32deded2c", 2105 | "reference": "9467db479d1b0487c99733bb1e7944d32deded2c", 2106 | "shasum": "" 2107 | }, 2108 | "require": { 2109 | "doctrine/instantiator": "^1.1", 2110 | "ext-dom": "*", 2111 | "ext-json": "*", 2112 | "ext-libxml": "*", 2113 | "ext-mbstring": "*", 2114 | "ext-xml": "*", 2115 | "myclabs/deep-copy": "^1.7", 2116 | "phar-io/manifest": "^1.0.2", 2117 | "phar-io/version": "^2.0", 2118 | "php": "^7.1", 2119 | "phpspec/prophecy": "^1.7", 2120 | "phpunit/php-code-coverage": "^6.0.7", 2121 | "phpunit/php-file-iterator": "^2.0.1", 2122 | "phpunit/php-text-template": "^1.2.1", 2123 | "phpunit/php-timer": "^2.1", 2124 | "sebastian/comparator": "^3.0", 2125 | "sebastian/diff": "^3.0", 2126 | "sebastian/environment": "^4.0", 2127 | "sebastian/exporter": "^3.1", 2128 | "sebastian/global-state": "^2.0", 2129 | "sebastian/object-enumerator": "^3.0.3", 2130 | "sebastian/resource-operations": "^2.0", 2131 | "sebastian/version": "^2.0.1" 2132 | }, 2133 | "conflict": { 2134 | "phpunit/phpunit-mock-objects": "*" 2135 | }, 2136 | "require-dev": { 2137 | "ext-pdo": "*" 2138 | }, 2139 | "suggest": { 2140 | "ext-soap": "*", 2141 | "ext-xdebug": "*", 2142 | "phpunit/php-invoker": "^2.0" 2143 | }, 2144 | "bin": [ 2145 | "phpunit" 2146 | ], 2147 | "type": "library", 2148 | "extra": { 2149 | "branch-alias": { 2150 | "dev-master": "7.5-dev" 2151 | } 2152 | }, 2153 | "autoload": { 2154 | "classmap": [ 2155 | "src/" 2156 | ] 2157 | }, 2158 | "notification-url": "https://packagist.org/downloads/", 2159 | "license": [ 2160 | "BSD-3-Clause" 2161 | ], 2162 | "authors": [ 2163 | { 2164 | "name": "Sebastian Bergmann", 2165 | "email": "sebastian@phpunit.de", 2166 | "role": "lead" 2167 | } 2168 | ], 2169 | "description": "The PHP Unit Testing framework.", 2170 | "homepage": "https://phpunit.de/", 2171 | "keywords": [ 2172 | "phpunit", 2173 | "testing", 2174 | "xunit" 2175 | ], 2176 | "time": "2020-01-08T08:45:45+00:00" 2177 | }, 2178 | { 2179 | "name": "sebastian/code-unit-reverse-lookup", 2180 | "version": "1.0.1", 2181 | "source": { 2182 | "type": "git", 2183 | "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", 2184 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18" 2185 | }, 2186 | "dist": { 2187 | "type": "zip", 2188 | "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 2189 | "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18", 2190 | "shasum": "" 2191 | }, 2192 | "require": { 2193 | "php": "^5.6 || ^7.0" 2194 | }, 2195 | "require-dev": { 2196 | "phpunit/phpunit": "^5.7 || ^6.0" 2197 | }, 2198 | "type": "library", 2199 | "extra": { 2200 | "branch-alias": { 2201 | "dev-master": "1.0.x-dev" 2202 | } 2203 | }, 2204 | "autoload": { 2205 | "classmap": [ 2206 | "src/" 2207 | ] 2208 | }, 2209 | "notification-url": "https://packagist.org/downloads/", 2210 | "license": [ 2211 | "BSD-3-Clause" 2212 | ], 2213 | "authors": [ 2214 | { 2215 | "name": "Sebastian Bergmann", 2216 | "email": "sebastian@phpunit.de" 2217 | } 2218 | ], 2219 | "description": "Looks up which function or method a line of code belongs to", 2220 | "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", 2221 | "time": "2017-03-04T06:30:41+00:00" 2222 | }, 2223 | { 2224 | "name": "sebastian/comparator", 2225 | "version": "3.0.2", 2226 | "source": { 2227 | "type": "git", 2228 | "url": "https://github.com/sebastianbergmann/comparator.git", 2229 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da" 2230 | }, 2231 | "dist": { 2232 | "type": "zip", 2233 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 2234 | "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da", 2235 | "shasum": "" 2236 | }, 2237 | "require": { 2238 | "php": "^7.1", 2239 | "sebastian/diff": "^3.0", 2240 | "sebastian/exporter": "^3.1" 2241 | }, 2242 | "require-dev": { 2243 | "phpunit/phpunit": "^7.1" 2244 | }, 2245 | "type": "library", 2246 | "extra": { 2247 | "branch-alias": { 2248 | "dev-master": "3.0-dev" 2249 | } 2250 | }, 2251 | "autoload": { 2252 | "classmap": [ 2253 | "src/" 2254 | ] 2255 | }, 2256 | "notification-url": "https://packagist.org/downloads/", 2257 | "license": [ 2258 | "BSD-3-Clause" 2259 | ], 2260 | "authors": [ 2261 | { 2262 | "name": "Jeff Welch", 2263 | "email": "whatthejeff@gmail.com" 2264 | }, 2265 | { 2266 | "name": "Volker Dusch", 2267 | "email": "github@wallbash.com" 2268 | }, 2269 | { 2270 | "name": "Bernhard Schussek", 2271 | "email": "bschussek@2bepublished.at" 2272 | }, 2273 | { 2274 | "name": "Sebastian Bergmann", 2275 | "email": "sebastian@phpunit.de" 2276 | } 2277 | ], 2278 | "description": "Provides the functionality to compare PHP values for equality", 2279 | "homepage": "https://github.com/sebastianbergmann/comparator", 2280 | "keywords": [ 2281 | "comparator", 2282 | "compare", 2283 | "equality" 2284 | ], 2285 | "time": "2018-07-12T15:12:46+00:00" 2286 | }, 2287 | { 2288 | "name": "sebastian/diff", 2289 | "version": "3.0.2", 2290 | "source": { 2291 | "type": "git", 2292 | "url": "https://github.com/sebastianbergmann/diff.git", 2293 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29" 2294 | }, 2295 | "dist": { 2296 | "type": "zip", 2297 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 2298 | "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29", 2299 | "shasum": "" 2300 | }, 2301 | "require": { 2302 | "php": "^7.1" 2303 | }, 2304 | "require-dev": { 2305 | "phpunit/phpunit": "^7.5 || ^8.0", 2306 | "symfony/process": "^2 || ^3.3 || ^4" 2307 | }, 2308 | "type": "library", 2309 | "extra": { 2310 | "branch-alias": { 2311 | "dev-master": "3.0-dev" 2312 | } 2313 | }, 2314 | "autoload": { 2315 | "classmap": [ 2316 | "src/" 2317 | ] 2318 | }, 2319 | "notification-url": "https://packagist.org/downloads/", 2320 | "license": [ 2321 | "BSD-3-Clause" 2322 | ], 2323 | "authors": [ 2324 | { 2325 | "name": "Kore Nordmann", 2326 | "email": "mail@kore-nordmann.de" 2327 | }, 2328 | { 2329 | "name": "Sebastian Bergmann", 2330 | "email": "sebastian@phpunit.de" 2331 | } 2332 | ], 2333 | "description": "Diff implementation", 2334 | "homepage": "https://github.com/sebastianbergmann/diff", 2335 | "keywords": [ 2336 | "diff", 2337 | "udiff", 2338 | "unidiff", 2339 | "unified diff" 2340 | ], 2341 | "time": "2019-02-04T06:01:07+00:00" 2342 | }, 2343 | { 2344 | "name": "sebastian/environment", 2345 | "version": "4.2.3", 2346 | "source": { 2347 | "type": "git", 2348 | "url": "https://github.com/sebastianbergmann/environment.git", 2349 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368" 2350 | }, 2351 | "dist": { 2352 | "type": "zip", 2353 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/464c90d7bdf5ad4e8a6aea15c091fec0603d4368", 2354 | "reference": "464c90d7bdf5ad4e8a6aea15c091fec0603d4368", 2355 | "shasum": "" 2356 | }, 2357 | "require": { 2358 | "php": "^7.1" 2359 | }, 2360 | "require-dev": { 2361 | "phpunit/phpunit": "^7.5" 2362 | }, 2363 | "suggest": { 2364 | "ext-posix": "*" 2365 | }, 2366 | "type": "library", 2367 | "extra": { 2368 | "branch-alias": { 2369 | "dev-master": "4.2-dev" 2370 | } 2371 | }, 2372 | "autoload": { 2373 | "classmap": [ 2374 | "src/" 2375 | ] 2376 | }, 2377 | "notification-url": "https://packagist.org/downloads/", 2378 | "license": [ 2379 | "BSD-3-Clause" 2380 | ], 2381 | "authors": [ 2382 | { 2383 | "name": "Sebastian Bergmann", 2384 | "email": "sebastian@phpunit.de" 2385 | } 2386 | ], 2387 | "description": "Provides functionality to handle HHVM/PHP environments", 2388 | "homepage": "http://www.github.com/sebastianbergmann/environment", 2389 | "keywords": [ 2390 | "Xdebug", 2391 | "environment", 2392 | "hhvm" 2393 | ], 2394 | "time": "2019-11-20T08:46:58+00:00" 2395 | }, 2396 | { 2397 | "name": "sebastian/exporter", 2398 | "version": "3.1.2", 2399 | "source": { 2400 | "type": "git", 2401 | "url": "https://github.com/sebastianbergmann/exporter.git", 2402 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e" 2403 | }, 2404 | "dist": { 2405 | "type": "zip", 2406 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/68609e1261d215ea5b21b7987539cbfbe156ec3e", 2407 | "reference": "68609e1261d215ea5b21b7987539cbfbe156ec3e", 2408 | "shasum": "" 2409 | }, 2410 | "require": { 2411 | "php": "^7.0", 2412 | "sebastian/recursion-context": "^3.0" 2413 | }, 2414 | "require-dev": { 2415 | "ext-mbstring": "*", 2416 | "phpunit/phpunit": "^6.0" 2417 | }, 2418 | "type": "library", 2419 | "extra": { 2420 | "branch-alias": { 2421 | "dev-master": "3.1.x-dev" 2422 | } 2423 | }, 2424 | "autoload": { 2425 | "classmap": [ 2426 | "src/" 2427 | ] 2428 | }, 2429 | "notification-url": "https://packagist.org/downloads/", 2430 | "license": [ 2431 | "BSD-3-Clause" 2432 | ], 2433 | "authors": [ 2434 | { 2435 | "name": "Sebastian Bergmann", 2436 | "email": "sebastian@phpunit.de" 2437 | }, 2438 | { 2439 | "name": "Jeff Welch", 2440 | "email": "whatthejeff@gmail.com" 2441 | }, 2442 | { 2443 | "name": "Volker Dusch", 2444 | "email": "github@wallbash.com" 2445 | }, 2446 | { 2447 | "name": "Adam Harvey", 2448 | "email": "aharvey@php.net" 2449 | }, 2450 | { 2451 | "name": "Bernhard Schussek", 2452 | "email": "bschussek@gmail.com" 2453 | } 2454 | ], 2455 | "description": "Provides the functionality to export PHP variables for visualization", 2456 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 2457 | "keywords": [ 2458 | "export", 2459 | "exporter" 2460 | ], 2461 | "time": "2019-09-14T09:02:43+00:00" 2462 | }, 2463 | { 2464 | "name": "sebastian/global-state", 2465 | "version": "2.0.0", 2466 | "source": { 2467 | "type": "git", 2468 | "url": "https://github.com/sebastianbergmann/global-state.git", 2469 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4" 2470 | }, 2471 | "dist": { 2472 | "type": "zip", 2473 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 2474 | "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4", 2475 | "shasum": "" 2476 | }, 2477 | "require": { 2478 | "php": "^7.0" 2479 | }, 2480 | "require-dev": { 2481 | "phpunit/phpunit": "^6.0" 2482 | }, 2483 | "suggest": { 2484 | "ext-uopz": "*" 2485 | }, 2486 | "type": "library", 2487 | "extra": { 2488 | "branch-alias": { 2489 | "dev-master": "2.0-dev" 2490 | } 2491 | }, 2492 | "autoload": { 2493 | "classmap": [ 2494 | "src/" 2495 | ] 2496 | }, 2497 | "notification-url": "https://packagist.org/downloads/", 2498 | "license": [ 2499 | "BSD-3-Clause" 2500 | ], 2501 | "authors": [ 2502 | { 2503 | "name": "Sebastian Bergmann", 2504 | "email": "sebastian@phpunit.de" 2505 | } 2506 | ], 2507 | "description": "Snapshotting of global state", 2508 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 2509 | "keywords": [ 2510 | "global state" 2511 | ], 2512 | "time": "2017-04-27T15:39:26+00:00" 2513 | }, 2514 | { 2515 | "name": "sebastian/object-enumerator", 2516 | "version": "3.0.3", 2517 | "source": { 2518 | "type": "git", 2519 | "url": "https://github.com/sebastianbergmann/object-enumerator.git", 2520 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5" 2521 | }, 2522 | "dist": { 2523 | "type": "zip", 2524 | "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5", 2525 | "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5", 2526 | "shasum": "" 2527 | }, 2528 | "require": { 2529 | "php": "^7.0", 2530 | "sebastian/object-reflector": "^1.1.1", 2531 | "sebastian/recursion-context": "^3.0" 2532 | }, 2533 | "require-dev": { 2534 | "phpunit/phpunit": "^6.0" 2535 | }, 2536 | "type": "library", 2537 | "extra": { 2538 | "branch-alias": { 2539 | "dev-master": "3.0.x-dev" 2540 | } 2541 | }, 2542 | "autoload": { 2543 | "classmap": [ 2544 | "src/" 2545 | ] 2546 | }, 2547 | "notification-url": "https://packagist.org/downloads/", 2548 | "license": [ 2549 | "BSD-3-Clause" 2550 | ], 2551 | "authors": [ 2552 | { 2553 | "name": "Sebastian Bergmann", 2554 | "email": "sebastian@phpunit.de" 2555 | } 2556 | ], 2557 | "description": "Traverses array structures and object graphs to enumerate all referenced objects", 2558 | "homepage": "https://github.com/sebastianbergmann/object-enumerator/", 2559 | "time": "2017-08-03T12:35:26+00:00" 2560 | }, 2561 | { 2562 | "name": "sebastian/object-reflector", 2563 | "version": "1.1.1", 2564 | "source": { 2565 | "type": "git", 2566 | "url": "https://github.com/sebastianbergmann/object-reflector.git", 2567 | "reference": "773f97c67f28de00d397be301821b06708fca0be" 2568 | }, 2569 | "dist": { 2570 | "type": "zip", 2571 | "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be", 2572 | "reference": "773f97c67f28de00d397be301821b06708fca0be", 2573 | "shasum": "" 2574 | }, 2575 | "require": { 2576 | "php": "^7.0" 2577 | }, 2578 | "require-dev": { 2579 | "phpunit/phpunit": "^6.0" 2580 | }, 2581 | "type": "library", 2582 | "extra": { 2583 | "branch-alias": { 2584 | "dev-master": "1.1-dev" 2585 | } 2586 | }, 2587 | "autoload": { 2588 | "classmap": [ 2589 | "src/" 2590 | ] 2591 | }, 2592 | "notification-url": "https://packagist.org/downloads/", 2593 | "license": [ 2594 | "BSD-3-Clause" 2595 | ], 2596 | "authors": [ 2597 | { 2598 | "name": "Sebastian Bergmann", 2599 | "email": "sebastian@phpunit.de" 2600 | } 2601 | ], 2602 | "description": "Allows reflection of object attributes, including inherited and non-public ones", 2603 | "homepage": "https://github.com/sebastianbergmann/object-reflector/", 2604 | "time": "2017-03-29T09:07:27+00:00" 2605 | }, 2606 | { 2607 | "name": "sebastian/recursion-context", 2608 | "version": "3.0.0", 2609 | "source": { 2610 | "type": "git", 2611 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 2612 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8" 2613 | }, 2614 | "dist": { 2615 | "type": "zip", 2616 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 2617 | "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8", 2618 | "shasum": "" 2619 | }, 2620 | "require": { 2621 | "php": "^7.0" 2622 | }, 2623 | "require-dev": { 2624 | "phpunit/phpunit": "^6.0" 2625 | }, 2626 | "type": "library", 2627 | "extra": { 2628 | "branch-alias": { 2629 | "dev-master": "3.0.x-dev" 2630 | } 2631 | }, 2632 | "autoload": { 2633 | "classmap": [ 2634 | "src/" 2635 | ] 2636 | }, 2637 | "notification-url": "https://packagist.org/downloads/", 2638 | "license": [ 2639 | "BSD-3-Clause" 2640 | ], 2641 | "authors": [ 2642 | { 2643 | "name": "Jeff Welch", 2644 | "email": "whatthejeff@gmail.com" 2645 | }, 2646 | { 2647 | "name": "Sebastian Bergmann", 2648 | "email": "sebastian@phpunit.de" 2649 | }, 2650 | { 2651 | "name": "Adam Harvey", 2652 | "email": "aharvey@php.net" 2653 | } 2654 | ], 2655 | "description": "Provides functionality to recursively process PHP variables", 2656 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 2657 | "time": "2017-03-03T06:23:57+00:00" 2658 | }, 2659 | { 2660 | "name": "sebastian/resource-operations", 2661 | "version": "2.0.1", 2662 | "source": { 2663 | "type": "git", 2664 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 2665 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9" 2666 | }, 2667 | "dist": { 2668 | "type": "zip", 2669 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 2670 | "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9", 2671 | "shasum": "" 2672 | }, 2673 | "require": { 2674 | "php": "^7.1" 2675 | }, 2676 | "type": "library", 2677 | "extra": { 2678 | "branch-alias": { 2679 | "dev-master": "2.0-dev" 2680 | } 2681 | }, 2682 | "autoload": { 2683 | "classmap": [ 2684 | "src/" 2685 | ] 2686 | }, 2687 | "notification-url": "https://packagist.org/downloads/", 2688 | "license": [ 2689 | "BSD-3-Clause" 2690 | ], 2691 | "authors": [ 2692 | { 2693 | "name": "Sebastian Bergmann", 2694 | "email": "sebastian@phpunit.de" 2695 | } 2696 | ], 2697 | "description": "Provides a list of PHP built-in functions that operate on resources", 2698 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 2699 | "time": "2018-10-04T04:07:39+00:00" 2700 | }, 2701 | { 2702 | "name": "sebastian/version", 2703 | "version": "2.0.1", 2704 | "source": { 2705 | "type": "git", 2706 | "url": "https://github.com/sebastianbergmann/version.git", 2707 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019" 2708 | }, 2709 | "dist": { 2710 | "type": "zip", 2711 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019", 2712 | "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019", 2713 | "shasum": "" 2714 | }, 2715 | "require": { 2716 | "php": ">=5.6" 2717 | }, 2718 | "type": "library", 2719 | "extra": { 2720 | "branch-alias": { 2721 | "dev-master": "2.0.x-dev" 2722 | } 2723 | }, 2724 | "autoload": { 2725 | "classmap": [ 2726 | "src/" 2727 | ] 2728 | }, 2729 | "notification-url": "https://packagist.org/downloads/", 2730 | "license": [ 2731 | "BSD-3-Clause" 2732 | ], 2733 | "authors": [ 2734 | { 2735 | "name": "Sebastian Bergmann", 2736 | "email": "sebastian@phpunit.de", 2737 | "role": "lead" 2738 | } 2739 | ], 2740 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 2741 | "homepage": "https://github.com/sebastianbergmann/version", 2742 | "time": "2016-10-03T07:35:21+00:00" 2743 | }, 2744 | { 2745 | "name": "symfony/polyfill-ctype", 2746 | "version": "v1.14.0", 2747 | "source": { 2748 | "type": "git", 2749 | "url": "https://github.com/symfony/polyfill-ctype.git", 2750 | "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38" 2751 | }, 2752 | "dist": { 2753 | "type": "zip", 2754 | "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", 2755 | "reference": "fbdeaec0df06cf3d51c93de80c7eb76e271f5a38", 2756 | "shasum": "" 2757 | }, 2758 | "require": { 2759 | "php": ">=5.3.3" 2760 | }, 2761 | "suggest": { 2762 | "ext-ctype": "For best performance" 2763 | }, 2764 | "type": "library", 2765 | "extra": { 2766 | "branch-alias": { 2767 | "dev-master": "1.14-dev" 2768 | } 2769 | }, 2770 | "autoload": { 2771 | "psr-4": { 2772 | "Symfony\\Polyfill\\Ctype\\": "" 2773 | }, 2774 | "files": [ 2775 | "bootstrap.php" 2776 | ] 2777 | }, 2778 | "notification-url": "https://packagist.org/downloads/", 2779 | "license": [ 2780 | "MIT" 2781 | ], 2782 | "authors": [ 2783 | { 2784 | "name": "Gert de Pagter", 2785 | "email": "BackEndTea@gmail.com" 2786 | }, 2787 | { 2788 | "name": "Symfony Community", 2789 | "homepage": "https://symfony.com/contributors" 2790 | } 2791 | ], 2792 | "description": "Symfony polyfill for ctype functions", 2793 | "homepage": "https://symfony.com", 2794 | "keywords": [ 2795 | "compatibility", 2796 | "ctype", 2797 | "polyfill", 2798 | "portable" 2799 | ], 2800 | "time": "2020-01-13T11:15:53+00:00" 2801 | }, 2802 | { 2803 | "name": "theseer/tokenizer", 2804 | "version": "1.1.3", 2805 | "source": { 2806 | "type": "git", 2807 | "url": "https://github.com/theseer/tokenizer.git", 2808 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" 2809 | }, 2810 | "dist": { 2811 | "type": "zip", 2812 | "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 2813 | "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", 2814 | "shasum": "" 2815 | }, 2816 | "require": { 2817 | "ext-dom": "*", 2818 | "ext-tokenizer": "*", 2819 | "ext-xmlwriter": "*", 2820 | "php": "^7.0" 2821 | }, 2822 | "type": "library", 2823 | "autoload": { 2824 | "classmap": [ 2825 | "src/" 2826 | ] 2827 | }, 2828 | "notification-url": "https://packagist.org/downloads/", 2829 | "license": [ 2830 | "BSD-3-Clause" 2831 | ], 2832 | "authors": [ 2833 | { 2834 | "name": "Arne Blankerts", 2835 | "email": "arne@blankerts.de", 2836 | "role": "Developer" 2837 | } 2838 | ], 2839 | "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", 2840 | "time": "2019-06-13T22:48:21+00:00" 2841 | }, 2842 | { 2843 | "name": "webmozart/assert", 2844 | "version": "1.7.0", 2845 | "source": { 2846 | "type": "git", 2847 | "url": "https://github.com/webmozart/assert.git", 2848 | "reference": "aed98a490f9a8f78468232db345ab9cf606cf598" 2849 | }, 2850 | "dist": { 2851 | "type": "zip", 2852 | "url": "https://api.github.com/repos/webmozart/assert/zipball/aed98a490f9a8f78468232db345ab9cf606cf598", 2853 | "reference": "aed98a490f9a8f78468232db345ab9cf606cf598", 2854 | "shasum": "" 2855 | }, 2856 | "require": { 2857 | "php": "^5.3.3 || ^7.0", 2858 | "symfony/polyfill-ctype": "^1.8" 2859 | }, 2860 | "conflict": { 2861 | "vimeo/psalm": "<3.6.0" 2862 | }, 2863 | "require-dev": { 2864 | "phpunit/phpunit": "^4.8.36 || ^7.5.13" 2865 | }, 2866 | "type": "library", 2867 | "autoload": { 2868 | "psr-4": { 2869 | "Webmozart\\Assert\\": "src/" 2870 | } 2871 | }, 2872 | "notification-url": "https://packagist.org/downloads/", 2873 | "license": [ 2874 | "MIT" 2875 | ], 2876 | "authors": [ 2877 | { 2878 | "name": "Bernhard Schussek", 2879 | "email": "bschussek@gmail.com" 2880 | } 2881 | ], 2882 | "description": "Assertions to validate method input/output with nice error messages.", 2883 | "keywords": [ 2884 | "assert", 2885 | "check", 2886 | "validate" 2887 | ], 2888 | "time": "2020-02-14T12:15:55+00:00" 2889 | } 2890 | ], 2891 | "aliases": [], 2892 | "minimum-stability": "dev", 2893 | "stability-flags": [], 2894 | "prefer-stable": true, 2895 | "prefer-lowest": false, 2896 | "platform": { 2897 | "php": "^7.2" 2898 | }, 2899 | "platform-dev": [] 2900 | } 2901 | --------------------------------------------------------------------------------