Lines 96.77% 30 / 31
Methods 75.00% 3 / 4
Classes 0.00% 0 / 1
Covered by tests of size
Name Lines Methods CRAP
 __construct 0.00% 0 / 1 0.00% 0 / 1 2
 factory 100.00% 5 / 5 100.00% 1 / 1 2
 getInstance 100.00% 1 / 1 100.00% 1 / 1 1
 createTenantDatabase 100.00% 24 / 24 100.00% 1 / 1 6
33class Database
34{
35    /**
36     * Instance.
37     */
38    private static ?DriverInterface $driver = null;
39
40    /**
41     * Constructor.
42     */
43    private function __construct(
44        protected Configuration $configuration,
45    ) {
46    }
47
48    /**
49     * Database factory.
50     *
51     * Returns a SchemaInstaller that uses the dialect-agnostic DatabaseSchema.
52     *
53     * @param Configuration $configuration phpMyFAQ configuration container
54     * @param string        $type Database management system type
55     * @throws Exception
56     */
57    public static function factory(Configuration $configuration, string $type): DriverInterface
58    {
59        try {
60            $dialect = DialectFactory::createForType(strtolower($type));
61        } catch (\InvalidArgumentException) {
62            throw new Exception('Invalid Database Type: ' . $type);
63        }
64
65        self::$driver = new SchemaInstaller($configuration, $dialect);
66
67        return self::$driver;
68    }
69
70    /**
71     * Returns the single instance.
72     */
73    public static function getInstance(): ?DriverInterface
74    {
75        return self::$driver;
76    }
77
78    /**
79     * Creates a dedicated tenant database for supported drivers (PostgreSQL, SQL Server).
80     *
81     * @throws \RuntimeException if the driver does not support database-per-tenant isolation
82     */
83    public static function createTenantDatabase(Configuration $configuration, string $type, string $databaseName): bool
84    {
85        $normalizedType = strtolower($type);
86
87        if (!preg_match('/^[A-Za-z0-9_]+$/', $databaseName)) {
88            throw new \InvalidArgumentException(sprintf('Invalid tenant database identifier: "%s".', $databaseName));
89        }
90
91        if (str_contains($normalizedType, 'pgsql')) {
92            $existsQuery = sprintf(
93                "SELECT 1 FROM pg_database WHERE datname = '%s'",
94                $configuration->getDb()->escape($databaseName),
95            );
96            $existsResult = $configuration->getDb()->query($existsQuery);
97            if ($existsResult !== false && $configuration->getDb()->numRows($existsResult) > 0) {
98                return true;
99            }
100
101            return (bool) $configuration->getDb()->query(sprintf('CREATE DATABASE "%s"', $databaseName));
102        }
103
104        if (str_contains($normalizedType, 'sqlsrv')) {
105            return (bool) $configuration
106                ->getDb()
107                ->query(sprintf(
108                    "IF DB_ID('%s') IS NULL CREATE DATABASE [%s]",
109                    $configuration->getDb()->escape($databaseName),
110                    $databaseName,
111                ));
112        }
113
114        throw new \RuntimeException(sprintf(
115            'Database-per-tenant isolation is not supported for driver "%s". Use PostgreSQL or SQL Server.',
116            $type,
117        ));
118    }
119}