diff --git a/.dockerignore b/.dockerignore
index e9d9b9c9..dde3d033 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,3 +5,4 @@
!./docker/config/.gitignore
./var/*
!./var/.gitignore
+.phpunit.result.cache
diff --git a/.gitignore b/.gitignore
index 1eaa3875..db782c29 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/.idea/*
/vendor/*
+.phpunit.result.cache
diff --git a/composer.json b/composer.json
index 5cacc2a2..9a4faff4 100644
--- a/composer.json
+++ b/composer.json
@@ -10,6 +10,9 @@
"*": "dist"
}
},
+ "scripts": {
+ "test": "vendor/bin/phpunit --colors=always"
+ },
"require": {
"php": ">=8.1",
"ext-pdo": "*",
@@ -33,7 +36,13 @@
"require-dev": {
"roave/security-advisories": "dev-latest",
"symfony/var-dumper": "^6.0",
- "perftools/php-profiler": "^1.0"
+ "perftools/php-profiler": "^1.0",
+ "phpunit/phpunit": "^9.5"
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Tests\\": "tests"
+ }
},
"autoload": {
"files": [
diff --git a/composer.lock b/composer.lock
index 01064b95..3e7db2bc 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "2154f627531b8a6fc293d6d2b3cba1a1",
+ "content-hash": "661be76a15d00ad6df2adbe8d3b96059",
"packages": [
{
"name": "dragonmantank/cron-expression",
@@ -1836,6 +1836,186 @@
}
],
"packages-dev": [
+ {
+ "name": "doctrine/instantiator",
+ "version": "1.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/doctrine/instantiator.git",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "reference": "d56bf6102915de5702778fe20f2de3b2fe570b5b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^8.0",
+ "ext-pdo": "*",
+ "ext-phar": "*",
+ "phpbench/phpbench": "^0.13 || 1.0.0-alpha2",
+ "phpstan/phpstan": "^0.12",
+ "phpstan/phpstan-phpunit": "^0.12",
+ "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Marco Pivetta",
+ "email": "ocramius@gmail.com",
+ "homepage": "https://ocramius.github.io/"
+ }
+ ],
+ "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
+ "homepage": "https://www.doctrine-project.org/projects/instantiator.html",
+ "keywords": [
+ "constructor",
+ "instantiate"
+ ],
+ "support": {
+ "issues": "https://github.com/doctrine/instantiator/issues",
+ "source": "https://github.com/doctrine/instantiator/tree/1.4.0"
+ },
+ "funding": [
+ {
+ "url": "https://www.doctrine-project.org/sponsorship.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://www.patreon.com/phpdoctrine",
+ "type": "patreon"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-10T18:47:58+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.10.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "reference": "776f831124e9c62e1a2c601ecc52e776d8bb7220",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.0",
+ "doctrine/common": "^2.6",
+ "phpunit/phpunit": "^7.1"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.10.2"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2020-11-13T09:40:50+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v4.13.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "210577fe3cf7badcc5814d99455df46564f3c077"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/210577fe3cf7badcc5814d99455df46564f3c077",
+ "reference": "210577fe3cf7badcc5814d99455df46564f3c077",
+ "shasum": ""
+ },
+ "require": {
+ "ext-tokenizer": "*",
+ "php": ">=7.0"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.13.2"
+ },
+ "time": "2021-11-30T19:35:32+00:00"
+ },
{
"name": "perftools/php-profiler",
"version": "1.0.0",
@@ -1898,6 +2078,765 @@
},
"time": "2021-10-18T01:40:28+00:00"
},
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
+ "reference": "97803eca37d319dfa7826cc2437fc020857acb53",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.3"
+ },
+ "time": "2021-07-20T11:28:43+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "15a90844ad40f127afd244c0cad228de2a80052a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/15a90844ad40f127afd244c0cad228de2a80052a",
+ "reference": "15a90844ad40f127afd244c0cad228de2a80052a",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.1.1"
+ },
+ "time": "2022-02-07T21:56:48+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-common",
+ "version": "2.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-2.x": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "opensource@ijaap.nl"
+ }
+ ],
+ "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
+ "homepage": "http://www.phpdoc.org",
+ "keywords": [
+ "FQSEN",
+ "phpDocumentor",
+ "phpdoc",
+ "reflection",
+ "static analysis"
+ ],
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
+ },
+ "time": "2020-06-27T09:03:43+00:00"
+ },
+ {
+ "name": "phpdocumentor/reflection-docblock",
+ "version": "5.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
+ "reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
+ "shasum": ""
+ },
+ "require": {
+ "ext-filter": "*",
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.2",
+ "phpdocumentor/type-resolver": "^1.3",
+ "webmozart/assert": "^1.9.1"
+ },
+ "require-dev": {
+ "mockery/mockery": "~1.3.2",
+ "psalm/phar": "^4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ },
+ {
+ "name": "Jaap van Otterdijk",
+ "email": "account@ijaap.nl"
+ }
+ ],
+ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
+ "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
+ },
+ "time": "2021-10-19T17:43:47+00:00"
+ },
+ {
+ "name": "phpdocumentor/type-resolver",
+ "version": "1.6.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpDocumentor/TypeResolver.git",
+ "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/93ebd0014cab80c4ea9f5e297ea48672f1b87706",
+ "reference": "93ebd0014cab80c4ea9f5e297ea48672f1b87706",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0",
+ "phpdocumentor/reflection-common": "^2.0"
+ },
+ "require-dev": {
+ "ext-tokenizer": "*",
+ "psalm/phar": "^4.8"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-1.x": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "phpDocumentor\\Reflection\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Mike van Riel",
+ "email": "me@mikevanriel.com"
+ }
+ ],
+ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
+ "support": {
+ "issues": "https://github.com/phpDocumentor/TypeResolver/issues",
+ "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.0"
+ },
+ "time": "2022-01-04T19:58:01+00:00"
+ },
+ {
+ "name": "phpspec/prophecy",
+ "version": "v1.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpspec/prophecy.git",
+ "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
+ "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.2",
+ "php": "^7.2 || ~8.0, <8.2",
+ "phpdocumentor/reflection-docblock": "^5.2",
+ "sebastian/comparator": "^3.0 || ^4.0",
+ "sebastian/recursion-context": "^3.0 || ^4.0"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^6.0 || ^7.0",
+ "phpunit/phpunit": "^8.0 || ^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Prophecy\\": "src/Prophecy"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Konstantin Kudryashov",
+ "email": "ever.zet@gmail.com",
+ "homepage": "http://everzet.com"
+ },
+ {
+ "name": "Marcello Duarte",
+ "email": "marcello.duarte@gmail.com"
+ }
+ ],
+ "description": "Highly opinionated mocking framework for PHP 5.3+",
+ "homepage": "https://github.com/phpspec/prophecy",
+ "keywords": [
+ "Double",
+ "Dummy",
+ "fake",
+ "mock",
+ "spy",
+ "stub"
+ ],
+ "support": {
+ "issues": "https://github.com/phpspec/prophecy/issues",
+ "source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
+ },
+ "time": "2021-12-08T12:19:24+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "9.2.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "665a1ac0a763c51afc30d6d130dac0813092b17f"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/665a1ac0a763c51afc30d6d130dac0813092b17f",
+ "reference": "665a1ac0a763c51afc30d6d130dac0813092b17f",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^4.13.0",
+ "php": ">=7.3",
+ "phpunit/php-file-iterator": "^3.0.3",
+ "phpunit/php-text-template": "^2.0.2",
+ "sebastian/code-unit-reverse-lookup": "^2.0.2",
+ "sebastian/complexity": "^2.0",
+ "sebastian/environment": "^5.1.2",
+ "sebastian/lines-of-code": "^1.0.3",
+ "sebastian/version": "^3.0.1",
+ "theseer/tokenizer": "^1.2.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcov": "*",
+ "ext-xdebug": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.11"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2022-02-18T12:46:09+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "3.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-12-02T12:48:52+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "3.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:58:55+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T05:33:50+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "5.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:16:10+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "9.5.14",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "1883687169c017d6ae37c58883ca3994cfc34189"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/1883687169c017d6ae37c58883ca3994cfc34189",
+ "reference": "1883687169c017d6ae37c58883ca3994cfc34189",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/instantiator": "^1.3.1",
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.10.1",
+ "phar-io/manifest": "^2.0.3",
+ "phar-io/version": "^3.0.2",
+ "php": ">=7.3",
+ "phpspec/prophecy": "^1.12.1",
+ "phpunit/php-code-coverage": "^9.2.7",
+ "phpunit/php-file-iterator": "^3.0.5",
+ "phpunit/php-invoker": "^3.1.1",
+ "phpunit/php-text-template": "^2.0.3",
+ "phpunit/php-timer": "^5.0.2",
+ "sebastian/cli-parser": "^1.0.1",
+ "sebastian/code-unit": "^1.0.6",
+ "sebastian/comparator": "^4.0.5",
+ "sebastian/diff": "^4.0.3",
+ "sebastian/environment": "^5.1.3",
+ "sebastian/exporter": "^4.0.3",
+ "sebastian/global-state": "^5.0.1",
+ "sebastian/object-enumerator": "^4.0.3",
+ "sebastian/resource-operations": "^3.0.3",
+ "sebastian/type": "^2.3.4",
+ "sebastian/version": "^3.0.2"
+ },
+ "require-dev": {
+ "ext-pdo": "*",
+ "phpspec/prophecy-phpunit": "^2.0.1"
+ },
+ "suggest": {
+ "ext-soap": "*",
+ "ext-xdebug": "*"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "9.5-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.14"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2022-02-18T12:54:07+00:00"
+ },
{
"name": "roave/security-advisories",
"version": "dev-latest",
@@ -2349,6 +3288,970 @@
],
"time": "2022-02-17T14:18:41+00:00"
},
+ {
+ "name": "sebastian/cli-parser",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:08:49+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "1.0.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:08:54+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "2.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:30:19+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "4.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382",
+ "reference": "55f4261989e546dc112258c7a75935a81a7ce382",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/diff": "^4.0",
+ "sebastian/exporter": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:49:45+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "2.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
+ "reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.7",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T15:52:27+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:10:38+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "5.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/388b6ced16caa751030f6a69e588299fa09200ac",
+ "reference": "388b6ced16caa751030f6a69e588299fa09200ac",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "http://www.github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "source": "https://github.com/sebastianbergmann/environment/tree/5.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T05:52:38+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+ "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-11-11T14:18:36+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "5.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+ "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^9.3"
+ },
+ "suggest": {
+ "ext-uopz": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "http://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2022-02-14T08:28:10+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "1.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^4.6",
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-11-28T06:42:11+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71",
+ "reference": "5c9eeac41b290a3712d88851518825ad78f45c71",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3",
+ "sebastian/object-reflector": "^2.0",
+ "sebastian/recursion-context": "^4.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:12:34+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:14:26+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "4.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
+ "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-10-26T13:17:30+00:00"
+ },
+ {
+ "name": "sebastian/resource-operations",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/resource-operations.git",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides a list of PHP built-in functions that operate on resources",
+ "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/resource-operations/issues",
+ "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:45:17+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "2.3.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b8cd8a1c753c90bc1a0f5372170e3e489136f914",
+ "reference": "b8cd8a1c753c90bc1a0f5372170e3e489136f914",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^9.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.3-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "source": "https://github.com/sebastianbergmann/type/tree/2.3.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2021-06-15T12:49:02+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c",
+ "reference": "c6c1022351a901512170118436c764e473f6de8c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "source": "https://github.com/sebastianbergmann/version/tree/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2020-09-28T06:39:44+00:00"
+ },
{
"name": "symfony/var-dumper",
"version": "v6.0.3",
@@ -2436,6 +4339,56 @@
}
],
"time": "2022-01-17T16:30:44+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2021-07-28T10:34:58+00:00"
}
],
"aliases": [],
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
new file mode 100644
index 00000000..38c3f198
--- /dev/null
+++ b/phpunit.xml.dist
@@ -0,0 +1,11 @@
+
+
+
+
+ tests
+
+
+
+
diff --git a/public/index.php b/public/index.php
index 42160982..47667bad 100644
--- a/public/index.php
+++ b/public/index.php
@@ -2,15 +2,7 @@
declare(strict_types=1);
-use App\Libs\Config;
-use App\Libs\Container;
-use App\Libs\HttpException;
-use App\Libs\Servers\ServerInterface;
-use App\Libs\Storage\StorageInterface;
-use Nyholm\Psr7\Response;
-use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
-use Psr\Log\LoggerInterface;
error_reporting(E_ALL);
ini_set('error_reporting', 'On');
@@ -41,82 +33,8 @@ if (!file_exists(__DIR__ . '/../vendor/autoload.php')) {
require __DIR__ . '/../vendor/autoload.php';
-$fn = function (ServerRequestInterface $request): ResponseInterface {
- try {
- if (true !== Config::get('webhook.enabled', false)) {
- throw new HttpException('Webhook is disabled via config.', 500);
- }
-
- if (null === Config::get('webhook.apikey', null)) {
- throw new HttpException('No webhook.apikey is set in config.', 500);
- }
-
- // -- get apikey from header or query.
- $apikey = $request->getHeaderLine('x-apikey');
- if (empty($apikey)) {
- $apikey = ag($request->getQueryParams(), 'apikey', '');
- if (empty($apikey)) {
- throw new HttpException('No API key was given.', 400);
- }
- }
-
- if (!hash_equals(Config::get('webhook.apikey'), $apikey)) {
- throw new HttpException('Invalid API key was given.', 401);
- }
-
- if (null === ($type = ag($request->getQueryParams(), 'type', null))) {
- throw new HttpException('No type was given via type= query.', 400);
- }
-
- $types = Config::get('supported', []);
-
- if (null === ($backend = ag($types, $type))) {
- throw new HttpException('Invalid server type was given.', 400);
- }
-
- $class = new ReflectionClass($backend);
-
- if (!$class->implementsInterface(ServerInterface::class)) {
- throw new HttpException('Invalid Parser Class.', 500);
- }
-
- /** @var ServerInterface $backend */
- $entity = $backend::parseWebhook($request);
-
- if (null === $entity || !$entity->hasGuids()) {
- return new Response(status: 200, headers: ['X-Status' => 'No GUIDs.']);
- }
-
- $storage = Container::get(StorageInterface::class);
-
- if (null === ($backend = $storage->get($entity))) {
- $storage->insert($entity);
- return jsonResponse(status: 200, body: $entity->getAll());
- }
-
- if ($backend->updated > $entity->updated) {
- return new Response(
- status: 200,
- headers: ['X-Status' => 'Entity date is older than what available in storage.']
- );
- }
-
- if ($backend->apply($entity)->isChanged()) {
- $backend = $storage->update($backend);
-
- return jsonResponse(status: 200, body: $backend->getAll());
- }
-
- return new Response(status: 200, headers: ['X-Status' => 'Entity is unchanged.']);
- } catch (HttpException $e) {
- Container::get(LoggerInterface::class)->error($e->getMessage());
-
- if (200 === $e->getCode()) {
- return new Response(status: $e->getCode(), headers: ['X-Status' => $e->getMessage()]);
- }
-
- return jsonResponse(status: $e->getCode(), body: ['error' => true, 'message' => $e->getMessage()]);
+(new App\Libs\KernelConsole())->boot()->runHttp(
+ function (ServerRequestInterface $request) {
+ return serveHttpRequest($request);
}
-};
-
-(new App\Libs\KernelConsole())->boot()->runHttp($fn);
+);
diff --git a/src/Libs/Entity/StateEntity.php b/src/Libs/Entity/StateEntity.php
index 5f07c742..db2041fd 100644
--- a/src/Libs/Entity/StateEntity.php
+++ b/src/Libs/Entity/StateEntity.php
@@ -136,6 +136,12 @@ final class StateEntity implements StateInterface
return $this;
}
+ public function updateOriginal(): StateInterface
+ {
+ $this->data = $this->getAll();
+ return $this;
+ }
+
private function isEqual(StateInterface $entity): bool
{
foreach ($this->getAll() as $key => $val) {
diff --git a/src/Libs/Entity/StateInterface.php b/src/Libs/Entity/StateInterface.php
index f1b16b7b..72799674 100644
--- a/src/Libs/Entity/StateInterface.php
+++ b/src/Libs/Entity/StateInterface.php
@@ -81,4 +81,5 @@ interface StateInterface
*/
public function apply(StateInterface $entity, bool $guidOnly = false): StateInterface;
+ public function updateOriginal(): StateInterface;
}
diff --git a/src/Libs/Storage/PDO/PDOAdapter.php b/src/Libs/Storage/PDO/PDOAdapter.php
index 94def5ed..241b81e9 100644
--- a/src/Libs/Storage/PDO/PDOAdapter.php
+++ b/src/Libs/Storage/PDO/PDOAdapter.php
@@ -6,6 +6,7 @@ namespace App\Libs\Storage\PDO;
use App\Libs\Container;
use App\Libs\Entity\StateInterface;
+use App\Libs\Storage\StorageException;
use App\Libs\Storage\StorageInterface;
use Closure;
use DateTimeInterface;
@@ -14,7 +15,6 @@ use PDO;
use PDOException;
use PDOStatement;
use Psr\Log\LoggerInterface;
-use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -28,38 +28,26 @@ final class PDOAdapter implements StorageInterface
];
private PDO|null $pdo = null;
- private string|null $driver = null;
private bool $viaCommit = false;
- private PDOStatement|null $stmtInsert = null;
- private PDOStatement|null $stmtUpdate = null;
- private PDOStatement|null $stmtDelete = null;
+ /**
+ * Cache Prepared Statements.
+ *
+ * @var array
+ */
+ private array $stmt = [
+ 'insert' => null,
+ 'update' => null,
+ ];
public function __construct(private LoggerInterface $logger)
{
}
- public function getAll(DateTimeInterface|null $date = null): array
- {
- $arr = [];
-
- $sql = "SELECT * FROM state";
-
- if (null !== $date) {
- $sql .= ' WHERE updated > ' . $date->getTimestamp();
- }
-
- foreach ($this->pdo->query($sql) as $row) {
- $arr[] = Container::get(StateInterface::class)::fromArray($row);
- }
-
- return $arr;
- }
-
public function setUp(array $opts): StorageInterface
{
if (null === ($opts['dsn'] ?? null)) {
- throw new RuntimeException('No storage.opts.dsn (Data Source Name) was provided.');
+ throw new StorageException('No storage.opts.dsn (Data Source Name) was provided.', 10);
}
$this->pdo = new PDO(
@@ -75,13 +63,13 @@ final class PDOAdapter implements StorageInterface
)
);
- $this->driver = $this->getDriver();
+ $driver = $this->getDriver();
- if (!in_array($this->driver, $this->supported)) {
- throw new RuntimeException(sprintf('%s Driver is not supported.', $this->driver));
+ if (!in_array($driver, $this->supported)) {
+ throw new StorageException(sprintf('%s Driver is not supported.', $driver), 11);
}
- if (null !== ($exec = ag($opts, "exec.{$this->driver}")) && is_array($exec)) {
+ if (null !== ($exec = ag($opts, "exec.{$driver}")) && is_array($exec)) {
foreach ($exec as $cmd) {
$this->pdo->exec($cmd);
}
@@ -90,17 +78,10 @@ final class PDOAdapter implements StorageInterface
return $this;
}
- public function setLogger(LoggerInterface $logger): StorageInterface
- {
- $this->logger = $logger;
-
- return $this;
- }
-
public function insert(StateInterface $entity): StateInterface
{
if (null === $this->pdo) {
- throw new RuntimeException('Setup(): method was not called.');
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
}
try {
@@ -111,24 +92,24 @@ final class PDOAdapter implements StorageInterface
}
if (null !== $data['id']) {
- throw new RuntimeException(
- sprintf('Trying to insert already saved entity #%s', $data['id'])
+ throw new StorageException(
+ sprintf('Trying to insert already saved entity #%s', $data['id']), 21
);
}
unset($data['id']);
- if (null === $this->stmtInsert) {
- $this->stmtInsert = $this->pdo->prepare(
- $this->pdoInsert('state', array_keys($data))
+ if (null === ($this->stmt['insert'] ?? null)) {
+ $this->stmt['insert'] = $this->pdo->prepare(
+ $this->pdoInsert('state', StateInterface::ENTITY_KEYS)
);
}
- $this->stmtInsert->execute($data);
+ $this->stmt['insert']->execute($data);
$entity->id = (int)$this->pdo->lastInsertId();
} catch (PDOException $e) {
- $this->stmtInsert = null;
+ $this->stmt['insert'] = null;
if (false === $this->viaCommit) {
$this->logger->error($e->getMessage(), $entity->meta ?? []);
return $entity;
@@ -136,13 +117,56 @@ final class PDOAdapter implements StorageInterface
throw $e;
}
- return $entity;
+ return $entity->updateOriginal();
+ }
+
+ public function get(StateInterface $entity): StateInterface|null
+ {
+ if (null === $this->pdo) {
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
+ }
+
+ $arr = array_intersect_key(
+ $entity->getAll(),
+ array_flip(StateInterface::ENTITY_GUIDS)
+ );
+
+ if (null !== $entity->id) {
+ $arr['id'] = $entity->id;
+ }
+
+ return $this->matchAnyId($arr, $entity);
+ }
+
+ public function getAll(DateTimeInterface|null $date = null, StateInterface|null $class = null): array
+ {
+ if (null === $this->pdo) {
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
+ }
+
+ $arr = [];
+
+ $sql = 'SELECT * FROM state';
+
+ if (null !== $date) {
+ $sql .= ' WHERE updated > ' . $date->getTimestamp();
+ }
+
+ if (null === $class) {
+ $class = Container::get(StateInterface::class);
+ }
+
+ foreach ($this->pdo->query($sql) as $row) {
+ $arr[] = $class::fromArray($row);
+ }
+
+ return $arr;
}
public function update(StateInterface $entity): StateInterface
{
if (null === $this->pdo) {
- throw new RuntimeException('Setup(): method was not called.');
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
}
try {
@@ -153,16 +177,18 @@ final class PDOAdapter implements StorageInterface
}
if (null === $data['id']) {
- throw new RuntimeException('Trying to update unsaved entity');
+ throw new StorageException('Trying to update unsaved entity', 51);
}
- if (null === $this->stmtUpdate) {
- $this->stmtUpdate = $this->pdo->prepare($this->pdoUpdate('state', array_keys($data)));
+ if (null === ($this->stmt['update'] ?? null)) {
+ $this->stmt['update'] = $this->pdo->prepare(
+ $this->pdoUpdate('state', StateInterface::ENTITY_KEYS)
+ );
}
- $this->stmtUpdate->execute($data);
+ $this->stmt['update']->execute($data);
} catch (PDOException $e) {
- $this->stmtUpdate = null;
+ $this->stmt['update'] = null;
if (false === $this->viaCommit) {
$this->logger->error($e->getMessage(), $entity->meta ?? []);
return $entity;
@@ -170,31 +196,35 @@ final class PDOAdapter implements StorageInterface
throw $e;
}
- return $entity;
+ return $entity->updateOriginal();
}
- public function get(StateInterface $entity): StateInterface|null
+ public function matchAnyId(array $ids, StateInterface|null $class = null): StateInterface|null
{
if (null === $this->pdo) {
- throw new RuntimeException('Setup(): method was not called.');
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
}
- if (null !== $entity->id) {
- $stmt = $this->pdo->query("SELECT * FROM state WHERE id = " . (int)$entity->id);
+ if (null === $class) {
+ $class = Container::get(StateInterface::class);
+ }
+
+ if (null !== ($ids['id'] ?? null)) {
+ $stmt = $this->pdo->query("SELECT * FROM state WHERE id = " . (int)$ids['id']);
if (false === ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
return null;
}
- return Container::get(StateInterface::class)::fromArray($row);
+ return $class::fromArray($row);
}
$cond = $where = [];
foreach (StateInterface::ENTITY_GUIDS as $key) {
- if (null === $entity->{$key}) {
+ if (null === ($ids[$key] ?? null)) {
continue;
}
- $cond[$key] = $entity->{$key};
+ $cond[$key] = $ids[$key];
}
if (empty($cond)) {
@@ -202,106 +232,47 @@ final class PDOAdapter implements StorageInterface
}
foreach ($cond as $key => $_) {
- $where[] = $this->escapeIdentifier($key) . ' = :' . $key;
+ $where[] = $key . ' = :' . $key;
}
$sqlWhere = implode(' OR ', $where);
- $stmt = $this->pdo->prepare(
- sprintf(
- "SELECT * FROM %s WHERE %s LIMIT 1",
- $this->escapeIdentifier('state'),
- $sqlWhere
- )
- );
+ $cachedKey = md5($sqlWhere);
- if (false === $stmt->execute($cond)) {
- throw new RuntimeException('Unable to prepare sql statement');
- }
+ try {
+ if (null === ($this->stmt[$cachedKey] ?? null)) {
+ $this->stmt[$cachedKey] = $this->pdo->prepare("SELECT * FROM state WHERE {$sqlWhere}");
+ }
- if (false === ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
- return null;
- }
+ if (false === $this->stmt[$cachedKey]->execute($cond)) {
+ $this->stmt[$cachedKey] = null;
+ throw new StorageException('Failed to execute sql query.', 61);
+ }
- return Container::get(StateInterface::class)::fromArray($row);
- }
-
- public function matchAnyId(array $ids): StateInterface|null
- {
- if (null === $this->pdo) {
- throw new RuntimeException('Setup(): method was not called.');
- }
-
- if (null !== ($ids['id'] ?? null)) {
- $stmt = $this->pdo->prepare(
- sprintf(
- 'SELECT * FROM %s WHERE %s = :id LIMIT 1',
- $this->escapeIdentifier('state'),
- $this->escapeIdentifier('id'),
- )
- );
-
- if (false === ($stmt->execute(['id' => $ids['id']]))) {
+ if (false === ($row = $this->stmt[$cachedKey]->fetch(PDO::FETCH_ASSOC))) {
return null;
}
- if (false === ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
- return null;
- }
-
- return Container::get(StateInterface::class)::fromArray($row);
+ return $class::fromArray($row);
+ } catch (PDOException|StorageException $e) {
+ $this->stmt[$cachedKey] = null;
+ throw $e;
}
-
- $cond = $where = [];
-
- foreach ($ids as $_val) {
- if (null === $_val || !str_starts_with($_val, 'guid_')) {
- continue;
- }
-
- [$key, $val] = explode('://', $_val);
-
- $cond[$key] = $val;
- }
-
- if (empty($cond)) {
- return null;
- }
-
- foreach ($cond as $key => $_) {
- $where[] = $this->escapeIdentifier($key) . ' = :' . $key;
- }
-
- $sqlWhere = implode(' OR ', $where);
-
- $stmt = $this->pdo->prepare(
- sprintf(
- "SELECT * FROM %s WHERE %s LIMIT 1",
- $this->escapeIdentifier('state'),
- $sqlWhere
- )
- );
-
- if (false === $stmt->execute($cond)) {
- throw new RuntimeException('Unable to prepare sql statement');
- }
-
- if (false === ($row = $stmt->fetch(PDO::FETCH_ASSOC))) {
- return null;
- }
-
- return Container::get(StateInterface::class)::fromArray($row);
}
public function remove(StateInterface $entity): bool
{
+ if (null === $this->pdo) {
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
+ }
+
if (null === $entity->id && !$entity->hasGuids()) {
return false;
}
try {
if (null === $entity->id) {
- if (null === $dbEntity = $this->get($entity)) {
+ if (null === ($dbEntity = $this->get($entity))) {
return false;
}
$id = $dbEntity->id;
@@ -309,20 +280,9 @@ final class PDOAdapter implements StorageInterface
$id = $entity->id;
}
- if (null === $this->stmtDelete) {
- $this->stmtDelete = $this->pdo->prepare(
- sprintf(
- 'DELETE FROM %s WHERE %s = :id',
- $this->escapeIdentifier('state'),
- $this->escapeIdentifier('id'),
- )
- );
- }
-
- $this->stmtDelete->execute(['id' => $id]);
+ $this->pdo->query('DELETE FROM state WHERE id = ' . (int)$id);
} catch (PDOException $e) {
$this->logger->error($e->getMessage());
- $this->stmtDelete = null;
return false;
}
@@ -332,7 +292,7 @@ final class PDOAdapter implements StorageInterface
public function commit(array $entities): array
{
if (null === $this->pdo) {
- throw new RuntimeException('Setup(): method was not called.');
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
}
return $this->transactional(function () use ($entities) {
@@ -377,6 +337,45 @@ final class PDOAdapter implements StorageInterface
});
}
+ public function migrations(string $dir, InputInterface $input, OutputInterface $output, array $opts = []): mixed
+ {
+ if (null === $this->pdo) {
+ throw new StorageException('Setup(): method was not called.', StorageException::SETUP_NOT_CALLED);
+ }
+
+ $class = new PDOMigrations($this->pdo);
+
+ return match (strtolower($dir)) {
+ StorageInterface::MIGRATE_UP => $class->up($input, $output),
+ StorageInterface::MIGRATE_DOWN => $class->down($output),
+ default => throw new StorageException(sprintf('Unknown direction \'%s\' was given.', $dir), 91),
+ };
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function makeMigration(string $name, OutputInterface $output, array $opts = []): mixed
+ {
+ if (null === $this->pdo) {
+ throw new StorageException('Setup(): method was not called.');
+ }
+
+ return (new PDOMigrations($this->pdo))->make($name, $output);
+ }
+
+ public function maintenance(InputInterface $input, OutputInterface $output, array $opts = []): mixed
+ {
+ return (new PDOMigrations($this->pdo))->runMaintenance();
+ }
+
+ public function setLogger(LoggerInterface $logger): StorageInterface
+ {
+ $this->logger = $logger;
+
+ return $this;
+ }
+
/**
* Wrap Transaction.
*
@@ -409,6 +408,22 @@ final class PDOAdapter implements StorageInterface
}
}
+ /**
+ * Get PDO Driver.
+ *
+ * @return string
+ */
+ private function getDriver(): string
+ {
+ $driver = $this->pdo->getAttribute($this->pdo::ATTR_DRIVER_NAME);
+
+ if (empty($driver) || !is_string($driver)) {
+ $driver = 'unknown';
+ }
+
+ return strtolower($driver);
+ }
+
/**
* Generate SQL Insert Statement.
*
@@ -418,7 +433,7 @@ final class PDOAdapter implements StorageInterface
*/
private function pdoInsert(string $table, array $columns): string
{
- $queryString = 'INSERT INTO ' . $this->escapeIdentifier($table) . ' (%{columns}) VALUES(%{values})';
+ $queryString = "INSERT INTO {$table} (%(columns)) VALUES(%(values))";
$sql_columns = $sql_placeholder = [];
@@ -427,12 +442,12 @@ final class PDOAdapter implements StorageInterface
continue;
}
- $sql_columns[] = $this->escapeIdentifier($column, true);
- $sql_placeholder[] = ':' . $this->escapeIdentifier($column, false);
+ $sql_columns[] = $column;
+ $sql_placeholder[] = ':' . $column;
}
$queryString = str_replace(
- ['%{columns}', '%{values}'],
+ ['%(columns)', '%(values)'],
[implode(', ', $sql_columns), implode(', ', $sql_placeholder)],
$queryString
);
@@ -449,11 +464,8 @@ final class PDOAdapter implements StorageInterface
*/
private function pdoUpdate(string $table, array $columns): string
{
- $queryString = sprintf(
- 'UPDATE %s SET ${place} = ${holder} WHERE %s = :id',
- $this->escapeIdentifier($table, true),
- $this->escapeIdentifier('id', true)
- );
+ /** @noinspection SqlWithoutWhere */
+ $queryString = "UPDATE {$table} SET %(place) = %(holder) WHERE id = :id";
$placeholders = [];
@@ -461,95 +473,14 @@ final class PDOAdapter implements StorageInterface
if ('id' === $column) {
continue;
}
- $placeholders[] = sprintf(
- '%1$s = :%2$s',
- $this->escapeIdentifier($column, true),
- $this->escapeIdentifier($column, false)
- );
+ $placeholders[] = sprintf('%1$s = :%1$s', $column);
}
- return trim(str_replace('${place} = ${holder}', implode(', ', $placeholders), $queryString));
- }
-
- private function escapeIdentifier(string $text, bool $quote = true): string
- {
- // table or column has to be valid ASCII name.
- // this is opinionated, but we only allow [a-zA-Z0-9_] in column/table name.
- if (!preg_match('#\w#', $text)) {
- throw new RuntimeException(
- sprintf(
- 'Invalid identifier "%s": Column/table must be valid ASCII code.',
- $text
- )
- );
- }
-
- // The first character cannot be [0-9]:
- if (preg_match('/^\d/', $text)) {
- throw new RuntimeException(
- sprintf(
- 'Invalid identifier "%s": Must begin with a letter or underscore.',
- $text
- )
- );
- }
-
- if (!$quote) {
- return $text;
- }
-
- return match ($this->driver) {
- 'mssql' => '[' . $text . ']',
- 'mysql' => '`' . $text . '`',
- default => '"' . $text . '"',
- };
+ return trim(str_replace('%(place) = %(holder)', implode(', ', $placeholders), $queryString));
}
public function __destruct()
{
- $this->stmtDelete = $this->stmtUpdate = $this->stmtInsert = null;
- }
-
- public function migrations(string $dir, InputInterface $input, OutputInterface $output, array $opts = []): mixed
- {
- if (null === $this->pdo) {
- throw new RuntimeException('Setup(): method was not called.');
- }
-
- $class = new PDOMigrations($this->pdo);
-
- return match ($dir) {
- StorageInterface::MIGRATE_UP => $class->up($input, $output),
- StorageInterface::MIGRATE_DOWN => $class->down($output),
- default => throw new RuntimeException(sprintf('Unknown direction \'%s\' was given.', $dir)),
- };
- }
-
- /**
- * @throws Exception
- */
- public function makeMigration(string $name, OutputInterface $output, array $opts = []): void
- {
- if (null === $this->pdo) {
- throw new RuntimeException('Setup(): method was not called.');
- }
-
- (new PDOMigrations($this->pdo))->make($name, $output);
- }
-
- public function maintenance(InputInterface $input, OutputInterface $output, array $opts = []): mixed
- {
- return (new PDOMigrations($this->pdo))->runMaintenance();
- }
-
- private function getDriver(): string
- {
- $driver = $this->pdo->getAttribute($this->pdo::ATTR_DRIVER_NAME);
-
- if (empty($driver) || !is_string($driver)) {
- $driver = 'unknown';
- }
-
- return strtolower($driver);
+ $this->stmt = [];
}
}
diff --git a/src/Libs/Storage/StorageException.php b/src/Libs/Storage/StorageException.php
new file mode 100644
index 00000000..9491088e
--- /dev/null
+++ b/src/Libs/Storage/StorageException.php
@@ -0,0 +1,12 @@
+
+ */
+ public function getAll(DateTimeInterface|null $date = null, StateInterface|null $class = null): array;
+
/**
* Update Entity immediately.
*
@@ -52,13 +63,14 @@ interface StorageInterface
public function update(StateInterface $entity): StateInterface;
/**
- * Get Entity.
+ * Get Entity Using array of ids.
*
- * @param StateInterface $entity
+ * @param array $ids
+ * @param StateInterface|null $class Create object based on given class, if null use default class.
*
* @return StateInterface|null
*/
- public function get(StateInterface $entity): StateInterface|null;
+ public function matchAnyId(array $ids, StateInterface|null $class = null): StateInterface|null;
/**
* Remove Entity.
@@ -78,24 +90,6 @@ interface StorageInterface
*/
public function commit(array $entities): array;
- /**
- * Load All Entities From backend.
- *
- * @param DateTimeInterface|null $date Get Entities That has changed since given time.
- *
- * @return array
- */
- public function getAll(DateTimeInterface|null $date = null): array;
-
- /**
- * Get Entity Using array of ids.
- *
- * @param array $ids
- *
- * @return StateInterface|null
- */
- public function matchAnyId(array $ids): StateInterface|null;
-
/**
* Migrate Backend Storage Schema.
*
@@ -124,6 +118,17 @@ interface StorageInterface
* @param string $name
* @param OutputInterface $output
* @param array $opts
+ *
+ * @return mixed can return migration file name in supported cases.
*/
- public function makeMigration(string $name, OutputInterface $output, array $opts = []): void;
+ public function makeMigration(string $name, OutputInterface $output, array $opts = []): mixed;
+
+ /**
+ * Inject Logger.
+ *
+ * @param LoggerInterface $logger
+ * @return $this
+ */
+ public function setLogger(LoggerInterface $logger): self;
+
}
diff --git a/src/Libs/helpers.php b/src/Libs/helpers.php
index ef19502a..db9b5378 100644
--- a/src/Libs/helpers.php
+++ b/src/Libs/helpers.php
@@ -3,10 +3,15 @@
declare(strict_types=1);
use App\Libs\Config;
+use App\Libs\Container;
use App\Libs\Extends\Date;
+use App\Libs\HttpException;
+use App\Libs\Servers\ServerInterface;
+use App\Libs\Storage\StorageInterface;
use Nyholm\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
+use Psr\Log\LoggerInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\ResponseStreamInterface;
@@ -112,7 +117,7 @@ if (!function_exists('ag_set')) {
if (is_array($at)) {
$at[array_shift($keys)] = $value;
} else {
- throw new RuntimeException("Can not set value at this path ($path) because is not array.");
+ throw new RuntimeException("Can not set value at this path ($path) because its not array.");
}
} else {
$path = array_shift($keys);
@@ -268,3 +273,87 @@ if (!function_exists('httpClientChunks')) {
}
}
}
+
+if (!function_exists('serveHttpRequest')) {
+ /**
+ * @throws ReflectionException
+ */
+ function serveHttpRequest(ServerRequestInterface $request): ResponseInterface
+ {
+ try {
+ if (true !== Config::get('webhook.enabled', false)) {
+ throw new HttpException('Webhook is disabled via config.', 500);
+ }
+
+ if (null === Config::get('webhook.apikey', null)) {
+ throw new HttpException('No webhook.apikey is set in config.', 500);
+ }
+
+ // -- get apikey from header or query.
+ $apikey = $request->getHeaderLine('x-apikey');
+ if (empty($apikey)) {
+ $apikey = ag($request->getQueryParams(), 'apikey', '');
+ if (empty($apikey)) {
+ throw new HttpException('No API key was given.', 400);
+ }
+ }
+
+ if (!hash_equals(Config::get('webhook.apikey'), $apikey)) {
+ throw new HttpException('Invalid API key was given.', 401);
+ }
+
+ if (null === ($type = ag($request->getQueryParams(), 'type', null))) {
+ throw new HttpException('No type was given via type= query.', 400);
+ }
+
+ $types = Config::get('supported', []);
+
+ if (null === ($backend = ag($types, $type))) {
+ throw new HttpException('Invalid server type was given.', 400);
+ }
+
+ $class = new ReflectionClass($backend);
+
+ if (!$class->implementsInterface(ServerInterface::class)) {
+ throw new HttpException('Invalid Parser Class.', 500);
+ }
+
+ /** @var ServerInterface $backend */
+ $entity = $backend::parseWebhook($request);
+
+ if (null === $entity || !$entity->hasGuids()) {
+ return new Response(status: 200, headers: ['X-Status' => 'No GUIDs.']);
+ }
+
+ $storage = Container::get(StorageInterface::class);
+
+ if (null === ($backend = $storage->get($entity))) {
+ $storage->insert($entity);
+ return jsonResponse(status: 200, body: $entity->getAll());
+ }
+
+ if ($backend->updated > $entity->updated) {
+ return new Response(
+ status: 200,
+ headers: ['X-Status' => 'Entity date is older than what available in storage.']
+ );
+ }
+
+ if ($backend->apply($entity)->isChanged()) {
+ $backend = $storage->update($backend);
+
+ return jsonResponse(status: 200, body: $backend->getAll());
+ }
+
+ return new Response(status: 200, headers: ['X-Status' => 'Entity is unchanged.']);
+ } catch (HttpException $e) {
+ Container::get(LoggerInterface::class)->error($e->getMessage());
+
+ if (200 === $e->getCode()) {
+ return new Response(status: $e->getCode(), headers: ['X-Status' => $e->getMessage()]);
+ }
+
+ return jsonResponse(status: $e->getCode(), body: ['error' => true, 'message' => $e->getMessage()]);
+ }
+ }
+}
diff --git a/tests/Storage/PDOAdapterTest.php b/tests/Storage/PDOAdapterTest.php
new file mode 100644
index 00000000..e9303b2c
--- /dev/null
+++ b/tests/Storage/PDOAdapterTest.php
@@ -0,0 +1,313 @@
+ null,
+ 'type' => StateInterface::TYPE_EPISODE,
+ 'updated' => 0,
+ 'watched' => 1,
+ 'meta' => [],
+ 'guid_plex' => StateInterface::TYPE_EPISODE . '/1',
+ 'guid_imdb' => StateInterface::TYPE_EPISODE . '/2',
+ 'guid_tvdb' => StateInterface::TYPE_EPISODE . '/3',
+ 'guid_tmdb' => StateInterface::TYPE_EPISODE . '/4',
+ 'guid_tvmaze' => StateInterface::TYPE_EPISODE . '/5',
+ 'guid_tvrage' => StateInterface::TYPE_EPISODE . '/6',
+ 'guid_anidb' => StateInterface::TYPE_EPISODE . '/7',
+ ];
+
+ private array $testMovie = [
+ 'id' => null,
+ 'type' => StateInterface::TYPE_MOVIE,
+ 'updated' => 1,
+ 'watched' => 1,
+ 'meta' => [],
+ 'guid_plex' => StateInterface::TYPE_MOVIE . '/10',
+ 'guid_imdb' => StateInterface::TYPE_MOVIE . '/20',
+ 'guid_tvdb' => StateInterface::TYPE_MOVIE . '/30',
+ 'guid_tmdb' => StateInterface::TYPE_MOVIE . '/40',
+ 'guid_tvmaze' => StateInterface::TYPE_MOVIE . '/50',
+ 'guid_tvrage' => StateInterface::TYPE_MOVIE . '/60',
+ 'guid_anidb' => StateInterface::TYPE_MOVIE . '/70',
+ ];
+
+ private StorageInterface|null $storage = null;
+
+ public function setUp(): void
+ {
+ $this->output = new NullOutput();
+ $this->input = new ArrayInput([]);
+
+ $this->storage = new PDOAdapter(new CliLogger($this->output));
+ $this->storage->setUp(['dsn' => 'sqlite::memory:']);
+ $this->storage->migrations('up', $this->input, $this->output);
+ }
+
+ /** StorageInterface::setUp */
+ public function test_setup_throw_exception_if_no_dsn(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(10);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->setUp([]);
+ }
+
+ public function test_setup_throw_exception_if_invalid_dsn(): void
+ {
+ $this->expectException(PDOException::class);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->setUp(['dsn' => 'not_real_driver::foo']);
+ }
+
+ /** StorageInterface::insert */
+ public function test_insert_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+
+ $storage->insert(new StateEntity([]));
+ }
+
+ public function test_insert_throw_exception_if_has_id(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(21);
+ $item = new StateEntity($this->testEpisode);
+ $this->storage->insert($item);
+ $this->storage->insert($item);
+ }
+
+ public function test_insert_successful(): void
+ {
+ $item = $this->storage->insert(new StateEntity($this->testEpisode));
+ $this->assertSame(1, $item->id);
+ }
+
+ /** StorageInterface::get */
+ public function test_get_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->get(new StateEntity([]));
+ }
+
+ public function test_get_conditions(): void
+ {
+ $item = new StateEntity($this->testEpisode);
+
+ // -- db should be empty at this stage. as such we expect null.
+ $this->assertNull($this->storage->get($item));
+
+ // -- insert and return object and assert it's the same
+ $modified = $this->storage->insert(clone $item);
+
+ $this->assertSame($modified->getAll(), $this->storage->get($item)->getAll());
+
+ // -- look up based on id
+ $this->assertSame($modified->getAll(), $this->storage->get($modified)->getAll());
+ }
+
+ /** StorageInterface::getAll */
+ public function test_getAll_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->getAll();
+ }
+
+ public function test_getAll_call_without_initialized_container(): void
+ {
+ $this->expectException(Error::class);
+ $this->expectExceptionMessage('Call to a member function');
+ $this->storage->getAll();
+ }
+
+ public function test_getAll_conditions(): void
+ {
+ $item = new StateEntity($this->testEpisode);
+
+ $this->assertSame([], $this->storage->getAll(class: $item));
+
+ $this->storage->insert($item);
+
+ $this->assertCount(1, $this->storage->getAll(class: $item));
+
+ // -- future date should be 0.
+ $this->assertCount(0, $this->storage->getAll(date: new DateTimeImmutable('now'), class: $item));
+ }
+
+ /** StorageInterface::update */
+ public function test_update_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->update(new StateEntity([]));
+ }
+
+ public function test_update_call_without_id_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(51);
+ $item = new StateEntity($this->testEpisode);
+
+ $this->storage->update($item);
+ }
+
+ public function test_update_conditions(): void
+ {
+ $item = $this->storage->insert(new StateEntity($this->testEpisode));
+ $item->guid_plex = StateInterface::TYPE_EPISODE . '/1000';
+
+ $updatedItem = $this->storage->update($item);
+
+ $this->assertSame($item, $updatedItem);
+ $this->assertSame($updatedItem->getAll(), $this->storage->get($item)->getAll());
+ }
+
+ /** StorageInterface::update */
+ public function test_matchAnyId_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->matchAnyId([]);
+ }
+
+ public function test_matchAnyId_call_without_initialized_container(): void
+ {
+ $this->expectException(Error::class);
+ $this->expectExceptionMessage('Call to a member function');
+ $this->storage->matchAnyId([]);
+ }
+
+ public function test_matchAnyId_conditions(): void
+ {
+ $item1 = new StateEntity($this->testEpisode);
+ $item2 = new StateEntity($this->testMovie);
+
+ $this->assertNull(
+ $this->storage->matchAnyId(
+ array_intersect_key($item1->getAll(), array_flip(StateInterface::ENTITY_GUIDS)),
+ $item1
+ )
+ );
+
+ $newItem1 = $this->storage->insert($item1);
+ $newItem2 = $this->storage->insert($item2);
+
+ $this->assertSame(
+ $newItem1->getAll(),
+ $this->storage->matchAnyId(
+ array_intersect_key($item1->getAll(), array_flip(StateInterface::ENTITY_GUIDS)),
+ $item1
+ )->getAll()
+ );
+
+ $this->assertSame(
+ $newItem2->getAll(),
+ $this->storage->matchAnyId(
+ array_intersect_key($item2->getAll(), array_flip(StateInterface::ENTITY_GUIDS)),
+ $item2
+ )->getAll()
+ );
+ }
+
+ /** StorageInterface::remove */
+ public function test_remove_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->remove(new StateEntity([]));
+ }
+
+ public function test_remove_conditions(): void
+ {
+ $item1 = new StateEntity($this->testEpisode);
+ $item2 = new StateEntity($this->testMovie);
+ $item3 = new StateEntity([]);
+
+ $this->assertFalse($this->storage->remove($item1));
+
+ $item1 = $this->storage->insert($item1);
+ $this->storage->insert($item2);
+
+ $this->assertTrue($this->storage->remove($item1));
+ $this->assertInstanceOf(StateInterface::class, $this->storage->get($item2));
+
+ // -- remove without id pointer.
+ $this->assertTrue($this->storage->remove($item2));
+ $this->assertFalse($this->storage->remove($item3));
+ }
+
+ /** StorageInterface::commit */
+ public function test_commit_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->commit([]);
+ }
+
+ public function test_commit_conditions(): void
+ {
+ $item1 = new StateEntity($this->testEpisode);
+ $item2 = new StateEntity($this->testMovie);
+
+ $this->assertSame(
+ [
+ StateInterface::TYPE_MOVIE => ['added' => 1, 'updated' => 0, 'failed' => 0],
+ StateInterface::TYPE_EPISODE => ['added' => 1, 'updated' => 0, 'failed' => 0],
+ ],
+ $this->storage->commit([$item1, $item2])
+ );
+
+ $item1->guid_anidb = StateInterface::TYPE_EPISODE . '/1';
+ $item2->guid_anidb = StateInterface::TYPE_MOVIE . '/1';
+
+ $this->assertSame(
+ [
+ StateInterface::TYPE_MOVIE => ['added' => 0, 'updated' => 1, 'failed' => 0],
+ StateInterface::TYPE_EPISODE => ['added' => 0, 'updated' => 1, 'failed' => 0],
+ ],
+ $this->storage->commit([$item1, $item2])
+ );
+ }
+
+ /** StorageInterface::migrations */
+ public function test_migrations_call_without_setup_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(StorageException::SETUP_NOT_CALLED);
+ $storage = new PDOAdapter(new CliLogger($this->output));
+ $storage->migrations('f', new ArrayInput([]), new NullOutput());
+ }
+ public function test_migrations_call_with_wrong_direction_exception(): void
+ {
+ $this->expectException(StorageException::class);
+ $this->expectExceptionCode(91);
+ $this->storage->migrations('not_dd', new ArrayInput([]), new NullOutput());
+ }
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
new file mode 100644
index 00000000..9f2d88bd
--- /dev/null
+++ b/tests/bootstrap.php
@@ -0,0 +1,15 @@
+