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)) {
foreach ($params as $index => $param) {
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;
}
}
}
$params = $this->normalizeParams($params);
}
if (null !== $this->logger) {
@ -101,4 +78,40 @@ class DbalLogger implements SQLLogger
{
$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()
{
$logger = $this->getMock('Psr\\Log\\LoggerInterface');

View File

@ -82,7 +82,7 @@ class UniqueEntityValidator extends ConstraintValidator
$criteria = array();
foreach ($fields as $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);

View File

@ -21,10 +21,10 @@ use Symfony\Component\HttpKernel\Event\GetResponseEvent;
*/
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
parent::__construct(array());
parent::__construct(array(), $extraFields);
}
public function onKernelRequest(GetResponseEvent $event)

View File

@ -18,6 +18,42 @@ use Symfony\Component\HttpFoundation\Request;
class WebProcessorTest extends \PHPUnit_Framework_TestCase
{
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(
'REQUEST_URI' => 'A',
@ -40,15 +76,7 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase
->method('getRequest')
->will($this->returnValue($request));
$processor = new WebProcessor();
$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']);
return array($event, $server);
}
/**
@ -57,7 +85,7 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase
*
* @return array Record
*/
protected function getRecord($level = Logger::WARNING, $message = 'test')
private function getRecord($level = Logger::WARNING, $message = 'test')
{
return array(
'message' => $message,
@ -69,4 +97,17 @@ class WebProcessorTest extends \PHPUnit_Framework_TestCase
'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:
* disable the garbage collector;
* enforce a consistent `C` locale;
* auto-register `class_exists` to load Doctrine annotations;
* 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();
}
// Enforce a consistent locale
setlocale(LC_ALL, 'C');
if (class_exists('Doctrine\Common\Annotations\AnnotationRegistry')) {
AnnotationRegistry::registerLoader('class_exists');
}

View File

@ -98,6 +98,7 @@
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
</div>
@ -106,6 +107,7 @@
{%- for child in form %}
{{- form_widget(child, {
parent_label_class: label_attr.class|default(''),
translation_domain: choice_translation_domain,
}) -}}
{% endfor -%}
</div>
@ -156,7 +158,7 @@
{%- endblock 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 required %}
{% set label_attr = label_attr|merge({class: (label_attr.class|default('') ~ ' required')|trim}) %}
@ -169,7 +171,7 @@
{% endif %}
<label{% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %}>
{{- 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>
{% endif %}
{% endblock checkbox_radio_label %}

View File

@ -46,7 +46,7 @@
<div {{ block('widget_container_attributes') }}>
{%- for child in form %}
{{- form_widget(child) -}}
{{- form_label(child) -}}
{{- form_label(child, null, {translation_domain: choice_translation_domain}) -}}
{% endfor -%}
</div>
{%- endblock choice_widget_expanded -%}
@ -57,7 +57,7 @@
{%- endif -%}
<select {{ block('widget_attributes') }}{% if multiple %} multiple="multiple"{% endif %}>
{%- 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 -%}
{%- if preferred_choices|length > 0 -%}
{% set options = preferred_choices %}
@ -225,7 +225,7 @@
{% set label = name|humanize %}
{%- 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 -%}
{%- endblock form_label -%}

View File

@ -106,106 +106,103 @@ EOF
$kernel = $this->getContainer()->get('kernel');
// Define Root Path to App folder
$rootPaths = array($kernel->getRootDir());
$transPaths = array($kernel->getRootDir().'/Resources/');
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
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) {
// 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])) {
throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.</error>', $rootPaths[0]));
if (!is_dir($transPaths[0])) {
throw new \InvalidArgumentException(sprintf('"%s" is neither an enabled bundle nor a directory.', $transPaths[0]));
}
}
} elseif ($input->getOption('all')) {
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) {
// get bundle directory
$translationsPath = $rootPath.'/Resources/translations';
// Extract used messages
$extractedCatalogue = $this->extractMessages($locale, $transPaths);
$output->writeln(sprintf('Translations in <info>%s</info>', $translationsPath));
// Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $transPaths, $loader);
// Extract used messages
$extractedCatalogue = $this->extractMessages($locale, $rootPath);
// Merge defined and extracted messages to get all message ids
$mergeOperation = new MergeOperation($extractedCatalogue, $currentCatalogue);
$allMessages = $mergeOperation->getResult()->all($domain);
if (null !== $domain) {
$allMessages = array($domain => $allMessages);
}
// Load defined messages
$currentCatalogue = $this->loadCurrentMessages($locale, $translationsPath, $loader);
// No defined or extracted messages
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) {
$allMessages = array($domain => $allMessages);
$outputMessage .= sprintf(' and domain "%s"', $domain);
}
// No defined or extracted messages
if (empty($allMessages) || null !== $domain && empty($allMessages[$domain])) {
$outputMessage = sprintf('<info>No defined or extracted messages for locale "%s"</info>', $locale);
$output->warning($outputMessage);
if (null !== $domain) {
$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);
return;
}
// 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)
@ -257,15 +254,18 @@ EOF
/**
* @param string $locale
* @param string $rootPath
* @param array $transPaths
*
* @return MessageCatalogue
*/
private function extractMessages($locale, $rootPath)
private function extractMessages($locale, $transPaths)
{
$extractedCatalogue = new MessageCatalogue($locale);
if (is_dir($rootPath.'/Resources/views')) {
$this->getContainer()->get('translation.extractor')->extract($rootPath.'/Resources/views', $extractedCatalogue);
foreach ($transPaths as $path) {
$path = $path.'views';
if (is_dir($path)) {
$this->getContainer()->get('translation.extractor')->extract($path, $extractedCatalogue);
}
}
return $extractedCatalogue;
@ -273,16 +273,19 @@ EOF
/**
* @param string $locale
* @param string $translationsPath
* @param array $transPaths
* @param TranslationLoader $loader
*
* @return MessageCatalogue
*/
private function loadCurrentMessages($locale, $translationsPath, TranslationLoader $loader)
private function loadCurrentMessages($locale, $transPaths, TranslationLoader $loader)
{
$currentCatalogue = new MessageCatalogue($locale);
if (is_dir($translationsPath)) {
$loader->loadMessages($translationsPath, $currentCatalogue);
foreach ($transPaths as $path) {
$path = $path.'translations';
if (is_dir($path)) {
$loader->loadMessages($path, $currentCatalogue);
}
}
return $currentCatalogue;
@ -290,12 +293,12 @@ EOF
/**
* @param string $locale
* @param string $translationsPath
* @param array $transPaths
* @param TranslationLoader $loader
*
* @return MessageCatalogue[]
*/
private function loadFallbackCatalogues($locale, $translationsPath, TranslationLoader $loader)
private function loadFallbackCatalogues($locale, $transPaths, TranslationLoader $loader)
{
$fallbackCatalogues = array();
$translator = $this->getContainer()->get('translator');
@ -306,7 +309,12 @@ EOF
}
$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;
}
}

View File

@ -69,6 +69,8 @@ EOF
protected function execute(InputInterface $input, OutputInterface $output)
{
$output = new SymfonyStyle($input, $output);
$kernel = $this->getContainer()->get('kernel');
// check presence of force or dump-message
if ($input->getOption('force') !== true && $input->getOption('dump-messages') !== true) {
$output->error('You must choose one of --force or --dump-messages');
@ -87,30 +89,30 @@ EOF
$kernel = $this->getContainer()->get('kernel');
// Define Root Path to App folder
$rootPath = $kernel->getRootDir();
$transPaths = array($kernel->getRootDir().'/Resources/');
$currentName = 'app folder';
// Override with provided Bundle info
if (null !== $input->getArgument('bundle')) {
try {
$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();
} catch (\InvalidArgumentException $e) {
// such a bundle does not exist, so treat the argument as path
$rootPath = $input->getArgument('bundle');
$currentName = $rootPath;
$transPaths = array($input->getArgument('bundle').'/Resources/');
$currentName = $transPaths[0];
if (!is_dir($rootPath)) {
throw new \InvalidArgumentException(sprintf('<error>"%s" is neither an enabled bundle nor a directory.</error>', $rootPath));
if (!is_dir($transPaths[0])) {
throw new \InvalidArgumentException(sprintf('<error>"%s" is neither an enabled bundle nor a directory.</error>', $transPaths[0]));
}
}
}
$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));
// load any messages from templates
@ -118,13 +120,23 @@ EOF
$output->text('Parsing templates');
$extractor = $this->getContainer()->get('translation.extractor');
$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
$currentCatalogue = new MessageCatalogue($input->getArgument('locale'));
$output->text('Loading translation files');
$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
$operation = $input->getOption('clean')
@ -150,7 +162,7 @@ EOF
array_map(function ($id) {
return sprintf('<fg=green>%s</>', $id);
}, $newKeys),
array_map(function($id) {
array_map(function ($id) {
return sprintf('<fg=red>%s</>', $id);
}, array_keys($operation->getObsoleteMessages($domain)))
));
@ -168,7 +180,18 @@ EOF
// save the files
if ($input->getOption('force') === true) {
$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();

View File

@ -119,9 +119,9 @@ class Controller extends ContainerAware
* @param mixed $attributes The attributes
* @param mixed $object The object
*
* @throws \LogicException
*
* @return bool
*
* @throws \LogicException
*/
protected function isGranted($attributes, $object = null)
{
@ -231,7 +231,7 @@ class Controller extends ContainerAware
*
* @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);
}

View File

@ -444,7 +444,7 @@ class FrameworkExtension extends Extension
/**
* 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 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 (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 echo $view['form']->block($form, 'choice_widget_options', array('choices' => $preferred_choices)) ?>
<?php if (count($choices) > 0 && null !== $separator): ?>

View File

@ -1,6 +1,6 @@
<div <?php echo $view['form']->block($form, 'widget_container_attributes') ?>>
<?php foreach ($form as $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 ?>
</div>

View File

@ -4,5 +4,5 @@
<?php if (!$label) { $label = isset($label_format)
? strtr($label_format, array('%name%' => $name, '%id%' => $id))
: $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 ?>

View File

@ -14,7 +14,7 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Bundle\FrameworkBundle\DependencyInjection\FrameworkExtension;
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\ParameterBag\ParameterBag;
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');
}
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($basePath, $package->getArgument(0));
$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($baseUrls, $package->getArgument(0));

View File

@ -77,7 +77,7 @@ class Cookie
if (null !== $expires) {
$timestampAsDateTime = \DateTime::createFromFormat('U', $expires);
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();

View File

@ -22,7 +22,7 @@ class ClassMapGeneratorTest extends \PHPUnit_Framework_TestCase
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);
$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->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');
}

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->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');
}

View File

@ -40,7 +40,7 @@ class TextDescriptor extends Descriptor
$totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName());
$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(),
str_repeat(' ', $spacingWidth),
// + 17 = 2 spaces + <info> + </info> + 2 spaces
@ -77,7 +77,7 @@ class TextDescriptor extends Descriptor
$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,
str_repeat(' ', $spacingWidth),
// + 17 = 2 spaces + <info> + </info> + 2 spaces
@ -207,7 +207,7 @@ class TextDescriptor extends Descriptor
foreach ($namespace['commands'] as $name) {
$this->writeText("\n");
$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;
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()) {
$valueLength = 1 + strlen($option->getName()); // = + value

View File

@ -221,7 +221,7 @@ class ArgvInput extends Input
}
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)) {

View File

@ -30,6 +30,9 @@ use Symfony\Component\Console\Formatter\OutputFormatterInterface;
*/
class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
{
/**
* @var StreamOutput
*/
private $stderr;
/**
@ -43,14 +46,12 @@ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface
*/
public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null)
{
$outputStream = 'php://stdout';
if (!$this->hasStdoutSupport()) {
$outputStream = 'php://output';
}
$outputStream = $this->hasStdoutSupport() ? 'php://stdout' : 'php://output';
$errorStream = $this->hasStderrSupport() ? 'php://stderr' : 'php://output';
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
* 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
*/
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_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_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'])) {
$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
foreach ($definition->getArguments() as $k => $v) {

View File

@ -170,6 +170,16 @@ class DefinitionDecorator extends Definition
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.
*

View File

@ -1301,11 +1301,6 @@ EOF;
foreach ($value->getArguments() as $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()) {
$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));
} elseif ($value instanceof Variable) {
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) {
if ($value = $service->getAttribute($key)) {
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);
$definition->$method(XmlUtils::phpize($value));
@ -172,7 +172,7 @@ class XmlFileLoader extends FileLoader
$triggerDeprecation = 'request' !== (string) $service->getAttribute('id');
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);

View File

@ -179,7 +179,7 @@ class YamlFileLoader extends FileLoader
}
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);
}
@ -209,17 +209,17 @@ class YamlFileLoader extends FileLoader
}
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']);
}
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']);
}
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']);
}

