Merge remote-tracking branch 'origin/2.7' into 2.8

Conflicts:
	src/Symfony/Bundle/FrameworkBundle/Command/TranslationDebugCommand.php
	src/Symfony/Component/DependencyInjection/Tests/Compiler/ResolveDefinitionTemplatesPassTest.php
This commit is contained in:
Abdellatif Ait boudad 2015-07-01 14:16:54 +00:00
commit 05f3c3db7b
110 changed files with 1846 additions and 798 deletions

View File

@ -50,30 +50,7 @@ class DbalLogger implements SQLLogger
} }
if (is_array($params)) { if (is_array($params)) {
foreach ($params as $index => $param) { $params = $this->normalizeParams($params);
if (!is_string($params[$index])) {
continue;
}
// non utf-8 strings break json encoding
if (!preg_match('//u', $params[$index])) {
$params[$index] = self::BINARY_DATA_VALUE;
continue;
}
// detect if the too long string must be shorten
if (function_exists('mb_strlen')) {
if (self::MAX_STRING_LENGTH < mb_strlen($params[$index], 'UTF-8')) {
$params[$index] = mb_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]';
continue;
}
} else {
if (self::MAX_STRING_LENGTH < strlen($params[$index])) {
$params[$index] = substr($params[$index], 0, self::MAX_STRING_LENGTH - 6).' [...]';
continue;
}
}
}
} }
if (null !== $this->logger) { if (null !== $this->logger) {
@ -101,4 +78,40 @@ class DbalLogger implements SQLLogger
{ {
$this->logger->debug($message, $params); $this->logger->debug($message, $params);
} }
private function normalizeParams(array $params)
{
foreach ($params as $index => $param) {
// normalize recursively
if (is_array($param)) {
$params[$index] = $this->normalizeParams($param);
continue;
}
if (!is_string($params[$index])) {
continue;
}
// non utf-8 strings break json encoding
if (!preg_match('//u', $params[$index])) {
$params[$index] = self::BINARY_DATA_VALUE;
continue;
}
// detect if the too long string must be shorten
if (function_exists('mb_strlen')) {
if (self::MAX_STRING_LENGTH < mb_strlen($params[$index], 'UTF-8')) {
$params[$index] = mb_substr($params[$index], 0, self::MAX_STRING_LENGTH - 6, 'UTF-8').' [...]';
continue;
}
} else {
if (self::MAX_STRING_LENGTH < strlen($params[$index])) {
$params[$index] = substr($params[$index], 0, self::MAX_STRING_LENGTH - 6).' [...]';
continue;
}
}
}
return $params;
}
} }

View File

@ -73,6 +73,37 @@ class DbalLoggerTest extends \PHPUnit_Framework_TestCase
)); ));
} }
public function testLogNonUtf8Array()
{
$logger = $this->getMock('Psr\\Log\\LoggerInterface');
$dbalLogger = $this
->getMockBuilder('Symfony\\Bridge\\Doctrine\\Logger\\DbalLogger')
->setConstructorArgs(array($logger, null))
->setMethods(array('log'))
->getMock()
;
$dbalLogger
->expects($this->once())
->method('log')
->with('SQL', array(
'utf8' => 'foo',
array(
'nonutf8' => DbalLogger::BINARY_DATA_VALUE,
)
)
)
;
$dbalLogger->startQuery('SQL', array(
'utf8' => 'foo',
array(
'nonutf8' => "\x7F\xFF",
)
));
}
public function testLogLongString() public function testLogLongString()
{ {
$logger = $this->getMock('Psr\\Log\\LoggerInterface'); $logger = $this->getMock('Psr\\Log\\LoggerInterface');

View File

@ -82,7 +82,7 @@ class UniqueEntityValidator extends ConstraintValidator
$criteria = array(); $criteria = array();
foreach ($fields as $fieldName) { foreach ($fields as $fieldName) {
if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) { if (!$class->hasField($fieldName) && !$class->hasAssociation($fieldName)) {
throw new ConstraintDefinitionException(sprintf("The field '%s' is not mapped by Doctrine, so it cannot be validated for uniqueness.", $fieldName)); throw new ConstraintDefinitionException(sprintf('The field "%s" is not mapped by Doctrine, so it cannot be validated for uniqueness.', $fieldName));
} }
$criteria[$fieldName] = $class->reflFields[$fieldName]->getValue($entity); $criteria[$fieldName] = $class->reflFields[$fieldName]->getValue($entity);

View File

@ -21,10 +21,10 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent;
*/ */
class WebProcessor extends BaseWebProcessor class WebProcessor extends BaseWebProcessor
{ {
public function __construct() public function __construct(array $extraFields = null)
{ {
// Pass an empty array as the default null value would access $_SERVER // Pass an empty array as the default null value would access $_SERVER
parent::__construct(array()); parent::__construct(array(), $extraFields);
} }
public function onKernelRequest(GetResponseEvent $event) public function onKernelRequest(GetResponseEvent $event)

View File

@ -18,6 +18,42 @@ use Symfony\Component\HttpFoundation\Request;
class WebProcessorTest extends \PHPUnit_Framework_TestCase class WebProcessorTest extends \PHPUnit_Framework_TestCase
{ {
public function testUsesRequestServerData() public function testUsesRequestServerData()
{
list($event, $server) = $this->createRequestEvent();
$processor = new WebProcessor();
$processor->onKernelRequest($event);
$record = $processor($this->getRecord());
$this->assertCount(5, $record['extra']);
$this->assertEquals($server['REQUEST_URI'], $record['extra']['url']);
$this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']);
$this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']);
$this->assertEquals($server['SERVER_NAME'], $record['extra']['server']);
$this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']);
}
public function testCanBeConstructedWithExtraFields()
{
if (!$this->isExtraFieldsSupported()) {
$this->markTestSkipped('WebProcessor of the installed Monolog version does not support $extraFields parameter');
}
list($event, $server) = $this->createRequestEvent();
$processor = new WebProcessor(array('url', 'referrer'));
$processor->onKernelRequest($event);
$record = $processor($this->getRecord());
$this->assertCount(2, $record['extra']);
$this->assertEquals($server['REQUEST_URI'], $record['extra']['url']);
$this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']);
}
/**
* @return array
*/
private function createRequestEvent()
{ {
$server = array( $server = array(
'REQUEST_URI' => 'A', 'REQUEST_URI' => 'A',
@ -40,15 +76,7 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase
->method('getRequest') ->method('getRequest')
->will($this->returnValue($request)); ->will($this->returnValue($request));
$processor = new WebProcessor(); return array($event, $server);
$processor->onKernelRequest($event);
$record = $processor($this->getRecord());
$this->assertEquals($server['REQUEST_URI'], $record['extra']['url']);
$this->assertEquals($server['REMOTE_ADDR'], $record['extra']['ip']);
$this->assertEquals($server['REQUEST_METHOD'], $record['extra']['http_method']);
$this->assertEquals($server['SERVER_NAME'], $record['extra']['server']);
$this->assertEquals($server['HTTP_REFERER'], $record['extra']['referrer']);
} }
/** /**
@ -57,7 +85,7 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase
* *
* @return array Record * @return array Record
*/ */
protected function getRecord($level = Logger::WARNING, $message = 'test') private function getRecord($level = Logger::WARNING, $message = 'test')
{ {
return array( return array(
'message' => $message, 'message' => $message,
@ -69,4 +97,17 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase
'extra' => array(), 'extra' => array(),
); );
} }
private function isExtraFieldsSupported()
{
$monologWebProcessorClass = new \ReflectionClass('Monolog\Processor\WebProcessor');
foreach ($monologWebProcessorClass->getConstructor()->getParameters() as $parameter) {
if ('extraFields' === $parameter->getName()) {
return true;
}
}
return false;
}
} }

View File

@ -6,6 +6,7 @@ Provides utilities for PHPUnit, especially user deprecation notices management.
It comes with the following features: It comes with the following features:
* disable the garbage collector; * disable the garbage collector;
* enforce a consistent `C` locale;
* auto-register `class_exists` to load Doctrine annotations; * auto-register `class_exists` to load Doctrine annotations;
* print a user deprecation notices summary at the end of the test suite. * print a user deprecation notices summary at the end of the test suite.

View File

@ -14,6 +14,9 @@ if (PHP_VERSION_ID >= 50400 && gc_enabled()) {
gc_disable(); gc_disable();
} }
// Enforce a consistent locale
setlocale(LC_ALL, 'C');
if (class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) { if (class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) {
AnnotationRegistry::registerLoader('class_exists'); AnnotationRegistry::registerLoader('class_exists');
} }

View File

@ -98,6 +98,7 @@
{%- for child in form %} {%- for child in form %}
{{- form_widget(child, { {{- form_widget(child, {
parent_label_class: label_attr.class|default(''), parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}} }) -}}
{% endfor -%} {% endfor -%}
</div> </div>
@ -106,6 +107,7 @@
{%- for child in form %} {%- for child in form %}
{{- form_widget(child, { {{- form_widget(child, {
parent_label_class: label_attr.class|default(''), parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}} }) -}}
{% endfor -%} {% endfor -%}
</div> </div>
@ -156,7 +158,7 @@
{%- endblock radio_label %} {%- endblock radio_label %}
{% block checkbox_radio_label %} {% block checkbox_radio_label %}
{# Do no display the label if widget is not defined in order to prevent double label rendering #} {# Do not display the label if widget is not defined in order to prevent double label rendering #}
{% if widget is defined %} {% if widget is defined %}
{% if required %} {% if required %}
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %} {% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %}
@ -169,7 +171,7 @@
{% endif %} {% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}> <label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{- widget|raw -}} {{- widget|raw -}}
{{- label is not sameas(false) ? label|trans({}, translation_domain) -}} {{- label is not sameas(false) ? (translation_domain is sameas(false) ? label : label|trans({}, translation_domain)) -}}
</label> </label>
{% endif %} {% endif %}
{% endblock checkbox_radio_label %} {% endblock checkbox_radio_label %}

View File

@ -46,7 +46,7 @@
<div {{ block('widget_container_attributes') }}> <div {{ block('widget_container_attributes') }}>
{%- for child in form %} {%- for child in form %}
{{- form_widget(child) -}} {{- form_widget(child) -}}
{{- form_label(child) -}} {{- form_label(child, null, {translation_domain: choice_translation_domain}) -}}
{% endfor -%} {% endfor -%}
</div> </div>
{%- endblock choice_widget_expanded -%} {%- endblock choice_widget_expanded -%}
@ -57,7 +57,7 @@
{%- endif -%} {%- endif -%}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}> <select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{%- if placeholder is not none -%} {%- if placeholder is not none -%}
<option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ placeholder|trans({}, translation_domain) }}</option> <option value=""{% if required and value is empty %} selected="selected"{% endif %}>{{ placeholder != '' ? placeholder|trans({}, translation_domain) }}</option>
{%- endif -%} {%- endif -%}
{%- if preferred_choices|length > 0 -%} {%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %} {% set options = preferred_choices %}
@ -225,7 +225,7 @@
{% set label = name|humanize %} {% set label = name|humanize %}
{%- endif -%} {%- endif -%}
{%- endif -%} {%- endif -%}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ label|trans({}, translation_domain) }}</label> <label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>{{ translation_domain is sameas(false) ? label : label|trans({}, translation_domain) }}</label>
{%- endif -%} {%- endif -%}
{%- endblock form_label -%} {%- endblock form_label -%}

View File

@ -106,106 +106,103 @@ EOF
$kernel = $this->getContainer()->get('kernel'); $kernel = $this->getContainer()->get('kernel');
// Define Root Path to App folder // Define Root Path to App folder
$rootPaths = array($kernel->getRootDir()); $transPaths = array($kernel->getRootDir().'/Resources/');
// Override with provided Bundle info // Override with provided Bundle info
if (null !== $input->getArgument('bundle')) { if (null !== $input->getArgument('bundle')) {
try { try {
$rootPaths = array($kernel->getBundle($input->getArgument('bundle'))->getPath()); $bundle = $kernel->getBundle($input->getArgument('bundle'));
$transPaths = array(
$bundle->getPath().'/Resources/',
sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName()),
);
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path // such a bundle does not exist, so treat the argument as path
$rootPaths = array($input->getArgument('bundle')); $transPaths = array($input->getArgument('bundle').'/Resources/');
if (!is_dir($rootPaths[0])) { if (!is_dir($transPaths[0])) {
throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.</error>', $rootPaths[0])); throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
} }
} }
} elseif ($input->getOption('all')) { } elseif ($input->getOption('all')) {
foreach ($kernel->getBundles() as $bundle) { foreach ($kernel->getBundles() as $bundle) {
$rootPaths[] = $bundle->getPath(); $transPaths[] = $bundle->getPath().'/Resources/';
$transPaths[] = sprintf('%s/Resources/%s/', $kernel->getRootDir(), $bundle->getName());
} }
} }
foreach ($rootPaths as $rootPath) { // Extract used messages
// get bundle directory $extractedCatalogue = $this->extractMessages($locale, $transPaths);
$translationsPath = $rootPath.'/Resources/translations';
$output->writeln(sprintf('Translations in <info>%s</info>', $translationsPath)); // Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $transPaths, $loader);
// Extract used messages // Merge defined and extracted messages to get all message ids
$extractedCatalogue = $this->extractMessages($locale, $rootPath); $mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue);
$allMessages = $mergeOperation->getResult()->all($domain);
if (null !== $domain) {
$allMessages = array($domain => $allMessages);
}
// Load defined messages // No defined or extracted messages
$currentCatalogue = $this->loadCurrentMessages($locale, $translationsPath, $loader); if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) {
$outputMessage = sprintf('No defined or extracted messages for locale "%s"', $locale);
// Merge defined and extracted messages to get all message ids
$mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue);
$allMessages = $mergeOperation->getResult()->all($domain);
if (null !== $domain) { if (null !== $domain) {
$allMessages = array($domain => $allMessages); $outputMessage .= sprintf(' and domain "%s"', $domain);
} }
// No defined or extracted messages $output->warning($outputMessage);
if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) {
$outputMessage = sprintf('<info>No defined or extracted messages for locale "%s"</info>', $locale);
if (null !== $domain) { return;
$outputMessage .= sprintf(' <info>and domain "%s"</info>', $domain);
}
$output->writeln($outputMessage);
continue;
}
// Load the fallback catalogues
$fallbackCatalogues = $this->loadFallbackCatalogues($locale, $translationsPath, $loader);
// Display header line
$headers = array('State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale));
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$headers[] = sprintf('Fallback Message Preview (%s)', $fallbackCatalogue->getLocale());
}
// Iterate all message ids and determine their state
$rows = array();
foreach ($allMessages as $domain => $messages) {
foreach (array_keys($messages) as $messageId) {
$value = $currentCatalogue->get($messageId, $domain);
$states = array();
if ($extractedCatalogue->defines($messageId, $domain)) {
if (!$currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_MISSING;
}
} elseif ($currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_UNUSED;
}
if (!in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused')
|| !in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) {
continue;
}
foreach ($fallbackCatalogues as $fallbackCatalogue) {
if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) {
$states[] = self::MESSAGE_EQUALS_FALLBACK;
break;
}
}
$row = array($this->formatStates($states), $domain, $this->formatId($messageId), $this->sanitizeString($value));
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$row[] = $this->sanitizeString($fallbackCatalogue->get($messageId, $domain));
}
$rows[] = $row;
}
}
$output->table($headers, $rows);
} }
// Load the fallback catalogues
$fallbackCatalogues = $this->loadFallbackCatalogues($locale, $transPaths, $loader);
// Display header line
$headers = array('State', 'Domain', 'Id', sprintf('Message Preview (%s)', $locale));
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$headers[] = sprintf('Fallback Message Preview (%s)', $fallbackCatalogue->getLocale());
}
$rows = array();
// Iterate all message ids and determine their state
foreach ($allMessages as $domain => $messages) {
foreach (array_keys($messages) as $messageId) {
$value = $currentCatalogue->get($messageId, $domain);
$states = array();
if ($extractedCatalogue->defines($messageId, $domain)) {
if (!$currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_MISSING;
}
} elseif ($currentCatalogue->defines($messageId, $domain)) {
$states[] = self::MESSAGE_UNUSED;
}
if (!in_array(self::MESSAGE_UNUSED, $states) && true === $input->getOption('only-unused')
|| !in_array(self::MESSAGE_MISSING, $states) && true === $input->getOption('only-missing')) {
continue;
}
foreach ($fallbackCatalogues as $fallbackCatalogue) {
if ($fallbackCatalogue->defines($messageId, $domain) && $value === $fallbackCatalogue->get($messageId, $domain)) {
$states[] = self::MESSAGE_EQUALS_FALLBACK;
break;
}
}
$row = array($this->formatStates($states), $domain, $this->formatId($messageId), $this->sanitizeString($value));
foreach ($fallbackCatalogues as $fallbackCatalogue) {
$row[] = $this->sanitizeString($fallbackCatalogue->get($messageId, $domain));
}
$rows[] = $row;
}
}
$output->table($headers, $rows);
} }
private function formatState($state) private function formatState($state)
@ -257,15 +254,18 @@ EOF
/** /**
* @param string $locale * @param string $locale
* @param string $rootPath * @param array $transPaths
* *
* @return MessageCatalogue * @return MessageCatalogue
*/ */
private function extractMessages($locale, $rootPath) private function extractMessages($locale, $transPaths)
{ {
$extractedCatalogue = new MessageCatalogue($locale); $extractedCatalogue = new MessageCatalogue($locale);
if (is_dir($rootPath.'/Resources/views')) { foreach ($transPaths as $path) {
$this->getContainer()->get('translation.extractor')->extract($rootPath.'/Resources/views', $extractedCatalogue); $path = $path.'views';
if (is_dir($path)) {
$this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue);
}
} }
return $extractedCatalogue; return $extractedCatalogue;
@ -273,16 +273,19 @@ EOF
/** /**
* @param string $locale * @param string $locale
* @param string $translationsPath * @param array $transPaths
* @param TranslationLoader $loader * @param TranslationLoader $loader
* *
* @return MessageCatalogue * @return MessageCatalogue
*/ */
private function loadCurrentMessages($locale, $translationsPath, TranslationLoader $loader) private function loadCurrentMessages($locale, $transPaths, TranslationLoader $loader)
{ {
$currentCatalogue = new MessageCatalogue($locale); $currentCatalogue = new MessageCatalogue($locale);
if (is_dir($translationsPath)) { foreach ($transPaths as $path) {
$loader->loadMessages($translationsPath, $currentCatalogue); $path = $path.'translations';
if (is_dir($path)) {
$loader->loadMessages($path, $currentCatalogue);
}
} }
return $currentCatalogue; return $currentCatalogue;
@ -290,12 +293,12 @@ EOF
/** /**
* @param string $locale * @param string $locale
* @param string $translationsPath * @param array $transPaths
* @param TranslationLoader $loader * @param TranslationLoader $loader
* *
* @return MessageCatalogue[] * @return MessageCatalogue[]
*/ */
private function loadFallbackCatalogues($locale, $translationsPath, TranslationLoader $loader) private function loadFallbackCatalogues($locale, $transPaths, TranslationLoader $loader)
{ {
$fallbackCatalogues = array(); $fallbackCatalogues = array();
$translator = $this->getContainer()->get('translator'); $translator = $this->getContainer()->get('translator');
@ -306,7 +309,12 @@ EOF
} }
$fallbackCatalogue = new MessageCatalogue($fallbackLocale); $fallbackCatalogue = new MessageCatalogue($fallbackLocale);
$loader->loadMessages($translationsPath, $fallbackCatalogue); foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$loader->loadMessages($path, $fallbackCatalogue);
}
}
$fallbackCatalogues[] = $fallbackCatalogue; $fallbackCatalogues[] = $fallbackCatalogue;
} }
} }

View File

