diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..2fb241cb
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,10 @@
+/.gitattributes export-ignore
+/.github export-ignore
+/.gitignore export-ignore
+/.php-cs-fixer.php export-ignore
+/tests export-ignore
+/phpstan.neon export-ignore
+/phpstan-baseline.neon export-ignore
+/phpunit.xml export-ignore
+/tools export-ignore
+/Makefile export-ignore
diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml
new file mode 100644
index 00000000..2b7545a3
--- /dev/null
+++ b/.github/workflows/static.yml
@@ -0,0 +1,47 @@
+on: [pull_request]
+name: Static analysis
+jobs:
+ phpstan:
+ name: PHPStan
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.2
+
+ - name: Download dependencies
+ uses: ramsey/composer-install@v2
+
+ - name: Install PHPStan
+ uses: ramsey/composer-install@v2
+ with:
+ composer-options: "--working-dir=tools/phpstan"
+
+ - name: PHPStan
+ run: tools/phpstan/vendor/bin/phpstan analyze --no-progress --error-format=checkstyle
+
+ php-cs-fixer:
+ name: PHP-CS-Fixer
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.2
+
+ - name: Install php-cs-fixer
+ uses: ramsey/composer-install@v2
+ with:
+ composer-options: "--working-dir=tools/php-cs-fixer"
+
+ - name: PHP-CS-Fixer
+ run: tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --dry-run --diff --config=.php-cs-fixer.php
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..a8a41f19
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,53 @@
+on: [pull_request]
+name: PHPUnit
+
+jobs:
+ tests:
+ name: Tests
+ runs-on: Ubuntu-20.04
+
+ strategy:
+ matrix:
+ include:
+ # Lowest Deps
+ - php: 8.1
+ symfony-require: 5.4.*
+ composer-flags: '--prefer-stable --prefer-lowest'
+ # LTS with latest stable PHP
+ - php: 8.2
+ symfony-require: 6.4.*
+ composer-flags: '--prefer-stable'
+ # Active release
+ - php: 8.3
+ symfony-require: 7.0.*
+ composer-flags: '--prefer-stable --ignore-platform-req=php+'
+ # Development release
+ - php: nightly
+ symfony-require: 7.1.*@dev
+ composer-flags: '--ignore-platform-req=php+'
+ stability: dev
+ can-fail: true
+ fail-fast: false
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: intl-74.1
+ coverage: none
+
+ - name: Configure Composer minimum stability
+ if: matrix.stability
+ run: composer config minimum-stability ${{ matrix.stability }}
+
+ - name: Install dependencies
+ env:
+ SYMFONY_REQUIRE: ${{ matrix.symfony-require }}
+ run: composer update ${{ matrix.composer-flags }} --no-interaction --no-progress --optimize-autoloader
+
+ - name: Run PHPUnit
+ run: vendor/bin/phpunit
diff --git a/.gitignore b/.gitignore
index 402f4a78..1ed5aeb9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,8 @@
+/.idea/
+/.php-cs-fixer.cache
+/.phpunit.result.cache
/composer.lock
/composer.phar
-/phpunit.xml
+/tools/*/composer.lock
+/tools/*/vendor
/vendor/
diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php
new file mode 100644
index 00000000..0fe0325e
--- /dev/null
+++ b/.php-cs-fixer.php
@@ -0,0 +1,20 @@
+files()
+ ->in(__DIR__)
+ ->exclude(__DIR__.'/vendor')
+;
+
+$config = new PhpCsFixer\Config();
+
+return $config
+ ->setRiskyAllowed(true)
+ ->setRules([
+ '@Symfony' => true,
+ '@Symfony:risky' => true,
+ 'array_syntax' => ['syntax' => 'short'],
+ 'declare_strict_types' => true,
+ ])
+ ->setFinder($finder)
+;
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 6953585d..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,45 +0,0 @@
-dist: trusty
-
-sudo: false
-
-language: php
-
-php:
- - 5.4
- - 5.5
- - 5.6
- - 7.0
- - 7.1
-
-cache:
- directories:
- - $HOME/.composer/cache/files
-
-matrix:
- fast_failure: true
- include:
- - dist: precise
- php: 5.4
- env: dependencies=lowest
- - php: 7.0
- env: SYMFONY_VERSION=2.7.x
- - php: 7.0
- env: SYMFONY_VERSION=2.8.x
- - php: 7.0
- env: SYMFONY_VERSION=3.0.x
- - php: 7.0
- env: SYMFONY_VERSION=3.1.x
- - php: hhvm
- env: SYMFONY_VERSION=3.4.x
- - php: 7.1
- env: SYMFONY_VERSION=4.0.x stability=beta
-
-before_install:
- - if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then echo 'memory_limit=-1' >> ~/.phpenv/versions/$(phpenv version-name)/etc/conf.d/travis.ini; fi
- - if [ "$SYMFONY_VERSION" != "" ]; then composer require --dev --no-update symfony/symfony:$SYMFONY_VERSION; fi
- - if [ "$stability" != "" ]; then composer config minimum-stability $stability; fi
-
-install:
- - if [ "$dependencies" = "lowest" ]; then composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction; else composer update --prefer-dist --no-interaction; fi;
-
-script: ./vendor/bin/phpunit --coverage-text
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index 006772f8..00000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,131 +0,0 @@
-Changelog
-=========
-
-2.0.x-dev
------
-
-Upcoming release
-
-* Drop support for Symfony < 2.7
-* Add support for Symfony 3.4+
-* Drop support for PHP 5.3
-* Rename `tel_widget` to `phone_number_widget`
-* Add services only if relevant (optional) dependencies are available
-
-1.3.1
------
-
-17 January 2018
-
-* Undo minor breaking change by reinstating and deprecating `se` code for Swedish translations.
-* Validator uses `buildViolation` instead of `addViolation` for Symfony >= 2.5.
-
-1.3.0
------
-
-15 January 2018
-
-* Symfony 4 support.
-* Add `phone_number_of_type` Twig test and `isType` test to `PhoneNumberHelper`.
-* Deprecate `PhoneNumberFormatHelper` in favour of `PhoneNumberHelper` and `PhoneNumberFormatExtension` in favour of `PhoneNumberHelperExtension`.
-* Swedish translation improvements. Rename country code `se`->`sv`.
-* Add `country_placeholder` option.
-* Regard `"0"` as an invalid phone number.
-
-1.2.0
------
-
-17 December 2016.
-
-* Add Symfony Serializer support.
-* Confirm libphonenumber 8.0 compatibility.
-* Deprecate `phone_number_format` Twig function in favour of a filter.
-* Avoid `choices_as_values` deprecation notice in Symfony 3.1.
-
-1.1.3
------
-
-7 September 2016.
-
-* Add basic Danish, Swedish and Finnish translations.
-
-1.1.2
------
-
-31 March 2016.
-
-* Allow the country choice form widget to not be required.
-* Add difference between form type in Symfony 2 and 3 to the documentation.
-
-1.1.1
------
-
-12 March 2016.
-
-* Correct the block prefix for PhoneNumberType in Symfony 3.
-
-1.1.0
------
-
-25 January 2016.
-
-* Add translations for the validation constraint (BC break).
-* Add validation of the phone number type.
-* Throw an exception if Doctrine can't convert a database value to/from a `PhoneNumber`.
-* Add country choice form widget.
-* Add `libphonenumber.phone_number_offline_geocoder` service.
-* Add `libphonenumber.short_number_info` service.
-* Add `libphonenumber.phone_number_to_carrier_mapper` service.
-* Add `libphonenumber.phone_number_to_time_zones_mapper` service.
-* Deprecate `.class` parameters.
-
-1.0.6
------
-
-22 December 2015.
-
-* Confirm Symfony 3.0 compatibility.
-
-1.0.5
------
-
-15 April 2015.
-
-* Cater for Symfony's deprecation notices.
-* Throw a `ConversionException` in the Doctrine type when the value is not a `PhoneNumber`.
-
-1.0.4
------
-
-3 November 2014.
-
-* Confirm libphonenumber 7.0 compatibility.
-
-1.0.3
------
-
-20 October 2014.
-
-* Handle international numbers correctly when using the national format and a default region.
-* Throw a `TransformationFailedException` when required in the form data transformer.
-
-1.0.2
------
-
-27 February 2014.
-
-* Confirm libphonenumber 6.0 compatibility.
-
-1.0.1
------
-
-30 January 2014.
-
-* Changed libphonenumber port to giggsey/libphonenumber-for-php.
-
-1.0.0
------
-
-10 October 2013.
-
-* Initial release.
diff --git a/DependencyInjection/Compiler/FormPhpTemplateCompilerPass.php b/DependencyInjection/Compiler/FormPhpTemplateCompilerPass.php
deleted file mode 100644
index 59062140..00000000
--- a/DependencyInjection/Compiler/FormPhpTemplateCompilerPass.php
+++ /dev/null
@@ -1,47 +0,0 @@
-hasParameter('templating.helper.form.resources')) {
- return;
- }
-
- $parameter = $container->getParameter('templating.helper.form.resources');
-
- if (in_array('MisdPhoneNumberBundle:Form', $parameter)) {
- return;
- }
-
- // Insert right after FrameworkBundle:Form if exists.
- if (($key = array_search('FrameworkBundle:Form', $parameter)) !== false) {
- array_splice($parameter, ++$key, 0, array('MisdPhoneNumberBundle:Form'));
- } else {
- // Put it in first position.
- array_unshift($resources, array('MisdPhoneNumberBundle:Form'));
- }
-
- $container->setParameter('templating.helper.form.resources', $parameter);
- }
-}
diff --git a/DependencyInjection/Compiler/FormTwigTemplateCompilerPass.php b/DependencyInjection/Compiler/FormTwigTemplateCompilerPass.php
deleted file mode 100644
index 5a68a59e..00000000
--- a/DependencyInjection/Compiler/FormTwigTemplateCompilerPass.php
+++ /dev/null
@@ -1,54 +0,0 @@
-hasParameter('twig.form.resources')) {
- return;
- }
-
- $parameter = $container->getParameter('twig.form.resources');
-
- if (in_array($this->phoneNumberLayout, $parameter)) {
- return;
- }
-
- // Insert right after base template if it exists.
- if (($key = array_search('bootstrap_3_horizontal_layout.html.twig', $parameter)) !== false) {
- array_splice($parameter, ++$key, 0, array($this->phoneNumberBootstrapLayout));
- } elseif (($key = array_search('bootstrap_3_layout.html.twig', $parameter)) !== false) {
- array_splice($parameter, ++$key, 0, array($this->phoneNumberBootstrapLayout));
- } elseif (($key = array_search('form_div_layout.html.twig', $parameter)) !== false) {
- array_splice($parameter, ++$key, 0, array($this->phoneNumberLayout));
- } else {
- // Put it in first position.
- array_unshift($parameter, array($this->phoneNumberLayout));
- }
-
- $container->setParameter('twig.form.resources', $parameter);
- }
-}
diff --git a/DependencyInjection/Compiler/ParentLocalesCompilerPass.php b/DependencyInjection/Compiler/ParentLocalesCompilerPass.php
deleted file mode 100644
index a7495e68..00000000
--- a/DependencyInjection/Compiler/ParentLocalesCompilerPass.php
+++ /dev/null
@@ -1,131 +0,0 @@
- parent locale map (as defined in ICU).
- *
- * @var array
- */
- private $localParents = array(
- 'es_AR' => 'es_419',
- 'es_BO' => 'es_419',
- 'es_CL' => 'es_419',
- 'es_CO' => 'es_419',
- 'es_CR' => 'es_419',
- 'es_CU' => 'es_419',
- 'es_DO' => 'es_419',
- 'es_EC' => 'es_419',
- 'es_GT' => 'es_419',
- 'es_HN' => 'es_419',
- 'es_MX' => 'es_419',
- 'es_NI' => 'es_419',
- 'es_PA' => 'es_419',
- 'es_PE' => 'es_419',
- 'es_PR' => 'es_419',
- 'es_PY' => 'es_419',
- 'es_SV' => 'es_419',
- 'es_US' => 'es_419',
- 'es_UY' => 'es_419',
- 'es_VE' => 'es_419',
- );
-
- /**
- * {@inheritdoc}
- */
- public function process(ContainerBuilder $container)
- {
- try {
- $translator = $container->findDefinition('translator');
- } catch (InvalidArgumentException $e) {
- return;
- }
-
- if (
- 'Symfony\Component\Translation\Translator' !== $translator->getClass()
- &&
- false === is_subclass_of($translator->getClass(), 'Symfony\Component\Translation\Translator')
- ) {
- return;
- }
-
- $methodCalls = array_values($translator->getMethodCalls());
-
- foreach ($this->localParents as $locale => $parent) {
- $path = realpath(
- sprintf(
- '%s/../../Resources/translations/validators.%s.xlf',
- __DIR__,
- $parent
- )
- );
-
- if (false === $path) {
- continue;
- }
-
- $parentKey = null;
-
- // Find the position of the parent locale addResource() call so that
- // we can insert the child locale's call directly after rather than
- // just appending it. This means that the user can then still
- // override the translation.
- foreach ($methodCalls as $i => $methodCall) {
- if (
- 'addResource' === $methodCall[0]
- &&
- $path === realpath($methodCall[1][1])
- ) {
- $parentKey = $i;
- break;
- }
- }
-
- $extraMethodCall = array(
- 'addResource',
- array(
- 'xlf',
- $path,
- $locale,
- 'validators',
- ),
- );
-
- if (null === $parentKey) {
- $methodCalls[] = $extraMethodCall;
- } else {
- array_splice(
- $methodCalls,
- $parentKey + 1,
- 0,
- array($extraMethodCall)
- );
- }
- }
-
- $translator->setMethodCalls($methodCalls);
- }
-}
diff --git a/DependencyInjection/MisdPhoneNumberExtension.php b/DependencyInjection/MisdPhoneNumberExtension.php
deleted file mode 100644
index 904694c6..00000000
--- a/DependencyInjection/MisdPhoneNumberExtension.php
+++ /dev/null
@@ -1,75 +0,0 @@
-load('services.xml');
- if (interface_exists('Symfony\Component\Templating\Helper\HelperInterface')) {
- $loader->load('templating.xml');
- if (class_exists('Symfony\Bundle\TwigBundle\TwigBundle')) {
- $loader->load('twig.xml');
- }
- }
- if (interface_exists('Symfony\Component\Form\FormTypeInterface')) {
- $loader->load('form.xml');
- }
- if (interface_exists('Symfony\Component\Serializer\Normalizer\NormalizerInterface')) {
- $loader->load('serializer.xml');
- }
- if (class_exists('JMS\SerializerBundle\JMSSerializerBundle')) {
- $loader->load('jms_serializer.xml');
- }
-
- $this->setFactory($container->getDefinition('libphonenumber.phone_number_util'));
- $this->setFactory($container->getDefinition('libphonenumber.phone_number_offline_geocoder'));
- $this->setFactory($container->getDefinition('libphonenumber.short_number_info'));
- $this->setFactory($container->getDefinition('libphonenumber.phone_number_to_carrier_mapper'));
- $this->setFactory($container->getDefinition('libphonenumber.phone_number_to_time_zones_mapper'));
- }
-
- /**
- * Set Factory of FactoryClass & FactoryMethod based on Symfony version.
- *
- * to be removed when dependency on Symfony DependencyInjection is bumped to 2.6 and
- * services inlined in services.xml
- *
- * @param $def
- */
- private function setFactory(Definition $def)
- {
- if (method_exists($def, 'setFactory')) {
- // to be inlined in services.xml when dependency on Symfony DependencyInjection is bumped to 2.6
- $def->setFactory(array($def->getClass(), 'getInstance'));
- } else {
- // to be removed when dependency on Symfony DependencyInjection is bumped to 2.6
- $def->setFactoryClass($def->getClass());
- $def->setFactoryMethod('getInstance');
- }
- }
-}
diff --git a/Form/Type/PhoneNumberType.php b/Form/Type/PhoneNumberType.php
deleted file mode 100644
index d7990979..00000000
--- a/Form/Type/PhoneNumberType.php
+++ /dev/null
@@ -1,193 +0,0 @@
-getCountryCodeForRegion($country);
-
- if ($code) {
- $countries[$country] = $code;
- }
- }
- }
-
- if (empty($countries)) {
- foreach ($util->getSupportedRegions() as $country) {
- $countries[$country] = $util->getCountryCodeForRegion($country);
- }
- }
-
- $countryChoices = array();
-
- foreach (Intl::getRegionBundle()->getCountryNames() as $region => $name) {
- if (false === isset($countries[$region])) {
- continue;
- }
-
- $countryChoices[sprintf('%s (+%s)', $name, $countries[$region])] = $region;
- }
-
- $transformerChoices = array_values($countryChoices);
-
- $countryOptions = $numberOptions = array(
- 'error_bubbling' => true,
- 'required' => $options['required'],
- 'disabled' => $options['disabled'],
- 'translation_domain' => $options['translation_domain'],
- );
-
- if (method_exists('Symfony\\Component\\Form\\AbstractType', 'getBlockPrefix')) {
- $choiceType = 'Symfony\\Component\\Form\\Extension\\Core\\Type\\ChoiceType';
- $textType = 'Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType';
- $countryOptions['choice_translation_domain'] = false;
-
- // To be removed when dependency on Symfony Form is bumped to 3.1.
- if (!in_array('Symfony\\Component\\Form\\DataTransformerInterface', class_implements('Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType'))) {
- $countryOptions['choices_as_values'] = true;
- }
- } else {
- // To be removed when dependency on Symfony Form is bumped to 2.7.
- $choiceType = 'choice';
- $textType = 'text';
- $countryChoices = array_flip($countryChoices);
- }
-
- $countryOptions['required'] = true;
- $countryOptions['choices'] = $countryChoices;
- $countryOptions['preferred_choices'] = $options['preferred_country_choices'];
-
- if ($options['country_placeholder']) {
- $countryOptions['placeholder'] = $options['country_placeholder'];
- }
-
- $builder
- ->add('country', $choiceType, $countryOptions)
- ->add('number', $textType, $numberOptions)
- ->addViewTransformer(new PhoneNumberToArrayTransformer($transformerChoices));
- } else {
- $builder->addViewTransformer(
- new PhoneNumberToStringTransformer($options['default_region'], $options['format'])
- );
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function buildView(FormView $view, FormInterface $form, array $options)
- {
- $view->vars['type'] = 'tel';
- $view->vars['widget'] = $options['widget'];
- }
-
- /**
- * {@inheritdoc}
- *
- * @deprecated To be removed when the Symfony Form component compatibility
- * is bumped to at least 2.7.
- */
- public function setDefaultOptions(OptionsResolverInterface $resolver)
- {
- $this->configureOptions($resolver);
- }
-
- /**
- * {@inheritdoc}
- */
- public function configureOptions(OptionsResolver $resolver)
- {
- $resolver->setDefaults(
- array(
- 'widget' => self::WIDGET_SINGLE_TEXT,
- 'compound' => function (Options $options) {
- return PhoneNumberType::WIDGET_SINGLE_TEXT !== $options['widget'];
- },
- 'default_region' => PhoneNumberUtil::UNKNOWN_REGION,
- 'format' => PhoneNumberFormat::INTERNATIONAL,
- 'invalid_message' => 'This value is not a valid phone number.',
- 'by_reference' => false,
- 'error_bubbling' => false,
- 'country_choices' => array(),
- 'country_placeholder' => false,
- 'preferred_country_choices' => array(),
- )
- );
-
- if (method_exists($resolver, 'setDefault')) {
- $resolver->setAllowedValues(
- 'widget',
- array(
- self::WIDGET_SINGLE_TEXT,
- self::WIDGET_COUNTRY_CHOICE,
- )
- );
- } else {
- // To be removed when dependency on Symfony OptionsResolver is bumped to 2.6.
- $resolver->setAllowedValues(
- array(
- 'widget' => array(
- self::WIDGET_SINGLE_TEXT,
- self::WIDGET_COUNTRY_CHOICE,
- ),
- )
- );
- }
- }
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return $this->getBlockPrefix();
- }
-
- /**
- * {@inheritdoc}
- */
- public function getBlockPrefix()
- {
- return 'phone_number';
- }
-}
diff --git a/Resources/meta/LICENSE b/LICENSE
similarity index 100%
rename from Resources/meta/LICENSE
rename to LICENSE
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..e8e19de5
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,30 @@
+.PHONY: ${TARGETS}
+.DEFAULT_GOAL := help
+
+DIR := ${CURDIR}
+QA_IMAGE := jakzal/phpqa:latest
+
+help:
+ @echo "\033[33mUsage:\033[0m"
+ @echo " make [command]"
+ @echo ""
+ @echo "\033[33mAvailable commands:\033[0m"
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort \
+ | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[32m%s\033[0m___%s\n", $$1, $$2}' | column -ts___
+
+cs-lint: ## Verify check styles
+ composer install --working-dir=tools/php-cs-fixer
+ tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --dry-run --diff
+
+cs-fix: ## Apply Check styles
+ composer install --working-dir=tools/php-cs-fixer
+ tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff
+
+phpstan: ## Run PHPStan
+ composer install --working-dir=tools/phpstan
+ tools/phpstan/vendor/bin/phpstan analyze
+
+phpunit: ## Run phpunit
+ -./vendor/bin/phpunit
+
+test: phpunit cs-lint phpstan ## Run tests
diff --git a/README.md b/README.md
index 8d6aa1e2..53fbafac 100644
--- a/README.md
+++ b/README.md
@@ -1,45 +1,62 @@
-PhoneNumberBundle
-=================
+# PhoneNumberBundle
-[](https://travis-ci.org/misd-service-development/phone-number-bundle)
-[](https://packagist.org/packages/misd/phone-number-bundle)
-[](https://packagist.org/packages/misd/phone-number-bundle)
-[](https://packagist.org/packages/misd/phone-number-bundle)
-[](https://packagist.org/packages/misd/phone-number-bundle)
+[](https://github.com/odolbeau/phone-number-bundle/actions)
+[](https://packagist.org/packages/odolbeau/phone-number-bundle)
+[](https://packagist.org/packages/odolbeau/phone-number-bundle)
-This bundle integrates [Google's libphonenumber](https://github.com/googlei18n/libphonenumber) into your Symfony2-Symfony4 application through the [giggsey/libphonenumber-for-php](https://github.com/giggsey/libphonenumber-for-php) port.
+[](https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md)
-Installation
-------------
+
+**This bundle is a fork of [misd-service-development/phone-number-bundle](https://github.com/misd-service-development/phone-number-bundle). As this project doesn't look maintained anymore, we decided to create & maintain a fork.**
+
+This bundle integrates [Google's libphonenumber](https://github.com/googlei18n/libphonenumber) into your Symfony application through the [giggsey/libphonenumber-for-php](https://github.com/giggsey/libphonenumber-for-php) port.
+
+## Installation
1. Use Composer to download the PhoneNumberBundle:
```bash
- $ composer require misd/phone-number-bundle
+composer require odolbeau/phone-number-bundle
```
+if you're using Symfony Flex, that's all you have to do! Otherwise:
+
2. Register the bundle in your application:
```php
- // app/AppKernel.php
-
- public function registerBundles()
- {
- $bundles = array(
- // ...
- new Misd\PhoneNumberBundle\MisdPhoneNumberBundle()
- );
- }
+// app/AppKernel.php
+
+public function registerBundles()
+{
+ $bundles = [
+ // ...
+ new Misd\PhoneNumberBundle\MisdPhoneNumberBundle()
+ ];
+}
+```
+
+### Update from `misd/phone-number-bundle`
+
+The update from `misd/phone-number-bundle` to `odolbeau/phone-number-bundle` should be really easy.
+
+Update your composer.json:
+
+```diff
+- "misd/phone-number-bundle": "^1.3",
++ "odolbeau/phone-number-bundle": "^4.0",
```
-Usage
------
+Then run `composer update misd/phone-number-bundle odolbeau/phone-number-bundle`.
+
+If you're using a container parameter or alias defined by `misd/phone-number-bundle` you can use `"odolbeau/phone-number-bundle": "^2.0"` until your project is cleaned.
+
+## Usage
### Services
The following services are available:
-| Service | ID | libphonenumber version |
+| Service | ID (Removed in 3.0) | libphonenumber version |
| ----------------------------------------------------- | -------------------------------------------------- | ---------------------- |
| `libphonenumber\PhoneNumberUtil` | `libphonenumber.phone_number_util` | |
| `libphonenumber\geocoding\PhoneNumberOfflineGeocoder` | `libphonenumber.phone_number_offline_geocoder` | >=5.8.8 |
@@ -47,12 +64,13 @@ The following services are available:
| `libphonenumber\PhoneNumberToCarrierMapper` | `libphonenumber.phone_number_to_carrier_mapper` | >=5.8.8 |
| `libphonenumber\PhoneNumberToTimeZonesMapper` | `libphonenumber.phone_number_to_time_zones_mapper` | >=5.8.8 |
-So to parse a string into a `libphonenumber\PhoneNumber` object:
+To parse a string into a `libphonenumber\PhoneNumber` object, [inject the service](https://symfony.com/doc/current/service_container.html) and:
```php
- $phoneNumber = $container->get('libphonenumber.phone_number_util')->parse($string, PhoneNumberUtil::UNKNOWN_REGION);
+ $phoneNumber = $this->phoneNumberUtil->parse($string, PhoneNumberUtil::UNKNOWN_REGION);
```
+
### Doctrine mapping
*Requires `doctrine/doctrine-bundle`.*
@@ -60,107 +78,50 @@ So to parse a string into a `libphonenumber\PhoneNumber` object:
To persist `libphonenumber\PhoneNumber` objects, add the `Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType` mapping to your application's config:
```yml
- // app/config.yml
+// app/config.yml
- doctrine:
- dbal:
- types:
- phone_number: Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType
+doctrine:
+ dbal:
+ types:
+ phone_number: Misd\PhoneNumberBundle\Doctrine\DBAL\Types\PhoneNumberType
```
You can then use the `phone_number` mapping:
```php
- /**
- * @ORM\Column(type="phone_number")
- */
- private $phoneNumber;
+/**
+ * @ORM\Column(type="phone_number")
+ */
+private $phoneNumber;
```
This creates a `varchar(35)` column with a Doctrine mapping comment.
Note that if you're putting the `phone_number` type on an already-existing schema the current values must be converted to the `libphonenumber\PhoneNumberFormat::E164` format.
-### Templating
+### Twig Templating
-#### Twig
+If any of the `form_div_layout`, `bootstrap_3_*`, `bootstrap_4_*` or `bootstrap_5_*` layouts are registered in your twig configuration, the bundle will automatically register the template used to render the `Misd\PhoneNumberBundle\Form\Type` form type.
+#### phone_number_format
-##### phone_number_format
The `phone_number_format` filter can be used to format a phone number object. A `libphonenumber\PhoneNumberFormat` constant can be passed as argument to specify in which format the number should be printed.
For example, to format an object called `myPhoneNumber` in the `libphonenumber\PhoneNumberFormat::NATIONAL` format:
```php
- {{ myPhoneNumber|phone_number_format('NATIONAL') }}
+{{ myPhoneNumber|phone_number_format('NATIONAL') }}
```
By default phone numbers are formatted in the `libphonenumber\PhoneNumberFormat::INTERNATIONAL` format.
-###### phone_number_of_type
+##### phone_number_of_type
The `phone_number_of_type` test can be used to check a phone number against a type: A `libphonenumber\PhoneNumberType` constant name must be passed to specify to which type a number has to match.
For example, to check if an object called `myPhoneNumber` is a `libphonenumber\PhoneNumberType::MOBILE` type:
```php
- {% if myPhoneNumber is phone_number_of_type('MOBILE') }} %} ... {% endif %}
-```
-
-#### PHP template
-
-##### format()
-
-The `format()` method in the `phone_number_helper` takes two arguments: a `libphonenumber\PhoneNumber` object and an optional `libphonenumber\PhoneNumberFormat` constant name or value.
-
-For example, to format `$myPhoneNumber` in the `libphonenumber\PhoneNumberFormat::NATIONAL` format, either use:
-
-```php
- format($myPhoneNumber, 'NATIONAL') ?>
-```
-
-or:
-
-```php
- format($myPhoneNumber, \libphonenumber\PhoneNumberFormat::NATIONAL) ?>
-```
-
-By default phone numbers are formatted in the `libphonenumber\PhoneNumberFormat::INTERNATIONAL` format.
-
-###### isType()
-
-The `isType()` method in the `phone_number_helper` takes two arguments: a `libphonenumber\PhoneNumber` object and an optional `libphonenumber\PhoneNumberType` constant name or value.
-
-For example, to check if $myPhoneNumber` is a `libphonenumber\PhoneNumberType::MOBILE` type:
-
-```php
- isType($myPhoneNumber, 'MOBILE'): ?>
- ...
-
-```
-
-or:
-
-```php
- isType($myPhoneNumber, \libphonenumber\PhoneNumberType::MOBILE): ?>
- ...
-
-```
-
-### Serializing `libphonenumber\PhoneNumber` objects
-
-*Requires `jms/serializer-bundle`.*
-
-Instances of `libphonenumber\PhoneNumber` are automatically serialized in the E.164 format.
-
-Phone numbers can be deserialized from an international format by setting the type to `libphonenumber\PhoneNumber`. For example:
-
-```php
- use JMS\Serializer\Annotation\Type;
-
- /**
- * @Type("libphonenumber\PhoneNumber")
- */
- private $phoneNumber;
+{% if myPhoneNumber is phone_number_of_type('MOBILE') }} %} ... {% endif %}
```
### Using `libphonenumber\PhoneNumber` objects in forms
@@ -172,14 +133,14 @@ You can use the `PhoneNumberType` (`phone_number` for Symfony 2.7) form type to
A single text field allows the user to type in the complete phone number. When an international prefix is not entered, the number is assumed to be part of the set `default_region`. For example:
```php
- use libphonenumber\PhoneNumberFormat;
- use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
- use Symfony\Component\Form\FormBuilderInterface;
-
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder->add('phone_number', PhoneNumberType::class, array('default_region' => 'GB', 'format' => PhoneNumberFormat::NATIONAL));
- }
+use libphonenumber\PhoneNumberFormat;
+use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
+use Symfony\Component\Form\FormBuilderInterface;
+
+public function buildForm(FormBuilderInterface $builder, array $options)
+{
+ $builder->add('phoneNumber', PhoneNumberType::class, ['default_region' => 'GB', 'format' => PhoneNumberFormat::NATIONAL]);
+}
```
By default the `default_region` and `format` options are `PhoneNumberUtil::UNKNOWN_REGION` and `PhoneNumberFormat::INTERNATIONAL` respectively.
@@ -189,14 +150,18 @@ By default the `default_region` and `format` options are `PhoneNumberUtil::UNKNO
The phone number can be split into a country choice and phone number text fields. This allows the user to choose the relevant country (from a customisable list) and type in the phone number without international dialling.
```php
- use libphonenumber\PhoneNumberFormat;
- use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
- use Symfony\Component\Form\FormBuilderInterface;
-
- public function buildForm(FormBuilderInterface $builder, array $options)
- {
- $builder->add('phone_number', PhoneNumberType::class, array('widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE, 'country_choices' => array('GB', 'JE', 'FR', 'US'), 'preferred_country_choices' => array('GB', 'JE')));
- }
+use libphonenumber\PhoneNumberFormat;
+use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
+use Symfony\Component\Form\FormBuilderInterface;
+
+public function buildForm(FormBuilderInterface $builder, array $options)
+{
+ $builder->add('phoneNumber', PhoneNumberType::class, [
+ 'widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE,
+ 'country_choices' => ['GB', 'JE', 'FR', 'US'],
+ 'preferred_country_choices' => ['GB', 'JE']
+ ]);
+}
```
This produces the preferred choices of 'Jersey' and 'United Kingdom', and regular choices of 'France' and 'United States'.
@@ -204,28 +169,63 @@ This produces the preferred choices of 'Jersey' and 'United Kingdom', and regula
By default the `country_choices` is empty, which means all countries are included, as is `preferred_country_choices`.
The option `country_placeholder` can be specified to create a placeholder option on above the whole list.
+The option `country_display_type` can be specified to change the country dropdown label format. There are two formats available :
+| display type | Result |
+| ----------------------------------| -----------------------|
+| `display_country_full` (default) | United Kingdom (+44) |
+| `display_country_short` | GB +44 |
+
+And with the option `country_display_emoji_flag` set to `true` (default is `false`) you can add the emoji flag of the country before the label :
+| display type | Result |
+| ----------------------------------| --------------------------|
+| `display_country_full` (default) | đŦđ§ United Kingdom (+44) |
+| `display_country_short` | đŦđ§ GB +44 |
+
### Validating phone numbers
+âšī¸ _Using a Symfony or PHP version that does not support attributes? This bundle also supports validation as annotation.
+Take a look at the [old documentation](https://github.com/odolbeau/phone-number-bundle/blob/v3.4.2/README.md#validating-phone-numbers)._
+
You can use the `Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber` constraint to make sure that either a `libphonenumber\PhoneNumber` object or a plain string is a valid phone number. For example:
```php
- use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
+use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
- /**
- * @AssertPhoneNumber
- */
- private $phoneNumber;
+ #[AssertPhoneNumber()]
+private $phoneNumber;
```
You can set the default region through the `defaultRegion` property:
```php
- use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
+use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
+
+ #[AssertPhoneNumber(defaultRegion: 'GB')]
+private $phoneNumber;
+```
+
+You can also set default region in the bundle config:
+
+```yaml
+misd_phone_number:
+ validator:
+ default_region: GB
+```
+
+You can also define a region dynamically according to the context of the validated object thanks to the "regionPath" property (here according to the user's region):
+
+```php
+use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
+
+#[AssertPhoneNumber(regionPath: 'countryCode')]
+private $phoneNumber;
+
+private $countryCode;
- /**
- * @AssertPhoneNumber(defaultRegion="GB")
- */
- private $phoneNumber;
+public function getCountryCode()
+{
+ return $this->countryCode;
+}
```
By default any valid phone number will be accepted. You can restrict the type through the `type` property, recognised values:
@@ -245,10 +245,13 @@ By default any valid phone number will be accepted. You can restrict the type th
(Note that libphonenumber cannot always distinguish between mobile and fixed-line numbers (eg in the USA), in which case it will be accepted.)
```php
- /**
- * @AssertPhoneNumber(type="mobile")
- */
- private $mobilePhoneNumber;
+use Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber as AssertPhoneNumber;
+
+#[AssertPhoneNumber(type: [AssertPhoneNumber::MOBILE])]
+private $mobilePhoneNumber;
+
+#[AssertPhoneNumber(type: [AssertPhoneNumber::FIXED_LINE, AssertPhoneNumber::VOIP])]
+private $fixedOrVoipPhoneNumber;
```
### Translations
@@ -258,3 +261,19 @@ The bundle contains translations for the form field and validation constraints.
In cases where a language uses multiple terms for mobile phones, the generic language locale will use the term 'mobile', while country-specific locales will use the relevant term. So in English, for example, `en` uses 'mobile', `en_US` uses 'cell' and `en_SG` uses 'handphone'.
If your language doesn't yet have translations, feel free to open a pull request to add them in!
+
+### Configuration
+
+To disable integrations with components
+
+```yaml
+misd_phone_number:
+ twig: false
+ form: false
+ serializer: false
+ validator: false
+```
+
+## License
+
+This bundle is released under the MIT License. See the bundled LICENSE file for details.
diff --git a/Resources/config/jms_serializer.xml b/Resources/config/jms_serializer.xml
deleted file mode 100644
index c9ddbb65..00000000
--- a/Resources/config/jms_serializer.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
- Misd\PhoneNumberBundle\Serializer\Handler\PhoneNumberHandler
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Resources/config/services.xml b/Resources/config/services.xml
deleted file mode 100644
index f66c7ffb..00000000
--- a/Resources/config/services.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
- libphonenumber\PhoneNumberUtil
- libphonenumber\geocoding\PhoneNumberOfflineGeocoder
- libphonenumber\ShortNumberInfo
- libphonenumber\PhoneNumberToCarrierMapper
- libphonenumber\PhoneNumberToTimeZonesMapper
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Resources/config/templating.xml b/Resources/config/templating.xml
deleted file mode 100644
index 9c7cedae..00000000
--- a/Resources/config/templating.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
- Misd\PhoneNumberBundle\Templating\Helper\PhoneNumberHelper
- %misd_phone_number.templating.helper.class%
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Resources/config/twig.xml b/Resources/config/twig.xml
deleted file mode 100644
index 0a2ab6ac..00000000
--- a/Resources/config/twig.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
- Misd\PhoneNumberBundle\Twig\Extension\PhoneNumberHelperExtension
- %misd_phone_number.twig.extension.class%
-
-
-
-
-
-
-
-
-
-
-
diff --git a/Resources/views/Form/phone_number_widget.html.php b/Resources/views/Form/phone_number_widget.html.php
deleted file mode 100644
index 133c03cd..00000000
--- a/Resources/views/Form/phone_number_widget.html.php
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
block($form, 'widget_container_attributes') ?>>
- widget($form['country']).$view['form']->widget($form['number']); ?>
-
-
- block($form, 'form_widget_simple'); ?>
-
diff --git a/Serializer/Handler/PhoneNumberHandler.php b/Serializer/Handler/PhoneNumberHandler.php
deleted file mode 100644
index d0a6d98a..00000000
--- a/Serializer/Handler/PhoneNumberHandler.php
+++ /dev/null
@@ -1,100 +0,0 @@
-phoneNumberUtil = $phoneNumberUtil;
- }
-
- /**
- * Serialize a phone number.
- *
- * @param VisitorInterface $visitor Serialization visitor.
- * @param PhoneNumber $phoneNumber Phone number.
- * @param array $type Type.
- * @param mixed $context Context.
- *
- * @return mixed Serialized phone number.
- */
- public function serializePhoneNumber(VisitorInterface $visitor, PhoneNumber $phoneNumber, array $type, $context)
- {
- $formatted = $this->phoneNumberUtil->format($phoneNumber, PhoneNumberFormat::E164);
-
- return $visitor->visitString($formatted, $type, $context);
- }
-
- /**
- * Deserialize a phone number from JSON.
- *
- * @param JsonDeserializationVisitor $visitor Deserialization visitor.
- * @param string|null $data Data.
- * @param array $type Type.
- *
- * @return PhoneNumber|null Phone number.
- */
- public function deserializePhoneNumberFromJson(JsonDeserializationVisitor $visitor, $data, array $type)
- {
- if (null === $data) {
- return null;
- }
-
- return $this->phoneNumberUtil->parse($data, PhoneNumberUtil::UNKNOWN_REGION);
- }
-
- /**
- * Deserialize a phone number from XML.
- *
- * @param XmlDeserializationVisitor $visitor Deserialization visitor.
- * @param SimpleXMLElement $data Data.
- * @param array $type Type.
- *
- * @return PhoneNumber|null Phone number.
- */
- public function deserializePhoneNumberFromXml(XmlDeserializationVisitor $visitor, $data, array $type)
- {
- $attributes = $data->attributes();
- if (
- (isset($attributes['nil'][0]) && (string) $attributes['nil'][0] === 'true') ||
- (isset($attributes['xsi:nil'][0]) && (string) $attributes['xsi:nil'][0] === 'true')
- ) {
- return null;
- }
-
- return $this->phoneNumberUtil->parse($data, PhoneNumberUtil::UNKNOWN_REGION);
- }
-}
diff --git a/Templating/Helper/PhoneNumberFormatHelper.php b/Templating/Helper/PhoneNumberFormatHelper.php
deleted file mode 100644
index 809cd0b2..00000000
--- a/Templating/Helper/PhoneNumberFormatHelper.php
+++ /dev/null
@@ -1,17 +0,0 @@
-phoneNumberUtil = $phoneNumberUtil;
- }
-
- /**
- * {@inheritdoc}
- */
- public function setCharset($charset)
- {
- $this->charset = $charset;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getCharset()
- {
- return $this->charset;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'phone_number_helper';
- }
-
- /**
- * Format a phone number.
- *
- * @param PhoneNumber $phoneNumber Phone number.
- * @param int|string $format Format, or format constant name.
- *
- * @return string Formatted phone number.
- *
- * @throws InvalidArgumentException If an argument is invalid.
- */
- public function format(PhoneNumber $phoneNumber, $format = PhoneNumberFormat::INTERNATIONAL)
- {
- if (true === is_string($format)) {
- $constant = '\libphonenumber\PhoneNumberFormat::' . $format;
-
- if (false === defined($constant)) {
- throw new InvalidArgumentException('The format must be either a constant value or name in libphonenumber\PhoneNumberFormat');
- }
-
- $format = constant('\libphonenumber\PhoneNumberFormat::' . $format);
- }
-
- return $this->phoneNumberUtil->format($phoneNumber, $format);
- }
-
- /**
- * @param PhoneNumber $phoneNumber Phone number.
- * @param int|string $type PhoneNumberType, or PhoneNumberType constant name.
- *
- * @return bool
- *
- * @throws InvalidArgumentException If type argument is invalid.
- */
- public function isType(PhoneNumber $phoneNumber, $type = PhoneNumberType::UNKNOWN)
- {
- if (true === is_string($type)) {
- $constant = '\libphonenumber\PhoneNumberType::' . $type;
-
- if (false === defined($constant)) {
- throw new InvalidArgumentException('The format must be either a constant value or name in libphonenumber\PhoneNumberType');
- }
-
- $type = constant('\libphonenumber\PhoneNumberType::' . $type);
- }
-
- return $this->phoneNumberUtil->getNumberType($phoneNumber) === $type ? true : false;
- }
-}
diff --git a/Tests/DependencyInjection/Compiler/ParentLocalesCompilerPassTest.php b/Tests/DependencyInjection/Compiler/ParentLocalesCompilerPassTest.php
deleted file mode 100644
index c9a940c0..00000000
--- a/Tests/DependencyInjection/Compiler/ParentLocalesCompilerPassTest.php
+++ /dev/null
@@ -1,79 +0,0 @@
-process($container);
- }
-
- public function testDifferentTranslator()
- {
- $compilerPass = new ParentLocalesCompilerPass();
-
- $container = new ContainerBuilder();
-
- $translatorDefinition = new Definition(
- 'Symfony\Component\Translation\IdentityTranslator'
- );
-
- $container->setDefinition('translator', $translatorDefinition);
-
- $compilerPass->process($container);
-
- $this->assertEmpty($translatorDefinition->getMethodCalls());
- }
-
- public function testTranslator()
- {
- $compilerPass = new ParentLocalesCompilerPass();
-
- $container = new ContainerBuilder();
-
- $translatorDefinition = new Definition(
- 'Symfony\Component\Translation\Translator'
- );
-
- $container->setDefinition('translator', $translatorDefinition);
-
- $compilerPass->process($container);
-
- $this->assertCount(
- count($this->getLocalParents($compilerPass)),
- $translatorDefinition->getMethodCalls()
- );
- }
-
- private function getLocalParents(ParentLocalesCompilerPass $compilerPass)
- {
- $reflectionClass = new ReflectionClass('Misd\PhoneNumberBundle\DependencyInjection\Compiler\ParentLocalesCompilerPass');
- $reflectionProperty = $reflectionClass->getProperty('localParents');
- $reflectionProperty->setAccessible(true);
-
- return $reflectionProperty->getValue($compilerPass);
- }
-}
diff --git a/Tests/DependencyInjection/MisdPhoneNumberExtensionTest.php b/Tests/DependencyInjection/MisdPhoneNumberExtensionTest.php
deleted file mode 100644
index c0398c86..00000000
--- a/Tests/DependencyInjection/MisdPhoneNumberExtensionTest.php
+++ /dev/null
@@ -1,160 +0,0 @@
-container = new ContainerBuilder();
-
- $extension->load(array(), $this->container);
-
- $this->assertHasService(
- 'libphonenumber.phone_number_util',
- 'libphonenumber\PhoneNumberUtil'
- );
- if (class_exists('libphonenumber\geocoding\PhoneNumberOfflineGeocoder') && extension_loaded('intl')) {
- $this->assertHasService(
- 'libphonenumber.phone_number_offline_geocoder',
- 'libphonenumber\geocoding\PhoneNumberOfflineGeocoder'
- );
- }
- if (class_exists('libphonenumber\ShortNumberInfo')) {
- $this->assertHasService(
- 'libphonenumber.short_number_info',
- 'libphonenumber\ShortNumberInfo'
- );
- }
- if (class_exists('libphonenumber\PhoneNumberToCarrierMapper') && extension_loaded('intl')) {
- $this->assertHasService(
- 'libphonenumber.phone_number_to_carrier_mapper',
- 'libphonenumber\PhoneNumberToCarrierMapper'
- );
- }
- if (class_exists('libphonenumber\PhoneNumberToTimeZonesMapper')) {
- $this->assertHasService(
- 'libphonenumber.phone_number_to_time_zones_mapper',
- 'libphonenumber\PhoneNumberToTimeZonesMapper'
- );
- }
- $this->assertHasService(
- 'misd_phone_number.templating.helper',
- 'Misd\PhoneNumberBundle\Templating\Helper\PhoneNumberHelper'
- );
- $this->assertServiceHasTag(
- 'misd_phone_number.templating.helper',
- 'templating.helper',
- array('alias' => 'phone_number_helper')
- );
-
- // Assert deprecated 'phone_number_format' alias
- $this->assertServiceHasTag(
- 'misd_phone_number.templating.helper',
- 'templating.helper',
- array('alias' => 'phone_number_format')
- );
-
- $this->assertHasService(
- 'misd_phone_number.form.type',
- 'Misd\PhoneNumberBundle\Form\Type\PhoneNumberType'
- );
- $this->assertServiceHasTag(
- 'misd_phone_number.form.type',
- 'form.type',
- array('alias' => 'phone_number')
- );
- $this->assertHasService(
- 'misd_phone_number.serializer.handler',
- 'Misd\PhoneNumberBundle\Serializer\Handler\PhoneNumberHandler'
- );
- $this->assertServiceHasTag(
- 'misd_phone_number.serializer.handler',
- 'jms_serializer.handler',
- array(
- 'type' => 'libphonenumber\PhoneNumber',
- 'direction' => 'serialization',
- 'format' => 'json',
- 'method' => 'serializePhoneNumber',
- )
- );
- $this->assertServiceHasTag(
- 'misd_phone_number.serializer.handler',
- 'jms_serializer.handler',
- array(
- 'type' => 'libphonenumber\PhoneNumber',
- 'direction' => 'deserialization',
- 'format' => 'json',
- 'method' => 'deserializePhoneNumberFromJson',
- )
- );
- $this->assertServiceHasTag(
- 'misd_phone_number.serializer.handler',
- 'jms_serializer.handler',
- array(
- 'type' => 'libphonenumber\PhoneNumber',
- 'direction' => 'serialization',
- 'format' => 'xml',
- 'method' => 'serializePhoneNumber',
- )
- );
- $this->assertServiceHasTag(
- 'misd_phone_number.serializer.handler',
- 'jms_serializer.handler',
- array(
- 'type' => 'libphonenumber\PhoneNumber',
- 'direction' => 'deserialization',
- 'format' => 'xml',
- 'method' => 'deserializePhoneNumberFromXml',
- )
- );
- $this->assertServiceHasTag(
- 'misd_phone_number.serializer.handler',
- 'jms_serializer.handler',
- array(
- 'type' => 'libphonenumber\PhoneNumber',
- 'direction' => 'serialization',
- 'format' => 'yml',
- 'method' => 'serializePhoneNumber',
- )
- );
- }
-
- protected function assertHasService($id, $instanceOf)
- {
- $this->assertTrue($this->container->has($id));
- $this->assertInstanceOf($instanceOf, $this->container->get($id));
- }
-
- protected function assertServiceHasTag($id, $tag, $attributes = array())
- {
- $services = $this->container->findTaggedServiceIds($tag);
-
- $this->assertArrayHasKey($id, $services);
- $this->assertContains($attributes, $services[$id]);
- }
-}
diff --git a/Tests/Doctrine/DBAL/Types/PhoneNumberTypeTest.php b/Tests/Doctrine/DBAL/Types/PhoneNumberTypeTest.php
deleted file mode 100644
index 6694237e..00000000
--- a/Tests/Doctrine/DBAL/Types/PhoneNumberTypeTest.php
+++ /dev/null
@@ -1,129 +0,0 @@
-platform = $this->getMockBuilder('Doctrine\DBAL\Platforms\AbstractPlatform')
- ->setMethods(array('getVarcharTypeDeclarationSQL'))
- ->getMockForAbstractClass();
-
- $this->platform->expects($this->any())
- ->method('getVarcharTypeDeclarationSQL')
- ->will($this->returnValue('DUMMYVARCHAR()'));
-
- $this->type = Type::getType('phone_number');
- $this->phoneNumberUtil = PhoneNumberUtil::getInstance();
- }
-
- public function testInstanceOf()
- {
- $this->assertInstanceOf('Doctrine\DBAL\Types\Type', $this->type);
- }
-
- public function testGetName()
- {
- $this->assertSame('phone_number', $this->type->getName());
- }
-
- public function testGetSQLDeclaration()
- {
- $this->assertSame('DUMMYVARCHAR()', $this->type->getSQLDeclaration(array(), $this->platform));
- }
-
- public function testConvertToDatabaseValueWithNull()
- {
- $this->assertNull($this->type->convertToDatabaseValue(null, $this->platform));
- }
-
- public function testConvertToDatabaseValueWithPhoneNumber()
- {
- $phoneNumber = $this->phoneNumberUtil->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION);
-
- $this->assertSame('+441234567890', $this->type->convertToDatabaseValue($phoneNumber, $this->platform));
- }
-
- /**
- * @expectedException \Doctrine\DBAL\Types\ConversionException
- */
- public function testConvertToDatabaseValueFailure()
- {
- $this->type->convertToDatabaseValue('foo', $this->platform);
- }
-
- public function testConvertToPHPValueWithNull()
- {
- $this->assertNull($this->type->convertToPHPValue(null, $this->platform));
- }
-
- public function testConvertToPHPValueWithPhoneNumber()
- {
- $phoneNumber = $this->type->convertToPHPValue('+441234567890', $this->platform);
-
- $this->assertInstanceOf('libphoneNumber\PhoneNumber', $phoneNumber);
- $this->assertSame('+441234567890', $this->phoneNumberUtil->format($phoneNumber, PhoneNumberFormat::E164));
- }
-
- public function testConvertToPHPValueWithAPhoneNumberInstance()
- {
- $expectedPhoneNumber = $this->phoneNumberUtil->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION);
-
- $phoneNumber = $this->type->convertToPHPValue($expectedPhoneNumber, $this->platform);
-
- $this->assertEquals($expectedPhoneNumber, $phoneNumber);
- }
-
- /**
- * @expectedException \Doctrine\DBAL\Types\ConversionException
- */
- public function testConvertToPHPValueFailure()
- {
- $this->type->convertToPHPValue('foo', $this->platform);
- }
-
- public function testRequiresSQLCommentHint()
- {
- $this->assertTrue($this->type->requiresSQLCommentHint($this->platform));
- }
-}
diff --git a/Tests/Form/DataTransformer/PhoneNumberToArrayTransformerTest.php b/Tests/Form/DataTransformer/PhoneNumberToArrayTransformerTest.php
deleted file mode 100644
index 90d1681a..00000000
--- a/Tests/Form/DataTransformer/PhoneNumberToArrayTransformerTest.php
+++ /dev/null
@@ -1,183 +0,0 @@
-phoneNumberUtil = PhoneNumberUtil::getInstance();
- }
-
- public function testConstructor()
- {
- $transformer = new PhoneNumberToArrayTransformer(array());
-
- $this->assertInstanceOf('Symfony\Component\Form\DataTransformerInterface', $transformer);
- }
-
- /**
- * @dataProvider transformProvider
- */
- public function testTransform(array $countryChoices, $actual, $expected)
- {
- $transformer = new PhoneNumberToArrayTransformer($countryChoices);
-
- if (is_array($actual)) {
- try {
- $phoneNumber = $this->phoneNumberUtil->parse($actual['number'], $actual['country']);
- } catch (NumberParseException $e) {
- $phoneNumber = $actual['number'];
- }
- } else {
- $phoneNumber = $actual['number'];
- }
-
- try {
- $transformed = $transformer->transform($phoneNumber);
- } catch (TransformationFailedException $e) {
- $transformed = self::TRANSFORMATION_FAILED;
- }
-
- $this->assertSame($expected, $transformed);
- }
-
- /**
- * 0 => Country choices
- * 1 => Actual value
- * 2 => Expected result
- */
- public function transformProvider()
- {
- return array(
- array(
- array('GB'),
- null,
- array('country' => '', 'number' => ''),
- ),
- array(
- array('GB'),
- array('country' => 'GB', 'number' => '01234567890'),
- array('country' => 'GB', 'number' => '01234 567890'),
- ),
- array(// Wrong country code, but matching country exists.
- array('GB', 'JE'),
- array('country' => 'JE', 'number' => '01234567890'),
- array('country' => 'GB', 'number' => '01234 567890'),
- ),
- array(// Wrong country code, but matching country exists.
- array('GB', 'JE'),
- array('country' => 'JE', 'number' => '+441234567890'),
- array('country' => 'GB', 'number' => '01234 567890'),
- ),
- array(// Country code not in list.
- array('US'),
- array('country' => 'GB', 'number' => '01234567890'),
- self::TRANSFORMATION_FAILED,
- ),
- array(
- array('US'),
- array('country' => 'GB', 'number' => 'foo'),
- self::TRANSFORMATION_FAILED,
- ),
- );
- }
-
- /**
- * @dataProvider reverseTransformProvider
- */
- public function testReverseTransform(array $countryChoices, $actual, $expected)
- {
- $transformer = new PhoneNumberToArrayTransformer($countryChoices);
-
- try {
- $transformed = $transformer->reverseTransform($actual);
- } catch (TransformationFailedException $e) {
- $transformed = self::TRANSFORMATION_FAILED;
- }
-
- if ($transformed instanceof PhoneNumber) {
- $transformed = $this->phoneNumberUtil->format($transformed, PhoneNumberFormat::E164);
- }
-
- $this->assertSame($expected, $transformed);
- }
-
- /**
- * 0 => Country choices
- * 1 => Actual value
- * 2 => Expected result
- */
- public function reverseTransformProvider()
- {
- return array(
- array(
- array('GB'),
- null,
- null,
- ),
- array(
- array('GB'),
- 'foo',
- self::TRANSFORMATION_FAILED,
- ),
- array(
- array('GB'),
- array('country' => '', 'number' => ''),
- null,
- ),
- array(
- array('GB'),
- array('country' => 'GB', 'number' => ''),
- null,
- ),
- array(
- array('GB'),
- array('country' => '', 'number' => 'foo'),
- self::TRANSFORMATION_FAILED,
- ),
- array(
- array('GB'),
- array('country' => 'GB', 'number' => '01234 567890'),
- '+441234567890',
- ),
- array(
- array('GB'),
- array('country' => 'GB', 'number' => '+44 1234 567890'),
- '+441234567890',
- ),
- array(// Country code not in list.
- array('US'),
- array('country' => 'GB', 'number' => '+44 1234 567890'),
- self::TRANSFORMATION_FAILED,
- ),
- );
- }
-}
diff --git a/Tests/Form/Type/PhoneNumberTypeTest.php b/Tests/Form/Type/PhoneNumberTypeTest.php
deleted file mode 100644
index df4b615f..00000000
--- a/Tests/Form/Type/PhoneNumberTypeTest.php
+++ /dev/null
@@ -1,264 +0,0 @@
-factory->create($type, null, $options);
-
- $form->submit($input);
-
- if(method_exists($form, 'getTransformationFailure') && $failure = $form->getTransformationFailure()) {
- throw $failure;
- } else {
- $this->assertTrue($form->isSynchronized());
- }
-
- $view = $form->createView();
-
- $this->assertSame('tel', $view->vars['type']);
- $this->assertSame($output, $view->vars['value']);
- }
-
- /**
- * 0 => Input
- * 1 => Options
- * 2 => Output
- */
- public function singleFieldProvider()
- {
- return array(
- array('+441234567890', array(), '+44 1234 567890'),
- array('+44 1234 567890', array('format' => PhoneNumberFormat::NATIONAL), '+44 1234 567890'),
- array('+44 1234 567890', array('default_region' => 'GB', 'format' => PhoneNumberFormat::NATIONAL), '01234 567890'),
- array('+1 650-253-0000', array('default_region' => 'GB', 'format' => PhoneNumberFormat::NATIONAL), '00 1 650-253-0000'),
- array('01234 567890', array('default_region' => 'GB'), '+44 1234 567890'),
- array('', array(), ''),
- );
- }
-
- /**
- * @dataProvider countryChoiceValuesProvider
- */
- public function testCountryChoiceValues($input, $options, $output)
- {
- $options['widget'] = PhoneNumberType::WIDGET_COUNTRY_CHOICE;
- if (method_exists('Symfony\\Component\\Form\\FormTypeInterface', 'getName')) {
- $type = new PhoneNumberType();
- } else {
- $type = 'Misd\\PhoneNumberBundle\\Form\\Type\\PhoneNumberType';
- }
- $form = $this->factory->create($type, null, $options);
-
- $form->submit($input);
-
- if(method_exists($form, 'getTransformationFailure') && $failure = $form->getTransformationFailure()) {
- throw $failure;
- } else {
- $this->assertTrue($form->isSynchronized());
- }
-
- $view = $form->createView();
-
- $this->assertSame('tel', $view->vars['type']);
- $this->assertSame($output, $view->vars['value']);
- }
-
- /**
- * 0 => Input
- * 1 => Options
- * 2 => Output
- */
- public function countryChoiceValuesProvider()
- {
- return array(
- array(array('country' => 'GB', 'number' => '01234 567890'), array(), array('country' => 'GB', 'number' => '01234 567890')),
- array(array('country' => 'GB', 'number' => '+44 1234 567890'), array(), array('country' => 'GB', 'number' => '01234 567890')),
- array(array('country' => 'GB', 'number' => '1234 567890'), array(), array('country' => 'GB', 'number' => '01234 567890')),
- array(array('country' => 'GB', 'number' => '+1 650-253-0000'), array(), array('country' => 'US', 'number' => '(650) 253-0000')),
- array(array('country' => '', 'number' => ''), array(), array('country' => '', 'number' => '')),
- );
- }
-
- /**
- * @dataProvider countryChoiceChoicesProvider
- */
- public function testCountryChoiceChoices(array $choices, $expectedChoicesCount, array $expectedChoices)
- {
- IntlTestHelper::requireIntl($this);
-
- if (method_exists('Symfony\\Component\\Form\\FormTypeInterface', 'getName')) {
- $type = new PhoneNumberType();
- } else {
- $type = 'Misd\\PhoneNumberBundle\\Form\\Type\\PhoneNumberType';
- }
- $form = $this->factory->create($type, null, array('widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE, 'country_choices' => $choices));
-
- $view = $form->createView();
- $choices = $view['country']->vars['choices'];
-
- $this->assertCount($expectedChoicesCount, $choices);
- foreach ($expectedChoices as $expectedChoice) {
- $this->assertContains($expectedChoice, $choices, '', false, false);
- }
- }
-
- /**
- * 0 => Choices
- * 1 => Expected choices count
- * 2 => Expected choices
- */
- public function countryChoiceChoicesProvider()
- {
- return array(
- array(
- array(),
- count(PhoneNumberUtil::getInstance()->getSupportedRegions()),
- array(
- $this->createChoiceView('United Kingdom (+44)', 'GB'),
- ),
- ),
- array(
- array('GB', 'US'),
- 2,
- array(
- $this->createChoiceView('United Kingdom (+44)', 'GB'),
- $this->createChoiceView('United States (+1)', 'US'),
- ),
- ),
- array(
- array('GB', 'US', PhoneNumberUtil::UNKNOWN_REGION),
- 2,
- array(
- $this->createChoiceView('United Kingdom (+44)', 'GB'),
- $this->createChoiceView('United States (+1)', 'US'),
- ),
- ),
- );
- }
-
- /**
- * @dataProvider countryChoicePlaceholderProvider
- * @param $placeholder
- * @param $expectedPlaceholder
- */
- public function testCountryChoicePlaceholder($placeholder, $expectedPlaceholder)
- {
- IntlTestHelper::requireIntl($this);
- if (method_exists('Symfony\\Component\\Form\\FormTypeInterface', 'getName')) {
- $type = new PhoneNumberType();
- } else {
- $type = 'Misd\\PhoneNumberBundle\\Form\\Type\\PhoneNumberType';
- }
- $form = $this->factory->create($type, null, array('widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE, 'country_placeholder' => $placeholder));
-
- $view = $form->createView();
- $renderedPlaceholder = $view['country']->vars['placeholder'];
- $this->assertEquals($expectedPlaceholder, $renderedPlaceholder);
- }
- /**
- * 0 => Filled
- * 1 => not filled
- * 2 => empty
- */
- public function countryChoicePlaceholderProvider()
- {
- return array(
- array(
- "Choose a country",
- "Choose a country"
- ),
- array(
- null,
- null
- ),
- array(
- "",
- ""
- )
- );
- }
-
- public function testCountryChoiceTranslations()
- {
- IntlTestHelper::requireFullIntl($this);
- Locale::setDefault('fr');
-
- $form = $this->factory->create(new PhoneNumberType(), null, array('widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE));
-
- $view = $form->createView();
- $choices = $view['country']->vars['choices'];
-
- $this->assertContains($this->createChoiceView('Royaume-Uni (+44)', 'GB'), $choices, '', false, false);
- $this->assertFalse($view['country']->vars['choice_translation_domain']);
- }
-
- /**
- * @expectedException \Symfony\Component\OptionsResolver\Exception\InvalidOptionsException
- */
- public function testInvalidWidget()
- {
- if (method_exists('Symfony\\Component\\Form\\FormTypeInterface', 'getName')) {
- $type = new PhoneNumberType();
- } else {
- $type = 'Misd\\PhoneNumberBundle\\Form\\Type\\PhoneNumberType';
- }
- $this->factory->create($type, null, array('widget' => 'foo'));
- }
-
- public function testGetNameAndBlockPrefixAreTel()
- {
- $type = new PhoneNumberType();
-
- $this->assertSame('phone_number', $type->getBlockPrefix());
- $this->assertSame($type->getBlockPrefix(), $type->getName());
- }
-
- private function createChoiceView($label, $code)
- {
- if (class_exists('Symfony\Component\Form\ChoiceList\View\ChoiceView')) {
- $class = 'Symfony\Component\Form\ChoiceList\View\ChoiceView';
- } else {
- $class = 'Symfony\Component\Form\Extension\Core\View\ChoiceView';
- }
-
- return new $class($code, $code, $label);
- }
-}
diff --git a/Tests/Serializer/Handler/PhoneNumberHandlerTest.php b/Tests/Serializer/Handler/PhoneNumberHandlerTest.php
deleted file mode 100644
index cbab73ec..00000000
--- a/Tests/Serializer/Handler/PhoneNumberHandlerTest.php
+++ /dev/null
@@ -1,125 +0,0 @@
-getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $handler = new PhoneNumberHandler($phoneNumberUtil);
-
- $visitor = $this->getMockBuilder('JMS\Serializer\VisitorInterface')->getMock();
-
- $test = $this->getMock('libphoneNumber\PhoneNumber');
- $type = array();
- $context = $this->getMock('JMS\Serializer\Context');
-
- $phoneNumberUtil->expects($this->once())->method('format')->with($test)->will($this->returnValue('foo'));
- $visitor->expects($this->once())->method('visitString')->with('foo', $type, $context)
- ->will($this->returnValue('bar'));
-
- $this->assertSame('bar', $handler->serializePhoneNumber($visitor, $test, $type, $context));
- }
-
- public function testDeserializePhoneNumberFromJsonWhenNull()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $handler = new PhoneNumberHandler($phoneNumberUtil);
-
- $visitor = $this->getMockBuilder('JMS\Serializer\JsonDeserializationVisitor')
- ->disableOriginalConstructor()->getMock();
-
- $this->assertNull($handler->deserializePhoneNumberFromJson($visitor, null, array()));
- }
-
- public function testDeserializePhoneNumberFromJson()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $handler = new PhoneNumberHandler($phoneNumberUtil);
-
- $visitor = $this->getMockBuilder('JMS\Serializer\JsonDeserializationVisitor')
- ->disableOriginalConstructor()->getMock();
-
- $test = '+441234567890';
-
- $phoneNumberUtil->expects($this->once())->method('parse')->with($test, PhoneNumberUtil::UNKNOWN_REGION)
- ->will($this->returnValue('foo'));
-
- $this->assertSame('foo', $handler->deserializePhoneNumberFromJson($visitor, $test, array()));
- }
-
- public function testDeserializePhoneNumberFromXmlWhenNil()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $handler = new PhoneNumberHandler($phoneNumberUtil);
-
- $visitor = $this->getMockBuilder('JMS\Serializer\XmlDeserializationVisitor')
- ->disableOriginalConstructor()->getMock();
-
- $xml = new SimpleXMLElement('');
-
- $this->assertNull($handler->deserializePhoneNumberFromXml($visitor, $xml, array()));
- }
-
- public function testDeserializePhoneNumberFromXmlWhenXsiNil()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $handler = new PhoneNumberHandler($phoneNumberUtil);
-
- $visitor = $this->getMockBuilder('JMS\Serializer\XmlDeserializationVisitor')
- ->disableOriginalConstructor()->getMock();
-
- $xml = new SimpleXMLElement('');
- $xml->addAttribute('xsi:nil', 'true', 'http://www.w3.org/2001/XMLSchema-instance');
-
- $this->assertNull($handler->deserializePhoneNumberFromXml($visitor, $xml, array()));
- }
-
- public function testDeserializePhoneNumberFromXml()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $handler = new PhoneNumberHandler($phoneNumberUtil);
-
- $visitor = $this->getMockBuilder('JMS\Serializer\XmlDeserializationVisitor')
- ->disableOriginalConstructor()->getMock();
-
- $test = '+441234567890';
-
- $xml = new SimpleXMLElement(sprintf('%s', $test));
-
- $phoneNumberUtil->expects($this->once())->method('parse')->with($test, PhoneNumberUtil::UNKNOWN_REGION)
- ->will($this->returnValue('foo'));
-
- $this->assertSame('foo', $handler->deserializePhoneNumberFromXml($visitor, $xml, array()));
- }
-}
diff --git a/Tests/Serializer/Normalizer/PhoneNumberNormalizerTest.php b/Tests/Serializer/Normalizer/PhoneNumberNormalizerTest.php
deleted file mode 100644
index c93dd796..00000000
--- a/Tests/Serializer/Normalizer/PhoneNumberNormalizerTest.php
+++ /dev/null
@@ -1,101 +0,0 @@
-markTestSkipped('The Symfony Serializer is not available.');
- }
- }
-
- public function testSupportNormalization()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $normalizer = new PhoneNumberNormalizer($phoneNumberUtil);
-
- $this->assertTrue($normalizer->supportsNormalization(new PhoneNumber()));
- $this->assertFalse($normalizer->supportsNormalization(new \stdClass()));
- }
-
- public function testNormalize()
- {
- $phoneNumber = new PhoneNumber();
- $phoneNumber->setRawInput('+33193166989');
-
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
- $phoneNumberUtil->expects($this->once())->method('format')->with($phoneNumber, PhoneNumberFormat::E164)->will($this->returnValue('+33193166989'));
-
- $normalizer = new PhoneNumberNormalizer($phoneNumberUtil);
-
- $this->assertEquals('+33193166989', $normalizer->normalize($phoneNumber));
- }
-
- public function testSupportDenormalization()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $normalizer = new PhoneNumberNormalizer($phoneNumberUtil);
-
- $this->assertTrue($normalizer->supportsDenormalization('+33193166989', 'libphonenumber\PhoneNumber'));
- $this->assertFalse($normalizer->supportsDenormalization('+33193166989', 'stdClass'));
- }
-
- public function testDenormalize()
- {
- $phoneNumber = new PhoneNumber();
- $phoneNumber->setRawInput('+33193166989');
-
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $phoneNumberUtil->expects($this->once())->method('parse')
- ->with('+33193166989', PhoneNumberUtil::UNKNOWN_REGION)
- ->will($this->returnValue($phoneNumber));
-
- $normalizer = new PhoneNumberNormalizer($phoneNumberUtil);
-
- $this->assertSame($phoneNumber, $normalizer->denormalize('+33193166989', 'libphonenumber\PhoneNumber'));
- }
-
- /**
- * @expectedException \Symfony\Component\Serializer\Exception\UnexpectedValueException
- */
- public function testInvalidDateThrowException()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $phoneNumberUtil->expects($this->once())->method('parse')
- ->with('invalid phone number', PhoneNumberUtil::UNKNOWN_REGION)
- ->willThrowException(new NumberParseException(NumberParseException::INVALID_COUNTRY_CODE, ""));
-
- $normalizer = new PhoneNumberNormalizer($phoneNumberUtil);
- $normalizer->denormalize('invalid phone number', 'libphonenumber\PhoneNumber');
- }
-}
diff --git a/Tests/Templating/Helper/PhoneNumberHelperTest.php b/Tests/Templating/Helper/PhoneNumberHelperTest.php
deleted file mode 100644
index 3dab84b1..00000000
--- a/Tests/Templating/Helper/PhoneNumberHelperTest.php
+++ /dev/null
@@ -1,107 +0,0 @@
-getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $helper = new PhoneNumberHelper($phoneNumberUtil);
-
- $this->assertInstanceOf('Symfony\Component\Templating\Helper\HelperInterface', $helper);
- }
-
- public function testCharset()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $helper = new PhoneNumberHelper($phoneNumberUtil);
-
- $helper->setCharset('test');
-
- $this->assertSame('test', $helper->getCharset());
- }
-
- public function testName()
- {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $helper = new PhoneNumberHelper($phoneNumberUtil);
-
- $this->assertTrue(is_string($helper->getName()));
- }
-
- /**
- * @dataProvider processProvider
- */
- public function testProcess($format, $expectedFormat)
- {
- $phoneNumber = $this->getMock('libphonenumber\PhoneNumber');
-
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
- $phoneNumberUtil->expects($this->once())->method('format')->with($phoneNumber, $expectedFormat);
-
- $helper = new PhoneNumberHelper($phoneNumberUtil);
-
- $helper->format($phoneNumber, $format);
- }
-
- /**
- * 0 => Format
- * 1 => Expected format
- */
- public function processProvider()
- {
- return array(
- array(PhoneNumberFormat::NATIONAL, PhoneNumberFormat::NATIONAL),
- array('NATIONAL', PhoneNumberFormat::NATIONAL),
- );
- }
-
- /**
- * @expectedException \Misd\PhoneNumberBundle\Exception\InvalidArgumentException
- */
- public function testProcessInvalidArgumentException()
- {
- $phoneNumber = $this->getMock('libphonenumber\PhoneNumber');
-
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $helper = new PhoneNumberHelper($phoneNumberUtil);
-
- $helper->format($phoneNumber, 'foo');
- }
-
- public function testDeprecatedClassName() {
- $phoneNumberUtil = $this->getMockBuilder('libphonenumber\PhoneNumberUtil')
- ->disableOriginalConstructor()->getMock();
-
- $helper = new PhoneNumberFormatHelper($phoneNumberUtil);
-
- $this->assertInstanceOf('Misd\PhoneNumberBundle\Templating\Helper\PhoneNumberHelper', $helper);
- }
-}
diff --git a/Tests/Twig/Extension/PhoneNumberHelperExtensionTest.php b/Tests/Twig/Extension/PhoneNumberHelperExtensionTest.php
deleted file mode 100644
index a6e0daf2..00000000
--- a/Tests/Twig/Extension/PhoneNumberHelperExtensionTest.php
+++ /dev/null
@@ -1,101 +0,0 @@
-helper = $this->getMockBuilder('Misd\PhoneNumberBundle\Templating\Helper\PhoneNumberHelper')
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->extension = new PhoneNumberHelperExtension($this->helper);
- }
-
- public function testConstructor()
- {
- $this->assertInstanceOf('Twig_Extension', $this->extension);
- }
-
- public function testGetFunctions()
- {
- $functions = $this->extension->getFunctions();
-
- $this->assertCount(2, $functions);
- $this->assertInstanceOf('Twig_SimpleFunction', $functions[0]);
- $this->assertSame('phone_number_format', $functions[0]->getName());
-
- $callable = $functions[0]->getCallable();
-
- $this->assertSame($this->helper, $callable[0]);
- $this->assertSame('format', $callable[1]);
-
- $this->assertInstanceOf('Twig_SimpleFunction', $functions[1]);
- $this->assertSame('phone_number_is_type', $functions[1]->getName());
-
- $callable = $functions[1]->getCallable();
-
- $this->assertSame($this->helper, $callable[0]);
- $this->assertSame('isType', $callable[1]);
- }
-
- public function testGetFilters()
- {
- $filters = $this->extension->getFilters();
-
- $this->assertCount(1, $filters);
- $this->assertInstanceOf('Twig_SimpleFilter', $filters[0]);
- $this->assertSame('phone_number_format', $filters[0]->getName());
-
- $callable = $filters[0]->getCallable();
-
- $this->assertSame($this->helper, $callable[0]);
- $this->assertSame('format', $callable[1]);
- }
-
- public function testGetTests()
- {
- $tests = $this->extension->getTests();
-
- $this->assertCount(1, $tests);
- $this->assertInstanceOf('Twig_SimpleTest', $tests[0]);
- $this->assertSame('phone_number_of_type', $tests[0]->getName());
-
- $callable = $tests[0]->getCallable();
-
- $this->assertSame($this->helper, $callable[0]);
- $this->assertSame('isType', $callable[1]);
- }
-
- public function testGetName()
- {
- $this->assertTrue(is_string($this->extension->getName()));
- }
-}
diff --git a/Tests/Validator/Constraints/PhoneNumberTest.php b/Tests/Validator/Constraints/PhoneNumberTest.php
deleted file mode 100644
index 4a620c6d..00000000
--- a/Tests/Validator/Constraints/PhoneNumberTest.php
+++ /dev/null
@@ -1,72 +0,0 @@
-assertObjectHasAttribute('message', $phoneNumber);
- $this->assertObjectHasAttribute('type', $phoneNumber);
- $this->assertObjectHasAttribute('defaultRegion', $phoneNumber);
- }
-
- /**
- * @dataProvider messageProvider
- */
- public function testMessage($message = null, $type = null, $expectedMessage)
- {
- $phoneNumber = new PhoneNumber();
-
- if (null !== $message) {
- $phoneNumber->message = $message;
- }
- if (null !== $type) {
- $phoneNumber->type = $type;
- }
-
- $this->assertSame($expectedMessage, $phoneNumber->getMessage());
- }
-
- /**
- * 0 => Message (optional)
- * 1 => Type (optional)
- * 2 => Expected message
- */
- public function messageProvider()
- {
- return array(
- array(null, null, 'This value is not a valid phone number.'),
- array(null, 'fixed_line', 'This value is not a valid fixed-line number.'),
- array(null, 'mobile', 'This value is not a valid mobile number.'),
- array(null, 'pager', 'This value is not a valid pager number.'),
- array(null, 'personal_number', 'This value is not a valid personal number.'),
- array(null, 'premium_rate', 'This value is not a valid premium-rate number.'),
- array(null, 'shared_cost', 'This value is not a valid shared-cost number.'),
- array(null, 'toll_free', 'This value is not a valid toll-free number.'),
- array(null, 'uan', 'This value is not a valid UAN.'),
- array(null, 'voip', 'This value is not a valid VoIP number.'),
- array(null, 'voicemail', 'This value is not a valid voicemail access number.'),
- array('foo', null, 'foo'),
- array('foo', 'fixed_line', 'foo'),
- array('foo', 'mobile', 'foo'),
- );
- }
-}
diff --git a/Tests/Validator/Constraints/PhoneNumberValidatorTest.php b/Tests/Validator/Constraints/PhoneNumberValidatorTest.php
deleted file mode 100644
index 9a073d3e..00000000
--- a/Tests/Validator/Constraints/PhoneNumberValidatorTest.php
+++ /dev/null
@@ -1,171 +0,0 @@
-context = $this->getMockBuilder($executionContextClass)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->validator = new PhoneNumberValidator();
- $this->validator->initialize($this->context);
- }
-
- /**
- * @dataProvider validateProvider
- */
- public function testValidate($value, $violates, $type = null, $defaultRegion = null)
- {
- $constraint = new PhoneNumber();
-
- if (null !== $type) {
- $constraint->type = $type;
- }
-
- if (null !== $defaultRegion) {
- $constraint->defaultRegion = $defaultRegion;
- }
-
- if (true === $violates) {
- if ($value instanceof PhoneNumberObject) {
- $constraintValue = PhoneNumberUtil::getInstance()->format($value, PhoneNumberFormat::INTERNATIONAL);
- } else {
- $constraintValue = (string) $value;
- }
-
- if ($this->context instanceof ExecutionContextInterface) {
- $constraintViolationBuilder = $this->getMockBuilder('Symfony\Component\Validator\Violation\ConstraintViolationBuilderInterface')
- ->getMock();
-
- $constraintViolationBuilder->expects($this->any())
- ->method('setParameter')
- ->willReturnSelf();
-
- $constraintViolationBuilder->expects($this->any())
- ->method('setCode')
- ->willReturnSelf();
-
- $this->context->expects($this->once())
- ->method('buildViolation')
- ->with($constraint->getMessage())
- ->willReturn($constraintViolationBuilder);
- } else {
- $this->context->expects($this->once())
- ->method('addViolation')
- ->with($constraint->getMessage(), array(
- '{{ type }}' => $constraint->type,
- '{{ value }}' => $constraintValue
- ));
- }
- } else {
- if ($this->context instanceof ExecutionContextInterface) {
- $this->context->expects($this->never())
- ->method('buildViolation');
- } else {
- $this->context->expects($this->never())
- ->method('addViolation');
- }
- }
-
- $this->validator->validate($value, $constraint);
- }
-
- /**
- * 0 => Value
- * 1 => Violates?
- * 2 => Type (optional)
- * 3 => Default region (optional)
- */
- public function validateProvider()
- {
- return array(
- array(null, false),
- array('', false),
- array(PhoneNumberUtil::getInstance()->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION), false),
- array(PhoneNumberUtil::getInstance()->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION), false, 'fixed_line'),
- array(PhoneNumberUtil::getInstance()->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION), true, 'mobile'),
- array(PhoneNumberUtil::getInstance()->parse('+44123456789', PhoneNumberUtil::UNKNOWN_REGION), true),
- array('+441234567890', false),
- array('+441234567890', false, 'fixed_line'),
- array('+441234567890', true, 'mobile'),
- array('+44123456789', true),
- array('+44123456789', true, 'mobile'),
- array('+12015555555', false),
- array('+12015555555', false, 'fixed_line'),
- array('+12015555555', false, 'mobile'),
- array('+447640123456', false, 'pager'),
- array('+441234567890', true, 'pager'),
- array('+447012345678', false, 'personal_number'),
- array('+441234567890', true, 'personal_number'),
- array('+449012345678', false, 'premium_rate'),
- array('+441234567890', true, 'premium_rate'),
- array('+448431234567', false, 'shared_cost'),
- array('+441234567890', true, 'shared_cost'),
- array('+448001234567', false, 'toll_free'),
- array('+441234567890', true, 'toll_free'),
- array('+445512345678', false, 'uan'),
- array('+441234567890', true, 'uan'),
- array('+445612345678', false, 'voip'),
- array('+441234567890', true, 'voip'),
- array('+41860123456789', false, 'voicemail'),
- array('+441234567890', true, 'voicemail'),
- array('2015555555', false, null, 'US'),
- array('2015555555', false, 'fixed_line', 'US'),
- array('2015555555', false, 'mobile', 'US'),
- array('01234 567890', false, null, 'GB'),
- array('foo', true),
- );
- }
-
- /**
- * @expectedException \Symfony\Component\Validator\Exception\UnexpectedTypeException
- */
- public function testValidateThrowsUnexpectedTypeExceptionOnBadValue()
- {
- $constraint = new PhoneNumber();
- $this->validator->validate($this, $constraint);
- }
-
- protected function createValidator()
- {
- return new PhoneNumberValidator();
- }
-}
diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php
deleted file mode 100644
index da102c33..00000000
--- a/Tests/bootstrap.php
+++ /dev/null
@@ -1,22 +0,0 @@
-helper = $helper;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFunctions()
- {
- return array(
- new \Twig_SimpleFunction('phone_number_format', array($this->helper, 'format'), array('deprecated' => '1.2')),
- new \Twig_SimpleFunction('phone_number_is_type', array($this->helper, 'isType'), array('deprecated' => '1.2')),
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getFilters()
- {
- return array(
- new \Twig_SimpleFilter('phone_number_format', array($this->helper, 'format')),
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getTests()
- {
- return array(
- new \Twig_SimpleTest('phone_number_of_type', array($this->helper, 'isType')),
- );
- }
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return 'phone_number_helper';
- }
-}
diff --git a/Validator/Constraints/PhoneNumber.php b/Validator/Constraints/PhoneNumber.php
deleted file mode 100644
index ec82f63a..00000000
--- a/Validator/Constraints/PhoneNumber.php
+++ /dev/null
@@ -1,96 +0,0 @@
- 'INVALID_PHONE_NUMBER_ERROR',
- );
-
- public $message = null;
- public $type = self::ANY;
- public $defaultRegion = PhoneNumberUtil::UNKNOWN_REGION;
-
- public function getType()
- {
- switch ($this->type) {
- case self::FIXED_LINE:
- case self::MOBILE:
- case self::PAGER:
- case self::PERSONAL_NUMBER:
- case self::PREMIUM_RATE:
- case self::SHARED_COST:
- case self::TOLL_FREE:
- case self::UAN:
- case self::VOIP:
- case self::VOICEMAIL:
- return $this->type;
- }
-
- return self::ANY;
- }
-
- public function getMessage()
- {
- if (null !== $this->message) {
- return $this->message;
- }
-
- switch ($this->type) {
- case self::FIXED_LINE:
- return 'This value is not a valid fixed-line number.';
- case self::MOBILE:
- return 'This value is not a valid mobile number.';
- case self::PAGER:
- return 'This value is not a valid pager number.';
- case self::PERSONAL_NUMBER:
- return 'This value is not a valid personal number.';
- case self::PREMIUM_RATE:
- return 'This value is not a valid premium-rate number.';
- case self::SHARED_COST:
- return 'This value is not a valid shared-cost number.';
- case self::TOLL_FREE:
- return 'This value is not a valid toll-free number.';
- case self::UAN:
- return 'This value is not a valid UAN.';
- case self::VOIP:
- return 'This value is not a valid VoIP number.';
- case self::VOICEMAIL:
- return 'This value is not a valid voicemail access number.';
- }
-
- return 'This value is not a valid phone number.';
- }
-}
diff --git a/Validator/Constraints/PhoneNumberValidator.php b/Validator/Constraints/PhoneNumberValidator.php
deleted file mode 100644
index 8c4843bc..00000000
--- a/Validator/Constraints/PhoneNumberValidator.php
+++ /dev/null
@@ -1,135 +0,0 @@
-parse($value, $constraint->defaultRegion);
- } catch (NumberParseException $e) {
- $this->addViolation($value, $constraint);
-
- return;
- }
- } else {
- $phoneNumber = $value;
- $value = $phoneUtil->format($phoneNumber, PhoneNumberFormat::INTERNATIONAL);
- }
-
- if (false === $phoneUtil->isValidNumber($phoneNumber)) {
- $this->addViolation($value, $constraint);
-
- return;
- }
-
- switch ($constraint->getType()) {
- case PhoneNumber::FIXED_LINE:
- $validTypes = array(PhoneNumberType::FIXED_LINE, PhoneNumberType::FIXED_LINE_OR_MOBILE);
- break;
- case PhoneNumber::MOBILE:
- $validTypes = array(PhoneNumberType::MOBILE, PhoneNumberType::FIXED_LINE_OR_MOBILE);
- break;
- case PhoneNumber::PAGER:
- $validTypes = array(PhoneNumberType::PAGER);
- break;
- case PhoneNumber::PERSONAL_NUMBER:
- $validTypes = array(PhoneNumberType::PERSONAL_NUMBER);
- break;
- case PhoneNumber::PREMIUM_RATE:
- $validTypes = array(PhoneNumberType::PREMIUM_RATE);
- break;
- case PhoneNumber::SHARED_COST:
- $validTypes = array(PhoneNumberType::SHARED_COST);
- break;
- case PhoneNumber::TOLL_FREE:
- $validTypes = array(PhoneNumberType::TOLL_FREE);
- break;
- case PhoneNumber::UAN:
- $validTypes = array(PhoneNumberType::UAN);
- break;
- case PhoneNumber::VOIP:
- $validTypes = array(PhoneNumberType::VOIP);
- break;
- case PhoneNumber::VOICEMAIL:
- $validTypes = array(PhoneNumberType::VOICEMAIL);
- break;
- default:
- $validTypes = array();
- break;
- }
-
- if (count($validTypes)) {
- $type = $phoneUtil->getNumberType($phoneNumber);
-
- if (false === in_array($type, $validTypes)) {
- $this->addViolation($value, $constraint);
-
- return;
- }
-
- }
- }
-
- /**
- * Add a violation.
- *
- * @param mixed $value The value that should be validated.
- * @param Constraint $constraint The constraint for the validation.
- */
- private function addViolation($value, Constraint $constraint)
- {
- /** @var \Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumber $constraint */
- if ($this->context instanceof ExecutionContextInterface) {
- $this->context->buildViolation($constraint->getMessage())
- ->setParameter('{{ type }}', $constraint->getType())
- ->setParameter('{{ value }}', $this->formatValue($value))
- ->setCode(PhoneNumber::INVALID_PHONE_NUMBER_ERROR)
- ->addViolation();
- } else {
- $this->context->addViolation($constraint->getMessage(), array(
- '{{ type }}' => $constraint->getType(),
- '{{ value }}' => $value
- ));
- }
- }
-}
diff --git a/composer.json b/composer.json
index 3ef12f77..355cb414 100644
--- a/composer.json
+++ b/composer.json
@@ -1,51 +1,58 @@
{
- "name": "misd/phone-number-bundle",
+ "name": "odolbeau/phone-number-bundle",
"type": "symfony-bundle",
- "description": "Integrates libphonenumber into your Symfony2-Symfony4 application",
+ "description": "Integrates libphonenumber into your Symfony application",
"keywords": ["bundle", "phone-number", "phonenumber", "telephone number", "libphonenumber"],
- "homepage": "https://github.com/misd-service-development/phone-number-bundle",
+ "homepage": "https://github.com/odolbeau/phone-number-bundle",
"license": "MIT",
"support": {
- "issues": "https://github.com/misd-service-development/phone-number-bundle/issues"
+ "issues": "https://github.com/odolbeau/phone-number-bundle/issues"
+ },
+ "config": {
+ "sort-packages": true
},
"require": {
- "php": ">=5.3.9",
- "giggsey/libphonenumber-for-php": "~5.7|~6.0|~7.0|~8.0",
- "symfony/framework-bundle": "~2.7|~3.0|~4.0"
+ "php": ">=8.1",
+ "giggsey/libphonenumber-for-php": "^8.9 || ^9",
+ "symfony/framework-bundle": "^5.4 || ^6.3 || ^7.0",
+ "symfony/intl": "^5.4 || ^6.3 || ^7.0",
+ "symfony/polyfill-mbstring": "^1.28"
},
"require-dev": {
- "doctrine/doctrine-bundle": "~1.0",
- "jms/serializer-bundle": "~0.11|~1.0|~2.0",
- "phpunit/phpunit": "~4.0|^5.7",
- "symfony/form": "~2.7|~3.0|~4.0",
- "symfony/serializer": "~2.7|~3.1|~4.0",
- "symfony/templating": "~2.7|~3.0|~4.0",
- "symfony/twig-bundle": "~2.7|~3.0|~4.0",
- "symfony/validator": "~2.7|~3.0|~4.0"
- },
- "conflict": {
- "twig/twig": "<1.12.0"
+ "doctrine/doctrine-bundle": "^1.12|^2.0",
+ "phpspec/prophecy-phpunit": "^2.0",
+ "phpunit/phpunit": "^9.6.11",
+ "symfony/form": "^5.4 || ^6.3 || ^7.0",
+ "symfony/phpunit-bridge": "^7.0",
+ "symfony/property-access": "^5.4 || ^6.3 || ^7.0",
+ "symfony/serializer": "^5.4 || ^6.3 || ^7.0",
+ "symfony/twig-bundle": "^5.4 || ^6.3 || ^7.0",
+ "symfony/validator": "^5.4 || ^6.3 || ^7.0"
},
"suggest": {
"doctrine/doctrine-bundle": "Add a DBAL mapping type",
- "jms/serializer-bundle": "Serialize/deserialize phone numbers using JSM library",
- "symfony/serializer": "Serialize/deserialize phone numbers using Symfony library",
"symfony/form": "Add a data transformer",
- "symfony/templating": "Format phone numbers in templates",
+ "symfony/property-access": "Choose a path in the validation constraint",
+ "symfony/serializer": "Serialize/deserialize phone numbers using Symfony library",
"symfony/twig-bundle": "Format phone numbers in Twig templates",
"symfony/validator": "Add a validation constraint"
},
+ "replace": {
+ "misd/phone-number-bundle": "self.version"
+ },
"autoload": {
"psr-4": {
- "Misd\\PhoneNumberBundle\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
+ "Misd\\PhoneNumberBundle\\": "src/"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "Misd\\PhoneNumberBundle\\Tests\\": "tests/"
+ }
},
"extra": {
"branch-alias": {
- "dev-master": "2.0.x-dev"
+ "dev-master": "3.10.x-dev"
}
}
}
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
new file mode 100644
index 00000000..b0ad9c1d
--- /dev/null
+++ b/phpstan-baseline.neon
@@ -0,0 +1,6 @@
+parameters:
+ ignoreErrors:
+ -
+ message: "#^Parameter \\#2 \\$regionCallingFrom of method libphonenumber\\\\PhoneNumberUtil\\:\\:formatOutOfCountryCallingNumber\\(\\) expects string, string\\|null given\\.$#"
+ count: 1
+ path: src/Templating/Helper/PhoneNumberHelper.php
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 00000000..c17ff6e2
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,12 @@
+includes:
+ - phpstan-baseline.neon
+ - tools/phpstan/vendor/jangregor/phpstan-prophecy/extension.neon
+
+parameters:
+ level: 8
+ paths:
+ - src
+ - tests
+
+ ignoreErrors:
+ - '#Call to an undefined method Symfony\\Component\\Config\\Definition\\Builder\\NodeDefinition::children\(\).#'
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 00000000..1fbcc1eb
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tests
+
+
+
+
+
+ src
+
+
+
+
+
+
+
diff --git a/phpunit.xml.dist b/phpunit.xml.dist
deleted file mode 100644
index 23c8b463..00000000
--- a/phpunit.xml.dist
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
- ./Tests/
-
-
-
-
-
-
-
- ./
-
- ./Resources
- ./Tests
- ./vendor
-
-
-
-
diff --git a/src/DependencyInjection/Compiler/FormTwigTemplateCompilerPass.php b/src/DependencyInjection/Compiler/FormTwigTemplateCompilerPass.php
new file mode 100644
index 00000000..9a5b1312
--- /dev/null
+++ b/src/DependencyInjection/Compiler/FormTwigTemplateCompilerPass.php
@@ -0,0 +1,66 @@
+hasParameter('twig.form.resources')) {
+ return;
+ }
+
+ /**
+ * @var string[] $parameter
+ */
+ $parameter = $container->getParameter('twig.form.resources');
+
+ if (\in_array($this->phoneNumberLayout, $parameter)) {
+ return;
+ }
+
+ // Insert right after base template if it exists.
+ if (false !== ($key = array_search('bootstrap_5_horizontal_layout.html.twig', $parameter))) {
+ array_splice($parameter, ++$key, 0, [$this->phoneNumberBootstrap5Layout]);
+ } elseif (false !== ($key = array_search('bootstrap_5_layout.html.twig', $parameter))) {
+ array_splice($parameter, ++$key, 0, [$this->phoneNumberBootstrap5Layout]);
+ } elseif (false !== ($key = array_search('bootstrap_4_horizontal_layout.html.twig', $parameter))) {
+ array_splice($parameter, ++$key, 0, [$this->phoneNumberBootstrap4Layout]);
+ } elseif (false !== ($key = array_search('bootstrap_4_layout.html.twig', $parameter))) {
+ array_splice($parameter, ++$key, 0, [$this->phoneNumberBootstrap4Layout]);
+ } elseif (false !== ($key = array_search('bootstrap_3_horizontal_layout.html.twig', $parameter))) {
+ array_splice($parameter, ++$key, 0, [$this->phoneNumberBootstrapLayout]);
+ } elseif (false !== ($key = array_search('bootstrap_3_layout.html.twig', $parameter))) {
+ array_splice($parameter, ++$key, 0, [$this->phoneNumberBootstrapLayout]);
+ } elseif (false !== ($key = array_search('form_div_layout.html.twig', $parameter))) {
+ array_splice($parameter, ++$key, 0, [$this->phoneNumberLayout]);
+ } else {
+ // Put it in first position.
+ array_unshift($parameter, $this->phoneNumberLayout);
+ }
+
+ $container->setParameter('twig.form.resources', $parameter);
+ }
+}
diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php
new file mode 100644
index 00000000..3d24bf3a
--- /dev/null
+++ b/src/DependencyInjection/Configuration.php
@@ -0,0 +1,102 @@
+getRootNode();
+
+ $normalizer = function ($value) {
+ if (\is_bool($value)) {
+ return [
+ 'enabled' => $value,
+ ];
+ }
+
+ return $value;
+ };
+
+ $rootNode
+ ->children()
+ ->arrayNode('twig')
+ ->addDefaultsIfNotSet()
+ ->beforeNormalization()->always($normalizer)->end()
+ ->children()
+ ->scalarNode('enabled')
+ ->defaultValue(class_exists(TwigBundle::class))
+ ->end()
+ ->end()
+ ->end()
+ ->arrayNode('form')
+ ->addDefaultsIfNotSet()
+ ->beforeNormalization()->always($normalizer)->end()
+ ->children()
+ ->scalarNode('enabled')
+ ->defaultValue(interface_exists(FormTypeInterface::class))
+ ->end()
+ ->end()
+ ->end()
+ ->arrayNode('serializer')
+ ->addDefaultsIfNotSet()
+ ->beforeNormalization()->always($normalizer)->end()
+ ->children()
+ ->scalarNode('enabled')
+ ->defaultValue(interface_exists(NormalizerInterface::class))
+ ->end()
+ ->scalarNode('default_region')
+ ->defaultValue(PhoneNumberUtil::UNKNOWN_REGION)
+ ->beforeNormalization()->always(function ($value) {
+ return strtoupper($value);
+ })->end()
+ ->end()
+ ->scalarNode('format')
+ ->defaultValue(PhoneNumberFormat::E164)
+ ->end()
+ ->end()
+ ->end()
+ ->arrayNode('validator')
+ ->addDefaultsIfNotSet()
+ ->beforeNormalization()->always($normalizer)->end()
+ ->children()
+ ->scalarNode('enabled')->defaultValue(interface_exists(ValidatorInterface::class))->end()
+ ->scalarNode('default_region')
+ ->defaultValue(PhoneNumberUtil::UNKNOWN_REGION)
+ ->beforeNormalization()->always(function ($value) {
+ return strtoupper($value);
+ })->end()
+ ->end()
+ ->scalarNode('format')
+ // The difference between serializer and validator is historical, they are here to keep the BC
+ ->defaultValue(PhoneNumberFormat::INTERNATIONAL)
+ ->end()
+ ->end()
+ ->end()
+ ->end()
+ ;
+
+ return $treeBuilder;
+ }
+}
diff --git a/src/DependencyInjection/MisdPhoneNumberExtension.php b/src/DependencyInjection/MisdPhoneNumberExtension.php
new file mode 100644
index 00000000..58c761f0
--- /dev/null
+++ b/src/DependencyInjection/MisdPhoneNumberExtension.php
@@ -0,0 +1,52 @@
+processConfiguration($configuration, $configs);
+
+ $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
+ $loader->load('services.xml');
+ if ($config['twig']['enabled']) {
+ $loader->load('twig.xml');
+ }
+ if ($config['form']['enabled']) {
+ $loader->load('form.xml');
+ }
+ if ($config['serializer']['enabled']) {
+ $loader->load('serializer.xml');
+
+ $container->setParameter('misd_phone_number.serializer.default_region', $config['serializer']['default_region']);
+ $container->setParameter('misd_phone_number.serializer.format', $config['serializer']['format']);
+ }
+ if ($config['validator']['enabled']) {
+ $loader->load('validator.xml');
+
+ $container->setParameter('misd_phone_number.validator.default_region', $config['validator']['default_region']);
+ $container->setParameter('misd_phone_number.validator.format', $config['validator']['format']);
+ }
+ }
+}
diff --git a/Doctrine/DBAL/Types/PhoneNumberType.php b/src/Doctrine/DBAL/Types/PhoneNumberType.php
similarity index 57%
rename from Doctrine/DBAL/Types/PhoneNumberType.php
rename to src/Doctrine/DBAL/Types/PhoneNumberType.php
index 5f59089f..c7606cb5 100644
--- a/Doctrine/DBAL/Types/PhoneNumberType.php
+++ b/src/Doctrine/DBAL/Types/PhoneNumberType.php
@@ -1,5 +1,7 @@
getVarcharTypeDeclarationSQL(array('length' => 35));
+ // DBAL < 4
+ if (method_exists(AbstractPlatform::class, 'getVarcharTypeDeclarationSQL')) {
+ // @phpstan-ignore-next-line
+ return $platform->getVarcharTypeDeclarationSQL(['length' => $column['length'] ?? 35]);
+ }
+
+ // DBAL 4
+ return $platform->getStringTypeDeclarationSQL(['length' => $column['length'] ?? 35]);
}
- /**
- * {@inheritdoc}
- */
- public function convertToDatabaseValue($value, AbstractPlatform $platform)
+ public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
{
if (null === $value) {
return null;
}
if (!$value instanceof PhoneNumber) {
- throw new ConversionException('Expected \libphonenumber\PhoneNumber, got ' . gettype($value));
+ throw new ConversionException('Expected \libphonenumber\PhoneNumber, got '.\gettype($value));
}
- $util = PhoneNumberUtil::getInstance();
-
- return $util->format($value, PhoneNumberFormat::E164);
+ return PhoneNumberUtil::getInstance()->format($value, PhoneNumberFormat::E164);
}
- /**
- * {@inheritdoc}
- */
- public function convertToPHPValue($value, AbstractPlatform $platform)
+ public function convertToPHPValue($value, AbstractPlatform $platform): ?PhoneNumber
{
if (null === $value || $value instanceof PhoneNumber) {
return $value;
@@ -77,14 +73,17 @@ public function convertToPHPValue($value, AbstractPlatform $platform)
try {
return $util->parse($value, PhoneNumberUtil::UNKNOWN_REGION);
} catch (NumberParseException $e) {
- throw ConversionException::conversionFailed($value, self::NAME);
+ if (method_exists(ConversionException::class, 'conversionFailed')) {
+ // DBAL < 4
+ throw ConversionException::conversionFailed($value, self::NAME);
+ }
+
+ // DBAL 4
+ throw InvalidType::new($value, self::NAME, ['null', 'string']);
}
}
- /**
- * {@inheritdoc}
- */
- public function requiresSQLCommentHint(AbstractPlatform $platform)
+ public function requiresSQLCommentHint(AbstractPlatform $platform): bool
{
return true;
}
diff --git a/Exception/InvalidArgumentException.php b/src/Exception/InvalidArgumentException.php
similarity index 95%
rename from Exception/InvalidArgumentException.php
rename to src/Exception/InvalidArgumentException.php
index 3b9e15d5..c12bdd1d 100644
--- a/Exception/InvalidArgumentException.php
+++ b/src/Exception/InvalidArgumentException.php
@@ -1,5 +1,7 @@
*/
class PhoneNumberToArrayTransformer implements DataTransformerInterface
{
/**
- * @var array
+ * @var string[]
*/
- private $countryChoices;
+ private array $countryChoices;
/**
- * Constructor.
- *
- * @param array $countryChoices
+ * @param string[] $countryChoices
*/
public function __construct(array $countryChoices)
{
@@ -39,54 +39,62 @@ public function __construct(array $countryChoices)
}
/**
- * {@inheritdoc}
+ * @return array{country: string, number: string}
*/
- public function transform($phoneNumber)
+ public function transform(mixed $value): array
{
- if (null === $phoneNumber) {
- return array('country' => '', 'number' => '');
- } elseif (false === $phoneNumber instanceof PhoneNumber) {
+ if (null === $value) {
+ return ['country' => '', 'number' => ''];
+ }
+
+ if (!$value instanceof PhoneNumber) {
throw new TransformationFailedException('Expected a \libphonenumber\PhoneNumber.');
}
+ if (!$value->hasCountryCode() && !$value->hasNationalNumber()) {
+ return ['country' => '', 'number' => ''];
+ }
+
$util = PhoneNumberUtil::getInstance();
- if (false === in_array($util->getRegionCodeForNumber($phoneNumber), $this->countryChoices)) {
+ if (false === \in_array($util->getRegionCodeForNumber($value), $this->countryChoices)) {
throw new TransformationFailedException('Invalid country.');
}
- return array(
- 'country' => $util->getRegionCodeForNumber($phoneNumber),
- 'number' => $util->format($phoneNumber, PhoneNumberFormat::NATIONAL),
- );
+ return [
+ 'country' => (string) $util->getRegionCodeForNumber($value),
+ 'number' => $util->format($value, PhoneNumberFormat::NATIONAL),
+ ];
}
- /**
- * {@inheritdoc}
- */
- public function reverseTransform($value)
+ public function reverseTransform(mixed $value): ?PhoneNumber
{
if (!$value) {
return null;
}
- if (!is_array($value)) {
+ if (!\is_array($value)) {
throw new TransformationFailedException('Expected an array.');
}
- if ('' === trim($value['number'])) {
+ /* @phpstan-ignore-next-line */
+ if ('' === trim($value['number'] ?? '')) {
return null;
}
$util = PhoneNumberUtil::getInstance();
+ if (preg_match('/\p{L}/u', $value['number'])) {
+ throw new TransformationFailedException('The number can not contain letters.');
+ }
+
try {
$phoneNumber = $util->parse($value['number'], $value['country']);
} catch (NumberParseException $e) {
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
}
- if (false === in_array($util->getRegionCodeForNumber($phoneNumber), $this->countryChoices)) {
+ if (null !== $phoneNumber && false === \in_array($util->getRegionCodeForNumber($phoneNumber), $this->countryChoices)) {
throw new TransformationFailedException('Invalid country.');
}
diff --git a/Form/DataTransformer/PhoneNumberToStringTransformer.php b/src/Form/DataTransformer/PhoneNumberToStringTransformer.php
similarity index 54%
rename from Form/DataTransformer/PhoneNumberToStringTransformer.php
rename to src/Form/DataTransformer/PhoneNumberToStringTransformer.php
index 8be566be..dd7d4be4 100644
--- a/Form/DataTransformer/PhoneNumberToStringTransformer.php
+++ b/src/Form/DataTransformer/PhoneNumberToStringTransformer.php
@@ -1,5 +1,7 @@
*/
class PhoneNumberToStringTransformer implements DataTransformerInterface
{
- /**
- * Default region code.
- *
- * @var string
- */
- private $defaultRegion;
-
- /**
- * Display format.
- *
- * @var int
- */
- private $format;
+ private string $defaultRegion;
+ private int $format;
- /**
- * Constructor.
- *
- * @param string $defaultRegion Default region code.
- * @param int $format Display format.
- */
public function __construct(
- $defaultRegion = PhoneNumberUtil::UNKNOWN_REGION,
- $format = PhoneNumberFormat::INTERNATIONAL
+ string $defaultRegion = PhoneNumberUtil::UNKNOWN_REGION,
+ int $format = PhoneNumberFormat::INTERNATIONAL,
) {
$this->defaultRegion = $defaultRegion;
$this->format = $format;
}
- /**
- * {@inheritdoc}
- */
- public function transform($phoneNumber)
+ public function transform($value): string
{
- if (null === $phoneNumber) {
+ if (null === $value) {
return '';
- } elseif (false === $phoneNumber instanceof PhoneNumber) {
+ }
+
+ if (!$value instanceof PhoneNumber) {
throw new TransformationFailedException('Expected a \libphonenumber\PhoneNumber.');
}
$util = PhoneNumberUtil::getInstance();
if (PhoneNumberFormat::NATIONAL === $this->format) {
- return $util->formatOutOfCountryCallingNumber($phoneNumber, $this->defaultRegion);
+ return $util->formatOutOfCountryCallingNumber($value, $this->defaultRegion);
}
- return $util->format($phoneNumber, $this->format);
+ return $util->format($value, $this->format);
}
- /**
- * {@inheritdoc}
- */
- public function reverseTransform($string)
+ public function reverseTransform($value): ?PhoneNumber
{
- if (!$string && $string !== '0') {
+ if (!$value && '0' !== $value) {
return null;
}
- $util = PhoneNumberUtil::getInstance();
+ if (preg_match('/\p{L}/u', $value)) {
+ throw new TransformationFailedException('The number can not contain letters.');
+ }
try {
- return $util->parse($string, $this->defaultRegion);
+ return PhoneNumberUtil::getInstance()->parse($value, $this->defaultRegion);
} catch (NumberParseException $e) {
throw new TransformationFailedException($e->getMessage(), $e->getCode(), $e);
}
diff --git a/src/Form/Type/PhoneNumberType.php b/src/Form/Type/PhoneNumberType.php
new file mode 100644
index 00000000..e5274a99
--- /dev/null
+++ b/src/Form/Type/PhoneNumberType.php
@@ -0,0 +1,192 @@
+getCountryCodeForRegion($country);
+
+ if ($code) {
+ $countries[$country] = $code;
+ }
+ }
+ }
+
+ if (empty($countries)) {
+ foreach ($util->getSupportedRegions() as $country) {
+ $countries[$country] = $util->getCountryCodeForRegion($country);
+ }
+ }
+
+ $countryChoices = [];
+ $intlCountries = Countries::getNames();
+
+ foreach ($countries as $regionCode => $countryCode) {
+ if (!isset($intlCountries[$regionCode])) {
+ continue;
+ }
+
+ $label = $this->formatDisplayChoice(
+ $options['country_display_type'],
+ $intlCountries[$regionCode],
+ (string) $regionCode,
+ (string) $countryCode,
+ $options['country_display_emoji_flag']
+ );
+
+ $countryChoices[$label] = $regionCode;
+ }
+
+ /**
+ * @var string[] $transformerChoices
+ */
+ $transformerChoices = array_values($countryChoices);
+
+ $countryOptions = array_replace([
+ 'error_bubbling' => true,
+ 'disabled' => $options['disabled'],
+ 'translation_domain' => $options['translation_domain'],
+ 'choice_translation_domain' => false,
+ 'required' => true,
+ 'choices' => $countryChoices,
+ 'preferred_choices' => $options['preferred_country_choices'],
+ ], $options['country_options']);
+
+ if ($options['country_placeholder']) {
+ $countryOptions['placeholder'] = $options['country_placeholder'];
+ }
+
+ $numberOptions = array_replace([
+ 'error_bubbling' => true,
+ 'required' => $options['required'],
+ 'disabled' => $options['disabled'],
+ 'translation_domain' => $options['translation_domain'],
+ ], $options['number_options']);
+
+ $builder
+ ->add('country', ChoiceType::class, $countryOptions)
+ ->add('number', TextType::class, $numberOptions)
+ ->addViewTransformer(new PhoneNumberToArrayTransformer($transformerChoices));
+ } else {
+ $builder->addViewTransformer(
+ new PhoneNumberToStringTransformer($options['default_region'], $options['format'])
+ );
+ }
+ }
+
+ public function buildView(FormView $view, FormInterface $form, array $options): void
+ {
+ $view->vars['type'] = 'tel';
+ $view->vars['widget'] = $options['widget'];
+ }
+
+ public function configureOptions(OptionsResolver $resolver): void
+ {
+ $resolver->setDefaults([
+ 'widget' => self::WIDGET_SINGLE_TEXT,
+ 'compound' => function (Options $options): bool {
+ return self::WIDGET_SINGLE_TEXT !== $options['widget'];
+ },
+ 'default_region' => PhoneNumberUtil::UNKNOWN_REGION,
+ 'format' => PhoneNumberFormat::INTERNATIONAL,
+ 'invalid_message' => 'This value is not a valid phone number.',
+ 'by_reference' => false,
+ 'error_bubbling' => false,
+ 'country_choices' => [],
+ 'country_display_type' => self::DISPLAY_COUNTRY_FULL,
+ 'country_display_emoji_flag' => false,
+ 'country_placeholder' => false,
+ 'preferred_country_choices' => [],
+ 'country_options' => [],
+ 'number_options' => [],
+ ]);
+
+ $resolver->setAllowedValues('widget', [
+ self::WIDGET_SINGLE_TEXT,
+ self::WIDGET_COUNTRY_CHOICE,
+ ]);
+
+ $resolver->setAllowedValues('country_display_type', [
+ self::DISPLAY_COUNTRY_FULL,
+ self::DISPLAY_COUNTRY_SHORT,
+ ]);
+
+ $resolver->setAllowedTypes('country_options', 'array');
+ $resolver->setAllowedTypes('number_options', 'array');
+ }
+
+ public function getName(): string
+ {
+ return $this->getBlockPrefix();
+ }
+
+ public function getBlockPrefix(): string
+ {
+ return 'phone_number';
+ }
+
+ private function formatDisplayChoice(string $displayType, string $regionName, string $regionCode, string $countryCode, bool $displayEmojiFlag): string
+ {
+ $formattedDisplay = \sprintf('%s (+%s)', $regionName, $countryCode);
+ if (self::DISPLAY_COUNTRY_SHORT === $displayType) {
+ $formattedDisplay = \sprintf('%s +%s', $regionCode, $countryCode);
+ }
+
+ if ($displayEmojiFlag) {
+ $formattedDisplay = self::getEmojiFlag($regionCode).' '.$formattedDisplay;
+ }
+
+ return $formattedDisplay;
+ }
+
+ private static function getEmojiFlag(string $countryCode): string
+ {
+ $regionalOffset = 0x1F1A5;
+
+ return mb_chr($regionalOffset + mb_ord($countryCode[0], 'UTF-8'), 'UTF-8')
+ .mb_chr($regionalOffset + mb_ord($countryCode[1], 'UTF-8'), 'UTF-8');
+ }
+}
diff --git a/MisdPhoneNumberBundle.php b/src/MisdPhoneNumberBundle.php
similarity index 55%
rename from MisdPhoneNumberBundle.php
rename to src/MisdPhoneNumberBundle.php
index 65088304..e556d1ab 100644
--- a/MisdPhoneNumberBundle.php
+++ b/src/MisdPhoneNumberBundle.php
@@ -1,5 +1,7 @@
addCompilerPass(
- new ParentLocalesCompilerPass(),
- PassConfig::TYPE_BEFORE_REMOVING
- );
- $container->addCompilerPass(new FormPhpTemplateCompilerPass());
$container->addCompilerPass(new FormTwigTemplateCompilerPass());
}
}
diff --git a/Resources/config/form.xml b/src/Resources/config/form.xml
similarity index 54%
rename from Resources/config/form.xml
rename to src/Resources/config/form.xml
index 7e86fc6b..39c70a4d 100644
--- a/Resources/config/form.xml
+++ b/src/Resources/config/form.xml
@@ -4,16 +4,10 @@
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">
-
-
- Misd\PhoneNumberBundle\Form\Type\PhoneNumberType
-
-
-
+
-
diff --git a/Resources/config/serializer.xml b/src/Resources/config/serializer.xml
similarity index 60%
rename from Resources/config/serializer.xml
rename to src/Resources/config/serializer.xml
index 6738e0b9..8f0cc9d1 100644
--- a/Resources/config/serializer.xml
+++ b/src/Resources/config/serializer.xml
@@ -5,11 +5,12 @@
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
-
+
-
+
+ %misd_phone_number.serializer.default_region%
+ %misd_phone_number.serializer.format%
-
diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml
new file mode 100644
index 00000000..d249c758
--- /dev/null
+++ b/src/Resources/config/services.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Resources/config/twig.xml b/src/Resources/config/twig.xml
new file mode 100644
index 00000000..13d8a556
--- /dev/null
+++ b/src/Resources/config/twig.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Resources/config/validator.xml b/src/Resources/config/validator.xml
new file mode 100644
index 00000000..9da846a3
--- /dev/null
+++ b/src/Resources/config/validator.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+ %misd_phone_number.validator.default_region%
+ %misd_phone_number.validator.format%
+
+
+
+
+
+
+
diff --git a/src/Resources/meta/LICENSE b/src/Resources/meta/LICENSE
new file mode 100644
index 00000000..e69de29b
diff --git a/src/Resources/translations/validators.ar.xlf b/src/Resources/translations/validators.ar.xlf
new file mode 100644
index 00000000..60a38c75
--- /dev/null
+++ b/src/Resources/translations/validators.ar.xlf
@@ -0,0 +1,51 @@
+
+
+
+
+
+ This value is not a valid phone number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
ŲØ§ØĒŲ ØĩØ§ŲØ.
+
+
+ This value is not a valid fixed-line number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
ŲØ§ØĒŲ ØĢابØĒ ØĩØ§ŲØ.
+
+
+ This value is not a valid mobile number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
ŲØ§ØĒŲ Ų
ØŲ
ŲŲ ØĩØ§ŲØ.
+
+
+ This value is not a valid pager number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
Ø¨ŲØŦØą ØĩØ§ŲØ.
+
+
+ This value is not a valid personal number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
Ø´ØŽØĩŲ ØĩØ§ŲØ.
+
+
+ This value is not a valid premium-rate number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
ŲØ§ØĒŲ Ø¨ØŗØšØą Ų
Ų
ŲØ˛ ØĩØ§ŲØ.
+
+
+ This value is not a valid shared-cost number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
ŲØ§ØĒŲ Ø¨ØĒŲŲŲØŠ Ų
Ø´ØĒØąŲØŠ ØĩØ§ŲØ.
+
+
+ This value is not a valid toll-free number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
ŲØ§ØĒŲ Ų
ØŦاŲŲ ØĩØ§ŲØ.
+
+
+ This value is not a valid UAN.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
UAN ØĩØ§ŲØ.
+
+
+ This value is not a valid VoIP number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
VoIP ØĩØ§ŲØ.
+
+
+ This value is not a valid voicemail access number.
+ ŲØ°Ų اŲŲŲŲ
ØŠ ŲŲØŗØĒ ØąŲŲ
ŲØĩŲŲ ŲØĩŲØ¯ŲŲ Ø§ŲØ¨ØąŲد Ø§ŲØĩŲØĒŲ ØĩØ§ŲØ.
+
+
+
+
diff --git a/Resources/translations/validators.bs.xlf b/src/Resources/translations/validators.bs.xlf
similarity index 100%
rename from Resources/translations/validators.bs.xlf
rename to src/Resources/translations/validators.bs.xlf
diff --git a/Resources/translations/validators.bs_Cyrl.xlf b/src/Resources/translations/validators.bs_Cyrl.xlf
similarity index 100%
rename from Resources/translations/validators.bs_Cyrl.xlf
rename to src/Resources/translations/validators.bs_Cyrl.xlf
diff --git a/src/Resources/translations/validators.ca.xlf b/src/Resources/translations/validators.ca.xlf
new file mode 100644
index 00000000..7d169a76
--- /dev/null
+++ b/src/Resources/translations/validators.ca.xlf
@@ -0,0 +1,51 @@
+
+
+
+
+
+ This value is not a valid phone number.
+ Aquest valor no Ês un nÃēmero de telèfon và lid.
+
+
+ This value is not a valid fixed-line number.
+ Aquest valor no Ês un nÃēmero de lÃnia fixa và lid.
+
+
+ This value is not a valid mobile number.
+ Aquest valor no Ês un nÃēmero de mÃ˛bil và lid.
+
+
+ This value is not a valid pager number.
+ Aquest valor no Ês un nÃēmero de cercapersones và lid.
+
+
+ This value is not a valid personal number.
+ Aquest valor no Ês un nÃēmero personal và lid.
+
+
+ This value is not a valid premium-rate number.
+ Aquest valor no Ês un nÃēmero de tarifa premium và lid.
+
+
+ This value is not a valid shared-cost number.
+ Aquest valor no Ês un nÃēmero de cost compartit và lid.
+
+
+ This value is not a valid toll-free number.
+ Aquest valor no Ês un nÃēmero gratuït và lid.
+
+
+ This value is not a valid UAN.
+ Aquest valor no Ês un UAN và lid.
+
+
+ This value is not a valid VoIP number.
+ Aquest valor no Ês un nÃēmero VoIP và lid.
+
+
+ This value is not a valid voicemail access number.
+ Aquest valor no Ês un nÃēmero d'accÊs a la bÃēstia de veu và lid.
+
+
+
+
diff --git a/Resources/translations/validators.fr.xlf b/src/Resources/translations/validators.cs.xlf
similarity index 68%
rename from Resources/translations/validators.fr.xlf
rename to src/Resources/translations/validators.cs.xlf
index 0cf2c3c1..bf8b9d8b 100644
--- a/Resources/translations/validators.fr.xlf
+++ b/src/Resources/translations/validators.cs.xlf
@@ -4,15 +4,15 @@
This value is not a valid phone number.
- Cette valeur n'est pas un numÊro de tÊlÊphone valide.
+ Tato hodnota nenà platnÊ telefonnà ÄÃslo.
This value is not a valid fixed-line number.
- Cette valeur n'est pas un numÊro de tÊlÊphone fixe valide.
+ Tato hodnota nenà platnÃŊm ÄÃslem pevnÊ linky.
This value is not a valid mobile number.
- Cette valeur n'est pas un numÊro de tÊlÊphone mobile valide.
+ Tato hodnota nenà platnÊ mobilnà telefonnà ÄÃslo.
diff --git a/Resources/translations/validators.de.xlf b/src/Resources/translations/validators.de.xlf
similarity index 100%
rename from Resources/translations/validators.de.xlf
rename to src/Resources/translations/validators.de.xlf
diff --git a/Resources/translations/validators.dk.xlf b/src/Resources/translations/validators.dk.xlf
similarity index 100%
rename from Resources/translations/validators.dk.xlf
rename to src/Resources/translations/validators.dk.xlf
diff --git a/Resources/translations/validators.en.xlf b/src/Resources/translations/validators.en.xlf
similarity index 100%
rename from Resources/translations/validators.en.xlf
rename to src/Resources/translations/validators.en.xlf
diff --git a/Resources/translations/validators.en_AU.xlf b/src/Resources/translations/validators.en_AU.xlf
similarity index 100%
rename from Resources/translations/validators.en_AU.xlf
rename to src/Resources/translations/validators.en_AU.xlf
diff --git a/Resources/translations/validators.en_CA.xlf b/src/Resources/translations/validators.en_CA.xlf
similarity index 100%
rename from Resources/translations/validators.en_CA.xlf
rename to src/Resources/translations/validators.en_CA.xlf
diff --git a/Resources/translations/validators.en_GB.xlf b/src/Resources/translations/validators.en_GB.xlf
similarity index 100%
rename from Resources/translations/validators.en_GB.xlf
rename to src/Resources/translations/validators.en_GB.xlf
diff --git a/Resources/translations/validators.en_IE.xlf b/src/Resources/translations/validators.en_IE.xlf
similarity index 100%
rename from Resources/translations/validators.en_IE.xlf
rename to src/Resources/translations/validators.en_IE.xlf
diff --git a/Resources/translations/validators.en_NZ.xlf b/src/Resources/translations/validators.en_NZ.xlf
similarity index 100%
rename from Resources/translations/validators.en_NZ.xlf
rename to src/Resources/translations/validators.en_NZ.xlf
diff --git a/Resources/translations/validators.en_PH.xlf b/src/Resources/translations/validators.en_PH.xlf
similarity index 100%
rename from Resources/translations/validators.en_PH.xlf
rename to src/Resources/translations/validators.en_PH.xlf
diff --git a/Resources/translations/validators.en_SG.xlf b/src/Resources/translations/validators.en_SG.xlf
similarity index 100%
rename from Resources/translations/validators.en_SG.xlf
rename to src/Resources/translations/validators.en_SG.xlf
diff --git a/Resources/translations/validators.en_US.xlf b/src/Resources/translations/validators.en_US.xlf
similarity index 100%
rename from Resources/translations/validators.en_US.xlf
rename to src/Resources/translations/validators.en_US.xlf
diff --git a/Resources/translations/validators.en_ZA.xlf b/src/Resources/translations/validators.en_ZA.xlf
similarity index 100%
rename from Resources/translations/validators.en_ZA.xlf
rename to src/Resources/translations/validators.en_ZA.xlf
diff --git a/Resources/translations/validators.es.xlf b/src/Resources/translations/validators.es.xlf
similarity index 100%
rename from Resources/translations/validators.es.xlf
rename to src/Resources/translations/validators.es.xlf
diff --git a/Resources/translations/validators.es_419.xlf b/src/Resources/translations/validators.es_419.xlf
similarity index 100%
rename from Resources/translations/validators.es_419.xlf
rename to src/Resources/translations/validators.es_419.xlf
diff --git a/Resources/translations/validators.fi.xlf b/src/Resources/translations/validators.fi.xlf
similarity index 100%
rename from Resources/translations/validators.fi.xlf
rename to src/Resources/translations/validators.fi.xlf
diff --git a/src/Resources/translations/validators.fr.xlf b/src/Resources/translations/validators.fr.xlf
new file mode 100644
index 00000000..eb0bc65c
--- /dev/null
+++ b/src/Resources/translations/validators.fr.xlf
@@ -0,0 +1,51 @@
+
+
+
+
+
+ This value is not a valid phone number.
+ Cette valeur n'est pas un numÊro de tÊlÊphone valide.
+
+
+ This value is not a valid fixed-line number.
+ Cette valeur n'est pas un numÊro de tÊlÊphone fixe valide.
+
+
+ This value is not a valid mobile number.
+ Cette valeur n'est pas un numÊro de tÊlÊphone mobile valide.
+
+
+ This value is not a valid pager number.
+ Cette valeur n'est pas un numÊro de tÊlÊphone bipeur valide.
+
+
+ This value is not a valid personal number.
+ Cette valeur n'est pas un numÊro de tÊlÊphone personnel valide.
+
+
+ This value is not a valid premium-rate number.
+ Cette valeur n'est pas un numÊro de tÊlÊphone premium valide.
+
+
+ This value is not a valid shared-cost number.
+ Cette valeur n'est pas un numÊro à coÃģt partagÊ valide.
+
+
+ This value is not a valid toll-free number.
+ Cette valeur n'est pas un numÊro vert valide.
+
+
+ This value is not a valid UAN.
+ Cette valeur n'est pas un numÊro de tÊlÊphone UAN valide.
+
+
+ This value is not a valid VoIP number.
+ Cette valeur n'est pas un numÊro de tÊlÊphone VoIP valide.
+
+
+ This value is not a valid voicemail access number.
+ Cette valeur n'est pas un numÊro de rÊpondeur valide.
+
+
+
+
diff --git a/Resources/translations/validators.hr.xlf b/src/Resources/translations/validators.hr.xlf
similarity index 100%
rename from Resources/translations/validators.hr.xlf
rename to src/Resources/translations/validators.hr.xlf
diff --git a/src/Resources/translations/validators.hu.xlf b/src/Resources/translations/validators.hu.xlf
new file mode 100644
index 00000000..43cd7606
--- /dev/null
+++ b/src/Resources/translations/validators.hu.xlf
@@ -0,0 +1,51 @@
+
+
+
+
+
+ This value is not a valid phone number.
+ HibÃĄs telefonszÃĄm.
+
+
+ This value is not a valid fixed-line number.
+ HibÃĄs vonalas telefonszÃĄm.
+
+
+ This value is not a valid mobile number.
+ HibÃĄs mobil szÃĄm.
+
+
+ This value is not a valid pager number.
+ HibÃĄs csipogÃŗ szÃĄm.
+
+
+ This value is not a valid personal number.
+ HibÃĄs kÃļzponti telefonszÃĄm (follow me number).
+
+
+ This value is not a valid premium-rate number.
+ HibÃĄs emelt dÃjas telefonszÃĄm.
+
+
+ This value is not a valid shared-cost number.
+ HibÃĄs normÃĄl dÃjas telefonszÃĄm (shared-cost number).
+
+
+ This value is not a valid toll-free number.
+ HibÃĄs ingyenes telefonszÃĄm.
+
+
+ This value is not a valid UAN.
+ HibÃĄs kÃļzponti telefonszÃĄm (UAN).
+
+
+ This value is not a valid VoIP number.
+ HibÃĄs VoIP telefonszÃĄm.
+
+
+ This value is not a valid voicemail access number.
+ HibÃĄs hangposta hozzÃĄfÊrÊsi szÃĄm.
+
+
+
+
diff --git a/Resources/translations/validators.it.xlf b/src/Resources/translations/validators.it.xlf
similarity index 100%
rename from Resources/translations/validators.it.xlf
rename to src/Resources/translations/validators.it.xlf
diff --git a/Resources/translations/validators.nl.xlf b/src/Resources/translations/validators.nl.xlf
similarity index 100%
rename from Resources/translations/validators.nl.xlf
rename to src/Resources/translations/validators.nl.xlf
diff --git a/Resources/translations/validators.pl.xlf b/src/Resources/translations/validators.pl.xlf
similarity index 100%
rename from Resources/translations/validators.pl.xlf
rename to src/Resources/translations/validators.pl.xlf
diff --git a/src/Resources/translations/validators.pt.xlf b/src/Resources/translations/validators.pt.xlf
new file mode 100644
index 00000000..3a7f42e8
--- /dev/null
+++ b/src/Resources/translations/validators.pt.xlf
@@ -0,0 +1,51 @@
+
+
+
+
+
+ This value is not a valid phone number.
+ Este valor nÃŖo Ê um nÃēmero de telefone vÃĄlido.
+
+
+ This value is not a valid fixed-line number.
+ Este valor nÃŖo Ê um nÃēmero fixo vÃĄlido.
+
+
+ This value is not a valid mobile number.
+ Este valor nÃŖo Ê um nÃēmero de celular vÃĄlido.
+
+
+ This value is not a valid pager number.
+ Este valor nÃŖo Ê um nÃēmero de pager vÃĄlido.
+
+
+ This value is not a valid personal number.
+ Este valor nÃŖo Ê um nÃēmero pessoal vÃĄlido.
+
+
+ This value is not a valid premium-rate number.
+ Este valor nÃŖo Ê um nÃēmero de valor acrescentado vÃĄlido.
+
+
+ This value is not a valid shared-cost number.
+ Este valor nÃŖo Ê um nÃēmero de custo partilhado vÃĄlido.
+
+
+ This value is not a valid toll-free number.
+ Este valor nÃŖo Ê um nÃēmero de discagem gratuita vÃĄlido.
+
+
+ This value is not a valid UAN.
+ Este valor nÃŖo Ê um UAN vÃĄlido.
+
+
+ This value is not a valid VoIP number.
+ Este valor nÃŖo Ê um nÃēmero VoIP vÃĄlido.
+
+
+ This value is not a valid voicemail access number.
+ Este valor nÃŖo Ê um nÃēmero de correio de voz vÃĄlido.
+
+
+
+
diff --git a/src/Resources/translations/validators.pt_BR.xlf b/src/Resources/translations/validators.pt_BR.xlf
new file mode 100644
index 00000000..7ba7e977
--- /dev/null
+++ b/src/Resources/translations/validators.pt_BR.xlf
@@ -0,0 +1,11 @@
+
+
+
+
+
+ This value is not a valid shared-cost number.
+ Este valor nÃŖo Ê um nÃēmero de custo compartilhado vÃĄlido.
+
+
+
+
diff --git a/Resources/translations/validators.ru.xlf b/src/Resources/translations/validators.ru.xlf
similarity index 100%
rename from Resources/translations/validators.ru.xlf
rename to src/Resources/translations/validators.ru.xlf
diff --git a/Resources/translations/validators.se.xlf b/src/Resources/translations/validators.se.xlf
similarity index 100%
rename from Resources/translations/validators.se.xlf
rename to src/Resources/translations/validators.se.xlf
diff --git a/src/Resources/translations/validators.sk.xlf b/src/Resources/translations/validators.sk.xlf
new file mode 100644
index 00000000..939a51c1
--- /dev/null
+++ b/src/Resources/translations/validators.sk.xlf
@@ -0,0 +1,51 @@
+
+
+
+
+
+ This value is not a valid phone number.
+ TÃĄto hodnota nie je platnÊ telefÃŗnne ÄÃslo.
+
+
+ This value is not a valid fixed-line number.
+ TÃĄto hodnota nie je platnÃŊm ÄÃslom pevnej linky.
+
+
+ This value is not a valid mobile number.
+ TÃĄto hodnota nie je platnÊ mobilnÊ telefÃŗnne ÄÃslo.
+
+
+ This value is not a valid pager number.
+ TÃĄto hodnota nie je platnÃŊm ÄÃslom pagera.
+
+
+ This value is not a valid personal number.
+ TÃĄto hodnota nie je platnÃŊm osobnÃŊm ÄÃslom.
+
+
+ This value is not a valid premium-rate number.
+ TÃĄto hodnota nie je platnÃŊm ÄÃslom prÊmiovej sadzby.
+
+
+ This value is not a valid shared-cost number.
+ TÃĄto hodnota nie je platnÃŊm ÄÃslom so zdieÄžanÃŊmi nÃĄkladmi.
+
+
+ This value is not a valid toll-free number.
+ TÃĄto hodnota nie je platnÃŊm bezplatnÃŊm ÄÃslom.
+
+
+ This value is not a valid UAN.
+ TÃĄto hodnota nie je platnÃŊm UAN.
+
+
+ This value is not a valid VoIP number.
+ TÃĄto hodnota nie je platnÃŊm ÄÃslom VoIP.
+
+
+ This value is not a valid voicemail access number.
+ TÃĄto hodnota nie je platnÃŊm prÃstupovÃŊm ÄÃslom hlasovej schrÃĄnky.
+
+
+
+
diff --git a/Resources/translations/validators.sr.xlf b/src/Resources/translations/validators.sr.xlf
similarity index 100%
rename from Resources/translations/validators.sr.xlf
rename to src/Resources/translations/validators.sr.xlf
diff --git a/Resources/translations/validators.sr_Latn.xlf b/src/Resources/translations/validators.sr_Latn.xlf
similarity index 100%
rename from Resources/translations/validators.sr_Latn.xlf
rename to src/Resources/translations/validators.sr_Latn.xlf
diff --git a/Resources/translations/validators.sv.xlf b/src/Resources/translations/validators.sv.xlf
similarity index 100%
rename from Resources/translations/validators.sv.xlf
rename to src/Resources/translations/validators.sv.xlf
diff --git a/src/Resources/translations/validators.tr.xlf b/src/Resources/translations/validators.tr.xlf
new file mode 100644
index 00000000..b223dcfb
--- /dev/null
+++ b/src/Resources/translations/validators.tr.xlf
@@ -0,0 +1,15 @@
+
+
+
+
+
+ This value is not a valid mobile number.
+ Bu numara geçerli bir cep telefonu numarasÄą deÄil.
+
+
+ This value is not a valid personal number.
+ Bu numara geçerli bir kiÅisel telefon numarasÄą deÄil
+
+
+
+
diff --git a/src/Resources/translations/validators.uk.xlf b/src/Resources/translations/validators.uk.xlf
new file mode 100644
index 00000000..eb97c98d
--- /dev/null
+++ b/src/Resources/translations/validators.uk.xlf
@@ -0,0 +1,19 @@
+
+
+
+
+
+ This value is not a valid phone number.
+ ĐĐŊаŅĐĩĐŊĐŊŅ ĐŊĐĩ Ņ Đ´ĐžĐŋŅŅŅиĐŧиĐŧ ĐŊĐžĐŧĐĩŅĐžĐŧ ŅĐĩĐģĐĩŅĐžĐŊŅ.
+
+
+ This value is not a valid fixed-line number.
+ ĐĐŊаŅĐĩĐŊĐŊŅ ĐŊĐĩ Ņ Đ´ĐžĐŋŅŅŅиĐŧиĐŧ ĐŊĐžĐŧĐĩŅĐžĐŧ ŅŅĐēŅОваĐŊĐžĐŗĐž Св'ŅСĐēŅ.
+
+
+ This value is not a valid mobile number.
+ ĐĐŊаŅĐĩĐŊĐŊŅ ĐŊĐĩ Ņ Đ´ĐžĐŋŅŅŅиĐŧиĐŧ ĐŧОйŅĐģŅĐŊиĐŧ ĐŊĐžĐŧĐĩŅĐžĐŧ.
+
+
+
+
diff --git a/Resources/views/Form/phone_number.html.twig b/src/Resources/views/Form/phone_number.html.twig
similarity index 100%
rename from Resources/views/Form/phone_number.html.twig
rename to src/Resources/views/Form/phone_number.html.twig
diff --git a/Resources/views/Form/phone_number_bootstrap.html.twig b/src/Resources/views/Form/phone_number_bootstrap.html.twig
similarity index 100%
rename from Resources/views/Form/phone_number_bootstrap.html.twig
rename to src/Resources/views/Form/phone_number_bootstrap.html.twig
diff --git a/src/Resources/views/Form/phone_number_bootstrap_4.html.twig b/src/Resources/views/Form/phone_number_bootstrap_4.html.twig
new file mode 100644
index 00000000..727449a2
--- /dev/null
+++ b/src/Resources/views/Form/phone_number_bootstrap_4.html.twig
@@ -0,0 +1,13 @@
+{% block phone_number_widget -%}
+ {% if widget is constant('Misd\\PhoneNumberBundle\\Form\\Type\\PhoneNumberType::WIDGET_COUNTRY_CHOICE') %}
+ {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group')|trim}) %}
+
+
+ {{- form_widget(form.country) -}}
+
+ {{- form_widget(form.number) -}}
+
+ {% else -%}
+ {{- block('form_widget_simple') -}}
+ {%- endif %}
+{%- endblock phone_number_widget %}
diff --git a/src/Resources/views/Form/phone_number_bootstrap_5.html.twig b/src/Resources/views/Form/phone_number_bootstrap_5.html.twig
new file mode 100644
index 00000000..f5bd3ea7
--- /dev/null
+++ b/src/Resources/views/Form/phone_number_bootstrap_5.html.twig
@@ -0,0 +1,11 @@
+{% block phone_number_widget -%}
+ {% if widget is constant('Misd\\PhoneNumberBundle\\Form\\Type\\PhoneNumberType::WIDGET_COUNTRY_CHOICE') %}
+ {% set attr = attr|merge({class: (attr.class|default('') ~ ' input-group')|trim}) %}
+
+ {{- form_widget(form.country) -}}
+ {{- form_widget(form.number) -}}
+
+ {% else -%}
+ {{- block('form_widget_simple') -}}
+ {%- endif %}
+{%- endblock phone_number_widget %}
diff --git a/Serializer/Normalizer/PhoneNumberNormalizer.php b/src/Serializer/Normalizer/PhoneNumberNormalizer.php
similarity index 56%
rename from Serializer/Normalizer/PhoneNumberNormalizer.php
rename to src/Serializer/Normalizer/PhoneNumberNormalizer.php
index f73b38b4..632d3ad8 100644
--- a/Serializer/Normalizer/PhoneNumberNormalizer.php
+++ b/src/Serializer/Normalizer/PhoneNumberNormalizer.php
@@ -1,5 +1,7 @@
phoneNumberUtil = $phoneNumberUtil;
$this->region = $region;
@@ -61,30 +44,34 @@ public function __construct(PhoneNumberUtil $phoneNumberUtil, $region = PhoneNum
}
/**
- * {@inheritdoc}
+ * @param array $context
*
* @throws InvalidArgumentException
*/
- public function normalize($object, $format = null, array $context = array())
+ public function normalize(mixed $object, ?string $format = null, array $context = []): string
{
return $this->phoneNumberUtil->format($object, $this->format);
}
/**
- * {@inheritdoc}
+ * @param array $context
*/
- public function supportsNormalization($data, $format = null)
+ public function supportsNormalization(mixed $data, ?string $format = null, array $context = []): bool
{
return $data instanceof PhoneNumber;
}
/**
- * {@inheritdoc}
+ * @param array $context
*
* @throws UnexpectedValueException
*/
- public function denormalize($data, $class, $format = null, array $context = array())
+ public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): ?PhoneNumber
{
+ if (null === $data) {
+ return null;
+ }
+
try {
return $this->phoneNumberUtil->parse($data, $this->region);
} catch (NumberParseException $e) {
@@ -93,10 +80,18 @@ public function denormalize($data, $class, $format = null, array $context = arra
}
/**
- * {@inheritdoc}
+ * @param array $context
+ */
+ public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
+ {
+ return PhoneNumber::class === $type && \is_string($data);
+ }
+
+ /**
+ * for symfony/serializer >= 6.3.
*/
- public function supportsDenormalization($data, $type, $format = null)
+ public function getSupportedTypes(?string $format): array
{
- return $type === 'libphonenumber\PhoneNumber';
+ return [PhoneNumber::class => false];
}
-}
\ No newline at end of file
+}
diff --git a/src/Templating/Helper/PhoneNumberHelper.php b/src/Templating/Helper/PhoneNumberHelper.php
new file mode 100644
index 00000000..9c9cb1e1
--- /dev/null
+++ b/src/Templating/Helper/PhoneNumberHelper.php
@@ -0,0 +1,99 @@
+phoneNumberUtil = $phoneNumberUtil;
+ }
+
+ public function format(PhoneNumber|string $phoneNumber, string|int $format = PhoneNumberFormat::INTERNATIONAL): string
+ {
+ $phoneNumber = $this->getPhoneNumber($phoneNumber);
+
+ if (true === \is_string($format)) {
+ $constant = '\libphonenumber\PhoneNumberFormat::'.$format;
+
+ if (false === \defined($constant)) {
+ throw new InvalidArgumentException('The format must be either a constant value or name in libphonenumber\PhoneNumberFormat');
+ }
+
+ $format = \constant('\libphonenumber\PhoneNumberFormat::'.$format);
+ }
+
+ return $this->phoneNumberUtil->format($phoneNumber, $format);
+ }
+
+ /**
+ * Formats this phone number for out-of-country dialing purposes.
+ *
+ * @param PhoneNumber|string $phoneNumber phone number
+ * @param string|null $regionCode The ISO 3166-1 alpha-2 country code
+ */
+ public function formatOutOfCountryCallingNumber(PhoneNumber|string $phoneNumber, ?string $regionCode): string
+ {
+ $phoneNumber = $this->getPhoneNumber($phoneNumber);
+
+ return $this->phoneNumberUtil->formatOutOfCountryCallingNumber($phoneNumber, $regionCode);
+ }
+
+ /**
+ * @param PhoneNumber|string $phoneNumber phone number
+ * @param int|string $type phoneNumberType, or PhoneNumberType constant name
+ *
+ * @throws InvalidArgumentException if type argument is invalid
+ */
+ public function isType($phoneNumber, $type = PhoneNumberType::UNKNOWN): bool
+ {
+ $phoneNumber = $this->getPhoneNumber($phoneNumber);
+
+ if (true === \is_string($type)) {
+ $constant = '\libphonenumber\PhoneNumberType::'.$type;
+
+ if (false === \defined($constant)) {
+ throw new InvalidArgumentException('The format must be either a constant value or name in libphonenumber\PhoneNumberType');
+ }
+
+ $type = \constant('\libphonenumber\PhoneNumberType::'.$type);
+ }
+
+ return $this->phoneNumberUtil->getNumberType($phoneNumber) === $type;
+ }
+
+ private function getPhoneNumber(PhoneNumber|string $phoneNumber): PhoneNumber
+ {
+ if (\is_string($phoneNumber)) {
+ $phoneNumber = $this->phoneNumberUtil->parse($phoneNumber);
+ }
+
+ if (!$phoneNumber instanceof PhoneNumber) {
+ throw new InvalidArgumentException('The phone number supplied is not PhoneNumber or string.');
+ }
+
+ return $phoneNumber;
+ }
+}
diff --git a/src/Twig/Extension/PhoneNumberHelperExtension.php b/src/Twig/Extension/PhoneNumberHelperExtension.php
new file mode 100644
index 00000000..69304c4f
--- /dev/null
+++ b/src/Twig/Extension/PhoneNumberHelperExtension.php
@@ -0,0 +1,65 @@
+helper = $helper;
+ }
+
+ public function getFilters(): array
+ {
+ return [
+ new TwigFilter('phone_number_format', [$this->helper, 'format']),
+ new TwigFilter('phone_number_format_out_of_country_calling_number', [$this->helper, 'formatOutOfCountryCallingNumber']),
+ ];
+ }
+
+ public function getTests(): array
+ {
+ return [
+ new TwigTest('phone_number_of_type', [$this->helper, 'isType']),
+ ];
+ }
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return 'phone_number_helper';
+ }
+}
diff --git a/src/Validator/Constraints/PhoneNumber.php b/src/Validator/Constraints/PhoneNumber.php
new file mode 100644
index 00000000..b1f16dc8
--- /dev/null
+++ b/src/Validator/Constraints/PhoneNumber.php
@@ -0,0 +1,153 @@
+ 'INVALID_PHONE_NUMBER_ERROR',
+ ];
+
+ public ?string $message = null;
+ /**
+ * @var string|string[]
+ */
+ public string|array $type = self::ANY;
+ public ?string $defaultRegion = null;
+ public ?string $regionPath = null;
+ public ?int $format = null;
+
+ /**
+ * @param int|null $format Specify the format (\libphonenumber\PhoneNumberFormat::*)
+ * @param string|string[]|null $type
+ * @param array $options
+ */
+ #[HasNamedArguments]
+ public function __construct(
+ ?int $format = null,
+ string|array|null $type = null,
+ ?string $defaultRegion = null,
+ ?string $regionPath = null,
+ ?string $message = null,
+ ?array $groups = null,
+ $payload = null,
+ array $options = [],
+ ) {
+ parent::__construct($options, $groups, $payload);
+
+ $this->message = $message ?? $this->message;
+ $this->format = $format ?? $this->format;
+ $this->type = $type ?? $this->type;
+ $this->defaultRegion = $defaultRegion ?? $this->defaultRegion;
+ $this->regionPath = $regionPath ?? $this->regionPath;
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getTypes(): array
+ {
+ if (\is_array($this->type)) {
+ return $this->type;
+ }
+
+ return [$this->type];
+ }
+
+ public function getMessage(): string
+ {
+ if (null !== $this->message) {
+ return $this->message;
+ }
+
+ $types = $this->getTypes();
+ if (1 === \count($types)) {
+ $typeName = $this->getTypeName($types[0]);
+
+ return "This value is not a valid $typeName.";
+ }
+
+ return 'This value is not a valid phone number.';
+ }
+
+ /**
+ * @return string[]
+ */
+ public function getTypeNames(): array
+ {
+ $types = \is_array($this->type) ? $this->type : [$this->type];
+
+ $typeNames = [];
+ foreach ($types as $type) {
+ $typeNames[] = $this->getTypeName($type);
+ }
+
+ return $typeNames;
+ }
+
+ private function getTypeName(string $type): string
+ {
+ switch ($type) {
+ case self::FIXED_LINE:
+ return 'fixed-line number';
+ case self::MOBILE:
+ return 'mobile number';
+ case self::PAGER:
+ return 'pager number';
+ case self::PERSONAL_NUMBER:
+ return 'personal number';
+ case self::PREMIUM_RATE:
+ return 'premium-rate number';
+ case self::SHARED_COST:
+ return 'shared-cost number';
+ case self::TOLL_FREE:
+ return 'toll-free number';
+ case self::UAN:
+ return 'UAN';
+ case self::VOIP:
+ return 'VoIP number';
+ case self::VOICEMAIL:
+ return 'voicemail access number';
+ case self::ANY:
+ return 'phone number';
+ }
+
+ throw new InvalidArgumentException("Unknown phone number type \"$type\".");
+ }
+}
diff --git a/src/Validator/Constraints/PhoneNumberValidator.php b/src/Validator/Constraints/PhoneNumberValidator.php
new file mode 100644
index 00000000..0b304d98
--- /dev/null
+++ b/src/Validator/Constraints/PhoneNumberValidator.php
@@ -0,0 +1,185 @@
+phoneUtil = $phoneUtil ?? PhoneNumberUtil::getInstance();
+ $this->defaultRegion = $defaultRegion;
+ $this->format = $format;
+ }
+
+ public function validate(mixed $value, Constraint $constraint): void
+ {
+ if (!$constraint instanceof PhoneNumberConstraint) {
+ return;
+ }
+
+ if (null === $value || '' === $value) {
+ return;
+ }
+
+ if (!\is_scalar($value) && !(\is_object($value) && method_exists($value, '__toString'))) {
+ throw new UnexpectedTypeException($value, 'string');
+ }
+
+ if (false === $value instanceof PhoneNumberObject) {
+ $value = (string) $value;
+
+ try {
+ $phoneNumber = $this->phoneUtil->parse($value, $this->getRegion($constraint));
+ } catch (NumberParseException $e) {
+ $this->addViolation($value, $constraint);
+
+ return;
+ }
+ } else {
+ $phoneNumber = $value;
+ $value = $this->phoneUtil->format($phoneNumber, $constraint->format ?? $this->format);
+ }
+
+ if (false === $this->phoneUtil->isValidNumber($phoneNumber)) {
+ $this->addViolation($value, $constraint);
+
+ return;
+ }
+
+ $validTypes = [];
+ foreach ($constraint->getTypes() as $type) {
+ switch ($type) {
+ case PhoneNumberConstraint::FIXED_LINE:
+ $validTypes[] = PhoneNumberType::FIXED_LINE;
+ $validTypes[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
+ break;
+ case PhoneNumberConstraint::MOBILE:
+ $validTypes[] = PhoneNumberType::MOBILE;
+ $validTypes[] = PhoneNumberType::FIXED_LINE_OR_MOBILE;
+ break;
+ case PhoneNumberConstraint::PAGER:
+ $validTypes[] = PhoneNumberType::PAGER;
+ break;
+ case PhoneNumberConstraint::PERSONAL_NUMBER:
+ $validTypes[] = PhoneNumberType::PERSONAL_NUMBER;
+ break;
+ case PhoneNumberConstraint::PREMIUM_RATE:
+ $validTypes[] = PhoneNumberType::PREMIUM_RATE;
+ break;
+ case PhoneNumberConstraint::SHARED_COST:
+ $validTypes[] = PhoneNumberType::SHARED_COST;
+ break;
+ case PhoneNumberConstraint::TOLL_FREE:
+ $validTypes[] = PhoneNumberType::TOLL_FREE;
+ break;
+ case PhoneNumberConstraint::UAN:
+ $validTypes[] = PhoneNumberType::UAN;
+ break;
+ case PhoneNumberConstraint::VOIP:
+ $validTypes[] = PhoneNumberType::VOIP;
+ break;
+ case PhoneNumberConstraint::VOICEMAIL:
+ $validTypes[] = PhoneNumberType::VOICEMAIL;
+ break;
+ }
+ }
+
+ $validTypes = array_unique($validTypes);
+
+ if (0 < \count($validTypes)) {
+ $type = $this->phoneUtil->getNumberType($phoneNumber);
+
+ if (!\in_array($type, $validTypes, true)) {
+ $this->addViolation($value, $constraint);
+ }
+ }
+ }
+
+ private function getRegion(PhoneNumberConstraint $constraint): string
+ {
+ $defaultRegion = null;
+ if (null !== $path = $constraint->regionPath) {
+ $object = $this->context->getObject();
+ if (null === $object) {
+ throw new \LogicException('The current validation does not concern an object');
+ }
+
+ try {
+ $defaultRegion = $this->getPropertyAccessor()->getValue($object, $path);
+ } catch (NoSuchPropertyException $e) {
+ throw new ConstraintDefinitionException(\sprintf('Invalid property path "%s" provided to "%s" constraint: ', $path, get_debug_type($constraint)).$e->getMessage(), 0, $e);
+ }
+ }
+
+ return $defaultRegion ?? $constraint->defaultRegion ?? $this->defaultRegion;
+ }
+
+ public function setPropertyAccessor(PropertyAccessorInterface $propertyAccessor): void
+ {
+ $this->propertyAccessor = $propertyAccessor;
+ }
+
+ private function getPropertyAccessor(): PropertyAccessorInterface
+ {
+ if (null === $this->propertyAccessor) {
+ if (!class_exists(PropertyAccess::class)) {
+ throw new LogicException('Unable to use property path as the Symfony PropertyAccess component is not installed.');
+ }
+ $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
+ }
+
+ return $this->propertyAccessor;
+ }
+
+ /**
+ * Add a violation.
+ *
+ * @param mixed $value the value that should be validated
+ * @param PhoneNumberConstraint $constraint the constraint for the validation
+ */
+ private function addViolation($value, PhoneNumberConstraint $constraint): void
+ {
+ $this->context->buildViolation($constraint->getMessage())
+ ->setParameter('{{ types }}', implode(', ', $constraint->getTypeNames()))
+ ->setParameter('{{ value }}', $this->formatValue($value))
+ ->setCode(PhoneNumberConstraint::INVALID_PHONE_NUMBER_ERROR)
+ ->addViolation();
+ }
+}
diff --git a/tests/DependencyInjection/Compiler/FormTwigTemplateCompilerPassTest.php b/tests/DependencyInjection/Compiler/FormTwigTemplateCompilerPassTest.php
new file mode 100644
index 00000000..f8de9415
--- /dev/null
+++ b/tests/DependencyInjection/Compiler/FormTwigTemplateCompilerPassTest.php
@@ -0,0 +1,94 @@
+process($container);
+
+ $this->assertFalse($container->hasParameter('twig.form.resources'));
+ }
+
+ public function testItDoesNothingIfThePhoneNumberTwigIsAlreadyConfigured(): void
+ {
+ $subject = new FormTwigTemplateCompilerPass();
+ $container = new ContainerBuilder();
+ $inputParameter = ['@MisdPhoneNumber/Form/phone_number.html.twig'];
+ $container->setParameter('twig.form.resources', $inputParameter);
+
+ $subject->process($container);
+
+ $this->assertSame(
+ $inputParameter,
+ $container->getParameter('twig.form.resources')
+ );
+ }
+
+ /**
+ * @dataProvider themesProvider
+ */
+ public function testItAddsPhoneTemplatesAccordingToSymfonyTemplates(string $sfTemplate, string $phoneTemplate): void
+ {
+ $subject = new FormTwigTemplateCompilerPass();
+ $container = new ContainerBuilder();
+ $inputParameter = [$sfTemplate];
+ $container->setParameter('twig.form.resources', $inputParameter);
+
+ $subject->process($container);
+
+ $resources = $container->getParameter('twig.form.resources');
+
+ $this->assertTrue(\is_array($resources));
+ $this->assertTrue(\in_array($phoneTemplate, $resources, true));
+ }
+
+ /**
+ * @return iterable
+ */
+ public function themesProvider(): iterable
+ {
+ yield 'Bootstrap 5 horizontal' => [
+ 'bootstrap_5_horizontal_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number_bootstrap_5.html.twig',
+ ];
+ yield 'Bootstrap 5 vertical' => [
+ 'bootstrap_5_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number_bootstrap_5.html.twig',
+ ];
+ yield 'Bootstrap 4 horizontal' => [
+ 'bootstrap_4_horizontal_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number_bootstrap_4.html.twig',
+ ];
+ yield 'Bootstrap 4 vertical' => [
+ 'bootstrap_4_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number_bootstrap_4.html.twig',
+ ];
+ yield 'Bootstrap 3 horizontal' => [
+ 'bootstrap_3_horizontal_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number_bootstrap.html.twig',
+ ];
+ yield 'Bootstrap 3 vertical' => [
+ 'bootstrap_3_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number_bootstrap.html.twig',
+ ];
+ yield 'It adds phone number when using standard symfony form layout' => [
+ 'form_div_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number.html.twig',
+ ];
+ yield 'Test it enables phone number template anyway' => [
+ 'i_have_yolo_layout.html.twig',
+ '@MisdPhoneNumber/Form/phone_number.html.twig',
+ ];
+ }
+}
diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php
new file mode 100644
index 00000000..439433a1
--- /dev/null
+++ b/tests/DependencyInjection/ConfigurationTest.php
@@ -0,0 +1,125 @@
+ $configs
+ * @param array $expected
+ */
+ public function testConfiguration(array $configs, array $expected): void
+ {
+ $processor = new Processor();
+ $result = $processor->processConfiguration(new Configuration(), $configs);
+
+ $this->assertSame($expected, $result);
+ }
+
+ /**
+ * @return iterable, array}>
+ */
+ public function configurationDataProvider(): iterable
+ {
+ yield [[], [
+ 'twig' => [
+ 'enabled' => true,
+ ],
+ 'form' => [
+ 'enabled' => true,
+ ],
+ 'serializer' => [
+ 'enabled' => true,
+ 'default_region' => 'ZZ',
+ 'format' => PhoneNumberFormat::E164,
+ ],
+ 'validator' => [
+ 'enabled' => true,
+ 'default_region' => 'ZZ',
+ 'format' => PhoneNumberFormat::INTERNATIONAL,
+ ],
+ ]];
+
+ yield [[
+ 'misd_phone_number' => [
+ 'twig' => false,
+ 'form' => false,
+ 'serializer' => false,
+ 'validator' => false,
+ ],
+ ], [
+ 'twig' => [
+ 'enabled' => false,
+ ],
+ 'form' => [
+ 'enabled' => false,
+ ],
+ 'serializer' => [
+ 'enabled' => false,
+ 'default_region' => 'ZZ',
+ 'format' => PhoneNumberFormat::E164,
+ ],
+ 'validator' => [
+ 'enabled' => false,
+ 'default_region' => 'ZZ',
+ 'format' => PhoneNumberFormat::INTERNATIONAL,
+ ],
+ ]];
+
+ yield [[
+ 'misd_phone_number' => [
+ 'twig' => [
+ 'enabled' => false,
+ ],
+ 'form' => [
+ 'enabled' => false,
+ ],
+ 'serializer' => [
+ 'enabled' => false,
+ 'default_region' => 'GB',
+ 'format' => PhoneNumberFormat::E164,
+ ],
+ 'validator' => [
+ 'enabled' => false,
+ 'default_region' => 'GB',
+ 'format' => PhoneNumberFormat::INTERNATIONAL,
+ ],
+ ],
+ ], [
+ 'twig' => [
+ 'enabled' => false,
+ ],
+ 'form' => [
+ 'enabled' => false,
+ ],
+ 'serializer' => [
+ 'enabled' => false,
+ 'default_region' => 'GB',
+ 'format' => PhoneNumberFormat::E164,
+ ],
+ 'validator' => [
+ 'enabled' => false,
+ 'default_region' => 'GB',
+ 'format' => PhoneNumberFormat::INTERNATIONAL,
+ ],
+ ]];
+ }
+}
diff --git a/tests/DependencyInjection/MisdPhoneNumberExtensionTest.php b/tests/DependencyInjection/MisdPhoneNumberExtensionTest.php
new file mode 100644
index 00000000..1621ecda
--- /dev/null
+++ b/tests/DependencyInjection/MisdPhoneNumberExtensionTest.php
@@ -0,0 +1,132 @@
+container = new ContainerBuilder();
+
+ $extension->load([], $this->container);
+
+ $this->assertTrue($this->container->has('libphonenumber\PhoneNumberUtil'));
+ if (class_exists('libphonenumber\geocoding\PhoneNumberOfflineGeocoder') && \extension_loaded('intl')) {
+ $this->assertTrue($this->container->has('libphonenumber\geocoding\PhoneNumberOfflineGeocoder'));
+ }
+ if (class_exists('libphonenumber\ShortNumberInfo')) {
+ $this->assertTrue($this->container->has('libphonenumber\ShortNumberInfo'));
+ }
+ if (class_exists('libphonenumber\PhoneNumberToCarrierMapper') && \extension_loaded('intl')) {
+ $this->assertTrue($this->container->has('libphonenumber\PhoneNumberToCarrierMapper'));
+ }
+ if (class_exists('libphonenumber\PhoneNumberToTimeZonesMapper')) {
+ $this->assertTrue($this->container->has('libphonenumber\PhoneNumberToTimeZonesMapper'));
+ }
+ $this->assertTrue($this->container->has('Misd\PhoneNumberBundle\Templating\Helper\PhoneNumberHelper'));
+ $this->assertTrue($this->container->has('Misd\PhoneNumberBundle\Form\Type\PhoneNumberType'));
+
+ $services = $this->container->findTaggedServiceIds('form.type');
+ $this->assertArrayHasKey('Misd\PhoneNumberBundle\Form\Type\PhoneNumberType', $services);
+ $this->assertContains(['alias' => 'phone_number'], $services['Misd\PhoneNumberBundle\Form\Type\PhoneNumberType']);
+
+ $services = $this->container->findTaggedServiceIds('validator.constraint_validator');
+ $this->assertArrayHasKey('Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumberValidator', $services);
+
+ $this->assertSame(PhoneNumberUtil::UNKNOWN_REGION, $this->container->getParameter('misd_phone_number.validator.default_region'));
+ }
+
+ public function testDisabledServices(): void
+ {
+ $extension = new MisdPhoneNumberExtension();
+ $this->container = new ContainerBuilder();
+ $extension->load([
+ 'misd_phone_number' => [
+ 'twig' => false,
+ 'form' => false,
+ 'serializer' => false,
+ 'validator' => false,
+ ],
+ ], $this->container);
+
+ $this->assertTrue($this->container->has('libphonenumber\PhoneNumberUtil'));
+
+ $this->assertFalse($this->container->has('Misd\PhoneNumberBundle\Twig\Extension\PhoneNumberHelperExtension'));
+ $this->assertFalse($this->container->has('Misd\PhoneNumberBundle\Form\Type\PhoneNumberType'));
+ $this->assertFalse($this->container->has('Misd\PhoneNumberBundle\Serializer\Normalizer\PhoneNumberNormalizer'));
+ $this->assertFalse($this->container->has('Misd\PhoneNumberBundle\Validator\Constraints\PhoneNumberValidator'));
+ }
+
+ public function testValidatorParameters(): void
+ {
+ $extension = new MisdPhoneNumberExtension();
+ $this->container = new ContainerBuilder();
+ $extension->load([
+ 'misd_phone_number' => [
+ 'validator' => [
+ 'default_region' => 'GB',
+ 'format' => PhoneNumberFormat::E164,
+ ],
+ ],
+ ], $this->container);
+
+ $this->assertSame('GB', $this->container->getParameter('misd_phone_number.validator.default_region'));
+ $this->assertSame(0, $this->container->getParameter('misd_phone_number.validator.format'));
+ }
+
+ public function testNormalizerParameters(): void
+ {
+ $extension = new MisdPhoneNumberExtension();
+ $this->container = new ContainerBuilder();
+ $extension->load([
+ 'misd_phone_number' => [
+ 'serializer' => [
+ 'default_region' => 'FR',
+ 'format' => PhoneNumberFormat::INTERNATIONAL,
+ ],
+ ],
+ ], $this->container);
+
+ $this->assertSame('FR', $this->container->getParameter('misd_phone_number.serializer.default_region'));
+ $this->assertSame(PhoneNumberFormat::INTERNATIONAL, $this->container->getParameter('misd_phone_number.serializer.format'));
+ }
+
+ public function testValidatorDefaultRegionUppercase(): void
+ {
+ $extension = new MisdPhoneNumberExtension();
+ $this->container = new ContainerBuilder();
+ $extension->load([
+ 'misd_phone_number' => [
+ 'validator' => [
+ 'default_region' => 'gb',
+ ],
+ ],
+ ], $this->container);
+
+ $this->assertSame('GB', $this->container->getParameter('misd_phone_number.validator.default_region'));
+ }
+}
diff --git a/tests/Doctrine/DBAL/Types/PhoneNumberTypeTest.php b/tests/Doctrine/DBAL/Types/PhoneNumberTypeTest.php
new file mode 100644
index 00000000..268381b3
--- /dev/null
+++ b/tests/Doctrine/DBAL/Types/PhoneNumberTypeTest.php
@@ -0,0 +1,137 @@
+
+ */
+ private ObjectProphecy $platform;
+ private PhoneNumberType $type;
+ private PhoneNumberUtil $phoneNumberUtil;
+
+ public static function setUpBeforeClass(): void
+ {
+ Type::addType('phone_number', PhoneNumberType::class);
+ }
+
+ protected function setUp(): void
+ {
+ $this->platform = $this->prophesize(AbstractPlatform::class);
+ if (method_exists(AbstractPlatform::class, 'getVarcharTypeDeclarationSQL')) {
+ // DBAL < 4
+ $this->platform->getVarcharTypeDeclarationSQL()->willReturn('DUMMYVARCHAR()');
+ } else {
+ // DBAL 4
+ $this->platform->getStringTypeDeclarationSQL()->willReturn('DUMMYVARCHAR()');
+ }
+
+ /** @var PhoneNumberType $type */
+ $type = Type::getType('phone_number');
+
+ $this->type = $type;
+ $this->phoneNumberUtil = PhoneNumberUtil::getInstance();
+ }
+
+ public function testInstanceOf(): void
+ {
+ $this->assertInstanceOf(Type::class, $this->type);
+ }
+
+ public function testGetName(): void
+ {
+ $this->assertSame('phone_number', $this->type->getName());
+ }
+
+ public function testGetSQLDeclaration(): void
+ {
+ if (method_exists(AbstractPlatform::class, 'getVarcharTypeDeclarationSQL')) {
+ // DBAL < 4
+ $this->platform->getVarcharTypeDeclarationSQL(['length' => 35])->willReturn('DUMMYVARCHAR()');
+ } else {
+ // DBAL 4
+ $this->platform->getStringTypeDeclarationSQL(['length' => 35])->willReturn('DUMMYVARCHAR()');
+ }
+ $this->assertSame('DUMMYVARCHAR()', $this->type->getSQLDeclaration([], $this->platform->reveal()));
+ }
+
+ public function testConvertToDatabaseValueWithNull(): void
+ {
+ $this->assertNull($this->type->convertToDatabaseValue(null, $this->platform->reveal()));
+ }
+
+ public function testConvertToDatabaseValueWithPhoneNumber(): void
+ {
+ $phoneNumber = $this->phoneNumberUtil->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION);
+
+ $this->assertSame('+441234567890', $this->type->convertToDatabaseValue($phoneNumber, $this->platform->reveal()));
+ }
+
+ public function testConvertToDatabaseValueFailure(): void
+ {
+ $this->expectException(ConversionException::class);
+
+ $this->type->convertToDatabaseValue('foo', $this->platform->reveal());
+ }
+
+ public function testConvertToPHPValueWithNull(): void
+ {
+ $this->assertNull($this->type->convertToPHPValue(null, $this->platform->reveal()));
+ }
+
+ public function testConvertToPHPValueWithPhoneNumber(): void
+ {
+ $phoneNumber = $this->type->convertToPHPValue('+441234567890', $this->platform->reveal());
+
+ $this->assertInstanceOf('libphoneNumber\PhoneNumber', $phoneNumber);
+ $this->assertSame('+441234567890', $this->phoneNumberUtil->format($phoneNumber, PhoneNumberFormat::E164));
+ }
+
+ public function testConvertToPHPValueWithAPhoneNumberInstance(): void
+ {
+ $expectedPhoneNumber = $this->phoneNumberUtil->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION);
+
+ $phoneNumber = $this->type->convertToPHPValue($expectedPhoneNumber, $this->platform->reveal());
+
+ $this->assertEquals($expectedPhoneNumber, $phoneNumber);
+ }
+
+ public function testConvertToPHPValueFailure(): void
+ {
+ $this->expectException(ConversionException::class);
+
+ $this->type->convertToPHPValue('foo', $this->platform->reveal());
+ }
+
+ public function testRequiresSQLCommentHint(): void
+ {
+ $this->assertTrue($this->type->requiresSQLCommentHint($this->platform->reveal()));
+ }
+}
diff --git a/tests/Form/DataTransformer/PhoneNumberToArrayTransformerTest.php b/tests/Form/DataTransformer/PhoneNumberToArrayTransformerTest.php
new file mode 100644
index 00000000..72853dca
--- /dev/null
+++ b/tests/Form/DataTransformer/PhoneNumberToArrayTransformerTest.php
@@ -0,0 +1,192 @@
+phoneNumberUtil = PhoneNumberUtil::getInstance();
+ }
+
+ public function testConstructor(): void
+ {
+ $transformer = new PhoneNumberToArrayTransformer([]);
+
+ $this->assertInstanceOf('Symfony\Component\Form\DataTransformerInterface', $transformer);
+ }
+
+ /**
+ * @dataProvider transformProvider
+ *
+ * @param string[] $countryChoices
+ * @param array{country: string, number: string}|null $actual
+ * @param array{country: string, number: string}|string $expected
+ */
+ public function testTransform(array $countryChoices, ?array $actual, array|string $expected): void
+ {
+ $transformer = new PhoneNumberToArrayTransformer($countryChoices);
+
+ $phoneNumber = null;
+ if (\is_array($actual)) {
+ try {
+ $phoneNumber = $this->phoneNumberUtil->parse($actual['number'], $actual['country']);
+ } catch (NumberParseException $e) {
+ $phoneNumber = $actual['number'];
+ }
+ }
+
+ try {
+ /* @phpstan-ignore-next-line */
+ $transformed = $transformer->transform($phoneNumber);
+ } catch (TransformationFailedException $e) {
+ $transformed = self::TRANSFORMATION_FAILED;
+ }
+
+ $this->assertSame($expected, $transformed);
+ }
+
+ /**
+ * 0 => Country choices
+ * 1 => Actual value
+ * 2 => Expected result.
+ *
+ * @return iterable
+ */
+ public function transformProvider(): iterable
+ {
+ yield [
+ ['GB'],
+ null,
+ ['country' => '', 'number' => ''],
+ ];
+ yield [
+ ['GB'],
+ ['country' => 'GB', 'number' => '01234567890'],
+ ['country' => 'GB', 'number' => '01234 567890'],
+ ];
+ // Wrong country code, but matching country exists.
+ yield [
+ ['GB', 'JE'],
+ ['country' => 'JE', 'number' => '01234567890'],
+ ['country' => 'GB', 'number' => '01234 567890'],
+ ];
+ // Wrong country code, but matching country exists.
+ yield [
+ ['GB', 'JE'],
+ ['country' => 'JE', 'number' => '+441234567890'],
+ ['country' => 'GB', 'number' => '01234 567890'],
+ ];
+ // Country code not in list.
+ yield [
+ ['US'],
+ ['country' => 'GB', 'number' => '01234567890'],
+ self::TRANSFORMATION_FAILED,
+ ];
+ yield [
+ ['US'],
+ ['country' => 'GB', 'number' => 'foo'],
+ self::TRANSFORMATION_FAILED,
+ ];
+ }
+
+ /**
+ * @dataProvider reverseTransformProvider
+ *
+ * @param string[] $countryChoices
+ */
+ public function testReverseTransform(array $countryChoices, mixed $actual, ?string $expected): void
+ {
+ $transformer = new PhoneNumberToArrayTransformer($countryChoices);
+
+ try {
+ $transformed = $transformer->reverseTransform($actual);
+ } catch (TransformationFailedException $e) {
+ $transformed = self::TRANSFORMATION_FAILED;
+ }
+
+ if ($transformed instanceof PhoneNumber) {
+ $transformed = $this->phoneNumberUtil->format($transformed, PhoneNumberFormat::E164);
+ }
+
+ $this->assertSame($expected, $transformed);
+ }
+
+ /**
+ * 0 => Country choices
+ * 1 => Actual value
+ * 2 => Expected result.
+ *
+ * @return iterable
+ */
+ public function reverseTransformProvider(): iterable
+ {
+ yield [
+ ['GB'],
+ null,
+ null,
+ ];
+ yield [
+ ['GB'],
+ 'foo',
+ self::TRANSFORMATION_FAILED,
+ ];
+ yield [
+ ['GB'],
+ ['country' => '', 'number' => ''],
+ null,
+ ];
+ yield [
+ ['GB'],
+ ['country' => 'GB', 'number' => ''],
+ null,
+ ];
+ yield [
+ ['GB'],
+ ['country' => '', 'number' => 'foo'],
+ self::TRANSFORMATION_FAILED,
+ ];
+ yield [
+ ['GB'],
+ ['country' => 'GB', 'number' => '01234 567890'],
+ '+441234567890',
+ ];
+ yield [
+ ['GB'],
+ ['country' => 'GB', 'number' => '+44 1234 567890'],
+ '+441234567890',
+ ];
+ // Country code not in list.
+ yield [
+ ['US'],
+ ['country' => 'GB', 'number' => '+44 1234 567890'],
+ self::TRANSFORMATION_FAILED,
+ ];
+ }
+}
diff --git a/Tests/Form/DataTransformer/PhoneNumberToStringTransformerTest.php b/tests/Form/DataTransformer/PhoneNumberToStringTransformerTest.php
similarity index 56%
rename from Tests/Form/DataTransformer/PhoneNumberToStringTransformerTest.php
rename to tests/Form/DataTransformer/PhoneNumberToStringTransformerTest.php
index 40c74798..64acebea 100644
--- a/Tests/Form/DataTransformer/PhoneNumberToStringTransformerTest.php
+++ b/tests/Form/DataTransformer/PhoneNumberToStringTransformerTest.php
@@ -1,5 +1,7 @@
phoneNumberUtil = PhoneNumberUtil::getInstance();
}
- public function testConstructor()
+ public function testConstructor(): void
{
$transformer = new PhoneNumberToStringTransformer();
@@ -46,18 +45,20 @@ public function testConstructor()
/**
* @dataProvider transformProvider
*/
- public function testTransform($defaultRegion, $format, $actual, $expected)
+ public function testTransform(string $defaultRegion, int $format, ?string $actual, string $expected): void
{
$transformer = new PhoneNumberToStringTransformer($defaultRegion, $format);
$phoneNumberUtil = PhoneNumberUtil::getInstance();
try {
+ /* @phpstan-ignore-next-line */
$phoneNumber = $phoneNumberUtil->parse($actual, $defaultRegion);
} catch (NumberParseException $e) {
$phoneNumber = $actual;
}
try {
+ /* @phpstan-ignore-next-line */
$transformed = $transformer->transform($phoneNumber);
} catch (TransformationFailedException $e) {
$transformed = self::TRANSFORMATION_FAILED;
@@ -70,28 +71,28 @@ public function testTransform($defaultRegion, $format, $actual, $expected)
* 0 => Default region
* 1 => Format
* 2 => Actual value
- * 3 => Expected result
+ * 3 => Expected result.
+ *
+ * @return iterable
*/
- public function transformProvider()
+ public function transformProvider(): iterable
{
- return array(
- array(PhoneNumberUtil::UNKNOWN_REGION, PhoneNumberFormat::INTERNATIONAL, null, ''),
- array(PhoneNumberUtil::UNKNOWN_REGION, PhoneNumberFormat::NATIONAL, 'foo', self::TRANSFORMATION_FAILED),
- array(PhoneNumberUtil::UNKNOWN_REGION, PhoneNumberFormat::NATIONAL, '0', self::TRANSFORMATION_FAILED),
- array(
- PhoneNumberUtil::UNKNOWN_REGION,
- PhoneNumberFormat::INTERNATIONAL,
- '+441234567890',
- '+44 1234 567890',
- ),
- array('GB', PhoneNumberFormat::NATIONAL, '01234567890', '01234 567890'),
- );
+ yield [PhoneNumberUtil::UNKNOWN_REGION, PhoneNumberFormat::INTERNATIONAL, null, ''];
+ yield [PhoneNumberUtil::UNKNOWN_REGION, PhoneNumberFormat::NATIONAL, 'foo', self::TRANSFORMATION_FAILED];
+ yield [PhoneNumberUtil::UNKNOWN_REGION, PhoneNumberFormat::NATIONAL, '0', self::TRANSFORMATION_FAILED];
+ yield [
+ PhoneNumberUtil::UNKNOWN_REGION,
+ PhoneNumberFormat::INTERNATIONAL,
+ '+441234567890',
+ '+44 1234 567890',
+ ];
+ yield ['GB', PhoneNumberFormat::NATIONAL, '01234567890', '01234 567890'];
}
/**
* @dataProvider reverseTransformProvider
*/
- public function testReverseTransform($defaultRegion, $actual, $expected)
+ public function testReverseTransform(string $defaultRegion, ?string $actual, ?string $expected): void
{
$transformer = new PhoneNumberToStringTransformer($defaultRegion);
@@ -111,16 +112,16 @@ public function testReverseTransform($defaultRegion, $actual, $expected)
/**
* 0 => Default region
* 1 => Actual value
- * 2 => Expected result
+ * 2 => Expected result.
+ *
+ * @return iterable
*/
- public function reverseTransformProvider()
+ public function reverseTransformProvider(): iterable
{
- return array(
- array(PhoneNumberUtil::UNKNOWN_REGION, null, null),
- array(PhoneNumberUtil::UNKNOWN_REGION, 'foo', self::TRANSFORMATION_FAILED),
- array(PhoneNumberUtil::UNKNOWN_REGION, '0', self::TRANSFORMATION_FAILED),
- array(PhoneNumberUtil::UNKNOWN_REGION, '+44 1234 567890', '+441234567890'),
- array('GB', '01234 567890', '+441234567890'),
- );
+ yield [PhoneNumberUtil::UNKNOWN_REGION, null, null];
+ yield [PhoneNumberUtil::UNKNOWN_REGION, 'foo', self::TRANSFORMATION_FAILED];
+ yield [PhoneNumberUtil::UNKNOWN_REGION, '0', self::TRANSFORMATION_FAILED];
+ yield [PhoneNumberUtil::UNKNOWN_REGION, '+44 1234 567890', '+441234567890'];
+ yield ['GB', '01234 567890', '+441234567890'];
}
}
diff --git a/tests/Form/Type/PhoneNumberTypeTest.php b/tests/Form/Type/PhoneNumberTypeTest.php
new file mode 100644
index 00000000..022c0d74
--- /dev/null
+++ b/tests/Form/Type/PhoneNumberTypeTest.php
@@ -0,0 +1,331 @@
+factory = Forms::createFormFactoryBuilder()->getFormFactory();
+ }
+
+ /**
+ * @dataProvider singleFieldProvider
+ *
+ * @param array $options
+ */
+ public function testSingleField(string $input, array $options, string $output): void
+ {
+ $form = $this->factory->create(PhoneNumberType::class, null, $options);
+
+ $form->submit($input);
+
+ if (method_exists($form, 'getTransformationFailure') && $failure = $form->getTransformationFailure()) {
+ throw $failure;
+ } else {
+ $this->assertTrue($form->isSynchronized());
+ }
+
+ $view = $form->createView();
+
+ $this->assertSame('tel', $view->vars['type']);
+ $this->assertSame($output, $view->vars['value']);
+ }
+
+ /**
+ * 0 => Input
+ * 1 => Options
+ * 2 => Output.
+ *
+ * @return iterable, string}>
+ */
+ public function singleFieldProvider(): iterable
+ {
+ yield ['+441234567890', [], '+44 1234 567890'];
+ yield ['+44 1234 567890', ['format' => PhoneNumberFormat::NATIONAL], '+44 1234 567890'];
+ yield ['+44 1234 567890', ['default_region' => 'GB', 'format' => PhoneNumberFormat::NATIONAL], '01234 567890'];
+ yield ['+1 650-253-0000', ['default_region' => 'GB', 'format' => PhoneNumberFormat::NATIONAL], '00 1 650-253-0000'];
+ yield ['01234 567890', ['default_region' => 'GB'], '+44 1234 567890'];
+ yield ['', [], ''];
+ }
+
+ /**
+ * @dataProvider countryChoiceValuesProvider
+ *
+ * @param array $input
+ * @param array $output
+ */
+ public function testCountryChoiceValues(array $input, array $output): void
+ {
+ $options = ['widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE];
+ $form = $this->factory->create(PhoneNumberType::class, null, $options);
+
+ $form->submit($input);
+
+ if (method_exists($form, 'getTransformationFailure') && $failure = $form->getTransformationFailure()) {
+ throw $failure;
+ } else {
+ $this->assertTrue($form->isSynchronized());
+ }
+
+ $view = $form->createView();
+
+ $this->assertSame('tel', $view->vars['type']);
+ $this->assertSame($output, $view->vars['value']);
+ }
+
+ /**
+ * 0 => Input
+ * 1 => Options
+ * 2 => Output.
+ *
+ * @return iterable, array}>
+ */
+ public function countryChoiceValuesProvider(): iterable
+ {
+ yield [['country' => 'GB', 'number' => '01234 567890'], ['country' => 'GB', 'number' => '01234 567890']];
+ yield [['country' => 'GB', 'number' => '+44 1234 567890'], ['country' => 'GB', 'number' => '01234 567890']];
+ yield [['country' => 'GB', 'number' => '1234 567890'], ['country' => 'GB', 'number' => '01234 567890']];
+ yield [['country' => 'GB', 'number' => '+1 650-253-0000'], ['country' => 'US', 'number' => '(650) 253-0000']];
+ yield [['country' => '', 'number' => ''], ['country' => '', 'number' => '']];
+ }
+
+ /**
+ * @dataProvider countryChoiceChoicesProvider
+ *
+ * @param string[] $choices
+ * @param ChoiceView[] $expectedChoices
+ */
+ public function testCountryChoiceChoices(array $choices, int $expectedChoicesCount, array $expectedChoices): void
+ {
+ IntlTestHelper::requireIntl($this);
+
+ $form = $this->factory->create(
+ PhoneNumberType::class,
+ null,
+ ['widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE, 'country_choices' => $choices]
+ );
+
+ $view = $form->createView();
+ $choices = $view['country']->vars['choices'];
+
+ $this->assertCount($expectedChoicesCount, $choices);
+ foreach ($expectedChoices as $expectedChoice) {
+ $this->assertContainsEquals($expectedChoice, $choices);
+ }
+ }
+
+ /**
+ * 0 => Choices
+ * 1 => Expected choices count
+ * 2 => Expected choices.
+ *
+ * @return iterable
+ */
+ public function countryChoiceChoicesProvider(): iterable
+ {
+ yield [
+ [],
+ // 3 regions have an already used label "TA", "AC" and XK
+ // @see https://fr.wikipedia.org/wiki/ISO_3166-2#cite_note-UPU-1
+ 242,
+ [
+ $this->createChoiceView('United Kingdom (+44)', 'GB'),
+ ],
+ ];
+ yield [
+ ['GB', 'US'],
+ 2,
+ [
+ $this->createChoiceView('United Kingdom (+44)', 'GB'),
+ $this->createChoiceView('United States (+1)', 'US'),
+ ],
+ ];
+ yield [
+ ['GB', 'US', PhoneNumberUtil::UNKNOWN_REGION],
+ 2,
+ [
+ $this->createChoiceView('United Kingdom (+44)', 'GB'),
+ $this->createChoiceView('United States (+1)', 'US'),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider countryChoiceFormatProvider
+ *
+ * @param ChoiceView[] $expectedChoices
+ */
+ public function testCountryChoiceFormat(string $displayType, bool $displayEmojiFlag, array $expectedChoices): void
+ {
+ $options['widget'] = PhoneNumberType::WIDGET_COUNTRY_CHOICE;
+ $options['country_display_type'] = $displayType;
+ $options['country_display_emoji_flag'] = $displayEmojiFlag;
+ $form = $this->factory->create(PhoneNumberType::class, null, $options);
+
+ $view = $form->createView();
+ $choices = $view['country']->vars['choices'];
+
+ foreach ($expectedChoices as $expectedChoice) {
+ $this->assertContainsEquals($expectedChoice, $choices);
+ }
+ }
+
+ /**
+ * 0 => Display type
+ * 1 => Display emoji flag
+ * 2 => Expected choices.
+ *
+ * @return iterable
+ */
+ public function countryChoiceFormatProvider(): iterable
+ {
+ yield [
+ PhoneNumberType::DISPLAY_COUNTRY_FULL,
+ false,
+ [
+ $this->createChoiceView('United Kingdom (+44)', 'GB'),
+ ],
+ ];
+ yield [
+ PhoneNumberType::DISPLAY_COUNTRY_SHORT,
+ false,
+ [
+ $this->createChoiceView('GB +44', 'GB'),
+ ],
+ ];
+ yield [
+ PhoneNumberType::DISPLAY_COUNTRY_FULL,
+ true,
+ [
+ $this->createChoiceView('đŦđ§ United Kingdom (+44)', 'GB'),
+ ],
+ ];
+ yield [
+ PhoneNumberType::DISPLAY_COUNTRY_SHORT,
+ true,
+ [
+ $this->createChoiceView('đŦđ§ GB +44', 'GB'),
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider countryChoicePlaceholderProvider
+ */
+ public function testCountryChoicePlaceholder(?string $placeholder, ?string $expectedPlaceholder): void
+ {
+ IntlTestHelper::requireIntl($this);
+ $form = $this->factory->create(PhoneNumberType::class, null, ['widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE, 'country_placeholder' => $placeholder]);
+
+ $view = $form->createView();
+ $renderedPlaceholder = $view['country']->vars['placeholder'];
+ $this->assertEquals($expectedPlaceholder, $renderedPlaceholder);
+ }
+
+ /**
+ * 0 => Filled
+ * 1 => not filled
+ * 2 => empty.
+ *
+ * @return iterable
+ */
+ public function countryChoicePlaceholderProvider(): iterable
+ {
+ yield ['Choose a country', 'Choose a country'];
+ yield [null, null];
+ yield ['', ''];
+ }
+
+ public function testCountryChoiceTranslations(): void
+ {
+ IntlTestHelper::requireFullIntl($this);
+ \Locale::setDefault('fr');
+
+ $form = $this->factory->create(PhoneNumberType::class, null, ['widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE]);
+
+ $view = $form->createView();
+ $choices = $view['country']->vars['choices'];
+
+ $this->assertContainsEquals($this->createChoiceView('Royaume-Uni (+44)', 'GB'), $choices);
+ $this->assertFalse($view['country']->vars['choice_translation_domain']);
+ }
+
+ public function testInvalidWidget(): void
+ {
+ $this->expectException(InvalidOptionsException::class);
+
+ $this->factory->create(PhoneNumberType::class, null, ['widget' => 'foo']);
+ }
+
+ public function testGetNameAndBlockPrefixAreTel(): void
+ {
+ $type = new PhoneNumberType();
+
+ $this->assertSame('phone_number', $type->getBlockPrefix());
+ $this->assertSame($type->getBlockPrefix(), $type->getName());
+ }
+
+ public function testCountryChoiceCountryOptions(): void
+ {
+ $form = $this->factory->create(PhoneNumberType::class, null, [
+ 'widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE,
+ 'country_options' => [
+ 'attr' => [
+ 'class' => 'custom-select-class',
+ ],
+ ],
+ ]);
+ $view = $form->createView();
+
+ $this->assertEquals(['class' => 'custom-select-class'], $view['country']->vars['attr']);
+ }
+
+ public function testCountryChoiceNumberOptions(): void
+ {
+ $form = $this->factory->create(PhoneNumberType::class, null, [
+ 'widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE,
+ 'number_options' => [
+ 'attr' => [
+ 'placeholder' => '000 000',
+ ],
+ ],
+ ]);
+ $view = $form->createView();
+
+ $this->assertEquals(['placeholder' => '000 000'], $view['number']->vars['attr']);
+ }
+
+ private function createChoiceView(string $label, string $code): ChoiceView
+ {
+ return new ChoiceView($code, $code, $label);
+ }
+}
diff --git a/tests/Serializer/Normalizer/PhoneNumberNormalizerTest.php b/tests/Serializer/Normalizer/PhoneNumberNormalizerTest.php
new file mode 100644
index 00000000..1fca89bf
--- /dev/null
+++ b/tests/Serializer/Normalizer/PhoneNumberNormalizerTest.php
@@ -0,0 +1,108 @@
+markTestSkipped('The Symfony Serializer is not available.');
+ }
+ }
+
+ public function testSupportNormalization(): void
+ {
+ $normalizer = new PhoneNumberNormalizer($this->prophesize(PhoneNumberUtil::class)->reveal());
+
+ $this->assertTrue($normalizer->supportsNormalization(new PhoneNumber()));
+ $this->assertFalse($normalizer->supportsNormalization(new \stdClass()));
+ }
+
+ public function testNormalize(): void
+ {
+ $phoneNumber = new PhoneNumber();
+ $phoneNumber->setRawInput('+33193166989');
+
+ $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class);
+ $phoneNumberUtil->format($phoneNumber, PhoneNumberFormat::E164)->shouldBeCalledTimes(1)->willReturn('+33193166989');
+
+ $normalizer = new PhoneNumberNormalizer($phoneNumberUtil->reveal());
+
+ $this->assertEquals('+33193166989', $normalizer->normalize($phoneNumber));
+ }
+
+ public function testSupportDenormalization(): void
+ {
+ $normalizer = new PhoneNumberNormalizer($this->prophesize(PhoneNumberUtil::class)->reveal());
+
+ $this->assertTrue($normalizer->supportsDenormalization('+33193166989', 'libphonenumber\PhoneNumber'));
+ $this->assertFalse($normalizer->supportsDenormalization(new PhoneNumber(), 'libphonenumber\PhoneNumber'));
+ $this->assertFalse($normalizer->supportsDenormalization('+33193166989', 'stdClass'));
+ }
+
+ public function testDenormalize(): void
+ {
+ $phoneNumber = new PhoneNumber();
+ $phoneNumber->setRawInput('+33193166989');
+
+ $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class);
+ $phoneNumberUtil->parse('+33193166989', PhoneNumberUtil::UNKNOWN_REGION)->shouldBeCalledTimes(1)->willReturn($phoneNumber);
+
+ $normalizer = new PhoneNumberNormalizer($phoneNumberUtil->reveal());
+
+ $this->assertSame($phoneNumber, $normalizer->denormalize('+33193166989', 'libphonenumber\PhoneNumber'));
+ }
+
+ public function testItDenormalizeNullToNull(): void
+ {
+ $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class);
+ $phoneNumberUtil->parse(Argument::cetera())->shouldNotBeCalled();
+
+ $normalizer = new PhoneNumberNormalizer($phoneNumberUtil->reveal());
+
+ $this->assertNull($normalizer->denormalize(null, 'libphonenumber\PhoneNumber'));
+ }
+
+ public function testInvalidDateThrowException(): void
+ {
+ $this->expectException(UnexpectedValueException::class);
+
+ $phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class);
+ $phoneNumberUtil
+ ->parse('invalid phone number', PhoneNumberUtil::UNKNOWN_REGION)
+ ->shouldBeCalledTimes(1)
+ ->willThrow(new NumberParseException(NumberParseException::INVALID_COUNTRY_CODE, ''))
+ ;
+
+ $normalizer = new PhoneNumberNormalizer($phoneNumberUtil->reveal());
+ $normalizer->denormalize('invalid phone number', 'libphonenumber\PhoneNumber');
+ }
+}
diff --git a/tests/Templating/Helper/PhoneNumberHelperTest.php b/tests/Templating/Helper/PhoneNumberHelperTest.php
new file mode 100644
index 00000000..9f32e602
--- /dev/null
+++ b/tests/Templating/Helper/PhoneNumberHelperTest.php
@@ -0,0 +1,106 @@
+
+ */
+ protected ObjectProphecy $phoneNumberUtil;
+ protected PhoneNumberHelper $helper;
+
+ protected function setUp(): void
+ {
+ $this->phoneNumberUtil = $this->prophesize(PhoneNumberUtil::class);
+ $this->helper = new PhoneNumberHelper($this->phoneNumberUtil->reveal());
+ }
+
+ /**
+ * @dataProvider processProvider
+ */
+ public function testProcess(int|string $format, int $expectedFormat): void
+ {
+ $phoneNumber = $this->prophesize(PhoneNumber::class);
+ $this->phoneNumberUtil
+ ->format($phoneNumber->reveal(), $expectedFormat)
+ ->shouldBeCalledTimes(1)
+ ->willReturn('+33600000000');
+
+ $this->helper->format($phoneNumber->reveal(), $format);
+ }
+
+ /**
+ * 0 => Format
+ * 1 => Expected format.
+ *
+ * @return iterable
+ */
+ public function processProvider(): iterable
+ {
+ yield [PhoneNumberFormat::NATIONAL, PhoneNumberFormat::NATIONAL];
+ yield ['NATIONAL', PhoneNumberFormat::NATIONAL];
+ }
+
+ public function testProcessInvalidArgumentException(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+
+ $phoneNumber = $this->prophesize(PhoneNumber::class);
+
+ $this->helper->format($phoneNumber->reveal(), 'foo');
+ }
+
+ /**
+ * @dataProvider formatOutOfCountryCallingNumberProvider
+ */
+ public function testFormatOutOfCountryCallingNumber(string $phoneNumber, string $defaultRegion, ?string $regionCode, string $expectedResult): void
+ {
+ $phoneNumberUtil = PhoneNumberUtil::getInstance();
+ $helper = new PhoneNumberHelper($phoneNumberUtil);
+
+ $phoneNumber = $phoneNumberUtil->parse($phoneNumber, $defaultRegion);
+
+ $this->assertSame($expectedResult, $helper->formatOutOfCountryCallingNumber($phoneNumber, $regionCode));
+ }
+
+ /**
+ * 0 => The phone number.
+ * 1 => Phone number default region.
+ * 2 => Country calling from.
+ * 3 => Expected format.
+ *
+ * @return iterable
+ */
+ public function formatOutOfCountryCallingNumberProvider()
+ {
+ yield ['1-800-854-3680', 'US', 'US', '1 (800) 854-3680'];
+ yield ['1-800-854-3680', 'US', 'NL', '00 1 800-854-3680'];
+ yield ['1-800-854-3680', 'US', null, '+1 800-854-3680'];
+ }
+}
diff --git a/tests/Validator/Constraints/PhoneNumberTest.php b/tests/Validator/Constraints/PhoneNumberTest.php
new file mode 100644
index 00000000..3ec9420c
--- /dev/null
+++ b/tests/Validator/Constraints/PhoneNumberTest.php
@@ -0,0 +1,75 @@
+assertObjectHasProperty('message', $phoneNumber);
+ $this->assertObjectHasProperty('type', $phoneNumber);
+ $this->assertObjectHasProperty('defaultRegion', $phoneNumber);
+ $this->assertObjectHasProperty('regionPath', $phoneNumber);
+ }
+
+ /**
+ * @dataProvider messageProvider
+ *
+ * @param string|string[]|null $type
+ */
+ public function testMessage(?string $message, string|array|null $type, ?int $format, string $expectedMessage): void
+ {
+ $phoneNumber = new PhoneNumber($format, $type, null, null, $message);
+ $this->assertSame($expectedMessage, $phoneNumber->getMessage());
+ $this->assertSame($format, $phoneNumber->format);
+ }
+
+ /**
+ * 0 => Message (optional)
+ * 1 => Type (optional)
+ * 2 => Format (optional)
+ * 3 => Expected message.
+ *
+ * @return iterable
+ */
+ public function messageProvider(): iterable
+ {
+ yield [null, null, null, 'This value is not a valid phone number.'];
+ yield [null, 'fixed_line', null, 'This value is not a valid fixed-line number.'];
+ yield [null, 'mobile', null, 'This value is not a valid mobile number.'];
+ yield [null, 'pager', null, 'This value is not a valid pager number.'];
+ yield [null, 'personal_number', null, 'This value is not a valid personal number.'];
+ yield [null, 'premium_rate', null, 'This value is not a valid premium-rate number.'];
+ yield [null, 'shared_cost', null, 'This value is not a valid shared-cost number.'];
+ yield [null, 'toll_free', null, 'This value is not a valid toll-free number.'];
+ yield [null, 'uan', null, 'This value is not a valid UAN.'];
+ yield [null, 'voip', null, 'This value is not a valid VoIP number.'];
+ yield [null, 'voicemail', null, 'This value is not a valid voicemail access number.'];
+ yield [null, ['fixed_line', 'voip'], null, 'This value is not a valid phone number.'];
+ yield [null, ['uan', 'fixed_line'], null, 'This value is not a valid phone number.'];
+ yield ['foo', null, null, 'foo'];
+ yield ['foo', 'fixed_line', null, 'foo'];
+ yield ['foo', 'mobile', null, 'foo'];
+ yield [null, null, PhoneNumberFormat::E164, 'This value is not a valid phone number.'];
+ }
+}
diff --git a/tests/Validator/Constraints/PhoneNumberValidatorTest.php b/tests/Validator/Constraints/PhoneNumberValidatorTest.php
new file mode 100644
index 00000000..02014249
--- /dev/null
+++ b/tests/Validator/Constraints/PhoneNumberValidatorTest.php
@@ -0,0 +1,194 @@
+context = $this->createMock(ExecutionContextInterface::class);
+
+ $this->validator = new PhoneNumberValidator(PhoneNumberUtil::getInstance());
+ $this->validator->initialize($this->context);
+
+ $this->context->method('getObject')->willReturn(new Foo());
+ }
+
+ /**
+ * @dataProvider validateProvider
+ *
+ * @param string[]|string|null $type
+ */
+ public function testValidate(
+ string|LibPhoneNumber|null $value,
+ bool $violates,
+ array|string|null $type = null,
+ ?string $defaultRegion = null,
+ ?string $regionPath = null,
+ ?int $format = null,
+ ): void {
+ $constraint = new PhoneNumber($format, $type, $defaultRegion, $regionPath);
+
+ if (true === $violates) {
+ $constraintViolationBuilder = $this->createMock(ConstraintViolationBuilderInterface::class);
+ $constraintViolationBuilder
+ ->expects($this->exactly(2))
+ ->method('setParameter')
+ ->with($this->isType('string'), $this->isType('string'))
+ ->willReturn($constraintViolationBuilder);
+ $constraintViolationBuilder
+ ->expects($this->once())
+ ->method('setCode')
+ ->with($this->isType('string'))
+ ->willReturn($constraintViolationBuilder);
+
+ $this->context
+ ->expects($this->once())
+ ->method('buildViolation')
+ ->with($constraint->getMessage())
+ ->willReturn($constraintViolationBuilder);
+ } else {
+ $this->context->expects($this->never())->method('buildViolation');
+ }
+
+ $this->validator->validate($value, $constraint);
+ }
+
+ /**
+ * @requires PHP 8
+ */
+ public function testValidateFromAttribute(): void
+ {
+ $classMetadata = new ClassMetadata(PhoneNumberDummy::class);
+ if (class_exists(AnnotationLoader::class)) {
+ (new AnnotationLoader())->loadClassMetadata($classMetadata);
+ } else {
+ (new AttributeLoader())->loadClassMetadata($classMetadata);
+ }
+
+ [$constraint1] = $classMetadata->properties['phoneNumber1']->constraints;
+ [$constraint2] = $classMetadata->properties['phoneNumber2']->constraints;
+
+ $this->validator->validate('+33606060606', $constraint1);
+ $this->validator->validate('+441234567890', $constraint2);
+
+ $this->expectNotToPerformAssertions();
+ }
+
+ /**
+ * 0 => Value
+ * 1 => Violates?
+ * 2 => Type (optional)
+ * 3 => Default region (optional).
+ * 4 => Region Path (optional).
+ *
+ * @return iterable
+ */
+ public function validateProvider(): iterable
+ {
+ yield [null, false];
+ yield ['', false];
+ yield [PhoneNumberUtil::getInstance()->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION), false];
+ yield [PhoneNumberUtil::getInstance()->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION), false, 'fixed_line'];
+ yield [PhoneNumberUtil::getInstance()->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION), true, 'mobile'];
+ yield [PhoneNumberUtil::getInstance()->parse('+441234567890', PhoneNumberUtil::UNKNOWN_REGION), false, ['fixed_line', 'mobile']];
+ yield [PhoneNumberUtil::getInstance()->parse('+44123456789', PhoneNumberUtil::UNKNOWN_REGION), true];
+ yield ['+441234567890', false];
+ yield ['+441234567890', false, 'fixed_line'];
+ yield ['+441234567890', true, 'mobile'];
+ yield ['+441234567890', false, ['mobile', 'fixed_line']];
+ yield ['+441234567890', true, ['mobile', 'voip']];
+ yield ['+44123456789', true];
+ yield ['+44123456789', true, 'mobile'];
+ yield ['+12015555555', false];
+ yield ['+12015555555', false, 'fixed_line'];
+ yield ['+12015555555', false, 'mobile'];
+ yield ['+12015555555', false, ['mobile', 'fixed_line']];
+ yield ['+12015555555', true, ['pager', 'voip', 'uan']];
+ yield ['+447640123456', false, 'pager'];
+ yield ['+441234567890', true, 'pager'];
+ yield ['+447012345678', false, 'personal_number'];
+ yield ['+441234567890', true, 'personal_number'];
+ yield ['+449012345678', false, 'premium_rate'];
+ yield ['+441234567890', true, 'premium_rate'];
+ yield ['+441234567890', true, 'shared_cost'];
+ yield ['+448001234567', false, 'toll_free'];
+ yield ['+441234567890', true, 'toll_free'];
+ yield ['+445512345678', false, 'uan'];
+ yield ['+441234567890', true, 'uan'];
+ yield ['+445612345678', false, 'voip'];
+ yield ['+441234567890', true, 'voip'];
+ yield ['+41860123456789', false, 'voicemail'];
+ yield ['+441234567890', true, 'voicemail'];
+ yield ['2015555555', false, null, 'US'];
+ yield ['2015555555', false, 'fixed_line', 'US'];
+ yield ['2015555555', false, 'mobile', 'US'];
+ yield ['01234 567890', false, null, 'GB'];
+ yield ['foo', true];
+ yield ['+441234567890', true, 'mobile', null, 'regionPath'];
+ yield ['+33606060606', false, 'mobile', null, 'regionPath'];
+ yield ['+33606060606', false, 'mobile', null, null, PhoneNumberFormat::E164];
+ yield ['2015555555', true, null, null, null, PhoneNumberFormat::E164];
+ }
+
+ public function testValidateThrowsUnexpectedTypeExceptionOnBadValue(): void
+ {
+ $this->expectException(UnexpectedTypeException::class);
+
+ $this->validator->validate($this, new PhoneNumber());
+ }
+
+ protected function createValidator(): PhoneNumberValidator
+ {
+ return new PhoneNumberValidator(PhoneNumberUtil::getInstance());
+ }
+}
+
+class Foo
+{
+ public string $regionPath = 'GB';
+}
+
+class PhoneNumberDummy
+{
+ #[PhoneNumber(type: [PhoneNumber::MOBILE], defaultRegion: 'FR')]
+ /* @phpstan-ignore-next-line */
+ private PhoneNumber $phoneNumber1;
+
+ #[PhoneNumber(regionPath: 'regionPath')]
+ /* @phpstan-ignore-next-line */
+ private PhoneNumber $phoneNumber2;
+
+ public string $regionPath = 'GB';
+}
diff --git a/tools/php-cs-fixer/composer.json b/tools/php-cs-fixer/composer.json
new file mode 100644
index 00000000..c6437213
--- /dev/null
+++ b/tools/php-cs-fixer/composer.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "friendsofphp/php-cs-fixer": "^3.40"
+ }
+}
diff --git a/tools/phpstan/composer.json b/tools/phpstan/composer.json
new file mode 100644
index 00000000..ba6a7e87
--- /dev/null
+++ b/tools/phpstan/composer.json
@@ -0,0 +1,6 @@
+{
+ "require": {
+ "phpstan/phpstan": "^1.10.46",
+ "jangregor/phpstan-prophecy": "^1.0"
+ }
+}