diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4cdf108270e66..b03e61ef338c1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,6 @@ | Q | A | ------------- | --- +| Branch | master for features and deprecations / lowest applicable and maintained version otherwise | Bug fix? | yes/no | New feature? | yes/no | BC breaks? | yes/no diff --git a/.travis.yml b/.travis.yml index 34423e84b4e59..46572d8dd8b0b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,9 @@ language: php sudo: false +git: + depth: 1 + addons: apt_packages: - parallel diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a33841f67478b..ae18925cb6e20 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,16 +5,20 @@ Symfony is an open source, community-driven project. If you'd like to contribute, please read the following documents: -* [Contributing Code][1]: The document index related to contributions; +* [Reporting a Bug][1] +* [Submitting a Patch][2] +* [Symfony Core Team][3] +* [Security Issues][4] +* [Running Symfony Tests][5] +* [Our Backwards Compatibility Promise][6] +* [Coding Standards][7] +* [Conventions][8] -* [Submitting a Patch][2]: Guidelines for submitting a pull request; - -* [Pull Request Template][3]: Template header to use in your pull request - description; - -* [Backwards Compatibility][4]: Backward compatibility rules. - -[1]: https://symfony.com/doc/current/contributing/code/index.html -[2]: https://symfony.com/doc/current/contributing/code/patches.html#check-list -[3]: https://symfony.com/doc/current/contributing/code/patches.html#make-a-pull-request -[4]: https://symfony.com/doc/current/contributing/code/bc.html#working-on-symfony-code +[1]: https://symfony.com/doc/current/contributing/code/bugs.html +[2]: https://symfony.com/doc/current/contributing/code/patches.html +[3]: https://symfony.com/doc/current/contributing/code/core_team.html +[4]: https://symfony.com/doc/current/contributing/code/security.html +[5]: https://symfony.com/doc/current/contributing/code/tests.html +[6]: https://symfony.com/doc/current/contributing/code/bc.html +[7]: https://symfony.com/doc/current/contributing/code/standards.html +[8]: https://symfony.com/doc/current/contributing/code/conventions.html diff --git a/appveyor.yml b/appveyor.yml index 555cd0f2a6d7a..857ba04284f3d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,5 @@ build: false -shallow_clone: true -platform: x86 +clone_depth: 1 clone_folder: c:\projects\symfony cache: diff --git a/composer.json b/composer.json index f280ff8851133..c14ca8d454b5e 100644 --- a/composer.json +++ b/composer.json @@ -69,10 +69,17 @@ "doctrine/data-fixtures": "1.0.*", "doctrine/dbal": "~2.4", "doctrine/orm": "~2.4,>=2.4.5", - "monolog/monolog": "~1.3", - "propel/propel1": "~1.6", - "ircmaxell/password-compat": "~1.0", - "ocramius/proxy-manager": "~0.3.1" + "doctrine/doctrine-bundle": "~1.4", + "monolog/monolog": "~1.11", + "ocramius/proxy-manager": "~0.4|~1.0|~2.0", + "egulias/email-validator": "~1.2", + "symfony/polyfill-apcu": "~1.1", + "symfony/security-acl": "~2.8|~3.0", + "ocramius/proxy-manager": "~0.3.1|~1.0|~2.0", + "phpdocumentor/reflection": "^1.0.7" + }, + "conflict": { + "phpdocumentor/reflection": "<1.0.7" }, "autoload": { "psr-0": { "Symfony\\": "src/" }, diff --git a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php index bba5055d0085d..eb72a7d5d83cb 100644 --- a/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php +++ b/src/Symfony/Bridge/ProxyManager/LazyProxy/PhpDumper/ProxyDumper.php @@ -77,11 +77,17 @@ public function getProxyFactoryCode(Definition $definition, $id) $methodName = 'get'.Container::camelize($id).'Service'; $proxyClass = $this->getProxyClassName($definition); + $generatedClass = $this->generateProxyClass($definition); + + $constructorCall = $generatedClass->hasMethod('staticProxyConstructor') + ? $proxyClass.'::staticProxyConstructor' + : 'new '.$proxyClass; + return <<$methodName(false); @@ -101,11 +107,7 @@ function (&\$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface \$proxy) */ public function getProxyCode(Definition $definition) { - $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); - - $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); - - return $this->classGenerator->generate($generatedClass); + return $this->classGenerator->generate($this->generateProxyClass($definition)); } /** @@ -119,4 +121,18 @@ private function getProxyClassName(Definition $definition) { return str_replace('\\', '', $definition->getClass()).'_'.spl_object_hash($definition).$this->salt; } + + /** + * @param Definition $definition + * + * @return ClassGenerator + */ + private function generateProxyClass(Definition $definition) + { + $generatedClass = new ClassGenerator($this->getProxyClassName($definition)); + + $this->proxyGenerator->generate(new \ReflectionClass($definition->getClass()), $generatedClass); + + return $generatedClass; + } } diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php index 412674ee3f680..789799bce6e19 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Dumper/PhpDumperTest.php @@ -11,6 +11,7 @@ namespace Symfony\Bridge\ProxyManager\Tests\LazyProxy\Dumper; +use ProxyManager\ProxyGenerator\LazyLoading\MethodGenerator\StaticProxyConstructor; use Symfony\Bridge\ProxyManager\LazyProxy\PhpDumper\ProxyDumper; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; @@ -49,7 +50,11 @@ public function testDumpContainerWithProxyService() */ public function testDumpContainerWithProxyServiceWillShareProxies() { - require_once __DIR__.'/../Fixtures/php/lazy_service.php'; + if (class_exists(StaticProxyConstructor::class)) { // detecting ProxyManager v2 + require_once __DIR__.'/../Fixtures/php/lazy_service_with_hints.php'; + } else { + require_once __DIR__.'/../Fixtures/php/lazy_service.php'; + } $container = new \LazyServiceProjectServiceContainer(); diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt index 6bc1c6883b7da..e2775cfa5f3d3 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_structure.txt @@ -8,7 +8,7 @@ class ProjectServiceContainer extends Container if ($lazyLoad) { $container = $this; - return $this->services['foo'] = new stdClass_%s( + return $this->services['foo'] =%sstdClass_%s( function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { $wrappedInstance = $container->getFooService(false); @@ -23,5 +23,5 @@ class ProjectServiceContainer extends Container } } -class stdClass_%s extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +class stdClass_%s extends \stdClass implements \ProxyManager\%s {%a}%A \ No newline at end of file diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_with_hints.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_with_hints.php new file mode 100644 index 0000000000000..349d899649c92 --- /dev/null +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/Fixtures/php/lazy_service_with_hints.php @@ -0,0 +1,189 @@ +services = array(); + } + + /** + * Gets the 'foo' service. + * + * This service is shared. + * This method always returns the same instance of the service. + * + * @param bool $lazyLoad whether to try lazy-loading the service with a proxy + * + * @return stdClass A stdClass instance. + */ + public function getFooService($lazyLoad = true) + { + if ($lazyLoad) { + $container = $this; + + return $this->services['foo'] = new stdClass_c1d194250ee2e2b7d2eab8b8212368a8( + function (&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) { + $wrappedInstance = $this->getFooService(false); + + $proxy->setProxyInitializer(null); + + return true; + } + ); + } + + return new \stdClass(); + } +} + +class stdClass_c1d194250ee2e2b7d2eab8b8212368a8 extends \stdClass implements \ProxyManager\Proxy\LazyLoadingInterface, \ProxyManager\Proxy\ValueHolderInterface +{ + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $valueHolder5157dd96e88c0 = null; + + /** + * @var \Closure|null initializer responsible for generating the wrapped object + */ + private $initializer5157dd96e8924 = null; + + /** + * @override constructor for lazy initialization + * + * @param \Closure|null $initializer + */ + public function __construct($initializer) + { + $this->initializer5157dd96e8924 = $initializer; + } + + /** + * @param string $name + */ + public function __get($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__get', array('name' => $name)); + + return $this->valueHolder5157dd96e88c0->$name; + } + + /** + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__set', array('name' => $name, 'value' => $value)); + + $this->valueHolder5157dd96e88c0->$name = $value; + } + + /** + * @param string $name + * + * @return bool + */ + public function __isset($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__isset', array('name' => $name)); + + return isset($this->valueHolder5157dd96e88c0->$name); + } + + /** + * @param string $name + */ + public function __unset($name) + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__unset', array('name' => $name)); + + unset($this->valueHolder5157dd96e88c0->$name); + } + + /** + * + */ + public function __clone() + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__clone', array()); + + $this->valueHolder5157dd96e88c0 = clone $this->valueHolder5157dd96e88c0; + } + + /** + * + */ + public function __sleep() + { + $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, '__sleep', array()); + + return array('valueHolder5157dd96e88c0'); + } + + /** + * + */ + public function __wakeup() + { + } + + /** + * {@inheritdoc} + */ + public function setProxyInitializer(\Closure $initializer = null) + { + $this->initializer5157dd96e8924 = $initializer; + } + + /** + * {@inheritdoc} + */ + public function getProxyInitializer() + { + return $this->initializer5157dd96e8924; + } + + /** + * {@inheritdoc} + */ + public function initializeProxy() : bool + { + return $this->initializer5157dd96e8924 && $this->initializer5157dd96e8924->__invoke($this->valueHolder5157dd96e88c0, $this, 'initializeProxy', array()); + } + + /** + * {@inheritdoc} + */ + public function isProxyInitialized() : bool + { + return null !== $this->valueHolder5157dd96e88c0; + } + + /** + * {@inheritdoc} + */ + public function getWrappedValueHolderValue() + { + return $this->valueHolder5157dd96e88c0; + } +} diff --git a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php index fd20192d86e4f..0b98419f16203 100644 --- a/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php +++ b/src/Symfony/Bridge/ProxyManager/Tests/LazyProxy/PhpDumper/ProxyDumperTest.php @@ -69,7 +69,7 @@ public function testGetProxyFactoryCode() $code = $this->dumper->getProxyFactoryCode($definition, 'foo'); $this->assertStringMatchesFormat( - '%wif ($lazyLoad) {%w$container = $this;%wreturn $this->services[\'foo\'] = new ' + '%wif ($lazyLoad) {%w$container = $this;%wreturn $this->services[\'foo\'] =%s' .'SymfonyBridgeProxyManagerTestsLazyProxyPhpDumperProxyDumperTest_%s(%wfunction ' .'(&$wrappedInstance, \ProxyManager\Proxy\LazyLoadingInterface $proxy) use ($container) {' .'%w$wrappedInstance = $container->getFooService(false);%w$proxy->setProxyInitializer(null);' diff --git a/src/Symfony/Bridge/ProxyManager/composer.json b/src/Symfony/Bridge/ProxyManager/composer.json index 82b119b164898..71059cc35bd2c 100644 --- a/src/Symfony/Bridge/ProxyManager/composer.json +++ b/src/Symfony/Bridge/ProxyManager/composer.json @@ -18,7 +18,7 @@ "require": { "php": ">=5.3.3", "symfony/dependency-injection": "~2.3", - "ocramius/proxy-manager": "~0.3.1" + "ocramius/proxy-manager": "~0.3.1|~1.0|~2.0" }, "require-dev": { "symfony/config": "~2.3" diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php index 757c98b1e85df..e1e1edc416967 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/AddConstraintValidatorsPass.php @@ -27,6 +27,8 @@ public function process(ContainerBuilder $container) if (isset($attributes[0]['alias'])) { $validators[$attributes[0]['alias']] = $id; } + + $validators[$container->getDefinition($id)->getClass()] = $id; } $container->getDefinition('validator.validator_factory')->replaceArgument(1, $validators); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConstraintValidatorsPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConstraintValidatorsPassTest.php new file mode 100644 index 0000000000000..0629d1ebafd1f --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/AddConstraintValidatorsPassTest.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConstraintValidatorsPass; + +class AddConstraintValidatorsPassTest extends \PHPUnit_Framework_TestCase +{ + public function testThatConstraintValidatorServicesAreProcessed() + { + $services = array( + 'my_constraint_validator_service1' => array(0 => array('alias' => 'my_constraint_validator_alias1')), + 'my_constraint_validator_service2' => array(), + ); + + $validatorFactoryDefinition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $container = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('findTaggedServiceIds', 'getDefinition', 'hasDefinition') + ); + + $validatorDefinition1 = $this->getMock('Symfony\Component\DependencyInjection\Definition', array('getClass')); + $validatorDefinition2 = $this->getMock('Symfony\Component\DependencyInjection\Definition', array('getClass')); + + $validatorDefinition1->expects($this->atLeastOnce()) + ->method('getClass') + ->willReturn('My\Fully\Qualified\Class\Named\Validator1'); + $validatorDefinition2->expects($this->atLeastOnce()) + ->method('getClass') + ->willReturn('My\Fully\Qualified\Class\Named\Validator2'); + + $container->expects($this->any()) + ->method('getDefinition') + ->with($this->anything()) + ->will($this->returnValueMap(array( + array('my_constraint_validator_service1', $validatorDefinition1), + array('my_constraint_validator_service2', $validatorDefinition2), + array('validator.validator_factory', $validatorFactoryDefinition), + ))); + + $container->expects($this->atLeastOnce()) + ->method('findTaggedServiceIds') + ->will($this->returnValue($services)); + $container->expects($this->atLeastOnce()) + ->method('hasDefinition') + ->with('validator.validator_factory') + ->will($this->returnValue(true)); + + $validatorFactoryDefinition->expects($this->once()) + ->method('replaceArgument') + ->with(1, array( + 'My\Fully\Qualified\Class\Named\Validator1' => 'my_constraint_validator_service1', + 'my_constraint_validator_alias1' => 'my_constraint_validator_service1', + 'My\Fully\Qualified\Class\Named\Validator2' => 'my_constraint_validator_service2', + )); + + $addConstraintValidatorsPass = new AddConstraintValidatorsPass(); + $addConstraintValidatorsPass->process($container); + } + + public function testThatCompilerPassIsIgnoredIfThereIsNoConstraintValidatorFactoryDefinition() + { + $definition = $this->getMock('Symfony\Component\DependencyInjection\Definition'); + $container = $this->getMock( + 'Symfony\Component\DependencyInjection\ContainerBuilder', + array('hasDefinition', 'findTaggedServiceIds', 'getDefinition') + ); + + $container->expects($this->never())->method('findTaggedServiceIds'); + $container->expects($this->never())->method('getDefinition'); + $container->expects($this->atLeastOnce()) + ->method('hasDefinition') + ->with('validator.validator_factory') + ->will($this->returnValue(false)); + $definition->expects($this->never())->method('replaceArgument'); + + $addConstraintValidatorsPass = new AddConstraintValidatorsPass(); + $addConstraintValidatorsPass->process($container); + } +} diff --git a/src/Symfony/Component/Console/Formatter/OutputFormatter.php b/src/Symfony/Component/Console/Formatter/OutputFormatter.php index 7a78eb2f80b6c..154a041dae013 100644 --- a/src/Symfony/Component/Console/Formatter/OutputFormatter.php +++ b/src/Symfony/Component/Console/Formatter/OutputFormatter.php @@ -31,7 +31,15 @@ class OutputFormatter implements OutputFormatterInterface */ public static function escape($text) { - return preg_replace('/([^\\\\]?)#ix", $message, $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; @@ -164,6 +172,10 @@ public function format($message) $output .= $this->applyCurrentStyle(substr($message, $offset)); + if (false !== strpos($output, '<<')) { + return strtr($output, array('\\<' => '<', '<<' => '\\')); + } + return str_replace('\\<', '<', $output); } diff --git a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php index eff23c17b49c8..dc77b4d1fd9d2 100644 --- a/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php +++ b/src/Symfony/Component/Console/Tests/Formatter/OutputFormatterTest.php @@ -98,8 +98,8 @@ public function testStyleEscaping() $formatter = new OutputFormatter(true); $this->assertEquals( - "(\033[32mz>=2.0,format('('.$formatter->escape('z>=2.0,)') + "(\033[32mz>=2.0,<<format('('.$formatter->escape('z>=2.0,<\\<)') ); $this->assertEquals( diff --git a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php index b4816eda4e9f1..12194091e0da5 100644 --- a/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php +++ b/src/Symfony/Component/DomCrawler/Field/ChoiceFormField.php @@ -55,6 +55,10 @@ public function hasValue() */ public function isDisabled() { + if (parent::isDisabled() && 'select' === $this->type) { + return true; + } + foreach ($this->options as $option) { if ($option['value'] == $this->value && $option['disabled']) { return true; diff --git a/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php b/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php index 954cd9774b843..ce87b15c2ed59 100644 --- a/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php +++ b/src/Symfony/Component/DomCrawler/Tests/Field/ChoiceFormFieldTest.php @@ -120,6 +120,14 @@ public function testSelectWithEmptyBooleanAttribute() $this->assertEquals('bar', $field->getValue()); } + public function testSelectIsDisabled() + { + $node = $this->createSelectNode(array('foo' => false, 'bar' => true), array('disabled' => 'disabled')); + $field = new ChoiceFormField($node); + + $this->assertTrue($field->isDisabled(), '->isDisabled() returns true for selects with a disabled attribute'); + } + public function testMultipleSelects() { $node = $this->createSelectNode(array('foo' => false, 'bar' => false), array('multiple' => 'multiple')); diff --git a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php index d5b1c34bf4b3c..22f84da4a4758 100644 --- a/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php +++ b/src/Symfony/Component/HttpFoundation/BinaryFileResponse.php @@ -220,7 +220,7 @@ public function prepare(Request $request) $this->maxlen = 0; } elseif ($request->headers->has('Range')) { // Process the range headers. - if (!$request->headers->has('If-Range') || $this->getEtag() === $request->headers->get('If-Range')) { + if (!$request->headers->has('If-Range') || $this->hasValidIfRangeHeader($request->headers->get('If-Range'))) { $range = $request->headers->get('Range'); $fileSize = $this->file->getSize(); @@ -253,6 +253,19 @@ public function prepare(Request $request) return $this; } + private function hasValidIfRangeHeader($header) + { + if ($this->getEtag() === $header) { + return true; + } + + if (null === $lastModified = $this->getLastModified()) { + return false; + } + + return $lastModified->format('D, d M Y H:i:s').' GMT' === $header; + } + /** * Sends the file. * diff --git a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php index 35dfab5366501..88fb251366c53 100644 --- a/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php +++ b/src/Symfony/Component/HttpFoundation/Tests/BinaryFileResponseTest.php @@ -80,6 +80,37 @@ public function testRequests($requestRange, $offset, $length, $responseRange) $this->assertEquals($responseRange, $response->headers->get('Content-Range')); } + /** + * @dataProvider provideRanges + */ + public function testRequestsWithoutEtag($requestRange, $offset, $length, $responseRange) + { + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream')); + + // do a request to get the LastModified + $request = Request::create('/'); + $response->prepare($request); + $lastModified = $response->headers->get('Last-Modified'); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', $lastModified); + $request->headers->set('Range', $requestRange); + + $file = fopen(__DIR__.'/File/Fixtures/test.gif', 'r'); + fseek($file, $offset); + $data = fread($file, $length); + fclose($file); + + $this->expectOutputString($data); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(206, $response->getStatusCode()); + $this->assertEquals($responseRange, $response->headers->get('Content-Range')); + } + public function provideRanges() { return array( @@ -91,6 +122,25 @@ public function provideRanges() ); } + public function testRangeRequestsWithoutLastModifiedDate() + { + // prevent auto last modified + $response = BinaryFileResponse::create(__DIR__.'/File/Fixtures/test.gif', 200, array('Content-Type' => 'application/octet-stream'), true, null, false, false); + + // prepare a request for a range of the testing file + $request = Request::create('/'); + $request->headers->set('If-Range', date('D, d M Y H:i:s').' GMT'); + $request->headers->set('Range', 'bytes=1-4'); + + $this->expectOutputString(file_get_contents(__DIR__.'/File/Fixtures/test.gif')); + $response = clone $response; + $response->prepare($request); + $response->sendContent(); + + $this->assertEquals(200, $response->getStatusCode()); + $this->assertNull($response->headers->get('Content-Range')); + } + /** * @dataProvider provideFullFileRanges */