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