Merge branch '2.3' into 2.7

* 2.3:
  [DomCrawler] Invalid uri created from forms if base tag present
  [Console] update param type phpdoc for StreamOutput
  [Console] fix typo in OutputInterface
  [HttpKernel] fix broken multiline <esi:remove>
  [DoctrineBridge] Fixed #14840
  [FrameworkBundle] add a suggest for the serializer component
  [Yaml] Fix the parsing of float keys
  [Console] Ensure the console output is only detected as decorated when both stderr and stdout support colors
  Improve exception messages.
  Fix that two DirectoryResources with different patterns would be deduplicated
  Tests fix clockmock
  [WebProfilerBundle] Added tabindex="-1" to not interfer with normal UX
  missing "YAML" in the exception message.
  [framework-bundle] Add Test for TranslationUpdateCommand
  Use ObjectManager interface instead of EntityManager
This commit is contained in:
Fabien Potencier 2015-09-14 16:14:09 +02:00
commit cafd4af7cd
31 changed files with 364 additions and 22 deletions

View File

@ -129,11 +129,15 @@ abstract class AbstractDoctrineExtension extends Extension
*/
protected function setMappingDriverConfig(array $mappingConfig, $mappingName)
{
if (!is_dir($mappingConfig['dir'])) {
$mappingDirectory = $mappingConfig['dir'];
if (!is_dir($mappingDirectory)) {
throw new \InvalidArgumentException(sprintf('Invalid Doctrine mapping path given. Cannot load Doctrine mapping/bundle named "%s".', $mappingName));
}
$this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = realpath($mappingConfig['dir']);
if (substr($mappingDirectory, 0, 7) !== 'phar://') {
$mappingDirectory = realpath($mappingDirectory);
}
$this->drivers[$mappingConfig['type']][$mappingConfig['prefix']] = $mappingDirectory;
}
/**

View File

@ -14,7 +14,7 @@ namespace Symfony\Bridge\Doctrine\Form\ChoiceList;
use Symfony\Component\Form\Exception\UnexpectedTypeException;
use Doctrine\ORM\QueryBuilder;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManager;
use Doctrine\Common\Persistence\ObjectManager;
/**
* Loads entities using a {@link QueryBuilder} instance.
@ -43,7 +43,7 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
* deprecated and will not be
* supported anymore as of
* Symfony 3.0.
* @param EntityManager $manager Deprecated.
* @param ObjectManager $manager Deprecated.
* @param string $class Deprecated.
*
* @throws UnexpectedTypeException
@ -59,8 +59,8 @@ class ORMQueryBuilderLoader implements EntityLoaderInterface
if ($queryBuilder instanceof \Closure) {
@trigger_error('Passing a QueryBuilder closure to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);
if (!$manager instanceof EntityManager) {
throw new UnexpectedTypeException($manager, 'Doctrine\ORM\EntityManager');
if (!$manager instanceof ObjectManager) {
throw new UnexpectedTypeException($manager, 'Doctrine\Common\Persistence\ObjectManager');
}
@trigger_error('Passing an EntityManager to '.__CLASS__.'::__construct() is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED);

View File

@ -0,0 +1,141 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\Command;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;
use Symfony\Bundle\FrameworkBundle\Command\TranslationUpdateCommand;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\DependencyInjection;
use Symfony\Component\HttpKernel;
class TranslationUpdateCommandTest extends \PHPUnit_Framework_TestCase
{
private $fs;
private $translationDir;
public function testDumpMessagesAndClean()
{
$tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo')));
$tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true));
$this->assertRegExp('/foo/', $tester->getDisplay());
}
protected function setUp()
{
$this->fs = new Filesystem();
$this->translationDir = sys_get_temp_dir().'/'.uniqid('sf2_translation', true);
$this->fs->mkdir($this->translationDir.'/Resources/translations');
$this->fs->mkdir($this->translationDir.'/Resources/views');
}
protected function tearDown()
{
$this->fs->remove($this->translationDir);
}
/**
* @return CommandTester
*/
private function createCommandTester(DependencyInjection\ContainerInterface $container)
{
$command = new TranslationUpdateCommand();
$command->setContainer($container);
$application = new Application();
$application->add($command);
return new CommandTester($application->find('translation:update'));
}
private function getContainer($extractedMessages = array(), $loadedMessages = array(), HttpKernel\KernelInterface $kernel = null)
{
$translator = $this->getMockBuilder('Symfony\Component\Translation\Translator')
->disableOriginalConstructor()
->getMock();
$translator
->expects($this->any())
->method('getFallbackLocales')
->will($this->returnValue(array('en')));
$extractor = $this->getMock('Symfony\Component\Translation\Extractor\ExtractorInterface');
$extractor
->expects($this->any())
->method('extract')
->will(
$this->returnCallback(function ($path, $catalogue) use ($extractedMessages) {
$catalogue->add($extractedMessages);
})
);
$loader = $this->getMock('Symfony\Bundle\FrameworkBundle\Translation\TranslationLoader');
$loader
->expects($this->any())
->method('loadMessages')
->will(
$this->returnCallback(function ($path, $catalogue) use ($loadedMessages) {
$catalogue->add($loadedMessages);
})
);
$writer = $this->getMock('Symfony\Component\Translation\Writer\TranslationWriter');
$writer
->expects($this->any())
->method('getFormats')
->will(
$this->returnValue(array('xlf', 'yml'))
);
if (null === $kernel) {
$kernel = $this->getMock('Symfony\Component\HttpKernel\KernelInterface');
$kernel
->expects($this->any())
->method('getBundle')
->will($this->returnValueMap(array(
array('foo', true, $this->getBundle($this->translationDir)),
array('test', true, $this->getBundle('test')),
)));
}
$kernel
->expects($this->any())
->method('getRootDir')
->will($this->returnValue($this->translationDir));
$container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
$container
->expects($this->any())
->method('get')
->will($this->returnValueMap(array(
array('translation.extractor', 1, $extractor),
array('translation.loader', 1, $loader),
array('translation.writer', 1, $writer),
array('translator', 1, $translator),
array('kernel', 1, $kernel),
)));
return $container;
}
private function getBundle($path)
{
$bundle = $this->getMock('Symfony\Component\HttpKernel\Bundle\BundleInterface');
$bundle
->expects($this->any())
->method('getPath')
->will($this->returnValue($path))
;
return $bundle;
}
}