@ -69,6 +69,8 @@ EOF
protected function execute(InputInterface $input, OutputInterface $output) protected function execute(InputInterface $input, OutputInterface $output)
{ {
$output = new SymfonyStyle($input, $output); $output = new SymfonyStyle($input, $output);
$kernel = $this->getContainer()->get('kernel');
// check presence of force or dump-message // check presence of force or dump-message
if ($input->getOption('force') !== true && $input->getOption('dump-messages') !== true) { if ($input->getOption('force') !== true && $input->getOption('dump-messages') !== true) {
$output->error('You must choose one of --force or --dump-messages'); $output->error('You must choose one of --force or --dump-messages');
@ -87,30 +89,30 @@ EOF
$kernel = $this->getContainer()->get('kernel'); $kernel = $this->getContainer()->get('kernel');
// Define Root Path to App folder // Define Root Path to App folder
$rootPath = $kernel->getRootDir(); $transPaths = array($kernel->getRootDir().'/Resources/');
$currentName = 'app folder'; $currentName = 'app folder';
// Override with provided Bundle info // Override with provided Bundle info
if (null !== $input->getArgument('bundle')) { if (null !== $input->getArgument('bundle')) {
try { try {
$foundBundle = $kernel->getBundle($input->getArgument('bundle')); $foundBundle = $kernel->getBundle($input->getArgument('bundle'));
$rootPath = $foundBundle->getPath(); $transPaths = array(
$foundBundle->getPath().'/Resources/',
sprintf('%s/Resources/%s/', $kernel->getRootDir(), $foundBundle->getName()),
);
$currentName = $foundBundle->getName(); $currentName = $foundBundle->getName();
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path // such a bundle does not exist, so treat the argument as path
$rootPath = $input->getArgument('bundle'); $transPaths = array($input->getArgument('bundle').'/Resources/');
$currentName = $rootPath; $currentName = $transPaths[0];
if (!is_dir($rootPath)) { if (!is_dir($transPaths[0])) {
throw new \InvalidArgumentException(sprintf('<error>"%s" is neither an enabled bundle nor a directory.</error>', $rootPath)); throw new \InvalidArgumentException(sprintf('<error>"%s" is neither an enabled bundle nor a directory.</error>', $transPaths[0]));
} }
} }
} }
$output->title('Symfony translation update command'); $output->title('Symfony translation update command');
// get bundle directory
$translationsPath = $rootPath.'/Resources/translations';
$output->text(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName)); $output->text(sprintf('Generating "<info>%s</info>" translation files for "<info>%s</info>"', $input->getArgument('locale'), $currentName));
// load any messages from templates // load any messages from templates
@ -118,13 +120,23 @@ EOF
$output->text('Parsing templates'); $output->text('Parsing templates');
$extractor = $this->getContainer()->get('translation.extractor'); $extractor = $this->getContainer()->get('translation.extractor');
$extractor->setPrefix($input->getOption('prefix')); $extractor->setPrefix($input->getOption('prefix'));
$extractor->extract($rootPath.'/Resources/views/', $extractedCatalogue); foreach ($transPaths as $path) {
$path = $path.'views';
if (is_dir($path)) {
$extractor->extract($path, $extractedCatalogue);
}
}
// load any existing messages from the translation files // load any existing messages from the translation files
$currentCatalogue = new MessageCatalogue($input->getArgument('locale')); $currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
$output->text('Loading translation files'); $output->text('Loading translation files');
$loader = $this->getContainer()->get('translation.loader'); $loader = $this->getContainer()->get('translation.loader');
$loader->loadMessages($translationsPath, $currentCatalogue); foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$loader->loadMessages($path, $currentCatalogue);
}
}
// process catalogues // process catalogues
$operation = $input->getOption('clean') $operation = $input->getOption('clean')
@ -150,7 +162,7 @@ EOF
array_map(function ($id) { array_map(function ($id) {
return sprintf('<fg=green>%s</>', $id); return sprintf('<fg=green>%s</>', $id);
}, $newKeys), }, $newKeys),
array_map(function($id) { array_map(function ($id) {
return sprintf('<fg=red>%s</>', $id); return sprintf('<fg=red>%s</>', $id);
}, array_keys($operation->getObsoleteMessages($domain))) }, array_keys($operation->getObsoleteMessages($domain)))
)); ));
@ -168,7 +180,18 @@ EOF
// save the files // save the files
if ($input->getOption('force') === true) { if ($input->getOption('force') === true) {
$output->text('Writing files'); $output->text('Writing files');
$writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $translationsPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale')));
$bundleTransPath = false;
foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$bundleTransPath = $path;
}
}
if ($bundleTransPath) {
$writer->writeTranslations($operation->getResult(), $input->getOption('output-format'), array('path' => $bundleTransPath, 'default_locale' => $this->getContainer()->getParameter('kernel.default_locale')));
}
} }
$output->newLine(); $output->newLine();

View File

@ -119,9 +119,9 @@ class Controller extends ContainerAware
* @param mixed $attributes The attributes * @param mixed $attributes The attributes
* @param mixed $object The object * @param mixed $object The object
* *
* @throws \LogicException
*
* @return bool * @return bool
*
* @throws \LogicException
*/ */
protected function isGranted($attributes, $object = null) protected function isGranted($attributes, $object = null)
{ {
@ -231,7 +231,7 @@ class Controller extends ContainerAware
* *
* @return AccessDeniedException * @return AccessDeniedException
*/ */
public function createAccessDeniedException($message = 'Access Denied', \Exception $previous = null) public function createAccessDeniedException($message = 'Access Denied.', \Exception $previous = null)
{ {
return new AccessDeniedException($message, $previous); return new AccessDeniedException($message, $previous);
} }

View File

@ -444,7 +444,7 @@ class FrameworkExtension extends Extension
/** /**
* Loads the request configuration. * Loads the request configuration.
* *
* @param array $config A session configuration array * @param array $config A request configuration array
* @param ContainerBuilder $container A ContainerBuilder instance * @param ContainerBuilder $container A ContainerBuilder instance
* @param XmlFileLoader $loader An XmlFileLoader instance * @param XmlFileLoader $loader An XmlFileLoader instance
*/ */

View File

@ -1,37 +0,0 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="templating.asset.path_package.class">Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage</parameter>
<parameter key="templating.asset.url_package.class">Symfony\Component\Templating\Asset\UrlPackage</parameter>
<parameter key="templating.asset.package_factory.class">Symfony\Bundle\FrameworkBundle\Templating\Asset\PackageFactory</parameter>
</parameters>
<services>
<service id="templating.asset.path_package" class="%templating.asset.path_package.class%" abstract="true">
<argument type="service" id="request" />
<argument /> <!-- version -->
<argument /> <!-- version format -->
</service>
<service id="templating.asset.url_package" class="%templating.asset.url_package.class%" abstract="true">
<argument /> <!-- base urls -->
<argument /> <!-- version -->
<argument /> <!-- version format -->
</service>
<service id="templating.asset.request_aware_package" class="Symfony\Component\Templating\Asset\PackageInterface" abstract="true">
<factory service="templating.asset.package_factory" method="getPackage" />
<argument type="service" id="request" strict="false" />
<argument /> <!-- HTTP id -->
<argument /> <!-- SSL id -->
</service>
<service id="templating.asset.package_factory" class="%templating.asset.package_factory.class%">
<argument type="service" id="service_container" />
</service>
</services>
</container>

View File

@ -7,7 +7,7 @@
)) ?> )) ?>
<?php if ($multiple): ?> multiple="multiple"<?php endif ?> <?php if ($multiple): ?> multiple="multiple"<?php endif ?>
> >
<?php if (null !== $placeholder): ?><option value=""<?php if ($required and empty($value) && "0" !== $value): ?> selected="selected"<?php endif?>><?php echo $view->escape($view['translator']->trans($placeholder, array(), $translation_domain)) ?></option><?php endif; ?> <?php if (null !== $placeholder): ?><option value=""<?php if ($required and empty($value) && '0' !== $value): ?> selected="selected"<?php endif?>><?php echo '' != $placeholder ? $view->escape($view['translator']->trans($placeholder, array(), $translation_domain)) : '' ?></option><?php endif; ?>
<?php if (count($preferred_choices) > 0): ?> <?php if (count($preferred_choices) > 0): ?>
<?php echo $view['form']->block($form, 'choice_widget_options', array('choices' => $preferred_choices)) ?> <?php echo $view['form']->block($form, 'choice_widget_options', array('choices' => $preferred_choices)) ?>
<?php if (count($choices) > 0 && null !== $separator): ?> <?php if (count($choices) > 0 && null !== $separator): ?>

View File

@ -1,6 +1,6 @@
<div <?php echo $view['form']->block($form, 'widget_container_attributes') ?>> <div <?php echo $view['form']->block($form, 'widget_container_attributes') ?>>
<?php foreach ($form as $child): ?> <?php foreach ($form as $child): ?>
<?php echo $view['form']->widget($child) ?> <?php echo $view['form']->widget($child) ?>
<?php echo $view['form']->label($child) ?> <?php echo $view['form']->label($child, null, array('translation_domain' => $choice_translation_domain)) ?>
<?php endforeach ?> <?php endforeach ?>
</div> </div>

View File

