diff --git a/README.md b/README.md index 3c6a74a..2c37fea 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,22 @@ The default character encoding in Access databases is [Windows-1252](https://en. If you want to convert the data to UTF-8, a simple solution would be: ```php -$field = mb_convert_encoding($field, 'UTF-8', 'Windows-1252'); +$field = \mb_convert_encoding($field, 'UTF-8', 'Windows-1252'); +``` + +If you want all the data to be encoded automatically to UTF-8 (with the performance reduction that it may imply) +configure the driver as follows: + +```php +$connection = \Doctrine\DBAL\DriverManager::getConnection( + [ + 'driverClass' => \ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess\Driver::class, + 'dsn' => 'name of the created dsn', + 'driverOptions' => [ + 'charset' => 'UTF-8', + ], + ] +); ``` ## License diff --git a/composer.json b/composer.json index 311e95b..208c4dd 100644 --- a/composer.json +++ b/composer.json @@ -5,6 +5,7 @@ "license": "MIT", "require": { "php": "^7.4", + "ext-mbstring": "*", "ext-pdo": "*", "ext-odbc": "*", "doctrine/dbal": "^2.12" diff --git a/src/Doctrine/DBAL/Driver/MicrosoftAccess/PDO/Connection.php b/src/Doctrine/DBAL/Driver/MicrosoftAccess/PDO/Connection.php index 9953291..0afcb75 100644 --- a/src/Doctrine/DBAL/Driver/MicrosoftAccess/PDO/Connection.php +++ b/src/Doctrine/DBAL/Driver/MicrosoftAccess/PDO/Connection.php @@ -4,7 +4,7 @@ namespace ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess\PDO; use Doctrine\DBAL\Driver\PDO\Connection as PDOConnection; -use Doctrine\DBAL\Driver\PDO\SQLSrv\Statement; +use ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess\Statement; final class Connection extends PDOConnection { @@ -18,7 +18,11 @@ public function __construct($dsn, $user = null, $password = null, $options = nul \PDO::ATTR_STATEMENT_CLASS, [ Statement::class, - [], + [ + true === \array_key_exists('charset', $options) + ? $options['charset'] + : null + ], ], ); } diff --git a/src/Doctrine/DBAL/Driver/MicrosoftAccess/Statement.php b/src/Doctrine/DBAL/Driver/MicrosoftAccess/Statement.php new file mode 100644 index 0000000..094a38c --- /dev/null +++ b/src/Doctrine/DBAL/Driver/MicrosoftAccess/Statement.php @@ -0,0 +1,120 @@ +charset = $charset; + } + + public function bindParam($param, &$variable, $type = ParameterType::STRING, $length = null, $driverOptions = null) + { + switch ($type) { + case ParameterType::LARGE_OBJECT: + case ParameterType::BINARY: + if ($driverOptions === null) { + $driverOptions = \PDO::SQLSRV_ENCODING_BINARY; + } + + break; + + case ParameterType::ASCII: + $type = ParameterType::STRING; + $length = 0; + $driverOptions = \PDO::SQLSRV_ENCODING_SYSTEM; + break; + } + + return parent::bindParam($param, $variable, $type, $length, $driverOptions); + } + + public function bindValue($param, $value, $type = ParameterType::STRING) + { + return $this->bindParam($param, $value, $type); + } + + public function fetchOne() + { + return $this->convertStringEncoding( + parent::fetchOne() + ); + } + + public function fetchNumeric() + { + return $this->convertArrayEncoding( + parent::fetchNumeric() + ); + } + + public function fetchAssociative() + { + return $this->convertArrayEncoding( + parent::fetchAssociative() + ); + } + + public function fetchAllNumeric(): array + { + return $this->convertCollectionEncoding( + parent::fetchAllNumeric() + ); + } + + public function fetchFirstColumn(): array + { + return $this->convertArrayEncoding( + parent::fetchFirstColumn() + ); + } + + public function fetchAllAssociative(): array + { + return $this->convertCollectionEncoding( + parent::fetchAllAssociative() + ); + } + + private function convertCollectionEncoding(array $items): array + { + \array_walk( + $items, + function (&$item) { + $item = $this->convertArrayEncoding($item); + } + ); + + return $items; + } + + private function convertArrayEncoding(array $items): array + { + foreach ($items as $key => $value) { + $items[$key] = $this->convertStringEncoding($items[$key]); + } + + return $items; + } + + private function convertStringEncoding(?string $value): ?string + { + if (null === $this->charset) { + return $value; + } + + if (null === $value) { + return null; + } + + return \mb_convert_encoding($value, $this->charset, self::FROM_ENCODING); + } +} diff --git a/tests/BaseTest.php b/tests/BaseTest.php index c91a9d0..da2a93f 100644 --- a/tests/BaseTest.php +++ b/tests/BaseTest.php @@ -9,6 +9,21 @@ abstract class BaseTest extends TestCase { + protected function filename(): string + { + return 'default.mdb'; + } + + protected function dsn(): string + { + return 'dbal-msaccess'; + } + + protected function driverOptions(): array + { + return []; + } + public function connections(): \Iterator { if (false === $this->isMicrosoftWindows()) { @@ -19,7 +34,8 @@ public function connections(): \Iterator $params = [ 'driverClass' => Driver::class, - 'dsn' => 'dbal-msaccess', + 'dsn' => $this->dsn(), + 'driverOptions' => $this->driverOptions(), ]; $connection = DriverManager::getConnection($params); @@ -28,7 +44,11 @@ public function connections(): \Iterator $connection->connect(); } catch (\Doctrine\DBAL\Exception $exception) { throw new \Exception( - "You should create a DSN pointing to 'var\database.mdb' called 'dbal-msaccess'", + \sprintf( + "You should create a DSN pointing to 'var\%s' called '%s'", + $this->filename(), + $this->dsn() + ), ); } @@ -45,8 +65,8 @@ private function createDatabase(): string $ds = \DIRECTORY_SEPARATOR; $root = __DIR__ . $ds . '..' . $ds; - $source = $root . 'tests' . $ds . 'database.mdb'; - $dest = $root . 'var' . $ds . 'database.mdb'; + $source = $root . 'tests' . $ds . 'Databases' . $ds . $this->filename(); + $dest = $root . 'var' . $ds . $this->filename(); \copy($source, $dest); diff --git a/tests/Databases/Iurie-popovt-test.accdb b/tests/Databases/Iurie-popovt-test.accdb new file mode 100644 index 0000000..460f00a Binary files /dev/null and b/tests/Databases/Iurie-popovt-test.accdb differ diff --git a/tests/database.mdb b/tests/Databases/default.mdb similarity index 72% rename from tests/database.mdb rename to tests/Databases/default.mdb index 01e92e8..ff43ee0 100644 Binary files a/tests/database.mdb and b/tests/Databases/default.mdb differ diff --git a/tests/Doctrine/DBAL/Driver/MicrosoftAccess/StatementTest.php b/tests/Doctrine/DBAL/Driver/MicrosoftAccess/StatementTest.php new file mode 100644 index 0000000..66a4242 --- /dev/null +++ b/tests/Doctrine/DBAL/Driver/MicrosoftAccess/StatementTest.php @@ -0,0 +1,46 @@ + 'UTF-8', + ]; + } + + /** + * @test + * @dataProvider connections + */ + public function given_a_french_database_when_has_driver_options_then_it_appears_in_utf8(Connection $connection) + { + $result = $connection + ->createQueryBuilder() + ->select('note') + ->from(self::TABLE_NAME) + ->execute(); + + $item = $result->fetchAllAssociative()[0]; + + $this->assertSame('Matériel Prêt un mail est envoyé', $item['note']); + } +}