diff --git a/.travis.yml b/.travis.yml index 3bd5cf7..fd397c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,7 @@ php: - '7.0' - '7.1' - '7.2' + - '7.3' before_script: - composer update -o diff --git a/bin/run-php-stan.sh b/bin/run-php-stan.sh new file mode 100755 index 0000000..6d9190a --- /dev/null +++ b/bin/run-php-stan.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +vagrant ssh -- -t 'cd /vagrant; php vendor/bin/phpstan analyze --level 1 -- src/' diff --git a/composer.json b/composer.json index 6a66473..e8adb0b 100644 --- a/composer.json +++ b/composer.json @@ -31,9 +31,10 @@ }, "require-dev": { "monolog/monolog": "~1.17", - "phpunit/phpunit": "@stable", + "phpunit/phpunit": "^6.0", "phpmd/phpmd": "@stable", "phpdocumentor/phpdocumentor": "2.*", - "squizlabs/php_codesniffer": "^3.0" + "squizlabs/php_codesniffer": "^3.0", + "phpstan/phpstan": "^0.8" } } diff --git a/composer.lock b/composer.lock index 8f8f8ae..6297f26 100644 --- a/composer.lock +++ b/composer.lock @@ -4,20 +4,20 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "a38fb47acb9250e34dd86d3dee144d5a", + "content-hash": "b21ebe2466e014806c9834fa55f69f4a", "packages": [ { "name": "psr/log", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", - "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", "shasum": "" }, "require": { @@ -51,7 +51,7 @@ "psr", "psr-3" ], - "time": "2016-10-10T12:19:37+00:00" + "time": "2018-11-20T15:27:04+00:00" }, { "name": "unreal4u/dummy-logger", @@ -243,30 +243,30 @@ }, { "name": "doctrine/annotations", - "version": "v1.4.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97" + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/54cacc9b81758b14e3ce750f205a393d52339e97", - "reference": "54cacc9b81758b14e3ce750f205a393d52339e97", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", "shasum": "" }, "require": { "doctrine/lexer": "1.*", - "php": "^5.6 || ^7.0" + "php": "^7.1" }, "require-dev": { "doctrine/cache": "1.*", - "phpunit/phpunit": "^5.7" + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { @@ -307,36 +307,38 @@ "docblock", "parser" ], - "time": "2017-02-24T16:22:25+00:00" + "time": "2019-03-25T19:12:02+00:00" }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -356,12 +358,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "doctrine/lexer", @@ -419,16 +421,16 @@ }, { "name": "erusev/parsedown", - "version": "1.7.1", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1" + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7", + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7", "shasum": "" }, "require": { @@ -461,7 +463,7 @@ "markdown", "parser" ], - "time": "2018-03-08T01:11:30+00:00" + "time": "2019-03-17T18:48:37+00:00" }, { "name": "herrera-io/json", @@ -582,18 +584,69 @@ "abandoned": true, "time": "2013-10-30T17:23:01+00:00" }, + { + "name": "jean85/pretty-package-versions", + "version": "1.2", + "source": { + "type": "git", + "url": "https://github.com/Jean85/pretty-package-versions.git", + "reference": "75c7effcf3f77501d0e0caa75111aff4daa0dd48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/75c7effcf3f77501d0e0caa75111aff4daa0dd48", + "reference": "75c7effcf3f77501d0e0caa75111aff4daa0dd48", + "shasum": "" + }, + "require": { + "ocramius/package-versions": "^1.2.0", + "php": "^7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.0" + }, + "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 wrapper for ocramius/package-versions to get pretty versions strings", + "keywords": [ + "composer", + "package", + "release", + "versions" + ], + "time": "2018-06-13T13:22:40+00:00" + }, { "name": "jms/metadata", - "version": "1.6.0", + "version": "1.7.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/metadata.git", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab" + "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/6a06970a10e0a532fb52d3959547123b84a3b3ab", - "reference": "6a06970a10e0a532fb52d3959547123b84a3b3ab", + "url": "https://api.github.com/repos/schmittjoh/metadata/zipball/e5854ab1aa643623dc64adde718a8eec32b957a8", + "reference": "e5854ab1aa643623dc64adde718a8eec32b957a8", "shasum": "" }, "require": { @@ -616,9 +669,13 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + }, { "name": "Johannes M. Schmitt", "email": "schmittjoh@gmail.com" @@ -631,7 +688,7 @@ "xml", "yaml" ], - "time": "2016-12-05T10:18:33+00:00" + "time": "2018-10-26T12:40:10+00:00" }, { "name": "jms/parser-lib", @@ -670,56 +727,44 @@ }, { "name": "jms/serializer", - "version": "1.12.0", + "version": "0.16.0", "source": { "type": "git", "url": "https://github.com/schmittjoh/serializer.git", - "reference": "1ea5e0ba68b6b38c327eb3adf5888ac74b587e9c" + "reference": "c8a171357ca92b6706e395c757f334902d430ea9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/1ea5e0ba68b6b38c327eb3adf5888ac74b587e9c", - "reference": "1ea5e0ba68b6b38c327eb3adf5888ac74b587e9c", + "url": "https://api.github.com/repos/schmittjoh/serializer/zipball/c8a171357ca92b6706e395c757f334902d430ea9", + "reference": "c8a171357ca92b6706e395c757f334902d430ea9", "shasum": "" }, "require": { - "doctrine/annotations": "^1.0", - "doctrine/instantiator": "^1.0.3", + "doctrine/annotations": "1.*", "jms/metadata": "~1.1", "jms/parser-lib": "1.*", - "php": "^5.5|^7.0", - "phpcollection/phpcollection": "~0.1", - "phpoption/phpoption": "^1.1" - }, - "conflict": { - "twig/twig": "<1.12" + "php": ">=5.3.2", + "phpcollection/phpcollection": "~0.1" }, "require-dev": { "doctrine/orm": "~2.1", - "doctrine/phpcr-odm": "^1.3|^2.0", - "ext-pdo_sqlite": "*", - "jackalope/jackalope-doctrine-dbal": "^1.1.5", - "phpunit/phpunit": "^4.8|^5.0", + "doctrine/phpcr-odm": "~1.0.1", + "jackalope/jackalope-doctrine-dbal": "1.0.*", "propel/propel1": "~1.7", - "psr/container": "^1.0", - "symfony/dependency-injection": "^2.7|^3.3|^4.0", - "symfony/expression-language": "^2.6|^3.0", - "symfony/filesystem": "^2.1", - "symfony/form": "~2.1|^3.0", - "symfony/translation": "^2.1|^3.0", - "symfony/validator": "^2.2|^3.0", - "symfony/yaml": "^2.1|^3.0", - "twig/twig": "~1.12|~2.0" + "symfony/filesystem": "2.*", + "symfony/form": "~2.1", + "symfony/translation": "~2.0", + "symfony/validator": "~2.0", + "symfony/yaml": "2.*", + "twig/twig": ">=1.8,<2.0-dev" }, "suggest": { - "doctrine/cache": "Required if you like to use cache functionality.", - "doctrine/collections": "Required if you like to use doctrine collection types as ArrayCollection.", "symfony/yaml": "Required if you'd like to serialize data to YAML format." }, "type": "library", "extra": { "branch-alias": { - "dev-1.x": "1.11-dev" + "dev-master": "0.15-dev" } }, "autoload": { @@ -729,16 +774,14 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" + "Apache2" ], "authors": [ { - "name": "Asmir Mustafic", - "email": "goetas@gmail.com" - }, - { - "name": "Johannes M. Schmitt", - "email": "schmittjoh@gmail.com" + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com", + "homepage": "https://github.com/schmittjoh", + "role": "Developer of wrapped JMSSerializerBundle" } ], "description": "Library for (de-)serializing data of any complexity; supports XML, JSON, and YAML.", @@ -750,7 +793,7 @@ "serialization", "xml" ], - "time": "2018-05-25T17:01:33+00:00" + "time": "2014-03-18T08:39:00+00:00" }, { "name": "justinrainbow/json-schema", @@ -863,16 +906,16 @@ }, { "name": "monolog/monolog", - "version": "1.23.0", + "version": "1.24.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", - "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", "shasum": "" }, "require": { @@ -937,29 +980,34 @@ "logging", "psr-3" ], - "time": "2017-06-19T01:22:40+00:00" + "time": "2018-11-05T09:00:11+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "78af75148f9fdd34ea727c8b529a9b4a8f7b740c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/78af75148f9fdd34ea727c8b529a9b4a8f7b740c", + "reference": "78af75148f9fdd34ea727c8b529a9b4a8f7b740c", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.2" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpstan/phpstan": "^0.9.2", + "phpstan/phpstan-phpunit": "^0.9.4", + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -982,38 +1030,567 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "time": "2018-10-30T00:14:44+00:00" }, { - "name": "nikic/php-parser", - "version": "v1.4.1", + "name": "nette/bootstrap", + "version": "v3.0.0", "source": { "type": "git", - "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51" + "url": "https://github.com/nette/bootstrap.git", + "reference": "e1075af05c211915e03e0c86542f3ba5433df4a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/bootstrap/zipball/e1075af05c211915e03e0c86542f3ba5433df4a3", + "reference": "e1075af05c211915e03e0c86542f3ba5433df4a3", + "shasum": "" + }, + "require": { + "nette/di": "^3.0", + "nette/utils": "^3.0", + "php": ">=7.1" + }, + "require-dev": { + "latte/latte": "^2.2", + "nette/application": "^3.0", + "nette/caching": "^3.0", + "nette/database": "^3.0", + "nette/forms": "^3.0", + "nette/http": "^3.0", + "nette/mail": "^3.0", + "nette/robot-loader": "^3.0", + "nette/safe-stream": "^2.2", + "nette/security": "^3.0", + "nette/tester": "^2.0", + "tracy/tracy": "^2.6" + }, + "suggest": { + "nette/robot-loader": "to use Configurator::createRobotLoader()", + "tracy/tracy": "to use Configurator::enableTracy()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🅱 Nette Bootstrap: the simple way to configure and bootstrap your Nette application.", + "homepage": "https://nette.org", + "keywords": [ + "bootstrapping", + "configurator", + "nette" + ], + "time": "2019-03-26T12:59:07+00:00" + }, + { + "name": "nette/di", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/nette/di.git", + "reference": "19d83539245aaacb59470828919182411061841f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", - "reference": "f78af2c9c86107aa1a34cd1dbb5bbe9eeb0d9f51", + "url": "https://api.github.com/repos/nette/di/zipball/19d83539245aaacb59470828919182411061841f", + "reference": "19d83539245aaacb59470828919182411061841f", "shasum": "" }, "require": { "ext-tokenizer": "*", - "php": ">=5.3" + "nette/neon": "^3.0", + "nette/php-generator": "^3.2.2", + "nette/robot-loader": "^3.2", + "nette/schema": "^1.0", + "nette/utils": "^3.0", + "php": ">=7.1" + }, + "conflict": { + "nette/bootstrap": "<3.0" + }, + "require-dev": { + "nette/tester": "^2.2", + "tracy/tracy": "^2.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "3.0-dev" } }, "autoload": { + "classmap": [ + "src/" + ], "files": [ - "lib/bootstrap.php" + "src/compatibility.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "💎 Nette Dependency Injection Container: Flexible, compiled and full-featured DIC with perfectly usable autowiring and support for all new PHP 7.1 features.", + "homepage": "https://nette.org", + "keywords": [ + "compiled", + "di", + "dic", + "factory", + "ioc", + "nette", + "static" + ], + "time": "2019-04-03T19:35:46+00:00" + }, + { + "name": "nette/finder", + "version": "v2.5.0", + "source": { + "type": "git", + "url": "https://github.com/nette/finder.git", + "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/finder/zipball/6be1b83ea68ac558aff189d640abe242e0306fe2", + "reference": "6be1b83ea68ac558aff189d640abe242e0306fe2", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4 || ~3.0.0", + "php": ">=7.1" + }, + "conflict": { + "nette/nette": "<2.2" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "? Nette Finder: find files and directories with an intuitive API.", + "homepage": "https://nette.org", + "keywords": [ + "filesystem", + "glob", + "iterator", + "nette" + ], + "time": "2019-02-28T18:13:25+00:00" + }, + { + "name": "nette/neon", + "version": "v3.0.0", + "source": { + "type": "git", + "url": "https://github.com/nette/neon.git", + "reference": "cbff32059cbdd8720deccf9e9eace6ee516f02eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/neon/zipball/cbff32059cbdd8720deccf9e9eace6ee516f02eb", + "reference": "cbff32059cbdd8720deccf9e9eace6ee516f02eb", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "ext-json": "*", + "php": ">=7.0" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "? Nette NEON: encodes and decodes NEON file format.", + "homepage": "http://ne-on.org", + "keywords": [ + "export", + "import", + "neon", + "nette", + "yaml" + ], + "time": "2019-02-05T21:30:40+00:00" + }, + { + "name": "nette/php-generator", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "acff8b136fad84b860a626d133e791f95781f9f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/acff8b136fad84b860a626d133e791f95781f9f5", + "reference": "acff8b136fad84b860a626d133e791f95781f9f5", + "shasum": "" + }, + "require": { + "nette/utils": "^2.4.2 || ~3.0.0", + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.3 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "time": "2019-03-15T03:41:13+00:00" + }, + { + "name": "nette/robot-loader", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/nette/robot-loader.git", + "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/robot-loader/zipball/0712a0e39ae7956d6a94c0ab6ad41aa842544b5c", + "reference": "0712a0e39ae7956d6a94c0ab6ad41aa842544b5c", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "nette/finder": "^2.5", + "nette/utils": "^3.0", + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "^2.0", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "? Nette RobotLoader: high performance and comfortable autoloader that will search and autoload classes within your application.", + "homepage": "https://nette.org", + "keywords": [ + "autoload", + "class", + "interface", + "nette", + "trait" + ], + "time": "2019-03-08T21:57:24+00:00" + }, + { + "name": "nette/schema", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/nette/schema.git", + "reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/schema/zipball/6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d", + "reference": "6241d8d4da39e825dd6cb5bfbe4242912f4d7e4d", + "shasum": "" + }, + "require": { + "nette/utils": "^3.0.1", + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "^2.2", + "tracy/tracy": "^2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "📐 Nette Schema: validating data structures against a given Schema.", + "homepage": "https://nette.org", + "keywords": [ + "config", + "nette" + ], + "time": "2019-04-03T15:53:25+00:00" + }, + { + "name": "nette/utils", + "version": "v3.0.1", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "bd961f49b211997202bda1d0fbc410905be370d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/bd961f49b211997202bda1d0fbc410905be370d4", + "reference": "bd961f49b211997202bda1d0fbc410905be370d4", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize() and toAscii()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" ] }, "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0", + "GPL-3.0" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "time": "2019-03-22T01:00:30+00:00" + }, + { + "name": "nikic/php-parser", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "reference": "bb87e28e7d7b8d9a7fda231d37457c9210faf6ce", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "~4.0|~5.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", "license": [ "BSD-3-Clause" ], @@ -1027,7 +1604,57 @@ "parser", "php" ], - "time": "2015-09-19T14:15:08+00:00" + "time": "2018-02-28T20:30:58+00:00" + }, + { + "name": "ocramius/package-versions", + "version": "1.4.0", + "source": { + "type": "git", + "url": "https://github.com/Ocramius/PackageVersions.git", + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0.0", + "php": "^7.1.0" + }, + "require-dev": { + "composer/composer": "^1.6.3", + "doctrine/coding-standard": "^5.0.1", + "ext-zip": "*", + "infection/infection": "^0.7.1", + "phpunit/phpunit": "^7.0.0" + }, + "type": "composer-plugin", + "extra": { + "class": "PackageVersions\\Installer", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "PackageVersions\\": "src/PackageVersions" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", + "time": "2019-02-21T12:16:21+00:00" }, { "name": "pdepend/pdepend", @@ -1305,28 +1932,28 @@ }, { "name": "phpdocumentor/phpdocumentor", - "version": "v2.9.0", + "version": "v2.8.5", "source": { "type": "git", "url": "https://github.com/phpDocumentor/phpDocumentor2.git", - "reference": "be607da0eef9b9249c43c5b4820d25d631c73667" + "reference": "adfb4affa80e8cc0134616f2d2d264dd25c243eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/phpDocumentor2/zipball/be607da0eef9b9249c43c5b4820d25d631c73667", - "reference": "be607da0eef9b9249c43c5b4820d25d631c73667", + "url": "https://api.github.com/repos/phpDocumentor/phpDocumentor2/zipball/adfb4affa80e8cc0134616f2d2d264dd25c243eb", + "reference": "adfb4affa80e8cc0134616f2d2d264dd25c243eb", "shasum": "" }, "require": { "cilex/cilex": "~1.0", "erusev/parsedown": "~1.0", "herrera-io/phar-update": "1.0.3", - "jms/serializer": ">=0.12", + "jms/serializer": "~0.12", "monolog/monolog": "~1.6", "php": ">=5.3.3", "phpdocumentor/fileset": "~1.0", "phpdocumentor/graphviz": "~1.0", - "phpdocumentor/reflection": "^3.0", + "phpdocumentor/reflection": "~1.0", "phpdocumentor/reflection-docblock": "~2.0", "symfony/config": "~2.3", "symfony/console": "~2.3", @@ -1390,39 +2017,34 @@ "documentation", "phpdoc" ], - "time": "2016-05-22T09:50:56+00:00" + "time": "2015-07-28T06:36:40+00:00" }, { "name": "phpdocumentor/reflection", - "version": "3.0.1", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/phpDocumentor/Reflection.git", - "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d" + "reference": "7fa71b389e718ea5c33fa0cf0eb45280a4513b7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d", - "reference": "793bfd92d9a0fc96ae9608fb3e947c3f59fb3a0d", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/7fa71b389e718ea5c33fa0cf0eb45280a4513b7d", + "reference": "7fa71b389e718ea5c33fa0cf0eb45280a4513b7d", "shasum": "" }, "require": { - "nikic/php-parser": "^1.0", + "nikic/php-parser": ">=0.9", "php": ">=5.3.3", - "phpdocumentor/reflection-docblock": "~2.0", + "phpdocumentor/reflection-docblock": "2.*@dev", "psr/log": "~1.0" }, "require-dev": { "behat/behat": "~2.4", - "mockery/mockery": "~0.8", - "phpunit/phpunit": "~4.0" + "mockery/mockery": ">=0.7.0", + "phpunit/phpunit": "~3.7" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "autoload": { "psr-0": { "phpDocumentor": [ @@ -1444,7 +2066,7 @@ "reflection", "static analysis" ], - "time": "2016-05-21T08:42:32+00:00" + "time": "2014-02-16T15:05:27+00:00" }, { "name": "phpdocumentor/reflection-docblock", @@ -1613,16 +2235,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.7.6", + "version": "1.8.0", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712" + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712", - "reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", "shasum": "" }, "require": { @@ -1634,12 +2256,12 @@ }, "require-dev": { "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5" + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.7.x-dev" + "dev-master": "1.8.x-dev" } }, "autoload": { @@ -1672,7 +2294,61 @@ "spy", "stub" ], - "time": "2018-04-18T13:57:24+00:00" + "time": "2018-08-05T17:53:17+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "0.8.5", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "0dfb4f00959c53378cf15e32a79a254acada35d7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/0dfb4f00959c53378cf15e32a79a254acada35d7", + "reference": "0dfb4f00959c53378cf15e32a79a254acada35d7", + "shasum": "" + }, + "require": { + "jean85/pretty-package-versions": "^1.0.2", + "nette/bootstrap": "^2.4 || ^3.0", + "nette/di": "^2.4 || ^3.0", + "nette/robot-loader": "^3.0.1", + "nette/utils": "^2.4 || ^3.0", + "nikic/php-parser": "^3.0.2", + "php": "~7.0", + "symfony/console": "~2.7 || ~3.0", + "symfony/finder": "~2.7 || ~3.0" + }, + "require-dev": { + "consistence/coding-standard": "^2.0.0", + "jakub-onderka/php-parallel-lint": "^0.9.2", + "phing/phing": "^2.16.0", + "phpunit/phpunit": "^6.3", + "satooshi/php-coveralls": "^1.0", + "slevomat/coding-standard": "^3.1.1" + }, + "bin": [ + "bin/phpstan" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "0.8-dev" + } + }, + "autoload": { + "psr-4": { + "PHPStan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHPStan - PHP Static Analysis Tool", + "time": "2017-09-06T17:15:07+00:00" }, { "name": "phpunit/php-code-coverage", @@ -1925,16 +2601,16 @@ }, { "name": "phpunit/phpunit", - "version": "6.5.8", + "version": "6.5.14", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b" + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/4f21a3c6b97c42952fd5c2837bb354ec0199b97b", - "reference": "4f21a3c6b97c42952fd5c2837bb354ec0199b97b", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/bac23fe7ff13dbdb461481f706f0e9fe746334b7", + "reference": "bac23fe7ff13dbdb461481f706f0e9fe746334b7", "shasum": "" }, "require": { @@ -1952,7 +2628,7 @@ "phpunit/php-file-iterator": "^1.4.3", "phpunit/php-text-template": "^1.2.1", "phpunit/php-timer": "^1.0.9", - "phpunit/phpunit-mock-objects": "^5.0.5", + "phpunit/phpunit-mock-objects": "^5.0.9", "sebastian/comparator": "^2.1", "sebastian/diff": "^2.0", "sebastian/environment": "^3.1", @@ -2005,20 +2681,20 @@ "testing", "xunit" ], - "time": "2018-04-10T11:38:34+00:00" + "time": "2019-02-01T05:22:47+00:00" }, { "name": "phpunit/phpunit-mock-objects", - "version": "5.0.6", + "version": "5.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf" + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/33fd41a76e746b8fa96d00b49a23dadfa8334cdf", - "reference": "33fd41a76e746b8fa96d00b49a23dadfa8334cdf", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/cd1cf05c553ecfec36b170070573e540b67d3f1f", + "reference": "cd1cf05c553ecfec36b170070573e540b67d3f1f", "shasum": "" }, "require": { @@ -2031,7 +2707,7 @@ "phpunit/phpunit": "<6.0" }, "require-dev": { - "phpunit/phpunit": "^6.5" + "phpunit/phpunit": "^6.5.11" }, "suggest": { "ext-soap": "*" @@ -2064,7 +2740,8 @@ "mock", "xunit" ], - "time": "2018-01-06T05:45:45+00:00" + "abandoned": true, + "time": "2018-08-09T05:50:03+00:00" }, { "name": "pimple/pimple", @@ -2865,16 +3542,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.2.3", + "version": "3.4.1", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "4842476c434e375f9d3182ff7b89059583aa8b27" + "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/4842476c434e375f9d3182ff7b89059583aa8b27", - "reference": "4842476c434e375f9d3182ff7b89059583aa8b27", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5b4333b4010625d29580eb4a41f1e53251be6baa", + "reference": "5b4333b4010625d29580eb4a41f1e53251be6baa", "shasum": "" }, "require": { @@ -2907,25 +3584,25 @@ } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", + "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", "standards" ], - "time": "2018-02-20T21:35:23+00:00" + "time": "2019-03-19T03:22:27+00:00" }, { "name": "symfony/config", - "version": "v2.8.41", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "93bdf96d0e3c9b29740bf9050e7a996b443c8436" + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/93bdf96d0e3c9b29740bf9050e7a996b443c8436", - "reference": "93bdf96d0e3c9b29740bf9050e7a996b443c8436", + "url": "https://api.github.com/repos/symfony/config/zipball/7dd5f5040dc04c118d057fb5886563963eb70011", + "reference": "7dd5f5040dc04c118d057fb5886563963eb70011", "shasum": "" }, "require": { @@ -2969,20 +3646,20 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", - "time": "2018-05-01T22:52:40+00:00" + "time": "2018-11-26T09:38:12+00:00" }, { "name": "symfony/console", - "version": "v2.8.41", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7" + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/e8e59b74ad1274714dad2748349b55e3e6e630c7", - "reference": "e8e59b74ad1274714dad2748349b55e3e6e630c7", + "url": "https://api.github.com/repos/symfony/console/zipball/cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", + "reference": "cbcf4b5e233af15cd2bbd50dee1ccc9b7927dc12", "shasum": "" }, "require": { @@ -3030,7 +3707,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-05-15T21:17:45+00:00" + "time": "2018-11-20T15:55:20+00:00" }, { "name": "symfony/debug", @@ -3154,16 +3831,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v2.8.41", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9b69aad7d4c086dc94ebade2d5eb9145da5dac8c" + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9b69aad7d4c086dc94ebade2d5eb9145da5dac8c", - "reference": "9b69aad7d4c086dc94ebade2d5eb9145da5dac8c", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/a77e974a5fecb4398833b0709210e3d5e334ffb0", + "reference": "a77e974a5fecb4398833b0709210e3d5e334ffb0", "shasum": "" }, "require": { @@ -3210,7 +3887,7 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2018-04-06T07:35:03+00:00" + "time": "2018-11-21T14:20:20+00:00" }, { "name": "symfony/filesystem", @@ -3263,16 +3940,16 @@ }, { "name": "symfony/finder", - "version": "v2.8.41", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "79764d21163db295f0daf8bd9d9b91f97e65db6a" + "reference": "1444eac52273e345d9b95129bf914639305a9ba4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/79764d21163db295f0daf8bd9d9b91f97e65db6a", - "reference": "79764d21163db295f0daf8bd9d9b91f97e65db6a", + "url": "https://api.github.com/repos/symfony/finder/zipball/1444eac52273e345d9b95129bf914639305a9ba4", + "reference": "1444eac52273e345d9b95129bf914639305a9ba4", "shasum": "" }, "require": { @@ -3308,29 +3985,32 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-05-15T21:17:45+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.8.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/7cc359f1b7b80fc25ed7796be7d96adc9b354bae", - "reference": "7cc359f1b7b80fc25ed7796be7d96adc9b354bae", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { "php": ">=5.3.3" }, + "suggest": { + "ext-ctype": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3352,7 +4032,7 @@ }, { "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "email": "backendtea@gmail.com" } ], "description": "Symfony polyfill for ctype functions", @@ -3363,20 +4043,20 @@ "polyfill", "portable" ], - "time": "2018-04-30T19:57:29+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.8.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "3296adf6a6454a050679cde90f95350ad604b171" + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171", - "reference": "3296adf6a6454a050679cde90f95350ad604b171", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -3388,7 +4068,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -3422,20 +4102,20 @@ "portable", "shim" ], - "time": "2018-04-26T10:06:28+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", - "version": "v2.8.41", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "713952f2ccbcc8342ecdbe1cb313d3e2da8aad28" + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/713952f2ccbcc8342ecdbe1cb313d3e2da8aad28", - "reference": "713952f2ccbcc8342ecdbe1cb313d3e2da8aad28", + "url": "https://api.github.com/repos/symfony/process/zipball/c3591a09c78639822b0b290d44edb69bf9f05dc8", + "reference": "c3591a09c78639822b0b290d44edb69bf9f05dc8", "shasum": "" }, "require": { @@ -3471,20 +4151,20 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2018-05-15T21:17:45+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/stopwatch", - "version": "v2.8.41", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "57021208ad9830f8f8390c1a9d7bb390f32be89e" + "reference": "752586c80af8a85aeb74d1ae8202411c68836663" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/57021208ad9830f8f8390c1a9d7bb390f32be89e", - "reference": "57021208ad9830f8f8390c1a9d7bb390f32be89e", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/752586c80af8a85aeb74d1ae8202411c68836663", + "reference": "752586c80af8a85aeb74d1ae8202411c68836663", "shasum": "" }, "require": { @@ -3520,7 +4200,7 @@ ], "description": "Symfony Stopwatch Component", "homepage": "https://symfony.com", - "time": "2018-01-03T07:36:31+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/translation", @@ -3588,16 +4268,16 @@ }, { "name": "symfony/validator", - "version": "v2.8.41", + "version": "v2.8.49", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "96bbfd5534d2e07ba45255bad27ee90d3bc121a3" + "reference": "d5d2090bba3139d8ddb79959fbf516e87238fe3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/96bbfd5534d2e07ba45255bad27ee90d3bc121a3", - "reference": "96bbfd5534d2e07ba45255bad27ee90d3bc121a3", + "url": "https://api.github.com/repos/symfony/validator/zipball/d5d2090bba3139d8ddb79959fbf516e87238fe3a", + "reference": "d5d2090bba3139d8ddb79959fbf516e87238fe3a", "shasum": "" }, "require": { @@ -3658,20 +4338,20 @@ ], "description": "Symfony Validator Component", "homepage": "https://symfony.com", - "time": "2018-05-07T06:57:27+00:00" + "time": "2018-11-14T14:06:48+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8", + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8", "shasum": "" }, "require": { @@ -3698,34 +4378,35 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-04-04T09:56:43+00:00" }, { "name": "twig/twig", - "version": "v1.35.3", + "version": "v1.38.4", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "b48680b6eb7d16b5025b9bfc4108d86f6b8af86f" + "reference": "7732e9e7017d751313811bd118de61302e9c8b35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/b48680b6eb7d16b5025b9bfc4108d86f6b8af86f", - "reference": "b48680b6eb7d16b5025b9bfc4108d86f6b8af86f", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/7732e9e7017d751313811bd118de61302e9c8b35", + "reference": "7732e9e7017d751313811bd118de61302e9c8b35", "shasum": "" }, "require": { - "php": ">=5.3.3" + "php": ">=5.4.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { "psr/container": "^1.0", "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.3" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.35-dev" + "dev-master": "1.38-dev" } }, "autoload": { @@ -3754,16 +4435,16 @@ }, { "name": "Twig Team", - "homepage": "http://twig.sensiolabs.org/contributors", + "homepage": "https://twig.symfony.com/contributors", "role": "Contributors" } ], "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "http://twig.sensiolabs.org", + "homepage": "https://twig.symfony.com", "keywords": [ "templating" ], - "time": "2018-03-20T04:25:58+00:00" + "time": "2019-03-23T14:27:19+00:00" }, { "name": "zendframework/zend-cache", @@ -3960,16 +4641,16 @@ }, { "name": "zendframework/zend-filter", - "version": "2.8.0", + "version": "2.9.1", "source": { "type": "git", "url": "https://github.com/zendframework/zend-filter.git", - "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9" + "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/7b997dbe79459f1652deccc8786d7407fb66caa9", - "reference": "7b997dbe79459f1652deccc8786d7407fb66caa9", + "url": "https://api.github.com/repos/zendframework/zend-filter/zipball/1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", + "reference": "1c3e6d02f9cd5f6c929c9859498f5efbe216e86f", "shasum": "" }, "require": { @@ -3982,12 +4663,14 @@ "require-dev": { "pear/archive_tar": "^1.4.3", "phpunit/phpunit": "^5.7.23 || ^6.4.3", + "psr/http-factory": "^1.0", "zendframework/zend-coding-standard": "~1.0.0", "zendframework/zend-crypt": "^3.2.1", "zendframework/zend-servicemanager": "^2.7.8 || ^3.3", "zendframework/zend-uri": "^2.6" }, "suggest": { + "psr/http-factory-implementation": "psr/http-factory-implementation, for creating file upload instances when consuming PSR-7 in file upload filters", "zendframework/zend-crypt": "Zend\\Crypt component, for encryption filters", "zendframework/zend-i18n": "Zend\\I18n component for filters depending on i18n functionality", "zendframework/zend-servicemanager": "Zend\\ServiceManager component, for using the filter chain functionality", @@ -3996,8 +4679,8 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8.x-dev", - "dev-develop": "2.9.x-dev" + "dev-master": "2.9.x-dev", + "dev-develop": "2.10.x-dev" }, "zf": { "component": "Zend\\Filter", @@ -4019,7 +4702,7 @@ "filter", "zf" ], - "time": "2018-04-11T16:20:04+00:00" + "time": "2018-12-17T16:00:04+00:00" }, { "name": "zendframework/zend-hydrator", @@ -4256,16 +4939,16 @@ }, { "name": "zendframework/zend-servicemanager", - "version": "2.7.10", + "version": "2.7.11", "source": { "type": "git", "url": "https://github.com/zendframework/zend-servicemanager.git", - "reference": "ba7069c94c9af93122be9fa31cddd37f7707d5b4" + "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/ba7069c94c9af93122be9fa31cddd37f7707d5b4", - "reference": "ba7069c94c9af93122be9fa31cddd37f7707d5b4", + "url": "https://api.github.com/repos/zendframework/zend-servicemanager/zipball/99ec9ed5d0f15aed9876433c74c2709eb933d4c7", + "reference": "99ec9ed5d0f15aed9876433c74c2709eb933d4c7", "shasum": "" }, "require": { @@ -4304,7 +4987,7 @@ "servicemanager", "zf2" ], - "time": "2017-12-05T16:27:36+00:00" + "time": "2018-06-22T14:49:54+00:00" }, { "name": "zendframework/zend-stdlib", @@ -4484,7 +5167,6 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": { - "phpunit/phpunit": 0, "phpmd/phpmd": 0 }, "prefer-stable": false, diff --git a/src/Application/EmptyReadableResponse.php b/src/Application/EmptyReadableResponse.php index bc9844d..c788f46 100644 --- a/src/Application/EmptyReadableResponse.php +++ b/src/Application/EmptyReadableResponse.php @@ -17,6 +17,8 @@ */ final class EmptyReadableResponse extends ProtocolBase implements ReadableContentInterface { + const CONTROL_PACKET_VALUE = 0; + use ReadableContent; /** diff --git a/src/Application/EmptyWritableResponse.php b/src/Application/EmptyWritableResponse.php index f4137dd..0550213 100644 --- a/src/Application/EmptyWritableResponse.php +++ b/src/Application/EmptyWritableResponse.php @@ -16,7 +16,8 @@ */ final class EmptyWritableResponse extends ProtocolBase implements WritableContentInterface { - use WritableContent; + use /** @noinspection TraitsPropertiesConflictsInspection */ + WritableContent; const CONTROL_PACKET_VALUE = 0; diff --git a/src/Client.php b/src/Client.php index fc6e214..7e92bda 100644 --- a/src/Client.php +++ b/src/Client.php @@ -22,7 +22,7 @@ final class Client extends ProtocolBase implements ClientInterface { /** * Where all the magic happens - * @var Resource + * @var Resource|null */ private $socket; @@ -159,7 +159,7 @@ private function checkAndReturnAnswer(WritableContentInterface $object): string */ private function checkForConnectionErrors(int $errorCode, string $errorDescription): self { - if ($errorCode !== 0 || $this->socket === false) { + if ($errorCode !== 0 || $this->socket === null) { $this->logger->critical('Could not connect to broker', [ 'errorCode' => $errorCode, 'errorDescription' => $errorDescription, diff --git a/src/DataTypes/TopicFilter.php b/src/DataTypes/TopicFilter.php index 4bc94c4..39cb6fd 100644 --- a/src/DataTypes/TopicFilter.php +++ b/src/DataTypes/TopicFilter.php @@ -28,7 +28,7 @@ final class TopicFilter extends GeneralTopicRules * * @var QoSLevel */ - private $qosLevel = 0; + private $qosLevel; /** * TopicFilter constructor. diff --git a/src/DebugTools.php b/src/DebugTools.php index 01839c1..bba25e9 100644 --- a/src/DebugTools.php +++ b/src/DebugTools.php @@ -4,6 +4,10 @@ namespace unreal4u\MQTT; +use function chr; +use function ord; +use function strlen; + /** * Collection of function that have proven useful while debugging issues and creating unit tests * @package unreal4u\MQTT @@ -19,9 +23,9 @@ final class DebugTools public static function convertToBinaryRepresentation(string $rawString): string { $out = ''; - $strLength = \strlen($rawString); + $strLength = strlen($rawString); for ($a = 0; $a < $strLength; $a++) { - $dec = \ord($rawString[$a]); //determine symbol ASCII-code + $dec = ord($rawString[$a]); //determine symbol ASCII-code $bin = sprintf('%08d', base_convert($dec, 10, 2)); //convert to binary representation and add leading zeros $out .= $bin; } @@ -39,9 +43,9 @@ public static function convertToBinaryRepresentation(string $rawString): string public static function convertBinaryToString(string $binaryRepresentation, bool $applyBase64 = false): string { $output = ''; - $binaryStringLength = \strlen($binaryRepresentation); + $binaryStringLength = strlen($binaryRepresentation); for ($i = 0; $i < $binaryStringLength; $i += 8) { - $output .= \chr((int)base_convert(substr($binaryRepresentation, $i, 8), 2, 10)); + $output .= chr((int)base_convert(substr($binaryRepresentation, $i, 8), 2, 10)); } if ($applyBase64 === true) { diff --git a/src/Internals/DisconnectCleanup.php b/src/Internals/DisconnectCleanup.php index 7ecae50..0f74daf 100644 --- a/src/Internals/DisconnectCleanup.php +++ b/src/Internals/DisconnectCleanup.php @@ -9,6 +9,8 @@ */ final class DisconnectCleanup extends ProtocolBase implements ReadableContentInterface { + const CONTROL_PACKET_VALUE = 0; + use ReadableContent; /** diff --git a/src/Internals/ReadableContent.php b/src/Internals/ReadableContent.php index 3436212..9ce6145 100644 --- a/src/Internals/ReadableContent.php +++ b/src/Internals/ReadableContent.php @@ -5,6 +5,9 @@ namespace unreal4u\MQTT\Internals; use unreal4u\MQTT\Exceptions\InvalidResponseType; +use unreal4u\MQTT\Utilities; +use function ord; +use function strlen; /** * Trait ReadableContent @@ -18,16 +21,22 @@ trait ReadableContent */ protected $variableHeaderSize = 0; + /** + * The remaining length field may be from 1 to 4 bytes long, this field will represent that offset + * @var int + */ + private $sizeOfRemainingLengthField = 1; + /** * @param string $rawMQTTHeaders * @param ClientInterface $client * @return bool - * @throws \unreal4u\MQTT\Exceptions\InvalidResponseType + * @throws InvalidResponseType */ final public function instantiateObject(string $rawMQTTHeaders, ClientInterface $client): bool { //var_dump(base64_encode($rawMQTTHeaders)); // Make it a bit easier to create unit tests - $this->checkControlPacketValue(\ord($rawMQTTHeaders[0]) >> 4); + $this->checkControlPacketValue(ord($rawMQTTHeaders[0]) >> 4); $this->fillObject($rawMQTTHeaders, $client); return true; @@ -38,7 +47,7 @@ final public function instantiateObject(string $rawMQTTHeaders, ClientInterface * * @param int $controlPacketValue * @return bool - * @throws \unreal4u\MQTT\Exceptions\InvalidResponseType + * @throws InvalidResponseType */ private function checkControlPacketValue(int $controlPacketValue): bool { @@ -54,6 +63,53 @@ private function checkControlPacketValue(int $controlPacketValue): bool return true; } + /** + * Returns the correct format for the length in bytes of the remaining bytes + * + * @param ClientInterface $client + * @param string $rawMQTTHeaders + * @return int + */ + private function performRemainingLengthFieldOperations( + string &$rawMQTTHeaders, + ClientInterface $client + ): int { + // Early return: assume defaults if first digit has a value under 128, no further need for complex checks + if (ord($rawMQTTHeaders{1}) < 128) { + return ord($rawMQTTHeaders{1}); + } + + // If we have less than 4 bytes now, we should really try to recover the rest of the remaining field data + if (strlen($rawMQTTHeaders) < 4) { + // At this point we could actually read at least 128 as a minimum, but restrict it to what we need right now + $rawMQTTHeaders .= $client->readBrokerData(4 - strlen($rawMQTTHeaders)); + } + + $remainingBytes = Utilities::convertRemainingLengthStringToInt(substr($rawMQTTHeaders, 1, 4)); + + // Estimate how much longer is the remaining length field, this will also set $this->sizeOfRemainingLengthField + $this->calculateSizeOfRemainingLengthField($remainingBytes); + return $remainingBytes; + } + + /** + * Sets the offset of the remaining length field + * + * @param int $size + * @return int + */ + private function calculateSizeOfRemainingLengthField(int $size): int + { + $blockSize = $iterations = 0; + while ($size >= $blockSize) { + $iterations++; + $blockSize = 128 ** $iterations; + } + + $this->sizeOfRemainingLengthField = $iterations; + return $iterations; + } + /** * All classes must implement how to handle the object filling * @param string $rawMQTTHeaders diff --git a/src/Internals/WritableContent.php b/src/Internals/WritableContent.php index acc8b89..45cd1dd 100644 --- a/src/Internals/WritableContent.php +++ b/src/Internals/WritableContent.php @@ -4,9 +4,14 @@ namespace unreal4u\MQTT\Internals; +use DomainException; +use OutOfRangeException; use Psr\Log\LoggerInterface; use unreal4u\MQTT\Exceptions\MessageTooBig; use unreal4u\MQTT\Utilities; +use function chr; +use function get_class; +use function strlen; /** * Trait WritableContent @@ -37,55 +42,27 @@ trait WritableContent * * @param int $variableHeaderLength * @return string - * @throws \unreal4u\MQTT\Exceptions\MessageTooBig + * @throws MessageTooBig */ final public function createFixedHeader(int $variableHeaderLength): string { $this->logger->debug('Creating fixed header with values', [ - 'controlPacketValue' => static::CONTROL_PACKET_VALUE, + 'controlPacketValue' => self::getControlPacketValue(), 'specialFlags' => $this->specialFlags, 'variableHeaderLength' => $variableHeaderLength, - 'composed' => decbin(\chr((static::CONTROL_PACKET_VALUE << 4) | $this->specialFlags)), + 'composed' => decbin(chr((self::getControlPacketValue() << 4) | $this->specialFlags)), ]); // Binary OR is safe to do because the first 4 bits are always 0 after shifting return - \chr((static::CONTROL_PACKET_VALUE << 4) | $this->specialFlags) . - $this->getRemainingLength($variableHeaderLength); - } - - /** - * Returns the correct format for the length in bytes of the remaining bytes - * - * @param int $lengthInBytes - * @return string - * @throws \unreal4u\MQTT\Exceptions\MessageTooBig - */ - final public function getRemainingLength(int $lengthInBytes): string - { - if ($lengthInBytes > 268435455) { - throw new MessageTooBig('The message cannot exceed 268435455 bytes in length'); - } - - $x = $lengthInBytes; - $outputString = ''; - do { - $encodedByte = $x % 128; - $x >>= 7; // Shift 7 bytes - // if there are more data to encode, set the top bit of this byte - if ($x > 0) { - $encodedByte |= 128; - } - $outputString .= \chr($encodedByte); - } while ($x > 0); - - return $outputString; + chr((self::getControlPacketValue() << 4) | $this->specialFlags) . + Utilities::formatRemainingLengthOutput($variableHeaderLength); } /** * Creates the entire message * @return string - * @throws \unreal4u\MQTT\Exceptions\MessageTooBig + * @throws MessageTooBig */ final public function createSendableMessage(): string { @@ -93,7 +70,7 @@ final public function createSendableMessage(): string $this->logger->debug('Created variable header', ['variableHeader' => base64_encode($variableHeader)]); $payload = $this->createPayload(); $this->logger->debug('Created payload', ['payload' => base64_encode($payload)]); - $fixedHeader = $this->createFixedHeader(\strlen($variableHeader . $payload)); + $fixedHeader = $this->createFixedHeader(strlen($variableHeader . $payload)); $this->logger->debug('Created fixed header', ['fixedHeader' => base64_encode($fixedHeader)]); return $fixedHeader . $variableHeader . $payload; @@ -118,13 +95,13 @@ abstract public function createPayload(): string; * * @param string $nonFormattedString * @return string - * @throws \OutOfRangeException + * @throws OutOfRangeException */ final public function createUTF8String(string $nonFormattedString): string { $returnString = ''; if ($nonFormattedString !== '') { - $returnString = Utilities::convertNumberToBinaryString(\strlen($nonFormattedString)) . $nonFormattedString; + $returnString = Utilities::convertNumberToBinaryString(strlen($nonFormattedString)) . $nonFormattedString; } return $returnString; @@ -137,11 +114,11 @@ final public function createUTF8String(string $nonFormattedString): string * @param ClientInterface $client * * @return ReadableContentInterface - * @throws \DomainException + * @throws DomainException */ public function expectAnswer(string $brokerBitStream, ClientInterface $client): ReadableContentInterface { - $this->logger->info('String of incoming data confirmed, returning new object', ['callee' => \get_class($this)]); + $this->logger->info('String of incoming data confirmed, returning new object', ['callee' => get_class($this)]); $eventManager = new EventManager($this->logger); return $eventManager->analyzeHeaders($brokerBitStream, $client); diff --git a/src/Protocol/Connect/Parameters.php b/src/Protocol/Connect/Parameters.php index 6352e35..9a768ee 100644 --- a/src/Protocol/Connect/Parameters.php +++ b/src/Protocol/Connect/Parameters.php @@ -44,7 +44,7 @@ final class Parameters * * @var ClientId */ - private $clientId = ''; + private $clientId; /** * The keep alive is a time interval in seconds (defaults to 60), the clients commits to by sending regular PING diff --git a/src/Protocol/PubRec.php b/src/Protocol/PubRec.php index 4e841ea..deae08e 100644 --- a/src/Protocol/PubRec.php +++ b/src/Protocol/PubRec.php @@ -4,6 +4,8 @@ namespace unreal4u\MQTT\Protocol; +use LogicException; +use OutOfRangeException; use unreal4u\MQTT\Internals\ClientInterface; use unreal4u\MQTT\Internals\PacketIdentifierFunctionality; use unreal4u\MQTT\Internals\ProtocolBase; @@ -26,7 +28,9 @@ */ final class PubRec extends ProtocolBase implements ReadableContentInterface, WritableContentInterface { - use ReadableContent, WritableContent, PacketIdentifierFunctionality; + use ReadableContent, /** @noinspection TraitsPropertiesConflictsInspection */ + WritableContent, + PacketIdentifierFunctionality; const CONTROL_PACKET_VALUE = 5; @@ -34,7 +38,7 @@ final class PubRec extends ProtocolBase implements ReadableContentInterface, Wri * @param string $rawMQTTHeaders * @param ClientInterface $client * @return ReadableContentInterface - * @throws \OutOfRangeException + * @throws OutOfRangeException */ public function fillObject(string $rawMQTTHeaders, ClientInterface $client): ReadableContentInterface { @@ -45,7 +49,7 @@ public function fillObject(string $rawMQTTHeaders, ClientInterface $client): Rea /** * Creates the variable header that each method has * @return string - * @throws \OutOfRangeException + * @throws OutOfRangeException */ public function createVariableHeader(): string { @@ -75,7 +79,7 @@ public function shouldExpectAnswer(): bool * @param ClientInterface $client * @param WritableContentInterface $originalRequest * @return bool - * @throws \LogicException + * @throws LogicException */ public function performSpecialActions(ClientInterface $client, WritableContentInterface $originalRequest): bool { diff --git a/src/Protocol/PubRel.php b/src/Protocol/PubRel.php index 399f4ee..b78cbd8 100644 --- a/src/Protocol/PubRel.php +++ b/src/Protocol/PubRel.php @@ -27,7 +27,9 @@ */ final class PubRel extends ProtocolBase implements ReadableContentInterface, WritableContentInterface { - use ReadableContent, WritableContent, PacketIdentifierFunctionality; + use ReadableContent, /** @noinspection TraitsPropertiesConflictsInspection */ + WritableContent, + PacketIdentifierFunctionality; const CONTROL_PACKET_VALUE = 6; diff --git a/src/Protocol/Publish.php b/src/Protocol/Publish.php index 6d04c59..8e3a11c 100644 --- a/src/Protocol/Publish.php +++ b/src/Protocol/Publish.php @@ -4,12 +4,21 @@ namespace unreal4u\MQTT\Protocol; +use InvalidArgumentException; +use OutOfBoundsException; +use OutOfRangeException; use unreal4u\MQTT\Application\EmptyReadableResponse; use unreal4u\MQTT\DataTypes\Message; use unreal4u\MQTT\DataTypes\PacketIdentifier; use unreal4u\MQTT\DataTypes\QoSLevel; use unreal4u\MQTT\DataTypes\TopicName; +use unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined; +use unreal4u\MQTT\Exceptions\InvalidQoSLevel; use unreal4u\MQTT\Exceptions\InvalidRequest; +use unreal4u\MQTT\Exceptions\InvalidResponseType; +use unreal4u\MQTT\Exceptions\MessageTooBig; +use unreal4u\MQTT\Exceptions\MissingTopicName; +use unreal4u\MQTT\Exceptions\NotConnected; use unreal4u\MQTT\Internals\ClientInterface; use unreal4u\MQTT\Internals\PacketIdentifierFunctionality; use unreal4u\MQTT\Internals\ProtocolBase; @@ -18,6 +27,8 @@ use unreal4u\MQTT\Internals\WritableContent; use unreal4u\MQTT\Internals\WritableContentInterface; use unreal4u\MQTT\Utilities; +use function ord; +use function strlen; /** * A PUBLISH Control Packet is sent from a Client to a Server or vice-versa to transport an Application Message. @@ -38,7 +49,9 @@ */ final class Publish extends ProtocolBase implements ReadableContentInterface, WritableContentInterface { - use ReadableContent, WritableContent, PacketIdentifierFunctionality; + use ReadableContent, /** @noinspection TraitsPropertiesConflictsInspection */ + WritableContent, + PacketIdentifierFunctionality; const CONTROL_PACKET_VALUE = 3; @@ -57,15 +70,15 @@ final class Publish extends ProtocolBase implements ReadableContentInterface, Wr /** * @return string - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel - * @throws \unreal4u\MQTT\Exceptions\MissingTopicName - * @throws \OutOfRangeException - * @throws \InvalidArgumentException + * @throws InvalidQoSLevel + * @throws MissingTopicName + * @throws OutOfRangeException + * @throws InvalidArgumentException */ public function createVariableHeader(): string { if ($this->message === null) { - throw new \InvalidArgumentException('You must at least provide a message object with a topic name'); + throw new InvalidArgumentException('You must at least provide a message object with a topic name'); } $variableHeaderContents = $this->createUTF8String($this->message->getTopicName()); @@ -81,8 +94,8 @@ public function createVariableHeader(): string * Sets some common flags and returns the variable header string should there be one * * @return string - * @throws \OutOfRangeException - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel + * @throws OutOfRangeException + * @throws InvalidQoSLevel */ private function createVariableHeaderFlags(): string { @@ -111,14 +124,14 @@ private function createVariableHeaderFlags(): string /** * @return string - * @throws \unreal4u\MQTT\Exceptions\MissingTopicName - * @throws \unreal4u\MQTT\Exceptions\MessageTooBig - * @throws \InvalidArgumentException + * @throws MissingTopicName + * @throws MessageTooBig + * @throws InvalidArgumentException */ public function createPayload(): string { if ($this->message === null) { - throw new \InvalidArgumentException('A message must be set before publishing'); + throw new InvalidArgumentException('A message must be set before publishing'); } return $this->message->getPayload(); } @@ -126,7 +139,7 @@ public function createPayload(): string /** * QoS level 0 does not have to wait for a answer, so return false. Any other QoS level returns true * @return bool - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel + * @throws InvalidQoSLevel */ public function shouldExpectAnswer(): bool { @@ -141,7 +154,7 @@ public function shouldExpectAnswer(): bool * @param string $brokerBitStream * @param ClientInterface $client * @return ReadableContentInterface - * @throws \unreal4u\MQTT\Exceptions\InvalidResponseType + * @throws InvalidResponseType */ public function expectAnswer(string $brokerBitStream, ClientInterface $client): ReadableContentInterface { @@ -188,7 +201,7 @@ public function getMessage(): Message * @param int $firstByte * @param QoSLevel $qoSLevel * @return Publish - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel + * @throws InvalidQoSLevel */ private function analyzeFirstByte(int $firstByte, QoSLevel $qoSLevel): Publish { @@ -218,7 +231,7 @@ private function analyzeFirstByte(int $firstByte, QoSLevel $qoSLevel): Publish * * @param int $bitString * @return QoSLevel - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel + * @throws InvalidQoSLevel */ private function determineIncomingQoSLevel(int $bitString): QoSLevel { @@ -242,28 +255,38 @@ private function determineIncomingQoSLevel(int $bitString): QoSLevel * @param string $rawMQTTHeaders * @param ClientInterface $client * @return string - * @throws \OutOfBoundsException - * @throws \InvalidArgumentException - * @throws \unreal4u\MQTT\Exceptions\MessageTooBig - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel + * @throws OutOfBoundsException + * @throws InvalidArgumentException + * @throws MessageTooBig + * @throws InvalidQoSLevel */ private function completePossibleIncompleteMessage(string $rawMQTTHeaders, ClientInterface $client): string { - if (\strlen($rawMQTTHeaders) === 1) { - $this->logger->debug('Only one incoming byte, retrieving rest of size and the full payload'); - $restOfBytes = $client->readBrokerData(1); - $payload = $client->readBrokerData(\ord($restOfBytes)); - } else { - $this->logger->debug('More than 1 byte detected, calculating and retrieving the rest'); - $restOfBytes = $rawMQTTHeaders{1}; - $payload = substr($rawMQTTHeaders, 2); - $exactRest = \ord($restOfBytes) - \strlen($payload); - $payload .= $client->readBrokerData($exactRest); - $rawMQTTHeaders = $rawMQTTHeaders{0}; + // Read at least one extra byte from the stream if we know that the message is too short + if (strlen($rawMQTTHeaders) < 2) { + $rawMQTTHeaders .= $client->readBrokerData(1); + } + + $restOfBytes = $this->performRemainingLengthFieldOperations($rawMQTTHeaders, $client); + + /* + * A complete message consists of: + * - The very first byte + * - The size of the remaining length field (from 1 to 4 bytes) + * - The $restOfBytes + * + * So we have to compare what we already have vs the above calculation + * + * More information: + * http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc442180832 + */ + if (strlen($rawMQTTHeaders) < ($restOfBytes + $this->sizeOfRemainingLengthField + 1)) { + // Read only the portion of data we have left from the socket + $readableDataLeft = ($restOfBytes + $this->sizeOfRemainingLengthField + 1) - strlen($rawMQTTHeaders); + $rawMQTTHeaders .= $client->readBrokerData($readableDataLeft); } - // $rawMQTTHeaders may be redefined - return $rawMQTTHeaders . $restOfBytes . $payload; + return $rawMQTTHeaders; } /** @@ -271,29 +294,34 @@ private function completePossibleIncompleteMessage(string $rawMQTTHeaders, Clien * @param string $rawMQTTHeaders * @param ClientInterface $client * @return ReadableContentInterface - * @throws \unreal4u\MQTT\Exceptions\MessageTooBig - * @throws \OutOfBoundsException - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel - * @throws \InvalidArgumentException - * @throws \OutOfRangeException + * @throws MessageTooBig + * @throws OutOfBoundsException + * @throws InvalidQoSLevel + * @throws InvalidArgumentException + * @throws OutOfRangeException */ public function fillObject(string $rawMQTTHeaders, ClientInterface $client): ReadableContentInterface { - $rawMQTTHeaders = $this->completePossibleIncompleteMessage($rawMQTTHeaders, $client); + // Retrieve full message first + $fullMessage = $this->completePossibleIncompleteMessage($rawMQTTHeaders, $client); // Handy to maintain for debugging purposes #$this->logger->debug('Bin data', [\unreal4u\MQTT\DebugTools::convertToBinaryRepresentation($rawMQTTHeaders)]); - // TopicFilter size is always the 3rd byte - $firstByte = \ord($rawMQTTHeaders{0}); - $topicSize = \ord($rawMQTTHeaders{3}); + // Handy to have: the first byte + $firstByte = ord($fullMessage{0}); + // TopicName size is always on the second position after the size of the remaining length field (1 to 4 bytes) + $topicSize = ord($fullMessage{$this->sizeOfRemainingLengthField + 2}); + // With the first byte, we can determine the QoS level of the incoming message $qosLevel = $this->determineIncomingQoSLevel($firstByte); - $messageStartPosition = 4; + $messageStartPosition = $this->sizeOfRemainingLengthField + 3; + // If we have a QoS level present, we must retrieve the packet identifier as well if ($qosLevel->getQoSLevel() > 0) { $this->logger->debug('QoS level above 0, shifting message start position and getting packet identifier'); // [2 (fixed header) + 2 (topic size) + $topicSize] marks the beginning of the 2 packet identifier bytes $this->setPacketIdentifier(new PacketIdentifier(Utilities::convertBinaryStringToNumber( - $rawMQTTHeaders{4 + $topicSize} . $rawMQTTHeaders{5 + $topicSize} + $fullMessage{$this->sizeOfRemainingLengthField + 3 + $topicSize} . + $fullMessage{$this->sizeOfRemainingLengthField + 4 + $topicSize} ))); $this->logger->debug('Determined packet identifier', ['PI' => $this->getPacketIdentifier()]); $messageStartPosition += 2; @@ -302,8 +330,8 @@ public function fillObject(string $rawMQTTHeaders, ClientInterface $client): Rea // At this point $rawMQTTHeaders will be always 1 byte long, initialize a Message object with dummy data for now $this->message = new Message( // Save to assume a constant here: first 2 bytes will always be fixed header, next 2 bytes are topic size - substr($rawMQTTHeaders, $messageStartPosition + $topicSize), - new TopicName(substr($rawMQTTHeaders, 4, $topicSize)) + substr($fullMessage, $messageStartPosition + $topicSize), + new TopicName(substr($fullMessage, $this->sizeOfRemainingLengthField + 3, $topicSize)) ); $this->analyzeFirstByte($firstByte, $qosLevel); @@ -315,17 +343,15 @@ public function fillObject(string $rawMQTTHeaders, ClientInterface $client): Rea #'packetIdentifier' => $this->packetIdentifier->getPacketIdentifierValue(), // This is not always set! ]); - return $this; } /** * @inheritdoc - * @throws \unreal4u\MQTT\Exceptions\InvalidRequest - * @throws \unreal4u\MQTT\Exceptions\InvalidQoSLevel - * @throws \unreal4u\MQTT\Exceptions\ServerClosedConnection - * @throws \unreal4u\MQTT\Exceptions\NotConnected - * @throws \unreal4u\MQTT\Exceptions\Connect\NoConnectionParametersDefined + * @throws InvalidRequest + * @throws InvalidQoSLevel + * @throws NotConnected + * @throws NoConnectionParametersDefined */ public function performSpecialActions(ClientInterface $client, WritableContentInterface $originalRequest): bool { @@ -349,7 +375,7 @@ public function performSpecialActions(ClientInterface $client, WritableContentIn * Composes a PubRec answer with the same packetIdentifier as what we received * * @return PubRec - * @throws \unreal4u\MQTT\Exceptions\InvalidRequest + * @throws InvalidRequest */ private function composePubRecAnswer(): PubRec { @@ -363,7 +389,7 @@ private function composePubRecAnswer(): PubRec * Composes a PubAck answer with the same packetIdentifier as what we received * * @return PubAck - * @throws \unreal4u\MQTT\Exceptions\InvalidRequest + * @throws InvalidRequest */ private function composePubAckAnswer(): PubAck { @@ -377,7 +403,7 @@ private function composePubAckAnswer(): PubAck * Will check whether the current object has a packet identifier set. If not, we are in serious problems! * * @return Publish - * @throws \unreal4u\MQTT\Exceptions\InvalidRequest + * @throws InvalidRequest */ private function checkForValidPacketIdentifier(): self { diff --git a/src/Utilities.php b/src/Utilities.php index 72bfd23..1ddb043 100644 --- a/src/Utilities.php +++ b/src/Utilities.php @@ -4,6 +4,14 @@ namespace unreal4u\MQTT; +use LogicException; +use OutOfRangeException; +use unreal4u\MQTT\Exceptions\MessageTooBig; +use function chr; +use function dechex; +use function hexdec; +use function ord; + /** * Functionality that is shared across the entire package * @package unreal4u\MQTT @@ -15,12 +23,12 @@ final class Utilities * * @param int $number * @return int - * @throws \OutOfRangeException + * @throws OutOfRangeException */ public static function convertEndianness(int $number): int { if ($number > 65535) { - throw new \OutOfRangeException('This is an INT16 conversion, so the maximum is 65535'); + throw new OutOfRangeException('This is an INT16 conversion, so the maximum is 65535'); } $finalNumber = hexdec( @@ -38,15 +46,15 @@ public static function convertEndianness(int $number): int * * @param int $number * @return string - * @throws \OutOfRangeException + * @throws OutOfRangeException */ public static function convertNumberToBinaryString(int $number): string { if ($number > 65535) { - throw new \OutOfRangeException('This is an INT16 conversion, so the maximum is 65535'); + throw new OutOfRangeException('This is an INT16 conversion, so the maximum is 65535'); } - return \chr($number >> 8) . \chr($number & 255); + return chr($number >> 8) . chr($number & 255); } /** @@ -54,10 +62,100 @@ public static function convertNumberToBinaryString(int $number): string * * @param string $binaryString * @return int - * @throws \OutOfRangeException + * @throws OutOfRangeException */ public static function convertBinaryStringToNumber(string $binaryString): int { - return self::convertEndianness((\ord($binaryString{1}) << 8) + (\ord($binaryString{0}) & 255)); + return self::convertEndianness((ord($binaryString{1}) << 8) + (ord($binaryString{0}) & 255)); + } + + /** + * Returns the correct format for the length in bytes of the remaining bytes + * + * Original pseudo-code algorithm as per the documentation: + *
+ * do + * encodedByte = X MOD 128 + * X = X DIV 128 + * // if there are more data to encode, set the top bit of this byte + * if ( X > 0 ) + * encodedByte = encodedByte OR 128 + * endif + * 'output' encodedByte + * while ( X > 0 ) + *+ * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc385349213 + * + * @param int $lengthInBytes + * @return string + * @throws MessageTooBig + */ + public static function formatRemainingLengthOutput(int $lengthInBytes): string + { + if ($lengthInBytes > 268435455) { + throw new MessageTooBig('The message cannot exceed 268435455 bytes in length'); + } + + $x = $lengthInBytes; + $outputString = ''; + do { + $encodedByte = $x % 128; + $x >>= 7; // Shift 7 bytes + // if there are more data to encode, set the top bit of this byte + if ($x > 0) { + $encodedByte |= 128; + } + $outputString .= chr($encodedByte); + } while ($x > 0); + + return $outputString; + } + + /** + * The remaining length of a message is encoded in this specific way, the opposite of formatRemainingLengthOutput + * + * Many thanks to Peter's blog for your excellent examples and knowledge. + * @see http://indigoo.com/petersblog/?p=263 + * + * Original pseudo-algorithm as per the documentation: + *
+ * multiplier = 1 + * value = 0 + * do + * encodedByte = 'next byte from stream' + * value += (encodedByte & 127) * multiplier + * if (multiplier > 128*128*128) + * throw Error(Malformed Remaining Length) + * multiplier *= 128 + * while ((encodedByte & 128) != 0) + *+ * + * @see http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/errata01/os/mqtt-v3.1.1-errata01-os-complete.html#_Toc385349213 + * @see Utilities::formatRemainingLengthOutput() + * + * @param string $remainingLengthField + * @return int + */ + public static function convertRemainingLengthStringToInt(string $remainingLengthField): int + { + $multiplier = 128; + $value = 0; + $iteration = 0; + + do { + // Extract the next byte in the sequence + $encodedByte = ord($remainingLengthField{$iteration}); + + // Add the current multiplier^iteration * first half of byte + $value += ($encodedByte & 127) * ($multiplier ** $iteration); + if ($multiplier > 128 ** 3) { + throw new LogicException('Malformed remaining length field'); + } + + // Prepare for the next iteration + $iteration++; + } while (($encodedByte & 128) !== 0); + + return $value; } } diff --git a/tests/Internals/ReadableContentTest.php b/tests/Internals/ReadableContentTest.php index c4d6e52..199c2dd 100644 --- a/tests/Internals/ReadableContentTest.php +++ b/tests/Internals/ReadableContentTest.php @@ -7,16 +7,82 @@ use PHPUnit\Framework\TestCase; use tests\unreal4u\MQTT\Mocks\ClientMock; use unreal4u\MQTT\Exceptions\InvalidResponseType; +use unreal4u\MQTT\Internals\ClientInterface; +use unreal4u\MQTT\Internals\ReadableContent; +use unreal4u\MQTT\Internals\ReadableContentInterface; use unreal4u\MQTT\Protocol\PingResp; +use function base64_decode; +use function chr; class ReadableContentTest extends TestCase { + use ReadableContent; + public function test_incorrectControlPacketValue() { - $success = \chr(100) . \chr(0); + $success = chr(100) . chr(0); $pingResp = new PingResp(); $this->expectException(InvalidResponseType::class); $pingResp->instantiateObject($success, new ClientMock()); } + + public function provider_performRemainingLengthFieldOperations(): array + { + $mapValues[] = [base64_decode('IAI='), 2]; // remaining length: 2, which is 1 byte long + $mapValues[] = [base64_decode('IMgB'), 200]; // remaining length: 200, which is 2 bytes long + $mapValues[] = [base64_decode('IKnKAQ=='), 25897]; // remaining length: 25897, which is 3 bytes long + $mapValues[] = [base64_decode('IP///38='), 268435455]; // remaining length: 268435455, which is 4 bytes long + + return $mapValues; + } + + /** + * @dataProvider provider_performRemainingLengthFieldOperations + * @param string $binaryText + * @param int $numericRepresentation + */ + public function test_performRemainingLengthFieldOperations(string $binaryText, int $numericRepresentation) + { + $clientMock = new ClientMock(); + $returnValue = $this->performRemainingLengthFieldOperations($binaryText, $clientMock); + $this->assertSame($numericRepresentation, $returnValue); + } + + public function provider_calculateSizeOfRemainingLengthField(): array + { + $mapValues[] = [1, 1]; + $mapValues[] = [2, 1]; + $mapValues[] = [50, 1]; + $mapValues[] = [128, 2]; + $mapValues[] = [1280, 2]; + $mapValues[] = [16400, 3]; + $mapValues[] = [2097150, 3]; + $mapValues[] = [2097152, 4]; + $mapValues[] = [268435455, 4]; + + return $mapValues; + } + + /** + * @dataProvider provider_calculateSizeOfRemainingLengthField + * @param int $size + * @param int $expectedByteSize + */ + public function test_calculateSizeOfRemainingLengthField(int $size, int $expectedByteSize) + { + $returnValue = $this->calculateSizeOfRemainingLengthField($size); + $this->assertSame($expectedByteSize, $returnValue); + } + + /** + * All classes must implement how to handle the object filling + * @param string $rawMQTTHeaders + * @param ClientInterface $client + * @return ReadableContentInterface + */ + public function fillObject(string $rawMQTTHeaders, ClientInterface $client): ReadableContentInterface + { + // Not needed, can be safely ignored + } } diff --git a/tests/Protocol/PublishTest.php b/tests/Protocol/PublishTest.php index 15f28b8..78aba81 100644 --- a/tests/Protocol/PublishTest.php +++ b/tests/Protocol/PublishTest.php @@ -205,7 +205,6 @@ public function provider_performSpecialActions(): array * @param int $QoSLevel * @param int $packetIdentifier * @param string $expectedClassType - * @throws \unreal4u\MQTT\Exceptions\ServerClosedConnection */ public function test_performSpecialActions(int $QoSLevel, int $packetIdentifier, string $expectedClassType) { @@ -293,7 +292,8 @@ public function test_completePossibleIncompleteMessage(string $firstBytes, strin $output = base64_encode($method->invoke($this->publish, base64_decode($firstBytes), $clientMock)); $this->assertSame($expectedOutput, $output); - $this->assertTrue($clientMock->readBrokerDataWasCalled()); + // If there is no more data to read, readBrokerData should never even be called + $this->assertSame($append !== '', $clientMock->readBrokerDataWasCalled()); } public function provider_fillObject(): array @@ -302,11 +302,24 @@ public function provider_fillObject(): array $mapVal[] = ['MBQACWZpcnN0VGVzdOaxiUHlrZdCQw==', 0, 'firstTest', '汉A字BC', 0]; // QoS 1 with packetIdentifier 10 $mapVal[] = ['MiIACWZpcnN0VGVzdAAKSGVsbG8gd29ybGQhISAoMSAvIDMp', 1, 'firstTest', 'Hello world!! (1 / 3)', 10]; - // QoS 1 with packetIdentigier 15 and different message + // QoS 1 with packetIdentifier 15 and different message $mapVal[] = ['MiIACWZpcnN0VGVzdAAPSGVsbG8gd29ybGQhISAoMyAvIDMp', 1, 'firstTest', 'Hello world!! (3 / 3)', 15]; // QoS 2 with packetIdentifier 16 $mapVal[] = ['NCIACWZpcnN0VGVzdAAQSGVsbG8gd29ybGQhISAoMSAvIDEp', 2, 'firstTest', 'Hello world!! (1 / 1)', 16]; + // Real-life example of message that did go wrong, ensure it never happens again + $mapVal[] = [ + 'Me0BABpzaW5nbGVsYW1wL3RlbGVtZXRyeS9TVEFURXsiVGltZSI6IjIwMTktMDMtMjZUMjI6MzM6MzciLCJVcHRpbWUiOiI0NVQwNzow' . + 'OTowMiIsIlZjYyI6My40MjMsIlNsZWVwTW9kZSI6IkR5bmFtaWMiLCJTbGVlcCI6NTAsIkxvYWRBdmciOjE5LCJQT1dFUiI6Ik9GRiIs' . + 'IldpZmkiOnsiQVAiOjEsIlNTSWQiOiJYWFhYWFhYWCIsIkJTU0lkIjoiQUE6QUE6QUE6QUE6QUE6QUEiLCJDaGFubmVsIjozLCJSU1NJ' . + 'Ijo2MH0=', + 0, + 'singlelamp/telemetry/STATE', + '{"Time":"2019-03-26T22:33:37","Uptime":"45T07:09:02","Vcc":3.423,"SleepMode":"Dynamic","Sleep":50,"LoadA' . + 'vg":19,"POWER":"OFF","Wifi":{"AP":1,"SSId":"XXXXXXXX","BSSId":"AA:AA:AA:AA:AA:AA","Channel":3,"RSSI":60}', + 0 + ]; + return $mapVal; } diff --git a/tests/UtilitiesTest.php b/tests/UtilitiesTest.php index 5280468..2523576 100644 --- a/tests/UtilitiesTest.php +++ b/tests/UtilitiesTest.php @@ -5,6 +5,7 @@ namespace tests\unreal4u\MQTT; use PHPUnit\Framework\TestCase; +use unreal4u\MQTT\Exceptions\MessageTooBig; use unreal4u\MQTT\Utilities; class UtilitiesTest extends TestCase @@ -81,4 +82,60 @@ public function test_convertBinaryStringToNumber(int $expectedOutput, string $bi { $this->assertSame($expectedOutput, Utilities::convertBinaryStringToNumber(base64_decode($binaryString))); } + + /** + * Tests whether a message that is too big will throw an exception + */ + public function test_formatRemainingLengthOutputIsTooLarge() + { + $this->expectException(MessageTooBig::class); + Utilities::formatRemainingLengthOutput(268435456); + } + + /** + * @return array + */ + public function provider_remainingLength(): array + { + // Min. possible values, 1 byte long + $mapValues[] = [0, 'AA==']; + $mapValues[] = [1, 'AQ==']; + $mapValues[] = [2, 'Ag==']; + // Most probable cases, 1 to 2 bytes long + $mapValues[] = [24, 'GA==']; + $mapValues[] = [127, 'fw==']; + $mapValues[] = [128, 'gAE=']; + $mapValues[] = [129, 'gQE=']; + $mapValues[] = [200, 'yAE=']; + $mapValues[] = [364, '7AI=']; + $mapValues[] = [1023, '/wc=']; + // One extra byte + $mapValues[] = [16385, 'gYAB']; + $mapValues[] = [25897, 'qcoB']; + // Maximum number of bytes used in remaining length: 4 + $mapValues[] = [2097153, 'gYCAAQ==']; + $mapValues[] = [268435455, '////fw==']; + + return $mapValues; + } + + /** + * @dataProvider provider_remainingLength + * @param int $lengthInBytes + * @param string $expectedOutput + */ + public function test_formatRemainingLengthOutput(int $lengthInBytes, string $expectedOutput) + { + $this->assertSame($expectedOutput, base64_encode(Utilities::formatRemainingLengthOutput($lengthInBytes))); + } + + /** + * @dataProvider provider_remainingLength + * @param string $encodedLength + * @param int $expectedOutput + */ + public function test_convertRemainingLengthStringToInt(int $expectedOutput, string $encodedLength) + { + $this->assertSame($expectedOutput, Utilities::convertRemainingLengthStringToInt(base64_decode($encodedLength))); + } }