@ -4,5 +4,5 @@
<?php if (!$label) { $label = isset($label_format) <?php if (!$label) { $label = isset($label_format)
? strtr($label_format, array('%name%' => $name, '%id%' => $id)) ? strtr($label_format, array('%name%' => $name, '%id%' => $id))
: $view['form']->humanize($name); } ?> : $view['form']->humanize($name); } ?>
<label <?php foreach ($label_attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label, array(), $translation_domain)) ?></label> <label <?php foreach ($label_attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape(false !== $translation_domain ? $view['translator']->trans($label, array(), $translation_domain) : $label) ?></label>
<?php endif ?> <?php endif ?>

View File

@ -14,7 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase; use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension; use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\DependencyInjection\Loader\ClosureLoader; use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Reference;
@ -530,14 +530,14 @@ abstract class FrameworkExtensionTest extends TestCase
$this->assertUrlPackage($container, $package, array('https://bar2.example.com'), $legacy ? '' : 'SomeVersionScheme', $legacy ? '%%s?%%s' : '%%s?version=%%s'); $this->assertUrlPackage($container, $package, array('https://bar2.example.com'), $legacy ? '' : 'SomeVersionScheme', $legacy ? '%%s?%%s' : '%%s?version=%%s');
} }
private function assertPathPackage(ContainerBuilder $container, Definition $package, $basePath, $version, $format) private function assertPathPackage(ContainerBuilder $container, DefinitionDecorator $package, $basePath, $version, $format)
{ {
$this->assertEquals('assets.path_package', $package->getParent()); $this->assertEquals('assets.path_package', $package->getParent());
$this->assertEquals($basePath, $package->getArgument(0)); $this->assertEquals($basePath, $package->getArgument(0));
$this->assertVersionStrategy($container, $package->getArgument(1), $version, $format); $this->assertVersionStrategy($container, $package->getArgument(1), $version, $format);
} }
private function assertUrlPackage(ContainerBuilder $container, Definition $package, $baseUrls, $version, $format) private function assertUrlPackage(ContainerBuilder $container, DefinitionDecorator $package, $baseUrls, $version, $format)
{ {
$this->assertEquals('assets.url_package', $package->getParent()); $this->assertEquals('assets.url_package', $package->getParent());
$this->assertEquals($baseUrls, $package->getArgument(0)); $this->assertEquals($baseUrls, $package->getArgument(0));

View File

@ -77,7 +77,7 @@ class Cookie
if (null !== $expires) { if (null !== $expires) {
$timestampAsDateTime = \DateTime::createFromFormat('U', $expires); $timestampAsDateTime = \DateTime::createFromFormat('U', $expires);
if (false === $timestampAsDateTime) { if (false === $timestampAsDateTime) {
throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.'), $expires); throw new \UnexpectedValueException(sprintf('The cookie expiration time "%s" is not valid.', $expires));
} }
$this->expires = $timestampAsDateTime->getTimestamp(); $this->expires = $timestampAsDateTime->getTimestamp();

View File

@ -22,7 +22,7 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
public function prepare_workspace() public function prepare_workspace()
{ {
$this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().rand(0, 1000); $this->workspace = rtrim(sys_get_temp_dir(), DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.time().mt_rand(0, 1000);
mkdir($this->workspace, 0777, true); mkdir($this->workspace, 0777, true);
$this->workspace = realpath($this->workspace); $this->workspace = realpath($this->workspace);
} }

View File

@ -69,7 +69,7 @@ class DirectoryResourceTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if the resource has not changed'); $this->assertTrue($resource->isFresh(time() + 10), '->isFresh() returns true if the resource has not changed');
$this->assertFalse($resource->isFresh(time() - 86400), '->isFresh() returns false if the resource has been updated'); $this->assertFalse($resource->isFresh(time() - 86400), '->isFresh() returns false if the resource has been updated');
$resource = new DirectoryResource('/____foo/foobar'.rand(1, 999999)); $resource = new DirectoryResource('/____foo/foobar'.mt_rand(1, 999999));
$this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the resource does not exist'); $this->assertFalse($resource->isFresh(time()), '->isFresh() returns false if the resource does not exist');
} }

View File

@ -48,7 +48,7 @@ class FileResourceTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed'); $this->assertTrue($this->resource->isFresh($this->time + 10), '->isFresh() returns true if the resource has not changed');
$this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated'); $this->assertFalse($this->resource->isFresh($this->time - 86400), '->isFresh() returns false if the resource has been updated');
$resource = new FileResource('/____foo/foobar'.rand(1, 999999)); $resource = new FileResource('/____foo/foobar'.mt_rand(1, 999999));
$this->assertFalse($resource->isFresh($this->time), '->isFresh() returns false if the resource does not exist'); $this->assertFalse($resource->isFresh($this->time), '->isFresh() returns false if the resource does not exist');
} }

View File

@ -40,7 +40,7 @@ class TextDescriptor extends Descriptor
$totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName());
$spacingWidth = $totalWidth - strlen($argument->getName()) + 2; $spacingWidth = $totalWidth - strlen($argument->getName()) + 2;
$this->writeText(sprintf(" <info>%s</info>%s%s%s", $this->writeText(sprintf(' <info>%s</info>%s%s%s',
$argument->getName(), $argument->getName(),
str_repeat(' ', $spacingWidth), str_repeat(' ', $spacingWidth),
// + 17 = 2 spaces + <info> + </info> + 2 spaces // + 17 = 2 spaces + <info> + </info> + 2 spaces
@ -77,7 +77,7 @@ class TextDescriptor extends Descriptor
$spacingWidth = $totalWidth - strlen($synopsis) + 2; $spacingWidth = $totalWidth - strlen($synopsis) + 2;
$this->writeText(sprintf(" <info>%s</info>%s%s%s%s", $this->writeText(sprintf(' <info>%s</info>%s%s%s%s',
$synopsis, $synopsis,
str_repeat(' ', $spacingWidth), str_repeat(' ', $spacingWidth),
// + 17 = 2 spaces + <info> + </info> + 2 spaces // + 17 = 2 spaces + <info> + </info> + 2 spaces
@ -207,7 +207,7 @@ class TextDescriptor extends Descriptor
foreach ($namespace['commands'] as $name) { foreach ($namespace['commands'] as $name) {
$this->writeText("\n"); $this->writeText("\n");
$spacingWidth = $width - strlen($name); $spacingWidth = $width - strlen($name);
$this->writeText(sprintf(" <info>%s</info>%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options); $this->writeText(sprintf(' <info>%s</info>%s%s', $name, str_repeat(' ', $spacingWidth), $description->getCommand($name)->getDescription()), $options);
} }
} }
@ -266,7 +266,8 @@ class TextDescriptor extends Descriptor
{ {
$totalWidth = 0; $totalWidth = 0;
foreach ($options as $option) { foreach ($options as $option) {
$nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- // "-" + shortcut + ", --" + name
$nameLength = 1 + max(strlen($option->getShortcut()), 1) + 4 + strlen($option->getName());
if ($option->acceptValue()) { if ($option->acceptValue()) {
$valueLength = 1 + strlen($option->getName()); // = + value $valueLength = 1 + strlen($option->getName()); // = + value

View File

@ -221,7 +221,7 @@ class ArgvInput extends Input
} }
if (null !== $value && !$option->acceptValue()) { if (null !== $value && !$option->acceptValue()) {
throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name));
} }
if (null === $value && $option->acceptValue() && count($this->parsed)) { if (null === $value && $option->acceptValue() && count($this->parsed)) {

View File

@ -30,6 +30,9 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/ */
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{ {
/**
* @var StreamOutput
*/
private $stderr; private $stderr;
/** /**
@ -43,14 +46,12 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
*/ */
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
{ {
$outputStream = 'php://stdout'; $outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output';
if (!$this->hasStdoutSupport()) { $errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output';
$outputStream = 'php://output';
}
parent::__construct(fopen($outputStream, 'w'), $verbosity, $decorated, $formatter); parent::__construct(fopen($outputStream, 'w'), $verbosity, $decorated, $formatter);
$this->stderr = new StreamOutput(fopen('php://stderr', 'w'), $verbosity, $decorated, $this->getFormatter()); $this->stderr = new StreamOutput(fopen($errorStream, 'w'), $verbosity, $decorated, $this->getFormatter());
} }
/** /**
@ -100,14 +101,32 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
* Returns true if current environment supports writing console output to * Returns true if current environment supports writing console output to
* STDOUT. * STDOUT.
* *
* IBM iSeries (OS400) exhibits character-encoding issues when writing to
* STDOUT and doesn't properly convert ASCII to EBCDIC, resulting in garbage
* output.
*
* @return bool * @return bool
*/ */
protected function hasStdoutSupport() protected function hasStdoutSupport()
{ {
return ('OS400' != php_uname('s')); return false === $this->isRunningOS400();
}
/**
* Returns true if current environment supports writing console output to
* STDERR.
*
* @return bool
*/
protected function hasStderrSupport()
{
return false === $this->isRunningOS400();
}
/**
* Checks if current executing environment is IBM iSeries (OS400), which
* doesn't properly convert character-encodings between ASCII to EBCDIC.
*
* @return bool
*/
private function isRunningOS400()
{
return 'OS400' === php_uname('s');
} }
} }

View File

@ -42,6 +42,7 @@ class ObjectsProvider
'input_option_3' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description'), 'input_option_3' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, 'option description'),
'input_option_4' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'option description', array()), 'input_option_4' => new InputOption('option_name', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'option description', array()),
'input_option_5' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, "multiline\noption description"), 'input_option_5' => new InputOption('option_name', 'o', InputOption::VALUE_REQUIRED, "multiline\noption description"),
'input_option_6' => new InputOption('option_name', array('o', 'O'), InputOption::VALUE_REQUIRED, 'option with multiple shortcuts'),
); );
} }

View File

@ -0,0 +1 @@
{"name":"--option_name","shortcut":"-o|-O","accept_value":true,"is_value_required":true,"is_multiple":false,"description":"option with multiple shortcuts","default":null}

View File

@ -0,0 +1,9 @@
**option_name:**
* Name: `--option_name`
* Shortcut: `-o|-O`
* Accept value: yes
* Is value required: yes
* Is multiple: no
* Description: option with multiple shortcuts
* Default: `NULL`

View File

@ -0,0 +1 @@
<info>-o|O, --option_name=OPTION_NAME</info> option with multiple shortcuts

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<option name="--option_name" shortcut="-o" shortcuts="-o|-O" accept_value="1" is_value_required="1" is_multiple="0">
<description>option with multiple shortcuts</description>
<defaults/>
</option>

View File

@ -162,6 +162,14 @@ class ResolveDefinitionTemplatesPass implements CompilerPassInterface
if (isset($changes['lazy'])) { if (isset($changes['lazy'])) {
$def->setLazy($definition->isLazy()); $def->setLazy($definition->isLazy());
} }
if (isset($changes['decorated_service'])) {
$decoratedService = $definition->getDecoratedService();
if (null === $decoratedService) {
$def->setDecoratedService($decoratedService);
} else {
$def->setDecoratedService($decoratedService[0], $decoratedService[1]);
}
}
// merge arguments // merge arguments
foreach ($definition->getArguments() as $k => $v) { foreach ($definition->getArguments() as $k => $v) {

View File

@ -170,6 +170,16 @@ class DefinitionDecorator extends Definition
return parent::setLazy($boolean); return parent::setLazy($boolean);
} }
/**
* {@inheritdoc}
*/
public function setDecoratedService($id, $renamedId = null)
{
$this->changes['decorated_service'] = true;
return parent::setDecoratedService($id, $renamedId);
}
/** /**
* Gets an argument to pass to the service constructor/factory method. * Gets an argument to pass to the service constructor/factory method.
* *

View File

@ -1301,11 +1301,6 @@ EOF;
foreach ($value->getArguments() as $argument) { foreach ($value->getArguments() as $argument) {
$arguments[] = $this->dumpValue($argument); $arguments[] = $this->dumpValue($argument);
} }
$class = $this->dumpValue($value->getClass());
if (false !== strpos($class, '$')) {
throw new RuntimeException('Cannot dump definitions which have a variable class name.');
}
if (null !== $value->getFactory()) { if (null !== $value->getFactory()) {
$factory = $value->getFactory(); $factory = $value->getFactory();
@ -1343,6 +1338,15 @@ EOF;
} }
} }
$class = $value->getClass();
if (null === $class) {
throw new RuntimeException('Cannot dump definitions which have no class nor factory.');
}
$class = $this->dumpValue($class);
if (false !== strpos($class, '$')) {
throw new RuntimeException('Cannot dump definitions which have a variable class name.');
}
return sprintf('new \\%s(%s)', substr(str_replace('\\\\', '\\', $class), 1, -1), implode(', ', $arguments)); return sprintf('new \\%s(%s)', substr(str_replace('\\\\', '\\', $class), 1, -1), implode(', ', $arguments));
} elseif ($value instanceof Variable) { } elseif ($value instanceof Variable) {
return '$'.$value; return '$'.$value;

View File

@ -151,7 +151,7 @@ class XmlFileLoader extends FileLoader
foreach (array('class', 'shared', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'lazy', 'abstract') as $key) { foreach (array('class', 'shared', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'lazy', 'abstract') as $key) {
if ($value = $service->getAttribute($key)) { if ($value = $service->getAttribute($key)) {
if (in_array($key, array('factory-class', 'factory-method', 'factory-service'))) { if (in_array($key, array('factory-class', 'factory-method', 'factory-service'))) {
@trigger_error(sprintf('The "%s" attribute in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use the "factory" element instead.', $key, $file), E_USER_DEPRECATED); @trigger_error(sprintf('The "%s" attribute of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use the "factory" element instead.', $key, (string) $service->getAttribute('id'), $file), E_USER_DEPRECATED);
} }
$method = 'set'.str_replace('-', '', $key); $method = 'set'.str_replace('-', '', $key);
$definition->$method(XmlUtils::phpize($value)); $definition->$method(XmlUtils::phpize($value));
@ -172,7 +172,7 @@ class XmlFileLoader extends FileLoader
$triggerDeprecation = 'request' !== (string) $service->getAttribute('id'); $triggerDeprecation = 'request' !== (string) $service->getAttribute('id');
if ($triggerDeprecation) { if ($triggerDeprecation) {
@trigger_error(sprintf('The "synchronized" attribute in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $file), E_USER_DEPRECATED); @trigger_error(sprintf('The "synchronized" attribute of service "%s" in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', (string) $service->getAttribute('id'), $file), E_USER_DEPRECATED);
} }
$definition->setSynchronized(XmlUtils::phpize($value), $triggerDeprecation); $definition->setSynchronized(XmlUtils::phpize($value), $triggerDeprecation);

View File

@ -179,7 +179,7 @@ class YamlFileLoader extends FileLoader
} }
if (isset($service['synchronized'])) { if (isset($service['synchronized'])) {
@trigger_error(sprintf('The "synchronized" key in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $file), E_USER_DEPRECATED); @trigger_error(sprintf('The "synchronized" key of service "%s" in file "%s" is deprecated since version 2.7 and will be removed in 3.0.', $id, $file), E_USER_DEPRECATED);
$definition->setSynchronized($service['synchronized'], 'request' !== $id); $definition->setSynchronized($service['synchronized'], 'request' !== $id);
} }
@ -209,17 +209,17 @@ class YamlFileLoader extends FileLoader
} }
if (isset($service['factory_class'])) { if (isset($service['factory_class'])) {
@trigger_error(sprintf('The "factory_class" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); @trigger_error(sprintf('The "factory_class" key of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED);
$definition->setFactoryClass($service['factory_class']); $definition->setFactoryClass($service['factory_class']);
} }
if (isset($service['factory_method'])) { if (isset($service['factory_method'])) {
@trigger_error(sprintf('The "factory_method" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); @trigger_error(sprintf('The "factory_method" key of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED);
$definition->setFactoryMethod($service['factory_method']); $definition->setFactoryMethod($service['factory_method']);
} }
if (isset($service['factory_service'])) { if (isset($service['factory_service'])) {
@trigger_error(sprintf('The "factory_service" key in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $file), E_USER_DEPRECATED); @trigger_error(sprintf('The "factory_service" key of service "%s" in file "%s" is deprecated since version 2.6 and will be removed in 3.0. Use "factory" instead.', $id, $file), E_USER_DEPRECATED);
$definition->setFactoryService($service['factory_service']); $definition->setFactoryService($service['factory_service']);
} }

View File

@ -120,6 +120,25 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase
$this->assertEquals(array(), $def->getTags()); $this->assertEquals(array(), $def->getTags());
} }
public function testProcessDoesNotCopyDecoratedService()
{
$container = new ContainerBuilder();
$container
->register('parent')
->setDecoratedService('foo')
;
$container
->setDefinition('child', new DefinitionDecorator('parent'))
;
$this->process($container);
$def = $container->getDefinition('child');
$this->assertNull($def->getDecoratedService());
}
public function testProcessHandlesMultipleInheritance() public function testProcessHandlesMultipleInheritance()
{ {
$container = new ContainerBuilder(); $container = new ContainerBuilder();
@ -212,6 +231,19 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase
$this->assertSame('parentClass', $methodCalls[0][1][0]->getClass()); $this->assertSame('parentClass', $methodCalls[0][1][0]->getClass());
} }
public function testSetDecoratedServiceOnServiceHasParent()
{
$container = new ContainerBuilder();
$container->register('parent', 'stdClass');
$container->setDefinition('child1', new DefinitionDecorator('parent'))
->setDecoratedService('foo', 'foo_inner')
;
$this->assertEquals(array('foo', 'foo_inner'), $container->getDefinition('child1')->getDecoratedService());
}
protected function process(ContainerBuilder $container) protected function process(ContainerBuilder $container)
{ {
$pass = new ResolveDefinitionTemplatesPass(); $pass = new ResolveDefinitionTemplatesPass();

View File

@ -44,4 +44,4 @@ $phar->addFromString('schema/project-1.0.xsd', <<<EOT
</xsd:schema> </xsd:schema>
EOT EOT
); );
$phar->setStub('<?php require_once "phar://ProjectWithXsdExtensionInPhar.phar/ProjectWithXsdExtensionInPhar.php"; __HALT_COMPILER(); ?>'); $phar->setStub('<?php Phar::mapPhar("ProjectWithXsdExtensionInPhar.phar"); require_once "phar://ProjectWithXsdExtensionInPhar.phar/ProjectWithXsdExtensionInPhar.php"; __HALT_COMPILER(); ?>');

View File

@ -244,7 +244,7 @@ class Filesystem
$this->chgrp(new \FilesystemIterator($file), $group, true); $this->chgrp(new \FilesystemIterator($file), $group, true);
} }
if (is_link($file) && function_exists('lchgrp')) { if (is_link($file) && function_exists('lchgrp')) {
if (true !== @lchgrp($file, $group)) { if (true !== @lchgrp($file, $group) || (defined('HHVM_VERSION') && !posix_getgrnam($group))) {
throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file); throw new IOException(sprintf('Failed to chgrp file "%s".', $file), 0, null, $file);
} }
} else { } else {

View File

@ -15,11 +15,10 @@ use Symfony\Component\Finder\Glob;
class GlobTest extends \PHPUnit_Framework_TestCase class GlobTest extends \PHPUnit_Framework_TestCase
{ {
public function testGlobToRegexDelimiters() public function testGlobToRegexDelimiters()
{ {
$this->assertEquals(Glob::toRegex('.*'), '#^\.[^/]*$#'); $this->assertEquals('#^\.[^/]*$#', Glob::toRegex('.*'));
$this->assertEquals(Glob::toRegex('.*', true, true, ''), '^\.[^/]*$'); $this->assertEquals('^\.[^/]*$', Glob::toRegex('.*', true, true, ''));
$this->assertEquals(Glob::toRegex('.*', true, true, '/'), '/^\.[^/]*$/'); $this->assertEquals('/^\.[^/]*$/', Glob::toRegex('.*', true, true, '/'));
} }
} }

View File

@ -30,14 +30,21 @@ class ArrayChoiceList implements ChoiceListInterface
* *
* @var array * @var array
*/ */
protected $choices = array(); protected $choices;
/** /**
* The values of the choices. * The values indexed by the original keys.
* *
* @var string[] * @var array
*/ */
protected $values = array(); protected $structuredValues;
/**
* The original keys of the choices array.
*
* @var int[]|string[]
*/
protected $originalKeys;
/** /**
* The callback for creating the value for a choice. * The callback for creating the value for a choice.
@ -51,31 +58,41 @@ class ArrayChoiceList implements ChoiceListInterface
* *
* The given choice array must have the same array keys as the value array. * The given choice array must have the same array keys as the value array.
* *
* @param array $choices The selectable choices * @param array|\Traversable $choices The selectable choices
* @param callable|null $value The callable for creating the value for a * @param callable|null $value The callable for creating the value
* choice. If `null` is passed, incrementing * for a choice. If `null` is passed,
* integers are used as values * incrementing integers are used as
* values
*/ */
public function __construct(array $choices, $value = null) public function __construct($choices, $value = null)
{ {
if (null !== $value && !is_callable($value)) { if (null !== $value && !is_callable($value)) {
throw new UnexpectedTypeException($value, 'null or callable'); throw new UnexpectedTypeException($value, 'null or callable');
} }
$this->choices = $choices; if ($choices instanceof \Traversable) {
$this->values = array(); $choices = iterator_to_array($choices);
$this->valueCallback = $value;
if (null === $value) {
$i = 0;
foreach ($this->choices as $key => $choice) {
$this->values[$key] = (string) $i++;
}
} else {
foreach ($choices as $key => $choice) {
$this->values[$key] = (string) call_user_func($value, $choice);
}
} }
if (null !== $value) {
// If a deterministic value generator was passed, use it later
$this->valueCallback = $value;
} else {
// Otherwise simply generate incrementing integers as values
$i = 0;
$value = function () use (&$i) {
return $i++;
};
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
$this->flatten($choices, $value, $choicesByValues, $keysByValues, $structuredValues);
$this->choices = $choicesByValues;
$this->originalKeys = $keysByValues;
$this->structuredValues = $structuredValues;
} }
/** /**
@ -91,7 +108,23 @@ class ArrayChoiceList implements ChoiceListInterface
*/ */
public function getValues() public function getValues()
{ {
return $this->values; return array_map('strval', array_keys($this->choices));
}
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
return $this->structuredValues;
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
return $this->originalKeys;
} }
/** /**
@ -102,17 +135,8 @@ class ArrayChoiceList implements ChoiceListInterface
$choices = array(); $choices = array();
foreach ($values as $i => $givenValue) { foreach ($values as $i => $givenValue) {
foreach ($this->values as $j => $value) { if (isset($this->choices[$givenValue])) {
if ($value !== (string) $givenValue) { $choices[$i] = $this->choices[$givenValue];
continue;
}
$choices[$i] = $this->choices[$j];
unset($values[$i]);
if (0 === count($values)) {
break 2;
}
} }
} }
@ -131,28 +155,56 @@ class ArrayChoiceList implements ChoiceListInterface
$givenValues = array(); $givenValues = array();
foreach ($choices as $i => $givenChoice) { foreach ($choices as $i => $givenChoice) {
$givenValues[$i] = (string) call_user_func($this->valueCallback, $givenChoice); $givenValues[$i] = call_user_func($this->valueCallback, $givenChoice);
} }
return array_intersect($givenValues, $this->values); return array_intersect($givenValues, array_keys($this->choices));
} }
// Otherwise compare choices by identity // Otherwise compare choices by identity
foreach ($choices as $i => $givenChoice) { foreach ($choices as $i => $givenChoice) {
foreach ($this->choices as $j => $choice) { foreach ($this->choices as $value => $choice) {
if ($choice !== $givenChoice) { if ($choice === $givenChoice) {
continue; $values[$i] = (string) $value;
} break;
$values[$i] = $this->values[$j];
unset($choices[$i]);
if (0 === count($choices)) {
break 2;
} }
} }
} }
return $values; return $values;
} }
/**
* Flattens an array into the given output variables.
*
* @param array $choices The array to flatten
* @param callable $value The callable for generating choice values
* @param array $choicesByValues The flattened choices indexed by the
* corresponding values
* @param array $keysByValues The original keys indexed by the
* corresponding values
*
* @internal Must not be used by user-land code
*/
protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues)
{
if (null === $choicesByValues) {
$choicesByValues = array();
$keysByValues = array();
$structuredValues = array();
}
foreach ($choices as $key => $choice) {
if (is_array($choice)) {
$this->flatten($choice, $value, $choicesByValues, $keysByValues, $structuredValues[$key]);
continue;
}
$choiceValue = (string) call_user_func($value, $choice);
$choicesByValues[$choiceValue] = $choice;
$keysByValues[$choiceValue] = $key;
$structuredValues[$key] = $choiceValue;
}
}
} }

View File

@ -62,6 +62,8 @@ class ArrayKeyChoiceList extends ArrayChoiceList
* @return int|string The choice as PHP array key * @return int|string The choice as PHP array key
* *
* @throws InvalidArgumentException If the choice is not scalar * @throws InvalidArgumentException If the choice is not scalar
*
* @internal Must not be used outside this class
*/ */
public static function toArrayKey($choice) public static function toArrayKey($choice)
{ {
@ -89,23 +91,27 @@ class ArrayKeyChoiceList extends ArrayChoiceList
* If no values are given, the choices are cast to strings and used as * If no values are given, the choices are cast to strings and used as
* values. * values.
* *
* @param array $choices The selectable choices * @param array|\Traversable $choices The selectable choices
* @param callable $value The callable for creating the value for a * @param callable $value The callable for creating the value
* choice. If `null` is passed, the choices are * for a choice. If `null` is passed, the
* cast to strings and used as values * choices are cast to strings and used
* as values
* *
* @throws InvalidArgumentException If the keys of the choices don't match * @throws InvalidArgumentException If the keys of the choices don't match
* the keys of the values or if any of the * the keys of the values or if any of the
* choices is not scalar * choices is not scalar
*/ */
public function __construct(array $choices, $value = null) public function __construct($choices, $value = null)
{ {
$choices = array_map(array(__CLASS__, 'toArrayKey'), $choices); // If no values are given, use the choices as values
// Since the choices are stored in the collection keys, i.e. they are
// strings or integers, we are guaranteed to be able to convert them
// to strings
if (null === $value) { if (null === $value) {
$value = function ($choice) { $value = function ($choice) {
return (string) $choice; return (string) $choice;
}; };
$this->useChoicesAsValues = true; $this->useChoicesAsValues = true;
} }
@ -122,7 +128,7 @@ class ArrayKeyChoiceList extends ArrayChoiceList
// If the values are identical to the choices, so we can just return // If the values are identical to the choices, so we can just return
// them to improve performance a little bit // them to improve performance a little bit
return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, $this->values)); return array_map(array(__CLASS__, 'toArrayKey'), array_intersect($values, array_keys($this->choices)));
} }
return parent::getChoicesForValues($values); return parent::getChoicesForValues($values);
@ -143,4 +149,38 @@ class ArrayKeyChoiceList extends ArrayChoiceList
return parent::getValuesForChoices($choices); return parent::getValuesForChoices($choices);
} }
/**
* Flattens and flips an array into the given output variable.
*
* @param array $choices The array to flatten
* @param callable $value The callable for generating choice values
* @param array $choicesByValues The flattened choices indexed by the
* corresponding values
* @param array $keysByValues The original keys indexed by the
* corresponding values
*
* @internal Must not be used by user-land code
*/
protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues)
{
if (null === $choicesByValues) {
$choicesByValues = array();
$keysByValues = array();
$structuredValues = array();
}
foreach ($choices as $choice => $key) {
if (is_array($key)) {
$this->flatten($key, $value, $choicesByValues, $keysByValues, $structuredValues[$choice]);
continue;
}
$choiceValue = (string) call_user_func($value, $choice);
$choicesByValues[$choiceValue] = $choice;
$keysByValues[$choiceValue] = $key;
$structuredValues[$key] = $choiceValue;
}
}
} }

View File

@ -14,16 +14,13 @@ namespace Symfony\Component\Form\ChoiceList;
/** /**
* A list of choices that can be selected in a choice field. * A list of choices that can be selected in a choice field.
* *
* A choice list assigns string values to each of a list of choices. These * A choice list assigns unique string values to each of a list of choices.
* string values are displayed in the "value" attributes in HTML and submitted * These string values are displayed in the "value" attributes in HTML and
* back to the server. * submitted back to the server.
* *
* The acceptable data types for the choices depend on the implementation. * The acceptable data types for the choices depend on the implementation.
* Values must always be strings and (within the list) free of duplicates. * Values must always be strings and (within the list) free of duplicates.
* *
* The choices returned by {@link getChoices()} and the values returned by
* {@link getValues()} must have the same array indices.
*
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
interface ChoiceListInterface interface ChoiceListInterface
@ -31,23 +28,66 @@ interface ChoiceListInterface
/** /**
* Returns all selectable choices. * Returns all selectable choices.
* *
* The keys of the choices correspond to the keys of the values returned by * @return array The selectable choices indexed by the corresponding values
* {@link getValues()}.
*
* @return array The selectable choices
*/ */
public function getChoices(); public function getChoices();
/** /**
* Returns the values for the choices. * Returns the values for the choices.
* *
* The keys of the values correspond to the keys of the choices returned by * The values are strings that do not contain duplicates.
* {@link getChoices()}.
* *
* @return string[] The choice values * @return string[] The choice values
*/ */
public function getValues(); public function getValues();
/**
* Returns the values in the structure originally passed to the list.
*
* Contrary to {@link getValues()}, the result is indexed by the original
* keys of the choices. If the original array contained nested arrays, these
* nested arrays are represented here as well:
*
* $form->add('field', 'choice', array(
* 'choices' => array(
* 'Decided' => array('Yes' => true, 'No' => false),
* 'Undecided' => array('Maybe' => null),
* ),
* ));
*
* In this example, the result of this method is:
*
* array(
* 'Decided' => array('Yes' => '0', 'No' => '1'),
* 'Undecided' => array('Maybe' => '2'),
* )
*
* @return string[] The choice values
*/
public function getStructuredValues();
/**
* Returns the original keys of the choices.
*
* The original keys are the keys of the choice array that was passed in the
* "choice" option of the choice type. Note that this array may contain
* duplicates if the "choice" option contained choice groups:
*
* $form->add('field', 'choice', array(
* 'choices' => array(
* 'Decided' => array(true, false),
* 'Undecided' => array(null),
* ),
* ));
*
* In this example, the original key 0 appears twice, once for `true` and
* once for `null`.
*
* @return int[]|string[] The original choice keys indexed by the
* corresponding choice values
*/
public function getOriginalKeys();
/** /**
* Returns the choices corresponding to the given values. * Returns the choices corresponding to the given values.
* *

View File

@ -65,6 +65,30 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
return hash('sha256', $namespace.':'.json_encode($value)); return hash('sha256', $namespace.':'.json_encode($value));
} }
/**
* Flattens an array into the given output variable.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
private static function flatten(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flatten($value, $output);
continue;
}
$output[$key] = $value;
}
}
/** /**
* Decorates the given factory. * Decorates the given factory.
* *
@ -100,7 +124,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
// We ignore the choice groups for caching. If two choice lists are // We ignore the choice groups for caching. If two choice lists are
// requested with the same choices, but a different grouping, the same // requested with the same choices, but a different grouping, the same
// choice list is returned. // choice list is returned.
DefaultChoiceListFactory::flatten($choices, $flatChoices); self::flatten($choices, $flatChoices);
$hash = self::generateHash(array($flatChoices, $value), 'fromChoices'); $hash = self::generateHash(array($flatChoices, $value), 'fromChoices');
@ -129,7 +153,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
// We ignore the choice groups for caching. If two choice lists are // We ignore the choice groups for caching. If two choice lists are
// requested with the same choices, but a different grouping, the same // requested with the same choices, but a different grouping, the same
// choice list is returned. // choice list is returned.
DefaultChoiceListFactory::flattenFlipped($choices, $flatChoices); self::flatten($choices, $flatChoices);
$hash = self::generateHash(array($flatChoices, $value), 'fromFlippedChoices'); $hash = self::generateHash(array($flatChoices, $value), 'fromFlippedChoices');
@ -161,7 +185,6 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
{ {
// The input is not validated on purpose. This way, the decorated // The input is not validated on purpose. This way, the decorated
// factory may decide which input to accept and which not. // factory may decide which input to accept and which not.
$hash = self::generateHash(array($list, $preferredChoices, $label, $index, $groupBy, $attr)); $hash = self::generateHash(array($list, $preferredChoices, $label, $index, $groupBy, $attr));
if (!isset($this->views[$hash])) { if (!isset($this->views[$hash])) {

View File

@ -69,7 +69,7 @@ interface ChoiceListFactoryInterface
* argument. * argument.
* *
* @param ChoiceLoaderInterface $loader The choice loader * @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable $value The callable generating the choice * @param null|callable $value The callable generating the choice
* values * values
* *
* @return ChoiceListInterface The choice list * @return ChoiceListInterface The choice list
@ -98,25 +98,20 @@ interface ChoiceListFactoryInterface
* The preferred choices can also be passed as array. Each choice that is * The preferred choices can also be passed as array. Each choice that is
* contained in that array will be marked as preferred. * contained in that array will be marked as preferred.
* *
* The groups can be passed as a multi-dimensional array. In that case, a
* group will be created for each array entry containing a nested array.
* For all other entries, the choice for the corresponding key will be
* inserted at that position.
*
* The attributes can be passed as multi-dimensional array. The keys should * The attributes can be passed as multi-dimensional array. The keys should
* match the keys of the choices. The values should be arrays of HTML * match the keys of the choices. The values should be arrays of HTML
* attributes that should be added to the respective choice. * attributes that should be added to the respective choice.
* *
* @param ChoiceListInterface $list The choice list * @param ChoiceListInterface $list The choice list
* @param null|array|callable $preferredChoices The preferred choices * @param null|array|callable $preferredChoices The preferred choices
* @param null|callable $label The callable generating * @param null|callable $label The callable generating the
* the choice labels * choice labels
* @param null|callable $index The callable generating * @param null|callable $index The callable generating the
* the view indices * view indices
* @param null|array|\Traversable|callable $groupBy The callable generating * @param null|callable $groupBy The callable generating the
* the group names * group names
* @param null|array|callable $attr The callable generating * @param null|array|callable $attr The callable generating the
* the HTML attributes * HTML attributes
* *
* @return ChoiceListView The choice list view * @return ChoiceListView The choice list view
*/ */