View File

@ -52,6 +52,7 @@
"symfony/console": "For using the console commands",
"symfony/finder": "For using the translation loader and cache warmer",
"symfony/form": "For using forms",
"symfony/serializer": "For using the serializer service",
"symfony/validator": "For using validation",
"symfony/yaml": "For using the debug:config and lint:yaml commands",
"doctrine/cache": "For using alternative cache drivers"

View File

@ -1,7 +1,7 @@
<!-- START of Symfony Web Debug Toolbar -->
{% if 'normal' != position %}
<div id="sfMiniToolbar-{{ token }}" class="sf-minitoolbar" data-no-turbolink>
<a href="javascript:void(0);" title="Show Symfony toolbar" onclick="
<a href="javascript:void(0);" title="Show Symfony toolbar" tabindex="-1" accesskey="D" onclick="
var elem = this.parentNode;
if (elem.style.display == 'none') {
document.getElementById('sfToolbarMainContent-{{ token }}').style.display = 'none';
@ -36,7 +36,7 @@
{% endfor %}
{% if 'normal' != position %}
<a class="hide-button" title="Close Toolbar" onclick="
<a class="hide-button" title="Close Toolbar" tabindex="-1" accesskey="D" onclick="
var p = this.parentNode;
p.style.display = 'none';
(p.previousElementSibling || p.previousSibling).style.display = 'none';

View File

@ -38,7 +38,7 @@ class DirectoryResource implements ResourceInterface, \Serializable
*/
public function __toString()
{
return (string) $this->resource;
return md5(serialize(array($this->resource, $this->pattern)));
}
/**

View File

@ -54,7 +54,6 @@ class DirectoryResourceTest extends \PHPUnit_Framework_TestCase
{
$resource = new DirectoryResource($this->directory);
$this->assertSame($this->directory, $resource->getResource(), '->getResource() returns the path to the resource');
$this->assertSame($this->directory, (string) $resource, '->__toString() returns the path to the resource');
}
public function testGetPattern()
@ -87,6 +86,13 @@ class DirectoryResourceTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($resource->isFresh(time() + 10), '->isFresh() returns false if a new file is added');
}
public function testIsFreshNewFileWithDifferentPattern()
{
$resource = new DirectoryResource($this->directory, '/.xml$/');
touch($this->directory.'/new.yaml', time() + 20);
$this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if a new file with a non-matching pattern is added');
}
public function testIsFreshDeleteFile()
{
$resource = new DirectoryResource($this->directory);
@ -149,4 +155,12 @@ class DirectoryResourceTest extends \PHPUnit_Framework_TestCase
$this->assertSame($this->directory, $resource->getResource());
$this->assertSame('/\.(foo|xml)$/', $resource->getPattern());
}
public function testResourcesWithDifferentPatternsAreDifferent()
{
$resourceA = new DirectoryResource($this->directory, '/.xml$/');
$resourceB = new DirectoryResource($this->directory, '/.yaml$/');
$this->assertEquals(2, count(array_unique(array($resourceA, $resourceB))));
}
}

View File

@ -48,7 +48,12 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
parent::__construct($this->openOutputStream(), $verbosity, $decorated, $formatter);
$actualDecorated = $this->isDecorated();
$this->stderr = new StreamOutput($this->openErrorStream(), $verbosity, $decorated, $this->getFormatter());
if (null === $decorated) {
$this->setDecorated($actualDecorated && $this->stderr->isDecorated());
}
}
/**

View File

@ -48,7 +48,7 @@ interface OutputInterface
/**
* Writes a message to the output and adds a newline at the end.
*
* @param string|array $messages The message as an array of lines of a single string
* @param string|array $messages The message as an array of lines or a single string
* @param int $type The type of output (one of the OUTPUT constants)
*
* @throws \InvalidArgumentException When unknown output type is given

View File

@ -35,7 +35,7 @@ class StreamOutput extends Output
/**
* Constructor.
*
* @param mixed $stream A stream resource
* @param resource $stream A stream resource
* @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface)
* @param bool|null $decorated Whether to decorate messages (null for auto-guessing)
* @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter)

View File

@ -320,7 +320,7 @@ class YamlFileLoader extends FileLoader
try {
$configuration = $this->yamlParser->parse(file_get_contents($file));
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e);
throw new InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $file), 0, $e);
}
return $this->validate($configuration, $file);

View File

@ -0,0 +1,2 @@
parameters:
FOO: bar

View File

@ -88,6 +88,7 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
array('bad_services'),
array('bad_service'),
array('bad_calls'),
array('bad_format'),
);
}

View File

@ -790,7 +790,7 @@ class Crawler extends \SplObjectStorage
throw new \InvalidArgumentException('The current node list is empty.');
}
$form = new Form($this->getNode(0), $this->uri, $method);
$form = new Form($this->getNode(0), $this->uri, $method, $this->baseHref);
if (null !== $values) {
$form->setValues($values);

View File

@ -33,20 +33,27 @@ class Form extends Link implements \ArrayAccess
*/
private $fields;
/**
* @var string
*/
private $baseHref;
/**
* Constructor.
*
* @param \DOMElement $node A \DOMElement instance
* @param string $currentUri The URI of the page where the form is embedded
* @param string $method The method to use for the link (if null, it defaults to the method defined by the form)
* @param string $baseHref The URI of the <base> used for relative links, but not for empty action
*
* @throws \LogicException if the node is not a button inside a form tag
*
* @api
*/
public function __construct(\DOMElement $node, $currentUri, $method = null)
public function __construct(\DOMElement $node, $currentUri, $method = null, $baseHref = null)
{
parent::__construct($node, $currentUri, $method);
$this->baseHref = $baseHref;
$this->initialize();
}
@ -458,6 +465,10 @@ class Form extends Link implements \ArrayAccess
$this->addField($node);
}
}
if ($this->baseHref && '' !== $this->node->getAttribute('action')) {
$this->currentUri = $this->baseHref;
}
}
private function addField(\DOMElement $node)

