Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
zoilomora committed Nov 20, 2020
0 parents commit 72ffb71
Show file tree
Hide file tree
Showing 19 changed files with 1,031 additions and 0 deletions.
28 changes: 28 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# phpstorm project files
.idea

# netbeans project files
nbproject/*

# zend studio for eclipse project files
.buildpath
.project
.settings

# windows thumbnail cache
Thumbs.db

# Mac DS_Store Files
.DS_Store

# Composer
/vendor/
composer.lock

# PHP_CodeSniffer
phpcs.xml

# PHPUnit
/report/
phpunit.xml
.phpunit.result.cache
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 Zoilo Mora

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.
46 changes: 46 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Doctrine DBAL for Microsoft Access
An implementation of the [doctrine/dbal](https://github.com/doctrine/dbal) library to support **Microsoft Access databases** in **Microsoft OS**.

There are some functionalities that are not supported by Microsoft Access in a PDO-based connection. For these functionalities the implementation uses a direct connection through ODBC.

## OS Requirements
- Microsoft Access Database Engine Redistributable ([2010](https://www.microsoft.com/download/details.aspx?id=13255) or [2016](https://www.microsoft.com/download/details.aspx?id=54920)).
- Register a **DSN** in **ODBC Data Source Administrator** (odbcad32.exe).

## Installation

1) Install via [composer](https://getcomposer.org/)

```shell script
composer require zoilomora/doctrine-dbal-msaccess
```

### Register a **DSN**
We don't need to reinvent the wheel, on the internet there are hundreds of tutorials on how to set up a DSN for Microsoft Access.
I leave you a [video](https://www.youtube.com/watch?v=biSjA8ms_Wk) that I think explains it perfectly.
Once the DSN is configured we will have to configure the connection in the following way:
```php
$connection = \Doctrine\DBAL\DriverManager::getConnection(
[
'driverClass' => \ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess\Driver::class,
'dsn' => 'name of the created dsn',
]
);
```
## Discovered problems
### Character encoding problems
The default character encoding in Access databases is [Windows-1252](https://en.wikipedia.org/wiki/Windows-1252).
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');
```
## License
Licensed under the [MIT license](http://opensource.org/licenses/MIT)
Read [LICENSE](LICENSE) for more information
27 changes: 27 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "zoilomora/doctrine-dbal-msaccess",
"description": "Doctrine DBAL implementation for Microsoft Access",
"type": "library",
"license": "MIT",
"require": {
"php": "^7.4",
"ext-pdo": "*",
"ext-odbc": "*",
"doctrine/dbal": "^2.12"
},
"autoload": {
"psr-4": {
"ZoiloMora\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"ZoiloMora\\Tests\\": "tests/"
}
},
"require-dev": {
"pccomponentes/coding-standard": "^1.2",
"phpunit/phpunit": "^9.4",
"phpstan/phpstan": "^0.12.56"
}
}
14 changes: 14 additions & 0 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" ?>
<ruleset name="Project rules">
<file>src</file>
<file>tests</file>
<rule ref="vendor/pccomponentes/coding-standard/src/ruleset.xml">
<exclude name="PSR2.Methods.MethodDeclaration.Underscore" />
<exclude name="SlevomatCodingStandard.PHP.DisallowReference.DisallowedPassingByReference" />
<exclude name="SlevomatCodingStandard.ControlStructures.BlockControlStructureSpacing.IncorrectLinesCountAfterControlStructure" />
<exclude name="SlevomatCodingStandard.ControlStructures.BlockControlStructureSpacing.IncorrectLinesCountBeforeControlStructure" />
</rule>
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
<exclude-pattern>tests/*</exclude-pattern>
</rule>
</ruleset>
6 changes: 6 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parameters:
level: 5
paths:
- src
ignoreErrors:
- '#qualifier of function odbc_columns expects string#'
17 changes: 17 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" backupGlobals="false" stopOnDefect="false" colors="true">
<coverage>
<include>
<directory>./src</directory>
</include>
<report>
<html outputDirectory="./report"/>
</report>
</coverage>
<testsuites>
<testsuite name="Unit Tests">
<directory suffix="Test.php">./tests/</directory>
</testsuite>
</testsuites>
<logging/>
</phpunit>
91 changes: 91 additions & 0 deletions src/Doctrine/DBAL/Driver/MicrosoftAccess/Driver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);

namespace ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess;

use Doctrine\DBAL\Exception;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\DBAL\Schema\AbstractSchemaManager;
use ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess\PDO\Connection as PDOConnection;
use ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess\ODBC\Connection as ODBCConnection;
use ZoiloMora\Doctrine\DBAL\Platforms\MicrosoftAccessPlatform;
use ZoiloMora\Doctrine\DBAL\Schema\MicrosoftAccessSchemaManager;

final class Driver implements \Doctrine\DBAL\Driver
{
protected ?ODBCConnection $odbcConnection = null;

public function connect(
array $params,
$username = null,
$password = null,
array $driverOptions = []
): \Doctrine\DBAL\Driver\Connection {
$this->assertRequiredParameters($params);

try {
$conn = new PDOConnection(
$this->constructPdoDsn($params),
$username,
$password,
$driverOptions,
);
$this->odbcConnection = new ODBCConnection(
$this->constructOdbcDsn($params),
);
} catch (\PDOException $e) {
throw Exception::driverException($this, $e);
}

return $conn;
}

public function getName(): string
{
return 'pdo_msaccess';
}

public function getDatabase(\Doctrine\DBAL\Connection $conn): string
{
return 'unknown';
}

public function getDatabasePlatform(): AbstractPlatform
{
return new MicrosoftAccessPlatform();
}

public function getSchemaManager(\Doctrine\DBAL\Connection $conn): AbstractSchemaManager
{
return new MicrosoftAccessSchemaManager($conn, $this->odbcConnection);
}

private function assertRequiredParameters(array $params): void
{
if (false === \array_key_exists('dsn', $params)) {
throw new \Exception("The parameter 'dsn' is mandatory");
}
}

protected function constructPdoDsn(array $params): string
{
$dsn = 'odbc:';

if (isset($params['dsn']) && '' !== $params['dsn']) {
return $dsn . $params['dsn'];
}

return $dsn;
}

protected function constructOdbcDsn(array $params): string
{
$dsn = '';

if (isset($params['dsn']) && '' !== $params['dsn']) {
return $dsn . $params['dsn'];
}

return $dsn;
}
}
68 changes: 68 additions & 0 deletions src/Doctrine/DBAL/Driver/MicrosoftAccess/ODBC/Connection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);

namespace ZoiloMora\Doctrine\DBAL\Driver\MicrosoftAccess\ODBC;

final class Connection
{
private $connection;

public function __construct(string $dsn, string $user = '', string $password = '')
{
$this->connection = \odbc_connect($dsn, $user, $password);

if (false === $this->connection) {
throw new \Exception('Connection failure!');
}
}

public function listTableNames(): array
{
$tables = \odbc_tables($this->connection);

$list = [];

while ($table = \odbc_fetch_array($tables)) {
$list[] = [
'Name' => $table['TABLE_NAME'],
'Type' => $table['TABLE_TYPE'],
];
}

return $list;
}

public function listTableColumns(string $tableName): array
{
$columns = \odbc_columns($this->connection, null, '', $tableName);

$list = [];

while ($column = \odbc_fetch_array($columns)) {
$list[] = [
'name' => $column['COLUMN_NAME'],
'comment' => $this->sanitizeComment($column['REMARKS']),
'type' => \strtolower($column['TYPE_NAME']),
'length' => (int) $column['COLUMN_SIZE'],
'notnull' => '1' !== $column['NULLABLE'],
'default' => null,
'scale' => (int) $column['COLUMN_SIZE'],
'precision' => (int) $column['COLUMN_SIZE'],
'autoincrement' => 'COUNTER' === $column['TYPE_NAME'],
];
}

return $list;
}

private function sanitizeComment(?string $comment): ?string
{
if (null === $comment) {
return null;
}

$comment = \utf8_encode($comment);

return \substr($comment, 0, \strpos($comment, "\x00"));
}
}
Loading

0 comments on commit 72ffb71

Please sign in to comment.