View File

@ -15,11 +15,11 @@ use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList;
use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\LazyChoiceList; use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView; use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\ChoiceList\View\ChoiceView;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView; use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView;
/** /**
@ -29,72 +29,12 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView;
*/ */
class DefaultChoiceListFactory implements ChoiceListFactoryInterface class DefaultChoiceListFactory implements ChoiceListFactoryInterface
{ {
/**
* Flattens an array into the given output variable.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
public static function flatten(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flatten($value, $output);
continue;
}
$output[$key] = $value;
}
}
/**
* Flattens and flips an array into the given output variable.
*
* During the flattening, the keys and values of the input array are
* flipped.
*
* @param array $array The array to flatten
* @param array $output The flattened output
*
* @internal Should not be used by user-land code
*/
public static function flattenFlipped(array $array, &$output)
{
if (null === $output) {
$output = array();
}
foreach ($array as $key => $value) {
if (is_array($value)) {
self::flattenFlipped($value, $output);
continue;
}
$output[$value] = $key;
}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function createListFromChoices($choices, $value = null) public function createListFromChoices($choices, $value = null)
{ {
if ($choices instanceof \Traversable) { return new ArrayChoiceList($choices, $value);
$choices = iterator_to_array($choices);
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
self::flatten($choices, $flatChoices);
return new ArrayChoiceList($flatChoices, $value);
} }
/** /**
@ -105,26 +45,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
*/ */
public function createListFromFlippedChoices($choices, $value = null) public function createListFromFlippedChoices($choices, $value = null)
{ {
if ($choices instanceof \Traversable) { return new ArrayKeyChoiceList($choices, $value);
$choices = iterator_to_array($choices);
}
// If the choices are given as recursive array (i.e. with explicit
// choice groups), flatten the array. The grouping information is needed
// in the view only.
self::flattenFlipped($choices, $flatChoices);
// If no values are given, use the choices as values
// Since the choices are stored in the collection keys, i.e. they are
// strings or integers, we are guaranteed to be able to convert them
// to strings
if (null === $value) {
$value = function ($choice) {
return (string) $choice;
};
}
return new ArrayKeyChoiceList($flatChoices, $value);
} }
/** /**
@ -141,22 +62,24 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null) public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
{ {
// Backwards compatibility // Backwards compatibility
if ($list instanceof LegacyChoiceListInterface && empty($preferredChoices) if ($list instanceof LegacyChoiceListAdapter && empty($preferredChoices)
&& null === $label && null === $index && null === $groupBy && null === $attr) { && null === $label && null === $index && null === $groupBy && null === $attr) {
$mapToNonLegacyChoiceView = function (LegacyChoiceView $choiceView) { $mapToNonLegacyChoiceView = function (LegacyChoiceView $choiceView) {
return new ChoiceView($choiceView->data, $choiceView->value, $choiceView->label); return new ChoiceView($choiceView->data, $choiceView->value, $choiceView->label);
}; };
$adaptedList = $list->getAdaptedList();
return new ChoiceListView( return new ChoiceListView(
array_map($mapToNonLegacyChoiceView, $list->getRemainingViews()), array_map($mapToNonLegacyChoiceView, $adaptedList->getRemainingViews()),
array_map($mapToNonLegacyChoiceView, $list->getPreferredViews()) array_map($mapToNonLegacyChoiceView, $adaptedList->getPreferredViews())
); );
} }
$preferredViews = array(); $preferredViews = array();
$otherViews = array(); $otherViews = array();
$choices = $list->getChoices(); $choices = $list->getChoices();
$values = $list->getValues(); $keys = $list->getOriginalKeys();
if (!is_callable($preferredChoices) && !empty($preferredChoices)) { if (!is_callable($preferredChoices) && !empty($preferredChoices)) {
$preferredChoices = function ($choice) use ($preferredChoices) { $preferredChoices = function ($choice) use ($preferredChoices) {
@ -169,36 +92,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
$index = 0; $index = 0;
} }
// If $groupBy is not given, no grouping is done
if (empty($groupBy)) {
foreach ($choices as $key => $choice) {
self::addChoiceView(
$choice,
$key,
$label,
$values,
$index,
$attr,
$preferredChoices,
$preferredViews,
$otherViews
);
}
return new ChoiceListView($otherViews, $preferredViews);
}
// If $groupBy is a callable, choices are added to the group with the // If $groupBy is a callable, choices are added to the group with the
// name returned by the callable. If the callable returns null, the // name returned by the callable. If the callable returns null, the
// choice is not added to any group // choice is not added to any group
if (is_callable($groupBy)) { if (is_callable($groupBy)) {
foreach ($choices as $key => $choice) { foreach ($choices as $value => $choice) {
self::addChoiceViewGroupedBy( self::addChoiceViewGroupedBy(
$groupBy, $groupBy,
$choice, $choice,
$key, (string) $value,
$label, $label,
$values, $keys,
$index, $index,
$attr, $attr,
$preferredChoices, $preferredChoices,
@ -207,13 +111,12 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
); );
} }
} else { } else {
// If $groupBy is passed as array, use that array as template for // Otherwise use the original structure of the choices
// constructing the groups
self::addChoiceViewsGroupedBy( self::addChoiceViewsGroupedBy(
$groupBy, $list->getStructuredValues(),
$label, $label,
$choices, $choices,
$values, $keys,
$index, $index,
$attr, $attr,
$preferredChoices, $preferredChoices,
@ -239,15 +142,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
return new ChoiceListView($otherViews, $preferredViews); return new ChoiceListView($otherViews, $preferredViews);
} }
private static function addChoiceView($choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) private static function addChoiceView($choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{ {
$value = $values[$key]; // $value may be an integer or a string, since it's stored in the array
// keys. We want to guarantee it's a string though.
$key = $keys[$value];
$nextIndex = is_int($index) ? $index++ : call_user_func($index, $choice, $key, $value); $nextIndex = is_int($index) ? $index++ : call_user_func($index, $choice, $key, $value);
$view = new ChoiceView( $view = new ChoiceView(
$choice, $choice,
$value, $value,
// If the labels are null, use the choice key by default // If the labels are null, use the original choice key by default
null === $label ? (string) $key : (string) call_user_func($label, $choice, $key, $value), null === $label ? (string) $key : (string) call_user_func($label, $choice, $key, $value),
// The attributes may be a callable or a mapping from choice indices // The attributes may be a callable or a mapping from choice indices
// to nested arrays // to nested arrays
@ -262,19 +167,19 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
} }
} }
private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) private static function addChoiceViewsGroupedBy($groupBy, $label, $choices, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{ {
foreach ($groupBy as $key => $content) { foreach ($groupBy as $key => $value) {
// Add the contents of groups to new ChoiceGroupView instances // Add the contents of groups to new ChoiceGroupView instances
if (is_array($content)) { if (is_array($value)) {
$preferredViewsForGroup = array(); $preferredViewsForGroup = array();
$otherViewsForGroup = array(); $otherViewsForGroup = array();
self::addChoiceViewsGroupedBy( self::addChoiceViewsGroupedBy(
$content, $value,
$label, $label,
$choices, $choices,
$values, $keys,
$index, $index,
$attr, $attr,
$isPreferred, $isPreferred,
@ -295,10 +200,10 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
// Add ungrouped items directly // Add ungrouped items directly
self::addChoiceView( self::addChoiceView(
$choices[$key], $choices[$value],
$key, $value,
$label, $label,
$values, $keys,
$index, $index,
$attr, $attr,
$isPreferred, $isPreferred,
@ -308,17 +213,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
} }
} }
private static function addChoiceViewGroupedBy($groupBy, $choice, $key, $label, $values, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews) private static function addChoiceViewGroupedBy($groupBy, $choice, $value, $label, $keys, &$index, $attr, $isPreferred, &$preferredViews, &$otherViews)
{ {
$groupLabel = call_user_func($groupBy, $choice, $key, $values[$key]); $groupLabel = call_user_func($groupBy, $choice, $keys[$value], $value);
if (null === $groupLabel) { if (null === $groupLabel) {
// If the callable returns null, don't group the choice // If the callable returns null, don't group the choice
self::addChoiceView( self::addChoiceView(
$choice, $choice,
$key, $value,
$label, $label,
$values, $keys,
$index, $index,
$attr, $attr,
$isPreferred, $isPreferred,
@ -329,6 +234,8 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
return; return;
} }
$groupLabel = (string) $groupLabel;
// Initialize the group views if necessary. Unnnecessarily built group // Initialize the group views if necessary. Unnnecessarily built group
// views will be cleaned up at the end of createView() // views will be cleaned up at the end of createView()
if (!isset($preferredViews[$groupLabel])) { if (!isset($preferredViews[$groupLabel])) {
@ -338,9 +245,9 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
self::addChoiceView( self::addChoiceView(
$choice, $choice,
$key, $value,
$label, $label,
$values, $keys,
$index, $index,
$attr, $attr,
$isPreferred, $isPreferred,

View File

@ -54,8 +54,8 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface
/** /**
* Decorates the given factory. * Decorates the given factory.
* *
* @param ChoiceListFactoryInterface $decoratedFactory The decorated factory * @param ChoiceListFactoryInterface $decoratedFactory The decorated factory
* @param null|PropertyAccessorInterface $propertyAccessor The used property accessor * @param null|PropertyAccessorInterface $propertyAccessor The used property accessor
*/ */
public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null) public function __construct(ChoiceListFactoryInterface $decoratedFactory, PropertyAccessorInterface $propertyAccessor = null)
{ {
@ -98,8 +98,6 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface
if (is_object($choice) || is_array($choice)) { if (is_object($choice) || is_array($choice)) {
return $accessor->getValue($choice, $value); return $accessor->getValue($choice, $value);
} }
return;
}; };
} }
@ -128,9 +126,9 @@ class PropertyAccessDecorator implements ChoiceListFactoryInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
* *
* @param ChoiceLoaderInterface $loader The choice loader * @param ChoiceLoaderInterface $loader The choice loader
* @param null|callable|string|PropertyPath $value The callable or path for * @param null|callable|string|PropertyPath $value The callable or path for
* generating the choice values * generating the choice values
* *
* @return ChoiceListInterface The choice list * @return ChoiceListInterface The choice list
*/ */

View File

@ -43,13 +43,6 @@ class LazyChoiceList implements ChoiceListInterface
*/ */
private $value; private $value;
/**
* Whether to use the value callback to compare choices.
*
* @var bool
*/
private $compareByValue;
/** /**
* @var ChoiceListInterface|null * @var ChoiceListInterface|null
*/ */
@ -66,11 +59,10 @@ class LazyChoiceList implements ChoiceListInterface
* @param null|callable $value The callable generating the choice * @param null|callable $value The callable generating the choice
* values * values
*/ */
public function __construct(ChoiceLoaderInterface $loader, $value = null, $compareByValue = false) public function __construct(ChoiceLoaderInterface $loader, $value = null)
{ {
$this->loader = $loader; $this->loader = $loader;
$this->value = $value; $this->value = $value;
$this->compareByValue = $compareByValue;
} }
/** /**
@ -97,6 +89,30 @@ class LazyChoiceList implements ChoiceListInterface
return $this->loadedList->getValues(); return $this->loadedList->getValues();
} }
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
if (!$this->loadedList) {
$this->loadedList = $this->loader->loadChoiceList($this->value);
}
return $this->loadedList->getStructuredValues();
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
if (!$this->loadedList) {
$this->loadedList = $this->loader->loadChoiceList($this->value);
}
return $this->loadedList->getOriginalKeys();
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */

View File

@ -0,0 +1,144 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\ChoiceList;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
/**
* Adapts a legacy choice list implementation to {@link ChoiceListInterface}.
*
* @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated Added for backwards compatibility in Symfony 2.7, to be
* removed in Symfony 3.0.
*/
class LegacyChoiceListAdapter implements ChoiceListInterface
{
/**
* @var LegacyChoiceListInterface
*/
private $adaptedList;
/**
* @var array|null
*/
private $choices;
/**
* @var array|null
*/
private $values;
/**
* @var array|null
*/
private $structuredValues;
/**
* Adapts a legacy choice list to {@link ChoiceListInterface}.
*
* @param LegacyChoiceListInterface $adaptedList The adapted list
*/
public function __construct(LegacyChoiceListInterface $adaptedList)
{
$this->adaptedList = $adaptedList;
}
/**
* {@inheritdoc}
*/
public function getChoices()
{
if (!$this->choices) {
$this->initialize();
}
return $this->choices;
}
/**
* {@inheritdoc}
*/
public function getValues()
{
if (!$this->values) {
$this->initialize();
}
return $this->values;
}
/**
* {@inheritdoc}
*/
public function getStructuredValues()
{
if (!$this->structuredValues) {
$this->initialize();
}
return $this->structuredValues;
}
/**
* {@inheritdoc}
*/
public function getOriginalKeys()
{
if (!$this->structuredValues) {
$this->initialize();
}
return array_flip($this->structuredValues);
}
/**
* {@inheritdoc}
*/
public function getChoicesForValues(array $values)
{
return $this->adaptedList->getChoicesForValues($values);
}
/**
* {@inheritdoc}
*/
public function getValuesForChoices(array $choices)
{
return $this->adaptedList->getValuesForChoices($choices);
}
/**
* Returns the adapted choice list.
*
* @return LegacyChoiceListInterface The adapted list
*/
public function getAdaptedList()
{
return $this->adaptedList;
}
private function initialize()
{
$this->choices = array();
$this->values = array();
$this->structuredValues = $this->adaptedList->getValues();
$innerChoices = $this->adaptedList->getChoices();
foreach ($innerChoices as $index => $choice) {
$value = $this->structuredValues[$index];
$this->values[] = $value;
$this->choices[$value] = $choice;
}
}
}

View File

@ -11,9 +11,6 @@
namespace Symfony\Component\Form\Extension\Core\ChoiceList; namespace Symfony\Component\Form\Extension\Core\ChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface as BaseChoiceListInterface;
use Symfony\Component\Form\FormConfigBuilder;
/** /**
* Contains choices that can be selected in a form field. * Contains choices that can be selected in a form field.
* *
@ -30,10 +27,24 @@ use Symfony\Component\Form\FormConfigBuilder;
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
* *
* @deprecated since version 2.7, to be removed in 3.0. * @deprecated since version 2.7, to be removed in 3.0.
* Use {@link BaseChoiceListInterface} instead. * Use {@link \Symfony\Component\Form\ChoiceList\ChoiceListInterface} instead.
*/ */
interface ChoiceListInterface extends BaseChoiceListInterface interface ChoiceListInterface
{ {
/**
* Returns the list of choices.
*
* @return array The choices with their indices as keys
*/
public function getChoices();
/**
* Returns the values for the choices.
*
* @return array The values with the corresponding choice indices as keys
*/
public function getValues();
/** /**
* Returns the choice views of the preferred choices as nested array with * Returns the choice views of the preferred choices as nested array with
* the choice groups as top-level keys. * the choice groups as top-level keys.
@ -84,6 +95,37 @@ interface ChoiceListInterface extends BaseChoiceListInterface
*/ */
public function getRemainingViews(); public function getRemainingViews();
/**
* Returns the choices corresponding to the given values.
*
* The choices can have any data type.
*
* The choices must be returned with the same keys and in the same order
* as the corresponding values in the given array.
*
* @param array $values An array of choice values. Not existing values in
* this array are ignored
*
* @return array An array of choices with ascending, 0-based numeric keys
*/
public function getChoicesForValues(array $values);
/**
* Returns the values corresponding to the given choices.
*
* The values must be strings.
*
* The values must be returned with the same keys and in the same order
* as the corresponding choices in the given array.
*
* @param array $choices An array of choices. Not existing choices in this
* array are ignored
*
* @return array An array of choice values with ascending, 0-based numeric
* keys
*/
public function getValuesForChoices(array $choices);
/** /**
* Returns the indices corresponding to the given choices. * Returns the indices corresponding to the given choices.
* *

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator; use Symfony\Component\Form\ChoiceList\Factory\PropertyAccessDecorator;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
@ -27,6 +28,7 @@ use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents; use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface; use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface as LegacyChoiceListInterface;
use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener; use Symfony\Component\Form\Extension\Core\EventListener\MergeCollectionListener;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoiceToValueTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer; use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
@ -259,6 +261,10 @@ class ChoiceType extends AbstractType
if ($choiceList) { if ($choiceList) {
@trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED); @trigger_error('The "choice_list" option is deprecated since version 2.7 and will be removed in 3.0. Use "choice_loader" instead.', E_USER_DEPRECATED);
if ($choiceList instanceof LegacyChoiceListInterface) {
return new LegacyChoiceListAdapter($choiceList);
}
return $choiceList; return $choiceList;
} }
@ -338,7 +344,7 @@ class ChoiceType extends AbstractType
$resolver->setNormalizer('placeholder', $placeholderNormalizer); $resolver->setNormalizer('placeholder', $placeholderNormalizer);
$resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer); $resolver->setNormalizer('choice_translation_domain', $choiceTranslationDomainNormalizer);
$resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface')); $resolver->setAllowedTypes('choice_list', array('null', 'Symfony\Component\Form\ChoiceList\ChoiceListInterface', 'Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface'));
$resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable')); $resolver->setAllowedTypes('choices', array('null', 'array', '\Traversable'));
$resolver->setAllowedTypes('choice_translation_domain', array('null', 'bool', 'string')); $resolver->setAllowedTypes('choice_translation_domain', array('null', 'bool', 'string'));
$resolver->setAllowedTypes('choices_as_values', 'bool'); $resolver->setAllowedTypes('choices_as_values', 'bool');

View File

@ -146,6 +146,10 @@ class DateType extends AbstractType
// remove special characters unless the format was explicitly specified // remove special characters unless the format was explicitly specified
if (!is_string($options['format'])) { if (!is_string($options['format'])) {
// remove quoted strings first
$pattern = preg_replace('/\'[^\']+\'/', '', $pattern);
// remove remaining special chars
$pattern = preg_replace('/[^yMd]+/', '', $pattern); $pattern = preg_replace('/[^yMd]+/', '', $pattern);
} }

View File

@ -931,7 +931,7 @@ class Form implements \IteratorAggregate, FormInterface
$child->setParent($this); $child->setParent($this);
if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) { if (!$this->lockSetData && $this->defaultDataSet && !$this->config->getInheritData()) {
$iterator = new InheritDataAwareIterator(new \ArrayIterator(array($child))); $iterator = new InheritDataAwareIterator(new \ArrayIterator(array($child->getName() => $child)));
$iterator = new \RecursiveIteratorIterator($iterator); $iterator = new \RecursiveIteratorIterator($iterator);
$this->config->getDataMapper()->mapDataToForms($viewData, $iterator); $this->config->getDataMapper()->mapDataToForms($viewData, $iterator);
} }

View File

@ -200,27 +200,35 @@ class ResolvedFormType implements ResolvedFormTypeInterface
$this->innerType->setDefaultOptions($this->optionsResolver); $this->innerType->setDefaultOptions($this->optionsResolver);
$reflector = new \ReflectionMethod($this->innerType, 'setDefaultOptions'); if (method_exists($this->innerType, 'configureOptions')) {
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType'; $reflector = new \ReflectionMethod($this->innerType, 'setDefaultOptions');
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType';
$reflector = new \ReflectionMethod($this->innerType, 'configureOptions'); $reflector = new \ReflectionMethod($this->innerType, 'configureOptions');
$isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType'; $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType';
if ($isOldOverwritten && !$isNewOverwritten) { if ($isOldOverwritten && !$isNewOverwritten) {
@trigger_error(get_class($this->innerType).': The FormTypeInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeInterface with Symfony 3.0.', E_USER_DEPRECATED); @trigger_error(get_class($this->innerType).': The FormTypeInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeInterface with Symfony 3.0.', E_USER_DEPRECATED);
}
} else {
@trigger_error(get_class($this->innerType).': The FormTypeInterface::configureOptions() method will be added in Symfony 3.0. You should extend AbstractType or implement it in your classes.', E_USER_DEPRECATED);
} }
foreach ($this->typeExtensions as $extension) { foreach ($this->typeExtensions as $extension) {
$extension->setDefaultOptions($this->optionsResolver); $extension->setDefaultOptions($this->optionsResolver);
$reflector = new \ReflectionMethod($extension, 'setDefaultOptions'); if (method_exists($extension, 'configureOptions')) {
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension'; $reflector = new \ReflectionMethod($extension, 'setDefaultOptions');
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension';
$reflector = new \ReflectionMethod($extension, 'configureOptions'); $reflector = new \ReflectionMethod($extension, 'configureOptions');
$isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension'; $isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension';
if ($isOldOverwritten && !$isNewOverwritten) { if ($isOldOverwritten && !$isNewOverwritten) {
@trigger_error(get_class($extension).': The FormTypeExtensionInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeExtensionInterface with Symfony 3.0.', E_USER_DEPRECATED); @trigger_error(get_class($extension).': The FormTypeExtensionInterface::setDefaultOptions() method is deprecated since version 2.7 and will be removed in 3.0. Use configureOptions() instead. This method will be added to the FormTypeExtensionInterface with Symfony 3.0.', E_USER_DEPRECATED);
}
} else {
@trigger_error(get_class($this->innerType).': The FormTypeExtensionInterface::configureOptions() method will be added in Symfony 3.0. You should extend AbstractTypeExtension or implement it in your classes.', E_USER_DEPRECATED);
} }
} }
} }

View File

@ -231,6 +231,29 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
); );
} }
public function testSingleChoiceWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => false,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array('attr' => array('class' => 'my&class')),
'/select
[@name="name"]
[@class="my&class form-control"]
[not(@required)]
[
./option[@value="&a"][@selected="selected"][.="Choice&A"]
/following-sibling::option[@value="&b"][not(@selected)][.="Choice&B"]
]
[count(./option)=2]
'
);
}
public function testSingleChoiceAttributes() public function testSingleChoiceAttributes()
{ {
$form = $this->factory->createNamed('name', 'choice', '&a', array( $form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -359,7 +382,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
[@class="my&class form-control"] [@class="my&class form-control"]
[not(@required)] [not(@required)]
[ [
./option[@value=""][.="[trans][/trans]"] ./option[@value=""][.=""]
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
] ]
@ -383,7 +406,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
[@class="my&class form-control"] [@class="my&class form-control"]
[not(@required)] [not(@required)]
[ [
./option[@value=""][.="[trans][/trans]"] ./option[@value=""][.=""]
/following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/trans]"] /following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
] ]
@ -457,7 +480,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
[@class="my&class form-control"] [@class="my&class form-control"]
[@required="required"] [@required="required"]
[ [
./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"] ./option[@value=""][not(@selected)][not(@disabled)][.=""]
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
] ]
@ -629,6 +652,42 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
); );
} }
public function testSingleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => true,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="radio"]
[
./label
[.="Choice&A"]
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
]
]
/following-sibling::div
[@class="radio"]
[
./label
[.="Choice&B"]
[
./input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testSingleChoiceExpandedAttributes() public function testSingleChoiceExpandedAttributes()
{ {
$form = $this->factory->createNamed('name', 'choice', '&a', array( $form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -792,6 +851,52 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
); );
} }
public function testMultipleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'),
'multiple' => true,
'expanded' => true,
'required' => true,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./div
[@class="checkbox"]
[
./label
[.="Choice&A"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[.="Choice&B"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
]
]
/following-sibling::div
[@class="checkbox"]
[
./label
[.="Choice&C"]
[
./input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
]
]
/following-sibling::input[@type="hidden"][@id="name__token"][@class="form-control"]
]
'
);
}
public function testMultipleChoiceExpandedAttributes() public function testMultipleChoiceExpandedAttributes()
{ {
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(
@ -1278,17 +1383,17 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
./select ./select
[@id="name_month"] [@id="name_month"]
[@class="form-control"] [@class="form-control"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]] [./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]] [./option[@value="1"][@selected="selected"]]
/following-sibling::select /following-sibling::select
[@id="name_day"] [@id="name_day"]
[@class="form-control"] [@class="form-control"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]] [./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]] [./option[@value="1"][@selected="selected"]]
/following-sibling::select /following-sibling::select
[@id="name_year"] [@id="name_year"]
[@class="form-control"] [@class="form-control"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]] [./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1950"][@selected="selected"]] [./option[@value="1950"][@selected="selected"]]
] ]
[count(./select)=3] [count(./select)=3]

View File

@ -168,6 +168,20 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
); );
} }
public function testLabelWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'text', null, array(
'translation_domain' => false,
));
$this->assertMatchesXpath($this->renderLabel($form->createView()),
'/label
[@for="name"]
[.="Name"]
'
);
}
public function testLabelOnForm() public function testLabelOnForm()
{ {
$form = $this->factory->createNamed('name', 'date'); $form = $this->factory->createNamed('name', 'date');
@ -513,6 +527,28 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
); );
} }
public function testSingleChoiceWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => false,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/select
[@name="name"]
[not(@required)]
[
./option[@value="&a"][@selected="selected"][.="Choice&A"]
/following-sibling::option[@value="&b"][not(@selected)][.="Choice&B"]
]
[count(./option)=2]
'
);
}
public function testSingleChoiceAttributes() public function testSingleChoiceAttributes()
{ {
$form = $this->factory->createNamed('name', 'choice', '&a', array( $form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -635,7 +671,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
[@name="name"] [@name="name"]
[not(@required)] [not(@required)]
[ [
./option[@value=""][.="[trans][/trans]"] ./option[@value=""][.=""]
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
] ]
@ -658,7 +694,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
[@name="name"] [@name="name"]
[not(@required)] [not(@required)]
[ [
./option[@value=""][.="[trans][/trans]"] ./option[@value=""][.=""]
/following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/trans]"] /following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
] ]
@ -735,7 +771,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
[@name="name"] [@name="name"]
[@required="required"] [@required="required"]
[ [
./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"] ./option[@value=""][not(@selected)][not(@disabled)][.=""]
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"] /following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/trans]"]
/following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"] /following-sibling::option[@value="&b"][not(@selected)][.="[trans]Choice&B[/trans]"]
] ]
@ -889,6 +925,29 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
); );
} }
public function testSingleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B'),
'multiple' => false,
'expanded' => true,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="radio"][@name="name"][@id="name_0"][@value="&a"][@checked]
/following-sibling::label[@for="name_0"][.="Choice&A"]
/following-sibling::input[@type="radio"][@name="name"][@id="name_1"][@value="&b"][not(@checked)]
/following-sibling::label[@for="name_1"][.="Choice&B"]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=3]
'
);
}
public function testSingleChoiceExpandedAttributes() public function testSingleChoiceExpandedAttributes()
{ {
$form = $this->factory->createNamed('name', 'choice', '&a', array( $form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -986,6 +1045,32 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
); );
} }
public function testMultipleChoiceExpandedWithoutTranslation()
{
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(
'choices' => array('&a' => 'Choice&A', '&b' => 'Choice&B', '&c' => 'Choice&C'),
'multiple' => true,
'expanded' => true,
'required' => true,
'choice_translation_domain' => false,
));
$this->assertWidgetMatchesXpath($form->createView(), array(),
'/div
[
./input[@type="checkbox"][@name="name[]"][@id="name_0"][@checked][not(@required)]
/following-sibling::label[@for="name_0"][.="Choice&A"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_1"][not(@checked)][not(@required)]
/following-sibling::label[@for="name_1"][.="Choice&B"]
/following-sibling::input[@type="checkbox"][@name="name[]"][@id="name_2"][@checked][not(@required)]
/following-sibling::label[@for="name_2"][.="Choice&C"]
/following-sibling::input[@type="hidden"][@id="name__token"]
]
[count(./input)=4]
'
);
}
public function testMultipleChoiceExpandedAttributes() public function testMultipleChoiceExpandedAttributes()
{ {
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array( $form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(
@ -1442,15 +1527,15 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
[ [
./select ./select
[@id="name_month"] [@id="name_month"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]] [./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]] [./option[@value="1"][@selected="selected"]]
/following-sibling::select /following-sibling::select
[@id="name_day"] [@id="name_day"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]] [./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]] [./option[@value="1"][@selected="selected"]]
/following-sibling::select /following-sibling::select
[@id="name_year"] [@id="name_year"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]] [./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1950"][@selected="selected"]] [./option[@value="1950"][@selected="selected"]]
] ]
[count(./select)=3] [count(./select)=3]

View File

@ -31,6 +31,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
*/ */
protected $values; protected $values;
/**
* @var array
*/
protected $structuredValues;
/**
* @var array
*/
protected $keys;
/** /**
* @var mixed * @var mixed
*/ */
@ -71,25 +81,52 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
*/ */
protected $value4; protected $value4;
/**
* @var string
*/
protected $key1;
/**
* @var string
*/
protected $key2;
/**
* @var string
*/
protected $key3;
/**
* @var string
*/
protected $key4;
protected function setUp() protected function setUp()
{ {
parent::setUp(); parent::setUp();
$this->list = $this->createChoiceList(); $this->list = $this->createChoiceList();
$this->choices = $this->getChoices(); $choices = $this->getChoices();
$this->values = $this->getValues(); $this->values = $this->getValues();
$this->structuredValues = array_combine(array_keys($choices), $this->values);
$this->choices = array_combine($this->values, $choices);
$this->keys = array_combine($this->values, array_keys($choices));
// allow access to the individual entries without relying on their indices // allow access to the individual entries without relying on their indices
reset($this->choices); reset($this->choices);
reset($this->values); reset($this->values);
reset($this->keys);
for ($i = 1; $i <= 4; ++$i) { for ($i = 1; $i <= 4; ++$i) {
$this->{'choice'.$i} = current($this->choices); $this->{'choice'.$i} = current($this->choices);
$this->{'value'.$i} = current($this->values); $this->{'value'.$i} = current($this->values);
$this->{'key'.$i} = current($this->keys);
next($this->choices); next($this->choices);
next($this->values); next($this->values);
next($this->keys);
} }
} }
@ -103,6 +140,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame($this->values, $this->list->getValues()); $this->assertSame($this->values, $this->list->getValues());
} }
public function testGetStructuredValues()
{
$this->assertSame($this->values, $this->list->getStructuredValues());
}
public function testGetOriginalKeys()
{
$this->assertSame($this->keys, $this->list->getOriginalKeys());
}
public function testGetChoicesForValues() public function testGetChoicesForValues()
{ {
$values = array($this->value1, $this->value2); $values = array($this->value1, $this->value2);

View File

@ -29,8 +29,6 @@ class ArrayChoiceListTest extends AbstractChoiceListTest
protected function createChoiceList() protected function createChoiceList()
{ {
$i = 0;
return new ArrayChoiceList($this->getChoices()); return new ArrayChoiceList($this->getChoices());
} }
@ -60,11 +58,31 @@ class ArrayChoiceListTest extends AbstractChoiceListTest
$choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback); $choiceList = new ArrayChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback);
$this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues()); $this->assertSame(array(':foo', ':bar', ':baz'), $choiceList->getValues());
$this->assertSame(array(':foo' => 'foo', ':bar' => 'bar', ':baz' => 'baz'), $choiceList->getChoices());
$this->assertSame(array(':foo' => 2, ':bar' => 7, ':baz' => 10), $choiceList->getOriginalKeys());
$this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz'))); $this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz')));
$this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
} }
public function testCreateChoiceListWithGroupedChoices()
{
$choiceList = new ArrayChoiceList(array(
'Group 1' => array('A' => 'a', 'B' => 'b'),
'Group 2' => array('C' => 'c', 'D' => 'd'),
));
$this->assertSame(array('0', '1', '2', '3'), $choiceList->getValues());
$this->assertSame(array(
'Group 1' => array('A' => '0', 'B' => '1'),
'Group 2' => array('C' => '2', 'D' => '3'),
), $choiceList->getStructuredValues());
$this->assertSame(array(0 => 'a', 1 => 'b', 2 => 'c', 3 => 'd'), $choiceList->getChoices());
$this->assertSame(array(0 => 'A', 1 => 'B', 2 => 'C', 3 => 'D'), $choiceList->getOriginalKeys());
$this->assertSame(array(1 => 'a', 2 => 'b'), $choiceList->getChoicesForValues(array(1 => '0', 2 => '1')));
$this->assertSame(array(1 => '0', 2 => '1'), $choiceList->getValuesForChoices(array(1 => 'a', 2 => 'b')));
}
public function testCompareChoicesByIdentityByDefault() public function testCompareChoicesByIdentityByDefault()
{ {
$callback = function ($choice) { $callback = function ($choice) {

View File

@ -29,7 +29,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
protected function createChoiceList() protected function createChoiceList()
{ {
return new ArrayKeyChoiceList($this->getChoices()); return new ArrayKeyChoiceList(array_flip($this->getChoices()));
} }
protected function getChoices() protected function getChoices()
@ -44,9 +44,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
public function testUseChoicesAsValuesByDefault() public function testUseChoicesAsValuesByDefault()
{ {
$list = new ArrayKeyChoiceList(array(1 => '', 3 => 0, 7 => '1', 10 => 1.23)); $list = new ArrayKeyChoiceList(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float'));
$this->assertSame(array(1 => '', 3 => '0', 7 => '1', 10 => '1.23'), $list->getValues()); $this->assertSame(array('', '0', '1', '1.23'), $list->getValues());
$this->assertSame(array('' => '', 0 => 0, 1 => 1, '1.23' => '1.23'), $list->getChoices());
$this->assertSame(array('' => 'Empty', 0 => 'Zero', 1 => 'One', '1.23' => 'Float'), $list->getOriginalKeys());
} }
public function testNoChoices() public function testNoChoices()
@ -102,33 +104,22 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
public function provideConvertibleChoices() public function provideConvertibleChoices()
{ {
return array( return array(
array(array(0), array(0)), array(array(0 => 'Label'), array(0 => 0)),
array(array(1), array(1)), array(array(1 => 'Label'), array(1 => 1)),
array(array('0'), array(0)), array(array('1.23' => 'Label'), array('1.23' => '1.23')),
array(array('1'), array(1)), array(array('foobar' => 'Label'), array('foobar' => 'foobar')),
array(array('1.23'), array('1.23')),
array(array('foobar'), array('foobar')),
// The default value of choice fields is NULL. It should be treated // The default value of choice fields is NULL. It should be treated
// like the empty value for this choice list type // like the empty value for this choice list type
array(array(null), array('')), array(array(null => 'Label'), array('' => '')),
array(array(1.23), array('1.23')), array(array('1.23' => 'Label'), array('1.23' => '1.23')),
// Always cast booleans to 0 and 1, because: // Always cast booleans to 0 and 1, because:
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No') // array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean // see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
array(array(true), array(1)), array(array(true => 'Label'), array(1 => 1)),
array(array(false), array(0)), array(array(false => 'Label'), array(0 => 0)),
); );
} }
/**
* @dataProvider provideInvalidChoices
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
*/
public function testFailIfInvalidChoices(array $choices)
{
new ArrayKeyChoiceList($choices);
}
/** /**
* @dataProvider provideInvalidChoices * @dataProvider provideInvalidChoices
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException * @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
@ -155,7 +146,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
return $value; return $value;
}; };
$list = new ArrayKeyChoiceList(array('choice'), $callback); $list = new ArrayKeyChoiceList(array('choice' => 'Label'), $callback);
$this->assertSame(array($converted), $list->getValues()); $this->assertSame(array($converted), $list->getValues());
} }
@ -169,15 +160,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
array('1', '1'), array('1', '1'),
array('1.23', '1.23'), array('1.23', '1.23'),
array('foobar', 'foobar'), array('foobar', 'foobar'),
// The default value of choice fields is NULL. It should be treated array('', ''),
// like the empty value for this choice list type
array(null, ''),
array(1.23, '1.23'),
// Always cast booleans to 0 and 1, because:
// array(true => 'Yes', false => 'No') === array(1 => 'Yes', 0 => 'No')
// see ChoiceTypeTest::testSetDataSingleNonExpandedAcceptsBoolean
array(true, '1'),
array(false, ''),
); );
} }
@ -187,9 +170,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
return ':'.$choice; return ':'.$choice;
}; };
$choiceList = new ArrayKeyChoiceList(array(2 => 'foo', 7 => 'bar', 10 => 'baz'), $callback); $choiceList = new ArrayKeyChoiceList(array('foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz'), $callback);
$this->assertSame(array(2 => ':foo', 7 => ':bar', 10 => ':baz'), $choiceList->getValues()); $this->assertSame(array(':foo', ':bar', ':baz'), $choiceList->getValues());
$this->assertSame(array(':foo' => 'foo', ':bar' => 'bar', ':baz' => 'baz'), $choiceList->getChoices());
$this->assertSame(array(':foo' => 'Foo', ':bar' => 'Bar', ':baz' => 'Baz'), $choiceList->getOriginalKeys());
$this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz'))); $this->assertSame(array(1 => 'foo', 2 => 'baz'), $choiceList->getChoicesForValues(array(1 => ':foo', 2 => ':baz')));
$this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz'))); $this->assertSame(array(1 => ':foo', 2 => ':baz'), $choiceList->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
} }

View File

@ -204,8 +204,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase
*/ */
public function testCreateFromFlippedChoicesSameChoices($choice1, $choice2) public function testCreateFromFlippedChoicesSameChoices($choice1, $choice2)
{ {
$choices1 = array($choice1); $choices1 = array($choice1 => 'A');
$choices2 = array($choice2); $choices2 = array($choice2 => 'A');
$list = new \stdClass(); $list = new \stdClass();
$this->decoratedFactory->expects($this->once()) $this->decoratedFactory->expects($this->once())
@ -222,8 +222,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase
*/ */
public function testCreateFromFlippedChoicesDifferentChoices($choice1, $choice2) public function testCreateFromFlippedChoicesDifferentChoices($choice1, $choice2)
{ {
$choices1 = array($choice1); $choices1 = array($choice1 => 'A');
$choices2 = array($choice2); $choices2 = array($choice2 => 'A');
$list1 = new \stdClass(); $list1 = new \stdClass();
$list2 = new \stdClass(); $list2 = new \stdClass();

View File

@ -15,6 +15,7 @@ use Symfony\Component\Form\ChoiceList\ArrayChoiceList;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface; use Symfony\Component\Form\ChoiceList\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory; use Symfony\Component\Form\ChoiceList\Factory\DefaultChoiceListFactory;
use Symfony\Component\Form\ChoiceList\LazyChoiceList; use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView; use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView; use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
use Symfony\Component\Form\ChoiceList\View\ChoiceView; use Symfony\Component\Form\ChoiceList\View\ChoiceView;
@ -77,6 +78,13 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2'; return $this->obj1 === $object || $this->obj2 === $object ? 'Group 1' : 'Group 2';
} }
public function getGroupAsObject($object)
{
return $this->obj1 === $object || $this->obj2 === $object
? new DefaultChoiceListFactoryTest_Castable('Group 1')
: new DefaultChoiceListFactoryTest_Castable('Group 2');
}
protected function setUp() protected function setUp()
{ {
$this->obj1 = (object) array('label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => array()); $this->obj1 = (object) array('label' => 'A', 'index' => 'w', 'value' => 'a', 'preferred' => false, 'group' => 'Group 1', 'attr' => array());
@ -199,7 +207,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D') array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D')
); );
$this->assertScalarListWithGeneratedValues($list); $this->assertScalarListWithChoiceValues($list);
} }
public function testCreateFromFlippedChoicesFlatTraversable() public function testCreateFromFlippedChoicesFlatTraversable()
@ -208,7 +216,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
new \ArrayIterator(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D')) new \ArrayIterator(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'))
); );
$this->assertScalarListWithGeneratedValues($list); $this->assertScalarListWithChoiceValues($list);
} }
public function testCreateFromFlippedChoicesFlatValuesAsCallable() public function testCreateFromFlippedChoicesFlatValuesAsCallable()
@ -247,7 +255,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
) )
); );
$this->assertScalarListWithGeneratedValues($list); $this->assertScalarListWithChoiceValues($list);
} }
public function testCreateFromFlippedChoicesGroupedTraversable() public function testCreateFromFlippedChoicesGroupedTraversable()
@ -259,7 +267,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
)) ))
); );
$this->assertScalarListWithGeneratedValues($list); $this->assertScalarListWithChoiceValues($list);
} }
public function testCreateFromFlippedChoicesGroupedValuesAsCallable() public function testCreateFromFlippedChoicesGroupedValuesAsCallable()
@ -523,33 +531,16 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertFlatViewWithCustomIndices($view); $this->assertFlatViewWithCustomIndices($view);
} }
public function testCreateViewFlatGroupByAsArray() public function testCreateViewFlatGroupByOriginalStructure()
{ {
$view = $this->factory->createView( $list = new ArrayChoiceList(array(
$this->list, 'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
array($this->obj2, $this->obj3), 'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
null, // label ));
null, // index
array(
'Group 1' => array('A' => true, 'B' => true),
'Group 2' => array('C' => true, 'D' => true),
)
);
$this->assertGroupedView($view);
}
public function testCreateViewFlatGroupByAsTraversable()
{
$view = $this->factory->createView( $view = $this->factory->createView(
$this->list, $list,
array($this->obj2, $this->obj3), array($this->obj2, $this->obj3)
null, // label
null, // index
new \ArrayIterator(array(
'Group 1' => array('A' => true, 'B' => true),
'Group 2' => array('C' => true, 'D' => true),
))
); );
$this->assertGroupedView($view); $this->assertGroupedView($view);
@ -581,6 +572,19 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertGroupedView($view); $this->assertGroupedView($view);
} }
public function testCreateViewFlatGroupByObjectThatCanBeCastToString()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
null, // label
null, // index
array($this, 'getGroupAsObject')
);
$this->assertGroupedView($view);
}
public function testCreateViewFlatGroupByAsClosure() public function testCreateViewFlatGroupByAsClosure()
{ {
$obj1 = $this->obj1; $obj1 = $this->obj1;
@ -592,8 +596,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
null, // label null, // label
null, // index null, // index
function ($object) use ($obj1, $obj2) { function ($object) use ($obj1, $obj2) {
return $obj1 === $object || $obj2 === $object ? 'Group 1' return $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2';
: 'Group 2';
} }
); );
@ -749,78 +752,86 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
->method('getRemainingViews') ->method('getRemainingViews')
->will($this->returnValue($other)); ->will($this->returnValue($other));
$view = $this->factory->createView($list); $view = $this->factory->createView(new LegacyChoiceListAdapter($list));
$this->assertEquals(array(new ChoiceView('y', 'y', 'Other')), $view->choices); $this->assertEquals(array(new ChoiceView('y', 'y', 'Other')), $view->choices);
$this->assertEquals(array(new ChoiceView('x', 'x', 'Preferred')), $view->preferredChoices); $this->assertEquals(array(new ChoiceView('x', 'x', 'Preferred')), $view->preferredChoices);
} }
private function assertScalarListWithGeneratedValues(ChoiceListInterface $list) private function assertScalarListWithChoiceValues(ChoiceListInterface $list)
{ {
$this->assertSame(array('a', 'b', 'c', 'd'), $list->getValues());
$this->assertSame(array( $this->assertSame(array(
'A' => 'a', 'a' => 'a',
'B' => 'b', 'b' => 'b',
'C' => 'c', 'c' => 'c',
'D' => 'd', 'd' => 'd',
), $list->getChoices()); ), $list->getChoices());
$this->assertSame(array( $this->assertSame(array(
'A' => 'a', 'a' => 'A',
'B' => 'b', 'b' => 'B',
'C' => 'c', 'c' => 'C',
'D' => 'd', 'd' => 'D',
), $list->getValues()); ), $list->getOriginalKeys());
} }
private function assertObjectListWithGeneratedValues(ChoiceListInterface $list) private function assertObjectListWithGeneratedValues(ChoiceListInterface $list)
{ {
$this->assertSame(array('0', '1', '2', '3'), $list->getValues());
$this->assertSame(array( $this->assertSame(array(
'A' => $this->obj1, 0 => $this->obj1,
'B' => $this->obj2, 1 => $this->obj2,
'C' => $this->obj3, 2 => $this->obj3,
'D' => $this->obj4, 3 => $this->obj4,
), $list->getChoices()); ), $list->getChoices());
$this->assertSame(array( $this->assertSame(array(
'A' => '0', 0 => 'A',
'B' => '1', 1 => 'B',
'C' => '2', 2 => 'C',
'D' => '3', 3 => 'D',
), $list->getValues()); ), $list->getOriginalKeys());
} }
private function assertScalarListWithCustomValues(ChoiceListInterface $list) private function assertScalarListWithCustomValues(ChoiceListInterface $list)
{ {
$this->assertSame(array('a', 'b', '1', '2'), $list->getValues());
$this->assertSame(array( $this->assertSame(array(
'A' => 'a', 'a' => 'a',
'B' => 'b', 'b' => 'b',
'C' => 'c', 1 => 'c',
'D' => 'd', 2 => 'd',
), $list->getChoices()); ), $list->getChoices());
$this->assertSame(array( $this->assertSame(array(
'A' => 'a', 'a' => 'A',
'B' => 'b', 'b' => 'B',
'C' => '1', 1 => 'C',
'D' => '2', 2 => 'D',
), $list->getValues()); ), $list->getOriginalKeys());
} }
private function assertObjectListWithCustomValues(ChoiceListInterface $list) private function assertObjectListWithCustomValues(ChoiceListInterface $list)
{ {
$this->assertSame(array('a', 'b', '1', '2'), $list->getValues());
$this->assertSame(array( $this->assertSame(array(
'A' => $this->obj1, 'a' => $this->obj1,
'B' => $this->obj2, 'b' => $this->obj2,
'C' => $this->obj3, 1 => $this->obj3,
'D' => $this->obj4, 2 => $this->obj4,
), $list->getChoices()); ), $list->getChoices());
$this->assertSame(array( $this->assertSame(array(
'A' => 'a', 'a' => 'A',
'B' => 'b', 'b' => 'B',
'C' => '1', 1 => 'C',
'D' => '2', 2 => 'D',
), $list->getValues()); ), $list->getOriginalKeys());
} }
private function assertFlatView($view) private function assertFlatView($view)
@ -897,3 +908,18 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
), $view); ), $view);
} }
} }
class DefaultChoiceListFactoryTest_Castable
{
private $property;
public function __construct($property)
{
$this->property = $property;
}
public function __toString()
{
return $this->property;
}
}

