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 -[![Build Status](https://img.shields.io/travis/misd-service-development/phone-number-bundle.svg?style=flat-square)](https://travis-ci.org/misd-service-development/phone-number-bundle) -[![Total Downloads](https://img.shields.io/packagist/dt/misd/phone-number-bundle.svg?style=flat-square)](https://packagist.org/packages/misd/phone-number-bundle) -[![Downloads per month](https://img.shields.io/packagist/dm/misd/phone-number-bundle.svg?style=flat-square)](https://packagist.org/packages/misd/phone-number-bundle) -[![Latest stable version](https://img.shields.io/packagist/v/misd/phone-number-bundle.svg?style=flat-square)](https://packagist.org/packages/misd/phone-number-bundle) -[![License](http://img.shields.io/packagist/l/misd/phone-number-bundle.svg?style=flat-square)](https://packagist.org/packages/misd/phone-number-bundle) +[![Build Status](https://github.com/odolbeau/phone-number-bundle/actions/workflows/tests.yml/badge.svg)](https://github.com/odolbeau/phone-number-bundle/actions) +[![Latest stable version](https://img.shields.io/packagist/v/odolbeau/phone-number-bundle.svg?style=flat-square)](https://packagist.org/packages/odolbeau/phone-number-bundle) +[![License](http://img.shields.io/packagist/l/odolbeau/phone-number-bundle.svg?style=flat-square)](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. +[![SWUbanner](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](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" + } +}