View File

@ -982,9 +982,12 @@ HTML;
public function getBaseTagWithFormData()
{
return array(
array('https://base.com/', 'link/', 'https://base.com/link/', 'https://base.com/link/', '<base> tag does work with a path and relative form action'),
array('/basepath', '/registration', 'http://domain.com/registration', 'http://domain.com/registration', '<base> tag does work with a path and form action'),
array('/basepath', '', 'http://domain.com/registration', 'http://domain.com/registration', '<base> tag does work with a path and empty form action'),
array('http://base.com/', '/registration', 'http://base.com/registration', 'http://domain.com/registration', '<base> tag does work with a URL and form action'),
array('http://base.com', '', 'http://domain.com/path/form', 'http://domain.com/path/form', '<base> tag does work with a URL and an empty form action'),
array('http://base.com/path', '/registration', 'http://base.com/registration', 'http://domain.com/path/form', '<base> tag does work with a URL and form action'),
);
}

View File

@ -18,7 +18,22 @@ function time($asFloat = false)
namespace Symfony\Component\HttpFoundation\Tests;
function with_clock_mock($enable = null)
{
static $enabled;
if (null === $enable) {
return $enabled;
}
$enabled = $enable;
}
function time()
{
if (!with_clock_mock()) {
return \time();
}
return $_SERVER['REQUEST_TIME'];
}