View File

@ -73,6 +73,36 @@ class LazyChoiceListTest extends \PHPUnit_Framework_TestCase
$this->assertSame('RESULT', $this->list->getValues()); $this->assertSame('RESULT', $this->list->getValues());
} }
public function testGetStructuredValuesLoadsInnerListOnFirstCall()
{
$this->loader->expects($this->once())
->method('loadChoiceList')
->with($this->value)
->will($this->returnValue($this->innerList));
$this->innerList->expects($this->exactly(2))
->method('getStructuredValues')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->list->getStructuredValues());
$this->assertSame('RESULT', $this->list->getStructuredValues());
}
public function testGetOriginalKeysLoadsInnerListOnFirstCall()
{
$this->loader->expects($this->once())
->method('loadChoiceList')
->with($this->value)
->will($this->returnValue($this->innerList));
$this->innerList->expects($this->exactly(2))
->method('getOriginalKeys')
->will($this->returnValue('RESULT'));
$this->assertSame('RESULT', $this->list->getOriginalKeys());
$this->assertSame('RESULT', $this->list->getOriginalKeys());
}
public function testGetChoicesForValuesForwardsCallIfListNotLoaded() public function testGetChoicesForValuesForwardsCallIfListNotLoaded()
{ {
$this->loader->expects($this->exactly(2)) $this->loader->expects($this->exactly(2))

View File

@ -0,0 +1,110 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\Form\Tests\ChoiceList;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface;
/**
* @author Bernhard Schussek <bschussek@gmail.com>
*/
class LegacyChoiceListAdapterTest extends \PHPUnit_Framework_TestCase
{
/**
* @var LegacyChoiceListAdapter
*/
private $list;
/**
* @var \PHPUnit_Framework_MockObject_MockObject|ChoiceListInterface
*/
private $adaptedList;
protected function setUp()
{
$this->adaptedList = $this->getMock('Symfony\Component\Form\Extension\Core\ChoiceList\ChoiceListInterface');
$this->list = new LegacyChoiceListAdapter($this->adaptedList);
}
public function testGetChoices()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(':a' => 'a', ':b' => 'b', ':c' => 'c'), $this->list->getChoices());
}
public function testGetValues()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(':a', ':b', ':c'), $this->list->getValues());
}
public function testGetStructuredValues()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(1 => ':a', 4 => ':b', 7 => ':c'), $this->list->getStructuredValues());
}
public function testGetOriginalKeys()
{
$this->adaptedList->expects($this->once())
->method('getChoices')
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->adaptedList->expects($this->once())
->method('getValues')
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(':a' => 1, ':b' => 4, ':c' => 7), $this->list->getOriginalKeys());
}
public function testGetChoicesForValues()
{
$this->adaptedList->expects($this->once())
->method('getChoicesForValues')
->with(array(1 => ':a', 4 => ':b', 7 => ':c'))
->willReturn(array(1 => 'a', 4 => 'b', 7 => 'c'));
$this->assertSame(array(1 => 'a', 4 => 'b', 7 => 'c'), $this->list->getChoicesForValues(array(1 => ':a', 4 => ':b', 7 => ':c')));
}
public function testGetValuesForChoices()
{
$this->adaptedList->expects($this->once())
->method('getValuesForChoices')
->with(array(1 => 'a', 4 => 'b', 7 => 'c'))
->willReturn(array(1 => ':a', 4 => ':b', 7 => ':c'));
$this->assertSame(array(1 => ':a', 4 => ':b', 7 => ':c'), $this->list->getValuesForChoices(array(1 => 'a', 4 => 'b', 7 => 'c')));
}
public function testGetAdaptedList()
{
$this->assertSame($this->adaptedList, $this->list->getAdaptedList());
}
}

