diff --git a/.github/build-packages.php b/.github/build-packages.php new file mode 100644 index 0000000000..2a2b4a396c --- /dev/null +++ b/.github/build-packages.php @@ -0,0 +1,74 @@ + $_SERVER['argc']) { + echo "Usage: branch version dir1 dir2 ... dirN\n"; + exit(1); +} +chdir(dirname(__DIR__)); + +$dirs = $_SERVER['argv']; +array_shift($dirs); +$mergeBase = trim(shell_exec(sprintf('git merge-base %s HEAD', array_shift($dirs)))); +$version = array_shift($dirs); + +$packages = array(); +$flags = PHP_VERSION_ID >= 50400 ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0; + +foreach ($dirs as $k => $dir) { + if (!system("git diff --name-only $mergeBase -- $dir", $exitStatus)) { + if ($exitStatus) { + exit($exitStatus); + } + unset($dirs[$k]); + continue; + } + echo "$dir\n"; + + $json = ltrim(file_get_contents($dir.'/composer.json')); + if (null === $package = json_decode($json)) { + passthru("composer validate $dir/composer.json"); + exit(1); + } + + $package->repositories = array(array( + 'type' => 'composer', + 'url' => 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__)).'/', + )); + if (false === strpos($json, "\n \"repositories\": [\n")) { + $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); + file_put_contents($dir.'/composer.json', $json); + } + passthru("cd $dir && tar -cf package.tar --exclude='package.tar' *"); + + $package->version = $version.'.999'; + $package->dist['type'] = 'tar'; + $package->dist['url'] = 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__))."/$dir/package.tar"; + + $packages[$package->name][$package->version] = $package; + + $versions = file_get_contents('https://packagist.org/packages/'.$package->name.'.json'); + $versions = json_decode($versions); + + foreach ($versions->package->versions as $v => $package) { + $packages[$package->name] += array($v => $package); + } +} + +file_put_contents('packages.json', json_encode(compact('packages'), $flags)); + +if ($dirs) { + $json = ltrim(file_get_contents('composer.json')); + if (null === $package = json_decode($json)) { + passthru("composer validate $dir/composer.json"); + exit(1); + } + + $package->repositories = array(array( + 'type' => 'composer', + 'url' => 'file://'.str_replace(DIRECTORY_SEPARATOR, '/', dirname(__DIR__)).'/', + )); + if (false === strpos($json, "\n \"repositories\": [\n")) { + $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); + file_put_contents('composer.json', $json); + } +} diff --git a/.github/travis.php b/.github/travis.php deleted file mode 100644 index daaa858dc7..0000000000 --- a/.github/travis.php +++ /dev/null @@ -1,54 +0,0 @@ - $_SERVER['argc']) { - echo "Usage: branch version dir1 dir2 ... dirN\n"; - exit(1); -} -chdir(dirname(__DIR__)); - -$dirs = $_SERVER['argv']; -array_shift($dirs); -$branch = array_shift($dirs); -$version = array_shift($dirs); - -$packages = array(); -$flags = PHP_VERSION_ID >= 50400 ? JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE : 0; - -foreach ($dirs as $dir) { - if (!system("git diff --name-only $branch...HEAD -- $dir", $exitStatus)) { - if ($exitStatus) { - exit($exitStatus); - } - continue; - } - echo "$dir\n"; - - $json = ltrim(file_get_contents($dir.'/composer.json')); - if (null === $package = json_decode($json)) { - passthru("composer validate $dir/composer.json"); - exit(1); - } - - $package->repositories = array(array( - 'type' => 'composer', - 'url' => 'file://'.dirname(__DIR__).'/', - )); - $json = rtrim(json_encode(array('repositories' => $package->repositories), $flags), "\n}").','.substr($json, 1); - file_put_contents($dir.'/composer.json', $json); - passthru("cd $dir && tar -cf package.tar --exclude='package.tar' *"); - - $package->version = 'master' !== $version ? $version.'.999' : 'dev-master'; - $package->dist['type'] = 'tar'; - $package->dist['url'] = 'file://'.dirname(__DIR__)."/$dir/package.tar"; - - $packages[$package->name][$package->version] = $package; - - $versions = @file_get_contents('https://packagist.org/packages/'.$package->name.'.json') ?: '{"package":{"versions":[]}}'; - $versions = json_decode($versions); - - foreach ($versions->package->versions as $v => $package) { - $packages[$package->name] += array($v => $package); - } -} - -file_put_contents('packages.json', json_encode(compact('packages'), $flags)); diff --git a/.travis.yml b/.travis.yml index 6980630039..d408596fbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,24 +64,29 @@ before_install: - if [[ ! $skip && ! $PHP = hhvm* ]]; then echo extension = redis.so >> $INI_FILE; fi; - if [[ ! $skip && ! $PHP = hhvm* ]]; then phpenv config-rm xdebug.ini || echo "xdebug not available"; fi - if [[ ! $skip ]]; then [ -d ~/.composer ] || mkdir ~/.composer; cp .composer/* ~/.composer/; fi - - if [[ ! $skip ]]; then ./phpunit install; fi - if [[ ! $skip ]]; then export PHPUNIT=$(readlink -f ./phpunit); fi - if [[ ! $skip ]]; then ldapadd -h localhost:3389 -D cn=admin,dc=symfony,dc=com -w symfony -f src/Symfony/Component/Ldap/Tests/Fixtures/data/base.ldif; fi - if [[ ! $skip ]]; then ldapadd -h localhost:3389 -D cn=admin,dc=symfony,dc=com -w symfony -f src/Symfony/Component/Ldap/Tests/Fixtures/data/fixtures.ldif; fi install: + - if [[ ! $skip && $deps ]]; then cp composer.json composer.json.orig; fi + - if [[ ! $skip && $deps ]]; then echo -e '{\n"require":{'"$(grep phpunit-bridge composer.json)"'"php":"*"},"minimum-stability":"dev"}' > composer.json; fi - if [[ ! $skip ]]; then COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi # Create local composer packages for each patched components and reference them in composer.json files when cross-testing components - - if [[ ! $skip && $deps ]]; then git fetch origin $TRAVIS_BRANCH && php .github/travis.php FETCH_HEAD $TRAVIS_BRANCH $COMPONENTS; fi + - if [[ ! $skip && $deps ]]; then php .github/build-packages.php HEAD^ $TRAVIS_BRANCH $COMPONENTS; fi + - if [[ ! $skip && $deps ]]; then mv composer.json composer.json.phpunit; mv composer.json.orig composer.json; fi + - if [[ ! $skip && ! $deps ]]; then php .github/build-packages.php HEAD^ $TRAVIS_BRANCH src/Symfony/Bridge/PhpUnit; fi # For the master branch when deps=high, the version before master is checked out and tested with the locally patched components - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then SYMFONY_VERSION=$(git ls-remote --heads | grep -o '/[1-9].*' | tail -n 1 | sed s/.//); else SYMFONY_VERSION=$(cat composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9.]*'); fi - - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); ./phpunit install; fi + - if [[ $deps = high && $TRAVIS_BRANCH = master ]]; then git fetch origin $SYMFONY_VERSION; git checkout -m FETCH_HEAD; COMPONENTS=$(find src/Symfony -mindepth 3 -type f -name phpunit.xml.dist -printf '%h\n'); fi # Legacy tests are skipped when deps=high and when the current branch version has not the same major version number than the next one - if [[ $deps = high && ${SYMFONY_VERSION%.*} != $(git show $(git ls-remote --heads | grep -FA1 /$SYMFONY_VERSION | tail -n 1):composer.json | grep '^ *"dev-master". *"[1-9]' | grep -o '[0-9]*' | head -n 1) ]]; then LEGACY=,legacy; fi - export COMPOSER_ROOT_VERSION=$SYMFONY_VERSION.x-dev - - if [[ ! $deps ]]; then composer update; else export SYMFONY_DEPRECATIONS_HELPER=weak; fi - - if [[ $TRAVIS_BRANCH = master ]]; then export SYMFONY_PHPUNIT_OVERLOAD=1; fi - - if [[ ! $PHP = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi + - if [[ ! $skip && $deps ]]; then export SYMFONY_DEPRECATIONS_HELPER=weak; fi + - if [[ ! $skip && $deps ]]; then mv composer.json.phpunit composer.json; fi + - if [[ ! $skip ]]; then composer update; fi + - if [[ ! $skip ]]; then COMPOSER_ROOT_VERSION= ./phpunit install; fi + - if [[ ! $skip && ! $PHP = hhvm* ]]; then php -i; else hhvm --php -r 'print_r($_SERVER);print_r(ini_get_all());'; fi script: - if [[ $skip ]]; then echo -e "\\n\\e[1;34mIntermediate PHP version $PHP is skipped for pull requests.\\e[0m"; fi diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index e10ae01026..7cbed979c4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -16,8 +16,8 @@ Symfony is the result of the work of many people who made the code better - Kris Wallsmith (kriswallsmith) - Jakub Zalas (jakubzalas) - Ryan Weaver (weaverryan) - - Kévin Dunglas (dunglas) - Javier Eguiluz (javier.eguiluz) + - Kévin Dunglas (dunglas) - Hugo Hamon (hhamon) - Abdellatif Ait boudad (aitboudad) - Pascal Borreli (pborreli) @@ -52,8 +52,8 @@ Symfony is the result of the work of many people who made the code better - Konstantin Kudryashov (everzet) - Bilal Amarni (bamarni) - Florin Patan (florinpatan) - - Peter Rehm (rpet) - Ener-Getick (energetick) + - Peter Rehm (rpet) - Iltar van der Berg (kjarli) - Kevin Bond (kbond) - Andrej Hudec (pulzarraider) @@ -315,6 +315,7 @@ Symfony is the result of the work of many people who made the code better - JhonnyL - hossein zolfi (ocean) - Clément Gautier (clementgautier) + - James Halsall (jaitsu) - Eduardo Gulias (egulias) - giulio de donato (liuggio) - Stéphane PY (steph_py) @@ -396,7 +397,6 @@ Symfony is the result of the work of many people who made the code better - Christian Wahler - Mathieu Lemoine - Gintautas Miselis - - James Halsall (jaitsu) - David Badura (davidbadura) - Zander Baldwin - Adam Harvey @@ -1302,6 +1302,7 @@ Symfony is the result of the work of many people who made the code better - Muriel (metalmumu) - Michael Pohlers (mick_the_big) - Cayetano Soriano Gallego (neoshadybeat) + - Ondrej Machulda (ondram) - Patrick McDougle (patrick-mcdougle) - Pablo Monterde Perez (plebs) - Jimmy Leger (redpanda) diff --git a/appveyor.yml b/appveyor.yml index 07cdec2952..7f3b017616 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -46,18 +46,20 @@ install: - echo curl.cainfo=c:\php\cacert.pem >> php.ini-max - copy /Y php.ini-max php.ini - cd c:\projects\symfony - - IF NOT EXIST composer.phar (appveyor DownloadFile https://getcomposer.org/download/1.2.0/composer.phar) + - IF NOT EXIST composer.phar (appveyor DownloadFile https://getcomposer.org/download/1.2.1/composer.phar) - php composer.phar self-update - copy /Y .composer\* %APPDATA%\Composer\ - - php phpunit install + - php .github/build-packages.php "HEAD^" %APPVEYOR_REPO_BRANCH% src\Symfony\Bridge\PhpUnit - IF %APPVEYOR_REPO_BRANCH%==master (SET COMPOSER_ROOT_VERSION=dev-master) ELSE (SET COMPOSER_ROOT_VERSION=%APPVEYOR_REPO_BRANCH%.x-dev) - php composer.phar update --no-progress --ansi + - SET COMPOSER_ROOT_VERSION= + - php phpunit install test_script: - cd c:\projects\symfony - SET X=0 - copy /Y c:\php\php.ini-min c:\php\php.ini - - php phpunit symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! - copy /Y c:\php\php.ini-max c:\php\php.ini - - php phpunit symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! + - php phpunit src\Symfony --exclude-group benchmark,intl-data || SET X=!errorlevel! - exit %X% diff --git a/composer.json b/composer.json index e314ccf6d8..b6016a6d0f 100644 --- a/composer.json +++ b/composer.json @@ -87,6 +87,7 @@ "ocramius/proxy-manager": "~0.4|~1.0|~2.0", "predis/predis": "~1.0", "egulias/email-validator": "~1.2,>=1.2.8|~2.0", + "symfony/phpunit-bridge": "~3.2", "symfony/polyfill-apcu": "~1.1", "symfony/security-acl": "~2.8|~3.0", "phpdocumentor/reflection-docblock": "^3.0" @@ -115,11 +116,6 @@ "**/Tests/" ] }, - "autoload-dev": { - "psr-4": { - "Symfony\\Bridge\\PhpUnit\\": "src/Symfony/Bridge/PhpUnit/" - } - }, "minimum-stability": "dev", "extra": { "branch-alias": { diff --git a/phpunit b/phpunit new file mode 100755 index 0000000000..f9243bcbf9 --- /dev/null +++ b/phpunit @@ -0,0 +1,9 @@ +#!/usr/bin/env php +getDomains() as $domain) { $newKeys = array_keys($operation->getNewMessages($domain)); $allKeys = array_keys($operation->getMessages($domain)); - $domainMessagesCount = count($newKeys) + count($allKeys); - $io->section(sprintf('Messages extracted for domain "%s" (%d messages)', $domain, $domainMessagesCount)); - - $io->listing(array_merge( + $list = array_merge( array_diff($allKeys, $newKeys), array_map(function ($id) { return sprintf('%s', $id); @@ -178,7 +175,12 @@ EOF array_map(function ($id) { return sprintf('%s', $id); }, array_keys($operation->getObsoleteMessages($domain))) - )); + ); + + $domainMessagesCount = count($list); + + $io->section(sprintf('Messages extracted for domain "%s" (%d message%s)', $domain, $domainMessagesCount, $domainMessagesCount > 1 ? 's' : '')); + $io->listing($list); $extractedMessagesCount += $domainMessagesCount; } @@ -187,7 +189,7 @@ EOF $io->comment('Xliff output version is 1.2'); } - $resultMessage = sprintf('%d messages were successfully extracted', $extractedMessagesCount); + $resultMessage = sprintf('%d message%s successfully extracted', $extractedMessagesCount, $extractedMessagesCount > 1 ? 's were' : ' was'); } if ($input->getOption('no-backup') === true) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css index c50c1a54c7..9a1cbb8cc2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/public/css/body.css @@ -39,7 +39,7 @@ build: 56 font-family: Georgia, "Times New Roman", Times, serif; font-size: 20px; color: #313131; - word-break: break-all; + word-wrap: break-word; } .sf-reset li { padding-bottom: 10px; diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php index d815c9d610..e5f930af93 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Command/TranslationUpdateCommandTest.php @@ -28,6 +28,15 @@ class TranslationUpdateCommandTest extends \PHPUnit_Framework_TestCase $tester = $this->createCommandTester($this->getContainer(array('messages' => array('foo' => 'foo')))); $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true)); $this->assertRegExp('/foo/', $tester->getDisplay()); + $this->assertRegExp('/1 message was successfully extracted/', $tester->getDisplay()); + } + + public function testDumpTwoMessagesAndClean() + { + $tester = $this->createCommandTester($this->getContainer(array('foo' => 'foo', 'bar' => 'bar'))); + $tester->execute(array('command' => 'translation:update', 'locale' => 'en', 'bundle' => 'foo', '--dump-messages' => true, '--clean' => true)); + $this->assertRegExp('/foo/', $tester->getDisplay()); + $this->assertRegExp('/bar/', $tester->getDisplay()); $this->assertRegExp('/2 messages were successfully extracted/', $tester->getDisplay()); } diff --git a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php index 3f7b4a639c..60745ec8e6 100644 --- a/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php +++ b/src/Symfony/Component/ClassLoader/ClassCollectionLoader.php @@ -55,7 +55,12 @@ class ClassCollectionLoader $classes = array_unique($classes); - $cache = $cacheDir.'/'.$name.$extension; + // cache the core classes + if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { + throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); + } + $cacheDir = rtrim(realpath($cacheDir), '/'.DIRECTORY_SEPARATOR); + $cache = $cacheDir.DIRECTORY_SEPARATOR.$name.$extension; // auto-reload $reload = false; @@ -119,6 +124,16 @@ class ClassCollectionLoader $declared[$class->getName()] = true; } + // cache the core classes + $cacheDir = dirname($cache); + if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) { + throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir)); + } + + $c = '(?:\s*+(?:(?:#|//)[^\n]*+\n|/\*(?:(?getName()] = true; - $files[$class->getName()] = $class->getFileName(); + $files[$class->getName()] = $file = $class->getFileName(); + $c = file_get_contents($file); - $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($class->getFileName())); + if (preg_match($strictTypesRegex, $c)) { + $file = explode(DIRECTORY_SEPARATOR, $file); - // fakes namespace declaration for global code - if (!$class->inNamespace()) { - $c = "\nnamespace\n{\n".$c."\n}\n"; + for ($i = 0; isset($file[$i], $cacheDir[$i]); ++$i) { + if ($file[$i] !== $cacheDir[$i]) { + break; + } + } + if (1 >= $i) { + $file = var_export(implode(DIRECTORY_SEPARATOR, $file), true); + } else { + $file = array_slice($file, $i); + $file = str_repeat('..'.DIRECTORY_SEPARATOR, count($cacheDir) - $i).implode(DIRECTORY_SEPARATOR, $file); + $file = '__DIR__.'.var_export(DIRECTORY_SEPARATOR.$file, true); + } + + $c = "\nnamespace {require $file;}"; + } else { + $c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', $c); + + // fakes namespace declaration for global code + if (!$class->inNamespace()) { + $c = "\nnamespace\n{\n".$c."\n}\n"; + } + + $c = self::fixNamespaceDeclarations(' realpath(__DIR__).'/Fixtures/Namespaced/Foo.php', 'Namespaced\\Baz' => realpath(__DIR__).'/Fixtures/Namespaced/Baz.php', 'Namespaced\\WithComments' => realpath(__DIR__).'/Fixtures/Namespaced/WithComments.php', + 'Namespaced\WithStrictTypes' => realpath(__DIR__).'/Fixtures/Namespaced/WithStrictTypes.php', ), ), array(__DIR__.'/Fixtures/beta/NamespaceCollision', array( diff --git a/src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced/WithStrictTypes.php b/src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced/WithStrictTypes.php new file mode 100644 index 0000000000..3c7870592b --- /dev/null +++ b/src/Symfony/Component/ClassLoader/Tests/Fixtures/Namespaced/WithStrictTypes.php @@ -0,0 +1,13 @@ +commandShouldRun()) { try { + $e = null; $exitCode = $command->run($input, $output); - } catch (\Exception $e) { + } catch (\Exception $x) { + $e = $x; + } catch (\Throwable $x) { + $e = new FatalThrowableError($x); + } + if (null !== $e) { $event = new ConsoleExceptionEvent($command, $input, $output, $e, $e->getCode()); $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); - $e = $event->getException(); + if ($e !== $event->getException()) { + $x = $e = $event->getException(); + } $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode()); $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); - throw $e; + throw $x; } } else { $exitCode = ConsoleCommandEvent::RETURN_CODE_DISABLED; diff --git a/src/Symfony/Component/Console/Tests/ApplicationTest.php b/src/Symfony/Component/Console/Tests/ApplicationTest.php index 3ef33f3794..c221430630 100644 --- a/src/Symfony/Component/Console/Tests/ApplicationTest.php +++ b/src/Symfony/Component/Console/Tests/ApplicationTest.php @@ -942,6 +942,62 @@ class ApplicationTest extends \PHPUnit_Framework_TestCase $this->assertContains('before.foo.caught.after.', $tester->getDisplay()); } + /** + * @expectedException \LogicException + * @expectedExceptionMessage caught + */ + public function testRunWithErrorAndDispatcher() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + $application->setCatchExceptions(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dym')); + $this->assertContains('before.dym.caught.after.', $tester->getDisplay(), 'The PHP Error did not dispached events'); + } + + public function testRunDispatchesAllEventsWithError() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('dym')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dym.'); + + throw new \Error('dymerr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dym')); + $this->assertContains('before.dym.caught.after.', $tester->getDisplay(), 'The PHP Error did not dispached events'); + } + + public function testRunWithErrorFailingStatusCode() + { + $application = new Application(); + $application->setDispatcher($this->getDispatcher()); + $application->setAutoExit(false); + + $application->register('dus')->setCode(function (InputInterface $input, OutputInterface $output) { + $output->write('dus.'); + + throw new \Error('duserr'); + }); + + $tester = new ApplicationTester($application); + $tester->run(array('command' => 'dus')); + $this->assertSame(1, $tester->getStatusCode(), 'Status code should be 1'); + } + public function testRunWithDispatcherSkippingCommand() { $application = new Application(); diff --git a/src/Symfony/Component/Console/composer.json b/src/Symfony/Component/Console/composer.json index cb73a00b8d..d19fd7f881 100644 --- a/src/Symfony/Component/Console/composer.json +++ b/src/Symfony/Component/Console/composer.json @@ -17,7 +17,8 @@ ], "require": { "php": ">=5.5.9", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/debug": "~2.7,>=2.7.2" }, "require-dev": { "symfony/event-dispatcher": "~2.8|~3.0", diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php index 508a8978ea..59d4e0a767 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RepeatedPass.php @@ -56,14 +56,12 @@ class RepeatedPass implements CompilerPassInterface */ public function process(ContainerBuilder $container) { - $this->repeat = false; - foreach ($this->passes as $pass) { - $pass->process($container); - } - - if ($this->repeat) { - $this->process($container); - } + do { + $this->repeat = false; + foreach ($this->passes as $pass) { + $pass->process($container); + } + } while ($this->repeat); } /** diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 997d06f41b..438f351067 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -351,7 +351,8 @@ class ContainerBuilder extends Container implements TaggedContainerInterface { $id = strtolower($id); - if ($this->isFrozen() && (!isset($this->definitions[$id]) || !$this->definitions[$id]->isSynthetic())) { + if ($this->isFrozen() && (isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())) { + // setting a synthetic service on a frozen container is alright throw new BadMethodCallException(sprintf('Setting service "%s" for an unknown or non-synthetic service definition on a frozen container is not allowed.', $id)); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php index c569232f20..75f87dc7fc 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/GraphvizDumper.php @@ -128,7 +128,7 @@ class GraphvizDumper extends Dumper * * @return array An array of edges */ - private function findEdges($id, $arguments, $required, $name) + private function findEdges($id, array $arguments, $required, $name) { $edges = array(); foreach ($arguments as $argument) { @@ -244,7 +244,7 @@ class GraphvizDumper extends Dumper * * @return string A comma separated list of attributes */ - private function addAttributes($attributes) + private function addAttributes(array $attributes) { $code = array(); foreach ($attributes as $k => $v) { @@ -261,7 +261,7 @@ class GraphvizDumper extends Dumper * * @return string A space separated list of options */ - private function addOptions($options) + private function addOptions(array $options) { $code = array(); foreach ($options as $k => $v) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index 089d3053cf..c87a7e6b92 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -374,7 +374,7 @@ class PhpDumper extends Dumper * @throws InvalidArgumentException * @throws RuntimeException */ - private function addServiceInstance($id, $definition) + private function addServiceInstance($id, Definition $definition) { $class = $definition->getClass(); @@ -422,7 +422,7 @@ class PhpDumper extends Dumper * * @return bool */ - private function isSimpleInstance($id, $definition) + private function isSimpleInstance($id, Definition $definition) { foreach (array_merge(array($definition), $this->getInlinedDefinitions($definition)) as $sDefinition) { if ($definition !== $sDefinition && !$this->hasReference($id, $sDefinition->getMethodCalls())) { @@ -446,7 +446,7 @@ class PhpDumper extends Dumper * * @return string */ - private function addServiceMethodCalls($id, $definition, $variableName = 'instance') + private function addServiceMethodCalls($id, Definition $definition, $variableName = 'instance') { $calls = ''; foreach ($definition->getMethodCalls() as $call) { @@ -461,7 +461,7 @@ class PhpDumper extends Dumper return $calls; } - private function addServiceProperties($id, $definition, $variableName = 'instance') + private function addServiceProperties($id, Definition $definition, $variableName = 'instance') { $code = ''; foreach ($definition->getProperties() as $name => $value) { @@ -481,7 +481,7 @@ class PhpDumper extends Dumper * * @throws ServiceCircularReferenceException when the container contains a circular reference */ - private function addServiceInlinedDefinitionsSetup($id, $definition) + private function addServiceInlinedDefinitionsSetup($id, Definition $definition) { $this->referenceVariables[$id] = new Variable('instance'); @@ -525,7 +525,7 @@ class PhpDumper extends Dumper * * @return string */ - private function addServiceConfigurator($id, $definition, $variableName = 'instance') + private function addServiceConfigurator($id, Definition $definition, $variableName = 'instance') { if (!$callable = $definition->getConfigurator()) { return ''; @@ -561,7 +561,7 @@ class PhpDumper extends Dumper * * @return string */ - private function addService($id, $definition) + private function addService($id, Definition $definition) { $this->definitionVariables = new \SplObjectStorage(); $this->referenceVariables = array(); @@ -1063,7 +1063,7 @@ EOF; * * @throws InvalidArgumentException */ - private function exportParameters($parameters, $path = '', $indent = 12) + private function exportParameters(array $parameters, $path = '', $indent = 12) { $php = array(); foreach ($parameters as $key => $value) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php index 68ce5005d0..bd51e82d83 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php @@ -271,7 +271,7 @@ class XmlDumper extends Dumper * @param \DOMElement $parent * @param string $keyAttribute */ - private function convertParameters($parameters, $type, \DOMElement $parent, $keyAttribute = 'key') + private function convertParameters(array $parameters, $type, \DOMElement $parent, $keyAttribute = 'key') { $withKeys = array_keys($parameters) !== range(0, count($parameters) - 1); foreach ($parameters as $key => $value) { @@ -317,7 +317,7 @@ class XmlDumper extends Dumper * * @return array */ - private function escape($arguments) + private function escape(array $arguments) { $args = array(); foreach ($arguments as $k => $v) { diff --git a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php index 24ec562a40..1a3efdd395 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php @@ -307,7 +307,7 @@ class YamlDumper extends Dumper * * @return array */ - private function prepareParameters($parameters, $escape = true) + private function prepareParameters(array $parameters, $escape = true) { $filtered = array(); foreach ($parameters as $key => $value) { @@ -330,7 +330,7 @@ class YamlDumper extends Dumper * * @return array */ - private function escape($arguments) + private function escape(array $arguments) { $args = array(); foreach ($arguments as $k => $v) { diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 98e78efce8..7679a709b8 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -452,9 +452,9 @@ class YamlFileLoader extends FileLoader { if (is_array($value)) { $value = array_map(array($this, 'resolveServices'), $value); - } elseif (is_string($value) && 0 === strpos($value, '@=')) { + } elseif (is_string($value) && 0 === strpos($value, '@=')) { return new Expression(substr($value, 2)); - } elseif (is_string($value) && 0 === strpos($value, '@')) { + } elseif (is_string($value) && 0 === strpos($value, '@')) { if (0 === strpos($value, '@@')) { $value = substr($value, 1); $invalidBehavior = null; diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index eb7890b506..8a76789277 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -708,14 +708,12 @@ class ContainerBuilderTest extends \PHPUnit_Framework_TestCase $container->set('a', new \stdClass()); } - /** - * @expectedException \BadMethodCallException - */ public function testThrowsExceptionWhenAddServiceOnAFrozenContainer() { $container = new ContainerBuilder(); $container->compile(); - $container->set('a', new \stdClass()); + $container->set('a', $foo = new \stdClass()); + $this->assertSame($foo, $container->get('a')); } public function testNoExceptionWhenSetSyntheticServiceOnAFrozenContainer() diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php index 837bceeb2a..09fabe0f62 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerTest.php @@ -211,7 +211,7 @@ class ContainerTest extends \PHPUnit_Framework_TestCase /** * @expectedException \Symfony\Component\DependencyInjection\Exception\RuntimeException - * @expectedExcepionMessage You have requested a synthetic service ("request"). The DIC does not know how to construct this service. + * @expectedExceptionMessage You have requested a synthetic service ("request"). The DIC does not know how to construct this service. */ public function testGetSyntheticServiceAlwaysThrows() { diff --git a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php index 22d655f60e..8434f8932b 100644 --- a/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php +++ b/src/Symfony/Component/Form/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformer.php @@ -133,7 +133,10 @@ class DateTimeToLocalizedStringTransformer extends BaseDateTimeTransformer } // read timestamp into DateTime object - the formatter delivers a timestamp - $dateTime = new \DateTime(sprintf('@%s', $timestamp), new \DateTimeZone($this->outputTimezone)); + $dateTime = new \DateTime(sprintf('@%s', $timestamp)); + // set timezone separately, as it would be ignored if set via the constructor, + // see http://php.net/manual/en/datetime.construct.php + $dateTime->setTimezone(new \DateTimeZone($this->outputTimezone)); } catch (\Exception $e) { throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e); } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php index ff8d0b4fde..86894f1adc 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FormType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FormType.php @@ -142,6 +142,13 @@ class FormType extends BaseType }; }; + // Wrap "post_max_size_message" in a closure to translate it lazily + $uploadMaxSizeMessage = function (Options $options) { + return function () use ($options) { + return $options['post_max_size_message']; + }; + }; + // For any form that is not represented by a single HTML control, // errors should bubble up by default $errorBubbling = function (Options $options) { @@ -172,9 +179,11 @@ class FormType extends BaseType 'action' => '', 'attr' => array(), 'post_max_size_message' => 'The uploaded file was too large. Please try to upload a smaller file.', + 'upload_max_size_message' => $uploadMaxSizeMessage, // internal )); $resolver->setAllowedTypes('label_attr', 'array'); + $resolver->setAllowedTypes('upload_max_size_message', array('callable')); } /** diff --git a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php index d1e5eece7b..004f4778f9 100644 --- a/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php +++ b/src/Symfony/Component/Form/Extension/HttpFoundation/HttpFoundationRequestHandler.php @@ -78,7 +78,7 @@ class HttpFoundationRequestHandler implements RequestHandlerInterface $form->submit(null, false); $form->addError(new FormError( - $form->getConfig()->getOption('post_max_size_message'), + call_user_func($form->getConfig()->getOption('upload_max_size_message')), null, array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()) )); diff --git a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php index d89a326f77..6f1f632118 100644 --- a/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php +++ b/src/Symfony/Component/Form/Extension/Validator/Type/UploadValidatorExtension.php @@ -42,9 +42,10 @@ class UploadValidatorExtension extends AbstractTypeExtension { $translator = $this->translator; $translationDomain = $this->translationDomain; - - $resolver->setNormalizer('post_max_size_message', function (Options $options, $errorMessage) use ($translator, $translationDomain) { - return $translator->trans($errorMessage, array(), $translationDomain); + $resolver->setNormalizer('upload_max_size_message', function (Options $options, $message) use ($translator, $translationDomain) { + return function () use ($translator, $translationDomain, $message) { + return $translator->trans(call_user_func($message), array(), $translationDomain); + }; }); } diff --git a/src/Symfony/Component/Form/NativeRequestHandler.php b/src/Symfony/Component/Form/NativeRequestHandler.php index 5541e96ad5..3607feb99c 100644 --- a/src/Symfony/Component/Form/NativeRequestHandler.php +++ b/src/Symfony/Component/Form/NativeRequestHandler.php @@ -86,7 +86,7 @@ class NativeRequestHandler implements RequestHandlerInterface $form->submit(null, false); $form->addError(new FormError( - $form->getConfig()->getOption('post_max_size_message'), + call_user_func($form->getConfig()->getOption('upload_max_size_message')), null, array('{{ max }}' => $this->serverParams->getNormalizedIniPostMaxSize()) )); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php index 1ac2dc5ea9..b54c0d50ef 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/DataTransformer/DateTimeToLocalizedStringTransformerTest.php @@ -134,6 +134,23 @@ class DateTimeToLocalizedStringTransformerTest extends DateTimeTestCase $this->assertEquals($dateTime->format('d.m.Y, H:i'), $transformer->transform($input)); } + public function testReverseTransformWithNoConstructorParameters() + { + $tz = date_default_timezone_get(); + date_default_timezone_set('Europe/Rome'); + + $transformer = new DateTimeToLocalizedStringTransformer(); + + $dateTime = new \DateTime('2010-02-03 04:05'); + + $this->assertEquals( + $dateTime->format('c'), + $transformer->reverseTransform('03.02.2010, 04:05')->format('c') + ); + + date_default_timezone_set($tz); + } + public function testTransformWithDifferentPatterns() { $transformer = new DateTimeToLocalizedStringTransformer('UTC', 'UTC', \IntlDateFormatter::FULL, \IntlDateFormatter::FULL, \IntlDateFormatter::GREGORIAN, 'MM*yyyy*dd HH|mm|ss'); diff --git a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php index 95a11a78c7..dbe13a2745 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Validator/Type/UploadValidatorExtensionTest.php @@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Tests\Extension\Validator\Type; use Symfony\Component\Form\Extension\Validator\Type\UploadValidatorExtension; use Symfony\Component\OptionsResolver\OptionsResolver; +use Symfony\Component\OptionsResolver\Options; class UploadValidatorExtensionTest extends TypeTestCase { @@ -29,10 +30,15 @@ class UploadValidatorExtensionTest extends TypeTestCase $resolver = new OptionsResolver(); $resolver->setDefault('post_max_size_message', 'old max {{ max }}!'); + $resolver->setDefault('upload_max_size_message', function (Options $options, $message) { + return function () use ($options) { + return $options['post_max_size_message']; + }; + }); $extension->configureOptions($resolver); $options = $resolver->resolve(); - $this->assertEquals('translated max {{ max }}!', $options['post_max_size_message']); + $this->assertEquals('translated max {{ max }}!', call_user_func($options['upload_max_size_message'])); } } diff --git a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php index ba4d6af5a8..0641486b7a 100644 --- a/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php +++ b/src/Symfony/Component/Security/Core/Authorization/Voter/Voter.php @@ -58,6 +58,7 @@ abstract class Voter implements VoterInterface /** * Perform a single access check operation on a given attribute, subject and token. + * It is safe to assume that $attribute and $subject already passed the "supports()" method check. * * @param string $attribute * @param mixed $subject diff --git a/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php b/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php index 793007e7f5..95e76ee279 100644 --- a/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php +++ b/src/Symfony/Component/Security/Core/Role/RoleHierarchy.php @@ -65,9 +65,17 @@ class RoleHierarchy implements RoleHierarchyInterface } $visited[] = $role; - $this->map[$main] = array_unique(array_merge($this->map[$main], $this->hierarchy[$role])); - $additionalRoles = array_merge($additionalRoles, array_diff($this->hierarchy[$role], $visited)); + + foreach ($this->hierarchy[$role] as $roleToAdd) { + $this->map[$main][] = $roleToAdd; + } + + foreach (array_diff($this->hierarchy[$role], $visited) as $additionalRole) { + $additionalRoles[] = $additionalRole; + } } + + $this->map[$main] = array_unique($this->map[$main]); } } }