瀏覽代碼

add MariaDB/MySQL driver support

phpdevcommunity 1 月之前
父節點
當前提交
cd6a0b833f
共有 41 個文件被更改,包括 1760 次插入443 次删除
  1. 5 0
      .env.test.dist
  2. 1 0
      .gitignore
  3. 22 2
      README.md
  4. 3 2
      src/Command/DatabaseCreateCommand.php
  5. 3 2
      src/Command/DatabaseDropCommand.php
  6. 3 2
      src/Command/QueryExecuteCommand.php
  7. 3 2
      src/Command/ShowTablesCommand.php
  8. 3 0
      src/Driver/DriverManager.php
  9. 76 0
      src/Driver/MariaDBDriver.php
  10. 1 1
      src/EntityManager.php
  11. 25 0
      src/EntityManagerInterface.php
  12. 53 10
      src/Generator/SchemaDiffGenerator.php
  13. 0 1
      src/Mapping/Column/IntColumn.php
  14. 29 2
      src/Mapping/Column/JoinColumn.php
  15. 68 25
      src/Metadata/ColumnMetadata.php
  16. 56 0
      src/Metadata/DatabaseSchemaDiffMetadata.php
  17. 82 0
      src/Metadata/ForeignKeyMetadata.php
  18. 6 2
      src/Michel/Package/MichelPaperORMPackage.php
  19. 7 6
      src/Migration/PaperMigration.php
  20. 10 2
      src/PaperConnection.php
  21. 106 22
      src/Platform/AbstractPlatform.php
  22. 305 0
      src/Platform/MariaDBPlatform.php
  23. 5 0
      src/Platform/PlatformInterface.php
  24. 97 21
      src/Platform/SqlitePlatform.php
  25. 3 2
      src/Query/QueryBuilder.php
  26. 3 3
      src/Repository/Repository.php
  27. 283 0
      src/Schema/MariaDBSchema.php
  28. 15 2
      src/Schema/SchemaInterface.php
  29. 65 17
      src/Schema/SqliteSchema.php
  30. 16 26
      tests/Common/OrmTestMemory.php
  31. 18 11
      tests/DatabaseShowTablesCommandTest.php
  32. 1 1
      tests/Entity/CommentTest.php
  33. 2 2
      tests/Entity/PostTest.php
  34. 2 2
      tests/Entity/TagTest.php
  35. 2 2
      tests/Entity/UserTest.php
  36. 125 81
      tests/Helper/DataBaseHelperTest.php
  37. 121 49
      tests/MigrationTest.php
  38. 37 59
      tests/PersistAndFlushTest.php
  39. 16 9
      tests/PlatformDiffTest.php
  40. 53 39
      tests/PlatformTest.php
  41. 29 36
      tests/RepositoryTest.php

+ 5 - 0
.env.test.dist

@@ -0,0 +1,5 @@
+MARIADB_HOST=
+MARIADB_POST=
+MARIADB_DB=
+MARIADB_USER=
+MARIADB_PASSWORD=

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 /vendor/
 /.idea/
 composer.lock
+.env.test

+ 22 - 2
README.md

@@ -33,13 +33,23 @@ require_once 'vendor/autoload.php';
 
 use PhpDevCommunity\PaperORM\EntityManager;
 
-// Basic configuration (MySQL, SQLite)  
+// Basic configuration SQLite
 $entityManager = new EntityManager([
             'driver' => 'sqlite',
             'user' => null,
             'password' => null,
             'memory' => true,
 ]);
+// Basic configuration MySQL/Mariadb
+$entityManager = new EntityManager([
+            'driver' => 'mariadb',
+            'host' => '127.0.0.1',
+            'port' => 3306,
+            'dbname' => 'paper_orm_test',
+            'user' => 'root',
+            'password' => 'root',
+            'charset' => 'utf8mb4',
+]);
 ```
 
 ✅ **PaperORM is now ready to use!**
@@ -247,13 +257,23 @@ require_once 'vendor/autoload.php';
 
 use PhpDevCommunity\PaperORM\EntityManager;
 
-// Configuration de base (MySQL, SQLite)  
+// Basic configuration SQLite
 $entityManager = new EntityManager([
             'driver' => 'sqlite',
             'user' => null,
             'password' => null,
             'memory' => true,
 ]);
+// Basic configuration MySQL/Mariadb
+$entityManager = new EntityManager([
+            'driver' => 'mariadb',
+            'host' => '127.0.0.1',
+            'port' => 3306,
+            'dbname' => 'paper_orm_test',
+            'user' => 'root',
+            'password' => 'root',
+            'charset' => 'utf8mb4',
+]);
 ```
 
 ✅ **PaperORM est maintenant prêt à être utilisé !**  

+ 3 - 2
src/Command/DatabaseCreateCommand.php

