From b57b64a539ab3bea62d39b16cdabf6e8b9dd2ea8 Mon Sep 17 00:00:00 2001 From: Benjamin Morel Date: Thu, 5 Aug 2021 13:59:25 +0200 Subject: [PATCH] First version --- .gitattributes | 7 ++ .github/FUNDING.yml | 1 + .github/workflows/ci.yml | 74 +++++++++++++++++++ .gitignore | 3 + LICENSE | 20 +++++ README.md | 46 ++++++++++++ composer.json | 34 +++++++++ phpunit-bootstrap.php | 10 +++ phpunit.xml | 13 ++++ psalm.xml | 15 ++++ src/Types/PhoneNumberType.php | 59 +++++++++++++++ tests/AbstractFunctionalTest.php | 55 ++++++++++++++ tests/Entity/User.php | 30 ++++++++ tests/Types/PhoneNumberTypeTest.php | 109 ++++++++++++++++++++++++++++ tests/TypesFunctionalTest.php | 100 +++++++++++++++++++++++++ 15 files changed, 576 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit-bootstrap.php create mode 100644 phpunit.xml create mode 100644 psalm.xml create mode 100644 src/Types/PhoneNumberType.php create mode 100644 tests/AbstractFunctionalTest.php create mode 100644 tests/Entity/User.php create mode 100644 tests/Types/PhoneNumberTypeTest.php create mode 100644 tests/TypesFunctionalTest.php diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f7f1622 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +.gitattributes export-ignore +.gitignore export-ignore +.github/ export-ignore +phpunit.xml export-ignore +phpunit-bootstrap.php export-ignore +psalm.xml export-ignore +tests/ export-ignore diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..689b999 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: BenMorel diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..52b3b15 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,74 @@ +name: CI + +on: + push: + pull_request: + +jobs: + psalm: + name: Psalm + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "8.0" + + - name: Install composer dependencies + uses: "ramsey/composer-install@v1" + + - name: Run Psalm + run: vendor/bin/psalm --show-info=false --find-unused-psalm-suppress --no-progress + + phpunit: + name: PHPUnit + runs-on: ubuntu-latest + + strategy: + matrix: + php-version: + - "7.1" + - "7.2" + - "7.3" + - "7.4" + - "8.0" + deps: + - "highest" + include: + - php-version: "7.1" + deps: "lowest" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + coverage: xdebug + + - name: Install composer dependencies + uses: "ramsey/composer-install@v1" + with: + dependency-versions: ${{ matrix.deps }} + + - name: Run PHPUnit + run: vendor/bin/phpunit + if: ${{ matrix.php-version != '8.0' }} + + - name: Run PHPUnit with coverage + run: | + mkdir -p mkdir -p build/logs + vendor/bin/phpunit --coverage-clover build/logs/clover.xml + if: ${{ matrix.php-version == '8.0' }} + + - name: Upload coverage report to Coveralls + run: vendor/bin/php-coveralls --coverage_clover=build/logs/clover.xml -v + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + if: ${{ matrix.php-version == '8.0' }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4901aab --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/vendor +/composer.lock +/.phpunit.result.cache diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b3c4e7d --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2021-present Benjamin Morel + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4587ea4 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +brick/phonenumber-doctrine +======================== + + + +Doctrine type mappings for [brick/phonenumber](https://github.com/brick/phonenumber). + +[![Build Status](https://github.com/brick/phonenumber-doctrine/workflows/CI/badge.svg)](https://github.com/brick/phonenumber-doctrine/actions) +[![Coverage Status](https://coveralls.io/repos/github/brick/phonenumber-doctrine/badge.svg?branch=master)](https://coveralls.io/github/brick/phonenumber-doctrine?branch=master) +[![Latest Stable Version](https://poser.pugx.org/brick/phonenumber-doctrine/v/stable)](https://packagist.org/packages/brick/phonenumber-doctrine) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT) + +Introduction +------------ + +This library provides a type mapping to use `PhoneNumber` objects as Doctrine entity properties. + +Installation +------------ + +This library is installable via [Composer](https://getcomposer.org/): + +```bash +composer require brick/phonenumber-doctrine +``` + +Requirements +------------ + +This library requires PHP 7.1 or later. + +Project status & release process +-------------------------------- + +The current releases are numbered `0.x.y`. When a non-breaking change is introduced (adding new methods, optimizing existing code, etc.), `y` is incremented. + +**When a breaking change is introduced, a new `0.x` version cycle is always started.** + +It is therefore safe to lock your project to a given release cycle, such as `0.1.*`. + +If you need to upgrade to a newer release cycle, check the [release history](https://github.com/brick/phonenumber-doctrine/releases) for a list of changes introduced by each further `0.x.0` version. + +Package contents +---------------- + +- [PhoneNumberType](https://github.com/brick/phonenumber-doctrine/blob/master/src/Types/PhoneNumberType.php) diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..7e2b44f --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "brick/phonenumber-doctrine", + "description": "Doctrine type mappings for brick/phonenumber", + "type": "library", + "keywords": [ + "Brick", + "PhoneNumber", + "Doctrine" + ], + "license": "MIT", + "require": { + "brick/phonenumber": "~0.2.0 || ~0.3.0", + "doctrine/dbal": "^2.7.0", + "doctrine/orm": "^2.6.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-pdo": "*", + "ext-pdo_sqlite": "*", + "phpunit/phpunit": "^7.5.15 || ^8.0 || ^9.0", + "php-coveralls/php-coveralls": "^2.4", + "vimeo/psalm": "^4.9" + }, + "autoload": { + "psr-4": { + "Brick\\PhoneNumber\\Doctrine\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "Brick\\PhoneNumber\\Doctrine\\Tests\\": "tests/" + } + } +} diff --git a/phpunit-bootstrap.php b/phpunit-bootstrap.php new file mode 100644 index 0000000..a7fcaba --- /dev/null +++ b/phpunit-bootstrap.php @@ -0,0 +1,10 @@ + + + + + tests + + + + + src + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..3240886 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/src/Types/PhoneNumberType.php b/src/Types/PhoneNumberType.php new file mode 100644 index 0000000..1db3e2a --- /dev/null +++ b/src/Types/PhoneNumberType.php @@ -0,0 +1,59 @@ +getName(), [PhoneNumber::class, 'null']); + } + + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + if (is_string($value)) { + return PhoneNumber::parse($value); + } + + throw ConversionException::conversionFailedInvalidType($value, $this->getName(), ['string', 'null']); + } + + public function getSQLDeclaration(array $column, AbstractPlatform $platform) + { + // E.164 defines the maximum length as 15 digits, to which we add 1 char for the leading + sign. + if (!isset($column['length'])) { + $column['length'] = 16; + } + + return $platform->getVarcharTypeDeclarationSQL($column); + } + + public function requiresSQLCommentHint(AbstractPlatform $platform) + { + return true; + } +} diff --git a/tests/AbstractFunctionalTest.php b/tests/AbstractFunctionalTest.php new file mode 100644 index 0000000..9ad5689 --- /dev/null +++ b/tests/AbstractFunctionalTest.php @@ -0,0 +1,55 @@ + 'sqlite:///:memory:']); + } + + final protected static function createEntityManager(Connection $connection): EntityManager + { + return EntityManager::create($connection, self::createConfiguration()); + } + + private static function createConfiguration(): Configuration + { + $config = new Configuration(); + + $driverImpl = $config->newDefaultAnnotationDriver([__DIR__ . '/tests/Entity'], false); + $config->setMetadataDriverImpl($driverImpl); + + $config->setProxyDir(sys_get_temp_dir()); + $config->setProxyNamespace('Brick\PhoneNumber\Doctrine\Tests\Proxy'); + + return $config; + } + + final protected static function truncateEntityTable(EntityManager $em): void + { + $em->createQueryBuilder() + ->delete(User::class, 's') + ->getQuery() + ->execute(); + } + + final protected function getFirstEntity(EntityManager $em): ?User + { + return $em->createQueryBuilder() + ->select('s') + ->from(User::class, 's') + ->getQuery() + ->getOneOrNullResult(); + } +} diff --git a/tests/Entity/User.php b/tests/Entity/User.php new file mode 100644 index 0000000..514dfec --- /dev/null +++ b/tests/Entity/User.php @@ -0,0 +1,30 @@ +getPhoneNumberType(); + $actualValue = $type->convertToDatabaseValue($value, new SqlitePlatform()); + + self::assertSame($expectedValue, $actualValue); + } + + public function providerConvertToDatabaseValue(): array + { + return [ + [null, null], + [PhoneNumber::parse('+331234567890'), '+331234567890'], + [PhoneNumber::parse('+447553848951'), '+447553848951'], + ]; + } + + /** + * @dataProvider providerConvertToDatabaseValueWithInvalidValue + */ + public function testConvertToDatabaseValueWithInvalidValue($value): void + { + $type = $this->getPhoneNumberType(); + + $this->expectException(ConversionException::class); + $type->convertToDatabaseValue($value, new SqlitePlatform()); + } + + public function providerConvertToDatabaseValueWithInvalidValue(): array + { + return [ + [123], + [false], + [true], + ['string'], + [new stdClass()], + ]; + } + + /** + * @dataProvider providerConvertToPHPValue + */ + public function testConvertToPHPValue(?string $value): void + { + $type = $this->getPhoneNumberType(); + $convertedValue = $type->convertToPHPValue($value, new SqlitePlatform()); + + if ($value === null) { + self::assertNull($convertedValue); + } else { + self::assertInstanceOf(PhoneNumber::class, $convertedValue); + self::assertSame($value, (string) $convertedValue); + } + } + + public function providerConvertToPHPValue(): array + { + return [ + [null], + ['+331234567890'], + ['+447553848951'], + ]; + } + + /** + * @dataProvider providerConvertToPHPValueWithInvalidValue + */ + public function testConvertToPHPValueWithInvalidValue($value, string $expectedExceptionClass): void + { + $type = $this->getPhoneNumberType(); + + $this->expectException($expectedExceptionClass); + $type->convertToPHPValue($value, new SqlitePlatform()); + } + + public function providerConvertToPHPValueWithInvalidValue(): array + { + return [ + [123, ConversionException::class], + ['', PhoneNumberParseException::class], + ['+33', PhoneNumberParseException::class], + ]; + } +} diff --git a/tests/TypesFunctionalTest.php b/tests/TypesFunctionalTest.php new file mode 100644 index 0000000..d51c119 --- /dev/null +++ b/tests/TypesFunctionalTest.php @@ -0,0 +1,100 @@ +getClassMetadata(User::class); + + $sql = $schemaTool->getUpdateSchemaSql([$classMetadata]); + self::assertCount(1, $sql); + $sql = $sql[0]; + + self::assertStringContainsString('phoneNumber VARCHAR(16) DEFAULT NULL --(DC2Type:PhoneNumber)', $sql); + + $connection->exec($sql); + + return $connection; + } + + /** + * @depends testCreateSchema + */ + public function testSaveNull(Connection $connection): Connection + { + $em = self::createEntityManager($connection); + self::truncateEntityTable($em); + + $entity = new User(); + + $em->persist($entity); + $em->flush(); + + // https://github.com/sebastianbergmann/phpunit/issues/3016 + self::assertTrue(true); + + return $connection; + } + + /** + * @depends testSaveNull + */ + public function testLoadNull(Connection $connection): void + { + $em = self::createEntityManager($connection); + + $entity = self::getFirstEntity($em); + + self::assertNotNull($entity); + self::assertNull($entity->phoneNumber); + } + + /** + * @depends testCreateSchema + */ + public function testSaveValues(Connection $connection): Connection + { + $em = self::createEntityManager($connection); + self::truncateEntityTable($em); + + $entity = new User(); + + $entity->phoneNumber = PhoneNumber::parse('+331234567890'); + + $em->persist($entity); + $em->flush(); + + // https://github.com/sebastianbergmann/phpunit/issues/3016 + self::assertTrue(true); + + return $connection; + } + + /** + * @depends testSaveValues + */ + public function testLoadValues(Connection $connection): void + { + $em = self::createEntityManager($connection); + + $entity = self::getFirstEntity($em); + + self::assertNotNull($entity); + + self::assertInstanceOf(PhoneNumber::class, $entity->phoneNumber); + self::assertSame('+331234567890', (string) $entity->phoneNumber); + } +}