diff --git a/UPGRADE-4.1.md b/UPGRADE-4.1.md index 8d96b710ed9ec..28302c42d305a 100644 --- a/UPGRADE-4.1.md +++ b/UPGRADE-4.1.md @@ -27,6 +27,40 @@ EventDispatcher * The `TraceableEventDispatcherInterface` has been deprecated. +Form +---- + + * Deprecated the `ChoiceLoaderInterface` implementation in `CountryType`, + `LanguageType`, `LocaleType` and `CurrencyType`, use the `choice_loader` + option instead. + + Before: + ```php + class MyCountryType extends CountryType + { + public function loadChoiceList() + { + // override the method + } + } + ``` + + After: + ```php + class MyCountryType extends AbstractType + { + public function getParent() + { + return CountryType::class; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setDefault('choice_loader', ...); // override the option instead + } + } + ``` + FrameworkBundle --------------- diff --git a/src/Symfony/Component/Form/CHANGELOG.md b/src/Symfony/Component/Form/CHANGELOG.md index a3a36bf7bca6d..0cf142de256cb 100644 --- a/src/Symfony/Component/Form/CHANGELOG.md +++ b/src/Symfony/Component/Form/CHANGELOG.md @@ -4,6 +4,8 @@ CHANGELOG 4.1.0 ----- + * added `choice_translation_locale` option to `CountryType`, `LanguageType`, `LocaleType` and `CurrencyType` + * deprecated the `ChoiceLoaderInterface` implementation in `CountryType`, `LanguageType`, `LocaleType` and `CurrencyType` * added `input=datetime_immutable` to DateType, TimeType, DateTimeType * added `rounding_mode` option to MoneyType diff --git a/src/Symfony/Component/Form/ChoiceList/Loader/IntlCallbackChoiceLoader.php b/src/Symfony/Component/Form/ChoiceList/Loader/IntlCallbackChoiceLoader.php new file mode 100644 index 0000000000000..f0140814fad6c --- /dev/null +++ b/src/Symfony/Component/Form/ChoiceList/Loader/IntlCallbackChoiceLoader.php @@ -0,0 +1,59 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Form\ChoiceList\Loader; + +/** + * Callback choice loader optimized for Intl choice types. + * + * @author Jules Pietri + * @author Yonel Ceruto + */ +class IntlCallbackChoiceLoader extends CallbackChoiceLoader +{ + /** + * {@inheritdoc} + */ + public function loadChoicesForValues(array $values, $value = null) + { + // Optimize + $values = array_filter($values); + if (empty($values)) { + return array(); + } + + // If no callable is set, values are the same as choices + if (null === $value) { + return $values; + } + + return $this->loadChoiceList($value)->getChoicesForValues($values); + } + + /** + * {@inheritdoc} + */ + public function loadValuesForChoices(array $choices, $value = null) + { + // Optimize + $choices = array_filter($choices); + if (empty($choices)) { + return array(); + } + + // If no callable is set, choices are the same as values + if (null === $value) { + return $choices; + } + + return $this->loadChoiceList($value)->getValuesForChoices($choices); + } +} diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php index a96a42d3d6b67..e206936dd32e8 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CountryType.php @@ -14,7 +14,9 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; use Symfony\Component\Intl\Intl; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class CountryType extends AbstractType implements ChoiceLoaderInterface @@ -27,6 +29,8 @@ class CountryType extends AbstractType implements ChoiceLoaderInterface * {@link \Symfony\Component\Intl\Intl::getRegionBundle()}. * * @var ArrayChoiceList + * + * @deprecated since Symfony 4.1 */ private $choiceList; @@ -36,9 +40,18 @@ class CountryType extends AbstractType implements ChoiceLoaderInterface public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choice_loader' => $this, + 'choice_loader' => function (Options $options) { + $choiceTranslationLocale = $options['choice_translation_locale']; + + return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { + return array_flip(Intl::getRegionBundle()->getCountryNames($choiceTranslationLocale)); + }); + }, 'choice_translation_domain' => false, + 'choice_translation_locale' => null, )); + + $resolver->setAllowedTypes('choice_translation_locale', array('null', 'string')); } /** @@ -59,9 +72,13 @@ public function getBlockPrefix() /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoiceList($value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + if (null !== $this->choiceList) { return $this->choiceList; } @@ -71,9 +88,13 @@ public function loadChoiceList($value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoicesForValues(array $values, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $values = array_filter($values); if (empty($values)) { @@ -90,9 +111,13 @@ public function loadChoicesForValues(array $values, $value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadValuesForChoices(array $choices, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $choices = array_filter($choices); if (empty($choices)) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php index 9970d03ad7195..79d874ca400b6 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/CurrencyType.php @@ -14,7 +14,9 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; use Symfony\Component\Intl\Intl; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class CurrencyType extends AbstractType implements ChoiceLoaderInterface @@ -27,6 +29,8 @@ class CurrencyType extends AbstractType implements ChoiceLoaderInterface * {@link \Symfony\Component\Intl\Intl::getCurrencyBundle()}. * * @var ArrayChoiceList + * + * @deprecated since Symfony 4.1 */ private $choiceList; @@ -36,9 +40,18 @@ class CurrencyType extends AbstractType implements ChoiceLoaderInterface public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choice_loader' => $this, + 'choice_loader' => function (Options $options) { + $choiceTranslationLocale = $options['choice_translation_locale']; + + return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { + return array_flip(Intl::getCurrencyBundle()->getCurrencyNames($choiceTranslationLocale)); + }); + }, 'choice_translation_domain' => false, + 'choice_translation_locale' => null, )); + + $resolver->setAllowedTypes('choice_translation_locale', array('null', 'string')); } /** @@ -59,9 +72,13 @@ public function getBlockPrefix() /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoiceList($value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + if (null !== $this->choiceList) { return $this->choiceList; } @@ -71,9 +88,13 @@ public function loadChoiceList($value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoicesForValues(array $values, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $values = array_filter($values); if (empty($values)) { @@ -90,9 +111,13 @@ public function loadChoicesForValues(array $values, $value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadValuesForChoices(array $choices, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $choices = array_filter($choices); if (empty($choices)) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php index 279402a3e28e3..6d35c2392140f 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LanguageType.php @@ -14,7 +14,9 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; use Symfony\Component\Intl\Intl; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class LanguageType extends AbstractType implements ChoiceLoaderInterface @@ -27,6 +29,8 @@ class LanguageType extends AbstractType implements ChoiceLoaderInterface * {@link \Symfony\Component\Intl\Intl::getLanguageBundle()}. * * @var ArrayChoiceList + * + * @deprecated since Symfony 4.1 */ private $choiceList; @@ -36,9 +40,18 @@ class LanguageType extends AbstractType implements ChoiceLoaderInterface public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choice_loader' => $this, + 'choice_loader' => function (Options $options) { + $choiceTranslationLocale = $options['choice_translation_locale']; + + return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { + return array_flip(Intl::getLanguageBundle()->getLanguageNames($choiceTranslationLocale)); + }); + }, 'choice_translation_domain' => false, + 'choice_translation_locale' => null, )); + + $resolver->setAllowedTypes('choice_translation_locale', array('null', 'string')); } /** @@ -59,9 +72,13 @@ public function getBlockPrefix() /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoiceList($value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + if (null !== $this->choiceList) { return $this->choiceList; } @@ -71,9 +88,13 @@ public function loadChoiceList($value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoicesForValues(array $values, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $values = array_filter($values); if (empty($values)) { @@ -90,9 +111,13 @@ public function loadChoicesForValues(array $values, $value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadValuesForChoices(array $choices, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $choices = array_filter($choices); if (empty($choices)) { diff --git a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php index de795956b77a1..4c0c467729ff1 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/LocaleType.php @@ -14,7 +14,9 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\ChoiceList\ArrayChoiceList; use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; use Symfony\Component\Intl\Intl; +use Symfony\Component\OptionsResolver\Options; use Symfony\Component\OptionsResolver\OptionsResolver; class LocaleType extends AbstractType implements ChoiceLoaderInterface @@ -27,6 +29,8 @@ class LocaleType extends AbstractType implements ChoiceLoaderInterface * {@link \Symfony\Component\Intl\Intl::getLocaleBundle()}. * * @var ArrayChoiceList + * + * @deprecated since Symfony 4.1 */ private $choiceList; @@ -36,9 +40,18 @@ class LocaleType extends AbstractType implements ChoiceLoaderInterface public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults(array( - 'choice_loader' => $this, + 'choice_loader' => function (Options $options) { + $choiceTranslationLocale = $options['choice_translation_locale']; + + return new IntlCallbackChoiceLoader(function () use ($choiceTranslationLocale) { + return array_flip(Intl::getLocaleBundle()->getLocaleNames($choiceTranslationLocale)); + }); + }, 'choice_translation_domain' => false, + 'choice_translation_locale' => null, )); + + $resolver->setAllowedTypes('choice_translation_locale', array('null', 'string')); } /** @@ -59,9 +72,13 @@ public function getBlockPrefix() /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoiceList($value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + if (null !== $this->choiceList) { return $this->choiceList; } @@ -71,9 +88,13 @@ public function loadChoiceList($value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadChoicesForValues(array $values, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $values = array_filter($values); if (empty($values)) { @@ -90,9 +111,13 @@ public function loadChoicesForValues(array $values, $value = null) /** * {@inheritdoc} + * + * @deprecated since Symfony 4.1 */ public function loadValuesForChoices(array $choices, $value = null) { + @trigger_error(sprintf('Method "%s" is deprecated since Symfony 4.1, use "choice_loader" option instead.', __METHOD__), E_USER_DEPRECATED); + // Optimize $choices = array_filter($choices); if (empty($choices)) { diff --git a/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php new file mode 100644 index 0000000000000..03e975dce1a24 --- /dev/null +++ b/src/Symfony/Component/Form/Tests/ChoiceList/Loader/IntlCallbackChoiceLoaderTest.php @@ -0,0 +1,104 @@ + + * + * 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\Loader; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\Form\ChoiceList\ChoiceListInterface; +use Symfony\Component\Form\ChoiceList\LazyChoiceList; +use Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader; + +/** + * @author Jules Pietri + * @author Yonel Ceruto + */ +class IntlCallbackChoiceLoaderTest extends TestCase +{ + /** + * @var \Symfony\Component\Form\ChoiceList\Loader\IntlCallbackChoiceLoader + */ + private static $loader; + + /** + * @var callable + */ + private static $value; + + /** + * @var array + */ + private static $choices; + + /** + * @var string[] + */ + private static $choiceValues; + + /** + * @var \Symfony\Component\Form\ChoiceList\LazyChoiceList + */ + private static $lazyChoiceList; + + public static function setUpBeforeClass() + { + self::$loader = new IntlCallbackChoiceLoader(function () { + return self::$choices; + }); + self::$value = function ($choice) { + return $choice->value ?? null; + }; + self::$choices = array( + (object) array('value' => 'choice_one'), + (object) array('value' => 'choice_two'), + ); + self::$choiceValues = array('choice_one', 'choice_two'); + self::$lazyChoiceList = new LazyChoiceList(self::$loader, self::$value); + } + + public function testLoadChoiceList() + { + $this->assertInstanceOf(ChoiceListInterface::class, self::$loader->loadChoiceList(self::$value)); + } + + public function testLoadChoiceListOnlyOnce() + { + $loadedChoiceList = self::$loader->loadChoiceList(self::$value); + + $this->assertSame($loadedChoiceList, self::$loader->loadChoiceList(self::$value)); + } + + public function testLoadChoicesForValuesLoadsChoiceListOnFirstCall() + { + $this->assertSame( + self::$loader->loadChoicesForValues(self::$choiceValues, self::$value), + self::$lazyChoiceList->getChoicesForValues(self::$choiceValues), + 'Choice list should not be reloaded.' + ); + } + + public function testLoadValuesForChoicesLoadsChoiceListOnFirstCall() + { + $this->assertSame( + self::$loader->loadValuesForChoices(self::$choices, self::$value), + self::$lazyChoiceList->getValuesForChoices(self::$choices), + 'Choice list should not be reloaded.' + ); + } + + public static function tearDownAfterClass() + { + self::$loader = null; + self::$value = null; + self::$choices = array(); + self::$choiceValues = array(); + self::$lazyChoiceList = null; + } +} diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php index 5f9af3e1c1b27..a8e9ddc7d34d9 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CountryTypeTest.php @@ -38,6 +38,25 @@ public function testCountriesAreSelectable() $this->assertContains(new ChoiceView('MY', 'MY', 'Malaysia'), $choices, '', false, false); } + /** + * @requires extension intl + */ + public function testChoiceTranslationLocaleOption() + { + $choices = $this->factory + ->create(static::TESTED_TYPE, null, array( + 'choice_translation_locale' => 'uk', + )) + ->createView()->vars['choices']; + + // Don't check objects for identity + $this->assertContains(new ChoiceView('DE', 'DE', 'Німеччина'), $choices, '', false, false); + $this->assertContains(new ChoiceView('GB', 'GB', 'Велика Британія'), $choices, '', false, false); + $this->assertContains(new ChoiceView('US', 'US', 'Сполучені Штати'), $choices, '', false, false); + $this->assertContains(new ChoiceView('FR', 'FR', 'Франція'), $choices, '', false, false); + $this->assertContains(new ChoiceView('MY', 'MY', 'Малайзія'), $choices, '', false, false); + } + public function testUnknownCountryIsNotIncluded() { $choices = $this->factory->create(static::TESTED_TYPE, 'country') diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php index be9264d7b19db..01733c491797e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/CurrencyTypeTest.php @@ -35,6 +35,23 @@ public function testCurrenciesAreSelectable() $this->assertContains(new ChoiceView('SIT', 'SIT', 'Slovenian Tolar'), $choices, '', false, false); } + /** + * @requires extension intl + */ + public function testChoiceTranslationLocaleOption() + { + $choices = $this->factory + ->create(static::TESTED_TYPE, null, array( + 'choice_translation_locale' => 'uk', + )) + ->createView()->vars['choices']; + + // Don't check objects for identity + $this->assertContains(new ChoiceView('EUR', 'EUR', 'євро'), $choices, '', false, false); + $this->assertContains(new ChoiceView('USD', 'USD', 'долар США'), $choices, '', false, false); + $this->assertContains(new ChoiceView('SIT', 'SIT', 'словенський толар'), $choices, '', false, false); + } + public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php index 5ea3c4a732165..ebe3d4ebb9f0e 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LanguageTypeTest.php @@ -37,6 +37,25 @@ public function testCountriesAreSelectable() $this->assertContains(new ChoiceView('my', 'my', 'Burmese'), $choices, '', false, false); } + /** + * @requires extension intl + */ + public function testChoiceTranslationLocaleOption() + { + $choices = $this->factory + ->create(static::TESTED_TYPE, null, array( + 'choice_translation_locale' => 'uk', + )) + ->createView()->vars['choices']; + + // Don't check objects for identity + $this->assertContains(new ChoiceView('en', 'en', 'англійська'), $choices, '', false, false); + $this->assertContains(new ChoiceView('en_GB', 'en_GB', 'British English'), $choices, '', false, false); + $this->assertContains(new ChoiceView('en_US', 'en_US', 'англійська (США)'), $choices, '', false, false); + $this->assertContains(new ChoiceView('fr', 'fr', 'французька'), $choices, '', false, false); + $this->assertContains(new ChoiceView('my', 'my', 'бірманська'), $choices, '', false, false); + } + public function testMultipleLanguagesIsNotIncluded() { $choices = $this->factory->create(static::TESTED_TYPE, 'language') diff --git a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php index 58b94517d4572..ab0dfa4bf17d3 100644 --- a/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php +++ b/src/Symfony/Component/Form/Tests/Extension/Core/Type/LocaleTypeTest.php @@ -35,6 +35,23 @@ public function testLocalesAreSelectable() $this->assertContains(new ChoiceView('zh_Hant_MO', 'zh_Hant_MO', 'Chinese (Traditional, Macau SAR China)'), $choices, '', false, false); } + /** + * @requires extension intl + */ + public function testChoiceTranslationLocaleOption() + { + $choices = $this->factory + ->create(static::TESTED_TYPE, null, array( + 'choice_translation_locale' => 'uk', + )) + ->createView()->vars['choices']; + + // Don't check objects for identity + $this->assertContains(new ChoiceView('en', 'en', 'англійська'), $choices, '', false, false); + $this->assertContains(new ChoiceView('en_GB', 'en_GB', 'англійська (Велика Британія)'), $choices, '', false, false); + $this->assertContains(new ChoiceView('zh_Hant_MO', 'zh_Hant_MO', 'китайська (традиційна, Макао, О.А.Р Китаю)'), $choices, '', false, false); + } + public function testSubmitNull($expected = null, $norm = null, $view = null) { parent::testSubmitNull($expected, $norm, ''); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy