Merge branch '3.4' into 4.0

* 3.4: (22 commits)
  Add application/ld+json format associated to json
  [HttpFoundation] Fix false-positive ConflictingHeadersException
  remove flex-specific suggestion on 3.4
  Add check for SecurityBundle in createAccessDeniedException
  [WebServerBundle] Fix escaping of php binary with arguments
  Error handlers' $context should be optional as it's deprecated
  [Serializer] Correct typing mistake in DocBlock
  [HttpKernel] fix cleaning legacy containers
  Display n/a for sub-requests time when Stopwatch component is not installed
  Updating message to inform the user how to install the component
  [Config] Fix closure CS
  PHP CS Fixer: use PHPUnit Migration ruleset
  [Process] Skip false-positive test on Windows/appveyor
  Update MemcachedTrait.php
  [Bridge/PhpUnit] thank phpunit/phpunit
  allow auto_wire for SessionAuthenticationStrategy class
  [Process] Fix setting empty env vars
  Fixed 'RouterInteface' typo
  [Process] Dont use getenv(), it returns arrays and can introduce subtle breaks accros PHP versions
  [WebServerBundle] fix a bug where require would not require the good file because of env
  ...
This commit is contained in:
Nicolas Grekas 2017-12-29 22:00:20 +01:00
commit 6a69c7f0d8
27 changed files with 167 additions and 33 deletions

View File

@ -8,9 +8,10 @@ return PhpCsFixer\Config::create()
->setRules(array(
'@Symfony' => true,
'@Symfony:risky' => true,
'@PHPUnit48Migration:risky' => true,
'php_unit_no_expectation_annotation' => false, // part of `PHPUnitXYMigration:risky` ruleset, to be enabled when PHPUnit 4.x support will be dropped, as we don't want to rewrite exceptions handling twice
'array_syntax' => array('syntax' => 'long'),
'protected_to_private' => false,
'php_unit_dedicate_assert' => array('target' => '3.5'),
))
->setRiskyAllowed(true)
->setFinder(

View File

@ -535,11 +535,11 @@ FrameworkBundle
first argument.
* `RouterDebugCommand::__construct()` now requires an instance of
`Symfony\Component\Routing\RouterInteface` as
`Symfony\Component\Routing\RouterInterface` as
first argument.
* `RouterMatchCommand::__construct()` now requires an instance of
`Symfony\Component\Routing\RouterInteface` as
`Symfony\Component\Routing\RouterInterface` as
first argument.
* `TranslationDebugCommand::__construct()` now requires an instance of

View File

@ -41,6 +41,10 @@
"extra": {
"branch-alias": {
"dev-master": "4.0-dev"
},
"thanks": {
"name": "phpunit/phpunit",
"url": "https://github.com/sebastianbergmann/phpunit"
}
}
}

View File

@ -67,10 +67,10 @@ CHANGELOG
`Symfony\Component\EventDispatcher\EventDispatcherInterface` as
first argument
* `RouterDebugCommand::__construct()` now takes an instance of
`Symfony\Component\Routing\RouterInteface` as
`Symfony\Component\Routing\RouterInterface` as
first argument
* `RouterMatchCommand::__construct()` now takes an instance of
`Symfony\Component\Routing\RouterInteface` as
`Symfony\Component\Routing\RouterInterface` as
first argument
* `TranslationDebugCommand::__construct()` now takes an instance of
`Symfony\Component\Translation\TranslatorInterface` as

View File

@ -289,10 +289,16 @@ trait ControllerTrait
*
* throw $this->createAccessDeniedException('Unable to access this page!');
*
* @throws \LogicException If the Security component is not available
*
* @final since version 3.4
*/
protected function createAccessDeniedException(string $message = 'Access Denied.', \Exception $previous = null): AccessDeniedException
{
if (!class_exists(AccessDeniedException::class)) {
throw new \LogicException('You can not use the "createAccessDeniedException" method if the Security component is not available.');
}
return new AccessDeniedException($message, $previous);
}