View File

@ -350,7 +350,7 @@ class CompoundFormTest extends AbstractFormTest
->with('bar', $this->isInstanceOf('\RecursiveIteratorIterator')) ->with('bar', $this->isInstanceOf('\RecursiveIteratorIterator'))
->will($this->returnCallback(function ($data, \RecursiveIteratorIterator $iterator) use ($child, $test) { ->will($this->returnCallback(function ($data, \RecursiveIteratorIterator $iterator) use ($child, $test) {
$test->assertInstanceOf('Symfony\Component\Form\Util\InheritDataAwareIterator', $iterator->getInnerIterator()); $test->assertInstanceOf('Symfony\Component\Form\Util\InheritDataAwareIterator', $iterator->getInnerIterator());
$test->assertSame(array($child), iterator_to_array($iterator)); $test->assertSame(array($child->getName() => $child), iterator_to_array($iterator));
})); }));
$form->initialize(); $form->initialize();

View File

@ -15,6 +15,6 @@ abstract class DateTimeTestCase extends \PHPUnit_Framework_TestCase
{ {
public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual) public static function assertDateTimeEquals(\DateTime $expected, \DateTime $actual)
{ {
self::assertEquals($expected->format('c'), $actual->format('c')); self::assertEquals($expected->format('U'), $actual->format('U'));
} }
} }

View File

@ -11,9 +11,9 @@
namespace Symfony\Component\Form\Tests\Extension\Core\EventListener; namespace Symfony\Component\Form\Tests\Extension\Core\EventListener;
use Symfony\Component\Form\ChoiceList\ArrayKeyChoiceList;
use Symfony\Component\Form\FormEvent; use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener; use Symfony\Component\Form\Extension\Core\EventListener\FixRadioInputListener;
use Symfony\Component\Form\Extension\Core\ChoiceList\SimpleChoiceList;
/** /**
* @group legacy * @group legacy
@ -26,7 +26,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
{ {
parent::setUp(); parent::setUp();
$this->choiceList = new SimpleChoiceList(array('' => 'Empty', 0 => 'A', 1 => 'B')); $this->choiceList = new ArrayKeyChoiceList(array('' => 'Empty', 0 => 'A', 1 => 'B'));
} }
protected function tearDown() protected function tearDown()
@ -45,7 +45,6 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$listener = new FixRadioInputListener($this->choiceList, true); $listener = new FixRadioInputListener($this->choiceList, true);
$listener->preSubmit($event); $listener->preSubmit($event);
// Indices in SimpleChoiceList are zero-based generated integers
$this->assertEquals(array(2 => '1'), $event->getData()); $this->assertEquals(array(2 => '1'), $event->getData());
} }
@ -58,7 +57,6 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$listener = new FixRadioInputListener($this->choiceList, true); $listener = new FixRadioInputListener($this->choiceList, true);
$listener->preSubmit($event); $listener->preSubmit($event);
// Indices in SimpleChoiceList are zero-based generated integers
$this->assertEquals(array(1 => '0'), $event->getData()); $this->assertEquals(array(1 => '0'), $event->getData());
} }
@ -71,13 +69,12 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
$listener = new FixRadioInputListener($this->choiceList, true); $listener = new FixRadioInputListener($this->choiceList, true);
$listener->preSubmit($event); $listener->preSubmit($event);
// Indices in SimpleChoiceList are zero-based generated integers
$this->assertEquals(array(0 => ''), $event->getData()); $this->assertEquals(array(0 => ''), $event->getData());
} }
public function testConvertEmptyStringToPlaceholderIfNotFound() public function testConvertEmptyStringToPlaceholderIfNotFound()
{ {
$list = new SimpleChoiceList(array(0 => 'A', 1 => 'B')); $list = new ArrayKeyChoiceList(array(0 => 'A', 1 => 'B'));
$data = ''; $data = '';
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); $form = $this->getMock('Symfony\Component\Form\Test\FormInterface');
@ -91,7 +88,7 @@ class FixRadioInputListenerTest extends \PHPUnit_Framework_TestCase
public function testDontConvertEmptyStringToPlaceholderIfNoPlaceholderUsed() public function testDontConvertEmptyStringToPlaceholderIfNoPlaceholderUsed()
{ {
$list = new SimpleChoiceList(array(0 => 'A', 1 => 'B')); $list = new ArrayKeyChoiceList(array(0 => 'A', 1 => 'B'));
$data = ''; $data = '';
$form = $this->getMock('Symfony\Component\Form\Test\FormInterface'); $form = $this->getMock('Symfony\Component\Form\Test\FormInterface');

View File

@ -30,7 +30,7 @@ class ChoiceTypePerformanceTest extends FormPerformanceTestCase
$choices = range(1, 300); $choices = range(1, 300);
for ($i = 0; $i < 100; ++$i) { for ($i = 0; $i < 100; ++$i) {
$this->factory->create('choice', rand(1, 400), array( $this->factory->create('choice', mt_rand(1, 400), array(
'choices' => $choices, 'choices' => $choices,
)); ));
} }

View File

@ -186,6 +186,32 @@ class ChoiceTypeTest extends \Symfony\Component\Form\Test\TypeTestCase
} }
} }
public function testExpandedChoicesOptionsAreFlattenedObjectChoices()
{
$obj1 = (object) array('id' => 1, 'name' => 'Bernhard');
$obj2 = (object) array('id' => 2, 'name' => 'Fabien');
$obj3 = (object) array('id' => 3, 'name' => 'Kris');
$obj4 = (object) array('id' => 4, 'name' => 'Jon');
$obj5 = (object) array('id' => 5, 'name' => 'Roman');
$form = $this->factory->create('choice', null, array(
'expanded' => true,
'choices' => array(
'Symfony' => array($obj1, $obj2, $obj3),
'Doctrine' => array($obj4, $obj5),
),
'choices_as_values' => true,
'choice_name' => 'id',
));
$this->assertSame(5, $form->count(), 'Each nested choice should become a new field, not the groups');
$this->assertTrue($form->has(1));
$this->assertTrue($form->has(2));
$this->assertTrue($form->has(3));
$this->assertTrue($form->has(4));
$this->assertTrue($form->has(5));
}
public function testExpandedCheckboxesAreNeverRequired() public function testExpandedCheckboxesAreNeverRequired()
{ {
$form = $this->factory->create('choice', null, array( $form = $this->factory->create('choice', null, array(

View File

@ -627,6 +627,20 @@ class DateTypeTest extends TestCase
$this->assertFalse(isset($view->vars['date_pattern'])); $this->assertFalse(isset($view->vars['date_pattern']));
} }
public function testDatePatternFormatWithQuotedStrings()
{
\Locale::setDefault('es_ES');
$form = $this->factory->create('date', null, array(
// EEEE, d 'de' MMMM 'de' y
'format' => \IntlDateFormatter::FULL,
));
$view = $form->createView();
$this->assertEquals('{{ day }}{{ month }}{{ year }}', $view->vars['date_pattern']);
}
public function testPassWidgetToView() public function testPassWidgetToView()
{ {
$form = $this->factory->create('date', null, array( $form = $this->factory->create('date', null, array(

View File

@ -95,34 +95,58 @@ class JsonResponse extends Response
*/ */
public function setData($data = array()) public function setData($data = array())
{ {
$errorHandler = null; if (defined('HHVM_VERSION')) {
$errorHandler = set_error_handler(function () use (&$errorHandler) { // HHVM does not trigger any warnings and let exceptions
if (JSON_ERROR_NONE !== json_last_error()) { // thrown from a JsonSerializable object pass through.
return; // If only PHP did the same...
$data = json_encode($data, $this->encodingOptions);
} else {
try {
if (PHP_VERSION_ID < 50400) {
// PHP 5.3 triggers annoying warnings for some
// types that can't be serialized as JSON (INF, resources, etc.)
// but doesn't provide the JsonSerializable interface.
set_error_handler('var_dump', 0);
$data = @json_encode($data, $this->encodingOptions);
} else {
// PHP 5.4 and up wrap exceptions thrown by JsonSerializable
// objects in a new exception that needs to be removed.
// Fortunately, PHP 5.5 and up do not trigger any warning anymore.
if (PHP_VERSION_ID < 50500) {
// Clear json_last_error()
json_encode(null);
$errorHandler = set_error_handler('var_dump');
restore_error_handler();
set_error_handler(function () use ($errorHandler) {
if (JSON_ERROR_NONE === json_last_error()) {
return $errorHandler && false !== call_user_func_array($errorHandler, func_get_args());
}
});
}
$data = json_encode($data, $this->encodingOptions);
}
if (PHP_VERSION_ID < 50500) {
restore_error_handler();
}
} catch (\Exception $e) {
if (PHP_VERSION_ID < 50500) {
restore_error_handler();
}
if (PHP_VERSION_ID >= 50400 && 'Exception' === get_class($e) && 0 === strpos($e->getMessage(), 'Failed calling ')) {
throw $e->getPrevious() ?: $e;
}
throw $e;
} }
if ($errorHandler) {
call_user_func_array($errorHandler, func_get_args());
}
});
try {
// Clear json_last_error()
json_encode(null);
$this->data = json_encode($data, $this->encodingOptions);
restore_error_handler();
} catch (\Exception $exception) {
restore_error_handler();
throw $exception;
} }
if (JSON_ERROR_NONE !== json_last_error()) { if (JSON_ERROR_NONE !== json_last_error()) {
throw new \InvalidArgumentException($this->transformJsonError()); throw new \InvalidArgumentException($this->transformJsonError());
} }
$this->data = $data;
return $this->update(); return $this->update();
} }

