diff --git a/.env b/.env index 8a5077a..7edc32e 100644 --- a/.env +++ b/.env @@ -18,3 +18,7 @@ APP_ENV=dev APP_SECRET=f27f6cb11fb594fcb5ab4591ec0aac77 ###< symfony/framework-bundle ### + +###> sentry/sentry-symfony ### +SENTRY_DSN= +###< sentry/sentry-symfony ### diff --git a/README.md b/README.md index c68f907..b043552 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ TODO: ----- - [ ] Chybí speciální slovník nebo vypnutí slovníku pro testy +- [ ] V reálný aplikaci bych použil Mockery, nicméně tady mě to přijde zbytečný +- [ ] Nastavení cache ideálně v memcached/redis etc. +- [ ] Vylepsit OTEL logs diff --git a/composer.json b/composer.json index 50c1c0b..0485b10 100644 --- a/composer.json +++ b/composer.json @@ -10,22 +10,27 @@ "ext-iconv": "*", "grpc/grpc": "^1.57", "guzzlehttp/promises": "*", + "nubium/this-should-never-happen-exception": "^1.0", "nyholm/psr7": "*", "open-telemetry/exporter-otlp": "^1.0", "open-telemetry/opentelemetry-auto-symfony": "^1.0@beta", + "open-telemetry/opentelemetry-logger-monolog": "^1.0", "open-telemetry/opentelemetry-propagation-server-timing": "^0.0.1", "open-telemetry/opentelemetry-propagation-traceresponse": "^0.0.2", "open-telemetry/sdk": "^1.0", "open-telemetry/transport-grpc": "^1.0", "php-http/httplug": "*", + "sentry/sentry-symfony": "^4.13", "symfony/asset": "7.0.*", "symfony/asset-mapper": "7.0.*", + "symfony/cache": "7.0.*", "symfony/console": "7.0.*", "symfony/dotenv": "7.0.*", "symfony/flex": "^2", "symfony/form": "7.0.*", "symfony/framework-bundle": "7.0.*", "symfony/http-client": "*", + "symfony/monolog-bundle": "^3.10", "symfony/options-resolver": "7.0.*", "symfony/runtime": "7.0.*", "symfony/translation": "7.0.*", diff --git a/composer.lock b/composer.lock index cb30729..d4f1c34 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,74 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "001cdd38079665d928258b761b93729c", + "content-hash": "60708df22ad1ee4eca712d46f8e02c3b", "packages": [ + { + "name": "clue/stream-filter", + "version": "v1.7.0", + "source": { + "type": "git", + "url": "https://github.com/clue/stream-filter.git", + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7", + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "Clue\\StreamFilter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "A simple and modern approach to stream filtering in PHP", + "homepage": "https://github.com/clue/stream-filter", + "keywords": [ + "bucket brigade", + "callback", + "filter", + "php_user_filter", + "stream", + "stream_filter_append", + "stream_filter_register" + ], + "support": { + "issues": "https://github.com/clue/stream-filter/issues", + "source": "https://github.com/clue/stream-filter/tree/v1.7.0" + }, + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2023-12-20T15:40:13+00:00" + }, { "name": "composer/semver", "version": "3.4.0", @@ -258,6 +324,381 @@ ], "time": "2023-12-03T20:19:20+00:00" }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:05:35+00:00" + }, + { + "name": "http-interop/http-factory-guzzle", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/http-interop/http-factory-guzzle.git", + "reference": "8f06e92b95405216b237521cc64c804dd44c4a81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/8f06e92b95405216b237521cc64c804dd44c4a81", + "reference": "8f06e92b95405216b237521cc64c804dd44c4a81", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.7||^2.0", + "php": ">=7.3", + "psr/http-factory": "^1.0" + }, + "provide": { + "psr/http-factory-implementation": "^1.0" + }, + "require-dev": { + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^9.5" + }, + "suggest": { + "guzzlehttp/psr7": "Includes an HTTP factory starting in version 2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Factory\\Guzzle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "An HTTP Factory using Guzzle PSR7", + "keywords": [ + "factory", + "http", + "psr-17", + "psr-7" + ], + "support": { + "issues": "https://github.com/http-interop/http-factory-guzzle/issues", + "source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.0" + }, + "time": "2021-07-21T13:50:14+00:00" + }, + { + "name": "jean85/pretty-package-versions", + "version": "2.0.5", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "reference": "ae547e455a3d8babd07b96966b17d7fd21d9c6af", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.0.0", + "php": "^7.1|^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.17", + "jean85/composer-provided-replaced-stub-package": "^1.0", + "phpstan/phpstan": "^0.12.66", + "phpunit/phpunit": "^7.5|^8.5|^9.4", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Jean85\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "A library to get pretty versions strings of installed dependencies", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "support": { + "issues": "https://github.com/Jean85/pretty-package-versions/issues", + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.5" + }, + "time": "2021-10-08T21:21:46+00:00" + }, + { + "name": "monolog/monolog", + "version": "3.5.0", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "reference": "c915e2634718dbc8a4a15c61b0e62e7a44e14448", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/log": "^2.0 || ^3.0" + }, + "provide": { + "psr/log-implementation": "3.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^3.0", + "doctrine/couchdb": "~1.0@dev", + "elasticsearch/elasticsearch": "^7 || ^8", + "ext-json": "*", + "graylog2/gelf-php": "^1.4.2 || ^2.0", + "guzzlehttp/guzzle": "^7.4.5", + "guzzlehttp/psr7": "^2.2", + "mongodb/mongodb": "^1.8", + "php-amqplib/php-amqplib": "~2.4 || ^3", + "phpstan/phpstan": "^1.9", + "phpstan/phpstan-deprecation-rules": "^1.0", + "phpstan/phpstan-strict-rules": "^1.4", + "phpunit/phpunit": "^10.1", + "predis/predis": "^1.1 || ^2", + "ruflin/elastica": "^7", + "symfony/mailer": "^5.4 || ^6", + "symfony/mime": "^5.4 || ^6" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler", + "ext-mbstring": "Allow to work properly with unicode symbols", + "ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)", + "ext-openssl": "Required to send log messages using SSL", + "ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "https://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "https://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/3.5.0" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2023-10-27T15:32:31+00:00" + }, + { + "name": "nubium/this-should-never-happen-exception", + "version": "v1.0", + "source": { + "type": "git", + "url": "https://github.com/nubium/this-should-never-happen-exception.git", + "reference": "3ed1b6f725881c527050c235e2503a8300427b86" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nubium/this-should-never-happen-exception/zipball/3ed1b6f725881c527050c235e2503a8300427b86", + "reference": "3ed1b6f725881c527050c235e2503a8300427b86", + "shasum": "" + }, + "require-dev": { + "jakub-onderka/php-parallel-lint": "~1.0", + "phpstan/phpstan": "~0.9" + }, + "type": "library", + "autoload": { + "psr-4": { + "Nubium\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jiri Travnicek", + "email": "jiri.travnicek@nubium.cz" + } + ], + "description": "Extend this exception and throw it anytime something unexpected happens.", + "support": { + "issues": "https://github.com/nubium/this-should-never-happen-exception/issues", + "source": "https://github.com/nubium/this-should-never-happen-exception/tree/master" + }, + "time": "2018-03-27T10:16:09+00:00" + }, { "name": "nyholm/psr7", "version": "1.8.1", @@ -660,6 +1101,64 @@ }, "time": "2023-12-12T11:41:45+00:00" }, + { + "name": "open-telemetry/opentelemetry-logger-monolog", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/opentelemetry-php/contrib-logger-monolog.git", + "reference": "da70f3678aba1e8187f889e78f3a2a55f43f6395" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/opentelemetry-php/contrib-logger-monolog/zipball/da70f3678aba1e8187f889e78f3a2a55f43f6395", + "reference": "da70f3678aba1e8187f889e78f3a2a55f43f6395", + "shasum": "" + }, + "require": { + "monolog/monolog": "^1.1|^2|^3", + "open-telemetry/api": "^1.0.0beta16" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3", + "google/protobuf": ">=3.5.0", + "guzzlehttp/guzzle": "^7.4", + "nyholm/psr7": "^1.6", + "open-telemetry/exporter-otlp": ">=1.0.0beta6", + "open-telemetry/sdk": ">=1.0.0beta7", + "phan/phan": "^5.0", + "phpbench/phpbench": "^1.2", + "phpstan/phpstan": "^1.1", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^9.5", + "psalm/plugin-phpunit": "^0.16", + "vimeo/psalm": "^4.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "OpenTelemetry\\Contrib\\Logs\\Monolog\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "OpenTelemetry Monolog handler.", + "homepage": "https://opentelemetry.io", + "keywords": [ + "handler", + "logs", + "monolog", + "open-telemetry", + "opentelemetry", + "otel" + ], + "support": { + "source": "https://github.com/opentelemetry-php/contrib-logger-monolog/tree/1.0.0" + }, + "time": "2023-10-17T21:44:43+00:00" + }, { "name": "open-telemetry/opentelemetry-propagation-server-timing", "version": "0.0.1", @@ -975,6 +1474,75 @@ }, "time": "2023-09-05T03:38:44+00:00" }, + { + "name": "php-http/client-common", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/client-common.git", + "reference": "1e19c059b0e4d5f717bf5d524d616165aeab0612" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/client-common/zipball/1e19c059b0e4d5f717bf5d524d616165aeab0612", + "reference": "1e19c059b0e4d5f717bf5d524d616165aeab0612", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0", + "php-http/httplug": "^2.0", + "php-http/message": "^1.6", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0 || ^2.0", + "symfony/options-resolver": "~4.0.15 || ~4.1.9 || ^4.2.1 || ^5.0 || ^6.0 || ^7.0", + "symfony/polyfill-php80": "^1.17" + }, + "require-dev": { + "doctrine/instantiator": "^1.1", + "guzzlehttp/psr7": "^1.4", + "nyholm/psr7": "^1.2", + "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", + "phpspec/prophecy": "^1.10.2", + "phpunit/phpunit": "^7.5.20 || ^8.5.33 || ^9.6.7" + }, + "suggest": { + "ext-json": "To detect JSON responses with the ContentTypePlugin", + "ext-libxml": "To detect XML responses with the ContentTypePlugin", + "php-http/cache-plugin": "PSR-6 Cache plugin", + "php-http/logger-plugin": "PSR-3 Logger plugin", + "php-http/stopwatch-plugin": "Symfony Stopwatch plugin" + }, + "type": "library", + "autoload": { + "psr-4": { + "Http\\Client\\Common\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Common HTTP Client implementations and tools for HTTPlug", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "common", + "http", + "httplug" + ], + "support": { + "issues": "https://github.com/php-http/client-common/issues", + "source": "https://github.com/php-http/client-common/tree/2.7.1" + }, + "time": "2023-11-30T10:31:25+00:00" + }, { "name": "php-http/discovery", "version": "1.19.2", @@ -1110,6 +1678,130 @@ }, "time": "2023-04-14T15:10:03+00:00" }, + { + "name": "php-http/message", + "version": "1.16.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message.git", + "reference": "47a14338bf4ebd67d317bf1144253d7db4ab55fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message/zipball/47a14338bf4ebd67d317bf1144253d7db4ab55fd", + "reference": "47a14338bf4ebd67d317bf1144253d7db4ab55fd", + "shasum": "" + }, + "require": { + "clue/stream-filter": "^1.5", + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.1 || ^2.0" + }, + "provide": { + "php-http/message-factory-implementation": "1.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.6", + "ext-zlib": "*", + "guzzlehttp/psr7": "^1.0 || ^2.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", + "php-http/message-factory": "^1.0.2", + "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", + "slim/slim": "^3.0" + }, + "suggest": { + "ext-zlib": "Used with compressor/decompressor streams", + "guzzlehttp/psr7": "Used with Guzzle PSR-7 Factories", + "laminas/laminas-diactoros": "Used with Diactoros Factories", + "slim/slim": "Used with Slim Framework PSR-7 implementation" + }, + "type": "library", + "autoload": { + "files": [ + "src/filters.php" + ], + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTP Message related tools", + "homepage": "http://php-http.org", + "keywords": [ + "http", + "message", + "psr-7" + ], + "support": { + "issues": "https://github.com/php-http/message/issues", + "source": "https://github.com/php-http/message/tree/1.16.0" + }, + "time": "2023-05-17T06:43:38+00:00" + }, + { + "name": "php-http/message-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/message-factory.git", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/message-factory/zipball/4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "reference": "4d8778e1c7d405cbb471574821c1ff5b68cc8f57", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "Factory interfaces for PSR-7 HTTP Message", + "homepage": "http://php-http.org", + "keywords": [ + "factory", + "http", + "message", + "stream", + "uri" + ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/1.1.0" + }, + "abandoned": "psr/http-factory", + "time": "2023-04-14T14:16:17+00:00" + }, { "name": "php-http/promise", "version": "1.3.0", @@ -1524,6 +2216,321 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "sentry/sdk", + "version": "3.6.0", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php-sdk.git", + "reference": "24c235ff2027401cbea099bf88689e1a1f197c7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php-sdk/zipball/24c235ff2027401cbea099bf88689e1a1f197c7a", + "reference": "24c235ff2027401cbea099bf88689e1a1f197c7a", + "shasum": "" + }, + "require": { + "http-interop/http-factory-guzzle": "^1.0", + "sentry/sentry": "^3.22", + "symfony/http-client": "^4.3|^5.0|^6.0|^7.0" + }, + "type": "metapackage", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "This is a metapackage shipping sentry/sentry with a recommended HTTP client.", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "sentry" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php-sdk/issues", + "source": "https://github.com/getsentry/sentry-php-sdk/tree/3.6.0" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2023-12-04T10:49:33+00:00" + }, + { + "name": "sentry/sentry", + "version": "3.22.1", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-php.git", + "reference": "8859631ba5ab15bc1af420b0eeed19ecc6c9d81d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/8859631ba5ab15bc1af420b0eeed19ecc6c9d81d", + "reference": "8859631ba5ab15bc1af420b0eeed19ecc6c9d81d", + "shasum": "" + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "guzzlehttp/promises": "^1.5.3|^2.0", + "jean85/pretty-package-versions": "^1.5|^2.0.4", + "php": "^7.2|^8.0", + "php-http/async-client-implementation": "^1.0", + "php-http/client-common": "^1.5|^2.0", + "php-http/discovery": "^1.15", + "php-http/httplug": "^1.1|^2.0", + "php-http/message": "^1.5", + "php-http/message-factory": "^1.1", + "psr/http-factory": "^1.0", + "psr/http-factory-implementation": "^1.0", + "psr/log": "^1.0|^2.0|^3.0", + "symfony/options-resolver": "^3.4.43|^4.4.30|^5.0.11|^6.0|^7.0", + "symfony/polyfill-php80": "^1.17" + }, + "conflict": { + "php-http/client-common": "1.8.0", + "raven/raven": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.19|3.4.*", + "guzzlehttp/psr7": "^1.8.4|^2.1.1", + "http-interop/http-factory-guzzle": "^1.0", + "monolog/monolog": "^1.6|^2.0|^3.0", + "nikic/php-parser": "^4.10.3", + "php-http/mock-client": "^1.3", + "phpbench/phpbench": "^1.0", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^8.5.14|^9.4", + "symfony/phpunit-bridge": "^5.2|^6.0", + "vimeo/psalm": "^4.17" + }, + "suggest": { + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler." + }, + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Sentry\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sentry", + "email": "accounts@sentry.io" + } + ], + "description": "A PHP SDK for Sentry (http://sentry.io)", + "homepage": "http://sentry.io", + "keywords": [ + "crash-reporting", + "crash-reports", + "error-handler", + "error-monitoring", + "log", + "logging", + "sentry" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-php/issues", + "source": "https://github.com/getsentry/sentry-php/tree/3.22.1" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2023-11-13T11:47:28+00:00" + }, + { + "name": "sentry/sentry-symfony", + "version": "4.13.2", + "source": { + "type": "git", + "url": "https://github.com/getsentry/sentry-symfony.git", + "reference": "bf049e69863465f2e0ba2555dbb5224641a37d67" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getsentry/sentry-symfony/zipball/bf049e69863465f2e0ba2555dbb5224641a37d67", + "reference": "bf049e69863465f2e0ba2555dbb5224641a37d67", + "shasum": "" + }, + "require": { + "guzzlehttp/psr7": "^1.7 || ^2.0", + "jean85/pretty-package-versions": "^1.5 || ^2.0", + "php": "^7.2||^8.0", + "sentry/sdk": "^3.6", + "sentry/sentry": "^3.22.1", + "symfony/cache-contracts": "^1.1||^2.4||^3.0", + "symfony/config": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/console": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/dependency-injection": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/event-dispatcher": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/http-kernel": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/polyfill-php80": "^1.22", + "symfony/psr-http-message-bridge": "^1.2||^2.0||^6.4||^7.0", + "symfony/security-core": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/security-http": "^4.4.20||^5.0.11||^6.0||^7.0" + }, + "require-dev": { + "doctrine/dbal": "^2.13||^3.0", + "doctrine/doctrine-bundle": "^1.12||^2.5", + "friendsofphp/php-cs-fixer": "^2.19||^3.40", + "masterminds/html5": "^2.8", + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^1.3", + "phpstan/phpstan-phpunit": "^1.0", + "phpstan/phpstan-symfony": "^1.0", + "phpunit/phpunit": "^8.5.14||^9.3.9", + "symfony/browser-kit": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/cache": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/dom-crawler": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/framework-bundle": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/http-client": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/messenger": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/monolog-bundle": "^3.4", + "symfony/phpunit-bridge": "^5.2.6||^6.0||^7.0", + "symfony/process": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/twig-bundle": "^4.4.20||^5.0.11||^6.0||^7.0", + "symfony/yaml": "^4.4.20||^5.0.11||^6.0||^7.0", + "vimeo/psalm": "^4.3||^5.16.0" + }, + "suggest": { + "doctrine/doctrine-bundle": "Allow distributed tracing of database queries using Sentry.", + "monolog/monolog": "Allow sending log messages to Sentry by using the included Monolog handler.", + "symfony/cache": "Allow distributed tracing of cache pools using Sentry.", + "symfony/twig-bundle": "Allow distributed tracing of Twig template rendering using Sentry." + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "releases/3.2.x": "3.2.x-dev", + "releases/2.x": "2.x-dev", + "releases/1.x": "1.x-dev" + } + }, + "autoload": { + "files": [ + "src/aliases.php" + ], + "psr-4": { + "Sentry\\SentryBundle\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "David Cramer", + "email": "dcramer@gmail.com" + }, + { + "name": "Alessandro Lai", + "email": "alessandro.lai85@gmail.com" + } + ], + "description": "Symfony integration for Sentry (http://getsentry.com)", + "homepage": "http://getsentry.com", + "keywords": [ + "errors", + "logging", + "sentry", + "symfony" + ], + "support": { + "issues": "https://github.com/getsentry/sentry-symfony/issues", + "source": "https://github.com/getsentry/sentry-symfony/tree/4.13.2" + }, + "funding": [ + { + "url": "https://sentry.io/", + "type": "custom" + }, + { + "url": "https://sentry.io/pricing/", + "type": "custom" + } + ], + "time": "2024-01-11T14:55:45+00:00" + }, { "name": "symfony/asset", "version": "v7.0.0", @@ -3256,6 +4263,165 @@ ], "time": "2023-12-30T15:41:17+00:00" }, + { + "name": "symfony/monolog-bridge", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bridge.git", + "reference": "4ee9e0b3a4736d5598888444e2f1cd3bf206067c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bridge/zipball/4ee9e0b3a4736d5598888444e2f1cd3bf206067c", + "reference": "4ee9e0b3a4736d5598888444e2f1cd3bf206067c", + "shasum": "" + }, + "require": { + "monolog/monolog": "^3", + "php": ">=8.2", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/console": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/mailer": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/mime": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Monolog\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Monolog with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/monolog-bridge/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-21T15:09:11+00:00" + }, + { + "name": "symfony/monolog-bundle", + "version": "v3.10.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/monolog-bundle.git", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/monolog-bundle/zipball/414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "reference": "414f951743f4aa1fd0f5bf6a0e9c16af3fe7f181", + "shasum": "" + }, + "require": { + "monolog/monolog": "^1.25.1 || ^2.0 || ^3.0", + "php": ">=7.2.5", + "symfony/config": "^5.4 || ^6.0 || ^7.0", + "symfony/dependency-injection": "^5.4 || ^6.0 || ^7.0", + "symfony/http-kernel": "^5.4 || ^6.0 || ^7.0", + "symfony/monolog-bridge": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^6.3 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\MonologBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony MonologBundle", + "homepage": "https://symfony.com", + "keywords": [ + "log", + "logging" + ], + "support": { + "issues": "https://github.com/symfony/monolog-bundle/issues", + "source": "https://github.com/symfony/monolog-bundle/tree/v3.10.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-06T17:08:13+00:00" + }, { "name": "symfony/options-resolver", "version": "v7.0.0", @@ -3323,6 +4489,78 @@ ], "time": "2023-08-08T10:20:21+00:00" }, + { + "name": "symfony/password-hasher", + "version": "v7.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/password-hasher.git", + "reference": "d2da68c2f7a240bd6edf7e96fdc7aca5e7beea66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/password-hasher/zipball/d2da68c2f7a240bd6edf7e96fdc7aca5e7beea66", + "reference": "d2da68c2f7a240bd6edf7e96fdc7aca5e7beea66", + "shasum": "" + }, + "require": { + "php": ">=8.2" + }, + "conflict": { + "symfony/security-core": "<6.4" + }, + "require-dev": { + "symfony/console": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\PasswordHasher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Robin Chalas", + "email": "robin.chalas@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides password hashing utilities", + "homepage": "https://symfony.com", + "keywords": [ + "hashing", + "password" + ], + "support": { + "source": "https://github.com/symfony/password-hasher/tree/v7.0.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-07T10:26:03+00:00" + }, { "name": "symfony/polyfill-intl-grapheme", "version": "v1.28.0", @@ -3897,6 +5135,89 @@ ], "time": "2023-11-25T08:38:27+00:00" }, + { + "name": "symfony/psr-http-message-bridge", + "version": "v7.0.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/psr-http-message-bridge.git", + "reference": "c5e973032e9a32c6f1bfa87d7832853b84cbaf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/c5e973032e9a32c6f1bfa87d7832853b84cbaf22", + "reference": "c5e973032e9a32c6f1bfa87d7832853b84cbaf22", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/http-message": "^1.0|^2.0", + "symfony/http-foundation": "^6.4|^7.0" + }, + "conflict": { + "php-http/discovery": "<1.15", + "symfony/http-kernel": "<6.4" + }, + "require-dev": { + "nyholm/psr7": "^1.1", + "php-http/discovery": "^1.15", + "psr/log": "^1.1.4|^2|^3", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\PsrHttpMessage\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "PSR HTTP message bridge", + "homepage": "https://symfony.com", + "keywords": [ + "http", + "http-message", + "psr-17", + "psr-7" + ], + "support": { + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.0.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-12-28T19:18:20+00:00" + }, { "name": "symfony/routing", "version": "v7.0.2", @@ -4057,6 +5378,176 @@ ], "time": "2023-10-20T16:35:23+00:00" }, + { + "name": "symfony/security-core", + "version": "v7.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-core.git", + "reference": "2ba040de8e6d93e07edc7307dc75b42e06137405" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-core/zipball/2ba040de8e6d93e07edc7307dc75b42e06137405", + "reference": "2ba040de8e6d93e07edc7307dc75b42e06137405", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/event-dispatcher-contracts": "^2.5|^3", + "symfony/password-hasher": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/event-dispatcher": "<6.4", + "symfony/http-foundation": "<6.4", + "symfony/ldap": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "psr/cache": "^1.0|^2.0|^3.0", + "psr/container": "^1.1|^2.0", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/ldap": "^6.4|^7.0", + "symfony/string": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Core\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - Core Library", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-core/tree/v7.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-30T11:04:23+00:00" + }, + { + "name": "symfony/security-http", + "version": "v7.0.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/security-http.git", + "reference": "acc9931d75cd16de08b1663223cb8ab36f61cc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/security-http/zipball/acc9931d75cd16de08b1663223cb8ab36f61cc0c", + "reference": "acc9931d75cd16de08b1663223cb8ab36f61cc0c", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/polyfill-mbstring": "~1.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/clock": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/http-client-contracts": "<3.0", + "symfony/security-bundle": "<6.4", + "symfony/security-csrf": "<6.4" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/clock": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-client-contracts": "^3.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/routing": "^6.4|^7.0", + "symfony/security-csrf": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "web-token/jwt-checker": "^3.1", + "web-token/jwt-signature-algorithm-ecdsa": "^3.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Security\\Http\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Security Component - HTTP Integration", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/security-http/tree/v7.0.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-11-30T11:04:23+00:00" + }, { "name": "symfony/service-contracts", "version": "v3.4.1", diff --git a/config/bundles.php b/config/bundles.php index cd8f9bb..50bcb8c 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -5,4 +5,6 @@ return [ Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], + Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], + Sentry\SentryBundle\SentryBundle::class => ['prod' => true], ]; diff --git a/config/packages/cache.yaml b/config/packages/cache.yaml index 6899b72..23dcfb1 100644 --- a/config/packages/cache.yaml +++ b/config/packages/cache.yaml @@ -1,7 +1,7 @@ framework: cache: # Unique name of your app: used to compute stable namespaces for cache keys. - #prefix_seed: your_vendor_name/app_name + prefix_seed: ovlach/symfony_example_app # The "app" cache stores to the filesystem by default. # The data in this cache should persist between deploys. diff --git a/config/packages/monolog.yaml b/config/packages/monolog.yaml new file mode 100644 index 0000000..955e0e0 --- /dev/null +++ b/config/packages/monolog.yaml @@ -0,0 +1,68 @@ +monolog: + channels: + - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists + +when@dev: + monolog: + handlers: + otel: # Tohle bych v realny aplikaci nepouzil na devu + type: service + id: App\Bridge\Monolog\Handler\SymfonyOtelHandler + + main: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + channels: ["!event"] + # uncomment to get logging in your browser + # you may have to allow bigger header sizes in your Web server configuration + #firephp: + # type: firephp + # level: info + #chromephp: + # type: chromephp + # level: info + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine", "!console"] + +when@test: + monolog: + handlers: + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + channels: ["!event"] + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + +when@prod: + monolog: + handlers: + otel: + type: service + id: App\Bridge\Monolog\Handler\SymfonyOtelHandler + main: + type: fingers_crossed + action_level: error + handler: nested + excluded_http_codes: [404, 405] + buffer_size: 50 # How many messages should be saved? Prevent memory leaks + nested: + type: stream + path: php://stderr + level: debug + formatter: monolog.formatter.json + console: + type: console + process_psr_3_messages: false + channels: ["!event", "!doctrine"] + deprecation: + type: stream + channels: [deprecation] + path: php://stderr diff --git a/config/packages/sentry.yaml b/config/packages/sentry.yaml new file mode 100644 index 0000000..9e4a04e --- /dev/null +++ b/config/packages/sentry.yaml @@ -0,0 +1,26 @@ +when@prod: + sentry: + dsn: '%env(SENTRY_DSN)%' + # this hooks into critical paths of the framework (and vendors) to perform + # automatic instrumentation (there might be some performance penalty) + # https://docs.sentry.io/platforms/php/guides/symfony/performance/instrumentation/automatic-instrumentation/ + tracing: + enabled: false + +# If you are using Monolog, you also need this additional configuration to log the errors correctly: +# https://docs.sentry.io/platforms/php/guides/symfony/#monolog-integration +# register_error_listener: false +# register_error_handler: false + +# monolog: +# handlers: +# sentry: +# type: sentry +# level: !php/const Monolog\Logger::ERROR +# hub_id: Sentry\State\HubInterface + +# Uncomment these lines to register a log message processor that resolves PSR-3 placeholders +# https://docs.sentry.io/platforms/php/guides/symfony/#monolog-integration +# services: +# Monolog\Processor\PsrLogMessageProcessor: +# tags: { name: monolog.processor, handler: sentry } diff --git a/config/services.yaml b/config/services.yaml index c2bc64f..f04a8e2 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -5,7 +5,11 @@ # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration parameters: env(SYMFONY_EXAMPLE_APP_CURRENCIES): '["CZK", "EUR", "USD", "GBP"]' + env(SYMFONY_EXAMPLE_APP_USETRENO_USERNAME): 'developer' + env(SYMFONY_EXAMPLE_APP_USETRENO_PASSWORD): 'developer123' app.currencies: '%env(json:SYMFONY_EXAMPLE_APP_CURRENCIES)%' + app.usetreno.username: '%env(string:SYMFONY_EXAMPLE_APP_USETRENO_USERNAME)%' + app.usetreno.password: '%env(string:SYMFONY_EXAMPLE_APP_USETRENO_PASSWORD)%' services: # default configuration for services in *this* file @@ -31,9 +35,27 @@ services: arguments: $imagePath: '%kernel.project_dir%/assets/images/wip.png' + App\Service\Remote\UsetrenoHttpClient: + arguments: + $username: '%app.usetreno.username%' + $password: '%app.usetreno.password%' + $retryCount: 2 + $retryWaitSeconds: 0.5 + + App\Service\Remote\UsetrenoQRCodeProvider: + arguments: + $retryCount: 2 + $retryWaitSeconds: 0.5 + + App\Service\CachedQRCodeGenerator: + arguments: + $innerGenerator: '@App\Service\Remote\UsetrenoQRCodeProvider' + $cacheDuration: 'PT60S' + + App\Service\CurrencyListerInterface: '@App\Service\StaticCurrencyLister' App\Service\QRCodeQROptionsProviderInterface: '@App\Service\QRCodeQROptionsDefaultProvider' - App\Service\QRCodeGeneratorInterface: '@App\Service\StubQRCodeGenerator' + App\Service\QRCodeGeneratorInterface: '@App\Service\CachedQRCodeGenerator' # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones diff --git a/docker/telemetry/otelcol-config.yaml b/docker/telemetry/otelcol-config.yaml index d087da7..ebbe007 100644 --- a/docker/telemetry/otelcol-config.yaml +++ b/docker/telemetry/otelcol-config.yaml @@ -11,22 +11,40 @@ processors: actions: - action: insert key: loki.attribute.labels - value: log.file, log.line, log.module_path, http.request_id, http.uri + value: context, code.filepath, code.namespace, code.function, code.lineno, http.request.method, http.request.body.size, url.full, url.scheme, url.path, http.route, http.response.status_code - action: insert - from_attribute: log.file - key: log.file + from_attribute: context + key: context - action: insert - from_attribute: log.line - key: log.line + from_attribute: code.namespace + key: code.namespace - action: insert - from_attribute: log.module_path - key: log.module_path + from_attribute: code.function + key: code.function - action: insert - from_attribute: http.request_id - key: http.request_id + from_attribute: code.lineno + key: code.lineno - action: insert - from_attribute: http.uri - key: http.uri + from_attribute: http.request.method + key: http.request.method + - action: insert + from_attribute: http.request.body.size + key: http.request.body.size + - action: insert + from_attribute: http.response.status_code + key: http.response.status_code + - action: insert + from_attribute: url.full + key: url.full + - action: insert + from_attribute: url.scheme + key: url.scheme + - action: insert + from_attribute: url.path + key: url.path + - action: insert + from_attribute: http.route + key: http.route - action: insert key: loki.format value: raw diff --git a/src/Bridge/Monolog/Handler/SymfonyOtelHandler.php b/src/Bridge/Monolog/Handler/SymfonyOtelHandler.php new file mode 100644 index 0000000..c7f4448 --- /dev/null +++ b/src/Bridge/Monolog/Handler/SymfonyOtelHandler.php @@ -0,0 +1,33 @@ +innerHandler = new Handler( + Globals::loggerProvider(), + LogLevel::INFO, //or `Logger::INFO`, or `Level::Info` depending on monolog version + true, + ); + } + + public function handle(LogRecord $record): bool + { + return $this->innerHandler->handle($record); + } +} diff --git a/src/Controller/ExceptionController.php b/src/Controller/ExceptionController.php new file mode 100644 index 0000000..5d86fb0 --- /dev/null +++ b/src/Controller/ExceptionController.php @@ -0,0 +1,17 @@ +generateQRCodeFromEntity($qrCode); + $qrCodeImage = $qrCodeGenerator->generateQRCodeFromEntity($this->codeEntityConverter->convert($qrCode)); $form = $this->createForm(QRCodeType::class, $this->createQrCodeEntity()); } } diff --git a/src/Entity/DTO/QRCode/QRCode.php b/src/Entity/DTO/QRCode/QRCode.php new file mode 100644 index 0000000..e0c6cda --- /dev/null +++ b/src/Entity/DTO/QRCode/QRCode.php @@ -0,0 +1,15 @@ +cacheDuration = new \DateInterval($cacheDuration); + } + + public function generateQRCodeFromEntity(QRCode $entity): string + { + $key = $this->innerGenerator->getCacheKey($entity); + return $this->cache->get($key, function(ItemInterface $c) use ($entity) { + $this->logger->debug("cache miss for key " . $c->getKey(), [ + 'entity' => $entity, + ]); + + $c->expiresAfter($this->cacheDuration); + + return $this->innerGenerator->generateQRCodeFromEntity($entity); + }); + } +} diff --git a/src/Service/DTO/QRCodeEntityConverter.php b/src/Service/DTO/QRCodeEntityConverter.php new file mode 100644 index 0000000..f1a2b4d --- /dev/null +++ b/src/Service/DTO/QRCodeEntityConverter.php @@ -0,0 +1,58 @@ +validator->validate($code)) !== 0) { + throw new \InvalidArgumentException("QRCode entity is not valid"); + } + + return new QRCode( + $code->getIban() ?? throw new \InvalidArgumentException("iban not set"), + \DateTimeImmutable::createFromMutable($code->getDueDate() ?? throw new \InvalidArgumentException("due date not set")), + $code->getMessage() ?? throw new \InvalidArgumentException("message not set"), + $this->convertMoney($code->getMoney() ?? throw new \InvalidArgumentException("money not set")), + $this->convertCodeOptions($code->getCodeQROptions() ?? throw new \InvalidArgumentException("codeQROptions not set")), + $this->convertIdentification($code->getPaymentIdentification()) + ); + } + + protected function convertMoney(\App\Entity\Input\QRCode\QRCodeMoney $money): QRCodeMoney + { + return new QRCodeMoney( + $money->getAmount() ?? throw new \InvalidArgumentException("amount not set"), + $money->getCurrency() ?? throw new \InvalidArgumentException("currency not set") + ); + } + + protected function convertCodeOptions(\App\Entity\Input\QRCode\QRCodeQROptions $options): QRCodeQROptions + { + return new QRCodeQROptions( + $options->getScale(), + $options->getMargin() + ); + } + + protected function convertIdentification(?\App\Entity\Input\QRCode\QRCodePaymentIdentification $ident): ?QRCodePaymentIdentification { + return match ($ident) { + null => null, + default => new QRCodePaymentIdentification( + $ident->getVariableSymbol(), + $ident->getSpecificSymbol(), + $ident->getConstantSymbol() + ) + }; + } +} diff --git a/src/Service/Exception/QRCodeGeneratorException.php b/src/Service/Exception/QRCodeGeneratorException.php new file mode 100644 index 0000000..ab2fd68 --- /dev/null +++ b/src/Service/Exception/QRCodeGeneratorException.php @@ -0,0 +1,9 @@ +qrCodeDefaultOptions = new QRCodeQROptions( - 1, - 0 + self::DEFAULT_SCALE, + self::DEFAULT_MARGIN ); } diff --git a/src/Service/QRCodeQROptionsProviderInterface.php b/src/Service/QRCodeQROptionsProviderInterface.php index f830c86..c9445dd 100644 --- a/src/Service/QRCodeQROptionsProviderInterface.php +++ b/src/Service/QRCodeQROptionsProviderInterface.php @@ -3,7 +3,7 @@ declare(strict_types=1); namespace App\Service; -use App\Entity\QRCode\QRCodeQROptions; +use App\Entity\Input\QRCode\QRCodeQROptions; interface QRCodeQROptionsProviderInterface { public function getDefault(): QRCodeQROptions; diff --git a/src/Service/Remote/Edge/Exceptions/MissingParameterException.php b/src/Service/Remote/Edge/Exceptions/MissingParameterException.php new file mode 100644 index 0000000..d4e2063 --- /dev/null +++ b/src/Service/Remote/Edge/Exceptions/MissingParameterException.php @@ -0,0 +1,9 @@ +iban, + $code->dueDate->format('Y-m-d'), + $code->message, + $this->convertMoney($code->money), + $this->convertCodeOptions($code->qrOptions), + $this->convertIdentification($code->paymentIdentification) + ); + } + + protected function convertMoney(QRCodeMoney $money): EdgeQRCodeMoney + { + return new EdgeQRCodeMoney( + $money->amount, + $money->currency + ); + } + + protected function convertCodeOptions(QRCodeQROptions $options): EdgeQRCodeQROptions + { + return new EdgeQRCodeQROptions( + $options->scale, + $options->margin + ); + } + + protected function convertIdentification(?QRCodePaymentIdentification $ident): EdgeQRCodePaymentIdentification { + // OMG: proc? symboly v ostatnich statech nejsou, prijde me lepsi posilat NULL, nebo empty string ;-) + return match ($ident) { + null => new EdgeQRCodePaymentIdentification( + "0", + "0", + "0" + ), + default => new EdgeQRCodePaymentIdentification( + $ident->variableSymbol ?? "0", + $ident->specificSymbol ?? "0", + $ident->constantSymbol ?? "0" + ) + }; + } +} diff --git a/src/Service/Remote/Exception/AuthorizeException.php b/src/Service/Remote/Exception/AuthorizeException.php new file mode 100644 index 0000000..08e29ff --- /dev/null +++ b/src/Service/Remote/Exception/AuthorizeException.php @@ -0,0 +1,8 @@ + $catchableExceptions + * @param LoggerInterface $logger + * @param callable $callback + * @return mixed + * @throws Exception + */ + protected function retryingFailRequest( + int $count, + float $sleep, + array $catchableExceptions, + LoggerInterface $logger, + callable $callback + ): mixed { + for ($i = 0; ; $i++) { + try { + return $callback(); + } catch (Exception $e) { + foreach ($catchableExceptions as $exceptionClass) { + if ($e instanceof $exceptionClass) { + $logger->error("transport: fail request retrying... got catchable exception", [ + 'exception' => $e, + 'try' => $i + ]); + + usleep((int) ($sleep * 1_000_000)); + + if ($i == $count) { + throw $e; + } + + continue 2; + } + } + + throw $e; + } + } + + // phpstan fail + return null; + } +} diff --git a/src/Service/Remote/UsetrenoHttpClient.php b/src/Service/Remote/UsetrenoHttpClient.php new file mode 100644 index 0000000..fb9842e --- /dev/null +++ b/src/Service/Remote/UsetrenoHttpClient.php @@ -0,0 +1,160 @@ + $options + * @return ResponseInterface + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function request(string $method, string $url, array $options = []): ResponseInterface { + if ($this->authorizationToken === null) { + $this->authorize(); + } + + return $this->innerClient->request($method, $url, $options); + } + + /** + * @param ResponseInterface|iterable $responses One or more responses created by the current HTTP client + * @param float|null $timeout + * @return ResponseStreamInterface + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function stream(iterable|ResponseInterface $responses, float $timeout = null): ResponseStreamInterface + { + if ($this->authorizationToken === null) { + $this->authorize(); + } + + return $this->innerClient->stream($responses, $timeout); + } + + /** + * @param array $options + * @return static(UsetrenoHttpClient) + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + * @throws TransportExceptionInterface + */ + public function withOptions(array $options): static + { + if ($this->authorizationToken === null) { + $this->authorize(); + } + + $this->innerClient = $this->innerClient->withOptions($options); + return $this; + } + + /** + * @throws TransportExceptionInterface + * @throws ClientExceptionInterface + * @throws RedirectionExceptionInterface + * @throws ServerExceptionInterface + */ + protected function authorize(): void { + $this->logger->debug("trying authorize request", [ + "AUTHORIZE_API" => static::AUTHORIZE_API + ]); + + $responseData = $this->retryingFailRequest($this->retryCount, $this->retryWaitSeconds, + [AuthorizeException::class, TransportException::class], $this->logger, function() { + $rq = $this->innerClient->request( + "POST", + static::AUTHORIZE_API, + [ + 'json' => new AuthRequest($this->username, $this->password), + ] + ); + + $statusCode = $rq->getStatusCode(); + $responseData = $rq->getContent(false); + + if ($statusCode != 200) { + $this->logger->error("authorization request status code is not ok", [ + "AUTHORIZE_API" => static::AUTHORIZE_API, + "statusCode" => $statusCode, + "content" => $responseData, + "username" => $this->username + ]); + + throw new AuthorizeException("Return code is not 200 OK (got: code: $statusCode)"); + } + + return $responseData; + }); + + if (!is_string($responseData)) { + throw new ThisShouldNeverHappenException("responseData is not a string"); + } + + $this->authorizationToken = $this->processAuthorizeResponse($responseData); + $this->innerClient = $this->innerClient->withOptions([ + 'headers' => [ + 'Authorization' => "Bearer " . $this->authorizationToken + ] + ]); + } + + /** + * @param string $responseData + * @return string Bearer token + * @throws AuthorizeException + */ + protected function processAuthorizeResponse(string $responseData): string { + $data = json_decode($responseData); + + if (!is_object($data)) { + $this->logger->error("authorize: received null response data", [ + "responseData" => $responseData + ]); + throw new AuthorizeException("Can't decode json response"); + } + + if (!isset($data->data->token)) { + $this->logger->error("authorize: empty token in response data", [ + "responseData" => $responseData + ]); + throw new AuthorizeException("Got invalid json response"); + } + + return $data->data->token; + } +} diff --git a/src/Service/Remote/UsetrenoQRCodeProvider.php b/src/Service/Remote/UsetrenoQRCodeProvider.php new file mode 100644 index 0000000..c824e32 --- /dev/null +++ b/src/Service/Remote/UsetrenoQRCodeProvider.php @@ -0,0 +1,119 @@ +codeEntityConverter->convert($entity); + + $this->logger->debug("Sending request for QR code", [ + "entity" => $entity, + "edgeEntity" => $edgeEntity + ]); + + $responseData = $this->retryingFailRequest($this->retryCount, $this->retryWaitSeconds, + [TransportException::class, UsetrenoQRCodeRemoteServerErrorException::class], $this->logger, + function() use ($edgeEntity) { + $response = $this->client->request(static::QRCODE_METHOD, static::QRCODE_API, [ + 'json' => $edgeEntity, + ]); + + $statusCode = $response->getStatusCode(); + $responseData = $response->getContent(false); + + if ($statusCode != 200) { + $this->logger->error("qrcode request status code is not ok", [ + "AUTHORIZE_API" => static::QRCODE_API, + "statusCode" => $statusCode, + "content" => $responseData, + ]); + + if ($statusCode > 500) { + throw new UsetrenoQRCodeRemoteServerErrorException("Return code is not 200 OK (got: code: $statusCode)"); + } + + throw new UsetrenoQRCodeException("Return code is not 200 OK (got: code: $statusCode)"); + } + + $this->logger->debug("QRCode generation success", [ + "responseContent" => $responseData, + ]); + + return $responseData; + }); + + if (!is_string($responseData)) { + throw new ThisShouldNeverHappenException("responseData is not a string"); + } + + return $this->parseBase64String($this->processQRCodeResponseEntity($responseData)); + } + + protected function parseBase64String(string $content): string { + $data = explode(';base64,', $content); + // check if response is image (we support png only now) + if ($data[0] === "image/png") { + throw new UsetrenoQRCodeException("Unsupported image type $data[0]"); + } + + return $data[1]; + } + + /** + * @param string $responseData + * @return string Bearer token + * @throws UsetrenoQRCodeException + */ + protected function processQRCodeResponseEntity(string $responseData): string { + $data = json_decode($responseData); + + if (!is_object($data)) { + $this->logger->error("qrcode: received null response data", [ + "responseData" => $responseData + ]); + throw new UsetrenoQRCodeException("Can't decode json response"); + } + + if (!isset($data->data->base64Data)) { + $this->logger->error("qrcode: empty base64Data in response data", [ + "responseData" => $responseData + ]); + throw new UsetrenoQRCodeException("Got invalid json response"); + } + + return $data->data->base64Data; + } + + public function getCacheKey(QRCode $entity): string + { + $edgeEntity = $this->codeEntityConverter->convert($entity); + $encodedEntity = json_encode($edgeEntity); + if ($encodedEntity === false) { + throw new \RuntimeException("Can't serialize edge entity"); + } + return base64_encode($encodedEntity); + } +} diff --git a/src/Service/StubQRCodeGenerator.php b/src/Service/StubQRCodeGenerator.php index 8dfc664..1279b00 100644 --- a/src/Service/StubQRCodeGenerator.php +++ b/src/Service/StubQRCodeGenerator.php @@ -3,7 +3,7 @@ declare(strict_types=1); namespace App\Service; -use App\Entity\QRCode\QRCode; +use App\Entity\DTO\QRCode\QRCode; readonly final class StubQRCodeGenerator implements QRCodeGeneratorInterface { diff --git a/src/Validator/BankPaymentIdentificationNumberValidator.php b/src/Validator/BankPaymentIdentificationNumberValidator.php index 3459da2..122b065 100644 --- a/src/Validator/BankPaymentIdentificationNumberValidator.php +++ b/src/Validator/BankPaymentIdentificationNumberValidator.php @@ -24,7 +24,7 @@ class BankPaymentIdentificationNumberValidator extends ConstraintValidator throw new UnexpectedValueException($value, "string"); } - if (strlen($value) <= 10 && filter_var($value, FILTER_VALIDATE_INT) !== false) { + if (strlen($value) <= 10 && filter_var($value, FILTER_VALIDATE_INT) !== false && (int) $value > 0) { return ; } diff --git a/symfony.lock b/symfony.lock index 02e7380..4016eb9 100644 --- a/symfony.lock +++ b/symfony.lock @@ -49,6 +49,18 @@ "tests/bootstrap.php" ] }, + "sentry/sentry-symfony": { + "version": "4.13", + "recipe": { + "repo": "github.com/symfony/recipes-contrib", + "branch": "main", + "version": "4.6", + "ref": "153de5f041f7e8a9c19f3674b800b76be0e6fd90" + }, + "files": [ + "config/packages/sentry.yaml" + ] + }, "symfony/asset-mapper": { "version": "7.0", "recipe": { @@ -107,6 +119,18 @@ "src/Kernel.php" ] }, + "symfony/monolog-bundle": { + "version": "3.10", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.7", + "ref": "213676c4ec929f046dfde5ea8e97625b81bc0578" + }, + "files": [ + "config/packages/monolog.yaml" + ] + }, "symfony/phpunit-bridge": { "version": "7.0", "recipe": { diff --git a/tests/Common/LoggerTrait.php b/tests/Common/LoggerTrait.php new file mode 100644 index 0000000..b625754 --- /dev/null +++ b/tests/Common/LoggerTrait.php @@ -0,0 +1,18 @@ +pushHandler(new TestHandler()); + return $logger; + } +} diff --git a/tests/Service/Remote/RetryingFailClientTraitTest.php b/tests/Service/Remote/RetryingFailClientTraitTest.php new file mode 100644 index 0000000..e945864 --- /dev/null +++ b/tests/Service/Remote/RetryingFailClientTraitTest.php @@ -0,0 +1,56 @@ +callCount = 0; + } + + public function testSuccess() { + $trait = new class { + use RetryingFailClientTrait { + retryingFailRequest as public; // make the method public + } + }; + + + $result = $trait->retryingFailRequest(2, 0, [\RuntimeException::class], $this->getLogger(), function () { + $this->callCount = $this->callCount + 1; + return 'foo'; + }); + + $this->assertEquals(1, $this->callCount); + $this->assertEquals('foo', $result); + } + public function testRetyingFail() { + $trait = new class { + use RetryingFailClientTrait { + retryingFailRequest as public; // make the method public + } + }; + + + try { + $trait->retryingFailRequest(2, 0, [\RuntimeException::class], $this->getLogger(), function () { + $this->callCount = $this->callCount + 1; + throw new \RuntimeException("test"); + }); + } catch (\RuntimeException) { + // do nothing + } + + $this->assertEquals(3, $this->callCount); + } +} diff --git a/tests/Service/Remote/UsetrenoHttpClientTest.php b/tests/Service/Remote/UsetrenoHttpClientTest.php new file mode 100644 index 0000000..af7845d --- /dev/null +++ b/tests/Service/Remote/UsetrenoHttpClientTest.php @@ -0,0 +1,54 @@ + [ + "expires_in" => 1000, + "token" => "foobarfoobar" + ], + "timestamp" => "2024-01-17T22:47:59+01:00", + "uri" => "/api/v1/token" + ]); + $authorizedRequestResponse = clone $authMockResponse; + $mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]); + $client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar", 0, 0); + $client->request("POST", "https://www.root.cz/"); + $this->assertEquals("https://topapi.top-test.cz/chameleon/api/v1/token", $authMockResponse->getRequestUrl()); + $headers = $authorizedRequestResponse->getRequestOptions()['headers']; + $this->assertEquals("https://www.root.cz/", $authorizedRequestResponse->getRequestUrl()); + $this->assertTrue(in_array("Authorization: Bearer foobarfoobar", $headers), "missing bearer authorization header"); + } + + public function testRequestFailedAuthorization() { + $this->expectException(AuthorizeException::class); + $authMockResponse = new JsonMockResponse([ + "errors" => [ + [ + "message" => "Bad credentials, please verify that your username/password are correctly set.", + "specificType" => "bad_credentials", + "type" => "security_error" + ] + ], + "timestamp" => "2024-01-17T23:55:25+01:00", + "uri" => "/api/v1/token" + ]); + + $authorizedRequestResponse = clone $authMockResponse; + $mockedClient = new MockHttpClient([$authMockResponse, $authorizedRequestResponse]); + $client = new UsetrenoHttpClient($mockedClient, $this->getLogger(), "foo", "bar", 0, 0); + $client->request("POST", "https://www.root.cz/"); + } +} diff --git a/tests/Service/Remote/UsetrenoQRCodeProviderTest.php b/tests/Service/Remote/UsetrenoQRCodeProviderTest.php new file mode 100644 index 0000000..d5d7e12 --- /dev/null +++ b/tests/Service/Remote/UsetrenoQRCodeProviderTest.php @@ -0,0 +1,118 @@ + "/api/v1/qr-code/create-for-bank-account-payment", + "timestamp" => "2024-01-16T14:05:42+01:00", + "data" => [ + "base64Data" => "data:image/png;base64,$base64Image" + ] + ]; + + [$edgeEntity, $qrCodeEntity] = $this->createQRCodeEntityPair(); + + $qrCodeProvider = $this->createQRCodeProvider($successRequest, 200, $edgeEntity, $qrCodeEntity); + $data = $qrCodeProvider->generateQRCodeFromEntity($qrCodeEntity); + $this->assertEquals($base64Image, $data); + } + + public function testFailureRequest() { + $this->expectException(QRCodeGeneratorException::class); + + $failureRequest = [ + "error" => "internal server error", + ]; + + [$edgeEntity, $qrCodeEntity] = $this->createQRCodeEntityPair(); + + $qrCodeProvider = $this->createQRCodeProvider($failureRequest, 500, $edgeEntity, $qrCodeEntity); + $data = $qrCodeProvider->generateQRCodeFromEntity($qrCodeEntity); + $this->assertEquals($failureRequest["data"]["base64Data"], $data); + } + + public function createQRCodeProvider(array $response, int $responseCode, EdgeQRCode $edgeEntity, QRCode $entity): UsetrenoQRCodeProvider + { + $responseMock = $this->createMock(ResponseInterface::class); + + $responseMock->expects($this->any()) + ->method('getContent') + ->will($this->returnValue(json_encode($response))); + $responseMock->expects($this->any()) + ->method('getStatusCode') + ->will($this->returnValue($responseCode)); + + $mock = $this->createMock(UsetrenoHttpClient::class); + $mock->expects($this->once()) + ->method('request') + ->will($this->returnValue($responseMock)); + + $converterMock = $this->createMock(QRCodeEntityConverter::class); + $converterMock->expects($this->once()) + ->method('convert') + ->will($this->returnValue($edgeEntity)); + + return new UsetrenoQRCodeProvider($this->getLogger(), $mock, $converterMock, 0, 0); + } + + protected function createQRCodeEntityPair() { + $edgeEntity = new EdgeQRCode( + "CZ0000", + (new \DateTime("now"))->format('Y-m-d'), + "foo", + new EdgeQRCodeMoney( + 100, + "CZK" + ), + new EdgeQRCodeQROptions( + 1, + 1 + ), + new EdgeQRCodePaymentIdentification( + "0", "0", "0" + ) + ); + + $qrCodeEntity = new QRCode( + "CZ0000", + new \DateTimeImmutable("now"), + "foo", + new QRCodeMoney( + 100, + "CZK" + ), + new QRCodeQROptions( + 1, + 1 + ), + new QRCodePaymentIdentification( + "0", "0", "0" + ) + ); + + return [$edgeEntity, $qrCodeEntity]; + } +} diff --git a/tests/Validator/BankPaymentIdentificationNumberValidatorTest.php b/tests/Validator/BankPaymentIdentificationNumberValidatorTest.php index d9e08e9..d33db73 100644 --- a/tests/Validator/BankPaymentIdentificationNumberValidatorTest.php +++ b/tests/Validator/BankPaymentIdentificationNumberValidatorTest.php @@ -35,6 +35,6 @@ class BankPaymentIdentificationNumberValidatorTest extends ValidatorTestCase } private function failureDp(): array { - return [['122.b'], ['a.a'], ['a'], ['2.040'], ['2,a'], ['122.1'], ['12345678901']]; + return [['122.b'], ['a.a'], ['a'], ['2.040'], ['2,a'], ['122.1'], ['12345678901'], ['-323']]; } } diff --git a/tests/Validator/MoneyValidatorTest.php b/tests/Validator/MoneyValidatorTest.php index a234620..8a869cd 100644 --- a/tests/Validator/MoneyValidatorTest.php +++ b/tests/Validator/MoneyValidatorTest.php @@ -35,6 +35,6 @@ class MoneyValidatorTest extends ValidatorTestCase } private function failureDp(): array { - return [['122.b'], ['a.a'], ['a'], ['2.040'], ['2,a']]; + return [['122.b'], ['a.a'], ['a'], ['2.040'], ['2,a'], ['-223']]; } }