View File

@ -120,6 +120,25 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase
$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()
{
$container = new ContainerBuilder();
@ -212,6 +231,19 @@ class ResolveDefinitionTemplatesPassTest extends \PHPUnit_Framework_TestCase
$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)
{
$pass = new ResolveDefinitionTemplatesPass();

View File

@ -44,4 +44,4 @@ $phar->addFromString('schema/project-1.0.xsd', <<<EOT
</xsd:schema>
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);
}
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);
}
} else {

View File

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

View File

@ -30,14 +30,21 @@ class ArrayChoiceList implements ChoiceListInterface
*
* @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.
@ -51,31 +58,41 @@ class ArrayChoiceList implements ChoiceListInterface
*
* The given choice array must have the same array keys as the value array.
*
* @param array $choices The selectable choices
* @param callable|null $value The callable for creating the value for a
* choice. If `null` is passed, incrementing
* integers are used as values
* @param array|\Traversable $choices The selectable choices
* @param callable|null $value The callable for creating the value
* for a choice. If `null` is passed,
* 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)) {
throw new UnexpectedTypeException($value, 'null or callable');
}
$this->choices = $choices;
$this->values = array();
$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 ($choices instanceof \Traversable) {
$choices = iterator_to_array($choices);
}
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()
{
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();
foreach ($values as $i => $givenValue) {
foreach ($this->values as $j => $value) {
if ($value !== (string) $givenValue) {
continue;
}
$choices[$i] = $this->choices[$j];
unset($values[$i]);
if (0 === count($values)) {
break 2;
}
if (isset($this->choices[$givenValue])) {
$choices[$i] = $this->choices[$givenValue];
}
}
@ -131,28 +155,56 @@ class ArrayChoiceList implements ChoiceListInterface
$givenValues = array();
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
foreach ($choices as $i => $givenChoice) {
foreach ($this->choices as $j => $choice) {
if ($choice !== $givenChoice) {
continue;
}
$values[$i] = $this->values[$j];
unset($choices[$i]);
if (0 === count($choices)) {
break 2;
foreach ($this->choices as $value => $choice) {
if ($choice === $givenChoice) {
$values[$i] = (string) $value;
break;
}
}
}
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
*
* @throws InvalidArgumentException If the choice is not scalar
*
* @internal Must not be used outside this class
*/
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
* values.
*
* @param array $choices The selectable choices
* @param callable $value The callable for creating the value for a
* choice. If `null` is passed, the choices are
* cast to strings and used as values
* @param array|\Traversable $choices The selectable choices
* @param callable $value The callable for creating the value
* for a choice. If `null` is passed, the
* choices are cast to strings and used
* as values
*
* @throws InvalidArgumentException If the keys of the choices don't match
* the keys of the values or if any of the
* 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) {
$value = function ($choice) {
return (string) $choice;
};
$this->useChoicesAsValues = true;
}
@ -122,7 +128,7 @@ class ArrayKeyChoiceList extends ArrayChoiceList
// If the values are identical to the choices, so we can just return
// 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);
@ -143,4 +149,38 @@ class ArrayKeyChoiceList extends ArrayChoiceList
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 choice list assigns string values to each of a list of choices. These
* string values are displayed in the "value" attributes in HTML and submitted
* back to the server.
* A choice list assigns unique string values to each of a list of choices.
* These string values are displayed in the "value" attributes in HTML and
* submitted back to the server.
*
* The acceptable data types for the choices depend on the implementation.
* 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>
*/
interface ChoiceListInterface
@ -31,23 +28,66 @@ interface ChoiceListInterface
/**
* Returns all selectable choices.
*
* The keys of the choices correspond to the keys of the values returned by
* {@link getValues()}.
*
* @return array The selectable choices
* @return array The selectable choices indexed by the corresponding values
*/
public function getChoices();
/**
* Returns the values for the choices.
*
* The keys of the values correspond to the keys of the choices returned by
* {@link getChoices()}.
* The values are strings that do not contain duplicates.
*
* @return string[] The choice values
*/
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.
*

View File

@ -65,6 +65,30 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
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.
*
@ -100,7 +124,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface
// We ignore the choice groups for caching. If two choice lists are
// requested with the same choices, but a different grouping, the same
// choice list is returned.
DefaultChoiceListFactory::flatten($choices, $flatChoices);
self::flatten($choices, $flatChoices);
$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
// requested with the same choices, but a different grouping, the same
// choice list is returned.
DefaultChoiceListFactory::flattenFlipped($choices, $flatChoices);
self::flatten($choices, $flatChoices);
$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
// factory may decide which input to accept and which not.
$hash = self::generateHash(array($list, $preferredChoices, $label, $index, $groupBy, $attr));
if (!isset($this->views[$hash])) {

View File

@ -69,7 +69,7 @@ interface ChoiceListFactoryInterface
* argument.
*
* @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
*
* @return ChoiceListInterface The choice list
@ -98,25 +98,20 @@ interface ChoiceListFactoryInterface
* The preferred choices can also be passed as array. Each choice that is
* 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
* match the keys of the choices. The values should be arrays of HTML
* attributes that should be added to the respective choice.
*
* @param ChoiceListInterface $list The choice list
* @param null|array|callable $preferredChoices The preferred choices
* @param null|callable $label The callable generating
* the choice labels
* @param null|callable $index The callable generating
* the view indices
* @param null|array|\Traversable|callable $groupBy The callable generating
* the group names
* @param null|array|callable $attr The callable generating
* the HTML attributes
* @param ChoiceListInterface $list The choice list
* @param null|array|callable $preferredChoices The preferred choices
* @param null|callable $label The callable generating the
* choice labels
* @param null|callable $index The callable generating the
* view indices
* @param null|callable $groupBy The callable generating the
* group names
* @param null|array|callable $attr The callable generating the
* HTML attributes
*
* @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\ChoiceListInterface;
use Symfony\Component\Form\ChoiceList\LazyChoiceList;
use Symfony\Component\Form\ChoiceList\LegacyChoiceListAdapter;
use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
use Symfony\Component\Form\ChoiceList\View\ChoiceGroupView;
use Symfony\Component\Form\ChoiceList\View\ChoiceListView;
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;
/**
@ -29,72 +29,12 @@ use Symfony\Component\Form\Extension\Core\View\ChoiceView as LegacyChoiceView;
*/
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}
*/
public function createListFromChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$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);
return new ArrayChoiceList($choices, $value);
}
/**
@ -105,26 +45,7 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
*/
public function createListFromFlippedChoices($choices, $value = null)
{
if ($choices instanceof \Traversable) {
$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);
return new ArrayKeyChoiceList($choices, $value);
}
/**
@ -141,22 +62,24 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
public function createView(ChoiceListInterface $list, $preferredChoices = null, $label = null, $index = null, $groupBy = null, $attr = null)
{
// Backwards compatibility
if ($list instanceof LegacyChoiceListInterface && empty($preferredChoices)
if ($list instanceof LegacyChoiceListAdapter && empty($preferredChoices)
&& null === $label && null === $index && null === $groupBy && null === $attr) {
$mapToNonLegacyChoiceView = function (LegacyChoiceView $choiceView) {
return new ChoiceView($choiceView->data, $choiceView->value, $choiceView->label);
};
$adaptedList = $list->getAdaptedList();
return new ChoiceListView(
array_map($mapToNonLegacyChoiceView, $list->getRemainingViews()),
array_map($mapToNonLegacyChoiceView, $list->getPreferredViews())
array_map($mapToNonLegacyChoiceView, $adaptedList->getRemainingViews()),
array_map($mapToNonLegacyChoiceView, $adaptedList->getPreferredViews())
);
}
$preferredViews = array();
$otherViews = array();
$choices = $list->getChoices();
$values = $list->getValues();
$keys = $list->getOriginalKeys();
if (!is_callable($preferredChoices) && !empty($preferredChoices)) {
$preferredChoices = function ($choice) use ($preferredChoices) {
@ -169,36 +92,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
$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
// name returned by the callable. If the callable returns null, the
// choice is not added to any group
if (is_callable($groupBy)) {
foreach ($choices as $key => $choice) {
foreach ($choices as $value => $choice) {
self::addChoiceViewGroupedBy(
$groupBy,
$choice,
$key,
(string) $value,
$label,
$values,
$keys,
$index,
$attr,
$preferredChoices,
@ -207,13 +111,12 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
);
}
} else {
// If $groupBy is passed as array, use that array as template for
// constructing the groups
// Otherwise use the original structure of the choices
self::addChoiceViewsGroupedBy(
$groupBy,
$list->getStructuredValues(),
$label,
$choices,
$values,
$keys,
$index,
$attr,
$preferredChoices,
@ -239,15 +142,17 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
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);
$view = new ChoiceView(
$choice,
$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),
// The attributes may be a callable or a mapping from choice indices
// 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
if (is_array($content)) {
if (is_array($value)) {
$preferredViewsForGroup = array();
$otherViewsForGroup = array();
self::addChoiceViewsGroupedBy(
$content,
$value,
$label,
$choices,
$values,
$keys,
$index,
$attr,
$isPreferred,
@ -295,10 +200,10 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
// Add ungrouped items directly
self::addChoiceView(
$choices[$key],
$key,
$choices[$value],
$value,
$label,
$values,
$keys,
$index,
$attr,
$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 the callable returns null, don't group the choice
self::addChoiceView(
$choice,
$key,
$value,
$label,
$values,
$keys,
$index,
$attr,
$isPreferred,
@ -329,6 +234,8 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
return;
}
$groupLabel = (string) $groupLabel;
// Initialize the group views if necessary. Unnnecessarily built group
// views will be cleaned up at the end of createView()
if (!isset($preferredViews[$groupLabel])) {
@ -338,9 +245,9 @@ class DefaultChoiceListFactory implements ChoiceListFactoryInterface
self::addChoiceView(
$choice,
$key,
$value,
$label,
$values,
$keys,
$index,
$attr,
$isPreferred,

View File

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

View File

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

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;
use Symfony\Component\Form\ChoiceList\ChoiceListInterface as BaseChoiceListInterface;
use Symfony\Component\Form\FormConfigBuilder;
/**
* 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>
*
* @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
* the choice groups as top-level keys.
@ -84,6 +95,37 @@ interface ChoiceListInterface extends BaseChoiceListInterface
*/
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.
*

View File

@ -13,6 +13,7 @@ namespace Symfony\Component\Form\Extension\Core\Type;
use Symfony\Component\Form\AbstractType;
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\ChoiceListInterface;
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\FormInterface;
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\DataTransformer\ChoiceToValueTransformer;
use Symfony\Component\Form\Extension\Core\DataTransformer\ChoicesToValuesTransformer;
@ -259,6 +261,10 @@ class ChoiceType extends AbstractType
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);
if ($choiceList instanceof LegacyChoiceListInterface) {
return new LegacyChoiceListAdapter($choiceList);
}
return $choiceList;
}
@ -338,7 +344,7 @@ class ChoiceType extends AbstractType
$resolver->setNormalizer('placeholder', $placeholderNormalizer);
$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('choice_translation_domain', array('null', 'bool', 'string'));
$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
if (!is_string($options['format'])) {
// remove quoted strings first
$pattern = preg_replace('/\'[^\']+\'/', '', $pattern);
// remove remaining special chars
$pattern = preg_replace('/[^yMd]+/', '', $pattern);
}

