Merge branch '3.4' into 4.0

* 3.4:
  [Translation] Process multiple segments within a single unit.
  Document the container.autowiring.strict_mode option
  fix custom radios/inputs for checkbox/radio type
  Another PR template tweak
  [FrameworkBundle] Add missing XML config for circular_reference_handler. Add tests.
  fix CS
  [PropertyInfo] ReflectionExtractor: give a chance to other extractors if no properties
  Clean calls to http_build_query()
  [WebProfilerBundle] limit ajax request to 100 and remove the last one
  Add support for URL-like DSNs for the PdoSessionHandler
  [HttpFoundation] Fix missing "throw" in JsonResponse
  Improve the documentation of
  Suppress warning from sapi_windows_vt100_support on stream other than STDIO
  removed extra-verbose comments
  Fixes #26136: Avoid emitting warning in hasParameterOption()
  Added a README entry to the PR template
  [HttpFoundation] Add x-zip-compressed to MimeTypeExtensionGuesser.
  [DI] Add null check for removeChild
This commit is contained in:
Nicolas Grekas 2018-02-22 11:50:29 +01:00
commit 0dc6acb123
27 changed files with 294 additions and 54 deletions

View File

@ -3,16 +3,18 @@
| Branch? | master for features / 2.7 up to 4.0 for bug fixes <!-- see below -->
| Bug fix? | yes/no
| New feature? | yes/no <!-- don't forget to update src/**/CHANGELOG.md files -->
| BC breaks? | yes/no
| BC breaks? | no <!-- see https://symfony.com/bc -->
| Deprecations? | yes/no <!-- don't forget to update UPGRADE-*.md files -->
| Tests pass? | yes/no
| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
| Tests pass? | yes <!-- please add some, will be required by reviewers -->
| Fixed tickets | #... <!-- #-prefixed issue number(s), if any -->
| License | MIT
| Doc PR | symfony/symfony-docs#... <!--highly recommended for new features-->
| Doc PR | symfony/symfony-docs#... <!-- required for new features -->
<!--
- Bug fixes must be submitted against the lowest branch where they apply
(lowest branches are regularly merged to upper ones so they get the fixes too).
- Features and deprecations must be submitted against the master branch.
- Replace this comment by a description of what your PR is solving.
Write a short README entry for your feature/bugfix here (replace this comment block.)
This will help people understand your PR and can be used as a start of the Doc PR.
Additionally:
- Bug fixes must be submitted against the lowest branch where they apply
(lowest branches are regularly merged to upper ones so they get the fixes too).
- Features and deprecations must be submitted against the master branch.
-->

View File

