From ba8a3a61830cc29c58c5daa908996e16784a0666 Mon Sep 17 00:00:00 2001 From: "J. King" Date: Wed, 29 Apr 2020 12:20:45 -0400 Subject: [PATCH] Start on HTTP parser tests --- lib/Parser/HTTP/Message.php | 25 +- tests/cases/AbstractParserTestCase.php | 36 +- tests/cases/HTTP/HTTPTest.php | 31 ++ tests/cases/HTTP/http.yaml | 20 + tests/cases/README.md | 43 +++ tests/phpunit.dist.xml | 3 + vendor-bin/phpunit/composer.json | 4 +- vendor-bin/phpunit/composer.lock | 499 ++++++++++++++++++++++++- 8 files changed, 624 insertions(+), 37 deletions(-) create mode 100644 tests/cases/HTTP/HTTPTest.php create mode 100644 tests/cases/HTTP/http.yaml create mode 100644 tests/cases/README.md diff --git a/lib/Parser/HTTP/Message.php b/lib/Parser/HTTP/Message.php index 411ef2e..515fe19 100644 --- a/lib/Parser/HTTP/Message.php +++ b/lib/Parser/HTTP/Message.php @@ -14,12 +14,15 @@ use Psr\Http\Message\MessageInterface; class Message { protected const TYPE_PATTERN = '/^[A-Za-z0-9!#$%&\'*+\-\.\^_`|~]+\/[A-Za-z0-9!#$%&\'*+\-\.\^_`|~]+\s*(;.*)?$/s'; - protected const DATE_PATTERN = '/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d\d (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d\d:\d\d:\d\d GMT|(?:Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day, \d\d-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d\d \d\d:\d\d:\d\d GMT|(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:\d\d| \d) \d\d:\d\d:\d\d \d{4})$/'; + protected const DATE_PATTERN = '/^(?|(Mon|Tue|Wed|Thu|Fri|Sat|Sun), \d\d (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) \d{4} \d\d:\d\d:\d\d GMT|(Mon|Tues|Wednes|Thurs|Fri|Satur|Sun)day, \d\d-(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-\d\d \d\d:\d\d:\d\d GMT|(Mon|Tue|Wed|Thu|Fri|Sat|Sun) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:\d\d| \d) \d\d:\d\d:\d\d \d{4})$/'; protected const ETAG_PATTERN = '/^.+$/'; protected const CCON_PATTERN = '/(?:^|,)\s*[^=,]+(?:=(?:"(?:\\"|[^"])*"[^,]*|[^,]*))?/'; + protected const SDAY_MAP = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + protected const FDAY_MAP = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; /** @var \Psr\Http\Message\MessageInterface */ protected $msg; + protected $url; public function __construct(MessageInterface $msg, string $url = null) { $this->msg = $msg; @@ -29,6 +32,7 @@ class Message { } public function parse(FeedStruct $feed = null): FeedStruct { + $feed = $feed ?? new FeedStruct; $feed->meta->url = strlen($this->url ?? "") ? new Url($this->url) : null; $feed->meta->type = $this->getContentType(); $feed->meta->date = $this->getDate(); @@ -55,9 +59,17 @@ class Message { } protected function parseDate(string $name): ?Date { - $d = $this->parseHeader($name, self::DATE_PATTERN); - if ($d) { - return Date::createFromString($d[0]); + foreach ($this->msg->getHeader($name) as $h) { + if (preg_match(self::DATE_PATTERN, $h, $match)) { + $out = Date::createFromString($match[0]); + if ($out) { + // ensure the weekday is the correct day for the date + $day = $out->format("w"); + if (self::SDAY_MAP[$day] === $match[1] || self::FDAY_MAP[$day] === $match[1]) { + return $out; + } + } + } } return null; } @@ -88,7 +100,8 @@ class Message { public function getMaxAge(): ?\DateInterval { $c = $this->parseHeader("Cache-Control", self::CCON_PATTERN, true); - var_export($c); - exit; + //var_export($c); + //exit; + return null; } } diff --git a/tests/cases/AbstractParserTestCase.php b/tests/cases/AbstractParserTestCase.php index 4186e31..441551a 100644 --- a/tests/cases/AbstractParserTestCase.php +++ b/tests/cases/AbstractParserTestCase.php @@ -6,35 +6,6 @@ declare(strict_types=1); namespace MensBeam\Lax\TestCase; -/* Test format is as follows: - - Each test is a YAML map with the following keys: - - - `input`: The test input as a string - - `output`: The result of the parsing upon success; described in more detail below - - `exception`: The exception ID thrown upon failure - - `type`: An HTTP Content-Type (with or without parameters) for the document - - `doc_url`: A fictitious URL where a newsfeed might be located, used for relative URL resolution - - The 'input' key along with either 'output' or 'exception' are required for all tests. - - The test output is necessarily mangled due to the limits of YAML: - - - Any field which should be an absolute URL should be written as a string, - which will be transformed accordingly. Relative URLs should be represented - as a sequence with the relative part first, followed by the base that should - be applied to it - - Any collections should be represented as sequences of maps, which will - all be transformed accordingly - - Rich text can either be supplied as a string (which will yield a Text object - with plain-text content) or as a map with any of the properties of the - Text class listed - - The transformations as performed by the `makeFeed` and `makeEntry` methods - of the abstract test case. - -*/ - use MensBeam\Lax\Feed; use MensBeam\Lax\Date; use MensBeam\Lax\Text; @@ -52,6 +23,7 @@ use MensBeam\Lax\Enclosure\Collection as EnclosureCollection; use MensBeam\Lax\Parser\Exception; use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Parser as YamlParser; +use GuzzleHttp\Psr7\Response; class AbstractParserTestCase extends \PHPUnit\Framework\TestCase { protected function provideParserTests(string $glob): iterable { @@ -62,6 +34,10 @@ class AbstractParserTestCase extends \PHPUnit\Framework\TestCase { } else { $test->output = $this->makeFeed($test->output); } + if (is_object($test->input)) { + assert((isset($test->input->head) && $test->input->head instanceof \stdClass) || (isset($test->input->body) && is_string($test->input->body)), "Input is not in a correct format"); + $test->input = new Response($test->input->status ?? 200, (array) ($test->input->head ?? []), $test->input->body ?? null); + } yield "$file: {$description}" => [ $test->input, $test->type ?? "", @@ -203,6 +179,8 @@ class AbstractParserTestCase extends \PHPUnit\Framework\TestCase { foreach ($meta as $k => $v) { if ($k === 'url') { $m->$k = new Url($v); + } elseif (in_array($k, ["date", "lastModified", "expires"])) { + $m->$k = new Date($v); } else { $m->$k = $v; } diff --git a/tests/cases/HTTP/HTTPTest.php b/tests/cases/HTTP/HTTPTest.php new file mode 100644 index 0000000..29b2524 --- /dev/null +++ b/tests/cases/HTTP/HTTPTest.php @@ -0,0 +1,31 @@ +expectExceptionObject($exp); + $p->parse(); + } else { + $act = $p->parse(); + $this->assertEquals($exp, $act); + } + } + + public function provideHTTPMessages(): iterable { + foreach ($this->provideParserTests(__DIR__."/*.yaml") as $k => $t) { + array_splice($t, 1, 1); + yield $k => $t; + } + } +} diff --git a/tests/cases/HTTP/http.yaml b/tests/cases/HTTP/http.yaml new file mode 100644 index 0000000..b29e4b8 --- /dev/null +++ b/tests/cases/HTTP/http.yaml @@ -0,0 +1,20 @@ +Dates: + input: + head: + Date: 'Thu, 05 Mar 2020 23:08:55 GMT' + Expires: 'Fri, 06 Mar 2020 09:22:00 GMT' + Last-Modified: 'Tue, 03 Mar 2020 06:14:00 GMT' + output: + meta: + date: '2020-03-05T23:08:55Z' + expires: '2020-03-06T09:22:00Z' + lastModified: '2020-03-03T06:14:00Z' + +Invalid dates: + input: + head: + Date: 'Thu, 05 Mar 2020 25:08:55 GMT' + Expires: 'Fri, 06 Mar 2020 09:22:00 EST' + Last-Modified: 'Wed, 03 Mar 2020 06:14:00 GMT' + output: + meta: {} diff --git a/tests/cases/README.md b/tests/cases/README.md new file mode 100644 index 0000000..e068985 --- /dev/null +++ b/tests/cases/README.md @@ -0,0 +1,43 @@ +# The Lax test suite + +## Feed parsing tests + +The tests under the `JSON`, `XML`, and `HTTP` directories are parser tests +with a shared format and test harness. The test harness is largely contained +in the `AbstractParserTestCase.php` file, which contains the logic to +transform the tests themselves into native PHP input and output. + +Tests are series of YAML files each containing a map (an object), each entry +an individual test which is itself a map. Tests have the following keys: + +- `input`: The test input as a string, or a map (see below) +- `output`: The result of the parsing upon success; described + in more detail below +- `exception`: The exception ID thrown upon failure +- `type`: An HTTP Content-Type (with or without parameters) for + the document +- `doc_url`: A fictitious URL where a newsfeed might be located, + used for relative URL resolution + +The 'input' key along with either 'output' or 'exception' are required for +all tests. All other keys may or may not be omitted in a test. + +The test output is necessarily mangled due to the limits of YAML: + +- Any field which should be an absolute URL is written as a string; relative + URLs are represented as a sequence with the relative part first, followed + by the base that is be applied to it +- Any collections are represented as sequences of maps +- Rich text can either be supplied as a string (which represents a Text object + with plain-text content) or as a map with any of the properties of the + Text class listed + +If the test input is a map, it represents a response from an HTTP server, +with the following keys: + +- `status`: The response status code. This is usually omitted + and should be assumed to be 200 unless specified +- `head`: The map of header-field in the response. Each entry + may be a string, or a sequence of strings +- `body`: The message body as a a string; it may be omitted + if not relevant to the functionality being tested diff --git a/tests/phpunit.dist.xml b/tests/phpunit.dist.xml index 800e430..fac6e43 100644 --- a/tests/phpunit.dist.xml +++ b/tests/phpunit.dist.xml @@ -31,5 +31,8 @@ cases/XML + + cases/HTTP + diff --git a/vendor-bin/phpunit/composer.json b/vendor-bin/phpunit/composer.json index bc16564..962cc3b 100644 --- a/vendor-bin/phpunit/composer.json +++ b/vendor-bin/phpunit/composer.json @@ -1,6 +1,8 @@ { "require-dev": { "phpunit/phpunit": "^9.0", - "symfony/yaml": "^5.0" + "symfony/yaml": "^5.0", + "guzzlehttp/guzzle": "^6.5", + "guzzlehttp/psr7": "^1.6" } } diff --git a/vendor-bin/phpunit/composer.lock b/vendor-bin/phpunit/composer.lock index b4bc2ea..3b76126 100644 --- a/vendor-bin/phpunit/composer.lock +++ b/vendor-bin/phpunit/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "5f6ef85b0137813e2008ed286079c104", + "content-hash": "4259aa457f6935c16ad776bc1a3897ea", "packages": [], "packages-dev": [ { @@ -63,6 +63,195 @@ ], "time": "2019-10-21T16:45:58+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "6.5.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/aab4ebd862aa7d04f01a4b51849d657db56d882e", + "reference": "aab4ebd862aa7d04f01a4b51849d657db56d882e", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.6.1", + "php": ">=5.5", + "symfony/polyfill-intl-idn": "^1.11" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.1" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.5-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2020-04-18T10:38:46+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2019-07-01T23:21:34+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.9.5", @@ -825,6 +1014,96 @@ ], "time": "2020-04-03T14:40:04+00:00" }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+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.", + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "sebastian/code-unit", "version": "1.0.0", @@ -1561,6 +1840,224 @@ ], "time": "2020-02-27T09:26:54+00:00" }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "reference": "47bd6aa45beb1cd7c6a16b7d1810133b728bdfcf", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "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": "2020-03-09T19:04:49+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "reference": "81ffd3a9c6d707be22e3012b827de1c9775fc5ac", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "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": "2020-03-09T19:04:49+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.15.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "37b0976c78b94856543260ce09b460a7bc852747" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/37b0976c78b94856543260ce09b460a7bc852747", + "reference": "37b0976c78b94856543260ce09b460a7bc852747", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.15-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "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": "2020-02-27T09:26:54+00:00" + }, { "name": "symfony/yaml", "version": "v5.0.7",