From 7e6cdf29e3120638aeb0222abf99b47fac3ee993 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Thu, 1 Aug 2019 18:13:22 +0200 Subject: [PATCH 01/12] Isolate headers building in a dedicated class --- Controller/StreamController.php | 61 ++++++----------- .../DebrilRssAtomExtension.php | 2 +- Resources/config/services.yml | 16 +++++ Response/HeadersBuilder.php | 68 +++++++++++++++++++ Tests/Response/HeadersBuilderTest.php | 45 ++++++++++++ 5 files changed, 152 insertions(+), 40 deletions(-) create mode 100644 Response/HeadersBuilder.php create mode 100644 Tests/Response/HeadersBuilderTest.php diff --git a/Controller/StreamController.php b/Controller/StreamController.php index 906ba2c..ecc5010 100644 --- a/Controller/StreamController.php +++ b/Controller/StreamController.php @@ -2,13 +2,14 @@ namespace Debril\RssAtomBundle\Controller; +use Debril\RssAtomBundle\Response\HeadersBuilder; +use Debril\RssAtomBundle\Provider\FeedContentProviderInterface; +use Debril\RssAtomBundle\Exception\FeedException\FeedNotFoundException; use FeedIo\FeedIo; use FeedIo\FeedInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; -use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; -use Debril\RssAtomBundle\Provider\FeedContentProviderInterface; -use Debril\RssAtomBundle\Exception\FeedException\FeedNotFoundException; +use Symfony\Component\HttpFoundation\Response; /** * Class StreamController. @@ -21,6 +22,20 @@ class StreamController extends AbstractController */ protected $since; + /** + * @var HeadersBuilder + */ + private $headersBuilder; + + /** + * StreamController constructor. + * @param $headersBuilder + */ + public function __construct(HeadersBuilder $headersBuilder) + { + $this->headersBuilder = $headersBuilder; + } + /** * @param Request $request * @param FeedContentProviderInterface $provider @@ -90,11 +105,11 @@ protected function setModifiedSince(Request $request) : self */ protected function createStreamResponse(array $options, string $format, FeedContentProviderInterface $provider, FeedIo $feedIo) : Response { - $content = $this->getContent($options, $provider); + $feed = $this->getContent($options, $provider); - if ($this->mustForceRefresh() || $content->getLastModified() > $this->getModifiedSince()) { - $response = new Response($feedIo->format($content, $format)); - $this->setFeedHeaders($response, $content, $format); + if ($this->mustForceRefresh() || $feed->getLastModified() > $this->getModifiedSince()) { + $response = new Response($feedIo->format($feed, $format)); + $this->headersBuilder->setResponseHeaders($response, $format, $feed->getLastModified()); } else { $response = new Response(); @@ -104,30 +119,6 @@ protected function createStreamResponse(array $options, string $format, FeedCont return $response; } - /** - * @param Response $response - * @param FeedInterface $feed - * @param string $format - * @return $this - */ - protected function setFeedHeaders(Response $response, FeedInterface $feed, string $format) : self - { - $contentType = - 'json' == $format ? - $this->getParameter('debril_rss_atom.content_type_json') : - $this->getParameter('debril_rss_atom.content_type_xml') - ; - $response->headers->set('Content-Type', $contentType); - if (! $this->isPrivate() ) { - $response->setPublic(); - } - - $response->setMaxAge(3600); - $response->setLastModified($feed->getLastModified()); - - return $this; - } - /** * Get the Stream's content using a FeedContentProviderInterface * The FeedContentProviderInterface instance is provided as a service @@ -159,12 +150,4 @@ protected function mustForceRefresh() : bool return $this->getParameter('debril_rss_atom.force_refresh'); } - /** - * @return boolean true if the feed must be private - */ - protected function isPrivate() : bool - { - return $this->getParameter('debril_rss_atom.private_feeds'); - } - } diff --git a/DependencyInjection/DebrilRssAtomExtension.php b/DependencyInjection/DebrilRssAtomExtension.php index bbfb255..1a55ea0 100644 --- a/DependencyInjection/DebrilRssAtomExtension.php +++ b/DependencyInjection/DebrilRssAtomExtension.php @@ -45,7 +45,7 @@ public function load(array $configs, ContainerBuilder $container) : void $loader->load('services.yml'); $this->setDateFormats($container, $config); - $container->setParameter('debril_rss_atom.private_feeds', $config['private']); + $container->setParameter('debril_rss_atom.public_feeds', !$config['private']); $container->setParameter('debril_rss_atom.force_refresh', $config['force_refresh']); $container->setParameter('debril_rss_atom.content_type_json', $config['content_type_json']); $container->setParameter('debril_rss_atom.content_type_xml', $config['content_type_xml']); diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 7faacd2..8804cdb 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -27,6 +27,22 @@ services: FeedIo\FeedIo: alias: feedio + debril.rss_atom.response.headers: + class: Debril\RssAtomBundle\Response\HeadersBuilder + arguments: ["%debril_rss_atom.public_feeds%"] + calls: + - method: setContentType + arguments: + - "json" + - "%debril_rss_atom.content_type_json%" + - method: setContentType + arguments: + - "xml" + - "%debril_rss_atom.content_type_xml%" + + Debril\RssAtomBundle\Response\HeadersBuilder: + alias: debril.rss_atom.response.headers + debril.rss_atom.provider: class: '%debril.rss_atom.provider.class%' diff --git a/Response/HeadersBuilder.php b/Response/HeadersBuilder.php new file mode 100644 index 0000000..7fe1115 --- /dev/null +++ b/Response/HeadersBuilder.php @@ -0,0 +1,68 @@ + self::DEFAULT_XML_CONTENT_TYPE + ]; + + /** + * if true, the response is marked s public + * @var bool + */ + private $public; + + /** + * maximum amount of time before the cache gets invalidated (in seconds) + * @var int + */ + private $maxAge; + + /** + * HeadersBuilder constructor. + * @param $public + * @param $maxAge + */ + public function __construct(bool $public = true, int $maxAge = self::DEFAULT_MAX_AGE) + { + $this->public = $public; + $this->maxAge = $maxAge; + } + + public function setContentType(string $format, $value): void + { + $this->contentTypes[$format] = $value; + } + + public function setResponseHeaders(Response $response, string $format, \DateTime $lastModified): void + { + $response->headers->set('Content-Type', $this->getContentType($format)); + + $this->public ? $response->setPublic() : $response->setPrivate(); + + $response->setMaxAge($this->maxAge); + $response->setLastModified($lastModified); + } + + private function getContentType(string $format): string + { + return $this->contentTypes[$format] ?? $this->contentTypes[self::FORMAT_XML]; + } + +} diff --git a/Tests/Response/HeadersBuilderTest.php b/Tests/Response/HeadersBuilderTest.php new file mode 100644 index 0000000..d7daf9d --- /dev/null +++ b/Tests/Response/HeadersBuilderTest.php @@ -0,0 +1,45 @@ +setResponseHeaders($response, 'xml', new \DateTime()); + $this->assertTrue($response->headers->getCacheControlDirective('public')); + $this->assertEquals(3600, $response->getMaxAge()); + } + + public function testSetResponseHeadersPrivate() + { + $builder = new HeadersBuilder(false, 30); + $response = new Response(); + + $builder->setResponseHeaders($response, 'xml', new \DateTime()); + $this->assertTrue($response->headers->getCacheControlDirective('private')); + $this->assertEquals(30, $response->getMaxAge()); + } + + public function testSetLastModified() + { + $builder = new HeadersBuilder(); + $response = new Response(); + $date = new \DateTime('2018-01-01'); + + $builder->setResponseHeaders($response, 'xml', $date); + $this->assertEquals($date, $response->getLastModified()); + } + + +} From 27823553bc6ae7614826034be315e0cbf57cc5bf Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Fri, 2 Aug 2019 17:46:43 +0200 Subject: [PATCH 02/12] Isolate the modified-since computation --- Controller/StreamController.php | 49 ++++++++------------------------- Request/ModifiedSince.php | 38 +++++++++++++++++++++++++ Resources/config/services.yml | 2 ++ 3 files changed, 52 insertions(+), 37 deletions(-) create mode 100644 Request/ModifiedSince.php diff --git a/Controller/StreamController.php b/Controller/StreamController.php index ecc5010..54d92df 100644 --- a/Controller/StreamController.php +++ b/Controller/StreamController.php @@ -2,6 +2,7 @@ namespace Debril\RssAtomBundle\Controller; +use Debril\RssAtomBundle\Request\ModifiedSince; use Debril\RssAtomBundle\Response\HeadersBuilder; use Debril\RssAtomBundle\Provider\FeedContentProviderInterface; use Debril\RssAtomBundle\Exception\FeedException\FeedNotFoundException; @@ -27,13 +28,20 @@ class StreamController extends AbstractController */ private $headersBuilder; + /** + * @var ModifiedSince + */ + private $modifiedSince; + /** * StreamController constructor. - * @param $headersBuilder + * @param HeadersBuilder $headersBuilder + * @param ModifiedSince $modifiedSince */ - public function __construct(HeadersBuilder $headersBuilder) + public function __construct(HeadersBuilder $headersBuilder, ModifiedSince $modifiedSince) { $this->headersBuilder = $headersBuilder; + $this->modifiedSince = $modifiedSince; } /** @@ -46,8 +54,7 @@ public function __construct(HeadersBuilder $headersBuilder) public function indexAction(Request $request, FeedContentProviderInterface $provider, FeedIo $feedIo) : Response { $options = $request->attributes->get('_route_params'); - $this->setModifiedSince($request); - $options['Since'] = $this->getModifiedSince(); + $options['Since'] = $this->modifiedSince->getValue(); return $this->createStreamResponse( $options, @@ -57,38 +64,6 @@ public function indexAction(Request $request, FeedContentProviderInterface $prov ); } - /** - * Extract the 'If-Modified-Since' value from the headers. - * - * @return \DateTime - */ - protected function getModifiedSince() : \DateTime - { - if (is_null($this->since)) { - $this->since = new \DateTime('@0'); - } - - return $this->since; - } - - /** - * @param Request $request - * - * @return $this - */ - protected function setModifiedSince(Request $request) : self - { - $this->since = new \DateTime(); - if ($request->headers->has('If-Modified-Since')) { - $string = $request->headers->get('If-Modified-Since'); - $this->since = \DateTime::createFromFormat(\DateTime::RSS, $string); - } else { - $this->since->setTimestamp(1); - } - - return $this; - } - /** * Generate the HTTP response * 200 : a full body containing the stream @@ -107,7 +82,7 @@ protected function createStreamResponse(array $options, string $format, FeedCont { $feed = $this->getContent($options, $provider); - if ($this->mustForceRefresh() || $feed->getLastModified() > $this->getModifiedSince()) { + if ($this->mustForceRefresh() || $feed->getLastModified() > $this->modifiedSince->getValue()) { $response = new Response($feedIo->format($feed, $format)); $this->headersBuilder->setResponseHeaders($response, $format, $feed->getLastModified()); diff --git a/Request/ModifiedSince.php b/Request/ModifiedSince.php new file mode 100644 index 0000000..841dc31 --- /dev/null +++ b/Request/ModifiedSince.php @@ -0,0 +1,38 @@ +value = $this->getModifiedSince($requestStack->getCurrentRequest()); + } + + /** + * @return \DateTime + */ + public function getValue(): \DateTime + { + return $this->value; + } + + private function getModifiedSince(Request $request):\DateTime + { + if ($request->headers->has(self::HTTP_HEADER_NAME)) { + $string = $request->headers->get(self::HTTP_HEADER_NAME); + return \DateTime::createFromFormat(\DateTime::RSS, $string); + } + + return new \DateTime('@1'); + } +} diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 8804cdb..5748958 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -40,6 +40,8 @@ services: - "xml" - "%debril_rss_atom.content_type_xml%" + Debril\RssAtomBundle\Request\ModifiedSince: ~ + Debril\RssAtomBundle\Response\HeadersBuilder: alias: debril.rss_atom.response.headers From 93f21183531923e38bea0982530a09626190f1d9 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Mon, 5 Aug 2019 18:35:19 +0200 Subject: [PATCH 03/12] Missing test for ModifiedSince --- Tests/Request/ModifiedSinceTest.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 Tests/Request/ModifiedSinceTest.php diff --git a/Tests/Request/ModifiedSinceTest.php b/Tests/Request/ModifiedSinceTest.php new file mode 100644 index 0000000..60ea40a --- /dev/null +++ b/Tests/Request/ModifiedSinceTest.php @@ -0,0 +1,27 @@ +headers->set('If-Modified-Since', $date->format(\DATE_RSS)); + $stack->push($request); + + $modifiedSince = new ModifiedSince($stack); + $this->assertEquals($date, $modifiedSince->getValue()); + } + +} From 8cf42d1db6bd0360ece8b602523dc03b13c5349e Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Mon, 5 Aug 2019 18:35:33 +0200 Subject: [PATCH 04/12] Build the stream inside the action --- Controller/StreamController.php | 46 +++++++-------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/Controller/StreamController.php b/Controller/StreamController.php index 54d92df..4f7e987 100644 --- a/Controller/StreamController.php +++ b/Controller/StreamController.php @@ -33,15 +33,22 @@ class StreamController extends AbstractController */ private $modifiedSince; + /** + * @var bool + */ + private $forceRefresh; + /** * StreamController constructor. * @param HeadersBuilder $headersBuilder * @param ModifiedSince $modifiedSince + * @param bool $forceRefresh */ - public function __construct(HeadersBuilder $headersBuilder, ModifiedSince $modifiedSince) + public function __construct(HeadersBuilder $headersBuilder, ModifiedSince $modifiedSince, bool $forceRefresh = false) { $this->headersBuilder = $headersBuilder; $this->modifiedSince = $modifiedSince; + $this->forceRefresh = $forceRefresh; } /** @@ -56,33 +63,10 @@ public function indexAction(Request $request, FeedContentProviderInterface $prov $options = $request->attributes->get('_route_params'); $options['Since'] = $this->modifiedSince->getValue(); - return $this->createStreamResponse( - $options, - $request->get('format', 'rss'), - $provider, - $feedIo - ); - } - - /** - * Generate the HTTP response - * 200 : a full body containing the stream - * 304 : Not modified. - * - * @param array $options - * @param $format - * @param FeedContentProviderInterface $provider - * @param FeedIo $feedIo - * - * @return Response - * - * @throws \Exception - */ - protected function createStreamResponse(array $options, string $format, FeedContentProviderInterface $provider, FeedIo $feedIo) : Response - { $feed = $this->getContent($options, $provider); + $format = $request->get('format', 'rss'); - if ($this->mustForceRefresh() || $feed->getLastModified() > $this->modifiedSince->getValue()) { + if ($this->forceRefresh || $feed->getLastModified() > $this->modifiedSince->getValue()) { $response = new Response($feedIo->format($feed, $format)); $this->headersBuilder->setResponseHeaders($response, $format, $feed->getLastModified()); @@ -115,14 +99,4 @@ protected function getContent(array $options, FeedContentProviderInterface $prov } } - /** - * Returns true if the controller must ignore the last modified date. - * - * @return bool - */ - protected function mustForceRefresh() : bool - { - return $this->getParameter('debril_rss_atom.force_refresh'); - } - } From 0d342bcd2ce699a55de1f33a0f8435ba25e9ffeb Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Tue, 6 Aug 2019 09:36:09 +0200 Subject: [PATCH 05/12] StreamController not dependent of AbstractController anymore --- Controller/StreamController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Controller/StreamController.php b/Controller/StreamController.php index 4f7e987..3a69d9d 100644 --- a/Controller/StreamController.php +++ b/Controller/StreamController.php @@ -8,14 +8,14 @@ use Debril\RssAtomBundle\Exception\FeedException\FeedNotFoundException; use FeedIo\FeedIo; use FeedIo\FeedInterface; -use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class StreamController. */ -class StreamController extends AbstractController +class StreamController { /** @@ -95,7 +95,7 @@ protected function getContent(array $options, FeedContentProviderInterface $prov try { return $provider->getFeedContent($options); } catch (FeedNotFoundException $e) { - throw $this->createNotFoundException('feed not found'); + throw new NotFoundHttpException('feed not found'); } } From afa71bf306997cd29d624b0235d92d0c59058149 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Tue, 6 Aug 2019 09:44:55 +0200 Subject: [PATCH 06/12] symfony/framework-bundle no longer needed --- composer.json | 1 - composer.lock | 1099 +++++++++++++++++++++++++------------------------ 2 files changed, 550 insertions(+), 550 deletions(-) diff --git a/composer.json b/composer.json index 09a65aa..c96706d 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,6 @@ "symfony/http-foundation": "~3.4|~4.0", "symfony/http-kernel": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", - "symfony/framework-bundle": "~3.4|~4.0", "debril/feed-io": "~3.0|~4.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 477c231..6453194 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4cb60f21dbd9c8c57d32a02340871bb2", + "content-hash": "4a5641c0c0941c996ab7a4d949f7b738", "packages": [ { "name": "debril/feed-io", @@ -253,52 +253,6 @@ ], "time": "2019-07-01T23:21:34+00:00" }, - { - "name": "psr/cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", - "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "time": "2016-08-06T20:24:11+00:00" - }, { "name": "psr/container", "version": "1.0.0", @@ -485,142 +439,6 @@ "description": "A polyfill for getallheaders.", "time": "2019-03-08T08:55:37+00:00" }, - { - "name": "symfony/cache", - "version": "v4.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "4acf343c9e3aea5a00d51926c01125441707635c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/4acf343c9e3aea5a00d51926c01125441707635c", - "reference": "4acf343c9e3aea5a00d51926c01125441707635c", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "psr/cache": "~1.0", - "psr/log": "~1.0", - "symfony/cache-contracts": "^1.1", - "symfony/service-contracts": "^1.1", - "symfony/var-exporter": "^4.2" - }, - "conflict": { - "doctrine/dbal": "<2.5", - "symfony/dependency-injection": "<3.4", - "symfony/var-dumper": "<3.4" - }, - "provide": { - "psr/cache-implementation": "1.0", - "psr/simple-cache-implementation": "1.0", - "symfony/cache-implementation": "1.0" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/cache": "~1.6", - "doctrine/dbal": "~2.5", - "predis/predis": "~1.1", - "psr/simple-cache": "^1.0", - "symfony/config": "~4.2", - "symfony/dependency-injection": "~3.4|~4.1", - "symfony/var-dumper": "^4.1.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Cache\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "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 Cache component with PSR-6, PSR-16, and tags", - "homepage": "https://symfony.com", - "keywords": [ - "caching", - "psr6" - ], - "time": "2019-06-26T07:55:28+00:00" - }, - { - "name": "symfony/cache-contracts", - "version": "v1.1.5", - "source": { - "type": "git", - "url": "https://github.com/symfony/cache-contracts.git", - "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", - "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "psr/cache": "^1.0" - }, - "suggest": { - "symfony/cache-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\Cache\\": "" - } - }, - "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": "Generic abstractions related to caching", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "time": "2019-06-13T11:15:36+00:00" - }, { "name": "symfony/config", "version": "v4.3.2", @@ -1019,7 +837,7 @@ }, { "name": "symfony/filesystem", - "version": "v4.3.2", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -1068,21 +886,27 @@ "time": "2019-06-23T08:51:25+00:00" }, { - "name": "symfony/finder", + "name": "symfony/http-foundation", "version": "v4.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "33c21f7d5d3dc8a140c282854a7e13aeb5d0f91a" + "url": "https://github.com/symfony/http-foundation.git", + "reference": "e1b507fcfa4e87d192281774b5ecd4265370180d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/33c21f7d5d3dc8a140c282854a7e13aeb5d0f91a", - "reference": "33c21f7d5d3dc8a140c282854a7e13aeb5d0f91a", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e1b507fcfa4e87d192281774b5ecd4265370180d", + "reference": "e1b507fcfa4e87d192281774b5ecd4265370180d", "shasum": "" }, "require": { - "php": "^7.1.3" + "php": "^7.1.3", + "symfony/mime": "^4.3", + "symfony/polyfill-mbstring": "~1.1" + }, + "require-dev": { + "predis/predis": "~1.0", + "symfony/expression-language": "~3.4|~4.0" }, "type": "library", "extra": { @@ -1092,7 +916,7 @@ }, "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\HttpFoundation\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -1112,223 +936,46 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-06-13T11:03:18+00:00" + "time": "2019-06-26T09:25:00+00:00" }, { - "name": "symfony/framework-bundle", + "name": "symfony/http-kernel", "version": "v4.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/framework-bundle.git", - "reference": "5aab516cef8e3772d6f7daa3ab62cd38713aae08" + "url": "https://github.com/symfony/http-kernel.git", + "reference": "4150f71e27ed37a74700561b77e3dbd754cbb44d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/5aab516cef8e3772d6f7daa3ab62cd38713aae08", - "reference": "5aab516cef8e3772d6f7daa3ab62cd38713aae08", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4150f71e27ed37a74700561b77e3dbd754cbb44d", + "reference": "4150f71e27ed37a74700561b77e3dbd754cbb44d", "shasum": "" }, "require": { - "ext-xml": "*", "php": "^7.1.3", - "symfony/cache": "~4.3", - "symfony/config": "~4.2", - "symfony/dependency-injection": "^4.3", - "symfony/filesystem": "~3.4|~4.0", - "symfony/finder": "~3.4|~4.0", - "symfony/http-foundation": "^4.3", - "symfony/http-kernel": "^4.3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/routing": "^4.3" + "psr/log": "~1.0", + "symfony/debug": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", + "symfony/http-foundation": "^4.1.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php73": "^1.9" }, "conflict": { - "phpdocumentor/reflection-docblock": "<3.0", - "phpdocumentor/type-resolver": "<0.2.1", - "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", - "symfony/asset": "<3.4", "symfony/browser-kit": "<4.3", - "symfony/console": "<4.3", - "symfony/dom-crawler": "<4.3", - "symfony/dotenv": "<4.2", - "symfony/form": "<4.3", - "symfony/messenger": "<4.3", - "symfony/property-info": "<3.4", - "symfony/serializer": "<4.2", - "symfony/stopwatch": "<3.4", - "symfony/translation": "<4.3", - "symfony/twig-bridge": "<4.1.1", - "symfony/validator": "<4.1", - "symfony/workflow": "<4.3" + "symfony/config": "<3.4", + "symfony/dependency-injection": "<4.3", + "symfony/translation": "<4.2", + "symfony/var-dumper": "<4.1.1", + "twig/twig": "<1.34|<2.4,>=2" + }, + "provide": { + "psr/log-implementation": "1.0" }, "require-dev": { - "doctrine/annotations": "~1.0", - "doctrine/cache": "~1.0", - "fig/link-util": "^1.0", - "phpdocumentor/reflection-docblock": "^3.0|^4.0", - "symfony/asset": "~3.4|~4.0", - "symfony/browser-kit": "^4.3", - "symfony/console": "^4.3", - "symfony/css-selector": "~3.4|~4.0", - "symfony/dom-crawler": "^4.3", - "symfony/expression-language": "~3.4|~4.0", - "symfony/form": "^4.3", - "symfony/http-client": "^4.3", - "symfony/lock": "~3.4|~4.0", - "symfony/mailer": "^4.3", - "symfony/messenger": "^4.3", - "symfony/mime": "^4.3", - "symfony/polyfill-intl-icu": "~1.0", - "symfony/process": "~3.4|~4.0", - "symfony/property-info": "~3.4|~4.0", - "symfony/security-csrf": "~3.4|~4.0", - "symfony/security-http": "~3.4|~4.0", - "symfony/serializer": "^4.3", - "symfony/stopwatch": "~3.4|~4.0", - "symfony/templating": "~3.4|~4.0", - "symfony/translation": "~4.3", - "symfony/twig-bundle": "~2.8|~3.2|~4.0", - "symfony/validator": "^4.1", - "symfony/var-dumper": "^4.3", - "symfony/web-link": "~3.4|~4.0", - "symfony/workflow": "^4.3", - "symfony/yaml": "~3.4|~4.0", - "twig/twig": "~1.34|~2.4" - }, - "suggest": { - "ext-apcu": "For best performance of the system caches", - "symfony/console": "For using the console commands", - "symfony/form": "For using forms", - "symfony/property-info": "For using the property_info service", - "symfony/serializer": "For using the serializer service", - "symfony/validator": "For using validation", - "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", - "symfony/yaml": "For using the debug:config and lint:yaml commands" - }, - "type": "symfony-bundle", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Bundle\\FrameworkBundle\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony FrameworkBundle", - "homepage": "https://symfony.com", - "time": "2019-06-26T06:50:02+00:00" - }, - { - "name": "symfony/http-foundation", - "version": "v4.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "e1b507fcfa4e87d192281774b5ecd4265370180d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e1b507fcfa4e87d192281774b5ecd4265370180d", - "reference": "e1b507fcfa4e87d192281774b5ecd4265370180d", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "symfony/mime": "^4.3", - "symfony/polyfill-mbstring": "~1.1" - }, - "require-dev": { - "predis/predis": "~1.0", - "symfony/expression-language": "~3.4|~4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\HttpFoundation\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony HttpFoundation Component", - "homepage": "https://symfony.com", - "time": "2019-06-26T09:25:00+00:00" - }, - { - "name": "symfony/http-kernel", - "version": "v4.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/http-kernel.git", - "reference": "4150f71e27ed37a74700561b77e3dbd754cbb44d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/4150f71e27ed37a74700561b77e3dbd754cbb44d", - "reference": "4150f71e27ed37a74700561b77e3dbd754cbb44d", - "shasum": "" - }, - "require": { - "php": "^7.1.3", - "psr/log": "~1.0", - "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "^4.3", - "symfony/http-foundation": "^4.1.1", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php73": "^1.9" - }, - "conflict": { - "symfony/browser-kit": "<4.3", - "symfony/config": "<3.4", - "symfony/dependency-injection": "<4.3", - "symfony/translation": "<4.2", - "symfony/var-dumper": "<4.1.1", - "twig/twig": "<1.34|<2.4,>=2" - }, - "provide": { - "psr/log-implementation": "1.0" - }, - "require-dev": { - "psr/cache": "~1.0", + "psr/cache": "~1.0", "symfony/browser-kit": "^4.3", "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", @@ -1736,82 +1383,6 @@ ], "time": "2019-02-06T07:57:58+00:00" }, - { - "name": "symfony/routing", - "version": "v4.3.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/routing.git", - "reference": "2ef809021d72071c611b218c47a3bf3b17b7325e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/2ef809021d72071c611b218c47a3bf3b17b7325e", - "reference": "2ef809021d72071c611b218c47a3bf3b17b7325e", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "conflict": { - "symfony/config": "<4.2", - "symfony/dependency-injection": "<3.4", - "symfony/yaml": "<3.4" - }, - "require-dev": { - "doctrine/annotations": "~1.2", - "psr/log": "~1.0", - "symfony/config": "~4.2", - "symfony/dependency-injection": "~3.4|~4.0", - "symfony/expression-language": "~3.4|~4.0", - "symfony/http-foundation": "~3.4|~4.0", - "symfony/yaml": "~3.4|~4.0" - }, - "suggest": { - "doctrine/annotations": "For using the annotation loader", - "symfony/config": "For using the all-in-one router or any loader", - "symfony/expression-language": "For using expression matching", - "symfony/http-foundation": "For using a Symfony Request object", - "symfony/yaml": "For using the YAML loader" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Routing\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Routing Component", - "homepage": "https://symfony.com", - "keywords": [ - "router", - "routing", - "uri", - "url" - ], - "time": "2019-06-26T13:54:39+00:00" - }, { "name": "symfony/service-contracts", "version": "v1.1.5", @@ -1869,90 +1440,30 @@ "standards" ], "time": "2019-06-13T11:15:36+00:00" - }, + } + ], + "packages-dev": [ { - "name": "symfony/var-exporter", - "version": "v4.3.2", + "name": "doctrine/annotations", + "version": "v1.6.1", "source": { "type": "git", - "url": "https://github.com/symfony/var-exporter.git", - "reference": "9dee83031dcf6dcb53bb7ec1c51de085329bf5cb" + "url": "https://github.com/doctrine/annotations.git", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/9dee83031dcf6dcb53bb7ec1c51de085329bf5cb", - "reference": "9dee83031dcf6dcb53bb7ec1c51de085329bf5cb", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", "shasum": "" }, "require": { - "php": "^7.1.3" + "doctrine/lexer": "1.*", + "php": "^7.1" }, "require-dev": { - "symfony/var-dumper": "^4.1.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.3-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\VarExporter\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "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": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", - "homepage": "https://symfony.com", - "keywords": [ - "clone", - "construct", - "export", - "hydrate", - "instantiate", - "serialize" - ], - "time": "2019-06-22T08:39:44+00:00" - } - ], - "packages-dev": [ - { - "name": "doctrine/annotations", - "version": "v1.6.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/annotations.git", - "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", - "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", - "shasum": "" - }, - "require": { - "doctrine/lexer": "1.*", - "php": "^7.1" - }, - "require-dev": { - "doctrine/cache": "1.*", - "phpunit/phpunit": "^6.4" + "doctrine/cache": "1.*", + "phpunit/phpunit": "^6.4" }, "type": "library", "extra": { @@ -3653,6 +3164,52 @@ ], "time": "2019-07-15T06:24:08+00:00" }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "time": "2016-08-06T20:24:11+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -4278,6 +3835,142 @@ "homepage": "https://symfony.com", "time": "2019-06-11T15:41:59+00:00" }, + { + "name": "symfony/cache", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "d263af3cec33afa862310e58545fdc10d779806f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/d263af3cec33afa862310e58545fdc10d779806f", + "reference": "d263af3cec33afa862310e58545fdc10d779806f", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "~1.0", + "psr/log": "~1.0", + "symfony/cache-contracts": "^1.1", + "symfony/service-contracts": "^1.1", + "symfony/var-exporter": "^4.2" + }, + "conflict": { + "doctrine/dbal": "<2.5", + "symfony/dependency-injection": "<3.4", + "symfony/var-dumper": "<3.4" + }, + "provide": { + "psr/cache-implementation": "1.0", + "psr/simple-cache-implementation": "1.0", + "symfony/cache-implementation": "1.0" + }, + "require-dev": { + "cache/integration-tests": "dev-master", + "doctrine/cache": "~1.6", + "doctrine/dbal": "~2.5", + "predis/predis": "~1.1", + "psr/simple-cache": "^1.0", + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.1", + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Cache\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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 Cache component with PSR-6, PSR-16, and tags", + "homepage": "https://symfony.com", + "keywords": [ + "caching", + "psr6" + ], + "time": "2019-06-28T13:16:30+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v1.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", + "reference": "ec5524b669744b5f1dc9c66d3c2b091eb7e7f0db", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "psr/cache": "^1.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "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": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-06-13T11:15:36+00:00" + }, { "name": "symfony/doctrine-bridge", "version": "v4.3.2", @@ -4430,29 +4123,277 @@ "time": "2019-06-13T11:03:18+00:00" }, { - "name": "symfony/translation-contracts", - "version": "v1.1.5", + "name": "symfony/finder", + "version": "v4.3.2", "source": { "type": "git", - "url": "https://github.com/symfony/translation-contracts.git", - "reference": "cb4b18ad7b92a26e83b65dde940fab78339e6f3c" + "url": "https://github.com/symfony/finder.git", + "reference": "33c21f7d5d3dc8a140c282854a7e13aeb5d0f91a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/cb4b18ad7b92a26e83b65dde940fab78339e6f3c", - "reference": "cb4b18ad7b92a26e83b65dde940fab78339e6f3c", + "url": "https://api.github.com/repos/symfony/finder/zipball/33c21f7d5d3dc8a140c282854a7e13aeb5d0f91a", + "reference": "33c21f7d5d3dc8a140c282854a7e13aeb5d0f91a", "shasum": "" }, "require": { "php": "^7.1.3" }, - "suggest": { - "symfony/translation-implementation": "" - }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "https://symfony.com", + "time": "2019-06-13T11:03:18+00:00" + }, + { + "name": "symfony/framework-bundle", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/framework-bundle.git", + "reference": "f4c4d2922c209349fa78bce2ba2faa57ccea1093" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/f4c4d2922c209349fa78bce2ba2faa57ccea1093", + "reference": "f4c4d2922c209349fa78bce2ba2faa57ccea1093", + "shasum": "" + }, + "require": { + "ext-xml": "*", + "php": "^7.1.3", + "symfony/cache": "~4.3", + "symfony/config": "~4.2", + "symfony/debug": "~4.0", + "symfony/dependency-injection": "^4.3", + "symfony/filesystem": "~3.4|~4.0", + "symfony/finder": "~3.4|~4.0", + "symfony/http-foundation": "^4.3", + "symfony/http-kernel": "^4.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/routing": "^4.3" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "<3.0", + "phpdocumentor/type-resolver": "<0.2.1", + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/asset": "<3.4", + "symfony/browser-kit": "<4.3", + "symfony/console": "<4.3", + "symfony/dom-crawler": "<4.3", + "symfony/dotenv": "<4.2", + "symfony/form": "<4.3", + "symfony/messenger": "<4.3", + "symfony/property-info": "<3.4", + "symfony/serializer": "<4.2", + "symfony/stopwatch": "<3.4", + "symfony/translation": "<4.3", + "symfony/twig-bridge": "<4.1.1", + "symfony/validator": "<4.1", + "symfony/workflow": "<4.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/cache": "~1.0", + "fig/link-util": "^1.0", + "phpdocumentor/reflection-docblock": "^3.0|^4.0", + "symfony/asset": "~3.4|~4.0", + "symfony/browser-kit": "^4.3", + "symfony/console": "^4.3", + "symfony/css-selector": "~3.4|~4.0", + "symfony/dom-crawler": "^4.3", + "symfony/expression-language": "~3.4|~4.0", + "symfony/form": "^4.3", + "symfony/http-client": "^4.3", + "symfony/lock": "~3.4|~4.0", + "symfony/mailer": "^4.3", + "symfony/messenger": "^4.3", + "symfony/mime": "^4.3", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/process": "~3.4|~4.0", + "symfony/property-info": "~3.4|~4.0", + "symfony/security-csrf": "~3.4|~4.0", + "symfony/security-http": "~3.4|~4.0", + "symfony/serializer": "^4.3", + "symfony/stopwatch": "~3.4|~4.0", + "symfony/templating": "~3.4|~4.0", + "symfony/translation": "~4.3", + "symfony/twig-bundle": "~2.8|~3.2|~4.0", + "symfony/validator": "^4.1", + "symfony/var-dumper": "^4.3", + "symfony/web-link": "~3.4|~4.0", + "symfony/workflow": "^4.3", + "symfony/yaml": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-apcu": "For best performance of the system caches", + "symfony/console": "For using the console commands", + "symfony/form": "For using forms", + "symfony/property-info": "For using the property_info service", + "symfony/serializer": "For using the serializer service", + "symfony/validator": "For using validation", + "symfony/web-link": "For using web links, features such as preloading, prefetching or prerendering", + "symfony/yaml": "For using the debug:config and lint:yaml commands" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\FrameworkBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony FrameworkBundle", + "homepage": "https://symfony.com", + "time": "2019-07-27T08:36:33+00:00" + }, + { + "name": "symfony/routing", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/routing.git", + "reference": "a88c47a5861549f5dc1197660818084c3b67d773" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/routing/zipball/a88c47a5861549f5dc1197660818084c3b67d773", + "reference": "a88c47a5861549f5dc1197660818084c3b67d773", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "conflict": { + "symfony/config": "<4.2", + "symfony/dependency-injection": "<3.4", + "symfony/yaml": "<3.4" + }, + "require-dev": { + "doctrine/annotations": "~1.2", + "psr/log": "~1.0", + "symfony/config": "~4.2", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "~3.4|~4.0", + "symfony/yaml": "~3.4|~4.0" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/http-foundation": "For using a Symfony Request object", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Routing\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2019-07-23T14:43:56+00:00" + }, + { + "name": "symfony/translation-contracts", + "version": "v1.1.5", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "cb4b18ad7b92a26e83b65dde940fab78339e6f3c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/cb4b18ad7b92a26e83b65dde940fab78339e6f3c", + "reference": "cb4b18ad7b92a26e83b65dde940fab78339e6f3c", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" } }, "autoload": { @@ -4578,6 +4519,66 @@ "homepage": "https://symfony.com", "time": "2019-06-22T08:39:44+00:00" }, + { + "name": "symfony/var-exporter", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "9dee83031dcf6dcb53bb7ec1c51de085329bf5cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/9dee83031dcf6dcb53bb7ec1c51de085329bf5cb", + "reference": "9dee83031dcf6dcb53bb7ec1c51de085329bf5cb", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "require-dev": { + "symfony/var-dumper": "^4.1.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "A blend of var_export() + serialize() to turn any serializable data structure to plain PHP code", + "homepage": "https://symfony.com", + "keywords": [ + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" + ], + "time": "2019-06-22T08:39:44+00:00" + }, { "name": "symfony/yaml", "version": "v4.3.2", From 2f2718c64722e22ae7285c39da11e1e71a2f001e Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Thu, 8 Aug 2019 17:43:20 +0200 Subject: [PATCH 07/12] Build the stream outside of the controller --- Controller/StreamController.php | 64 +++++---------------------- Resources/config/services.yml | 4 ++ Response/FeedBuilder.php | 66 ++++++++++++++++++++++++++++ Tests/Response/FeedBuilderTest.php | 70 ++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 53 deletions(-) create mode 100644 Response/FeedBuilder.php create mode 100644 Tests/Response/FeedBuilderTest.php diff --git a/Controller/StreamController.php b/Controller/StreamController.php index 3a69d9d..5200426 100644 --- a/Controller/StreamController.php +++ b/Controller/StreamController.php @@ -3,11 +3,9 @@ namespace Debril\RssAtomBundle\Controller; use Debril\RssAtomBundle\Request\ModifiedSince; -use Debril\RssAtomBundle\Response\HeadersBuilder; use Debril\RssAtomBundle\Provider\FeedContentProviderInterface; use Debril\RssAtomBundle\Exception\FeedException\FeedNotFoundException; -use FeedIo\FeedIo; -use FeedIo\FeedInterface; +use Debril\RssAtomBundle\Response\FeedBuilder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -19,14 +17,9 @@ class StreamController { /** - * @var \DateTime + * @var FeedBuilder */ - protected $since; - - /** - * @var HeadersBuilder - */ - private $headersBuilder; + private $feedBuilder; /** * @var ModifiedSince @@ -34,66 +27,31 @@ class StreamController private $modifiedSince; /** - * @var bool - */ - private $forceRefresh; - - /** - * StreamController constructor. - * @param HeadersBuilder $headersBuilder + * @param FeedBuilder $feedBuilder * @param ModifiedSince $modifiedSince - * @param bool $forceRefresh */ - public function __construct(HeadersBuilder $headersBuilder, ModifiedSince $modifiedSince, bool $forceRefresh = false) + public function __construct(FeedBuilder $feedBuilder, ModifiedSince $modifiedSince) { - $this->headersBuilder = $headersBuilder; + $this->feedBuilder = $feedBuilder; $this->modifiedSince = $modifiedSince; - $this->forceRefresh = $forceRefresh; } /** * @param Request $request * @param FeedContentProviderInterface $provider - * @param FeedIo $feedIo * @return Response * @throws \Exception */ - public function indexAction(Request $request, FeedContentProviderInterface $provider, FeedIo $feedIo) : Response + public function indexAction(Request $request, FeedContentProviderInterface $provider) : Response { $options = $request->attributes->get('_route_params'); $options['Since'] = $this->modifiedSince->getValue(); - $feed = $this->getContent($options, $provider); - $format = $request->get('format', 'rss'); - - if ($this->forceRefresh || $feed->getLastModified() > $this->modifiedSince->getValue()) { - $response = new Response($feedIo->format($feed, $format)); - $this->headersBuilder->setResponseHeaders($response, $format, $feed->getLastModified()); - - } else { - $response = new Response(); - $response->setNotModified(); - } - - return $response; - } - - /** - * Get the Stream's content using a FeedContentProviderInterface - * The FeedContentProviderInterface instance is provided as a service - * default : debril.provider.service. - * - * @param array $options - * @param FeedContentProviderInterface $provider - * - * @return FeedInterface - * - * @throws \Exception - */ - protected function getContent(array $options, FeedContentProviderInterface $provider) : FeedInterface - { try { - return $provider->getFeedContent($options); + return $this->feedBuilder->getResponse( + $request->get('format', 'rss'), + $provider->getFeedContent($options) + ); } catch (FeedNotFoundException $e) { throw new NotFoundHttpException('feed not found'); } diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 5748958..376b9e3 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -7,6 +7,8 @@ services: _defaults: autowire: true autoconfigure: true + bind: + $forceRefresh: "%debril_rss_atom.force_refresh%" guzzle.client: class: GuzzleHttp\Client @@ -42,6 +44,8 @@ services: Debril\RssAtomBundle\Request\ModifiedSince: ~ + Debril\RssAtomBundle\Response\FeedBuilder: ~ + Debril\RssAtomBundle\Response\HeadersBuilder: alias: debril.rss_atom.response.headers diff --git a/Response/FeedBuilder.php b/Response/FeedBuilder.php new file mode 100644 index 0000000..369cdc1 --- /dev/null +++ b/Response/FeedBuilder.php @@ -0,0 +1,66 @@ +feedIo = $feedIo; + $this->headersBuilder = $headersBuilder; + $this->modifiedSince = $modifiedSince; + $this->forceRefresh = $forceRefresh; + } + + /** + * Creates the HttpFoundation\Response instance corresponding to given feed + * + * @param string $format + * @param FeedInterface $feed + * @return Response + */ + public function getResponse(string $format, FeedInterface $feed): Response + { + if ($this->forceRefresh || $feed->getLastModified() > $this->modifiedSince->getValue()) { + $response = new Response($this->feedIo->format($feed, $format)); + $this->headersBuilder->setResponseHeaders($response, $format, $feed->getLastModified()); + } else { + $response = new Response(); + $response->setNotModified(); + } + + return $response; + } +} diff --git a/Tests/Response/FeedBuilderTest.php b/Tests/Response/FeedBuilderTest.php new file mode 100644 index 0000000..62863a1 --- /dev/null +++ b/Tests/Response/FeedBuilderTest.php @@ -0,0 +1,70 @@ +push($request); + $this->feedIo = Factory::create()->getFeedIo(); + $this->headersBuilder = new HeadersBuilder(); + $this->modifiedSince = new ModifiedSince($stack); + parent::setUp(); + } + + public function testGetResponse() + { + $feedBuilder = new FeedBuilder($this->feedIo, $this->headersBuilder, $this->modifiedSince); + + $response = $feedBuilder->getResponse('atom', $this->getFeed()); + $this->assertEquals('200', $response->getStatusCode()); + } + + private function getFeed(): Feed + { + $feed = new Feed(); + $feed->setTitle('rss-atom'); + $feed->setPublicId('http://public-id'); + $feed->setLink('http://link'); + $feed->setLastModified(new \DateTime('2018-06-01')); + + $item = new Feed\Item(); + $item->setLastModified(new \DateTime('2018-06-01')); + $item->setDescription("lorem ipsum"); + $item->setTitle('title'); + + $feed->add($item); + + return $feed; + } +} From f9a5f503e1c0c75d831812e9cb36a3438a29c6c5 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Fri, 9 Aug 2019 11:31:59 +0200 Subject: [PATCH 08/12] The Provider must now be a `FeedProviderInterface` --- Provider/DoctrineFeedContentProvider.php | 18 +++++++++++- Provider/FeedContentProviderInterface.php | 3 +- Provider/FeedProviderInterface.php | 19 +++++++++++++ Provider/MockProvider.php | 34 +++++++++++++++++++---- Tests/Controller/StreamControllerTest.php | 4 +-- 5 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 Provider/FeedProviderInterface.php diff --git a/Provider/DoctrineFeedContentProvider.php b/Provider/DoctrineFeedContentProvider.php index 1d0ce5b..8e9496e 100644 --- a/Provider/DoctrineFeedContentProvider.php +++ b/Provider/DoctrineFeedContentProvider.php @@ -12,9 +12,12 @@ use FeedIo\FeedInterface; use Doctrine\Bundle\DoctrineBundle\Registry; use Debril\RssAtomBundle\Exception\FeedException\FeedNotFoundException; +use Psr\Log\LoggerInterface; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\OptionsResolver\OptionsResolver; /** + * @deprecated since 4.3, will be dropped in 5.0 * @codeCoverageIgnore */ class DoctrineFeedContentProvider implements FeedContentProviderInterface @@ -30,10 +33,13 @@ class DoctrineFeedContentProvider implements FeedContentProviderInterface protected $repositoryName; /** + * DoctrineFeedContentProvider constructor. * @param Registry $doctrine + * @param LoggerInterface $logger */ - public function __construct(Registry $doctrine) + public function __construct(Registry $doctrine, LoggerInterface $logger) { + $logger->info('The \\Debril\\RssAtomBundle\\Provider\\DoctrineFeedContentProvider is deprecated since rss-atom-bundle 4.3, will be removed in 5.0'); $this->doctrine = $doctrine; } @@ -107,4 +113,14 @@ public function getIdFromOptions(array $options) : string return $options['id']; } + + /** + * @param Request $request + * @return FeedInterface + * @throws FeedNotFoundException + */ + public function getFeed(Request $request): FeedInterface + { + return $request->get('id'); + } } diff --git a/Provider/FeedContentProviderInterface.php b/Provider/FeedContentProviderInterface.php index b9ba3e9..ad8f059 100644 --- a/Provider/FeedContentProviderInterface.php +++ b/Provider/FeedContentProviderInterface.php @@ -15,9 +15,10 @@ use FeedIo\FeedInterface; /** + * @deprecated since 4.3 you MUST use `FeedProviderInterface` instead * Interface FeedContentProviderInterface. */ -interface FeedContentProviderInterface +interface FeedContentProviderInterface extends FeedProviderInterface { /** * @param array $options diff --git a/Provider/FeedProviderInterface.php b/Provider/FeedProviderInterface.php new file mode 100644 index 0000000..120a9bd --- /dev/null +++ b/Provider/FeedProviderInterface.php @@ -0,0 +1,19 @@ +get('id'); + + return $this->buildFeed($id); + } + /** * @param array $options - * * @return FeedInterface - * * @throws FeedNotFoundException */ public function getFeedContent(array $options) : FeedInterface { - $feed = new Feed(); + $id = array_key_exists('id', $options) ? $options['id'] : ''; - $id = array_key_exists('id', $options) ? $options['id'] : null; + return $this->buildFeed($id); + } + /** + * @param string $id + * @return FeedInterface + * @throws FeedNotFoundException + */ + protected function buildFeed(string $id): FeedInterface + { if ($id === 'not-found') { throw new FeedNotFoundException(); } - $feed->setPublicId($id); + $feed = new Feed(); + $feed->setPublicId($id); $feed->setTitle('thank you for using RssAtomBundle'); $feed->setDescription('this is the mock FeedContent'); $feed->setLink('https://raw.github.com/alexdebril/rss-atom-bundle/'); @@ -50,7 +71,8 @@ public function getFeedContent(array $options) : FeedInterface /** * @param Feed $feed - * @return Feed + * @return FeedInterface + * @throws \Exception */ protected function addItem(Feed $feed) : FeedInterface { diff --git a/Tests/Controller/StreamControllerTest.php b/Tests/Controller/StreamControllerTest.php index 8b4986f..51acdef 100644 --- a/Tests/Controller/StreamControllerTest.php +++ b/Tests/Controller/StreamControllerTest.php @@ -19,7 +19,7 @@ public function testIndex() { $client = static::createClient(); - $client->request('GET', '/mock/rss'); + $client->request('GET', '/mock/rss/id'); $response = $client->getResponse(); $this->assertEquals('200', $response->getStatusCode()); @@ -35,7 +35,7 @@ public function testIndex() $this->assertGreaterThan(0, strlen($response->getContent())); $this->assertTrue($response->isCacheable()); - $client->request('GET', '/mock/rss', array(), array(), array('HTTP_If-Modified-Since' => $lastModified->format(\DateTime::RSS))); + $client->request('GET', '/mock/rss/id', array(), array(), array('HTTP_If-Modified-Since' => $lastModified->format(\DateTime::RSS))); $response2 = $client->getResponse(); $this->assertEquals('304', $response2->getStatusCode()); From 84aead2cc33cbdbaa27be0acb0d49a3a301a2d85 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Fri, 9 Aug 2019 11:32:40 +0200 Subject: [PATCH 09/12] Bind the parameter only for the matching service --- Resources/config/services.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Resources/config/services.yml b/Resources/config/services.yml index 376b9e3..b3ddd05 100644 --- a/Resources/config/services.yml +++ b/Resources/config/services.yml @@ -7,8 +7,6 @@ services: _defaults: autowire: true autoconfigure: true - bind: - $forceRefresh: "%debril_rss_atom.force_refresh%" guzzle.client: class: GuzzleHttp\Client @@ -44,7 +42,9 @@ services: Debril\RssAtomBundle\Request\ModifiedSince: ~ - Debril\RssAtomBundle\Response\FeedBuilder: ~ + Debril\RssAtomBundle\Response\FeedBuilder: + bind: + $forceRefresh: "%debril_rss_atom.force_refresh%" Debril\RssAtomBundle\Response\HeadersBuilder: alias: debril.rss_atom.response.headers @@ -55,7 +55,7 @@ services: debril.provider.default: alias: debril.rss_atom.provider - Debril\RssAtomBundle\Provider\FeedContentProviderInterface: + Debril\RssAtomBundle\Provider\FeedProviderInterface: alias: debril.rss_atom.provider Debril\RssAtomBundle\Controller\StreamController: From c3181f27b6f1d68ecf70b47ce008f97d4f6ac019 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Fri, 9 Aug 2019 11:33:14 +0200 Subject: [PATCH 10/12] Backward compatibility with both Provider interfaces --- Controller/StreamController.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Controller/StreamController.php b/Controller/StreamController.php index 5200426..95dadd5 100644 --- a/Controller/StreamController.php +++ b/Controller/StreamController.php @@ -2,10 +2,12 @@ namespace Debril\RssAtomBundle\Controller; +use Debril\RssAtomBundle\Provider\FeedProviderInterface; use Debril\RssAtomBundle\Request\ModifiedSince; use Debril\RssAtomBundle\Provider\FeedContentProviderInterface; use Debril\RssAtomBundle\Exception\FeedException\FeedNotFoundException; use Debril\RssAtomBundle\Response\FeedBuilder; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -38,20 +40,23 @@ public function __construct(FeedBuilder $feedBuilder, ModifiedSince $modifiedSin /** * @param Request $request - * @param FeedContentProviderInterface $provider + * @param FeedProviderInterface $provider + * @param LoggerInterface $logger * @return Response - * @throws \Exception */ - public function indexAction(Request $request, FeedContentProviderInterface $provider) : Response + public function indexAction(Request $request, FeedProviderInterface $provider, LoggerInterface $logger) : Response { - $options = $request->attributes->get('_route_params'); - $options['Since'] = $this->modifiedSince->getValue(); - try { - return $this->feedBuilder->getResponse( - $request->get('format', 'rss'), - $provider->getFeedContent($options) - ); + if ($provider instanceof FeedContentProviderInterface) { + $logger->info('The \\Debril\\RssAtomBundle\\Provider\\FeedContentProviderInterface is deprecated since rss-atom-bundle 4.3, use FeedProviderInterface instead'); + $options = $request->attributes->get('_route_params'); + $options['Since'] = $this->modifiedSince->getValue(); + $feed = $provider->getFeedContent($options); + } else { + $feed = $provider->getFeed($request); + } + + return $this->feedBuilder->getResponse($request->get('format', 'rss'), $feed); } catch (FeedNotFoundException $e) { throw new NotFoundHttpException('feed not found'); } From c6d2b2be1d4fd7d8007ea68bd446d216da8ab2b6 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Fri, 9 Aug 2019 14:56:25 +0200 Subject: [PATCH 11/12] Upgrade guide --- UPGRADE-5.0.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 UPGRADE-5.0.md diff --git a/UPGRADE-5.0.md b/UPGRADE-5.0.md new file mode 100644 index 0000000..815cfc6 --- /dev/null +++ b/UPGRADE-5.0.md @@ -0,0 +1,27 @@ +# UPGRADE FROM 4.x to 5.0 + +## FeedContentProviderInterface is replaced with FeedProviderInterface + +In version 4.x `StreamController` expects a `FeedContentProviderInterface` to provide the feed. Now it takes a `FeedProviderInterface` which is slightly different because it takes a `Request` as a parameter and no longer an array. + +Before : + +```php +public function getFeedContent(array $options) : FeedInterface + +``` + +Now : + +```php +public function getFeed(Request $request): FeedInterface + +``` + +## DoctrineFeedContentProvider is removed + +As its implementation is too narrow to let developers do what they need, it's better to simply remove it. + +### That's it + +There are no other modifications to upgrade into 5.0. From b19d837e5f3fcdf8740e170cf200910c78c778f1 Mon Sep 17 00:00:00 2001 From: Alex Debril Date: Fri, 9 Aug 2019 15:00:21 +0200 Subject: [PATCH 12/12] Documentation --- README.md | 10 +++++----- Resources/sample/Provider.php | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3706022..ccda36c 100644 --- a/README.md +++ b/README.md @@ -189,12 +189,12 @@ The request will be handled by `StreamController`, according to the following st You must give to RssAtomBundle the content you want it to display in the feed. For that, two steps : -- write a class that implements `FeedContentProviderInterface`. This class that we call a 'provider' will be in charge of building the feed. +- write a class that implements `FeedProviderInterface`. This class that we call a 'provider' will be in charge of building the feed. - configure the dependency injection to make RssAtomBundle use it ##### FeedContentProviderInterface implementation -Your class just needs to implement the `Debril\RssAtomBundle\Provider\FeedContentProviderInterface` interface, for instance : +Your class just needs to implement the `Debril\RssAtomBundle\Provider\FeedProviderInterface` interface, for instance : ```php setTitle('Feed Title')