Merge branch '4.1' into 4.2

* 4.1:
  Bump phpunit bridge cache id
  [appveyor] fix create-project phpunit
  Fix HttpKernel Debug requirement
  Fix heredoc
  use final annotation to allow mocking the class
  synchronise the form builder docblock
  Grammar fix in exception message
  fix tests
  forward the parse error to the calling code
  [Debug][DebugClassLoader] Match more cases for final, deprecated and internal classes / methods extends
  ensure compatibility with older PHPUnit mocks
  [Security] Do not mix usage of password_*() functions and sodium_*() ones
This commit is contained in:
Nicolas Grekas 2019-01-24 22:39:51 +01:00
commit 61bf16c71d
23 changed files with 149 additions and 77 deletions

View File

@ -1,7 +1,7 @@
#!/usr/bin/env php
<?php
// Cache-Id: https://github.com/symfony/phpunit-bridge/commit/2155067dfc73e0e77dbc26f236af17e4df552de5
// Cache-Id: https://github.com/symfony/symfony/commit/aad0c58
if (!file_exists(__DIR__.'/vendor/symfony/phpunit-bridge/bin/simple-phpunit')) {
echo "Unable to find the `simple-phpunit` script in `vendor/symfony/phpunit-bridge/bin/`.\nPlease run `composer update` before running this command.\n";

View File

@ -45,14 +45,14 @@ class MergeDoctrineCollectionListenerTest extends TestCase
$this->form = null;
}
protected function getBuilder($name = 'name')
protected function getBuilder()
{
return new FormBuilder($name, null, $this->dispatcher, $this->factory);
return new FormBuilder('name', null, $this->dispatcher, $this->factory);
}
protected function getForm($name = 'name')
protected function getForm()
{
return $this->getBuilder($name)
return $this->getBuilder()
->setData($this->collection)
->addEventSubscriber(new MergeDoctrineCollectionListener())
->getForm();

View File

@ -90,7 +90,9 @@ if (!file_exists("$PHPUNIT_DIR/phpunit-$PHPUNIT_VERSION/phpunit") || md5_file(__
@mkdir($PHPUNIT_DIR, 0777, true);
chdir($PHPUNIT_DIR);
if (file_exists("phpunit-$PHPUNIT_VERSION")) {
passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? '(del /S /F /Q %s & rmdir %1$s) >nul': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION"));
passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s > NUL': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION.old"));
rename("phpunit-$PHPUNIT_VERSION", "phpunit-$PHPUNIT_VERSION.old");
passthru(sprintf('\\' === DIRECTORY_SEPARATOR ? 'rmdir /S /Q %s': 'rm -rf %s', "phpunit-$PHPUNIT_VERSION.old"));
}
passthru("$COMPOSER create-project --no-install --prefer-dist --no-scripts --no-plugins --no-progress --ansi phpunit/phpunit phpunit-$PHPUNIT_VERSION \"$PHPUNIT_VERSION.*\"");
chdir("phpunit-$PHPUNIT_VERSION");

View File

@ -15,6 +15,7 @@ use Fig\Link\Link;
use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\JsonResponse;
@ -487,7 +488,8 @@ abstract class ControllerTraitTest extends TestCase
public function testCreateForm()
{
$form = $this->getMockBuilder('Symfony\Component\Form\FormInterface')->getMock();
$config = $this->getMockBuilder('Symfony\Component\Form\FormConfigInterface')->getMock();
$form = new Form($config);
$formFactory = $this->getMockBuilder('Symfony\Component\Form\FormFactoryInterface')->getMock();
$formFactory->expects($this->once())->method('create')->willReturn($form);

View File

@ -232,8 +232,8 @@ class DebugClassLoader
// Detect annotations on the class
if (false !== $doc = $refl->getDocComment()) {
foreach (['final', 'deprecated', 'internal'] as $annotation) {
if (false !== \strpos($doc, $annotation) && preg_match('#\n \* @'.$annotation.'(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) {
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
self::${$annotation}[$class] = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
}
}
}
@ -325,7 +325,7 @@ class DebugClassLoader
foreach (['final', 'internal'] as $annotation) {
if (false !== \strpos($doc, $annotation) && preg_match('#\n\s+\* @'.$annotation.'(?:( .+?)\.?)?\r?\n\s+\*(?: @|/$)#s', $doc, $notice)) {
$message = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : '';
$message = isset($notice[1]) ? preg_replace('#\.?\r?\n( \*)? *(?= |\r?\n|$)#', '', $notice[1]) : '';
self::${$annotation.'Methods'}[$class][$method->name] = [$class, $message];
$finalOrInternal = true;
}

View File

@ -198,24 +198,31 @@ class DebugClassLoaderTest extends TestCase
public function testExtendedFinalClass()
{
set_error_handler(function () { return false; });
$e = error_reporting(0);
trigger_error('', E_USER_NOTICE);
$deprecations = [];
set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; });
$e = error_reporting(E_USER_DEPRECATED);
class_exists('Test\\'.__NAMESPACE__.'\\ExtendsFinalClass', true);
require __DIR__.'/Fixtures/FinalClasses.php';
$i = 1;
while(class_exists($finalClass = __NAMESPACE__.'\\Fixtures\\FinalClass'.$i++, false)) {
spl_autoload_call($finalClass);
class_exists('Test\\'.__NAMESPACE__.'\\Extends'.substr($finalClass, strrpos($finalClass, '\\') + 1), true);
}
error_reporting($e);
restore_error_handler();
$lastError = error_get_last();
unset($lastError['file'], $lastError['line']);
$xError = [
'type' => E_USER_DEPRECATED,
'message' => 'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass".',
];
$this->assertSame($xError, $lastError);
$this->assertSame([
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass1" class is considered final since version 3.3. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass1".',
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass2" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass2".',
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass3" class is considered final comment with @@@ and ***. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass3".',
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass4" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass4".',
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass5" class is considered final multiline comment. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass5".',
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass6" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass6".',
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass7" class is considered final another multiline comment... It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass7".',
'The "Symfony\Component\Debug\Tests\Fixtures\FinalClass8" class is considered final. It may change without further notice as of its next major version. You should not extend it from "Test\Symfony\Component\Debug\Tests\ExtendsFinalClass8".',
], $deprecations);
}
public function testExtendedFinalMethod()
@ -343,8 +350,9 @@ class ClassLoader
eval('namespace Test\\'.__NAMESPACE__.'; class NonDeprecatedInterfaceClass implements \\'.__NAMESPACE__.'\Fixtures\NonDeprecatedInterface {}');
} elseif ('Test\\'.__NAMESPACE__.'\Float' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class Float {}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}');
} elseif (0 === strpos($class, 'Test\\'.__NAMESPACE__.'\ExtendsFinalClass')) {
$classShortName = substr($class, strrpos($class, '\\') + 1);
eval('namespace Test\\'.__NAMESPACE__.'; class '.$classShortName.' extends \\'.__NAMESPACE__.'\Fixtures\\'.substr($classShortName, 7).' {}');
} elseif ('Test\\'.__NAMESPACE__.'\ExtendsAnnotatedClass' === $class) {
eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsAnnotatedClass extends \\'.__NAMESPACE__.'\Fixtures\AnnotatedClass {
public function deprecatedMethod() { }

View File

@ -1,10 +0,0 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @final
*/
class FinalClass
{
}

View File

@ -0,0 +1,84 @@
<?php
namespace Symfony\Component\Debug\Tests\Fixtures;
/**
* @final since version 3.3.
*/
class FinalClass1
{
// simple comment
}
/**
* @final
*/
class FinalClass2
{
// no comment
}
/**
* @final comment with @@@ and ***
*
* @author John Doe
*/
class FinalClass3
{
// with comment and a tag after
}
/**
* @final
* @author John Doe
*/
class FinalClass4
{
// without comment and a tag after
}
/**
* @author John Doe
*
*
* @final multiline
* comment
*/
class FinalClass5
{
// with comment and a tag before
}
/**
* @author John Doe
*
* @final
*/
class FinalClass6
{
// without comment and a tag before
}
/**
* @author John Doe
*
* @final another
*
* multiline comment...
*
* @return string
*/
class FinalClass7
{
// with comment and a tag before and after
}
/**
* @author John Doe
* @final
* @return string
*/
class FinalClass8
{
// without comment and a tag before and after
}

View File

@ -395,7 +395,7 @@ class XmlFileLoader extends FileLoader
try {
$dom = XmlUtils::loadFile($file, [$this, 'validateSchema']);
} catch (\InvalidArgumentException $e) {
throw new InvalidArgumentException(sprintf('Unable to parse file "%s".', $file), $e->getCode(), $e);
throw new InvalidArgumentException(sprintf('Unable to parse file "%s": %s', $file, $e->getMessage()), $e->getCode(), $e);
}
$this->validateExtensions($dom, $file);

View File

@ -630,7 +630,7 @@ class YamlFileLoader extends FileLoader
try {
$configuration = $this->yamlParser->parseFile($file, Yaml::PARSE_CONSTANT | Yaml::PARSE_CUSTOM_TAGS);
} 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: %s', $file, $e->getMessage()), 0, $e);
}
return $this->validate($configuration, $file);

View File

@ -68,7 +68,7 @@ class XmlFileLoaderTest extends TestCase
$this->fail('->parseFileToDOM() throws an InvalidArgumentException if the loaded file is not a valid XML file');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException', $e, '->parseFileToDOM() throws an InvalidArgumentException if the loaded file is not a valid XML file');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s".$#', 'parameters.ini'), $e->getMessage(), '->parseFileToDOM() throws an InvalidArgumentException if the loaded file is not a valid XML file');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s": .+.$#', 'parameters.ini'), $e->getMessage(), '->parseFileToDOM() throws an InvalidArgumentException if the loaded file is not a valid XML file');
$e = $e->getPrevious();
$this->assertInstanceOf('InvalidArgumentException', $e, '->parseFileToDOM() throws an InvalidArgumentException if the loaded file is not a valid XML file');
@ -82,7 +82,7 @@ class XmlFileLoaderTest extends TestCase
$this->fail('->parseFileToDOM() throws an InvalidArgumentException if the loaded file does not validate the XSD');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException', $e, '->parseFileToDOM() throws an InvalidArgumentException if the loaded file does not validate the XSD');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s".$#', 'nonvalid.xml'), $e->getMessage(), '->parseFileToDOM() throws an InvalidArgumentException if the loaded file is not a valid XML file');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s": .+.$#', 'nonvalid.xml'), $e->getMessage(), '->parseFileToDOM() throws an InvalidArgumentException if the loaded file is not a valid XML file');
$e = $e->getPrevious();
$this->assertInstanceOf('InvalidArgumentException', $e, '->parseFileToDOM() throws an InvalidArgumentException if the loaded file does not validate the XSD');
@ -428,7 +428,7 @@ class XmlFileLoaderTest extends TestCase
$this->fail('->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s".$#', 'services3.xml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s": .+.$#', 'services3.xml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
$e = $e->getPrevious();
$this->assertInstanceOf('InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
@ -465,7 +465,7 @@ class XmlFileLoaderTest extends TestCase
$this->fail('->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s".$#', 'services7.xml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s": .+.$#', 'services7.xml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
$e = $e->getPrevious();
$this->assertInstanceOf('InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the configuration does not validate the XSD');
@ -516,7 +516,7 @@ class XmlFileLoaderTest extends TestCase
$this->fail('->load() throws an InvalidArgumentException if the configuration contains a document type');
} catch (\Exception $e) {
$this->assertInstanceOf('Symfony\\Component\\DependencyInjection\\Exception\\InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the configuration contains a document type');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s".$#', 'withdoctype.xml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration contains a document type');
$this->assertRegExp(sprintf('#^Unable to parse file ".+%s": .+.$#', 'withdoctype.xml'), $e->getMessage(), '->load() throws an InvalidArgumentException if the configuration contains a document type');
$e = $e->getPrevious();
$this->assertInstanceOf('InvalidArgumentException', $e, '->load() throws an InvalidArgumentException if the configuration contains a document type');

View File

@ -212,7 +212,7 @@ final class Dotenv
}
if (' ' === $this->data[$this->cursor] || "\t" === $this->data[$this->cursor]) {
throw $this->createFormatException('Whitespace are not supported after the variable name');
throw $this->createFormatException('Whitespace characters are not supported after the variable name');
}
if ('=' !== $this->data[$this->cursor]) {

View File

@ -36,7 +36,7 @@ class DotenvTest extends TestCase
{
$tests = [
['FOO=BAR BAZ', "A value containing spaces must be surrounded by quotes in \".env\" at line 1.\n...FOO=BAR BAZ...\n ^ line 1 offset 11"],
['FOO BAR=BAR', "Whitespace are not supported after the variable name in \".env\" at line 1.\n...FOO BAR=BAR...\n ^ line 1 offset 3"],
['FOO BAR=BAR', "Whitespace characters are not supported after the variable name in \".env\" at line 1.\n...FOO BAR=BAR...\n ^ line 1 offset 3"],
['FOO', "Missing = in the environment variable declaration in \".env\" at line 1.\n...FOO...\n ^ line 1 offset 3"],
['FOO="foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO=\"foo...\n ^ line 1 offset 8"],
['FOO=\'foo', "Missing quote to end the value in \".env\" at line 1.\n...FOO='foo...\n ^ line 1 offset 8"],

View File

@ -205,7 +205,7 @@ class ResolvedFormType implements ResolvedFormTypeInterface
* Override this method if you want to customize the builder class.
*
* @param string $name The name of the builder
* @param string $dataClass The data class
* @param string|null $dataClass The data class
* @param FormFactoryInterface $factory The current form factory
* @param array $options The builder options
*

View File

@ -32,7 +32,7 @@ abstract class TypeTestCase extends FormIntegrationTestCase
parent::setUp();
$this->dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
$this->builder = new FormBuilder(null, null, $this->dispatcher, $this->factory);
$this->builder = new FormBuilder('', null, $this->dispatcher, $this->factory);
}
protected function tearDown()

View File

@ -55,7 +55,7 @@ abstract class AbstractFormTest extends TestCase
/**
* @param string $name
* @param EventDispatcherInterface $dispatcher
* @param string $dataClass
* @param string|null $dataClass
* @param array $options
*
* @return FormBuilder

View File

@ -41,7 +41,7 @@ class ButtonTest extends TestCase
$button->submit('');
$button->setParent($this->getFormBuilder('form')->getForm());
$button->setParent($this->getFormBuilder()->getForm());
}
/**
@ -49,7 +49,7 @@ class ButtonTest extends TestCase
*/
public function testDisabledIfParentIsDisabled($parentDisabled, $buttonDisabled, $result)
{
$form = $this->getFormBuilder('form')
$form = $this->getFormBuilder()
->setDisabled($parentDisabled)
->getForm()
;
@ -80,8 +80,8 @@ class ButtonTest extends TestCase
return new ButtonBuilder($name);
}
private function getFormBuilder($name)
private function getFormBuilder()
{
return new FormBuilder($name, null, $this->dispatcher, $this->factory);
return new FormBuilder('form', null, $this->dispatcher, $this->factory);
}
}

View File

@ -28,7 +28,7 @@ class CsrfValidationListenerTest extends TestCase
$this->dispatcher = $this->getMockBuilder('Symfony\Component\EventDispatcher\EventDispatcherInterface')->getMock();
$this->factory = $this->getMockBuilder('Symfony\Component\Form\FormFactoryInterface')->getMock();
$this->tokenManager = $this->getMockBuilder('Symfony\Component\Security\Csrf\CsrfTokenManagerInterface')->getMock();
$this->form = $this->getBuilder('post')
$this->form = $this->getBuilder()
->setDataMapper($this->getDataMapper())
->getForm();
}
@ -41,14 +41,9 @@ class CsrfValidationListenerTest extends TestCase
$this->form = null;
}
protected function getBuilder($name = 'name')
protected function getBuilder()
{
return new FormBuilder($name, null, $this->dispatcher, $this->factory, ['compound' => true]);
}
protected function getForm($name = 'name')
{
return $this->getBuilder($name)->getForm();
return new FormBuilder('post', null, $this->dispatcher, $this->factory, ['compound' => true]);
}
protected function getDataMapper()

View File

@ -12,7 +12,6 @@
namespace Symfony\Component\Form\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormTypeExtensionInterface;
use Symfony\Component\Form\FormTypeInterface;
use Symfony\Component\Form\ResolvedFormType;
@ -380,15 +379,4 @@ class ResolvedFormTypeTest extends TestCase
{
return $this->getMockBuilder('Symfony\Component\Form\FormFactoryInterface')->getMock();
}
/**
* @param string $name
* @param array $options
*
* @return FormBuilder
*/
protected function getBuilder($name = 'name', array $options = [])
{
return new FormBuilder($name, null, $this->dispatcher, $this->factory, $options);
}
}

View File

@ -19,6 +19,7 @@ use Symfony\Component\HttpKernel\Event\PostResponseEvent;
use Symfony\Component\HttpKernel\EventListener\ProfilerListener;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\HttpKernel\Profiler\Profile;
class ProfilerListenerTest extends TestCase
{
@ -27,9 +28,7 @@ class ProfilerListenerTest extends TestCase
*/
public function testKernelTerminate()
{
$profile = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profile')
->disableOriginalConstructor()
->getMock();
$profile = new Profile('token');
$profiler = $this->getMockBuilder('Symfony\Component\HttpKernel\Profiler\Profiler')
->disableOriginalConstructor()

View File

@ -81,7 +81,9 @@ class Argon2iPasswordEncoder extends BasePasswordEncoder implements SelfSaltingE
*/
public function isPasswordValid($encoded, $raw, $salt)
{
if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I')) {
// If $encoded was created via "sodium_crypto_pwhash_str()", the hashing algorithm may be "argon2id" instead of "argon2i".
// In this case, "password_verify()" cannot be used.
if (\PHP_VERSION_ID >= 70200 && \defined('PASSWORD_ARGON2I') && (false === strpos($encoded, '$argon2id$'))) {
return !$this->isPasswordTooLong($raw) && password_verify($raw, $encoded);
}
if (\function_exists('sodium_crypto_pwhash_str_verify')) {

View File

@ -17,8 +17,10 @@ use Symfony\Component\Security\Core\User\UserInterface;
/**
* Helper class for commonly-needed security tasks.
*
* @final
*/
final class Security
class Security
{
const ACCESS_DENIED_ERROR = '_security.403_error';
const AUTHENTICATION_ERROR = '_security.last_error';

View File

@ -1,7 +1,7 @@
This template is used for translation message extraction tests
<?php echo $view['translator']->trans('single-quoted key'); ?>
<?php echo $view['translator']->trans('double-quoted key'); ?>
<?php echo $view['translator']->trans(<<<'EOF'
<?php echo $view['translator']->trans(<<<EOF
heredoc key
EOF
); ?>