View File

@ -62,6 +62,7 @@
<service id="security.authentication.session_strategy" class="Symfony\Component\Security\Http\Session\SessionAuthenticationStrategy">
<argument>%security.authentication.session_strategy.strategy%</argument>
</service>
<service id="Symfony\Component\Security\Http\Session\SessionAuthenticationStrategyInterface" alias="security.authentication.session_strategy" />
<service id="security.encoder_factory.generic" class="Symfony\Component\Security\Core\Encoder\EncoderFactory">
<argument type="collection" />

View File

@ -14,10 +14,12 @@
} %}
{% endif %}
{% set has_time_events = collector.events|length > 0 %}
{% block toolbar %}
{% set total_time = collector.events|length ? '%.0f'|format(collector.duration) : 'n/a' %}
{% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %}
{% set initialization_time = collector.events|length ? '%.0f'|format(collector.inittime) : 'n/a' %}
{% set status_color = collector.events|length and collector.duration > 1000 ? 'yellow' : '' %}
{% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' : '' %}
{% set icon %}
{{ include('@WebProfiler/Icon/time.svg') }}
@ -75,10 +77,14 @@
<span class="label">Sub-Request{{ profile.children|length > 1 ? 's' }}</span>
</div>
{% set subrequests_time = 0 %}
{% for child in profile.children %}
{% set subrequests_time = subrequests_time + child.getcollector('time').events.__section__.duration %}
{% endfor %}
{% if has_time_events %}
{% set subrequests_time = 0 %}
{% for child in profile.children %}
{% set subrequests_time = subrequests_time + child.getcollector('time').events.__section__.duration %}
{% endfor %}
{% else %}
{% set subrequests_time = 'n/a' %}
{% endif %}
<div class="metric">
<span class="value">{{ subrequests_time }} <span class="unit">ms</span></span>

View File

@ -30,7 +30,7 @@ if (is_file($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'
return false;
}
$script = getenv('APP_FRONT_CONTROLLER') ?: 'index.php';
$script = isset($_ENV['APP_FRONT_CONTROLLER']) ? $_ENV['APP_FRONT_CONTROLLER'] : 'index.php';
$_SERVER = array_merge($_SERVER, $_ENV);
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$script;

View File

@ -146,11 +146,11 @@ class WebServer
private function createServerProcess(WebServerConfig $config)
{
$finder = new PhpExecutableFinder();
if (false === $binary = $finder->find()) {
if (false === $binary = $finder->find(false)) {
throw new \RuntimeException('Unable to find the PHP binary.');
}
$process = new Process(array($binary, '-S', $config->getAddress(), $config->getRouter()));
$process = new Process(array_merge(array($binary), $finder->findArguments(), array('-dvariables_order=EGPCS', '-S', $config->getAddress(), $config->getRouter())));
$process->setWorkingDirectory($config->getDocumentRoot());
$process->setTimeout(null);

View File

@ -32,7 +32,7 @@ class WebServerConfig
throw new \InvalidArgumentException(sprintf('Unable to find the front controller under "%s" (none of these files exist: %s).', $documentRoot, implode(', ', $this->getFrontControllerFileNames($env))));
}
putenv('APP_FRONT_CONTROLLER='.$file);
$_ENV['APP_FRONT_CONTROLLER'] = $file;
$this->documentRoot = $documentRoot;
$this->env = $env;

View File

@ -21,7 +21,7 @@
"symfony/console": "~3.4|~4.0",
"symfony/dependency-injection": "~3.4|~4.0",
"symfony/http-kernel": "~3.4|~4.0",
"symfony/process": "~3.4|~4.0"
"symfony/process": "^3.4.2|^4.0.2"
},
"autoload": {
"psr-4": { "Symfony\\Bundle\\WebServerBundle\\": "" },

View File

@ -71,7 +71,7 @@ trait MemcachedTrait
*
* @return \Memcached
*
* @throws \ErrorEception When invalid options or servers are provided
* @throws \ErrorException When invalid options or servers are provided
*/
public static function createConnection($servers, array $options = array())
{

View File

@ -186,7 +186,7 @@ class ExprBuilder
*/
public function thenInvalid($message)
{
$this->thenPart = function ($v) use ($message) {throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };
$this->thenPart = function ($v) use ($message) { throw new \InvalidArgumentException(sprintf($message, json_encode($v))); };
return $this;
}

View File

@ -158,7 +158,7 @@ class ResourceCheckerConfigCache implements ConfigCacheInterface
$meta = false;
$signalingException = new \UnexpectedValueException();
$prevUnserializeHandler = ini_set('unserialize_callback_func', '');
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context) use (&$prevErrorHandler, $signalingException) {
$prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) use (&$prevErrorHandler, $signalingException) {
if (E_WARNING === $type && 'Class __PHP_Incomplete_Class has no unserializer' === $msg) {
throw $signalingException;
}

View File

@ -573,6 +573,7 @@ class Application
{
$this->init();
$aliases = array();
$allCommands = $this->commandLoader ? array_merge($this->commandLoader->getNames(), array_keys($this->commands)) : array_keys($this->commands);
$expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name);
$commands = preg_grep('{^'.$expr.'}', $allCommands);
@ -605,14 +606,15 @@ class Application
// filter out aliases for commands which are already on the list
if (count($commands) > 1) {
$commandList = $this->commandLoader ? array_merge(array_flip($this->commandLoader->getNames()), $this->commands) : $this->commands;
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) {
$commands = array_unique(array_filter($commands, function ($nameOrAlias) use ($commandList, $commands, &$aliases) {
$commandName = $commandList[$nameOrAlias] instanceof Command ? $commandList[$nameOrAlias]->getName() : $nameOrAlias;
$aliases[$nameOrAlias] = $commandName;
return $commandName === $nameOrAlias || !in_array($commandName, $commands);
}));
}
$exact = in_array($name, $commands, true);
$exact = in_array($name, $commands, true) || isset($aliases[$name]);
if (count($commands) > 1 && !$exact) {
$usableWidth = $this->terminal->getWidth() - 10;
$abbrevs = array_values($commands);

View File

@ -56,6 +56,8 @@ class ApplicationTest extends TestCase
require_once self::$fixturesPath.'/BarBucCommand.php';
require_once self::$fixturesPath.'/FooSubnamespaced1Command.php';
require_once self::$fixturesPath.'/FooSubnamespaced2Command.php';
require_once self::$fixturesPath.'/TestTiti.php';
require_once self::$fixturesPath.'/TestToto.php';
}
protected function normalizeLineBreaks($text)
@ -282,6 +284,14 @@ class ApplicationTest extends TestCase
$application->findNamespace('f');
}
public function testFindNonAmbiguous()
{
$application = new Application();
$application->add(new \TestTiti());
$application->add(new \TestToto());
$this->assertEquals('test-toto', $application->find('test')->getName());
}
/**
* @expectedException \Symfony\Component\Console\Exception\CommandNotFoundException
* @expectedExceptionMessage There are no commands defined in the "bar" namespace.

View File

@ -0,0 +1,21 @@
<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TestTiti extends Command
{
protected function configure()
{
$this
->setName('test-titi')
->setDescription('The test:titi command')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->write('test-titi');
}
}

View File

@ -0,0 +1,22 @@
<?php
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TestToto extends Command
{
protected function configure()
{
$this
->setName('test-toto')
->setDescription('The test-toto command')
->setAliases(array('test'))
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->write('test-toto');
}
}

View File

@ -691,8 +691,8 @@ class Crawler implements \Countable, \IteratorAggregate
*/
public function filter($selector)
{
if (!class_exists('Symfony\\Component\\CssSelector\\CssSelectorConverter')) {
throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector 2.8+ is not installed (you can use filterXPath instead).');
if (!class_exists(CssSelectorConverter::class)) {
throw new \RuntimeException('To filter with a CSS selector, install the CssSelector component ("composer require symfony/css-selector"). Or use filterXpath instead.');
}
$converter = new CssSelectorConverter($this->isHtml);

View File

@ -1852,6 +1852,7 @@ class Request
'js' => array('application/javascript', 'application/x-javascript', 'text/javascript'),
'css' => array('text/css'),
'json' => array('application/json', 'application/x-json'),
'jsonld' => array('application/ld+json'),
'xml' => array('text/xml', 'application/xml', 'application/x-xml'),
'rdf' => array('application/rdf+xml'),
'atom' => array('application/atom+xml'),
@ -1927,13 +1928,13 @@ class Request
$clientValues = array();
$forwardedValues = array();
if (self::$trustedHeaders[$type] && $this->headers->has(self::$trustedHeaders[$type])) {
if ((self::$trustedHeaderSet & $type) && $this->headers->has(self::$trustedHeaders[$type])) {
foreach (explode(',', $this->headers->get(self::$trustedHeaders[$type])) as $v) {
$clientValues[] = (self::HEADER_X_FORWARDED_PORT === $type ? '0.0.0.0:' : '').trim($v);
}
}
if (self::$trustedHeaders[self::HEADER_FORWARDED] && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
if ((self::$trustedHeaderSet & self::HEADER_FORWARDED) && $this->headers->has(self::$trustedHeaders[self::HEADER_FORWARDED])) {
$forwardedValues = $this->headers->get(self::$trustedHeaders[self::HEADER_FORWARDED]);
$forwardedValues = preg_match_all(sprintf('{(?:%s)=(?:"?\[?)([a-zA-Z0-9\.:_\-/]*+)}', self::$forwardedParams[$type]), $forwardedValues, $matches) ? $matches[1] : array();
}

View File

@ -372,6 +372,7 @@ class RequestTest extends TestCase
array('js', array('application/javascript', 'application/x-javascript', 'text/javascript')),
array('css', array('text/css')),
array('json', array('application/json', 'application/x-json')),
array('jsonld', array('application/ld+json')),
array('xml', array('text/xml', 'application/xml', 'application/x-xml')),
array('rdf', array('application/rdf+xml')),
array('atom', array('application/atom+xml')),
@ -968,6 +969,26 @@ class RequestTest extends TestCase
$request->getClientIps();
}
/**
* @dataProvider getClientIpsWithConflictingHeadersProvider
*/
public function testGetClientIpsOnlyXHttpForwardedForTrusted($httpForwarded, $httpXForwardedFor)
{
$request = new Request();
$server = array(
'REMOTE_ADDR' => '88.88.88.88',
'HTTP_FORWARDED' => $httpForwarded,
'HTTP_X_FORWARDED_FOR' => $httpXForwardedFor,
);
Request::setTrustedProxies(array('88.88.88.88'), Request::HEADER_X_FORWARDED_FOR);
$request->initialize(array(), array(), array(), array(), array(), $server);
$this->assertSame(array_reverse(explode(',', $httpXForwardedFor)), $request->getClientIps());
}
public function getClientIpsWithConflictingHeadersProvider()
{
// $httpForwarded $httpXForwardedFor

View File

@ -532,7 +532,7 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
$oldContainerDir = dirname($oldContainer->getFileName());
foreach (glob(dirname($oldContainerDir).'/*.legacy') as $legacyContainer) {
if ($oldContainerDir.'.legacy' !== $legacyContainer && @unlink($legacyContainer)) {
(new Filesystem())->remove(substr($legacyContainer, 0, -16));
(new Filesystem())->remove(substr($legacyContainer, 0, -7));
}
}

View File

@ -64,6 +64,31 @@ class KernelTest extends TestCase
$this->assertNull($clone->getContainer());
}
public function testInitializeContainerClearsOldContainers()
{
$fs = new Filesystem();
$legacyContainerDir = __DIR__.'/Fixtures/cache/custom/ContainerA123456';
$fs->mkdir($legacyContainerDir);
touch($legacyContainerDir.'.legacy');
$kernel = new CustomProjectDirKernel();
$kernel->boot();
$containerDir = __DIR__.'/Fixtures/cache/custom/'.substr(get_class($kernel->getContainer()), 0, 16);
$this->assertTrue(unlink(__DIR__.'/Fixtures/cache/custom/FixturesCustomDebugProjectContainer.php.meta'));
$this->assertFileExists($containerDir);
$this->assertFileNotExists($containerDir.'.legacy');
$kernel = new CustomProjectDirKernel(function ($container) { $container->register('foo', 'stdClass')->setPublic(true); });
$kernel->boot();
$this->assertFileExists($containerDir);
$this->assertFileExists($containerDir.'.legacy');
$this->assertFileNotExists($legacyContainerDir);
$this->assertFileNotExists($legacyContainerDir.'.legacy');
}
public function testBootInitializesBundlesAndContainer()
{
$kernel = $this->getKernel(array('initializeBundles', 'initializeContainer'));
@ -710,7 +735,7 @@ class CustomProjectDirKernel extends Kernel
class PassKernel extends CustomProjectDirKernel implements CompilerPassInterface
{
public function __construct(\Closure $buildContainer = null)
public function __construct()
{
parent::__construct();
Kernel::__construct('pass', true);

View File

@ -288,12 +288,20 @@ class Process implements \IteratorAggregate
// @see : https://bugs.php.net/69442
$ptsWorkaround = fopen(__FILE__, 'r');
}
if (defined('HHVM_VERSION')) {
$envPairs = $env;
} else {
$envPairs = array();
foreach ($env as $k => $v) {
$envPairs[] = $k.'='.$v;
}
}
if (!is_dir($this->cwd)) {
throw new RuntimeException('The provided cwd does not exist.');
}
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $env, $options);
$this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $options);
if (!is_resource($this->process)) {
throw new RuntimeException('Unable to launch a new process.');
@ -1536,7 +1544,13 @@ class Process implements \IteratorAggregate
private function getDefaultEnv()
{
$env = getenv();
$env = array();
foreach ($_SERVER as $k => $v) {
if (is_string($v) && false !== $v = getenv($k)) {
$env[$k] = $v;
}
}
foreach ($_ENV as $k => $v) {
if (is_string($v)) {

View File

@ -1410,14 +1410,14 @@ class ProcessTest extends TestCase
public function testEnvIsInherited()
{
$process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ'));
$process = $this->getProcessForCode('echo serialize($_SERVER);', null, array('BAR' => 'BAZ', 'EMPTY' => ''));
putenv('FOO=BAR');
$_ENV['FOO'] = 'BAR';
$process->run();
$expected = array('BAR' => 'BAZ', 'FOO' => 'BAR');
$expected = array('BAR' => 'BAZ', 'EMPTY' => '', 'FOO' => 'BAR');
$env = array_intersect_key(unserialize($process->getOutput()), $expected);
$this->assertEquals($expected, $env);

View File

@ -57,7 +57,7 @@ class CustomNormalizer implements NormalizerInterface, DenormalizerInterface, Se
}
/**
* Checks if the given class implements the NormalizableInterface.
* Checks if the given class implements the DenormalizableInterface.
*
* @param mixed $data Data to denormalize from
* @param string $type The class to which the data should be denormalized

View File

@ -208,7 +208,7 @@ abstract class AbstractCloner implements ClonerInterface
*/
public function cloneVar($var, $filter = 0)
{
$this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context) {
$this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = array()) {
if (E_RECOVERABLE_ERROR === $type || E_USER_ERROR === $type) {
// Cloner never dies
throw new \ErrorException($msg, 0, $type, $file, $line);