View File

@ -23,6 +23,18 @@ require_once __DIR__.'/ClockMock.php';
*/
class CookieTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
with_clock_mock(true);
parent::setUp();
}
public function tearDown()
{
with_clock_mock(false);
parent::tearDown();
}
public function invalidNames()
{
return array(

View File

@ -18,6 +18,18 @@ require_once __DIR__.'/ClockMock.php';
class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
{
public function setUp()
{
with_clock_mock(true);
parent::setUp();
}
public function tearDown()
{
with_clock_mock(false);
parent::tearDown();
}
/**
* @covers Symfony\Component\HttpFoundation\ResponseHeaderBag::allPreserveCase
* @dataProvider provideAllPreserveCase

View File

@ -213,8 +213,8 @@ class Esi implements SurrogateInterface
// we don't use a proper XML parser here as we can have ESI tags in a plain text response
$content = $response->getContent();
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#', '', $content);
$content = preg_replace('#<esi\:comment[^>]*(?:/|</esi\:comment)>#', '', $content);
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#s', '', $content);
$content = preg_replace('#<esi\:comment[^>]+>#s', '', $content);
$chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, PREG_SPLIT_DELIM_CAPTURE);
$chunks[0] = str_replace($this->phpEscapeMap[0], $this->phpEscapeMap[1], $chunks[0]);

View File

@ -92,6 +92,28 @@ class EsiTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($response->headers->has('x-body-eval'));
}
public function testMultilineEsiRemoveTagsAreRemoved()
{
$esi = new Esi();
$request = Request::create('/');
$response = new Response('<esi:remove> <a href="http://www.example.com">www.example.com</a> </esi:remove> Keep this'."<esi:remove>\n <a>www.example.com</a> </esi:remove> And this");
$esi->process($request, $response);
$this->assertEquals(' Keep this And this', $response->getContent());
}
public function testCommentTagsAreRemoved()
{
$esi = new Esi();
$request = Request::create('/');
$response = new Response('<esi:comment text="some comment &gt;" /> Keep this');
$esi->process($request, $response);
$this->assertEquals(' Keep this', $response->getContent());
}
public function testProcess()
{
$esi = new Esi();

View File

@ -0,0 +1,3 @@
blog_show:
path: /blog/{slug}
defaults: { _controller: "MyBundle:Blog:show" }

View File

@ -51,7 +51,15 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
public function getPathsToInvalidFiles()
{
return array(array('nonvalid.yml'), array('nonvalid2.yml'), array('incomplete.yml'), array('nonvalidkeys.yml'), array('nonesense_resource_plus_path.yml'), array('nonesense_type_without_resource.yml'));
return array(
array('nonvalid.yml'),
array('nonvalid2.yml'),
array('incomplete.yml'),
array('nonvalidkeys.yml'),
array('nonesense_resource_plus_path.yml'),
array('nonesense_type_without_resource.yml'),
array('bad_format.yml'),
);
}
public function testLoadSpecialRouteName()

View File

@ -18,10 +18,27 @@ function microtime($asFloat = false)
namespace Symfony\Component\Stopwatch\Tests;
function with_clock_mock($enable = null)
{
static $enabled;
if (null === $enable) {
return $enabled;
}
$enabled = $enable;
}
function usleep($us)
{
static $now;
if (!with_clock_mock()) {
\usleep($us);
return;
}
if (null === $now) {
$now = \microtime(true);
}
@ -31,6 +48,10 @@ function usleep($us)
function microtime($asFloat = false)
{
if (!with_clock_mock()) {
return \microtime($asFloat);
}
if (!$asFloat) {
return \microtime(false);
}

View File

@ -24,6 +24,18 @@ class StopwatchEventTest extends \PHPUnit_Framework_TestCase
{
const DELTA = 37;
public function setUp()
{
with_clock_mock(true);
parent::setUp();
}
public function tearDown()
{
with_clock_mock(false);
parent::tearDown();
}
public function testGetOrigin()
{
$event = new StopwatchEvent(12);

View File

@ -24,6 +24,18 @@ class StopwatchTest extends \PHPUnit_Framework_TestCase
{
const DELTA = 20;
public function setUp()
{
with_clock_mock(true);
parent::setUp();
}
public function tearDown()
{
with_clock_mock(false);
parent::tearDown();
}
public function testStart()
{
$stopwatch = new Stopwatch();

View File

@ -121,7 +121,7 @@ class YamlFileLoader extends FileLoader
try {
$classes = $this->yamlParser->parse(file_get_contents($path));
} catch (ParseException $e) {
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid.', $path), 0, $e);
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain valid YAML.', $path), 0, $e);
}
// empty file

View File

@ -33,15 +33,26 @@ class YamlFileLoaderTest extends \PHPUnit_Framework_TestCase
$this->assertFalse($loader->loadClassMetadata($metadata));
}
public function testLoadClassMetadataThrowsExceptionIfNotAnArray()
/**
* @dataProvider provideInvalidYamlFiles
* @expectedException \InvalidArgumentException
*/
public function testInvalidYamlFiles($path)
{
$loader = new YamlFileLoader(__DIR__.'/nonvalid-mapping.yml');
$loader = new YamlFileLoader(__DIR__.'/'.$path);
$metadata = new ClassMetadata('Symfony\Component\Validator\Tests\Fixtures\Entity');
$this->setExpectedException('\InvalidArgumentException');
$loader->loadClassMetadata($metadata);
}
public function provideInvalidYamlFiles()
{
return array(
array('nonvalid-mapping.yml'),
array('bad-format.yml'),
);
}
/**
* @see https://github.com/symfony/symfony/pull/12158
*/

View File

@ -0,0 +1,9 @@
namespaces:
custom: Symfony\Component\Validator\Tests\Fixtures\
Symfony\Component\Validator\Tests\Fixtures\Entity:
constraints:
# Custom constraint
- Symfony\Component\Validator\Tests\Fixtures\ConstraintA: ~
# Custom constraint with namespaces prefix
- "custom:ConstraintB": ~

View File

@ -135,6 +135,11 @@ class Parser
throw $e;
}
// Convert float keys to strings, to avoid being converted to integers by PHP
if (is_float($key)) {
$key = (string) $key;
}
if ('<<' === $key) {
$mergeNode = true;
$allowOverwrite = true;

View File

@ -747,6 +747,24 @@ bar: 2
EOF;
$this->assertEquals(array('foo' => 1, 'bar' => 2), $this->parser->parse($yaml));
}
public function testFloatKeys()
{
$yaml = <<<EOF
foo:
1.2: "bar"
1.3: "baz"
EOF;
$expected = array(
'foo' => array(
'1.2' => 'bar',
'1.3' => 'baz',
),
);
$this->assertEquals($expected, $this->parser->parse($yaml));
}
}
class B