View File

@ -931,7 +931,7 @@ class Form implements \IteratorAggregate, FormInterface
$child->setParent($this);
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);
$this->config->getDataMapper()->mapDataToForms($viewData, $iterator);
}

View File

@ -200,27 +200,35 @@ class ResolvedFormType implements ResolvedFormTypeInterface
$this->innerType->setDefaultOptions($this->optionsResolver);
$reflector = new \ReflectionMethod($this->innerType, 'setDefaultOptions');
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType';
if (method_exists($this->innerType, 'configureOptions')) {
$reflector = new \ReflectionMethod($this->innerType, 'setDefaultOptions');
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType';
$reflector = new \ReflectionMethod($this->innerType, 'configureOptions');
$isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType';
$reflector = new \ReflectionMethod($this->innerType, 'configureOptions');
$isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractType';
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);
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);
}
} 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) {
$extension->setDefaultOptions($this->optionsResolver);
$reflector = new \ReflectionMethod($extension, 'setDefaultOptions');
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension';
if (method_exists($extension, 'configureOptions')) {
$reflector = new \ReflectionMethod($extension, 'setDefaultOptions');
$isOldOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension';
$reflector = new \ReflectionMethod($extension, 'configureOptions');
$isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension';
$reflector = new \ReflectionMethod($extension, 'configureOptions');
$isNewOverwritten = $reflector->getDeclaringClass()->getName() !== 'Symfony\Component\Form\AbstractTypeExtension';
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);
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);
}
} 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()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -359,7 +382,7 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
[@class="my&class form-control"]
[not(@required)]
[
./option[@value=""][.="[trans][/trans]"]
./option[@value=""][.=""]
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/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"]
[not(@required)]
[
./option[@value=""][.="[trans][/trans]"]
./option[@value=""][.=""]
/following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/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"]
[@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="&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()
{
$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()
{
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(
@ -1278,17 +1383,17 @@ abstract class AbstractBootstrap3LayoutTest extends AbstractLayoutTest
./select
[@id="name_month"]
[@class="form-control"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]]
[./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]]
/following-sibling::select
[@id="name_day"]
[@class="form-control"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]]
[./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]]
/following-sibling::select
[@id="name_year"]
[@class="form-control"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]]
[./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1950"][@selected="selected"]]
]
[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()
{
$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()
{
$form = $this->factory->createNamed('name', 'choice', '&a', array(
@ -635,7 +671,7 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
[@name="name"]
[not(@required)]
[
./option[@value=""][.="[trans][/trans]"]
./option[@value=""][.=""]
/following-sibling::option[@value="&a"][@selected="selected"][.="[trans]Choice&A[/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"]
[not(@required)]
[
./option[@value=""][.="[trans][/trans]"]
./option[@value=""][.=""]
/following-sibling::option[@value="&a"][not(@selected)][.="[trans]Choice&A[/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"]
[@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="&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()
{
$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()
{
$form = $this->factory->createNamed('name', 'choice', array('&a', '&c'), array(
@ -1442,15 +1527,15 @@ abstract class AbstractLayoutTest extends \Symfony\Component\Form\Test\FormInteg
[
./select
[@id="name_month"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]]
[./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]]
/following-sibling::select
[@id="name_day"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]]
[./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1"][@selected="selected"]]
/following-sibling::select
[@id="name_year"]
[./option[@value=""][not(@selected)][not(@disabled)][.="[trans][/trans]"]]
[./option[@value=""][not(@selected)][not(@disabled)][.=""]]
[./option[@value="1950"][@selected="selected"]]
]
[count(./select)=3]

View File

@ -31,6 +31,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
*/
protected $values;
/**
* @var array
*/
protected $structuredValues;
/**
* @var array
*/
protected $keys;
/**
* @var mixed
*/
@ -71,25 +81,52 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
*/
protected $value4;
/**
* @var string
*/
protected $key1;
/**
* @var string
*/
protected $key2;
/**
* @var string
*/
protected $key3;
/**
* @var string
*/
protected $key4;
protected function setUp()
{
parent::setUp();
$this->list = $this->createChoiceList();
$this->choices = $this->getChoices();
$choices = $this->getChoices();
$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
reset($this->choices);
reset($this->values);
reset($this->keys);
for ($i = 1; $i <= 4; ++$i) {
$this->{'choice'.$i} = current($this->choices);
$this->{'value'.$i} = current($this->values);
$this->{'key'.$i} = current($this->keys);
next($this->choices);
next($this->values);
next($this->keys);
}
}
@ -103,6 +140,16 @@ abstract class AbstractChoiceListTest extends \PHPUnit_Framework_TestCase
$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()
{
$values = array($this->value1, $this->value2);

View File

@ -29,8 +29,6 @@ class ArrayChoiceListTest extends AbstractChoiceListTest
protected function createChoiceList()
{
$i = 0;
return new ArrayChoiceList($this->getChoices());
}
@ -60,11 +58,31 @@ class ArrayChoiceListTest extends AbstractChoiceListTest
$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->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()
{
$callback = function ($choice) {

View File

@ -29,7 +29,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
protected function createChoiceList()
{
return new ArrayKeyChoiceList($this->getChoices());
return new ArrayKeyChoiceList(array_flip($this->getChoices()));
}
protected function getChoices()
@ -44,9 +44,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
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()
@ -102,33 +104,22 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
public function provideConvertibleChoices()
{
return array(
array(array(0), array(0)),
array(array(1), array(1)),
array(array('0'), array(0)),
array(array('1'), array(1)),
array(array('1.23'), array('1.23')),
array(array('foobar'), array('foobar')),
array(array(0 => 'Label'), array(0 => 0)),
array(array(1 => 'Label'), array(1 => 1)),
array(array('1.23' => 'Label'), array('1.23' => '1.23')),
array(array('foobar' => 'Label'), array('foobar' => 'foobar')),
// The default value of choice fields is NULL. It should be treated
// like the empty value for this choice list type
array(array(null), array('')),
array(array(1.23), array('1.23')),
array(array(null => 'Label'), array('' => '')),
array(array('1.23' => 'Label'), 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(array(true), array(1)),
array(array(false), array(0)),
array(array(true => 'Label'), array(1 => 1)),
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
* @expectedException \Symfony\Component\Form\Exception\InvalidArgumentException
@ -155,7 +146,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
return $value;
};
$list = new ArrayKeyChoiceList(array('choice'), $callback);
$list = new ArrayKeyChoiceList(array('choice' => 'Label'), $callback);
$this->assertSame(array($converted), $list->getValues());
}
@ -169,15 +160,7 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
array('1', '1'),
array('1.23', '1.23'),
array('foobar', 'foobar'),
// The default value of choice fields is NULL. It should be treated
// 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, ''),
array('', ''),
);
}
@ -187,9 +170,11 @@ class ArrayKeyChoiceListTest extends AbstractChoiceListTest
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->getValuesForChoices(array(1 => 'foo', 2 => 'baz')));
}

View File

@ -204,8 +204,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase
*/
public function testCreateFromFlippedChoicesSameChoices($choice1, $choice2)
{
$choices1 = array($choice1);
$choices2 = array($choice2);
$choices1 = array($choice1 => 'A');
$choices2 = array($choice2 => 'A');
$list = new \stdClass();
$this->decoratedFactory->expects($this->once())
@ -222,8 +222,8 @@ class CachingFactoryDecoratorTest extends \PHPUnit_Framework_TestCase
*/
public function testCreateFromFlippedChoicesDifferentChoices($choice1, $choice2)
{
$choices1 = array($choice1);
$choices2 = array($choice2);
$choices1 = array($choice1 => 'A');
$choices2 = array($choice2 => 'A');
$list1 = 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\Factory\DefaultChoiceListFactory;
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\ChoiceListView;
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';
}
public function getGroupAsObject($object)
{
return $this->obj1 === $object || $this->obj2 === $object
? new DefaultChoiceListFactoryTest_Castable('Group 1')
: new DefaultChoiceListFactoryTest_Castable('Group 2');
}
protected function setUp()
{
$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')
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesFlatTraversable()
@ -208,7 +216,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
new \ArrayIterator(array('a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D'))
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesFlatValuesAsCallable()
@ -247,7 +255,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
)
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesGroupedTraversable()
@ -259,7 +267,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
))
);
$this->assertScalarListWithGeneratedValues($list);
$this->assertScalarListWithChoiceValues($list);
}
public function testCreateFromFlippedChoicesGroupedValuesAsCallable()
@ -523,33 +531,16 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
$this->assertFlatViewWithCustomIndices($view);
}
public function testCreateViewFlatGroupByAsArray()
public function testCreateViewFlatGroupByOriginalStructure()
{
$view = $this->factory->createView(
$this->list,
array($this->obj2, $this->obj3),
null, // label
null, // index
array(
'Group 1' => array('A' => true, 'B' => true),
'Group 2' => array('C' => true, 'D' => true),
)
);
$list = new ArrayChoiceList(array(
'Group 1' => array('A' => $this->obj1, 'B' => $this->obj2),
'Group 2' => array('C' => $this->obj3, 'D' => $this->obj4),
));
$this->assertGroupedView($view);
}
public function testCreateViewFlatGroupByAsTraversable()
{
$view = $this->factory->createView(
$this->list,
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),
))
$list,
array($this->obj2, $this->obj3)
);
$this->assertGroupedView($view);
@ -581,6 +572,19 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
$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()
{
$obj1 = $this->obj1;
@ -592,8 +596,7 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
null, // label
null, // index
function ($object) use ($obj1, $obj2) {
return $obj1 === $object || $obj2 === $object ? 'Group 1'
: 'Group 2';
return $obj1 === $object || $obj2 === $object ? 'Group 1' : 'Group 2';
}
);
@ -749,78 +752,86 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
->method('getRemainingViews')
->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('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(
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'a' => 'a',
'b' => 'b',
'c' => 'c',
'd' => 'd',
), $list->getChoices());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
), $list->getValues());
'a' => 'A',
'b' => 'B',
'c' => 'C',
'd' => 'D',
), $list->getOriginalKeys());
}
private function assertObjectListWithGeneratedValues(ChoiceListInterface $list)
{
$this->assertSame(array('0', '1', '2', '3'), $list->getValues());
$this->assertSame(array(
'A' => $this->obj1,
'B' => $this->obj2,
'C' => $this->obj3,
'D' => $this->obj4,
0 => $this->obj1,
1 => $this->obj2,
2 => $this->obj3,
3 => $this->obj4,
), $list->getChoices());
$this->assertSame(array(
'A' => '0',
'B' => '1',
'C' => '2',
'D' => '3',
), $list->getValues());
0 => 'A',
1 => 'B',
2 => 'C',
3 => 'D',
), $list->getOriginalKeys());
}
private function assertScalarListWithCustomValues(ChoiceListInterface $list)
{
$this->assertSame(array('a', 'b', '1', '2'), $list->getValues());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => 'c',
'D' => 'd',
'a' => 'a',
'b' => 'b',
1 => 'c',
2 => 'd',
), $list->getChoices());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => '1',
'D' => '2',
), $list->getValues());
'a' => 'A',
'b' => 'B',
1 => 'C',
2 => 'D',
), $list->getOriginalKeys());
}
private function assertObjectListWithCustomValues(ChoiceListInterface $list)
{
$this->assertSame(array('a', 'b', '1', '2'), $list->getValues());
$this->assertSame(array(
'A' => $this->obj1,
'B' => $this->obj2,
'C' => $this->obj3,
'D' => $this->obj4,
'a' => $this->obj1,
'b' => $this->obj2,
1 => $this->obj3,
2 => $this->obj4,
), $list->getChoices());
$this->assertSame(array(
'A' => 'a',
'B' => 'b',
'C' => '1',
'D' => '2',
), $list->getValues());
'a' => 'A',
'b' => 'B',
1 => 'C',
2 => 'D',
), $list->getOriginalKeys());
}
private function assertFlatView($view)
@ -897,3 +908,18 @@ class DefaultChoiceListFactoryTest extends \PHPUnit_Framework_TestCase
), $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());
}
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()
{
$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'))
->will($this->returnCallback(function ($data, \RecursiveIteratorIterator $iterator) use ($child, $test) {
$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();

View File

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

View File

@ -30,7 +30,7 @@ class ChoiceTypePerformanceTest extends FormPerformanceTestCase
$choices = range(1, 300);
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,
));
}

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()
{
$form = $this->factory->create('choice', null, array(

View File

@ -627,6 +627,20 @@ class DateTypeTest extends TestCase
$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()
{
$form = $this->factory->create('date', null, array(

View File

@ -95,34 +95,58 @@ class JsonResponse extends Response
*/
public function setData($data = array())
{
$errorHandler = null;
$errorHandler = set_error_handler(function () use (&$errorHandler) {
if (JSON_ERROR_NONE !== json_last_error()) {
return;
if (defined('HHVM_VERSION')) {
// HHVM does not trigger any warnings and let exceptions
// thrown from a JsonSerializable object pass through.
// 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()) {
throw new \InvalidArgumentException($this->transformJsonError());
}
$this->data = $data;
return $this->update();
}

View File

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

View File

@ -1247,15 +1247,9 @@ class Response
{
$status = ob_get_status(true);
$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
&& (!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))
)
)
) {
while ($level-- > $targetLevel && ($s = $status[$level]) && (!isset($s['del']) ? !isset($s['flags']) || $flags === ($s['flags'] & $flags) : $s['del'])) {
if ($flush) {
ob_end_flush();
} else {

View File

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

View File

@ -118,7 +118,7 @@ class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
$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()
@ -127,7 +127,7 @@ class ResponseHeaderBagTest extends \PHPUnit_Framework_TestCase
$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()

View File

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

View File

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

View File

@ -123,7 +123,7 @@ class FullTransformer
// handle unimplemented characters
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->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->mkdir($this->directory);

View File

@ -35,7 +35,7 @@ class PhpBundleWriterTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$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->mkdir($this->directory);

View File

@ -36,7 +36,7 @@ class TextBundleWriterTest extends \PHPUnit_Framework_TestCase
protected function setUp()
{
$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->mkdir($this->directory);

View File

@ -33,7 +33,7 @@ class LocaleScannerTest extends \PHPUnit_Framework_TestCase
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->scanner = new LocaleScanner();

View File

@ -37,7 +37,7 @@ class PhpExecutableFinder
{
// HHVM support
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

View File

@ -11,21 +11,30 @@
namespace Symfony\Component\Process\Tests;
use Symfony\Component\Process\Exception\ProcessTimedOutException;
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\Process;
use Symfony\Component\Process\Exception\RuntimeException;
/**
* @author Robert Schönthal <seroscho@googlemail.com>
*/
abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{
protected static $phpBin;
public static function setUpBeforeClass()
{
$phpBin = new PhpExecutableFinder();
self::$phpBin = $phpBin->find();
}
public function testThatProcessDoesNotThrowWarningDuringRun()
{
@trigger_error('Test Error', E_USER_NOTICE);
$process = $this->getProcess("php -r 'sleep(3)'");
$process = $this->getProcess(self::$phpBin." -r 'sleep(3)'");
$process->run();
$actualError = error_get_last();
$this->assertEquals('Test Error', $actualError['message']);
@ -95,7 +104,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$expectedOutputSize = PipesInterface::CHUNK_SIZE * 2 + 2;
$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();
// 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)
{
$p = $this->getProcess(sprintf('php -r %s', escapeshellarg($code)));
$p = $this->getProcess(sprintf('%s -r %s', self::$phpBin, escapeshellarg($code)));
$p->run();
$this->assertSame($expected, $p->$getter());
@ -150,7 +159,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$expected = str_repeat(str_repeat('*', 1024), $size).'!';
$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->run();
@ -170,7 +179,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
fwrite($stream, $expected);
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->run();
@ -182,7 +191,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testSetInputWhileRunningThrowsAnException()
{
$process = $this->getProcess('php -r "usleep(500000);"');
$process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$process->start();
try {
$process->setInput('foobar');
@ -201,7 +210,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/
public function testInvalidInput($value)
{
$process = $this->getProcess('php -v');
$process = $this->getProcess(self::$phpBin.' -v');
$process->setInput($value);
}
@ -218,7 +227,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/
public function testValidInput($expected, $value)
{
$process = $this->getProcess('php -v');
$process = $this->getProcess(self::$phpBin.' -v');
$process->setInput($value);
$this->assertSame($expected, $process->getInput());
}
@ -238,7 +247,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/
public function testLegacyValidInput($expected, $value)
{
$process = $this->getProcess('php -v');
$process = $this->getProcess(self::$phpBin.' -v');
$process->setInput($value);
$this->assertSame($expected, $process->getInput());
}
@ -276,7 +285,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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;
$p->run(function ($type, $buffer) use (&$called) {
@ -288,7 +297,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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();
$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');
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();
while ($p->isRunning()) {
@ -317,7 +326,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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->clearErrorOutput();
@ -331,7 +340,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
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();
@ -357,7 +366,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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();
$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');
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();
while ($p->isRunning()) {
@ -386,7 +395,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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->clearOutput();
@ -400,7 +409,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$lock = tempnam(sys_get_temp_dir(), get_class($this).'Lock');
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();
@ -546,7 +555,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testStartIsNonBlocking()
{
$process = $this->getProcess('php -r "usleep(500000);"');
$process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$start = microtime(true);
$process->start();
$end = microtime(true);
@ -556,14 +565,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testUpdateStatus()
{
$process = $this->getProcess('php -h');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$this->assertTrue(strlen($process->getOutput()) > 0);
}
public function testGetExitCodeIsNullOnStart()
{
$process = $this->getProcess('php -r "usleep(200000);"');
$process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"');
$this->assertNull($process->getExitCode());
$process->start();
$this->assertNull($process->getExitCode());
@ -573,7 +582,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetExitCodeIsNullOnWhenStartingAgain()
{
$process = $this->getProcess('php -r "usleep(200000);"');
$process = $this->getProcess(self::$phpBin.' -r "usleep(200000);"');
$process->run();
$this->assertEquals(0, $process->getExitCode());
$process->start();
@ -584,14 +593,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetExitCode()
{
$process = $this->getProcess('php -m');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$this->assertSame(0, $process->getExitCode());
}
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->isStarted());
$this->assertFalse($process->isTerminated());
@ -610,7 +619,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testStop()
{
$process = $this->getProcess('php -r "sleep(4);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
$process->start();
$this->assertTrue($process->isRunning());
$process->stop();
@ -619,14 +628,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIsSuccessful()
{
$process = $this->getProcess('php -m');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$this->assertTrue($process->isSuccessful());
}
public function testIsSuccessfulOnlyAfterTerminated()
{
$process = $this->getProcess('php -r "sleep(1);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
$process->start();
while ($process->isRunning()) {
$this->assertFalse($process->isSuccessful());
@ -638,7 +647,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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();
$this->assertTrue($process->isRunning());
$process->wait();
@ -651,7 +660,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals');
}
$process = $this->getProcess('php -m');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$this->assertFalse($process->hasBeenSignaled());
}
@ -662,7 +671,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals');
}
$process = $this->getProcess('php -m');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$this->assertFalse($process->hasBeenSignaled());
}
@ -673,7 +682,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$this->markTestSkipped('Windows does not support POSIX signals');
}
$process = $this->getProcess('php -m');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$this->assertEquals(0, $process->getTermSignal());
}
@ -684,7 +693,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$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->stop();
$this->assertTrue($process->hasBeenSignaled());
@ -699,7 +708,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
// SIGTERM is only defined if pcntl extension is present
$termSignal = defined('SIGTERM') ? SIGTERM : 15;
$process = $this->getProcess('php -r "sleep(4);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(4);"');
$process->start();
$process->stop();
@ -728,7 +737,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testRestart()
{
$process1 = $this->getProcess('php -r "echo getmypid();"');
$process1 = $this->getProcess(self::$phpBin.' -r "echo getmypid();"');
$process1->run();
$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
// file handles from the other end.
$process = $this->getProcess('php -r "while (true) {}"');
$process = $this->getProcess(self::$phpBin.' -r "while (true) {}"');
$process->start();
// PHP will deadlock when it tries to cleanup $process
@ -759,7 +768,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testRunProcessWithTimeout()
{
$timeout = 0.5;
$process = $this->getProcess('php -r "usleep(600000);"');
$process = $this->getProcess(self::$phpBin.' -r "usleep(600000);"');
$process->setTimeout($timeout);
$start = microtime(true);
try {
@ -781,13 +790,13 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testCheckTimeoutOnNonStartedProcess()
{
$process = $this->getProcess('php -r "sleep(3);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->checkTimeout();
}
public function testCheckTimeoutOnTerminatedProcess()
{
$process = $this->getProcess('php -v');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$process->checkTimeout();
}
@ -796,7 +805,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
{
$timeout = 0.5;
$precision = 100000;
$process = $this->getProcess('php -r "sleep(3);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->setTimeout($timeout);
$start = microtime(true);
@ -818,7 +827,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testIdleTimeout()
{
$process = $this->getProcess('php -r "sleep(3);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(3);"');
$process->setTimeout(10);
$process->setIdleTimeout(0.5);
@ -835,7 +844,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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->setIdleTimeout(1);
@ -851,7 +860,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
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);
try {
@ -866,7 +875,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetPid()
{
$process = $this->getProcess('php -r "usleep(500000);"');
$process = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$process->start();
$this->assertGreaterThan(0, $process->getPid());
$process->wait();
@ -874,13 +883,13 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testGetPidIsNullBeforeStart()
{
$process = $this->getProcess('php -r "sleep(1);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
$this->assertNull($process->getPid());
}
public function testGetPidIsNullAfterRun()
{
$process = $this->getProcess('php -m');
$process = $this->getProcess(self::$phpBin.' -v');
$process->run();
$this->assertNull($process->getPid());
}
@ -925,7 +934,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testSignalProcessNotRunning()
{
$this->verifyPosixIsEnabled();
$process = $this->getProcess('php -m');
$process = $this->getProcess(self::$phpBin.' -v');
$process->signal(SIGHUP);
}
@ -934,7 +943,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/
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));
$process->{$method}();
}
@ -955,7 +964,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/
public function testMethodsThatNeedATerminatedProcess($method)
{
$process = $this->getProcess('php -r "sleep(1);"');
$process = $this->getProcess(self::$phpBin.' -r "sleep(1);"');
$process->start();
try {
$process->{$method}();
@ -997,7 +1006,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$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->signal(-4);
}
@ -1011,14 +1020,14 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$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->signal('Céphalopodes');
}
public function testDisableOutputDisablesTheOutput()
{
$p = $this->getProcess('php -r "usleep(500000);"');
$p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$this->assertFalse($p->isOutputDisabled());
$p->disableOutput();
$this->assertTrue($p->isOutputDisabled());
@ -1028,7 +1037,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testDisableOutputWhileRunningThrowsException()
{
$p = $this->getProcess('php -r "usleep(500000);"');
$p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->start();
$this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.');
$p->disableOutput();
@ -1036,7 +1045,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
public function testEnableOutputWhileRunningThrowsException()
{
$p = $this->getProcess('php -r "usleep(500000);"');
$p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput();
$p->start();
$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()
{
$p = $this->getProcess('php -r "usleep(500000);"');
$p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput();
$p->start();
$p->wait();
@ -1081,7 +1090,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/
public function testStartWithACallbackAndDisabledOutput($startMethod, $exception, $exceptionMessage)
{
$p = $this->getProcess('php -r "usleep(500000);"');
$p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput();
$this->setExpectedException($exception, $exceptionMessage);
$p->{$startMethod}(function () {});
@ -1101,7 +1110,7 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
*/
public function testGetOutputWhileDisabled($fetchMethod)
{
$p = $this->getProcess('php -r "usleep(500000);"');
$p = $this->getProcess(self::$phpBin.' -r "usleep(500000);"');
$p->disableOutput();
$p->start();
$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->iniSet('open_basedir', dirname(PHP_BINARY).PATH_SEPARATOR.'/');
$this->iniSet('open_basedir', dirname(PHP_BINARY).(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : ''));
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName());
@ -125,7 +125,7 @@ class ExecutableFinderTest extends \PHPUnit_Framework_TestCase
}
$this->setPath('');
$this->iniSet('open_basedir', PHP_BINARY.PATH_SEPARATOR.'/');
$this->iniSet('open_basedir', PHP_BINARY.(!defined('HHVM_VERSION') ? PATH_SEPARATOR.'/' : ''));
$finder = new ExecutableFinder();
$result = $finder->find($this->getPhpBinaryName(), false);

View File

@ -53,10 +53,10 @@ class PhpExecutableFinderTest extends \PHPUnit_Framework_TestCase
$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($f->find(false), $current, '::find() returns the executable PHP');
$this->assertEquals($current.' --php', $f->find(), '::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()
{
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->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.');
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) {
if ($process->isRunning()) {
$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.');
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) {
if ($process->isRunning()) {
$process->signal(defined('SIGTERM') ? SIGTERM : 15);

View File

@ -48,7 +48,7 @@ class Route
foreach ($data as $key => $value) {
$method = 'set'.str_replace('_', '', $key);
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);
}

View File

@ -39,7 +39,7 @@ class PhpGeneratorDumperTest extends \PHPUnit_Framework_TestCase
$this->routeCollection = new 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);
}

View File

@ -18,7 +18,7 @@ namespace Symfony\Component\Security\Core\Exception;
*/
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);
}

View File

@ -162,7 +162,8 @@ class SwitchUserListener implements ListenerInterface
}
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);
}

View File

@ -11,7 +11,9 @@
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\SecurityEvents;
class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
{
@ -100,6 +102,62 @@ class SwitchUserListenerTest extends \PHPUnit_Framework_TestCase
$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
*/

View File

@ -35,16 +35,16 @@ class Groups
public function __construct(array $data)
{
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'])) {
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) {
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()
{
$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->start();
$event->stop();
$this->assertTrue($event->getStartTime() < 1);
$this->assertLessThanOrEqual(1, $event->getStartTime());
$event = new StopwatchEvent(microtime(true) * 1000);
$event->start();

View File

@ -28,7 +28,7 @@ class CacheLoaderTest extends \PHPUnit_Framework_TestCase
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);
$loader = new ProjectTemplateLoader($varLoader = new ProjectTemplateLoaderVar(), $dir);

View File

@ -60,7 +60,7 @@ class CallbackValidator extends ConstraintValidator
call_user_func($method, $object, $this->context);
} elseif (null !== $object) {
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);

View File

@ -24,6 +24,7 @@ use Symfony\Component\Validator\Exception\UnexpectedTypeException;
*
* @see http://en.wikipedia.org/wiki/Bank_card_number
* @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
{
@ -64,10 +65,13 @@ class CardSchemeValidator extends ConstraintValidator
'LASER' => array(
'/^(6304|670[69]|6771)[0-9]{12,15}$/',
),
// Maestro cards begin with either 5018, 5020, 5038, 5893, 6304, 6759, 6761, 6762, 6763 or 0604
// They have between 12 and 19 digits.
// Maestro international cards begin with 675900..675999 and 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(
'/^(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.
'MASTERCARD' => array(

View File

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

View File

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

View File

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

View File

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

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