@@ -8,12 +8,13 @@ use PhpDevCommunity\Console\Option\CommandOption;
 use PhpDevCommunity\Console\Output\ConsoleOutput;
 use PhpDevCommunity\Console\OutputInterface;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 
 class DatabaseCreateCommand implements CommandInterface
 {
-    private EntityManager $entityManager;
+    private EntityManagerInterface $entityManager;
 
-    public function __construct(EntityManager $entityManager)
+    public function __construct(EntityManagerInterface $entityManager)
     {
         $this->entityManager = $entityManager;
     }

+ 3 - 2
src/Command/DatabaseDropCommand.php

@@ -9,14 +9,15 @@ use PhpDevCommunity\Console\Option\CommandOption;
 use PhpDevCommunity\Console\Output\ConsoleOutput;
 use PhpDevCommunity\Console\OutputInterface;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 
 class DatabaseDropCommand implements CommandInterface
 {
-    private EntityManager $entityManager;
+    private EntityManagerInterface $entityManager;
 
     private ?string $env;
 
-    public function __construct(EntityManager $entityManager, ?string $env = null)
+    public function __construct(EntityManagerInterface $entityManager, ?string $env = null)
     {
         $this->entityManager = $entityManager;
         $this->env = $env;

+ 3 - 2
src/Command/QueryExecuteCommand.php

@@ -8,11 +8,12 @@ use PhpDevCommunity\Console\InputInterface;
 use PhpDevCommunity\Console\Output\ConsoleOutput;
 use PhpDevCommunity\Console\OutputInterface;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 
 class QueryExecuteCommand implements CommandInterface
 {
-    private EntityManager $entityManager;
-    public function __construct(EntityManager $entityManager)
+    private EntityManagerInterface $entityManager;
+    public function __construct(EntityManagerInterface $entityManager)
     {
         $this->entityManager = $entityManager;
     }

+ 3 - 2
src/Command/ShowTablesCommand.php

@@ -9,12 +9,13 @@ use PhpDevCommunity\Console\Option\CommandOption;
 use PhpDevCommunity\Console\Output\ConsoleOutput;
 use PhpDevCommunity\Console\OutputInterface;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
 
 class ShowTablesCommand implements CommandInterface
 {
-    private EntityManager $entityManager;
-    public function __construct(EntityManager $entityManager)
+    private EntityManagerInterface $entityManager;
+    public function __construct(EntityManagerInterface $entityManager)
     {
         $this->entityManager = $entityManager;
     }

+ 3 - 0
src/Driver/DriverManager.php

@@ -4,12 +4,15 @@ namespace PhpDevCommunity\PaperORM\Driver;
 
 use Exception;
 use PhpDevCommunity\PaperORM\PaperConnection;
+use PhpDevCommunity\PaperORM\Platform\MariaDBPlatform;
 
 class DriverManager
 {
     private static array $driverSchemeAliases = [
         'sqlite' => SqliteDriver::class,
         'sqlite3' => SqliteDriver::class,
+        'mysql' => MariaDBDriver::class,
+        'mariadb' => MariaDBDriver::class
     ];
 
     public static function getConnection(string $driver, array $params): PaperConnection

+ 76 - 0
src/Driver/MariaDBDriver.php

@@ -0,0 +1,76 @@
+<?php
+
+namespace PhpDevCommunity\PaperORM\Driver;
+
+use PDO;
+use PhpDevCommunity\PaperORM\PaperConnection;
+use PhpDevCommunity\PaperORM\Pdo\PaperPDO;
+use PhpDevCommunity\PaperORM\Platform\MariaDBPlatform;
+use PhpDevCommunity\PaperORM\Platform\PlatformInterface;
+use PhpDevCommunity\PaperORM\Platform\SqlitePlatform;
+use PhpDevCommunity\PaperORM\Schema\MariaDBSchema;
+use PhpDevCommunity\PaperORM\Schema\SqliteSchema;
+
+final class MariaDBDriver implements DriverInterface
+{
+    public function connect(
+        #[SensitiveParameter]
+        array $params
+    ): PaperPDO
+    {
+        $driverOptions = $params['driverOptions'] ?? [
+            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
+        ];
+        if (!empty($params['persistent'])) {
+            $driverOptions[PDO::ATTR_PERSISTENT] = true;
+        }
+
+        return new PaperPDO(
+            $this->constructPdoDsn($params),
+            $params['user'] ?? '',
+            $params['password'] ?? '',
+            $driverOptions,
+        );
+    }
+
+    /**
+     * Constructs the Sqlite PDO DSN.
+     *
+     * @param array<string, mixed> $params
+     */
+    private function constructPdoDsn(array $params): string
+    {
+        $dsn = 'mysql:';
+        if (isset($params['host']) && $params['host'] !== '') {
+            $dsn .= 'host=' . $params['host'] . ';';
+        }
+
+        if (isset($params['port'])) {
+            $dsn .= 'port=' . $params['port'] . ';';
+        }
+
+        if (isset($params['dbname'])) {
+            $dsn .= 'dbname=' . $params['dbname'] . ';';
+        }
+
+        if (isset($params['unix_socket'])) {
+            $dsn .= 'unix_socket=' . $params['unix_socket'] . ';';
+        }
+
+        if (isset($params['charset'])) {
+            $dsn .= 'charset=' . $params['charset'] . ';';
+        }
+
+        return $dsn;
+    }
+
+    public function createDatabasePlatform(PaperConnection $connection): PlatformInterface
+    {
+        return new MariaDBPlatform($connection, $this->createDatabaseSchema());
+    }
+
+    public function createDatabaseSchema(): MariaDBSchema
+    {
+        return new MariaDBSchema();
+    }
+}

+ 1 - 1
src/EntityManager.php

@@ -8,7 +8,7 @@ use PhpDevCommunity\PaperORM\Mapper\EntityMapper;
 use PhpDevCommunity\PaperORM\Platform\PlatformInterface;
 use PhpDevCommunity\PaperORM\Repository\Repository;
 
-class EntityManager
+class EntityManager implements EntityManagerInterface
 {
     private PaperConnection $connection;
 

+ 25 - 0
src/EntityManagerInterface.php

@@ -0,0 +1,25 @@
+<?php
+
+namespace PhpDevCommunity\PaperORM;
+
+use PhpDevCommunity\PaperORM\Cache\EntityMemcachedCache;
+use PhpDevCommunity\PaperORM\Platform\PlatformInterface;
+use PhpDevCommunity\PaperORM\Repository\Repository;
+
+interface EntityManagerInterface
+{
+    public function persist(object $entity): void;
+
+    public function remove(object $entity): void;
+
+    public function flush(): void;
+
+    public function getRepository(string $entity): Repository;
+
+    public function createDatabasePlatform(): PlatformInterface;
+
+    public function getConnection(): PaperConnection;
+    public function getCache(): EntityMemcachedCache;
+
+    public function clear(): void;
+}

+ 53 - 10
src/Generator/SchemaDiffGenerator.php

@@ -4,8 +4,11 @@ namespace PhpDevCommunity\PaperORM\Generator;
 
 use LogicException;
 use PhpDevCommunity\PaperORM\Mapping\Column\Column;
+use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Index;
+use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
 use PhpDevCommunity\PaperORM\Platform\PlatformInterface;
+use PhpDevCommunity\PaperORM\Schema\SchemaInterface;
 
 final class SchemaDiffGenerator
 {
@@ -20,24 +23,35 @@ final class SchemaDiffGenerator
     {
         $tablesExist = $this->platform->listTables();
         $schema = $this->platform->getSchema();
-        $sqlUp = [];
-        $sqlDown = [];
         foreach ($tables as $tableName => $tableData) {
-
             if (!isset($tableData['columns'])) {
                 throw new LogicException(sprintf(
                     "Missing column definitions for table '%s'. Each table must have a 'columns' key with its column structure.",
                     $tableName
                 ));
             }
-
             if (!isset($tableData['indexes'])) {
                 throw new LogicException(sprintf(
                     "Missing index definitions for table '%s'. Ensure the 'indexes' key is set, even if empty, to maintain consistency.",
                     $tableName
                 ));
             }
+        }
+
+        list( $sqlUp, $sqlDown) = $this->createTables($tables, $schema, $tablesExist);
+        return [
+            'up' => $sqlUp,
+            'down' => $sqlDown
+        ];
+    }
 
+    private function createTables(array $tables,SchemaInterface $schema, array $tablesExist): array
+    {
+        $sqlUp = [];
+        $sqlDown = [];
+        $sqlForeignKeyUp = [];
+        $sqlForeignKeyDown = [];
+        foreach ($tables as $tableName => $tableData) {
             /**
              * @var array<Column> $columns
              * @var array<Index> $indexes
@@ -56,11 +70,15 @@ final class SchemaDiffGenerator
                 $indexes = [];
             }
 
+
             $diff = $this->platform->diff($tableName, $columns, $indexes);
             $columnsToAdd = $diff->getColumnsToAdd();
             $columnsToUpdate = $diff->getColumnsToUpdate();
             $columnsToDelete = $diff->getColumnsToDelete();
 
+            $foreignKeyToAdd = $diff->getForeignKeyToAdd();
+            $foreignKeyToDrop = $diff->getForeignKeyToDrop();
+
             $indexesToAdd = $diff->getIndexesToAdd();
             $indexesToUpdate = $diff->getIndexesToUpdate();
             $indexesToDelete = $diff->getIndexesToDelete();
@@ -71,6 +89,16 @@ final class SchemaDiffGenerator
                     $sqlUp[] = $schema->createIndex($index);
                     $sqlDown[] = $schema->dropIndex($index);
                 }
+
+                foreach ($foreignKeyToAdd as $foreignKey) {
+                    if ($schema->supportsAddForeignKey()) {
+                        $sqlForeignKeyUp[] = $schema->createForeignKeyConstraint($tableName, $foreignKey);
+                    }
+                    if ($schema->supportsDropForeignKey()) {
+                        $sqlForeignKeyDown[] = $schema->dropForeignKeyConstraints($tableName, $foreignKey->getName());
+                    }
+                }
+
                 $sqlDown[] = $schema->dropTable($tableName);
                 continue;
             }
@@ -91,6 +119,14 @@ final class SchemaDiffGenerator
                 $sqlUp[] = $schema->createIndex($index);
                 $sqlDown[] = $schema->dropIndex($index);
             }
+            foreach ($foreignKeyToAdd as $foreignKey) {
+                if ($schema->supportsAddForeignKey()) {
+                    $sqlUp[] = $schema->createForeignKeyConstraint($tableName, $foreignKey);
+                }
+                if ($schema->supportsDropForeignKey()) {
+                    $sqlDown[] = $schema->dropForeignKeyConstraints($tableName, $foreignKey->getName());
+                }
+            }
 
             foreach ($columnsToUpdate as $column) {
                 if ($schema->supportsModifyColumn()) {
@@ -130,13 +166,20 @@ final class SchemaDiffGenerator
                 $sqlDown[] = $schema->dropIndex($index);
                 $sqlDown[] = $schema->createIndex($diff->getOriginalIndex($index->getName()));
             }
-        }
 
+            foreach ($foreignKeyToDrop as $foreignKey) {
+                if ($schema->supportsDropForeignKey()) {
+                    $sqlForeignKeyUp[] = $schema->dropForeignKeyConstraints($tableName, $foreignKey->getName());
+                }
+
+                if ($schema->supportsAddForeignKey()) {
+                    $sqlForeignKeyDown[] = $schema->createForeignKeyConstraint($tableName, $diff->getOriginalForeignKey($foreignKey->getName()));
+                }
+            }
+        }
 
-        return [
-            'up' => $sqlUp,
-            'down' => $sqlDown
-        ];
+        $sqlUp = array_merge($sqlUp, $sqlForeignKeyUp);
+        $sqlDown = array_merge($sqlForeignKeyDown, $sqlDown);
+        return [$sqlUp, $sqlDown];
     }
-
 }

+ 0 - 1
src/Mapping/Column/IntColumn.php

@@ -7,7 +7,6 @@ use PhpDevCommunity\PaperORM\Types\IntType;
 #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class IntColumn extends Column
 {
-
     public function __construct(
         string $name = null,
         bool   $nullable = false,

+ 29 - 2
src/Mapping/Column/JoinColumn.php

@@ -8,6 +8,12 @@ use ReflectionClass;
 #[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)]
 final class JoinColumn extends Column
 {
+    public const NO_ACTION   = 0;
+    public const RESTRICT    = 1;
+    public const CASCADE     = 2;
+    public const SET_NULL    = 3;
+    public const SET_DEFAULT = 4;
+
     /**
      * @var string
      */
@@ -16,18 +22,30 @@ final class JoinColumn extends Column
      * @var string
      */
     private string $targetEntity;
+    private int $onDelete;
+    private int $onUpdate;
 
     final public function __construct(
         string  $name,
-        string  $referencedColumnName,
         string  $targetEntity,
+        string  $referencedColumnName = 'id',
         bool   $nullable = false,
-        bool   $unique = false
+        bool   $unique = false,
+        int    $onDelete = self::NO_ACTION,
+        int    $onUpdate = self::NO_ACTION
+
     )
     {
+
+        if ($onDelete === self::SET_NULL && $nullable === false) {
+            throw new \InvalidArgumentException('SET NULL requires nullable=true.');
+        }
+
         parent::__construct('', $name, IntegerType::class, $nullable, null, $unique);
         $this->referencedColumnName = $referencedColumnName;
         $this->targetEntity = $targetEntity;
+        $this->onDelete = $onDelete;
+        $this->onUpdate = $onUpdate;
     }
 
     public function getReferencedColumnName(): string
@@ -49,4 +67,13 @@ final class JoinColumn extends Column
         return '\\' . ltrim(parent::getType(), '\\');
     }
 
+    public function getOnDelete(): int
+    {
+        return $this->onDelete;
+    }
+
+    public function getOnUpdate(): int
+    {
+        return $this->onUpdate;
+    }
 }

+ 68 - 25
src/Metadata/ColumnMetadata.php

@@ -7,13 +7,14 @@ use PhpDevCommunity\PaperORM\Mapper\EntityMapper;
 use PhpDevCommunity\PaperORM\Mapping\Column\Column;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
 
 class ColumnMetadata
 {
     private string $name;
     private string $type;
     private bool $isPrimary;
-    private array $foreignKeyMetadata;
+    private ?ForeignKeyMetadata $foreignKeyMetadata = null;
     private bool $isNullable;
     private $defaultValue;
     private ?string $comment;
@@ -21,22 +22,22 @@ class ColumnMetadata
     private ?IndexMetadata $indexMetadata;
 
     public function __construct(
-        string  $name,
-        string  $type,
-        bool    $isPrimary = false,
-        array   $foreignKeyMetadata = [],
-        bool    $isNullable = true,
-                $defaultValue = null,
-        ?string $comment = null,
-        array   $attributes = []
+        string              $name,
+        string              $type,
+        bool                $isPrimary = false,
+        bool                $isNullable = true,
+                            $defaultValue = null,
+        ?ForeignKeyMetadata $foreignKeyMetadata = null,
+        ?string             $comment = null,
+        array               $attributes = []
     )
     {
         $this->name = $name;
         $this->type = strtoupper($type);
         $this->isPrimary = $isPrimary;
-        $this->foreignKeyMetadata = $foreignKeyMetadata;
         $this->isNullable = $isNullable;
         $this->defaultValue = $defaultValue;
+        $this->foreignKeyMetadata = $foreignKeyMetadata;
         $this->comment = $comment;
         $this->attributes = $attributes;
     }
@@ -65,7 +66,7 @@ class ColumnMetadata
         return $this->isPrimary;
     }
 
-    public function getForeignKeyMetadata(): array
+    public function getForeignKeyMetadata(): ?ForeignKeyMetadata
     {
         return $this->foreignKeyMetadata;
     }
@@ -77,7 +78,23 @@ class ColumnMetadata
 
     public function getDefaultValue()
     {
-        return $this->defaultValue;
+        $value = $this->defaultValue;
+        if (is_bool($value)) {
+            return intval($value);
+        }
+        return $value;
+    }
+
+    public function getDefaultValuePrintable()
+    {
+        $value = $this->defaultValue;
+        if (is_bool($value)) {
+            return $value ? '1' : '0';
+        }
+        if (is_string($value)) {
+            return "'$value'";
+        }
+        return $value;
     }
 
     public function getComment(): ?string
@@ -90,25 +107,38 @@ class ColumnMetadata
         return $this->attributes;
     }
 
-    public static function fromColumn(Column $column, string $sqlType): self
+    public static function fromColumn(
+        Column $column,
+        string $sqlType,
+        ?string $defaultFirstArgument = null,
+        ?string $defaultSecondArgument = null
+    ): self
     {
-        $foreignKeyMetadata = [];
+        $foreignKeyMetadata = null;
         if ($column instanceof JoinColumn) {
             $targetEntity = $column->getTargetEntity();
             if (is_subclass_of($targetEntity, EntityInterface::class)) {
-                $foreignKeyMetadata = [
-                    'referencedTable' => EntityMapper::getTable($targetEntity),
-                    'referencedColumn' => $column->getReferencedColumnName(),
-                ];
+                $tableName = EntityMapper::getTable($targetEntity);
+                $foreignKeyMetadata = ForeignKeyMetadata::fromArray([
+                    'name' => null,
+                    'columns' => [$column->getName()],
+                    'referenceTable' => $tableName,
+                    'referenceColumns' => [$column->getReferencedColumnName()],
+                    'onDelete' => $column->getOnDelete(),
+                    'onUpdate' => $column->getOnUpdate(),
+                ]);
             }
         }
-
         $arguments = [];
         if ($column->getFirstArgument()) {
             $arguments[] = $column->getFirstArgument();
+        }elseif ($defaultFirstArgument) {
+            $arguments[] = $defaultFirstArgument;
         }
         if ($column->getSecondArgument()) {
             $arguments[] = $column->getSecondArgument();
+        }elseif ($defaultSecondArgument) {
+            $arguments[] = $defaultSecondArgument;
         }
 
         $defaultValue = $column->getDefaultValue();
@@ -119,9 +149,9 @@ class ColumnMetadata
             $column->getName(),
             $sqlType,
             $column instanceof PrimaryKeyColumn,
-            $foreignKeyMetadata,
             $column->isNullable(),
             $defaultValue,
+            $foreignKeyMetadata,
             null,
             $arguments
         );
@@ -133,24 +163,37 @@ class ColumnMetadata
             $data['name'],
             $data['type'],
             $data['primary'] ?? false,
-            $data['foreignKeyMetadata'] ?? false,
             $data['null'] ?? true,
-            $data['default'] ?? null,
-            $data['comment'] ?? null,
+        $data['default'] ?? null,
+            $data['foreignKeyMetadata'] ?? null,
+        $data['comment'] ?? null,
             $data['attributes'] ?? []
         );
     }
 
+    public function replaceForeignKey(ForeignKeyMetadata $foreignKey): self
+    {
+        return new self(
+            $this->getName(),
+            $this->getType(),
+            $this->isPrimary(),
+            $this->isNullable(),
+            $this->getDefaultValue(),
+            $foreignKey,
+            $this->getComment(),
+            $this->getAttributes()
+        );
+    }
+
     public function toArray(): array
     {
         return [
             'name' => $this->getName(),
-
             'type' => $this->getType(),
             'primary' => $this->isPrimary(),
-            'foreignKeyMetadata' => $this->getForeignKeyMetadata(),
             'null' => $this->isNullable(),
             'default' => $this->getDefaultValue(),
+            'foreignKeyMetadata' => $this->getForeignKeyMetadata() ? $this->getForeignKeyMetadata()->toArray() : null,
             'comment' => $this->getComment(),
             'attributes' => $this->getAttributes(),
         ];

+ 56 - 0
src/Metadata/DatabaseSchemaDiffMetadata.php

@@ -3,6 +3,7 @@
 namespace PhpDevCommunity\PaperORM\Metadata;
 
 use LogicException;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
 
 final class DatabaseSchemaDiffMetadata
 {
@@ -12,6 +13,11 @@ final class DatabaseSchemaDiffMetadata
     private array $originalColumns = [];
 
 
+    private array $foreignKeyToAdd = [];
+    private array $foreignKeyToDrop = [];
+    private array $originalForeignKeys = [];
+
+
     private array $indexesToAdd = [];
     private array $indexesToUpdate = [];
     private array $indexesToDelete = [];
@@ -28,6 +34,9 @@ final class DatabaseSchemaDiffMetadata
         array $columnsToUpdate,
         array $columnsToDelete,
         array $originalColumns,
+        array $foreignKeyToAdd,
+        array $foreignKeyToDrop,
+        array $originalForeignKeys,
         array $indexesToAdd,
         array $indexesToUpdate,
         array $indexesToDelete,
@@ -63,6 +72,29 @@ final class DatabaseSchemaDiffMetadata
         }
 
 
+        foreach ($foreignKeyToAdd as $foreignKey) {
+            if (!$foreignKey instanceof ForeignKeyMetadata) {
+                throw new LogicException(sprintf("The foreign key '%s' is not supported.", get_class($foreignKey)));
+            }
+            $this->foreignKeyToAdd[$foreignKey->getName()] = $foreignKey;
+        }
+
+
+        foreach ($foreignKeyToDrop as $foreignKey) {
+            if (!$foreignKey instanceof ForeignKeyMetadata) {
+                throw new LogicException(sprintf("The foreign key '%s' is not supported.", get_class($foreignKey)));
+            }
+            $this->foreignKeyToDrop[$foreignKey->getName()] = $foreignKey;
+        }
+
+        foreach ($originalForeignKeys as $foreignKey) {
+            if (!$foreignKey instanceof ForeignKeyMetadata) {
+                throw new LogicException(sprintf("The foreign key '%s' is not supported.", get_class($foreignKey)));
+            }
+            $this->originalForeignKeys[$foreignKey->getName()] = $foreignKey;
+        }
+
+
         foreach ($indexesToAdd as $index) {
             if (!$index instanceof IndexMetadata) {
                 throw new LogicException(sprintf("The index '%s' is not supported.", get_class($index)));
@@ -124,6 +156,30 @@ final class DatabaseSchemaDiffMetadata
         return $this->originalColumns[$name];
     }
 
+    /**
+     * @return array<ForeignKeyMetadata>
+     */
+    public function getForeignKeyToAdd(): array
+    {
+        return $this->foreignKeyToAdd;
+    }
+
+    /**
+     * @return array<ForeignKeyMetadata>
+     */
+    public function getForeignKeyToDrop(): array
+    {
+        return $this->foreignKeyToDrop;
+    }
+
+    public function getOriginalForeignKey(string $name): ForeignKeyMetadata
+    {
+        if (!isset($this->originalForeignKeys[$name])) {
+            throw new LogicException(sprintf("The foreign key '%s' is not supported.", $name));
+        }
+        return $this->originalForeignKeys[$name];
+    }
+
 
     /**
      * @return IndexMetadata[]

+ 82 - 0
src/Metadata/ForeignKeyMetadata.php

@@ -0,0 +1,82 @@
+<?php
+
+namespace PhpDevCommunity\PaperORM\Metadata;
+
+final class ForeignKeyMetadata
+{
+    public const NO_ACTION   = 0;
+    public const RESTRICT    = 1;
+    public const CASCADE     = 2;
+    public const SET_NULL    = 3;
+    public const SET_DEFAULT = 4;
+
+    private array $columns;
+    private string $referenceTable;
+    private array $referenceColumns;
+    private ?string $name;
+
+    private int $onDelete;
+    private int $onUpdate;
+    public function __construct(array $columns, string $referenceTable, array $referenceColumns, int $onDelete = self::NO_ACTION, int $onUpdate = self::NO_ACTION, ?string $name = null)
+    {
+        $this->columns = $columns;
+        $this->referenceTable = $referenceTable;
+        $this->referenceColumns = $referenceColumns;
+        $this->name = $name;
+        $this->onDelete = $onDelete;
+        $this->onUpdate = $onUpdate;
+    }
+
+    public function getColumns(): array
+    {
+        return $this->columns;
+    }
+
+    public function getReferenceTable(): string
+    {
+        return $this->referenceTable;
+    }
+
+    public function getReferenceColumns(): array
+    {
+        return $this->referenceColumns;
+    }
+
+
+    public function getName(): ?string
+    {
+        return $this->name;
+    }
+
+    public function getOnDelete(): int
+    {
+        return $this->onDelete;
+    }
+
+    public function getOnUpdate(): int
+    {
+        return $this->onUpdate;
+    }
+
+    public static function fromArray(array $data): ForeignKeyMetadata
+    {
+        return new ForeignKeyMetadata($data['columns'], $data['referenceTable'], $data['referenceColumns'], $data['onDelete'] ?? self::NO_ACTION, $data['onUpdate'] ?? self::NO_ACTION, $data['name'] ?? null);
+    }
+
+    public static function fromForeignKeyMetadataOverrideName(ForeignKeyMetadata $foreignKey, string $name): ForeignKeyMetadata
+    {
+        return new ForeignKeyMetadata($foreignKey->getColumns(), $foreignKey->getReferenceTable(), $foreignKey->getReferenceColumns(),$foreignKey->getOnDelete(), $foreignKey->getOnUpdate(), $name);
+    }
+
+    public function toArray() : array
+    {
+        return [
+            'name' => $this->getName(),
+            'columns' => $this->getColumns(),
+            'referenceTable' => $this->getReferenceTable(),
+            'referenceColumns' => $this->getReferenceColumns(),
+            'onDelete' => $this->getOnDelete(),
+            'onUpdate' => $this->getOnUpdate(),
+        ];
+    }
+}

+ 6 - 2
src/Michel/Package/MichelPaperORMPackage.php

@@ -11,6 +11,7 @@ use PhpDevCommunity\PaperORM\Command\MigrationMigrateCommand;
 use PhpDevCommunity\PaperORM\Command\QueryExecuteCommand;
 use PhpDevCommunity\PaperORM\Command\ShowTablesCommand;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 use PhpDevCommunity\PaperORM\Migration\PaperMigration;
 use PhpDevCommunity\PaperORM\Parser\DSNParser;
 use Psr\Container\ContainerInterface;
@@ -20,6 +21,9 @@ class MichelPaperORMPackage implements PackageInterface
     public function getDefinitions(): array
     {
         return [
+            EntityManagerInterface::class => static function (ContainerInterface $container) {
+                return $container->get(EntityManager::class);
+            },
             EntityManager::class => static function (ContainerInterface $container) {
                 $dsn = $container->get('database.dsn');
                 if (!is_string($dsn) || empty($dsn)) {
@@ -30,7 +34,7 @@ class MichelPaperORMPackage implements PackageInterface
             },
             PaperMigration::class => static function (ContainerInterface $container) {
                 return PaperMigration::create(
-                    $container->get(EntityManager::class),
+                    $container->get(EntityManagerInterface::class),
                     $container->get('paper.migration.table'),
                     $container->get('paper.migration.dir')
                 );
@@ -39,7 +43,7 @@ class MichelPaperORMPackage implements PackageInterface
                 return new MigrationDiffCommand($container->get(PaperMigration::class), $container->get('paper.entity.dir'));
             },
             DatabaseDropCommand::class => static function (ContainerInterface $container) {
-                return new DatabaseDropCommand($container->get(EntityManager::class), $container->get('michel.environment'));
+                return new DatabaseDropCommand($container->get(EntityManagerInterface::class), $container->get('michel.environment'));
             }
         ];
     }

+ 7 - 6
src/Migration/PaperMigration.php

@@ -6,6 +6,7 @@ use DateTime;
 use PDOException;
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 use PhpDevCommunity\PaperORM\Generator\SchemaDiffGenerator;
 use PhpDevCommunity\PaperORM\Mapper\ColumnMapper;
 use PhpDevCommunity\PaperORM\Mapper\EntityMapper;
@@ -21,8 +22,8 @@ use function file_put_contents;
 final class PaperMigration
 {
 
-    /** @var EntityManager The EntityManager to use for migrations. */
-    private EntityManager $em;
+    /** @var EntityManagerInterface The EntityManager to use for migrations. */
+    private EntityManagerInterface $em;
     private PlatformInterface $platform;
     private string $tableName;
 
@@ -33,18 +34,18 @@ final class PaperMigration
      */
     private MigrationDirectory $directory;
 
-    public static function create(EntityManager $em, string $tableName, string $directory): self
+    public static function create(EntityManagerInterface $em, string $tableName, string $directory): self
     {
         return new self($em, $tableName, $directory);
     }
 
     /**
      * MigrateService constructor.
-     * @param EntityManager $em
+     * @param EntityManagerInterface $em
      * @param string $tableName
      * @param string $directory
      */
-    private function __construct(EntityManager $em, string $tableName, string $directory)
+    private function __construct(EntityManagerInterface $em, string $tableName, string $directory)
     {
         $this->em = $em;
         $this->platform = $em->createDatabasePlatform();
@@ -226,7 +227,7 @@ SQL;
         return $this->successList;
     }
 
-    public function getEntityManager(): EntityManager
+    public function getEntityManager(): EntityManagerInterface
     {
         return $this->em;
     }

+ 10 - 2
src/PaperConnection.php

@@ -15,7 +15,7 @@ final class PaperConnection
 
     private DriverInterface $driver;
 
-    private bool $debug = false;
+    private bool $debug;
 
     public function __construct(DriverInterface $driver, array $params)
     {
@@ -79,7 +79,7 @@ final class PaperConnection
 
     public function connect(): bool
     {
-        if ($this->pdo === null) {
+        if (!$this->isConnected()) {
             $this->pdo = $this->driver->connect($this->params);
             if ($this->debug) {
                 $this->pdo->enableSqlDebugger();
@@ -100,4 +100,12 @@ final class PaperConnection
         $this->pdo = null;
     }
 
+
+    public function cloneConnectionWithoutDbname(): self
+    {
+        $params = $this->params;
+        unset($params['dbname']);
+        return new self($this->driver, $params);
+    }
+
 }

+ 106 - 22
src/Platform/AbstractPlatform.php

@@ -5,28 +5,28 @@ namespace PhpDevCommunity\PaperORM\Platform;
 use LogicException;
 use PhpDevCommunity\PaperORM\Collection\ObjectStorage;
 use PhpDevCommunity\PaperORM\Mapping\Column\Column;
-use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Index;
-use PhpDevCommunity\PaperORM\Metadata\DatabaseSchemaDiffMetadata;
 use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
+use PhpDevCommunity\PaperORM\Metadata\DatabaseSchemaDiffMetadata;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
 use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
-use PhpDevCommunity\PaperORM\Schema\SchemaInterface;
 
 abstract class AbstractPlatform implements PlatformInterface
 {
-    final public function mapColumnsToMetadata(array $columns): array
+    final public function mapColumnsToMetadata(string $tableName, $columns): array
     {
         $columnsMetadata = [];
         foreach ($columns as $column) {
             if (!$column instanceof Column) {
                 throw new LogicException(sprintf("The column '%s' is not supported.", is_object($column) ? get_class($column) : gettype($column)));
             }
-            $columnsMetadata[] = $this->mapColumnToMetadata($column);
+            $columnsMetadata[] = $this->mapColumnToMetadata($tableName, $column);
         }
 
         return $columnsMetadata;
     }
-    final public function mapColumnToMetadata(Column $column): ColumnMetadata
+
+    final public function mapColumnToMetadata(string $tableName, Column $column): ColumnMetadata
     {
         $mappings = $this->getColumnTypeMappings();
         $className = get_class($column);
@@ -34,8 +34,20 @@ abstract class AbstractPlatform implements PlatformInterface
             throw new LogicException(sprintf("The column type '%s' is not supported.", $column->getType()));
         }
 
-        $sqlType = $mappings[$className];
-        return ColumnMetadata::fromColumn($column, $sqlType);
+        $mapping = $mappings[$className];
+        $sqlType = $mapping['type'];
+        $args = $mapping['args'];
+        $columnMetadata = ColumnMetadata::fromColumn($column, $sqlType,$args[0] ?? null, $args[1] ??  null);
+        if ($columnMetadata->getForeignKeyMetadata() && $columnMetadata->getForeignKeyMetadata()->getName() === null) {
+            $columnForeignKey = $columnMetadata->getForeignKeyMetadata();
+            return $columnMetadata->replaceForeignKey(
+                ForeignKeyMetadata::fromForeignKeyMetadataOverrideName(
+                    $columnForeignKey,
+                     $this->generateForeignKeyName($tableName, $columnForeignKey->getColumns())
+                )
+            );
+        }
+        return $columnMetadata;
     }
 
     /**
@@ -46,14 +58,25 @@ abstract class AbstractPlatform implements PlatformInterface
      */
     final public function diff(string $tableName, array $columns, array $indexes): DatabaseSchemaDiffMetadata
     {
-        list($columnsToAdd, $columnsToUpdate, $columnsToDrop, $originalColumns) = $this->diffColumns($tableName, $columns);
+        list(
+            $columnsToAdd,
+            $columnsToUpdate,
+            $columnsToDrop,
+            $originalColumns,
+            $foreignKeyToAdd,
+            $foreignKeyToDrop,
+            $originalForeignKeys,
+            ) = $this->diffColumns($tableName, $columns);
         list($indexesToAdd, $indexesToUpdate, $indexesToDrop, $originalIndexes) = $this->diffIndexes($tableName, $indexes);
+
         return new DatabaseSchemaDiffMetadata(
             $columnsToAdd,
             $columnsToUpdate,
             $columnsToDrop,
             $originalColumns,
-
+            $foreignKeyToAdd,
+            $foreignKeyToDrop,
+            $originalForeignKeys,
             $indexesToAdd,
             $indexesToUpdate,
             $indexesToDrop,
@@ -61,39 +84,89 @@ abstract class AbstractPlatform implements PlatformInterface
         );
     }
 
+    /**
+     * @param string $tableName
+     * @param array<Column> $columns
+     * @return array
+     *
+     */
     private function diffColumns(string $tableName, array $columns): array
     {
         $columnsFromTable = $this->listTableColumns($tableName);
-        $columnsFromTableByName = [];
+        $columnsExisting = [];
+        $foreignKeysExisting = [];
         foreach ($columnsFromTable as $columnMetadata) {
-            $columnsFromTableByName[$columnMetadata->getName()] = $columnMetadata;
+            $columnsExisting[$columnMetadata->getName()] = $columnMetadata;
+            if ($columnMetadata->getForeignKeyMetadata()) {
+                $foreignKeysExisting[$columnMetadata->getForeignKeyMetadata()->getName()] = $columnMetadata->getForeignKeyMetadata();
+            }
         }
 
         $columnsToAdd = [];
         $columnsToUpdate = [];
         $columnsToDrop = [];
 
-        $columnsExisting = [];
+        $foreignKeyToAdd = [];
+        $foreignKeyToDrop = [];
+
+        $columnsProcessed = [];
+        $foreignKeysProcessed = [];
         foreach ($columns as $column) {
-            $columnMetadata = $this->mapColumnToMetadata($column);
-            if (isset($columnsFromTableByName[$columnMetadata->getName()])) {
-                $columnFromTable = $columnsFromTableByName[$columnMetadata->getName()];
+            $columnMetadata = $this->mapColumnToMetadata($tableName, $column);
+            $willBeUpdated = false;
+            if (isset($columnsExisting[$columnMetadata->getName()])) {
+                $columnFromTable = $columnsExisting[$columnMetadata->getName()];
                 if ($columnFromTable->toArray() != $columnMetadata->toArray()) {
                     $columnsToUpdate[] = $columnMetadata;
+                    $willBeUpdated = true;
                 }
             } else {
                 $columnsToAdd[] = $columnMetadata;
             }
-            $columnsExisting[] = $columnMetadata->getName();
-        }
+            $columnsProcessed[] = $columnMetadata->getName();
+            if ($columnMetadata->getForeignKeyMetadata()) {
+                $columnForeignKey = $columnMetadata->getForeignKeyMetadata();
+                $foreignKeyName = $columnForeignKey->getName();
+                if (isset($foreignKeysExisting[$foreignKeyName])) {
+                    if ($willBeUpdated || $foreignKeysExisting[$foreignKeyName]->toArray() != $columnForeignKey->toArray()) {
+                        $foreignKeyToDrop[] = $foreignKeysExisting[$foreignKeyName];
+                        $foreignKeyToAdd[] = $columnForeignKey;
+                    }
+                }else {
+                    $foreignKeyToAdd[] = $columnForeignKey;
+                }
 
+                $foreignKeysProcessed[$foreignKeyName] = true;
+            }
+        }
 
-        foreach ($columnsFromTableByName as $columnMetadata) {
-            if (!in_array($columnMetadata->getName(), $columnsExisting)) {
+        foreach ($columnsExisting as $columnMetadata) {
+            $willDrop = !in_array($columnMetadata->getName(), $columnsProcessed);
+            if ($willDrop) {
                 $columnsToDrop[] = $columnMetadata;
             }
+            if ($columnMetadata->getForeignKeyMetadata()) {
+                $columnForeignKey = $columnMetadata->getForeignKeyMetadata();
+                $foreignKeyName = $columnForeignKey->getName();
+                if (($willDrop && isset($foreignKeysExisting[$foreignKeyName])) || !isset($foreignKeysProcessed[$foreignKeyName])) {
+                    $foreignKeyToDrop[] = $columnForeignKey;
+                }
+            }
         }
-        return [$columnsToAdd, $columnsToUpdate, $columnsToDrop, $columnsFromTable];
+
+        $foreignKeyToAdd = array_values(array_unique($foreignKeyToAdd, SORT_REGULAR));
+        $foreignKeyToDrop = array_values(array_unique($foreignKeyToDrop, SORT_REGULAR));
+
+        return [
+            $columnsToAdd,
+            $columnsToUpdate,
+            $columnsToDrop,
+            $columnsFromTable,
+
+            $foreignKeyToAdd,
+            $foreignKeyToDrop,
+            array_values($foreignKeysExisting),
+        ];
     }
 
     /**
@@ -116,7 +189,7 @@ abstract class AbstractPlatform implements PlatformInterface
                 if ($indexMetadata->toArray() != $indexFound->toArray()) {
                     $indexesToUpdate[] = $indexMetadata;
                 }
-            }else {
+            } else {
                 $indexesToAdd[] = $indexMetadata;
             }
             $indexesExisting[] = $indexMetadata->getName();
@@ -131,6 +204,7 @@ abstract class AbstractPlatform implements PlatformInterface
         return [$indexesToAdd, $indexesToUpdate, $indexesToDrop, $indexesFromTable->toArray()];
     }
 
+
     final protected function generateIndexName(string $tableName, array $columnNames): string
     {
         $hash = implode('', array_map(static function ($column) {
@@ -139,4 +213,14 @@ abstract class AbstractPlatform implements PlatformInterface
 
         return strtoupper(substr($this->getPrefixIndexName() . $hash, 0, $this->getMaxLength()));
     }
+
+    final protected function generateForeignKeyName(string $tableName, array $columnNames): string
+    {
+        $hash = implode('', array_map(static function ($column) {
+            return dechex(crc32($column));
+        }, array_merge([$tableName], $columnNames)));
+
+        return strtoupper(substr($this->getPrefixForeignKeyName() . $hash, 0, $this->getMaxLength()));
+
+    }
 }

+ 305 - 0
src/Platform/MariaDBPlatform.php

@@ -0,0 +1,305 @@
+<?php
+
+namespace PhpDevCommunity\PaperORM\Platform;
+
+use InvalidArgumentException;
+use LogicException;
+use PhpDevCommunity\PaperORM\Mapping\Column\BoolColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\Column;
+use PhpDevCommunity\PaperORM\Mapping\Column\DateColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\DateTimeColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\DecimalColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\FloatColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\IntColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\JsonColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
+use PhpDevCommunity\PaperORM\Mapping\Column\TextColumn;
+use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
+use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
+use PhpDevCommunity\PaperORM\PaperConnection;
+use PhpDevCommunity\PaperORM\Parser\SQLTypeParser;
+use PhpDevCommunity\PaperORM\Schema\MariaDBSchema;
+use PhpDevCommunity\PaperORM\Schema\SchemaInterface;
+
+class MariaDBPlatform extends AbstractPlatform
+{
+    private PaperConnection $connection;
+    private MariaDBSchema $schema;
+
+    public function __construct(PaperConnection $connection, MariaDBSchema $schema)
+    {
+        $this->connection = $connection;
+        $this->schema = $schema;
+    }
+
+    public function getDatabaseName(): string
+    {
+        return $this->connection->getParams()['dbname'] ?? '';
+    }
+
+    public function listTables(): array
+    {
+        $rows = $this->connection->fetchAll($this->schema->showTables());
+        $tables = [];
+        foreach ($rows as $table) {
+            $table = array_values($table);
+            $tables[] = $table[0];
+        }
+        rsort($tables, SORT_STRING);
+        return $tables;
+    }
+
+    /**
+     * @param string $tableName
+     * @return array<ColumnMetadata>
+     */
+    public function listTableColumns(string $tableName): array
+    {
+        $tables = $this->listTables();
+        if (!in_array($tableName, $tables)) {
+            return [];
+        }
+        $rows = $this->connection->fetchAll($this->schema->showTableColumns($tableName));
+        $foreignKeys = $this->connection->fetchAll($this->schema->showForeignKeys($tableName));
+        $columns = [];
+        foreach ($rows as $row) {
+            $foreignKeyMetadata = null;
+            foreach ($foreignKeys as $foreignKey) {
+                if ($row['Field'] == $foreignKey['COLUMN_NAME']) {
+                    $foreignKeyMetadata = ForeignKeyMetadata::fromArray([
+                        'name' => $foreignKey['CONSTRAINT_NAME'],
+                        'columns' => [$row['Field']],
+                        'referenceTable' => $foreignKey['REFERENCED_TABLE_NAME'],
+                        'referenceColumns' => [$foreignKey['REFERENCED_COLUMN_NAME']],
+                        'onDelete' => $this->convertForeignKeyRuleStringToCode($foreignKey['DELETE_RULE']),
+                        'onUpdate' => $this->convertForeignKeyRuleStringToCode($foreignKey['UPDATE_RULE']),
+                    ]);
+                    break;
+                }
+            }
+            $columnMetadata = ColumnMetadata::fromArray([
+                'name' => $row['Field'],
+                'type' => SQLTypeParser::getBaseType($row['Type']),
+                'primary' => ($row['Key'] === 'PRI'),
+                'foreignKeyMetadata' => $foreignKeyMetadata,
+                'null' => ($row['Null'] === 'YES'),
+                'default' => $row['Default'] ?? null,
+                'comment' => $row['comment'] ?? null,
+                'attributes' => SQLTypeParser::extractTypedParameters($row['Type']),
+            ]);
+            $columns[] = $columnMetadata;
+        }
+        return $columns;
+    }
+
+    /**
+     * @param string $tableName
+     * @return array<IndexMetadata>
+     */
+    public function listTableIndexes(string $tableName): array
+    {
+        $tables = $this->listTables();
+        if (!in_array($tableName, $tables)) {
+            return [];
+        }
+        $indexes = $this->connection->fetchAll($this->schema->showTableIndexes($tableName));
+        $indexByColumns = [];
+        foreach ($indexes as $index) {
+            $indexName = $index['Key_name'];
+            if (isset($indexByColumns[$indexName])) {
+                $indexByColumns[$indexName]['columns'][] = $index['Column_name'];
+                continue;
+            }
+            if ($indexName === 'PRIMARY') {
+                continue;
+            }
+            $indexByColumns[$indexName] = [
+                'tableName' => $index['Table'],
+                'name' => $indexName,
+                'columns' => [$index['Column_name']],
+                'unique' => ((int)$index['Non_unique'] === 0),
+            ];
+        }
+
+        $indexesFormatted = [];
+        foreach ($indexByColumns as $idx) {
+            $indexesFormatted[] = IndexMetadata::fromArray($idx);
+        }
+        return $indexesFormatted;
+    }
+
+    public function listDatabases(): array
+    {
+        throw new LogicException(sprintf("The method '%s' is not supported by the platform interface.", __METHOD__));
+    }
+
+    public function createDatabase(): void
+    {
+        $connection = $this->connection->cloneConnectionWithoutDbname();
+        $connection->executeStatement($this->schema->createDatabase($this->getDatabaseName()));
+    }
+
+    public function createDatabaseIfNotExists(): void
+    {
+        $connection = $this->connection->cloneConnectionWithoutDbname();
+        $connection->executeStatement($this->schema->createDatabaseIfNotExists($this->getDatabaseName()));
+    }
+
+    public function dropDatabase(): void
+    {
+        $connection = $this->connection->cloneConnectionWithoutDbname();
+        $database = $this->getDatabaseName();
+        $connection->executeStatement($this->schema->dropDatabase($database));
+    }
+
+    public function createTable(string $tableName, array $columns): int
+    {
+        return $this->executeStatement($this->schema->createTable($tableName, $this->mapColumnsToMetadata($tableName, $columns)));
+    }
+
+    public function createTableIfNotExists(string $tableName, array $columns, array $options = []): int
+    {
+        return $this->connection->executeStatement($this->schema->createTableIfNotExists($tableName, $this->mapColumnsToMetadata($tableName, $columns)));
+    }
+
+    public function dropTable(string $tableName): int
+    {
+        return $this->connection->executeStatement($this->schema->dropTable($tableName));
+    }
+
+    public function addColumn(string $tableName, Column $column): int
+    {
+        return $this->connection->executeStatement($this->schema->addColumn($tableName, $this->mapColumnToMetadata($tableName, $column)));
+    }
+
+    public function dropColumn(string $tableName, Column $column): int
+    {
+        return $this->connection->executeStatement($this->schema->dropColumn($tableName, $this->mapColumnToMetadata($tableName, $column)));
+    }
+
+    public function renameColumn(string $tableName, string $oldColumnName, string $newColumnName): int
+    {
+        return $this->connection->executeStatement($this->schema->renameColumn($tableName, $oldColumnName, $newColumnName));
+    }
+
+    public function createIndex(IndexMetadata $indexMetadata): int
+    {
+        return $this->connection->executeStatement($this->schema->createIndex($indexMetadata));
+    }
+
+    public function dropIndex(IndexMetadata $indexMetadata): int
+    {
+        return $this->connection->executeStatement($this->schema->dropIndex($indexMetadata));
+    }
+
+    public function createForeignKeyConstraint(string $tableName, ForeignKeyMetadata $foreignKey): int
+    {
+        return $this->executeStatement($this->schema->createForeignKeyConstraint($tableName, $foreignKey));
+    }
+
+    public function dropForeignKeyConstraints(string $tableName, string $foreignKeyName): int
+    {
+        return $this->executeStatement($this->schema->dropForeignKeyConstraints($tableName, $foreignKeyName));
+    }
+
+    public function getMaxLength(): int
+    {
+        return 30;
+    }
+
+    public function getPrefixIndexName(): string
+    {
+        return 'ix_';
+    }
+
+    public function getPrefixForeignKeyName(): string
+    {
+        return 'fk_';
+    }
+
+    public function getColumnTypeMappings(): array
+    {
+        return [
+            PrimaryKeyColumn::class => [
+                'type' => 'INT',
+                'args' => [11]
+            ],
+            IntColumn::class => [
+                'type' => 'INT',
+                'args' => [11]
+            ],
+            JoinColumn::class => [
+                'type' => 'INT',
+                'args' => [11]
+            ],
+            DecimalColumn::class => [
+                'type' => 'DECIMAL',
+                'args' => [10, 5]
+            ],
+            FloatColumn::class => [
+                'type' => 'FLOAT',
+                'args' => []
+            ],
+            DateColumn::class => [
+                'type' => 'DATE',
+                'args' => []
+            ],
+            DateTimeColumn::class => [
+                'type' => 'DATETIME',
+                'args' => []
+            ],
+            BoolColumn::class => [
+                'type' => 'TINYINT',
+                'args' => [1]
+            ],
+            TextColumn::class => [
+                'type' => 'TEXT',
+                'args' => []
+            ],
+            JsonColumn::class => [
+                'type' => 'JSON',
+                'args' => []
+            ],
+            StringColumn::class => [
+                'type' => 'VARCHAR',
+                'args' => [255]
+            ],
+        ];
+    }
+
+    public function convertForeignKeyRuleStringToCode(?string $rule): int
+    {
+        $rule = strtoupper($rule);
+        switch ($rule) {
+            case 'CASCADE':
+                return ForeignKeyMetadata::CASCADE;
+            case 'SET NULL':
+                return ForeignKeyMetadata::SET_NULL;
+            case 'RESTRICT':
+                return ForeignKeyMetadata::RESTRICT;
+            case 'NO ACTION':   /* fall-through */
+            default:
+                return ForeignKeyMetadata::NO_ACTION;
+        }
+    }
+
+    public function getSchema(): SchemaInterface
+    {
+        return $this->schema;
+    }
+
+    public function executeStatement(string $sql): int
+    {
+        $result = 0;
+        foreach (explode(';', $sql) as $stmt) {
+            $stmt = trim($stmt);
+            if (!empty($stmt)) {
+                $result += $this->connection->executeStatement($stmt);
+            }
+        }
+        return $result;
+    }
+}

+ 5 - 0
src/Platform/PlatformInterface.php

@@ -3,6 +3,7 @@
 namespace PhpDevCommunity\PaperORM\Platform;
 
 use PhpDevCommunity\PaperORM\Mapping\Column\Column;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
 use PhpDevCommunity\PaperORM\Metadata\DatabaseSchemaDiffMetadata;
 use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
 use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
@@ -83,9 +84,13 @@ interface PlatformInterface
     public function renameColumn(string $tableName, string $oldColumnName, string $newColumnName): int;
     public function createIndex(IndexMetadata $indexMetadata): int;
     public function dropIndex(IndexMetadata $indexMetadata): int;
+    public function createForeignKeyConstraint(string $tableName, ForeignKeyMetadata $foreignKey) :int;
+    public function dropForeignKeyConstraints(string $tableName, string $foreignKeyName): int;
     public function getColumnTypeMappings(): array;
+    public function convertForeignKeyRuleStringToCode(?string $rule): int;
     public function getMaxLength(): int;
     public function getPrefixIndexName(): string;
+    public function getPrefixForeignKeyName(): string;
     public function diff(string $tableName, array $columns, array $indexes): DatabaseSchemaDiffMetadata;
     public function getSchema(): SchemaInterface;
 }

+ 97 - 21
src/Platform/SqlitePlatform.php

@@ -2,7 +2,7 @@
 
 namespace PhpDevCommunity\PaperORM\Platform;
 
-use http\Exception\RuntimeException;
+use InvalidArgumentException;
 use LogicException;
 use PhpDevCommunity\PaperORM\Mapping\Column\BoolColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\Column;
@@ -17,11 +17,13 @@ use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\TextColumn;
 use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
 use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
 use PhpDevCommunity\PaperORM\PaperConnection;
 use PhpDevCommunity\PaperORM\Parser\SQLTypeParser;
 use PhpDevCommunity\PaperORM\Schema\SchemaInterface;
 use PhpDevCommunity\PaperORM\Schema\SqliteSchema;
+use RuntimeException;
 
 class SqlitePlatform extends AbstractPlatform
 {
@@ -36,6 +38,10 @@ class SqlitePlatform extends AbstractPlatform
 
     public function getDatabaseName(): string
     {
+        $memory = $this->connection->getParams()['memory'] ?? false;
+        if ($memory) {
+            return ':memory:';
+        }
         return $this->connection->getParams()['path'] ?? '';
     }
 
@@ -59,13 +65,17 @@ class SqlitePlatform extends AbstractPlatform
         $foreignKeys = $this->connection->fetchAll($this->schema->showForeignKeys($tableName));
         $columns = [];
         foreach ($rows as $row) {
-            $foreignKeyMetadata = [];
+            $foreignKeyMetadata = null;
             foreach ($foreignKeys as $foreignKey) {
                 if ($row['name'] == $foreignKey['from']) {
-                    $foreignKeyMetadata = [
-                        'referencedTable' => $foreignKey['table'],
-                        'referencedColumn' => $foreignKey['to'],
-                    ];
+                    $foreignKeyMetadata = ForeignKeyMetadata::fromArray([
+                        'name' => $this->generateForeignKeyName($tableName, [$row['name']]),
+                        'columns' => [$row['name']],
+                        'referenceTable' => $foreignKey['table'],
+                        'referenceColumns' => [$foreignKey['to']],
+                        'onDelete' => $this->convertForeignKeyRuleStringToCode($foreignKey['on_delete']),
+                        'onUpdate' => $this->convertForeignKeyRuleStringToCode($foreignKey['on_update']),
+                    ]);
                     break;
                 }
             }
@@ -154,12 +164,12 @@ class SqlitePlatform extends AbstractPlatform
 
     public function createTable(string $tableName, array $columns): int
     {
-        return $this->connection->executeStatement($this->schema->createTable($tableName, $this->mapColumnsToMetadata($columns)));
+        return $this->connection->executeStatement($this->schema->createTable($tableName, $this->mapColumnsToMetadata($tableName, $columns)));
     }
 
     public function createTableIfNotExists(string $tableName, array $columns, array $options = []): int
     {
-        return $this->connection->executeStatement($this->schema->createTableIfNotExists($tableName, $this->mapColumnsToMetadata($columns)));
+        return $this->connection->executeStatement($this->schema->createTableIfNotExists($tableName, $this->mapColumnsToMetadata($tableName, $columns)));
     }
 
     public function dropTable(string $tableName): int
@@ -169,12 +179,12 @@ class SqlitePlatform extends AbstractPlatform
 
     public function addColumn(string $tableName, Column $column): int
     {
-        return $this->connection->executeStatement($this->schema->addColumn($tableName, $this->mapColumnToMetadata($column)));
+        return $this->connection->executeStatement($this->schema->addColumn($tableName, $this->mapColumnToMetadata($tableName, $column)));
     }
 
     public function dropColumn(string $tableName, Column $column): int
     {
-        return $this->connection->executeStatement($this->schema->dropColumn($tableName, $this->mapColumnToMetadata($column)));
+        return $this->connection->executeStatement($this->schema->dropColumn($tableName, $this->mapColumnToMetadata($tableName, $column)));
     }
 
     public function renameColumn(string $tableName, string $oldColumnName, string $newColumnName): int
@@ -192,6 +202,16 @@ class SqlitePlatform extends AbstractPlatform
         return $this->connection->executeStatement($this->schema->dropIndex($indexMetadata));
     }
 
+    public function createForeignKeyConstraint(string $tableName, ForeignKeyMetadata $foreignKey): int
+    {
+        throw new LogicException(sprintf("The method '%s' is not supported by the platform interface.", __METHOD__));
+    }
+
+    public function dropForeignKeyConstraints(string $tableName, string $foreignKeyName): int
+    {
+        throw new LogicException(sprintf("The method '%s' is not supported by the platform interface.", __METHOD__));
+    }
+
     public function getMaxLength(): int
     {
         return 30;
@@ -202,23 +222,79 @@ class SqlitePlatform extends AbstractPlatform
         return 'ix_';
     }
 
+    public function getPrefixForeignKeyName(): string
+    {
+        return 'fk_';
+    }
+
     public function getColumnTypeMappings(): array
     {
         return [
-            PrimaryKeyColumn::class => 'INTEGER',
-            IntColumn::class => 'INTEGER',
-            JoinColumn::class => 'INTEGER',
-            DecimalColumn::class => 'DECIMAL',
-            FloatColumn::class => 'FLOAT',
-            DateColumn::class => 'DATE',
-            DateTimeColumn::class => 'DATETIME',
-            BoolColumn::class => 'BOOLEAN',
-            TextColumn::class => 'TEXT',
-            JsonColumn::class => 'JSON',
-            StringColumn::class => 'VARCHAR',
+            PrimaryKeyColumn::class => [
+                'type' => 'INTEGER',
+                'args' => [],
+            ],
+            IntColumn::class => [
+                'type' => 'INTEGER',
+                'args' => [],
+            ],
+            JoinColumn::class => [
+                'type' => 'INTEGER',
+                'args' => [],
+            ],
+            DecimalColumn::class => [
+                'type' => 'DECIMAL',
+                'args' => [10, 5],
+            ],
+            FloatColumn::class => [
+                'type' => 'FLOAT',
+                'args' => [],
+            ],
+            DateColumn::class => [
+                'type' => 'DATE',
+                'args' => [],
+            ],
+            DateTimeColumn::class => [
+                'type' => 'DATETIME',
+                'args' => [],
+            ],
+            BoolColumn::class => [
+                'type' => 'BOOLEAN',
+                'args' => [],
+            ],
+            TextColumn::class => [
+                'type' => 'TEXT',
+                'args' => [],
+            ],
+            JsonColumn::class => [
+                'type' => 'JSON',
+                'args' => [],
+            ],
+            StringColumn::class => [
+                'type' => 'VARCHAR',
+                'args' => [255],
+            ],
         ];
     }
 
+    public function convertForeignKeyRuleStringToCode(?string $rule): int
+    {
+        $rule = strtoupper($rule);
+        switch ($rule) {
+            case 'CASCADE':
+                return ForeignKeyMetadata::CASCADE;
+            case 'SET NULL':
+                return ForeignKeyMetadata::SET_NULL;
+            case 'SET DEFAULT':
+                return ForeignKeyMetadata::SET_DEFAULT;
+            case 'RESTRICT':
+                return ForeignKeyMetadata::RESTRICT;
+            case 'NO ACTION':   /* fall-through */
+            default:
+                return ForeignKeyMetadata::NO_ACTION;
+        }
+    }
+
     public function getSchema(): SchemaInterface
     {
         return $this->schema;

+ 3 - 2
src/Query/QueryBuilder.php

@@ -5,6 +5,7 @@ namespace PhpDevCommunity\PaperORM\Query;
 use InvalidArgumentException;
 use LogicException;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 use PhpDevCommunity\PaperORM\Hydrator\ArrayHydrator;
 use PhpDevCommunity\PaperORM\Hydrator\EntityHydrator;
 use PhpDevCommunity\PaperORM\Mapper\ColumnMapper;
@@ -16,7 +17,7 @@ use PhpDevCommunity\Sql\QL\JoinQL;
 
 final class QueryBuilder
 {
-    private EntityManager $em;
+    private EntityManagerInterface $em;
 
     private string $primaryKey;
 
@@ -30,7 +31,7 @@ final class QueryBuilder
 
     private ?int $maxResults = null;
 
-    public function __construct(EntityManager $em, string $primaryKey = 'id')
+    public function __construct(EntityManagerInterface $em, string $primaryKey = 'id')
     {
         $this->em = $em;
         $this->aliasGenerator = new AliasGenerator();

+ 3 - 3
src/Repository/Repository.php

@@ -5,6 +5,7 @@ namespace PhpDevCommunity\PaperORM\Repository;
 use LogicException;
 use PhpDevCommunity\PaperORM\Entity\EntityInterface;
 use PhpDevCommunity\PaperORM\EntityManager;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
 use PhpDevCommunity\PaperORM\Expression\Expr;
 use PhpDevCommunity\PaperORM\Hydrator\EntityHydrator;
 use PhpDevCommunity\PaperORM\Mapper\ColumnMapper;
@@ -16,10 +17,9 @@ use PhpDevCommunity\PaperORM\Serializer\SerializerToDb;
 
 abstract class Repository
 {
+    private EntityManagerInterface $em;
 
-    private EntityManager $em;
-
-    public function __construct(EntityManager $em)
+    public function __construct(EntityManagerInterface $em)
     {
         $this->em = $em;
     }

+ 283 - 0
src/Schema/MariaDBSchema.php

@@ -0,0 +1,283 @@
+<?php
+
+namespace PhpDevCommunity\PaperORM\Schema;
+
+use LogicException;
+use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
+use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
+use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
+
+class MariaDBSchema implements SchemaInterface
+{
+
+    public function showDatabases(): string
+    {
+        return  "SHOW DATABASES";
+    }
+
+    public function showTables(): string
+    {
+        return "SHOW TABLES";
+    }
+
+    public function showTableColumns(string $tableName): string
+    {
+        return sprintf("SHOW COLUMNS FROM %s", $tableName);
+    }
+
+    public function showForeignKeys(string $tableName): string
+    {
+        return trim(sprintf(
+            <<<SQL
+            SELECT DISTINCT
+                k.CONSTRAINT_NAME,
+                k.COLUMN_NAME,
+                k.REFERENCED_TABLE_NAME,
+                k.REFERENCED_COLUMN_NAME,
+                k.ORDINAL_POSITION /*!50116,
+                c.UPDATE_RULE,
+                c.DELETE_RULE */
+            FROM information_schema.key_column_usage k /*!50116
+            INNER JOIN information_schema.referential_constraints c
+                ON c.CONSTRAINT_NAME = k.CONSTRAINT_NAME
+                AND c.TABLE_NAME = k.TABLE_NAME */
+            WHERE k.TABLE_SCHEMA = DATABASE()
+              AND k.TABLE_NAME = "%s"
+              AND k.REFERENCED_COLUMN_NAME IS NOT NULL
+              /*!50116 AND c.CONSTRAINT_SCHEMA = DATABASE() */
+            ORDER BY k.ORDINAL_POSITION
+            SQL,
+            $tableName
+        ));
+    }
+
+    public function showTableIndexes(string $tableName): string
+    {
+        return sprintf('SHOW INDEXES FROM %s', $tableName);
+    }
+
+    public function createDatabase(string $databaseName): string
+    {
+        return sprintf('CREATE DATABASE %s', $databaseName);
+    }
+
+    public function createDatabaseIfNotExists(string $databaseName): string
+    {
+        return  sprintf('CREATE DATABASE IF NOT EXISTS %s', $databaseName);
+    }
+
+    public function dropDatabase(string $databaseName): string
+    {
+        return sprintf('DROP DATABASE %s', $databaseName);
+    }
+
+    /**
+     * @param string $tableName
+     * @param array<ColumnMetadata> $columns
+     * @param array $options
+     * @return string
+     */
+    public function createTable(string $tableName, array $columns, array $options = []): string
+    {
+        $lines = [];
+        foreach ($columns as $columnMetadata) {
+            $line = sprintf('%s %s', $columnMetadata->getName(), $columnMetadata->getTypeWithAttributes());
+            if ($columnMetadata->isPrimary()) {
+                $line .= ' AUTO_INCREMENT PRIMARY KEY NOT NULL';
+            }else {
+                if (!$columnMetadata->isNullable()) {
+                    $line .= ' NOT NULL';
+                }
+                if ($columnMetadata->getDefaultValue() !== null) {
+                    $line .= sprintf(' DEFAULT %s', $columnMetadata->getDefaultValuePrintable());
+                }elseif ($columnMetadata->isNullable()) {
+                    $line .= ' DEFAULT NULL';
+                }
+            }
+
+            $lines[] = $line;
+        }
+
+
+        $linesString = implode(',', $lines);
+        $createTable = sprintf("CREATE TABLE $tableName (%s)", $linesString);
+
+        $indexesSql = [];
+        $options['indexes'] = $options['indexes'] ?? [];
+        foreach ($options['indexes'] as $index) {
+            $indexesSql[] = $this->createIndex($index);
+        }
+
+        return $createTable.';'.implode(';', $indexesSql);
+    }
+
+    public function createTableIfNotExists(string $tableName, array $columns, array $options = []): string
+    {
+        $createTable = $this->createTable($tableName, $columns, $options);
+        return str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $createTable);
+    }
+
+    public function createForeignKeyConstraint(string $tableName, ForeignKeyMetadata $foreignKey): string
+    {
+        $sql = [];
+
+        if (empty($foreignKey->getName())) {
+            throw new LogicException(sprintf('The foreign key name can not be empty : table %s, columns %s', $tableName, implode(', ', $foreignKey->getColumns())));
+        }
+
+        $sql[] = sprintf('ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s(%s)',
+            $tableName,
+            $foreignKey->getName(),
+            implode(', ', $foreignKey->getColumns()),
+            $foreignKey->getReferenceTable(),
+            implode(', ', $foreignKey->getReferenceColumns())
+        );
+
+        switch ($foreignKey->getOnDelete()) {
+            case ForeignKeyMetadata::RESTRICT:
+                $sql[] = 'ON DELETE RESTRICT';
+                break;
+            case ForeignKeyMetadata::CASCADE:
+                $sql[] = 'ON DELETE CASCADE';
+                break;
+            case ForeignKeyMetadata::SET_NULL:
+                $sql[] = 'ON DELETE SET NULL';
+                break;
+            case ForeignKeyMetadata::NO_ACTION:
+                $sql[] = 'ON DELETE NO ACTION';
+                break;
+        }
+
+        switch ($foreignKey->getOnUpdate()) {
+            case ForeignKeyMetadata::RESTRICT:
+                $sql[] = 'ON UPDATE RESTRICT';
+                break;
+            case ForeignKeyMetadata::CASCADE:
+                $sql[] = 'ON UPDATE CASCADE';
+                break;
+            case ForeignKeyMetadata::SET_NULL:
+                $sql[] = 'ON UPDATE SET NULL';
+                break;
+            case ForeignKeyMetadata::NO_ACTION:
+                $sql[] = 'ON UPDATE NO ACTION';
+                break;
+        }
+
+        return implode(' ', $sql).';';
+    }
+
+    public function dropForeignKeyConstraints( string $tableName, string $foreignKeyName): string
+    {
+        return sprintf('ALTER TABLE %s DROP FOREIGN KEY %s', $tableName, $foreignKeyName);
+    }
+
+    public function dropTable(string $tableName): string
+    {
+        return sprintf('DROP TABLE %s', $tableName);
+    }
+
+    public function renameTable(string $oldTableName, string $newTableName): string
+    {
+        return sprintf('ALTER TABLE %s RENAME TO %s', $oldTableName, $newTableName);
+    }
+
+    public function addColumn(string $tableName, ColumnMetadata $columnMetadata): string
+    {
+        $sql =  sprintf('ALTER TABLE %s ADD COLUMN %s %s', $tableName, $columnMetadata->getName(), $columnMetadata->getTypeWithAttributes());
+        if (!$columnMetadata->isNullable()) {
+            $sql .= ' NOT NULL';
+        }
+
+        if ($columnMetadata->getDefaultValue() !== null) {
+            $sql .= sprintf(' DEFAULT %s', $columnMetadata->getDefaultValuePrintable());
+        }elseif ($columnMetadata->isNullable()) {
+            $sql .= ' DEFAULT NULL';
+        }
+
+        return $sql;
+    }
+
+    public function dropColumn(string $tableName, ColumnMetadata $columnMetadata): string
+    {
+        return sprintf('ALTER TABLE %s DROP COLUMN %s', $tableName, $columnMetadata->getName());
+    }
+
+    public function renameColumn(string $tableName, string $oldColumnName, string $newColumnName): string
+    {
+        return sprintf('ALTER TABLE %s RENAME COLUMN %s to %s', $tableName, $oldColumnName, $newColumnName);
+    }
+
+    public function modifyColumn(string $tableName, ColumnMetadata $columnMetadata): string
+    {
+        $sql = $this->addColumn($tableName, $columnMetadata);
+        return  str_replace('ADD COLUMN', 'MODIFY COLUMN', $sql);
+    }
+
+    /**
+     * @param IndexMetadata $indexMetadata
+     * @return string
+     */
+    public function createIndex(IndexMetadata $indexMetadata): string
+    {
+        $indexType = $indexMetadata->isUnique() ? 'CREATE UNIQUE INDEX' : 'CREATE INDEX';
+        return  sprintf('%s %s ON %s (%s)',
+            $indexType,
+            $indexMetadata->getName(),
+            $indexMetadata->getTableName(),
+            implode(', ', $indexMetadata->getColumns())
+        );
+    }
+
+    public function dropIndex(IndexMetadata $indexMetadata): string
+    {
+        return sprintf('DROP INDEX %s ON %s;', $indexMetadata->getName(), $indexMetadata->getTableName());
+    }
+
+
+    public function getDateTimeFormatString(): string
+    {
+        return 'Y-m-d H:i:s';
+    }
+
+    public function getDateFormatString(): string
+    {
+        return 'Y-m-d';
+    }
+
+
+    public function supportsForeignKeyConstraints(): bool
+    {
+       return true;
+    }
+
+    public function supportsIndexes(): bool
+    {
+        return true;
+    }
+
+    public function supportsTransactions(): bool
+    {
+        return true;
+    }
+
+    public function supportsDropColumn(): bool
+    {
+        return true;
+    }
+
+    public function supportsModifyColumn(): bool
+    {
+        return true;
+    }
+
+    public function supportsAddForeignKey(): bool
+    {
+        return true;
+    }
+
+    public function supportsDropForeignKey(): bool
+    {
+        return true;
+    }
+}

+ 15 - 2
src/Schema/SchemaInterface.php

@@ -2,6 +2,8 @@
 
 namespace PhpDevCommunity\PaperORM\Schema;
 
+use PhpDevCommunity\PaperORM\Mapping\Column\Column;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
 use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
 use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
 
@@ -80,10 +82,19 @@ interface SchemaInterface
      * Adds a new foreign key constraint.
      *
      * @param string $tableName The name of the table to modify.
-     * @param ColumnMetadata $columnMetadata
+     * @param ForeignKeyMetadata $foreignKey The instance of the foreign key.
      * @return string Returns the SQL query for adding the foreign key constraint.
      */
-    public function createForeignKeyConstraints(string $tableName, ColumnMetadata $columnMetadata) :string;
+    public function createForeignKeyConstraint(string $tableName, ForeignKeyMetadata $foreignKey) :string;
+
+    /**
+     * Drops an existing foreign key constraint.
+     *
+     * @param string $tableName The name of the table to modify.
+     * @param string $foreignKeyName The name of the foreign key to drop.
+     * @return string Returns the SQL query for dropping the foreign key constraint.
+     */
+    public function dropForeignKeyConstraints( string $tableName, string $foreignKeyName): string;
 
     /**
      * Drops an existing table.
@@ -201,4 +212,6 @@ interface SchemaInterface
      */
     public function supportsAddForeignKey(): bool;
 
+    public function supportsDropForeignKey(): bool;
+
 }

+ 65 - 17
src/Schema/SqliteSchema.php

@@ -3,8 +3,10 @@
 namespace PhpDevCommunity\PaperORM\Schema;
 
 use LogicException;
+use PhpDevCommunity\PaperORM\Metadata\ForeignKeyMetadata;
 use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
 use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
+use SQLite3;
 
 class SqliteSchema implements SchemaInterface
 {
@@ -68,12 +70,12 @@ class SqliteSchema implements SchemaInterface
                 $line .= ' NOT NULL';
             }
             if ($columnMetadata->getDefaultValue() !== null) {
-                $line .= sprintf(' DEFAULT %s', $columnMetadata->getDefaultValue());
+                $line .= sprintf(' DEFAULT %s', $columnMetadata->getDefaultValuePrintable());
             }
             $lines[] = $line;
 
             if (!empty($columnMetadata->getForeignKeyMetadata())) {
-                $foreignKeys[] = $columnMetadata;
+                $foreignKeys[] = $columnMetadata->getForeignKeyMetadata();
             }
         }
 
@@ -88,10 +90,10 @@ class SqliteSchema implements SchemaInterface
 
         $indexesSql = [];
         foreach ($options['indexes'] as $index) {
-            $createTable .= $this->createIndex($index);
+            $indexesSql[] = $this->createIndex($index);
         }
 
-        return $createTable.';'.implode(';', $indexesSql);
+        return $createTable . ';' . implode(';', $indexesSql);
     }
 
     public function createTableIfNotExists(string $tableName, array $columns, array $options = []): string
@@ -100,7 +102,12 @@ class SqliteSchema implements SchemaInterface
         return str_replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $createTable);
     }
 
-    public function createForeignKeyConstraints(string $tableName, ColumnMetadata $columnMetadata): string
+    public function createForeignKeyConstraint(string $tableName, ForeignKeyMetadata $foreignKey): string
+    {
+        throw new LogicException(sprintf("The method '%s' is not supported by the schema interface.", __METHOD__));
+    }
+
+    public function dropForeignKeyConstraints(string $tableName, string $foreignKeyName): string
     {
         throw new LogicException(sprintf("The method '%s' is not supported by the schema interface.", __METHOD__));
     }
@@ -117,14 +124,14 @@ class SqliteSchema implements SchemaInterface
 
     public function addColumn(string $tableName, ColumnMetadata $columnMetadata): string
     {
-        $sql =  sprintf('ALTER TABLE %s ADD %s %s', $tableName, $columnMetadata->getName(), $columnMetadata->getTypeWithAttributes());
+        $sql = sprintf('ALTER TABLE %s ADD %s %s', $tableName, $columnMetadata->getName(), $columnMetadata->getTypeWithAttributes());
 
         if (!$columnMetadata->isNullable()) {
             $sql .= ' NOT NULL';
         }
 
         if ($columnMetadata->getDefaultValue() !== null) {
-            $sql .= sprintf(' DEFAULT %s', $columnMetadata->getDefaultValue());
+            $sql .= sprintf(' DEFAULT %s', $columnMetadata->getDefaultValuePrintable());
         }
 
         return $sql;
@@ -133,7 +140,7 @@ class SqliteSchema implements SchemaInterface
     public function dropColumn(string $tableName, ColumnMetadata $columnMetadata): string
     {
         if (!$this->supportsDropColumn()) {
-            throw new \LogicException(sprintf("The method '%s' is not supported with SQLite versions older than 3.35.0.", __METHOD__));
+            throw new LogicException(sprintf("The method '%s' is not supported with SQLite versions older than 3.35.0.", __METHOD__));
         }
         return sprintf('ALTER TABLE %s DROP COLUMN %s', $tableName, $columnMetadata->getName());
     }
@@ -177,21 +184,55 @@ class SqliteSchema implements SchemaInterface
         return 'Y-m-d';
     }
 
-    private function foreignKeyConstraints(ColumnMetadata $columnMetadata): string
+    private function foreignKeyConstraints(ForeignKeyMetadata $foreignKey): string
     {
-        $foreignKeys = $columnMetadata->getForeignKeyMetadata();
-        if (empty($foreignKeys)) {
-            return '';
+        $referencedTable = $foreignKey->getReferenceTable();
+        $referencedColumns = $foreignKey->getReferenceColumns();
+        $sql = [];
+        $sql[] = sprintf('FOREIGN KEY (%s) REFERENCES %s (%s)', implode(', ', $foreignKey->getColumns()), $referencedTable, implode(', ', $referencedColumns));
+
+        switch ($foreignKey->getOnDelete()) {
+            case ForeignKeyMetadata::RESTRICT:
+                $sql[] = 'ON DELETE RESTRICT';
+                break;
+            case ForeignKeyMetadata::CASCADE:
+                $sql[] = 'ON DELETE CASCADE';
+                break;
+            case ForeignKeyMetadata::SET_DEFAULT:
+                $sql[] = 'ON DELETE SET DEFAULT';
+                break;
+            case ForeignKeyMetadata::SET_NULL:
+                $sql[] = 'ON DELETE SET NULL';
+                break;
+            case ForeignKeyMetadata::NO_ACTION:
+                $sql[] = 'ON DELETE NO ACTION';
+                break;
         }
-        $referencedTable = $foreignKeys['referencedTable'];
-        $referencedColumn = $foreignKeys['referencedColumn'];
 
-        return sprintf('FOREIGN KEY (%s) REFERENCES %s (%s)', $columnMetadata->getName(), $referencedTable, $referencedColumn);
+        switch ($foreignKey->getOnUpdate()) {
+            case ForeignKeyMetadata::RESTRICT:
+                $sql[] = 'ON UPDATE RESTRICT';
+                break;
+            case ForeignKeyMetadata::CASCADE:
+                $sql[] = 'ON UPDATE CASCADE';
+                break;
+            case ForeignKeyMetadata::SET_DEFAULT:
+                $sql[] = 'ON UPDATE SET DEFAULT';
+                break;
+            case ForeignKeyMetadata::SET_NULL:
+                $sql[] = 'ON UPDATE SET NULL';
+                break;
+            case ForeignKeyMetadata::NO_ACTION:
+                $sql[] = 'ON UPDATE NO ACTION';
+                break;
+        }
+
+        return implode(' ', $sql);
     }
 
     public function supportsForeignKeyConstraints(): bool
     {
-       return true;
+        return true;
     }
 
     public function supportsIndexes(): bool
@@ -206,7 +247,7 @@ class SqliteSchema implements SchemaInterface
 
     public function supportsDropColumn(): bool
     {
-        return \SQLite3::version()['versionString'] >= '3.35.0';
+        return SQLite3::version()['versionString'] >= '3.35.0';
     }
 
     public function supportsModifyColumn(): bool
@@ -218,4 +259,11 @@ class SqliteSchema implements SchemaInterface
     {
         return false;
     }
+
+
+    public function supportsDropForeignKey(): bool
+    {
+        return false;
+    }
+
 }

+ 16 - 26
tests/Common/OrmTestMemory.php

@@ -9,45 +9,35 @@ use Test\PhpDevCommunity\PaperORM\Helper\DataBaseHelperTest;
 
 class OrmTestMemory extends TestCase
 {
-    private EntityManager $em;
 
     protected function setUp(): void
     {
-        $this->em = new EntityManager([
-            'driver' => 'sqlite',
-            'user' => null,
-            'password' => null,
-            'memory' => true,
-            'debug' => false
-        ]);
-        $this->setUpDatabaseSchema();
     }
 
-    protected function setUpDatabaseSchema(): void
-    {
-        DataBaseHelperTest::init($this->em, 10000);
-    }
 
     protected function tearDown(): void
     {
-        $this->em->getConnection()->close();
     }
 
 
     protected function execute(): void
     {
-        $memory = memory_get_usage();
-        $users = $this->em->getRepository(UserTest::class)
-            ->findBy()
-            ->toObject()
-        ;
-        $this->assertStrictEquals(10000, count($users));
-        foreach ($users as $user) {
-            $this->assertInstanceOf(UserTest::class, $user);
-            $this->assertNotEmpty($user);
+        foreach (DataBaseHelperTest::drivers() as  $params) {
+            $em = new EntityManager($params);
+            DataBaseHelperTest::init($em, 10, false);
+            $memory = memory_get_usage();
+            $users = $em->getRepository(UserTest::class)
+                ->findBy()
+                ->toObject();
+            $this->assertStrictEquals(10, count($users));
+            foreach ($users as $user) {
+                $this->assertInstanceOf(UserTest::class, $user);
+                $this->assertNotEmpty($user);
+            }
+            $memory = memory_get_usage(true) - $memory;
+            $memory = ceil($memory / 1024 / 1024);
+            $this->assertTrue( $memory <= 30 );
+            $em->getConnection()->close();
         }
-        $memory = memory_get_usage(true) - $memory;
-        $memory = ceil($memory / 1024 / 1024);
-        $this->assertTrue( $memory <= 30 );
     }
 }

+ 18 - 11
tests/DatabaseShowTablesCommandTest.php

@@ -14,30 +14,36 @@ use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\UniTester\TestCase;
 use Test\PhpDevCommunity\PaperORM\Entity\UserTest;
+use Test\PhpDevCommunity\PaperORM\Helper\DataBaseHelperTest;
 
 class DatabaseShowTablesCommandTest extends TestCase
 {
-    private EntityManager $em;
 
     protected function setUp(): void
     {
-        $this->em = new EntityManager([
-            'driver' => 'sqlite',
-            'user' => null,
-            'password' => null,
-            'memory' => true,
-        ]);
 
     }
 
     protected function tearDown(): void
     {
-        $this->em->getConnection()->close();
     }
 
     protected function execute(): void
     {
-        $platform = $this->em->createDatabasePlatform();
+        foreach (DataBaseHelperTest::drivers() as $params) {
+            $em = new EntityManager($params);
+            $this->executeTest($em);
+            $em->getConnection()->close();
+        }
+    }
+
+    private function executeTest(EntityManager $em)
+    {
+
+        $platform = $em->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
         $platform->createTable('user', [
             new PrimaryKeyColumn('id'),
             new StringColumn('firstname'),
@@ -49,17 +55,18 @@ class DatabaseShowTablesCommandTest extends TestCase
 
         $platform->createTable('post', [
             new PrimaryKeyColumn('id'),
-            new JoinColumn('user_id', 'id', UserTest::class),
+            new JoinColumn('user_id', UserTest::class),
             new StringColumn('title'),
             new StringColumn('content'),
         ]);
 
         $runner = new CommandRunner([
-            new ShowTablesCommand($this->em)
+            new ShowTablesCommand($em)
         ]);
 
         $code = $runner->run(new CommandParser(['', 'paper:show:tables', '--columns']), new Output(function ($message) use(&$countMessages) {
         }));
         $this->assertEquals(0, $code);
+
     }
 }

+ 1 - 1
tests/Entity/CommentTest.php

@@ -33,7 +33,7 @@ class CommentTest implements EntityInterface, TableMetadataInterface
         return [
             (new PrimaryKeyColumn())->bindProperty('id'),
             (new StringColumn())->bindProperty('body'),
-            (new JoinColumn('post_id', 'id', PostTest::class))->bindProperty('post'),
+            (new JoinColumn('post_id', PostTest::class))->bindProperty('post'),
         ];
     }
 

+ 2 - 2
tests/Entity/PostTest.php

@@ -29,7 +29,7 @@ class PostTest implements EntityInterface
     #[DateTimeColumn(name: 'created_at')]
     private ?DateTime $createdAt = null;
 
-    #[JoinColumn(name: 'user_id', referencedColumnName: 'id', targetEntity:  UserTest::class)]
+    #[JoinColumn(name: 'user_id', targetEntity:  UserTest::class, nullable: true, unique: false, onDelete: JoinColumn::SET_NULL)]
     private ?UserTest $user = null;
 
     #[OneToMany(targetEntity: TagTest::class, mappedBy: 'post')]
@@ -63,7 +63,7 @@ class PostTest implements EntityInterface
             (new StringColumn())->bindProperty('title'),
             (new StringColumn())->bindProperty('content'),
             (new DateTimeColumn( 'created_at'))->bindProperty('createdAt'),
-            (new JoinColumn('user_id', 'id', UserTest::class))->bindProperty('user'),
+            (new JoinColumn('user_id', UserTest::class, 'id', true, false, JoinColumn::SET_NULL))->bindProperty('user'),
             (new OneToMany( TagTest::class, 'post'))->bindProperty('tags'),
             (new OneToMany( CommentTest::class, 'post'))->bindProperty('comments'),
         ];

+ 2 - 2
tests/Entity/TagTest.php

@@ -19,7 +19,7 @@ class TagTest implements EntityInterface
     #[StringColumn]
     private ?string $name = null;
 
-    #[JoinColumn(name : 'post_id', referencedColumnName : 'id', targetEntity : PostTest::class)]
+    #[JoinColumn(name : 'post_id', targetEntity : PostTest::class)]
     private ?PostTest $post = null;
 
     static public function getTableName(): string
@@ -40,7 +40,7 @@ class TagTest implements EntityInterface
         return [
             (new PrimaryKeyColumn())->bindProperty('id'),
             (new StringColumn())->bindProperty('name'),
-            (new JoinColumn( 'post_id', 'id', PostTest::class))->bindProperty('post'),
+            (new JoinColumn( 'post_id', PostTest::class))->bindProperty('post'),
         ];
     }
 

+ 2 - 2
tests/Entity/UserTest.php

@@ -40,7 +40,7 @@ class UserTest implements EntityInterface
     #[OneToMany(targetEntity: PostTest::class, mappedBy: 'user')]
     private ObjectStorage $posts;
 
-    #[JoinColumn(name: 'last_post_id', referencedColumnName: 'id', targetEntity: PostTest::class)]
+    #[JoinColumn(name: 'last_post_id', targetEntity: PostTest::class, nullable: true, unique: true, onDelete: JoinColumn::SET_NULL)]
     private ?PostTest $lastPost = null;
     public function __construct()
     {
@@ -72,7 +72,7 @@ class UserTest implements EntityInterface
             (new BoolColumn( 'is_active'))->bindProperty('active'),
             (new DateTimeColumn( 'created_at'))->bindProperty('createdAt'),
             (new OneToMany( PostTest::class,  'user'))->bindProperty('posts'),
-            (new JoinColumn( 'last_post_id', 'id', PostTest::class))->bindProperty('lastPost'),
+            (new JoinColumn( 'last_post_id', PostTest::class, 'id', true, true, JoinColumn::SET_NULL))->bindProperty('lastPost'),
         ];
     }
 

+ 125 - 81
tests/Helper/DataBaseHelperTest.php

@@ -2,68 +2,110 @@
 
 namespace Test\PhpDevCommunity\PaperORM\Helper;
 
-use PhpDevCommunity\PaperORM\EntityManager;
+use DateTime;
+use PhpDevCommunity\PaperORM\EntityManagerInterface;
+use PhpDevCommunity\PaperORM\Generator\SchemaDiffGenerator;
 use PhpDevCommunity\PaperORM\Mapping\Column\BoolColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\DateTimeColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\JoinColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
-use PhpDevCommunity\PaperORM\Metadata\IndexMetadata;
-use PhpDevCommunity\PaperORM\PaperConnection;
 use Test\PhpDevCommunity\PaperORM\Entity\PostTest;
 use Test\PhpDevCommunity\PaperORM\Entity\UserTest;
 
 class DataBaseHelperTest
 {
 
-    public static function init(EntityManager $entityManager, int $nbUsers = 5)
+    public static function drivers(): array
     {
-        $connection = $entityManager->getConnection();
-        $connection->close();
-        $connection->connect();
-        $platform = $entityManager->createDatabasePlatform();
-        $platform->createTable('user', [
+        return [
+            'sqlite' => [
+                'driver' => 'sqlite',
+                'user' => null,
+                'password' => null,
+                'memory' => true,
+            ],
+            'mariadb' => [
+                'driver' => 'mariadb',
+                'host' => getenv('MARIADB_HOST') ?: '127.0.0.1',
+                'port' => (int)(getenv('MARIADB_PORT') ?: 3306),
+                'dbname' => getenv('MARIADB_DB') ?: 'paper_orm_test',
+                'user' => getenv('MARIADB_USER') ?: 'root',
+                'password' => getenv('MARIADB_PASSWORD') ?: '',
+                'charset' => 'utf8mb4',
+            ],
+        ];
+    }
+    public static function init(EntityManagerInterface $entityManager, int $nbUsers = 5, bool $withPosts = true)
+    {
+        $userColumns = [
             new PrimaryKeyColumn('id'),
-            (new JoinColumn( 'last_post_id', 'id', PostTest::class, true)),
+            (new JoinColumn('last_post_id', PostTest::class, 'id', true, true)),
             new StringColumn('firstname'),
             new StringColumn('lastname'),
             new StringColumn('email'),
             new StringColumn('password'),
             new BoolColumn('is_active'),
             new DateTimeColumn('created_at', true),
-        ]);
-
-        $platform->createTable('post', [
+        ];
+        $postColumns = [
             new PrimaryKeyColumn('id'),
-            ( new JoinColumn( 'user_id', 'id', UserTest::class, false)),
+            (new JoinColumn('user_id', UserTest::class, 'id', true, false, JoinColumn::SET_NULL)),
             new StringColumn('title'),
             new StringColumn('content'),
             new DateTimeColumn('created_at', true),
-        ]);
-
-        $platform->createIndex(new IndexMetadata('post', 'idx_post_user_id', ['user_id']));
+        ];
 
-        $platform->createTable('tag', [
+        $tagColumns = [
             new PrimaryKeyColumn('id'),
-            (new JoinColumn('post_id', 'id', PostTest::class)),
+            (new JoinColumn('post_id', PostTest::class, 'id', true, false, JoinColumn::SET_NULL)),
             new StringColumn('name'),
-        ]);
-
-
-        $platform->createTable('comment', [
+        ];
+        $commentColumns = [
             new PrimaryKeyColumn('id'),
-            (new JoinColumn('post_id', 'id', PostTest::class)),
+            (new JoinColumn('post_id', PostTest::class, 'id', true, false, JoinColumn::SET_NULL)),
             new StringColumn('body'),
-        ]);
+        ];
+        $platform = $entityManager->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
+        $statements = (new SchemaDiffGenerator($platform))->generateDiffStatements([
+                'user' => [
+                    'columns' => $userColumns,
+                    'indexes' => [],
+                ],
+                'post' => [
+                    'columns' => $postColumns,
+                    'indexes' => [],
+                ],
+                'tag' => [
+                    'columns' => $tagColumns,
+                    'indexes' => [],
+                ],
+                'comment' => [
+                    'columns' => $commentColumns,
+                    'indexes' => [],
+                ],
+            ]
+        );
+
 
-        for ($i = 0; $i <$nbUsers; $i++) {
+        $connection = $entityManager->getConnection();
+        $connection->close();
+        $connection->connect();
+        foreach ($statements['up'] as $statement) {
+            $connection->executeStatement($statement);
+        }
+
+        for ($i = 0; $i < $nbUsers; $i++) {
             $user = [
                 'firstname' => 'John' . $i,
                 'lastname' => 'Doe' . $i,
                 'email' => $i . 'bqQpB@example.com',
                 'password' => 'password123',
                 'is_active' => true,
-                'created_at' => (new \DateTime())->format($platform->getSchema()->getDateTimeFormatString()),
+                'created_at' => (new DateTime())->format($platform->getSchema()->getDateTimeFormatString()),
             ];
 
             $stmt = $connection->getPdo()->prepare("INSERT INTO user (firstname, lastname, email, password, is_active, created_at) VALUES (:firstname, :lastname, :email, :password, :is_active, :created_at)");
@@ -77,80 +119,82 @@ class DataBaseHelperTest
             ]);
         }
 //
-        $nbPosts = $nbUsers - 1;
-        for ($i = 0; $i < $nbPosts; $i++) {
-            $id = uniqid('post_', true);
-            $post = [
-                'user_id' => $i + 1,
-                'title' => 'Post ' . $id,
-                'content' => 'Content ' . $id,
-                'created_at' => (new \DateTime())->format($platform->getSchema()->getDateTimeFormatString()),
-            ];
-
-            $stmt = $connection->getPdo()->prepare("INSERT INTO post (user_id, title, content, created_at)  VALUES (:user_id, :title, :content, :created_at)");
-            $stmt->execute([
-                'user_id' => $post['user_id'],
-                'title' => $post['title'],
-                'content' => $post['content'],
-                'created_at' => $post['created_at']
-            ]);
-            $id = uniqid('post_', true);
-            $post = [
-                'user_id' => $i + 1,
-                'title' => 'Post ' . $id,
-                'content' => 'Content ' . $id,
-            ];
-            $connection->executeStatement("INSERT INTO post (user_id, title, content) VALUES (
+        if ($withPosts) {
+            $nbPosts = $nbUsers - 1;
+            for ($i = 0; $i < $nbPosts; $i++) {
+                $id = uniqid('post_', true);
+                $post = [
+                    'user_id' => $i + 1,
+                    'title' => 'Post ' . $id,
+                    'content' => 'Content ' . $id,
+                    'created_at' => (new DateTime())->format($platform->getSchema()->getDateTimeFormatString()),
+                ];
+
+                $stmt = $connection->getPdo()->prepare("INSERT INTO post (user_id, title, content, created_at)  VALUES (:user_id, :title, :content, :created_at)");
+                $stmt->execute([
+                    'user_id' => $post['user_id'],
+                    'title' => $post['title'],
+                    'content' => $post['content'],
+                    'created_at' => $post['created_at']
+                ]);
+                $id = uniqid('post_', true);
+                $post = [
+                    'user_id' => $i + 1,
+                    'title' => 'Post ' . $id,
+                    'content' => 'Content ' . $id,
+                ];
+                $connection->executeStatement("INSERT INTO post (user_id, title, content) VALUES (
                 '{$post['user_id']}',
                 '{$post['title']}',
                 '{$post['content']}'
             )");
 
-            $lastId = $connection->getPdo()->lastInsertId();
-            $connection->executeStatement('UPDATE user SET last_post_id = ' . $lastId . ' WHERE id = ' . $post['user_id']);
-        }
-
-        $nbTags = $nbPosts * 2;
-        for ($i = 0; $i < $nbTags; $i++) {
-            $id = uniqid('tag_', true);
-            $tag = [
-                'post_id' => $i + 1,
-                'name' => 'Tag ' . $id,
-            ];
-            $connection->executeStatement("INSERT INTO tag (post_id, name) VALUES (
+                $lastId = $connection->getPdo()->lastInsertId();
+                $connection->executeStatement('UPDATE user SET last_post_id = ' . $lastId . ' WHERE id = ' . $post['user_id']);
+            }
+            $nbTags = $nbPosts * 2;
+            for ($i = 0; $i < $nbTags; $i++) {
+                $id = uniqid('tag_', true);
+                $tag = [
+                    'post_id' => $i + 1,
+                    'name' => 'Tag ' . $id,
+                ];
+                $connection->executeStatement("INSERT INTO tag (post_id, name) VALUES (
                 '{$tag['post_id']}',
                 '{$tag['name']}      '
             )");
 
-            $id = uniqid('tag_', true);
-            $tag = [
-                'post_id' => $i + 1,
-                'name' => 'Tag ' . $id,
-            ];
-            $connection->executeStatement("INSERT INTO tag (post_id, name) VALUES (
+                $id = uniqid('tag_', true);
+                $tag = [
+                    'post_id' => $i + 1,
+                    'name' => 'Tag ' . $id,
+                ];
+                $connection->executeStatement("INSERT INTO tag (post_id, name) VALUES (
                 '{$tag['post_id']}',
                 '{$tag['name']}      '
             )");
-        }
-
-        $nbComments = $nbTags - 1;
-        for ($i = 0; $i <$nbComments; $i++) {
-            $id = uniqid('comment_', true);
-            $comment = [
-                'post_id' => $i + 1,
-                'body' => 'Comment ' . $id,
-            ];
-            $connection->executeStatement("INSERT INTO comment (post_id, body) VALUES (
+            }
+            $nbComments = $nbTags - 1;
+            for ($i = 0; $i < $nbComments; $i++) {
+                $id = uniqid('comment_', true);
+                $comment = [
+                    'post_id' => $i + 1,
+                    'body' => 'Comment ' . $id,
+                ];
+                $connection->executeStatement("INSERT INTO comment (post_id, body) VALUES (
                 '{$comment['post_id']}',
                 '{$comment['body']}      '
             )");
 
-            $comment['body'] = 'Comment ' . $id . ' 2';
-            $connection->executeStatement("INSERT INTO comment (post_id, body) VALUES (
+                $comment['body'] = 'Comment ' . $id . ' 2';
+                $connection->executeStatement("INSERT INTO comment (post_id, body) VALUES (
                 '{$comment['post_id']}',
                 '{$comment['body']}      '
             )");
+            }
         }
+
+
     }
 
 }

+ 121 - 49
tests/MigrationTest.php

@@ -2,114 +2,186 @@
 
 namespace Test\PhpDevCommunity\PaperORM;
 
+use PhpDevCommunity\PaperORM\Driver\MariaDBDriver;
+use PhpDevCommunity\PaperORM\Driver\SqliteDriver;
 use PhpDevCommunity\PaperORM\EntityManager;
 use PhpDevCommunity\PaperORM\Mapper\ColumnMapper;
 use PhpDevCommunity\PaperORM\Mapping\Column\IntColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\Migration\PaperMigration;
 use PhpDevCommunity\UniTester\TestCase;
+use RuntimeException;
 use Test\PhpDevCommunity\PaperORM\Entity\PostTest;
 use Test\PhpDevCommunity\PaperORM\Entity\UserTest;
+use Test\PhpDevCommunity\PaperORM\Helper\DataBaseHelperTest;
 
 class MigrationTest extends TestCase
 {
-    private EntityManager $em;
     private string $migrationDir;
-    private PaperMigration $paperMigration;
+
     protected function setUp(): void
     {
-        $this->em = new EntityManager([
-            'driver' => 'sqlite',
-            'user' => null,
-            'password' => null,
-            'memory' => true,
-        ]);
         $this->migrationDir = __DIR__ . '/migrations';
-        $this->paperMigration = PaperMigration::create($this->em, 'mig_versions', $this->migrationDir);
-
+        $this->tearDown();
     }
+
     protected function tearDown(): void
     {
-        $this->em->getConnection()->close();
         $folder = $this->migrationDir;
         array_map('unlink', glob("$folder/*.*"));
     }
 
     protected function execute(): void
     {
-        $this->em->getConnection()->close();
-        $this->testDiff();
-        $this->testExecute();
-        $this->testColumnModification();
-        $this->testFailedMigration();
+        foreach (DataBaseHelperTest::drivers() as $params) {
+            $em = new EntityManager($params);
+            $paperMigration = PaperMigration::create($em, 'mig_versions', $this->migrationDir);
+            $platform = $em->createDatabasePlatform();
+            $platform->createDatabaseIfNotExists();
+            $platform->dropDatabase();
+            $platform->createDatabaseIfNotExists();
+            $this->testDiff($paperMigration);
+            $this->testExecute($paperMigration);
+            $this->testColumnModification($paperMigration);
+            $this->testFailedMigration($paperMigration);
+            $em->getConnection()->close();
+            $this->tearDown();
+        }
     }
 
-    private function testDiff() :   void
+
+    private function testDiff(PaperMigration $paperMigration): void
     {
-        $this->em->getConnection()->close();
-        $migrationFile = $this->paperMigration->diffEntities([
+        $em = $paperMigration->getEntityManager();
+        $driver = $em->getConnection()->getDriver();
+        $em->getConnection()->close();
+        $migrationFile = $paperMigration->diffEntities([
             UserTest::class,
             PostTest::class
         ]);
 
-        $this->assertStringContains(file_get_contents($migrationFile), '-- UP MIGRATION --');
-        $this->assertStringContains(file_get_contents($migrationFile), 'CREATE TABLE user (id INTEGER PRIMARY KEY NOT NULL,firstname VARCHAR(255) NOT NULL,lastname VARCHAR(255) NOT NULL,email VARCHAR(255) NOT NULL,password VARCHAR(255) NOT NULL,is_active BOOLEAN NOT NULL,created_at DATETIME NOT NULL,last_post_id INTEGER NOT NULL,FOREIGN KEY (last_post_id) REFERENCES post (id));');
-        $this->assertStringContains(file_get_contents($migrationFile), 'CREATE INDEX IX_8D93D6492D053F64 ON user (last_post_id);');
-        $this->assertStringContains(file_get_contents($migrationFile), 'CREATE TABLE post (id INTEGER PRIMARY KEY NOT NULL,title VARCHAR(255) NOT NULL,content VARCHAR(255) NOT NULL,created_at DATETIME NOT NULL,user_id INTEGER NOT NULL,FOREIGN KEY (user_id) REFERENCES user (id));');
-        $this->assertStringContains(file_get_contents($migrationFile), 'CREATE INDEX IX_5A8A6C8DA76ED395 ON post (user_id);');
+        switch (get_class($driver)) {
+            case SqliteDriver::class:
+                $lines = file($migrationFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+                $this->assertEquals($lines, array (
+                    0 => '-- UP MIGRATION --',
+                    1 => 'CREATE TABLE user (id INTEGER PRIMARY KEY NOT NULL,firstname VARCHAR(255) NOT NULL,lastname VARCHAR(255) NOT NULL,email VARCHAR(255) NOT NULL,password VARCHAR(255) NOT NULL,is_active BOOLEAN NOT NULL,created_at DATETIME NOT NULL,last_post_id INTEGER,FOREIGN KEY (last_post_id) REFERENCES post (id) ON DELETE SET NULL ON UPDATE NO ACTION);',
+                    2 => 'CREATE UNIQUE INDEX IX_8D93D6492D053F64 ON user (last_post_id);',
+                    3 => 'CREATE TABLE post (id INTEGER PRIMARY KEY NOT NULL,title VARCHAR(255) NOT NULL,content VARCHAR(255) NOT NULL,created_at DATETIME NOT NULL,user_id INTEGER,FOREIGN KEY (user_id) REFERENCES user (id) ON DELETE SET NULL ON UPDATE NO ACTION);',
+                    4 => 'CREATE INDEX IX_5A8A6C8DA76ED395 ON post (user_id);',
+                    5 => '-- DOWN MIGRATION --',
+                    6 => 'DROP INDEX IX_8D93D6492D053F64;',
+                    7 => 'DROP TABLE user;',
+                    8 => 'DROP INDEX IX_5A8A6C8DA76ED395;',
+                    9 => 'DROP TABLE post;',
+                ));
+                break;
+            case MariaDBDriver::class:
+                $lines = file($migrationFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+                $this->assertEquals($lines, array (
+                    0 => '-- UP MIGRATION --',
+                    1 => 'CREATE TABLE user (id INT(11) AUTO_INCREMENT PRIMARY KEY NOT NULL,firstname VARCHAR(255) NOT NULL,lastname VARCHAR(255) NOT NULL,email VARCHAR(255) NOT NULL,password VARCHAR(255) NOT NULL,is_active TINYINT(1) NOT NULL,created_at DATETIME NOT NULL,last_post_id INT(11) DEFAULT NULL);',
+                    2 => 'CREATE UNIQUE INDEX IX_8D93D6492D053F64 ON user (last_post_id);',
+                    3 => 'CREATE TABLE post (id INT(11) AUTO_INCREMENT PRIMARY KEY NOT NULL,title VARCHAR(255) NOT NULL,content VARCHAR(255) NOT NULL,created_at DATETIME NOT NULL,user_id INT(11) DEFAULT NULL);',
+                    4 => 'CREATE INDEX IX_5A8A6C8DA76ED395 ON post (user_id);',
+                    5 => 'ALTER TABLE user ADD CONSTRAINT FK_8D93D6492D053F64 FOREIGN KEY (last_post_id) REFERENCES post(id) ON DELETE SET NULL ON UPDATE NO ACTION;',
+                    6 => 'ALTER TABLE post ADD CONSTRAINT FK_5A8A6C8DA76ED395 FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE SET NULL ON UPDATE NO ACTION;',
+                    7 => '-- DOWN MIGRATION --',
+                    8 => 'ALTER TABLE user DROP FOREIGN KEY FK_8D93D6492D053F64;',
+                    9 => 'ALTER TABLE post DROP FOREIGN KEY FK_5A8A6C8DA76ED395;',
+                    10 => 'DROP INDEX IX_8D93D6492D053F64 ON user;',
+                    11 => 'DROP TABLE user;',
+                    12 => 'DROP INDEX IX_5A8A6C8DA76ED395 ON post;',
+                    13 => 'DROP TABLE post;',
+                ));
+                break;
+            default:
+                throw new RuntimeException(sprintf('Driver %s not supported', get_class($driver)));
+        }
 
-        $this->assertStringContains(file_get_contents($migrationFile), '-- DOWN MIGRATION --');
-        $this->assertStringContains(file_get_contents($migrationFile), 'DROP INDEX IX_8D93D6492D053F64;');
-        $this->assertStringContains(file_get_contents($migrationFile), 'DROP TABLE user;');
-        $this->assertStringContains(file_get_contents($migrationFile), 'DROP INDEX IX_5A8A6C8DA76ED395;');
-        $this->assertStringContains(file_get_contents($migrationFile), 'DROP TABLE post;');
-     }
+    }
 
-    private function testExecute(): void
+    private function testExecute(PaperMigration  $paperMigration): void
     {
-        $this->paperMigration->migrate();
-        $successList = $this->paperMigration->getSuccessList();
+        $paperMigration->migrate();
+        $successList = $paperMigration->getSuccessList();
         $this->assertTrue(count($successList) === 1);
 
-        $migrationFile = $this->paperMigration->diffEntities([UserTest::class]);
+        $migrationFile = $paperMigration->diffEntities([UserTest::class]);
         $this->assertNull($migrationFile);
     }
 
-    private function testColumnModification(): void
+    private function testColumnModification(PaperMigration  $paperMigration): void
     {
+        $em = $paperMigration->getEntityManager();
+        $driver = $em->getConnection()->getDriver();
+
         $userColumns = ColumnMapper::getColumns(UserTest::class);
-        $userColumns[3] = (new StringColumn( null, 255, true, null, true))->bindProperty('email');
-        $userColumns[] = (new IntColumn( null, false, 0))->bindProperty('childs');
-        $migrationFile = $this->paperMigration->diff([
+        $userColumns[3] = (new StringColumn(null, 255, true, null, true))->bindProperty('email');
+        $userColumns[] = (new IntColumn(null, false, 0))->bindProperty('childs');
+        $migrationFile = $paperMigration->diff([
             'user' => [
                 'columns' => $userColumns,
                 'indexes' => []
             ]
         ]);
-        $this->paperMigration->migrate();
-        $successList = $this->paperMigration->getSuccessList();
+        $paperMigration->migrate();
+        $successList = $paperMigration->getSuccessList();
         $this->assertTrue(count($successList) === 1);
         $this->assertEquals(pathinfo($migrationFile, PATHINFO_FILENAME), $successList[0]);
-        $this->assertStringContains(file_get_contents( $migrationFile ), 'ALTER TABLE user ADD childs INTEGER NOT NULL DEFAULT 0;');
-        $this->assertStringContains(file_get_contents( $migrationFile ), 'CREATE UNIQUE INDEX IX_8D93D649E7927C74 ON user (email);');
-        $this->assertStringContains(file_get_contents( $migrationFile ), 'DROP INDEX IX_8D93D649E7927C74;');
+        switch (get_class($driver)) {
+            case SqliteDriver::class:
+                $lines = file($migrationFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+                $this->assertEquals($lines, array (
+                    0 => '-- UP MIGRATION --',
+                    1 => 'ALTER TABLE user ADD childs INTEGER NOT NULL DEFAULT 0;',
+                    2 => 'CREATE UNIQUE INDEX IX_8D93D649E7927C74 ON user (email);',
+                    3 => '-- Modify column email is not supported with PhpDevCommunity\\PaperORM\\Schema\\SqliteSchema. Consider creating a new column and migrating the data.;',
+                    4 => '-- DOWN MIGRATION --',
+                    5 => '-- Drop column childs is not supported with PhpDevCommunity\\PaperORM\\Schema\\SqliteSchema. You might need to manually drop the column.;',
+                    6 => 'DROP INDEX IX_8D93D649E7927C74;',
+                ));
+
+                break;
+            case MariaDBDriver::class:
+                $lines = file($migrationFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+                $this->assertEquals($lines, array (
+                    0 => '-- UP MIGRATION --',
+                    1 => 'ALTER TABLE user ADD COLUMN childs INT(11) NOT NULL DEFAULT 0;',
+                    2 => 'CREATE UNIQUE INDEX IX_8D93D649E7927C74 ON user (email);',
+                    3 => 'ALTER TABLE user MODIFY COLUMN email VARCHAR(255) DEFAULT NULL;',
+                    4 => '-- DOWN MIGRATION --',
+                    5 => 'ALTER TABLE user DROP COLUMN childs;',
+                    6 => 'DROP INDEX IX_8D93D649E7927C74 ON user;',
+                    7 => 'ALTER TABLE user MODIFY COLUMN email VARCHAR(255) NOT NULL;',
+                ));
+                break;
+            default:
+                throw new RuntimeException(sprintf('Driver %s not supported', get_class($driver)));
+        }
+
     }
 
-    private function testFailedMigration(): void
+    private function testFailedMigration(PaperMigration  $paperMigration): void
     {
+        $em = $paperMigration->getEntityManager();
+        $driver = $em->getConnection()->getDriver();
+        if ($driver instanceof MariaDBDriver) {
+            return;
+        }
         $userColumns = ColumnMapper::getColumns(UserTest::class);
-        $userColumns[3] = (new StringColumn( null, 100, true, null, true))->bindProperty('email');
-        $this->paperMigration->diff([
+        $userColumns[3] = (new StringColumn(null, 100, true, null, true))->bindProperty('email');
+        $file = $paperMigration->diff([
             'user' => [
                 'columns' => $userColumns,
                 'indexes' => []
             ]
         ]);
 
-        $this->expectException( \RuntimeException::class, function ()  {
-            $this->paperMigration->migrate();
+        $this->expectException(RuntimeException::class, function () use ($paperMigration){
+            $paperMigration->migrate();
         });
-        $successList = $this->paperMigration->getSuccessList();
+        $successList = $paperMigration->getSuccessList();
         $this->assertTrue(count($successList) === 0);
 
     }

+ 37 - 59
tests/PersistAndFlushTest.php

@@ -12,41 +12,32 @@ use Test\PhpDevCommunity\PaperORM\Helper\DataBaseHelperTest;
 
 class PersistAndFlushTest extends TestCase
 {
-    private EntityManager $em;
 
     protected function setUp(): void
     {
-        $this->em = new EntityManager([
-            'driver' => 'sqlite',
-            'user' => null,
-            'password' => null,
-            'memory' => true,
-            'debug' => true
-        ]);
-        $this->setUpDatabaseSchema();
-    }
 
-    protected function setUpDatabaseSchema(): void
-    {
-        DataBaseHelperTest::init($this->em);
     }
 
     protected function tearDown(): void
     {
-        $this->em->getConnection()->close();
     }
 
     protected function execute(): void
     {
-        $this->testInsert();
-        $this->testUpdate();
-        $this->testUpdateJoinColumn();
-        $this->testDelete();
+        foreach (DataBaseHelperTest::drivers() as  $params) {
+            $em = new EntityManager($params);
+            DataBaseHelperTest::init($em);
+            $this->testInsert($em);
+            $this->testUpdate($em);
+            $this->testUpdateJoinColumn($em);
+            $this->testDelete($em);
+            $em->getConnection()->close();
+        }
     }
 
 
 
-    private function testInsert(): void
+    private function testInsert(EntityManager $em): void
     {
         $user = new UserTest();
         $this->assertNull($user->getId());
@@ -55,15 +46,15 @@ class PersistAndFlushTest extends TestCase
         $user->setPassword('secret');
         $user->setEmail('Xq5qI@example.com');
         $user->setActive(true);
-        $this->em->persist($user);
-        $this->em->flush();
+        $em->persist($user);
+        $em->flush();
         $this->assertNotNull($user->getId());
-        $this->em->clear();
+        $em->clear();
     }
 
-    private function testUpdate(): void
+    private function testUpdate(EntityManager $em): void
     {
-        $userRepository = $this->em->getRepository(UserTest::class);
+        $userRepository = $em->getRepository(UserTest::class);
         $user = $userRepository->findBy()->first()->orderBy('id')->toObject();
         $this->assertInstanceOf(ProxyInterface::class, $user);
         $this->assertInstanceOf(UserTest::class, $user);
@@ -74,10 +65,10 @@ class PersistAndFlushTest extends TestCase
         $user->setLastname('TOTO');
         $this->assertStrictEquals(2, count($user->__getPropertiesModified()));
 
-        $this->em->persist($user);
-        $this->em->flush();
+        $em->persist($user);
+        $em->flush();
         $user = null;
-        $this->em->clear();
+        $em->clear();
 
         $user = $userRepository->findBy()->first()->orderBy('id')->toObject();
         $this->assertInstanceOf(ProxyInterface::class, $user);
@@ -91,10 +82,10 @@ class PersistAndFlushTest extends TestCase
     }
 
 
-    private function testUpdateJoinColumn()
+    private function testUpdateJoinColumn(EntityManager $em)
     {
-        $userRepository = $this->em->getRepository(UserTest::class);
-        $postRepository = $this->em->getRepository(PostTest::class);
+        $userRepository = $em->getRepository(UserTest::class);
+        $postRepository = $em->getRepository(PostTest::class);
         $post = $postRepository->findBy()->first()
             ->where(Expr::isNotNull('user'))
             ->with(UserTest::class)
@@ -113,8 +104,8 @@ class PersistAndFlushTest extends TestCase
             $this->assertInstanceOf(PostTest::class, $postItem);
         }
         $post->setUser($user2);
-        $this->em->persist($post);
-        $this->em->flush();
+        $em->persist($post);
+        $em->flush();
         $user2 = $userRepository->find(2)
             ->with(PostTest::class)
             ->toObject();
@@ -124,43 +115,30 @@ class PersistAndFlushTest extends TestCase
         $this->assertStrictEquals(1, count($user1->getPosts()->toArray()));
     }
 
-    private function testDelete()
+    private function testDelete(EntityManager $em)
     {
-        $user = $this->em->getRepository(UserTest::class)->find(1)->toObject();
+        $user = $em->getRepository(UserTest::class)->find(1)->toObject();
         $this->assertInstanceOf(ProxyInterface::class, $user);
         $this->assertInstanceOf(UserTest::class, $user);
 
-        $posts = $user->getPosts();
-        $this->em->remove($user);
-        $this->em->flush();
+        $em->remove($user);
+        $em->flush();
         $this->assertFalse($user->__isInitialized());
-        /**
-         * @var PostTest|ProxyInterface $post
-         */
-        $post = $this->em->getRepository(PostTest::class)
-            ->findBy()
-            ->first()
-            ->where(Expr::equal('user', $user->getId()))
-            ->with(UserTest::class)
-            ->toObject();
-        $this->assertNull($post->getUser());
-
-        $user = $this->em->getRepository(UserTest::class)->find(1)->toObject();
-        $this->assertNull($user);
 
+        $posts = $user->getPosts();
         $ids = [];
-        foreach ($posts as $postToDelete) {
-            $ids[] = $postToDelete->getId();
-            $this->em->remove($postToDelete);
-            $this->em->flush();
-            $this->assertFalse($postToDelete->__isInitialized());
+        foreach ($posts as $post) {
+            $ids[] = $post->getId();
+            $em->remove($post);
+            $em->flush();
+            $this->assertFalse($post->__isInitialized());
         }
-        $this->assertStrictEquals($posts->count(), count($ids));
+        $user = $em->getRepository(UserTest::class)->find(1)->toObject();
+        $this->assertNull($user);
+
         foreach ($ids as $idPost) {
-            $postToDelete = $this->em->getRepository(PostTest::class)->find($idPost)->toObject();
+            $postToDelete = $em->getRepository(PostTest::class)->find($idPost)->toObject();
             $this->assertNull($postToDelete);
         }
-
-        $this->assertFalse($post->__isInitialized());
     }
 }

+ 16 - 9
tests/PlatformDiffTest.php

@@ -7,30 +7,36 @@ use PhpDevCommunity\PaperORM\Mapping\Column\BoolColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\UniTester\TestCase;
+use Test\PhpDevCommunity\PaperORM\Helper\DataBaseHelperTest;
 
 class PlatformDiffTest extends TestCase
 {
-    private EntityManager $em;
+
 
     protected function setUp(): void
     {
-        $this->em = new EntityManager([
-            'driver' => 'sqlite',
-            'user' => null,
-            'password' => null,
-            'memory' => true,
-        ]);
 
     }
 
     protected function tearDown(): void
     {
-        $this->em->getConnection()->close();
     }
 
     protected function execute(): void
     {
-        $platform = $this->em->createDatabasePlatform();
+        foreach (DataBaseHelperTest::drivers() as $params) {
+            $em = new EntityManager($params);
+            $this->executeTest($em);
+            $em->getConnection()->close();
+        }
+    }
+
+    private function executeTest(EntityManager  $em)
+    {
+        $platform = $em->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
         $columns = [
             new PrimaryKeyColumn('id'),
             new StringColumn('firstname'),
@@ -68,5 +74,6 @@ class PlatformDiffTest extends TestCase
 
         $diff = $platform->diff('user2', $columns, [] );
         $this->assertTrue(count($diff->getColumnsToAdd()) == 6);
+
     }
 }

+ 53 - 39
tests/PlatformTest.php

@@ -9,41 +9,41 @@ use PhpDevCommunity\PaperORM\Mapping\Column\PrimaryKeyColumn;
 use PhpDevCommunity\PaperORM\Mapping\Column\StringColumn;
 use PhpDevCommunity\PaperORM\Metadata\ColumnMetadata;
 use PhpDevCommunity\UniTester\TestCase;
+use Test\PhpDevCommunity\PaperORM\Helper\DataBaseHelperTest;
 
 class PlatformTest extends TestCase
 {
-    private EntityManager $em;
 
     protected function setUp(): void
     {
-        $this->em = new EntityManager([
-            'driver' => 'sqlite',
-            'user' => null,
-            'password' => null,
-            'memory' => true,
-        ]);
 
     }
 
     protected function tearDown(): void
     {
-        $this->em->getConnection()->close();
     }
 
     protected function execute(): void
     {
-      $this->testCreateTables();
-      $this->testDropTable();
-      $this->testDropColumn();
-      $this->testAddColumn();
-      $this->testRenameColumn();
+        foreach (DataBaseHelperTest::drivers() as $params) {
+            $em = new EntityManager($params);
+            $this->testCreateTables($em);
+            $this->testDropTable($em);
+            $this->testDropColumn($em);
+            $this->testAddColumn($em);
+            $this->testRenameColumn($em);
+            $em->getConnection()->close();
+        }
     }
 
-    public function testCreateTables()
+    public function testCreateTables(EntityManager $em)
     {
-        $this->em->getConnection()->close();
-        $this->em->getConnection()->connect();
-        $platform = $this->em->createDatabasePlatform();
+        $em->getConnection()->close();
+        $em->getConnection()->connect();
+        $platform = $em->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
         $platform->createTable('user', [
             new PrimaryKeyColumn('id'),
             new StringColumn('firstname'),
@@ -59,8 +59,6 @@ class PlatformTest extends TestCase
             new IntColumn('user_id'),
             new StringColumn('title'),
             new StringColumn('content'),
-        ], [
-            'FOREIGN KEY (user_id) REFERENCES user (id)'
         ]);
 
         $this->assertStrictEquals(2, count($platform->listTables()));
@@ -68,12 +66,16 @@ class PlatformTest extends TestCase
     }
 
 
-    public function testDropTable()
+    public function testDropTable(EntityManager $em)
     {
-        $this->em->getConnection()->close();
-        $this->em->getConnection()->connect();
-
-        $platform = $this->em->createDatabasePlatform();
+        $em->getConnection()->close();
+        $em->getConnection()->connect();
+        $platform = $em->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
+
+        $platform = $em->createDatabasePlatform();
         $platform->createTable('user', [
             new PrimaryKeyColumn('id'),
             new StringColumn('firstname'),
@@ -88,16 +90,20 @@ class PlatformTest extends TestCase
         $this->assertStrictEquals(0, count($platform->listTables()));
     }
 
-    public function testDropColumn()
+    public function testDropColumn(EntityManager $em)
     {
-        if (\SQLite3::version()['versionString'] < '3.35.0') {
+        $platform = $em->createDatabasePlatform();
+        if ($platform->getSchema()->supportsDropColumn() === false) {
             return;
         }
 
-        $this->em->getConnection()->close();
-        $this->em->getConnection()->connect();
+        $em->getConnection()->close();
+        $em->getConnection()->connect();
+        $platform = $em->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
 
-        $platform = $this->em->createDatabasePlatform();
         $platform->createTable('user', [
             new PrimaryKeyColumn('id'),
             new StringColumn('firstname'),
@@ -112,12 +118,16 @@ class PlatformTest extends TestCase
         $this->assertStrictEquals(5, count($platform->listTableColumns('user')));
     }
 
-    public function testAddColumn()
+    public function testAddColumn(EntityManager $em)
     {
-        $this->em->getConnection()->close();
-        $this->em->getConnection()->connect();
-
-        $platform = $this->em->createDatabasePlatform();
+        $em->getConnection()->close();
+        $em->getConnection()->connect();
+        $platform = $em->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
+
+        $platform = $em->createDatabasePlatform();
         $platform->createTable('user', [
             new PrimaryKeyColumn('id'),
             new StringColumn('firstname'),
@@ -132,12 +142,16 @@ class PlatformTest extends TestCase
         $this->assertStrictEquals(7, count($platform->listTableColumns('user')));
     }
 
-    public function testRenameColumn()
+    public function testRenameColumn(EntityManager $em)
     {
-        $this->em->getConnection()->close();
-        $this->em->getConnection()->connect();
-
-        $platform = $this->em->createDatabasePlatform();
+        $em->getConnection()->close();
+        $em->getConnection()->connect();
+        $platform = $em->createDatabasePlatform();
+        $platform->createDatabaseIfNotExists();
+        $platform->dropDatabase();
+        $platform->createDatabaseIfNotExists();
+
+        $platform = $em->createDatabasePlatform();
         $platform->createTable('user', [
             new PrimaryKeyColumn('id'),
             new StringColumn('firstname'),

+ 29 - 36
tests/RepositoryTest.php

@@ -10,34 +10,30 @@ use Test\PhpDevCommunity\PaperORM\Helper\DataBaseHelperTest;
 
 class RepositoryTest extends TestCase
 {
-    private EntityManager $em;
 
     protected function setUp(): void
     {
-        $this->em = new EntityManager([
-            'driver' => 'sqlite',
-            'user' => null,
-            'password' => null,
-            'memory' => true,
-        ]);
-        $this->setUpDatabaseSchema();
     }
 
     protected function tearDown(): void
     {
-        $this->em->getConnection()->close();
     }
 
     protected function execute(): void
     {
-        $this->testSelectWithoutJoin();
-        $this->testSelectInnerJoin();
-        $this->testSelectLeftJoin();
+        foreach (DataBaseHelperTest::drivers() as $params) {
+            $em = new EntityManager($params);
+            DataBaseHelperTest::init($em);
+            $this->testSelectWithoutJoin($em);
+            $this->testSelectInnerJoin($em);
+            $this->testSelectLeftJoin($em);
+            $em->getConnection()->close();
+        }
     }
 
-    public function testSelectWithoutJoin(): void
+    public function testSelectWithoutJoin(EntityManager  $em): void
     {
-        $userRepository = $this->em->getRepository(UserTest::class);
+        $userRepository = $em->getRepository(UserTest::class);
         $user = $userRepository->findBy()
             ->first()->orderBy('id')->toArray();
 
@@ -80,9 +76,9 @@ class RepositoryTest extends TestCase
         }
     }
 
-    public function testSelectInnerJoin(): void
+    public function testSelectInnerJoin(EntityManager $em): void
     {
-        $userRepository = $this->em->getRepository(UserTest::class);
+        $userRepository = $em->getRepository(UserTest::class);
         $user = $userRepository->findBy()
             ->first()
             ->orderBy('id', 'DESC')
@@ -95,7 +91,7 @@ class RepositoryTest extends TestCase
         $this->assertTrue(is_array( $user['lastPost'] ));
         $this->assertNotEmpty($user['lastPost']);
 
-        $this->em->clear();
+        $em->clear();
         /**
          * @var UserTest $user
          */
@@ -110,12 +106,12 @@ class RepositoryTest extends TestCase
         $this->assertInstanceOf(PostTest::class, $user->getLastPost());
 
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()->orderBy('id', 'DESC')->has(PostTest::class)->toArray();
         $this->assertStrictEquals( 4, $users[0]['id'] );
         $this->assertStrictEquals(4, count($users));
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()->orderBy('id', 'DESC')->has(PostTest::class)->toObject();
         $this->assertStrictEquals( 4, $users[0]->getId() );
         $this->assertStrictEquals(4, count($users));
@@ -132,7 +128,7 @@ class RepositoryTest extends TestCase
             }
         }
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->has('posts.tags')
@@ -148,7 +144,7 @@ class RepositoryTest extends TestCase
             }
         }
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->has('posts.tags')
@@ -166,7 +162,7 @@ class RepositoryTest extends TestCase
             }
         }
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->has('posts.tags')
@@ -188,7 +184,7 @@ class RepositoryTest extends TestCase
             }
         }
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->has('posts.tags')
@@ -207,7 +203,7 @@ class RepositoryTest extends TestCase
             }
         };
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->has('posts.tags')
@@ -226,7 +222,7 @@ class RepositoryTest extends TestCase
                 $this->assertNotEmpty($post->getComments()->toArray());
             }
         };
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->has('lastPost.comments')
@@ -259,9 +255,9 @@ class RepositoryTest extends TestCase
         }
     }
 
-    public function testSelectLeftJoin(): void
+    public function testSelectLeftJoin(EntityManager $em): void
     {
-        $userRepository = $this->em->getRepository(UserTest::class);
+        $userRepository = $em->getRepository(UserTest::class);
         $user = $userRepository->findBy()
             ->first()
             ->orderBy('id', 'DESC')
@@ -274,7 +270,7 @@ class RepositoryTest extends TestCase
         $this->assertEmpty($user['posts']);
         $this->assertNull($user['lastPost']);
 
-        $this->em->clear();
+        $em->clear();
         /**
          * @var UserTest $user
          */
@@ -288,12 +284,12 @@ class RepositoryTest extends TestCase
         $this->assertEmpty($user->getPosts()->toArray());
         $this->assertNull($user->getLastPost());
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()->orderBy('id', 'DESC')->with(PostTest::class)->toArray();
         $this->assertStrictEquals( 5, $users[0]['id'] );
         $this->assertStrictEquals(5, count($users));
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()->orderBy('id', 'DESC')->with(PostTest::class)->toObject();
 
         $this->assertStrictEquals( 5, $users[0]->getId() );
@@ -316,7 +312,7 @@ class RepositoryTest extends TestCase
             }
         }
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->with('posts.tags')
@@ -336,7 +332,7 @@ class RepositoryTest extends TestCase
             }
         }
 
-        $this->em->clear();
+        $em->clear();
         $users = $userRepository->findBy()
             ->orderBy('id', 'DESC')
             ->with('posts.tags')
@@ -359,8 +355,5 @@ class RepositoryTest extends TestCase
             }
         }
     }
-    protected function setUpDatabaseSchema(): void
-    {
-        DataBaseHelperTest::init($this->em);
-    }
+
 }