@ -163,7 +163,18 @@ DependencyInjection
autowire: true
```
* Autowiring services based on the types they implement is not supported anymore. Rename (or alias) your services to their FQCN id to make them autowirable.
* Autowiring services based on the types they implement is not supported anymore.
It will only look for an alias or a service id that matches a given FQCN.
Rename (or alias) your services to their FQCN id to make them autowirable.
In 3.4, you can activate this behavior instead of having deprecation messages
by setting the following parameter:
```yml
parameters:
container.autowiring.strict_mode: true
```
From 4.0, you can remove it as it's the default behavior and the parameter is not handled anymore.
* `_defaults` and `_instanceof` are now reserved service names in Yaml configurations. Please rename any services with that names.

View File

@ -190,7 +190,9 @@
{% block checkbox_radio_label -%}
{#- Do not display the label if widget is not defined in order to prevent double label rendering -#}
{%- if widget is defined -%}
{%- if parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class) -%}
{% set is_parent_custom = parent_label_class is defined and ('checkbox-custom' in parent_label_class or 'radio-custom' in parent_label_class) %}
{% set is_custom = label_attr.class is defined and ('checkbox-custom' in label_attr.class or 'radio-custom' in label_attr.class) %}
{%- if is_parent_custom or is_custom -%}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' custom-control-label')|trim}) -%}
{%- else %}
{%- set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' form-check-label')|trim}) -%}

View File

@ -230,6 +230,7 @@
<xsd:attribute name="cache" type="xsd:string" />
<xsd:attribute name="enable-annotations" type="xsd:boolean" />
<xsd:attribute name="name-converter" type="xsd:string" />
<xsd:attribute name="circular-reference-handler" type="xsd:string" />
</xsd:complexType>
<xsd:complexType name="property_info">

View File

@ -68,6 +68,7 @@ $container->loadFromExtension('framework', array(
'enabled' => true,
'enable_annotations' => true,
'name_converter' => 'serializer.name_converter.camel_case_to_snake_case',
'circular_reference_handler' => 'my.circular.reference.handler',
),
'property_info' => true,
'ide' => 'file%%link%%format',

View File

@ -41,7 +41,7 @@
</framework:translator>
<framework:validation enabled="true" />
<framework:annotations cache="file" debug="true" file-cache-dir="%kernel.cache_dir%/annotations" />
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" />
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" circular-reference-handler="my.circular.reference.handler" />
<framework:property-info />
</framework:config>
</container>

View File

@ -51,9 +51,10 @@ framework:
debug: true
file_cache_dir: '%kernel.cache_dir%/annotations'
serializer:
enabled: true
enable_annotations: true
name_converter: serializer.name_converter.camel_case_to_snake_case
enabled: true
enable_annotations: true
name_converter: serializer.name_converter.camel_case_to_snake_case
circular_reference_handler: my.circular.reference.handler
property_info: ~
ide: file%%link%%format
request:

View File

@ -774,6 +774,7 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertNull($container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
$this->assertEquals(new Reference('serializer.name_converter.camel_case_to_snake_case'), $container->getDefinition('serializer.normalizer.object')->getArgument(1));
$this->assertEquals(new Reference('property_info', ContainerBuilder::IGNORE_ON_INVALID_REFERENCE), $container->getDefinition('serializer.normalizer.object')->getArgument(3));
$this->assertEquals(array('setCircularReferenceHandler', array(new Reference('my.circular.reference.handler'))), $container->getDefinition('serializer.normalizer.object')->getMethodCalls()[0]);
}
public function testRegisterSerializerExtractor()

View File

@ -281,7 +281,7 @@ class ArgvInput extends Input
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if ($token === $value || 0 === strpos($token, $leading)) {
if ($token === $value || '' !== $leading && 0 === strpos($token, $leading)) {
return true;
}
}
@ -312,7 +312,7 @@ class ArgvInput extends Input
// For long options, test for '--option=' at beginning
// For short options, test for '-o' at beginning
$leading = 0 === strpos($value, '--') ? $value.'=' : $value;
if (0 === strpos($token, $leading)) {
if ('' !== $leading && 0 === strpos($token, $leading)) {
return substr($token, strlen($leading));
}
}

View File

@ -92,7 +92,7 @@ class StreamOutput extends Output
{
if (DIRECTORY_SEPARATOR === '\\') {
return
function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support($this->stream)
function_exists('sapi_windows_vt100_support') && @sapi_windows_vt100_support($this->stream)
|| '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')

View File

@ -370,6 +370,19 @@ class ArgvInputTest extends TestCase
$this->assertFalse($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input');
}
public function testNoWarningOnInvalidParameterOption()
{
$input = new ArgvInput(array('cli.php', '-edev'));
$this->assertTrue($input->hasParameterOption(array('-e', '')));
// No warning thrown
$this->assertFalse($input->hasParameterOption(array('-m', '')));
$this->assertEquals('dev', $input->getParameterOption(array('-e', '')));
// No warning thrown
$this->assertFalse($input->getParameterOption(array('-m', '')));
}
public function testToString()
{
$input = new ArgvInput(array('cli.php', '-f', 'foo'));

View File

@ -209,7 +209,7 @@ class Form extends Link implements \ArrayAccess
parse_str($query, $currentParameters);
}
$queryString = http_build_query(array_merge($currentParameters, $this->getValues()), null, '&');
$queryString = http_build_query(array_merge($currentParameters, $this->getValues()), '', '&');
$pos = strpos($uri, '?');
$base = false === $pos ? $uri : substr($uri, 0, $pos);

View File

@ -524,13 +524,18 @@ class Filesystem
/**
* Mirrors a directory to another.
*
* Copies files and directories from the origin directory into the target directory. By default:
*
* - existing files in the target directory will be overwritten, except if they are newer (see the `override` option)
* - files in the target directory that do not exist in the source directory will not be deleted (see the `delete` option)
*
* @param string $originDir The origin directory
* @param string $targetDir The target directory
* @param \Traversable $iterator A Traversable instance
* @param \Traversable $iterator Iterator that filters which files and directories to copy
* @param array $options An array of boolean options
* Valid options are:
* - $options['override'] Whether to override an existing file on copy or not (see copy())
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink())
* - $options['override'] If true, target files newer than origin files are overwritten (see copy(), defaults to false)
* - $options['copy_on_windows'] Whether to copy files instead of links on Windows (see symlink(), defaults to false)
* - $options['delete'] Whether to delete files that are not in the source directory (defaults to false)
*
* @throws IOException When file type is unknown

View File

@ -547,7 +547,7 @@ abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest
]
/following-sibling::div
[@class="form-check"]
[
[
./input[@type="radio"][@name="name"][@id="name_0"][@checked]
/following-sibling::label
[.="Choice&A"]
@ -959,8 +959,7 @@ abstract class AbstractBootstrap4LayoutTest extends AbstractBootstrap3LayoutTest
./span
[@class="input-group-text"]
[contains(.., "%")]
]
]
]
'
);

View File

@ -599,6 +599,7 @@ class MimeTypeExtensionGuesser implements ExtensionGuesserInterface
'application/x-xliff+xml' => 'xlf',
'application/x-xpinstall' => 'xpi',
'application/x-xz' => 'xz',
'application/x-zip-compressed' => 'zip',
'application/x-zmachine' => 'z1',
'application/xaml+xml' => 'xaml',
'application/xcap-diff+xml' => 'xdf',

View File

@ -524,7 +524,7 @@ class Request
*/
public function overrideGlobals()
{
$this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), null, '&')));
$this->server->set('QUERY_STRING', static::normalizeQueryString(http_build_query($this->query->all(), '', '&')));
$_GET = $this->query->all();
$_POST = $this->request->all();

View File

@ -164,7 +164,7 @@ class PdoSessionHandler extends AbstractSessionHandler
* * db_connection_options: An array of driver-specific connection options [default: array()]
* * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
*
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or null
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
* @param array $options An associative array of options
*
* @throws \InvalidArgumentException When PDO error mode is not PDO::ERRMODE_EXCEPTION
@ -178,6 +178,8 @@ class PdoSessionHandler extends AbstractSessionHandler
$this->pdo = $pdoOrDsn;
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
} elseif (is_string($pdoOrDsn) && false !== strpos($pdoOrDsn, '://')) {
$this->dsn = $this->buildDsnFromUrl($pdoOrDsn);
} else {
$this->dsn = $pdoOrDsn;
}
@ -431,6 +433,102 @@ class PdoSessionHandler extends AbstractSessionHandler
$this->driver = $this->pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
}
/**
* Builds a PDO DSN from a URL-like connection string.
*
* @param string $dsnOrUrl
*
* @return string
*
* @todo implement missing support for oci DSN (which look totally different from other PDO ones)
*/
private function buildDsnFromUrl($dsnOrUrl)
{
// (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid
$url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $dsnOrUrl);
$params = parse_url($url);
if (false === $params) {
return $dsnOrUrl; // If the URL is not valid, let's assume it might be a DSN already.
}
$params = array_map('rawurldecode', $params);
// Override the default username and password. Values passed through options will still win over these in the constructor.
if (isset($params['user'])) {
$this->username = $params['user'];
}
if (isset($params['pass'])) {
$this->password = $params['pass'];
}
if (!isset($params['scheme'])) {
throw new \InvalidArgumentException('URLs without scheme are not supported to configure the PdoSessionHandler');
}
$driverAliasMap = array(
'mssql' => 'sqlsrv',
'mysql2' => 'mysql', // Amazon RDS, for some weird reason
'postgres' => 'pgsql',
'postgresql' => 'pgsql',
'sqlite3' => 'sqlite',
);
$driver = isset($driverAliasMap[$params['scheme']]) ? $driverAliasMap[$params['scheme']] : $params['scheme'];
// Doctrine DBAL supports passing its internal pdo_* driver names directly too (allowing both dashes and underscores). This allows supporting the same here.
if (0 === strpos($driver, 'pdo_') || 0 === strpos($driver, 'pdo-')) {
$driver = substr($driver, 4);
}
switch ($driver) {
case 'mysql':
case 'pgsql':
$dsn = $driver.':';
if (isset($params['host']) && '' !== $params['host']) {
$dsn .= 'host='.$params['host'].';';
}
if (isset($params['port']) && '' !== $params['port']) {
$dsn .= 'port='.$params['port'].';';
}
if (isset($params['path'])) {
$dbName = substr($params['path'], 1); // Remove the leading slash
$dsn .= 'dbname='.$dbName.';';
}
return $dsn;
case 'sqlite':
return 'sqlite:'.substr($params['path'], 1);
case 'sqlsrv':
$dsn = 'sqlsrv:server=';
if (isset($params['host'])) {
$dsn .= $params['host'];
}
if (isset($params['port']) && '' !== $params['port']) {
$dsn .= ','.$params['port'];
}
if (isset($params['path'])) {
$dbName = substr($params['path'], 1); // Remove the leading slash
$dsn .= ';Database='.$dbName;
}
return $dsn;
default:
throw new \InvalidArgumentException(sprintf('The scheme "%s" is not supported by the PdoSessionHandler URL configuration. Pass a PDO DSN directly.', $params['scheme']));
}
}
/**
* Helper method to begin a transaction.
*

View File

@ -2177,7 +2177,7 @@ class RequestContentProxy extends Request
{
public function getContent($asResource = false)
{
return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent'));
return http_build_query(array('_method' => 'PUT', 'content' => 'mycontent'), '', '&');
}
}

View File

@ -317,6 +317,41 @@ class PdoSessionHandlerTest extends TestCase
$this->assertInstanceOf('\PDO', $method->invoke($storage));
}
/**
* @dataProvider provideUrlDsnPairs
*/
public function testUrlDsn($url, $expectedDsn, $expectedUser = null, $expectedPassword = null)
{
$storage = new PdoSessionHandler($url);
$this->assertAttributeEquals($expectedDsn, 'dsn', $storage);
if (null !== $expectedUser) {
$this->assertAttributeEquals($expectedUser, 'username', $storage);
}
if (null !== $expectedPassword) {
$this->assertAttributeEquals($expectedPassword, 'password', $storage);
}
}
public function provideUrlDsnPairs()
{
yield array('mysql://localhost/test', 'mysql:host=localhost;dbname=test;');
yield array('mysql://localhost:56/test', 'mysql:host=localhost;port=56;dbname=test;');
yield array('mysql2://root:pwd@localhost/test', 'mysql:host=localhost;dbname=test;', 'root', 'pwd');
yield array('postgres://localhost/test', 'pgsql:host=localhost;dbname=test;');
yield array('postgresql://localhost:5634/test', 'pgsql:host=localhost;port=5634;dbname=test;');
yield array('postgres://root:pwd@localhost/test', 'pgsql:host=localhost;dbname=test;', 'root', 'pwd');
yield 'sqlite relative path' => array('sqlite://localhost/tmp/test', 'sqlite:tmp/test');
yield 'sqlite absolute path' => array('sqlite://localhost//tmp/test', 'sqlite:/tmp/test');
yield 'sqlite relative path without host' => array('sqlite:///tmp/test', 'sqlite:tmp/test');
yield 'sqlite absolute path without host' => array('sqlite3:////tmp/test', 'sqlite:/tmp/test');
yield array('sqlite://localhost/:memory:', 'sqlite::memory:');
yield array('mssql://localhost/test', 'sqlsrv:server=localhost;Database=test');
yield array('mssql://localhost:56/test', 'sqlsrv:server=localhost,56;Database=test');
}
private function createStream($content)
{
$stream = tmpfile();

View File

@ -92,7 +92,7 @@ class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTyp
$properties[$propertyName] = $propertyName;
}
return array_values($properties);
return $properties ? array_values($properties) : null;
}
/**

View File

@ -59,6 +59,8 @@ class ReflectionExtractorTest extends TestCase
),
$this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')
);
$this->assertNull($this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\NoProperties'));
}
public function testGetPropertiesWithCustomPrefixes()

View File

@ -0,0 +1,21 @@
<?php
namespace Symfony\Component\PropertyInfo\Tests\Fixtures;
/*
* 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\Component\PropertyInfo\Tests\Fixtures;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class NoProperties
{
}

View File

@ -95,7 +95,7 @@ class SwitchUserListener implements ListenerInterface
if (!$this->stateless) {
$request->query->remove($this->usernameParameter);
$request->server->set('QUERY_STRING', http_build_query($request->query->all()));
$request->server->set('QUERY_STRING', http_build_query($request->query->all(), '', '&'));
$response = new RedirectResponse($request->getUri(), 302);
$event->setResponse($response);

View File

@ -114,7 +114,7 @@ class LogoutUrlGenerator
$url = UrlGeneratorInterface::ABSOLUTE_URL === $referenceType ? $request->getUriForPath($logoutPath) : $request->getBaseUrl().$logoutPath;
if (!empty($parameters)) {
$url .= '?'.http_build_query($parameters);
$url .= '?'.http_build_query($parameters, '', '&');
}
} else {
if (!$this->router) {

View File

@ -123,36 +123,37 @@ class XliffFileLoader implements LoaderInterface
$xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0');
foreach ($xml->xpath('//xliff:unit') as $unit) {
$segment = $unit->segment;
$source = $segment->source;
foreach ($unit->segment as $segment) {
$source = $segment->source;
// If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values
$target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding);
// If the xlf file has another encoding specified, try to convert it because
// simple_xml will always return utf-8 encoded values
$target = $this->utf8ToCharset((string) (isset($segment->target) ? $segment->target : $source), $encoding);
$catalogue->set((string) $source, $target, $domain);
$catalogue->set((string) $source, $target, $domain);
$metadata = array();
if (isset($segment->target) && $segment->target->attributes()) {
$metadata['target-attributes'] = array();
foreach ($segment->target->attributes() as $key => $value) {
$metadata['target-attributes'][$key] = (string) $value;
}
}
if (isset($unit->notes)) {
$metadata['notes'] = array();
foreach ($unit->notes->note as $noteNode) {
$note = array();
foreach ($noteNode->attributes() as $key => $value) {
$note[$key] = (string) $value;
$metadata = array();
if (isset($segment->target) && $segment->target->attributes()) {
$metadata['target-attributes'] = array();
foreach ($segment->target->attributes() as $key => $value) {
$metadata['target-attributes'][$key] = (string) $value;
}
$note['content'] = (string) $noteNode;
$metadata['notes'][] = $note;
}
}
$catalogue->setMetadata((string) $source, $metadata, $domain);
if (isset($unit->notes)) {
$metadata['notes'] = array();
foreach ($unit->notes->note as $noteNode) {
$note = array();
foreach ($noteNode->attributes() as $key => $value) {
$note[$key] = (string) $value;
}
$note['content'] = (string) $noteNode;
$metadata['notes'][] = $note;
}
}
$catalogue->setMetadata((string) $source, $metadata, $domain);
}
}
}

View File

@ -228,4 +228,33 @@ class XliffFileLoaderTest extends TestCase
$this->assertEquals('quality', $metadata['notes'][1]['category']);
$this->assertEquals('Fuzzy', $metadata['notes'][1]['content']);
}
public function testLoadVersion2WithMultiSegmentUnit()
{
$loader = new XliffFileLoader();
$resource = __DIR__.'/../fixtures/resources-2.0-multi-segment-unit.xlf';
$catalog = $loader->load($resource, 'en', 'domain1');
$this->assertSame('en', $catalog->getLocale());
$this->assertEquals(array(new FileResource($resource)), $catalog->getResources());
$this->assertFalse(libxml_get_last_error());
// test for "foo" metadata
$this->assertTrue($catalog->defines('foo', 'domain1'));
$metadata = $catalog->getMetadata('foo', 'domain1');
$this->assertNotEmpty($metadata);
$this->assertCount(1, $metadata['notes']);
$this->assertSame('processed', $metadata['notes'][0]['category']);
$this->assertSame('true', $metadata['notes'][0]['content']);
// test for "bar" metadata
$this->assertTrue($catalog->defines('bar', 'domain1'));
$metadata = $catalog->getMetadata('bar', 'domain1');
$this->assertNotEmpty($metadata);
$this->assertCount(1, $metadata['notes']);
$this->assertSame('processed', $metadata['notes'][0]['category']);
$this->assertSame('true', $metadata['notes'][0]['content']);
}
}

View File

@ -0,0 +1,17 @@
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0" version="2.0" srcLang="en-US" trgLang="en-US">
<file id="f1">
<unit id="1">
<notes>
<note category="processed">true</note>
</notes>
<segment id="id-foo">
<source>foo</source>
<target>foo (translated)</target>
</segment>
<segment id="id-bar">
<source>bar</source>
<target>bar (translated)</target>
</segment>
</unit>
</file>
</xliff>