View File

@ -517,10 +517,17 @@ class Request
*/ */
public function __toString() public function __toString()
{ {
$content = '';
try {
$content = $this->getContent();
} catch (\LogicException $e) {
return trigger_error($e, E_USER_ERROR);
}
return return
sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n". sprintf('%s %s %s', $this->getMethod(), $this->getRequestUri(), $this->server->get('SERVER_PROTOCOL'))."\r\n".
$this->headers."\r\n". $this->headers."\r\n".
$this->getContent(); $content;
} }
/** /**

View File

@ -1247,15 +1247,9 @@ class Response
{ {
$status = ob_get_status(true); $status = ob_get_status(true);
$level = count($status); $level = count($status);
$flags = PHP_VERSION_ID >= 50400 ? PHP_OUTPUT_HANDLER_REMOVABLE | ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE) : -1;
while ($level-- > $targetLevel while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || $flags === ($s['flags'] & $flags) : $s['del'])) {
&& (!empty($status[$level]['del'])
|| (isset($status[$level]['flags'])
&& ($status[$level]['flags'] & PHP_OUTPUT_HANDLER_REMOVABLE)
&& ($status[$level]['flags'] & ($flush ? PHP_OUTPUT_HANDLER_FLUSHABLE : PHP_OUTPUT_HANDLER_CLEANABLE))
)
)
) {
if ($flush) { if ($flush) {
ob_end_flush(); ob_end_flush();
} else { } else {

View File

@ -203,9 +203,8 @@ class JsonResponseTest extends \PHPUnit_Framework_TestCase
} }
/** /**
* @expectedException Exception * @expectedException \Exception
* @expectedExceptionMessage Failed calling Symfony\Component\HttpFoundation\Tests\JsonSerializableObject::jsonSerialize() * @expectedExceptionMessage This error is expected
* @link http://php.net/manual/en/jsonserializable.jsonserialize.php#114688
*/ */
public function testSetContentJsonSerializeError() public function testSetContentJsonSerializeError()
{ {
@ -224,9 +223,7 @@ if (interface_exists('JsonSerializable')) {
{ {
public function jsonSerialize() public function jsonSerialize()
{ {
trigger_error('This error is expected', E_USER_WARNING); throw new \Exception('This error is expected');
return array();
} }
} }
} }

View File

@ -118,7 +118,7 @@ class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
$bag->clearCookie('foo'); $bag->clearCookie('foo');
$this->assertContains('Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; httponly', explode("\r\n", $bag->__toString())); $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; httponly#m', $bag->__toString());
} }
public function testClearCookieSecureNotHttpOnly() public function testClearCookieSecureNotHttpOnly()
@ -127,7 +127,7 @@ class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
$bag->clearCookie('foo', '/', null, true, false); $bag->clearCookie('foo', '/', null, true, false);
$this->assertContains("Set-Cookie: foo=deleted; expires=".gmdate("D, d-M-Y H:i:s T", time() - 31536001)."; path=/; secure", explode("\r\n", $bag->__toString())); $this->assertRegExp('#^Set-Cookie: foo=deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; path=/; secure#m', $bag->__toString());
} }
public function testReplace() public function testReplace()

View File

@ -216,7 +216,6 @@ class NativeSessionStorageTest extends \PHPUnit_Framework_TestCase
{ {
$storage = $this->getStorage(); $storage = $this->getStorage();
$this->assertFalse(isset($_SESSION));
$this->assertFalse($storage->getSaveHandler()->isActive()); $this->assertFalse($storage->getSaveHandler()->isActive());
$this->assertFalse($storage->isStarted()); $this->assertFalse($storage->isStarted());

View File

@ -91,7 +91,6 @@ class PhpBridgeSessionStorageTest extends \PHPUnit_Framework_TestCase
$storage = $this->getStorage(); $storage = $this->getStorage();
$this->assertFalse(isset($_SESSION));
$this->assertFalse($storage->getSaveHandler()->isActive()); $this->assertFalse($storage->getSaveHandler()->isActive());
$this->assertFalse($storage->isStarted()); $this->assertFalse($storage->isStarted());

View File

@ -123,7 +123,7 @@ class FullTransformer
// handle unimplemented characters // handle unimplemented characters
if (false !== strpos($this->notImplementedChars, $dateChars[0])) { if (false !== strpos($this->notImplementedChars, $dateChars[0])) {
throw new NotImplementedException(sprintf("Unimplemented date character '%s' in format '%s'", $dateChars[0], $this->pattern)); throw new NotImplementedException(sprintf('Unimplemented date character "%s" in format "%s"', $dateChars[0], $this->pattern));
} }
} }

View File

@ -39,7 +39,7 @@ class JsonBundleWriterTest extends \PHPUnit_Framework_TestCase
} }
$this->writer = new JsonBundleWriter(); $this->writer = new JsonBundleWriter();
$this->directory = sys_get_temp_dir().'/JsonBundleWriterTest/'.rand(1000, 9999); $this->directory = sys_get_temp_dir().'/JsonBundleWriterTest/'.mt_rand(1000, 9999);
$this->filesystem = new Filesystem(); $this->filesystem = new Filesystem();
$this->filesystem->mkdir($this->directory); $this->filesystem->mkdir($this->directory);

View File

@ -35,7 +35,7 @@ class PhpBundleWriterTest extends \PHPUnit_Framework_TestCase
protected function setUp() protected function setUp()
{ {
$this->writer = new PhpBundleWriter(); $this->writer = new PhpBundleWriter();
$this->directory = sys_get_temp_dir().'/PhpBundleWriterTest/'.rand(1000, 9999); $this->directory = sys_get_temp_dir().'/PhpBundleWriterTest/'.mt_rand(1000, 9999);
$this->filesystem = new Filesystem(); $this->filesystem = new Filesystem();
$this->filesystem->mkdir($this->directory); $this->filesystem->mkdir($this->directory);

View File

@ -36,7 +36,7 @@ class TextBundleWriterTest extends \PHPUnit_Framework_TestCase
protected function setUp() protected function setUp()
{ {
$this->writer = new TextBundleWriter(); $this->writer = new TextBundleWriter();
$this->directory = sys_get_temp_dir().'/TextBundleWriterTest/'.rand(1000, 9999); $this->directory = sys_get_temp_dir().'/TextBundleWriterTest/'.mt_rand(1000, 9999);
$this->filesystem = new Filesystem(); $this->filesystem = new Filesystem();
$this->filesystem->mkdir($this->directory); $this->filesystem->mkdir($this->directory);

View File

@ -33,7 +33,7 @@ class LocaleScannerTest extends \PHPUnit_Framework_TestCase
protected function setUp() protected function setUp()
{ {
$this->directory = sys_get_temp_dir().'/LocaleScannerTest/'.rand(1000, 9999); $this->directory = sys_get_temp_dir().'/LocaleScannerTest/'.mt_rand(1000, 9999);
$this->filesystem = new Filesystem(); $this->filesystem = new Filesystem();
$this->scanner = new LocaleScanner(); $this->scanner = new LocaleScanner();

View File

@ -37,7 +37,7 @@ class PhpExecutableFinder
{ {
// HHVM support // HHVM support
if (defined('HHVM_VERSION')) { if (defined('HHVM_VERSION')) {
return (false !== ($hhvm = getenv('PHP_BINARY')) ? $hhvm : PHP_BINARY).($includeArgs ? ' '.implode(' ', $this->findArguments()) : ''); return (getenv('PHP_BINARY') ?: PHP_BINARY).($includeArgs ? ' '.implode(' ', $this->findArguments()) : '');
} }
// PHP_BINARY return the current sapi executable // PHP_BINARY return the current sapi executable

View File

@ -11,21 +11,30 @@
namespace Symfony\Component\Process\Tests; namespace Symfony\Component\Process\Tests;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\LogicException; use Symfony\Component\Process\Exception\LogicException;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
use Symfony\Component\Process\Exception\RuntimeException;
use Symfony\Component\Process\PhpExecutableFinder;
use Symfony\Component\Process\Pipes\PipesInterface; use Symfony\Component\Process\Pipes\PipesInterface;
use Symfony\Component\Process\Process; use Symfony\Component\Process\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/** /**
* @author Robert Schönthal <seroscho@googlemail.com> * @author Robert Schönthal <seroscho@googlemail.com>
*/ */
abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{ {
protected static $phpBin;
public static function setUpBeforeClass()
{
$phpBin = new PhpExecutableFinder();
self::$phpBin = $phpBin->find();
}
public function testThatProcessDoesNotThrowWarningDuringRun() public function testThatProcessDoesNotThrowWarningDuringRun()
{ {
@trigger_error('Test Error', E_USER_NOTICE); @trigger_error('Test Error', E_USER_NOTICE);
$process = $this->getProcess("php -r 'sleep(3)'"); $process = $this->getProcess(self::$phpBin." -r 'sleep(3)'");
$process->run(); $process->run();
$actualError = error_get_last(); $actualError = error_get_last();
$this->assertEquals('Test Error', $actualError['message']); $this->assertEquals('Test Error', $actualError['message']);
@ -95,7 +104,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2; $expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
$code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize); $code = sprintf('echo str_repeat(\'*\', %d);', $expectedOutputSize);
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
$p->start(); $p->start();
// Let's wait enough time for process to finish... // Let's wait enough time for process to finish...
@ -134,7 +143,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testProcessResponses($expected, $getter, $code) public function testProcessResponses($expected, $getter, $code)
{ {
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
$p->run(); $p->run();
$this->assertSame($expected, $p->$getter()); $this->assertSame($expected, $p->$getter());
@ -150,7 +159,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$expected = str_repeat(str_repeat('*', 1024), $size).'!'; $expected = str_repeat(str_repeat('*', 1024), $size).'!';
$expectedLength = (1024 * $size) + 1; $expectedLength = (1024 * $size) + 1;
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
$p->setInput($expected); $p->setInput($expected);
$p->run(); $p->run();
@ -170,7 +179,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
fwrite($stream, $expected); fwrite($stream, $expected);
rewind($stream); rewind($stream);
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
$p->setInput($stream); $p->setInput($stream);
$p->run(); $p->run();
@ -182,7 +191,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testSetInputWhileRunningThrowsAnException() public function testSetInputWhileRunningThrowsAnException()
{ {
$process = $this->getProcess('php -r "usleep(500000);"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$process->start(); $process->start();
try { try {
$process->setInput('foobar'); $process->setInput('foobar');
@ -201,7 +210,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testInvalidInput($value) public function testInvalidInput($value)
{ {
$process = $this->getProcess('php -v'); $process = $this->getProcess(self::$phpBin.' -v');
$process->setInput($value); $process->setInput($value);
} }
@ -218,7 +227,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testValidInput($expected, $value) public function testValidInput($expected, $value)
{ {
$process = $this->getProcess('php -v'); $process = $this->getProcess(self::$phpBin.' -v');
$process->setInput($value); $process->setInput($value);
$this->assertSame($expected, $process->getInput()); $this->assertSame($expected, $process->getInput());
} }
@ -238,7 +247,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testLegacyValidInput($expected, $value) public function testLegacyValidInput($expected, $value)
{ {
$process = $this->getProcess('php -v'); $process = $this->getProcess(self::$phpBin.' -v');
$process->setInput($value); $process->setInput($value);
$this->assertSame($expected, $process->getInput()); $this->assertSame($expected, $process->getInput());
} }
@ -276,7 +285,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testCallbackIsExecutedForOutput() public function testCallbackIsExecutedForOutput()
{ {
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('echo \'foo\';'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('echo \'foo\';')));
$called = false; $called = false;
$p->run(function ($type, $buffer) use (&$called) { $p->run(function ($type, $buffer) use (&$called) {
@ -288,7 +297,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetErrorOutput() public function testGetErrorOutput()
{ {
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
$p->run(); $p->run();
$this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches)); $this->assertEquals(3, preg_match_all('/ERROR/', $p->getErrorOutput(), $matches));
@ -301,7 +310,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
file_put_contents($lock, 'W'); file_put_contents($lock, 'W');
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
$p->start(); $p->start();
while ($p->isRunning()) { while ($p->isRunning()) {
@ -317,7 +326,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testFlushErrorOutput() public function testFlushErrorOutput()
{ {
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; }')));
$p->run(); $p->run();
$p->clearErrorOutput(); $p->clearErrorOutput();
@ -331,7 +340,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
file_put_contents($lock, 'W'); file_put_contents($lock, 'W');
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { file_put_contents(\'php://stderr\', \'ERROR\'); $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
$p->start(); $p->start();
@ -357,7 +366,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetOutput() public function testGetOutput()
{ {
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { echo \' foo \'; $n++; }')));
$p->run(); $p->run();
$this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches)); $this->assertEquals(3, preg_match_all('/foo/', $p->getOutput(), $matches));
@ -370,7 +379,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
file_put_contents($lock, 'W'); file_put_contents($lock, 'W');
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
$p->start(); $p->start();
while ($p->isRunning()) { while ($p->isRunning()) {
@ -386,7 +395,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testFlushOutput() public function testFlushOutput()
{ {
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n=0;while ($n<3) {echo \' foo \';$n++;}')));
$p->run(); $p->run();
$p->clearOutput(); $p->clearOutput();
@ -400,7 +409,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock'); $lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
file_put_contents($lock, 'W'); file_put_contents($lock, 'W');
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }'))); $p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 0; while ($n < 3) { if (\'W\' === file_get_contents('.var_export($lock, true).')) { echo \' foo \'; $n++; file_put_contents('.var_export($lock, true).', \'R\'); } usleep(100); }')));
$p->start(); $p->start();
@ -546,7 +555,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testStartIsNonBlocking() public function testStartIsNonBlocking()
{ {
$process = $this->getProcess('php -r "usleep(500000);"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$start = microtime(true); $start = microtime(true);
$process->start(); $process->start();
$end = microtime(true); $end = microtime(true);
@ -556,14 +565,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testUpdateStatus() public function testUpdateStatus()
{ {
$process = $this->getProcess('php -h'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$this->assertTrue(strlen($process->getOutput()) > 0); $this->assertTrue(strlen($process->getOutput()) > 0);
} }
public function testGetExitCodeIsNullOnStart() public function testGetExitCodeIsNullOnStart()
{ {
$process = $this->getProcess('php -r "usleep(200000);"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"');
$this->assertNull($process->getExitCode()); $this->assertNull($process->getExitCode());
$process->start(); $process->start();
$this->assertNull($process->getExitCode()); $this->assertNull($process->getExitCode());
@ -573,7 +582,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetExitCodeIsNullOnWhenStartingAgain() public function testGetExitCodeIsNullOnWhenStartingAgain()
{ {
$process = $this->getProcess('php -r "usleep(200000);"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"');
$process->run(); $process->run();
$this->assertEquals(0, $process->getExitCode()); $this->assertEquals(0, $process->getExitCode());
$process->start(); $process->start();
@ -584,14 +593,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetExitCode() public function testGetExitCode()
{ {
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$this->assertSame(0, $process->getExitCode()); $this->assertSame(0, $process->getExitCode());
} }
public function testStatus() public function testStatus()
{ {
$process = $this->getProcess('php -r "usleep(500000);"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$this->assertFalse($process->isRunning()); $this->assertFalse($process->isRunning());
$this->assertFalse($process->isStarted()); $this->assertFalse($process->isStarted());
$this->assertFalse($process->isTerminated()); $this->assertFalse($process->isTerminated());
@ -610,7 +619,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testStop() public function testStop()
{ {
$process = $this->getProcess('php -r "sleep(4);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
$process->start(); $process->start();
$this->assertTrue($process->isRunning()); $this->assertTrue($process->isRunning());
$process->stop(); $process->stop();
@ -619,14 +628,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIsSuccessful() public function testIsSuccessful()
{ {
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$this->assertTrue($process->isSuccessful()); $this->assertTrue($process->isSuccessful());
} }
public function testIsSuccessfulOnlyAfterTerminated() public function testIsSuccessfulOnlyAfterTerminated()
{ {
$process = $this->getProcess('php -r "sleep(1);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
$process->start(); $process->start();
while ($process->isRunning()) { while ($process->isRunning()) {
$this->assertFalse($process->isSuccessful()); $this->assertFalse($process->isSuccessful());
@ -638,7 +647,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIsNotSuccessful() public function testIsNotSuccessful()
{ {
$process = $this->getProcess('php -r "usleep(500000);throw new \Exception(\'BOUM\');"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);throw new \Exception(\'BOUM\');"');
$process->start(); $process->start();
$this->assertTrue($process->isRunning()); $this->assertTrue($process->isRunning());
$process->wait(); $process->wait();
@ -651,7 +660,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals'); $this->markTestSkipped('Windows does not support POSIX signals');
} }
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$this->assertFalse($process->hasBeenSignaled()); $this->assertFalse($process->hasBeenSignaled());
} }
@ -662,7 +671,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals'); $this->markTestSkipped('Windows does not support POSIX signals');
} }
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$this->assertFalse($process->hasBeenSignaled()); $this->assertFalse($process->hasBeenSignaled());
} }
@ -673,7 +682,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals'); $this->markTestSkipped('Windows does not support POSIX signals');
} }
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$this->assertEquals(0, $process->getTermSignal()); $this->assertEquals(0, $process->getTermSignal());
} }
@ -684,7 +693,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals'); $this->markTestSkipped('Windows does not support POSIX signals');
} }
$process = $this->getProcess('php -r "sleep(4);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
$process->start(); $process->start();
$process->stop(); $process->stop();
$this->assertTrue($process->hasBeenSignaled()); $this->assertTrue($process->hasBeenSignaled());
@ -699,7 +708,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
// SIGTERM is only defined if pcntl extension is present // SIGTERM is only defined if pcntl extension is present
$termSignal = defined('SIGTERM') ? SIGTERM : 15; $termSignal = defined('SIGTERM') ? SIGTERM : 15;
$process = $this->getProcess('php -r "sleep(4);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
$process->start(); $process->start();
$process->stop(); $process->stop();
@ -728,7 +737,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testRestart() public function testRestart()
{ {
$process1 = $this->getProcess('php -r "echo getmypid();"'); $process1 = $this->getProcess(self::$phpBin.' -r "echo getmypid();"');
$process1->run(); $process1->run();
$process2 = $process1->restart(); $process2 = $process1->restart();
@ -750,7 +759,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
// Sleep doesn't work as it will allow the process to handle signals and close // Sleep doesn't work as it will allow the process to handle signals and close
// file handles from the other end. // file handles from the other end.
$process = $this->getProcess('php -r "while (true) {}"'); $process = $this->getProcess(self::$phpBin.' -r "while (true) {}"');
$process->start(); $process->start();
// PHP will deadlock when it tries to cleanup $process // PHP will deadlock when it tries to cleanup $process
@ -759,7 +768,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testRunProcessWithTimeout() public function testRunProcessWithTimeout()
{ {
$timeout = 0.5; $timeout = 0.5;
$process = $this->getProcess('php -r "usleep(600000);"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(600000);"');
$process->setTimeout($timeout); $process->setTimeout($timeout);
$start = microtime(true); $start = microtime(true);
try { try {
@ -781,13 +790,13 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testCheckTimeoutOnNonStartedProcess() public function testCheckTimeoutOnNonStartedProcess()
{ {
$process = $this->getProcess('php -r "sleep(3);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->checkTimeout(); $process->checkTimeout();
} }
public function testCheckTimeoutOnTerminatedProcess() public function testCheckTimeoutOnTerminatedProcess()
{ {
$process = $this->getProcess('php -v'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$process->checkTimeout(); $process->checkTimeout();
} }
@ -796,7 +805,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{ {
$timeout = 0.5; $timeout = 0.5;
$precision = 100000; $precision = 100000;
$process = $this->getProcess('php -r "sleep(3);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->setTimeout($timeout); $process->setTimeout($timeout);
$start = microtime(true); $start = microtime(true);
@ -818,7 +827,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIdleTimeout() public function testIdleTimeout()
{ {
$process = $this->getProcess('php -r "sleep(3);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->setTimeout(10); $process->setTimeout(10);
$process->setIdleTimeout(0.5); $process->setIdleTimeout(0.5);
@ -835,7 +844,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIdleTimeoutNotExceededWhenOutputIsSent() public function testIdleTimeoutNotExceededWhenOutputIsSent()
{ {
$process = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 30; while ($n--) {echo "foo\n"; usleep(100000); }'))); $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 30; while ($n--) {echo "foo\n"; usleep(100000); }')));
$process->setTimeout(2); $process->setTimeout(2);
$process->setIdleTimeout(1); $process->setIdleTimeout(1);
@ -851,7 +860,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testStartAfterATimeout() public function testStartAfterATimeout()
{ {
$process = $this->getProcess(sprintf('php -r %s', escapeshellarg('$n = 1000; while ($n--) {echo \'\'; usleep(1000); }'))); $process = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg('$n = 1000; while ($n--) {echo \'\'; usleep(1000); }')));
$process->setTimeout(0.1); $process->setTimeout(0.1);
try { try {
@ -866,7 +875,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetPid() public function testGetPid()
{ {
$process = $this->getProcess('php -r "usleep(500000);"'); $process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$process->start(); $process->start();
$this->assertGreaterThan(0, $process->getPid()); $this->assertGreaterThan(0, $process->getPid());
$process->wait(); $process->wait();
@ -874,13 +883,13 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetPidIsNullBeforeStart() public function testGetPidIsNullBeforeStart()
{ {
$process = $this->getProcess('php -r "sleep(1);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
$this->assertNull($process->getPid()); $this->assertNull($process->getPid());
} }
public function testGetPidIsNullAfterRun() public function testGetPidIsNullAfterRun()
{ {
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$process->run(); $process->run();
$this->assertNull($process->getPid()); $this->assertNull($process->getPid());
} }
@ -925,7 +934,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testSignalProcessNotRunning() public function testSignalProcessNotRunning()
{ {
$this->verifyPosixIsEnabled(); $this->verifyPosixIsEnabled();
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$process->signal(SIGHUP); $process->signal(SIGHUP);
} }
@ -934,7 +943,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testMethodsThatNeedARunningProcess($method) public function testMethodsThatNeedARunningProcess($method)
{ {
$process = $this->getProcess('php -m'); $process = $this->getProcess(self::$phpBin.' -v');
$this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method)); $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', sprintf('Process must be started before calling %s.', $method));
$process->{$method}(); $process->{$method}();
} }
@ -955,7 +964,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testMethodsThatNeedATerminatedProcess($method) public function testMethodsThatNeedATerminatedProcess($method)
{ {
$process = $this->getProcess('php -r "sleep(1);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
$process->start(); $process->start();
try { try {
$process->{$method}(); $process->{$method}();
@ -997,7 +1006,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('POSIX signals do not work on Windows'); $this->markTestSkipped('POSIX signals do not work on Windows');
} }
$process = $this->getProcess('php -r "sleep(3);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->start(); $process->start();
$process->signal(-4); $process->signal(-4);
} }
@ -1011,14 +1020,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('POSIX signals do not work on Windows'); $this->markTestSkipped('POSIX signals do not work on Windows');
} }
$process = $this->getProcess('php -r "sleep(3);"'); $process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->start(); $process->start();
$process->signal('Céphalopodes'); $process->signal('Céphalopodes');
} }
public function testDisableOutputDisablesTheOutput() public function testDisableOutputDisablesTheOutput()
{ {
$p = $this->getProcess('php -r "usleep(500000);"'); $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$this->assertFalse($p->isOutputDisabled()); $this->assertFalse($p->isOutputDisabled());
$p->disableOutput(); $p->disableOutput();
$this->assertTrue($p->isOutputDisabled()); $this->assertTrue($p->isOutputDisabled());
@ -1028,7 +1037,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testDisableOutputWhileRunningThrowsException() public function testDisableOutputWhileRunningThrowsException()
{ {
$p = $this->getProcess('php -r "usleep(500000);"'); $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->start(); $p->start();
$this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.'); $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.');
$p->disableOutput(); $p->disableOutput();
@ -1036,7 +1045,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testEnableOutputWhileRunningThrowsException() public function testEnableOutputWhileRunningThrowsException()
{ {
$p = $this->getProcess('php -r "usleep(500000);"'); $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput(); $p->disableOutput();
$p->start(); $p->start();
$this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Enabling output while the process is running is not possible.'); $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Enabling output while the process is running is not possible.');
@ -1045,7 +1054,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testEnableOrDisableOutputAfterRunDoesNotThrowException() public function testEnableOrDisableOutputAfterRunDoesNotThrowException()
{ {
$p = $this->getProcess('php -r "usleep(500000);"'); $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput(); $p->disableOutput();
$p->start(); $p->start();
$p->wait(); $p->wait();
@ -1081,7 +1090,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testStartWithACallbackAndDisabledOutput($startMethod, $exception, $exceptionMessage) public function testStartWithACallbackAndDisabledOutput($startMethod, $exception, $exceptionMessage)
{ {
$p = $this->getProcess('php -r "usleep(500000);"'); $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput(); $p->disableOutput();
$this->setExpectedException($exception, $exceptionMessage); $this->setExpectedException($exception, $exceptionMessage);
$p->{$startMethod}(function () {}); $p->{$startMethod}(function () {});
@ -1101,7 +1110,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/ */
public function testGetOutputWhileDisabled($fetchMethod) public function testGetOutputWhileDisabled($fetchMethod)
{ {
$p = $this->getProcess('php -r "usleep(500000);"'); $p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput(); $p->disableOutput();
$p->start(); $p->start();
$this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled.'); $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled.');

View File

@ -102,7 +102,7 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Cannot test when open_basedir is set'); $this->markTestSkipped('Cannot test when open_basedir is set');
} }
$this->iniSet('open_basedir', dirname(PHP_BINARY).PATH_SEPARATOR.'/'); $this->iniSet('open_basedir', dirname(PHP_BINARY).(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : ''));
$finder = new ExecutableFinder(); $finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName()); $result = $finder->find($this->getPhpBinaryName());
@ -125,7 +125,7 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
} }
$this->setPath(''); $this->setPath('');
$this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/'); $this->iniSet('open_basedir', PHP_BINARY.(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : ''));
$finder = new ExecutableFinder(); $finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName(), false); $result = $finder->find($this->getPhpBinaryName(), false);

View File

@ -53,10 +53,10 @@ class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase
$f = new PhpExecutableFinder(); $f = new PhpExecutableFinder();
$current = $f->find(); $current = getenv('PHP_BINARY') ?: PHP_BINARY;
$this->assertEquals($f->find(), $current.' --php', '::find() returns the executable PHP'); $this->assertEquals($current.' --php', $f->find(), '::find() returns the executable PHP');
$this->assertEquals($f->find(false), $current, '::find() returns the executable PHP'); $this->assertEquals($current, $f->find(false), '::find() returns the executable PHP');
} }
/** /**

View File

@ -150,7 +150,7 @@ class SimpleProcessTest extends AbstractProcessTest
public function testStopTerminatesProcessCleanly() public function testStopTerminatesProcessCleanly()
{ {
try { try {
$process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"');
$process->run(function () use ($process) { $process->run(function () use ($process) {
$process->stop(); $process->stop();
}); });
@ -164,7 +164,7 @@ class SimpleProcessTest extends AbstractProcessTest
$this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
try { try {
$process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"');
$process->run(function () use ($process) { $process->run(function () use ($process) {
if ($process->isRunning()) { if ($process->isRunning()) {
$process->signal(defined('SIGKILL') ? SIGKILL : 9); $process->signal(defined('SIGKILL') ? SIGKILL : 9);
@ -180,7 +180,7 @@ class SimpleProcessTest extends AbstractProcessTest
$this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); $this->expectExceptionIfPHPSigchild('Symfony\Component\Process\Exception\RuntimeException', 'This PHP has been compiled with --enable-sigchild. The process can not be signaled.');
try { try {
$process = $this->getProcess('php -r "echo \'foo\'; sleep(1); echo \'bar\';"'); $process = $this->getProcess(self::$phpBin.' -r "echo \'foo\'; sleep(1); echo \'bar\';"');
$process->run(function () use ($process) { $process->run(function () use ($process) {
if ($process->isRunning()) { if ($process->isRunning()) {
$process->signal(defined('SIGTERM') ? SIGTERM : 15); $process->signal(defined('SIGTERM') ? SIGTERM : 15);

View File

@ -48,7 +48,7 @@ class Route
foreach ($data as $key => $value) { foreach ($data as $key => $value) {
$method = 'set'.str_replace('_', '', $key); $method = 'set'.str_replace('_', '', $key);
if (!method_exists($this, $method)) { if (!method_exists($this, $method)) {
throw new \BadMethodCallException(sprintf("Unknown property '%s' on annotation '%s'.", $key, get_class($this))); throw new \BadMethodCallException(sprintf('Unknown property "%s" on annotation "%s".', $key, get_class($this)));
} }
$this->$method($value); $this->$method($value);
} }

View File

@ -39,7 +39,7 @@ class PhpGeneratorDumperTest extends \PHPUnit_Framework_TestCase
$this->routeCollection = new RouteCollection(); $this->routeCollection = new RouteCollection();
$this->generatorDumper = new PhpGeneratorDumper($this->routeCollection); $this->generatorDumper = new PhpGeneratorDumper($this->routeCollection);
$this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.php'; $this->testTmpFilepath = sys_get_temp_dir().DIRECTORY_SEPARATOR.'php_generator.'.$this->getName().'.php';
@unlink($this->testTmpFilepath); @unlink($this->testTmpFilepath);
} }

View File

@ -18,7 +18,7 @@ namespace Symfony\Component\Security\Core\Exception;
*/ */
class AccessDeniedException extends \RuntimeException class AccessDeniedException extends \RuntimeException
{ {
public function __construct($message = 'Access Denied', \Exception $previous = null) public function __construct($message = 'Access Denied.', \Exception $previous = null)
{ {
parent::__construct($message, 403, $previous); parent::__construct($message, 403, $previous);
} }

View File

@ -162,7 +162,8 @@ class SwitchUserListener implements ListenerInterface
} }
if (null !== $this->dispatcher) { if (null !== $this->dispatcher) {
$switchEvent = new SwitchUserEvent($request, $original->getUser()); $user = $this->provider->refreshUser($original->getUser());
$switchEvent = new SwitchUserEvent($request, $user);
$this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent); $this->dispatcher->dispatch(SecurityEvents::SWITCH_USER, $switchEvent);
} }

View File

@ -11,7 +11,9 @@
namespace Symfony\Component\Security\Http\Tests\Firewall; namespace Symfony\Component\Security\Http\Tests\Firewall;
use Symfony\Component\Security\Http\Event\SwitchUserEvent;
use Symfony\Component\Security\Http\Firewall\SwitchUserListener; use Symfony\Component\Security\Http\Firewall\SwitchUserListener;
use Symfony\Component\Security\Http\SecurityEvents;
class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
{ {
@ -100,6 +102,62 @@ class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
$listener->handle($this->event); $listener->handle($this->event);
} }
public function testExitUserDispatchesEventWithRefreshedUser()
{
$originalUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
$refreshedUser = $this->getMock('Symfony\Component\Security\Core\User\UserInterface');
$this
->userProvider
->expects($this->any())
->method('refreshUser')
->with($originalUser)
->willReturn($refreshedUser);
$originalToken = $this->getToken();
$originalToken
->expects($this->any())
->method('getUser')
->willReturn($originalUser);
$role = $this
->getMockBuilder('Symfony\Component\Security\Core\Role\SwitchUserRole')
->disableOriginalConstructor()
->getMock();
$role->expects($this->any())->method('getSource')->willReturn($originalToken);
$this
->tokenStorage
->expects($this->any())
->method('getToken')
->willReturn($this->getToken(array($role)));
$this
->request
->expects($this->any())
->method('get')
->with('_switch_user')
->willReturn('_exit');
$this
->request
->expects($this->any())
->method('getUri')
->willReturn('/');
$this
->request
->query
->expects($this->any())
->method('all')
->will($this->returnValue(array()));
$dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$dispatcher
->expects($this->once())
->method('dispatch')
->with(SecurityEvents::SWITCH_USER, $this->callback(function (SwitchUserEvent $event) use ($refreshedUser) {
return $event->getTargetUser() === $refreshedUser;
}))
;
$listener = new SwitchUserListener($this->tokenStorage, $this->userProvider, $this->userChecker, 'provider123', $this->accessDecisionManager, null, '_switch_user', 'ROLE_ALLOWED_TO_SWITCH', $dispatcher);
$listener->handle($this->event);
}
/** /**
* @expectedException \Symfony\Component\Security\Core\Exception\AccessDeniedException * @expectedException \Symfony\Component\Security\Core\Exception\AccessDeniedException
*/ */

View File

@ -35,16 +35,16 @@ class Groups
public function __construct(array $data) public function __construct(array $data)
{ {
if (!isset($data['value']) || !$data['value']) { if (!isset($data['value']) || !$data['value']) {
throw new InvalidArgumentException(sprintf("Parameter of annotation '%s' cannot be empty.", get_class($this))); throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" cannot be empty.', get_class($this)));
} }
if (!is_array($data['value'])) { if (!is_array($data['value'])) {
throw new InvalidArgumentException(sprintf("Parameter of annotation '%s' must be an array of strings.", get_class($this))); throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be an array of strings.', get_class($this)));
} }
foreach ($data['value'] as $group) { foreach ($data['value'] as $group) {
if (!is_string($group)) { if (!is_string($group)) {
throw new InvalidArgumentException(sprintf("Parameter of annotation '%s' must be an array of strings.", get_class($this))); throw new InvalidArgumentException(sprintf('Parameter of annotation "%s" must be an array of strings.', get_class($this)));
} }
} }

View File

@ -137,12 +137,12 @@ class StopwatchEventTest extends \PHPUnit_Framework_TestCase
public function testStartTime() public function testStartTime()
{ {
$event = new StopwatchEvent(microtime(true) * 1000); $event = new StopwatchEvent(microtime(true) * 1000);
$this->assertTrue($event->getStartTime() < 0.5); $this->assertLessThanOrEqual(0.5, $event->getStartTime());
$event = new StopwatchEvent(microtime(true) * 1000); $event = new StopwatchEvent(microtime(true) * 1000);
$event->start(); $event->start();
$event->stop(); $event->stop();
$this->assertTrue($event->getStartTime() < 1); $this->assertLessThanOrEqual(1, $event->getStartTime());
$event = new StopwatchEvent(microtime(true) * 1000); $event = new StopwatchEvent(microtime(true) * 1000);
$event->start(); $event->start();

View File

@ -28,7 +28,7 @@ class CacheLoaderTest extends \PHPUnit_Framework_TestCase
public function testLoad() public function testLoad()
{ {
$dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.rand(111111, 999999); $dir = sys_get_temp_dir().DIRECTORY_SEPARATOR.mt_rand(111111, 999999);
mkdir($dir, 0777, true); mkdir($dir, 0777, true);
$loader = new ProjectTemplateLoader($varLoader = new ProjectTemplateLoaderVar(), $dir); $loader = new ProjectTemplateLoader($varLoader = new ProjectTemplateLoaderVar(), $dir);

View File

@ -60,7 +60,7 @@ class CallbackValidator extends ConstraintValidator
call_user_func($method, $object, $this->context); call_user_func($method, $object, $this->context);
} elseif (null !== $object) { } elseif (null !== $object) {
if (!method_exists($object, $method)) { if (!method_exists($object, $method)) {
throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist', $method)); throw new ConstraintDefinitionException(sprintf('Method "%s" targeted by Callback constraint does not exist in class %s', $method, get_class($object)));
} }
$reflMethod = new \ReflectionMethod($object, $method); $reflMethod = new \ReflectionMethod($object, $method);

View File

@ -24,6 +24,7 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
* *
* @see http://en.wikipedia.org/wiki/Bank_card_number * @see http://en.wikipedia.org/wiki/Bank_card_number
* @see http://www.regular-expressions.info/creditcard.html * @see http://www.regular-expressions.info/creditcard.html
* @see http://www.barclaycard.co.uk/business/files/Ranges_and_Rules_September_2014.pdf
*/ */
class CardSchemeValidator extends ConstraintValidator class CardSchemeValidator extends ConstraintValidator
{ {
@ -64,10 +65,13 @@ class CardSchemeValidator extends ConstraintValidator
'LASER' => array( 'LASER' => array(
'/^(6304|670[69]|6771)[0-9]{12,15}$/', '/^(6304|670[69]|6771)[0-9]{12,15}$/',
), ),
// Maestro cards begin with either 5018, 5020, 5038, 5893, 6304, 6759, 6761, 6762, 6763 or 0604 // Maestro international cards begin with 675900..675999 and have between 12 and 19 digits.
// They have between 12 and 19 digits. // Maestro UK cards begin with either 500000..509999 or 560000..699999 and have between 12 and 19 digits.
'MAESTRO' => array( 'MAESTRO' => array(
'/^(5018|5020|5038|6304|6759|6761|676[23]|0604)[0-9]{8,15}$/', '/^(6759[0-9]{2})[0-9]{6,13}$/',
'/^(50[0-9]{4})[0-9]{6,13}$/',
'/^5[6-9][0-9]{10,17}$/',
'/^6[0-9]{11,18}$/',
), ),
// All MasterCard numbers start with the numbers 51 through 55. All have 16 digits. // All MasterCard numbers start with the numbers 51 through 55. All have 16 digits.
'MASTERCARD' => array( 'MASTERCARD' => array(

View File

@ -25,8 +25,6 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class EmailValidator extends ConstraintValidator class EmailValidator extends ConstraintValidator
{ {
/** /**
* isStrict
*
* @var bool * @var bool
*/ */
private $isStrict; private $isStrict;
@ -81,7 +79,7 @@ class EmailValidator extends ConstraintValidator
return; return;
} }
} elseif (!preg_match('/.+\@.+\..+/', $value)) { } elseif (!preg_match('/^.+\@\S+\.\S+$/', $value)) {
if ($this->context instanceof ExecutionContextInterface) { if ($this->context instanceof ExecutionContextInterface) {
$this->context->buildViolation($constraint->message) $this->context->buildViolation($constraint->message)
->setParameter('{{ value }}', $this->formatValue($value)) ->setParameter('{{ value }}', $this->formatValue($value))

View File

@ -40,7 +40,7 @@
</trans-unit> </trans-unit>
<trans-unit id="10"> <trans-unit id="10">
<source>This field is missing.</source> <source>This field is missing.</source>
<target>Questo campo è manca.</target> <target>Questo campo è mancante.</target>
</trans-unit> </trans-unit>
<trans-unit id="11"> <trans-unit id="11">
<source>This value is not a valid date.</source> <source>This value is not a valid date.</source>

View File

@ -97,8 +97,10 @@ class CardSchemeValidatorTest extends AbstractConstraintValidatorTest
array('LASER', '6771656738314582216'), array('LASER', '6771656738314582216'),
array('MAESTRO', '6759744069209'), array('MAESTRO', '6759744069209'),
array('MAESTRO', '5020507657408074712'), array('MAESTRO', '5020507657408074712'),
array('MAESTRO', '5612559223580173965'),
array('MAESTRO', '6759744069209'), array('MAESTRO', '6759744069209'),
array('MAESTRO', '6759744069209'), array('MAESTRO', '6759744069209'),
array('MAESTRO', '6594371785970435599'),
array('MASTERCARD', '5555555555554444'), array('MASTERCARD', '5555555555554444'),
array('MASTERCARD', '5105105105105100'), array('MASTERCARD', '5105105105105100'),
array('VISA', '4111111111111111'), array('VISA', '4111111111111111'),

View File

@ -91,6 +91,7 @@ class EmailValidatorTest extends AbstractConstraintValidatorTest
array('example'), array('example'),
array('example@'), array('example@'),
array('example@localhost'), array('example@localhost'),
array('foo@example.com bar'),
); );
} }

Some files were not shown because too many files have changed in this diff Show More