Completed
Push — master ( 8a9a36...5d30b0 )
by Ondřej
03:00
created

Ivory::dropConnection()   C

Complexity

Conditions 7
Paths 16

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 19
nc 16
nop 2
1
<?php
2
declare(strict_types=1);
3
4
namespace Ivory;
5
6
use Ivory\Connection\ConnectionParameters;
7
use Ivory\Cache\ICacheControl;
8
use Ivory\Connection\IConnection;
9
use Ivory\Exception\ConnectionException;
10
use Ivory\Exception\StatementExceptionFactory;
11
use Ivory\Lang\SqlPattern\ISqlPatternParser;
12
use Ivory\Type\TypeRegister;
13
use Psr\Cache\CacheItemPoolInterface;
14
15
final class Ivory
16
{
17
    const VERSION = '0.1.0a1';
18
    
19
    /** @var ICoreFactory */
20
    private static $coreFactory = null;
21
    /** @var TypeRegister */
22
    private static $typeRegister = null;
23
    /** @var ISqlPatternParser */
24
    private static $sqlPatternParser = null;
25
    /** @var StatementExceptionFactory */
26
    private static $stmtExFactory = null;
27
    /** @var IConnection[] map: name => connection */
28
    private static $connections = [];
29
    /** @var IConnection|null */
30
    private static $defaultConn = null;
31
    /** @var CacheItemPoolInterface|null */
32
    private static $defaultCacheImpl = null;
33
    /** @var ICacheControl|null */
34
    private static $globalCacheControl = null;
35
36
37
    private function __construct()
38
    {
39
        // the class is static - no actual instance is to be used
40
    }
41
42
    public static function getCoreFactory(): ICoreFactory
43
    {
44
        if (self::$coreFactory === null) {
45
            self::$coreFactory = new StdCoreFactory();
46
        }
47
        return self::$coreFactory;
48
    }
49
50
    /**
51
     * Provides Ivory with a factory for core objects.
52
     *
53
     * Note that {@link Ivory} caches the objects so setting another core factory is only effective at the very
54
     * beginning of working with Ivory.
55
     *
56
     * @param ICoreFactory $coreFactory
57
     */
58
    public static function setCoreFactory(ICoreFactory $coreFactory)
59
    {
60
        self::$coreFactory = $coreFactory;
61
    }
62
63
    /**
64
     * @return TypeRegister the global type register, used for getting types not defined locally for a connection
65
     */
66
    public static function getTypeRegister(): TypeRegister
67
    {
68
        if (self::$typeRegister === null) {
69
            self::$typeRegister = self::getCoreFactory()->createGlobalTypeRegister();
70
        }
71
        return self::$typeRegister;
72
    }
73
74
    public static function getSqlPatternParser(): ISqlPatternParser
75
    {
76
        if (self::$sqlPatternParser === null) {
77
            $cacheControl = self::getGlobalCacheControl();
78
            self::$sqlPatternParser = self::getCoreFactory()->createSqlPatternParser($cacheControl);
79
        }
80
        return self::$sqlPatternParser;
81
    }
82
83
    /**
84
     * @return StatementExceptionFactory the global statement exception factory, used for emitting the
85
     *                                     {@link \Ivory\Exception\StatementException}s upon statement errors;
86
     *                                   like the type register, an overriding factory is also defined locally on each
87
     *                                     connection - this factory only applies if the local one does not
88
     */
89
    public static function getStatementExceptionFactory(): StatementExceptionFactory
90
    {
91
        if (self::$stmtExFactory === null) {
92
            self::$stmtExFactory = self::getCoreFactory()->createStatementExceptionFactory();
93
        }
94
        return self::$stmtExFactory;
95
    }
96
97
    /**
98
     * @return ICacheControl cache control used globally, independent of any specific connection
99
     */
100
    public static function getGlobalCacheControl(): ICacheControl
101
    {
102
        if (self::$globalCacheControl === null) {
103
            self::$globalCacheControl = self::getCoreFactory()->createCacheControl();
104
        }
105
        return self::$globalCacheControl;
106
    }
107
108
    /**
109
     * @return CacheItemPoolInterface|null the cache implementation to use for connections which do not set their own
110
     *                                       cache, or <tt>null</tt> if no default cache implementation is set up
111
     */
112
    public static function getDefaultCacheImpl(): ?CacheItemPoolInterface
113
    {
114
        return self::$defaultCacheImpl;
115
    }
116
117
    /**
118
     * @param CacheItemPoolInterface|null $cacheItemPool the cache implementation to use for connections which do not
119
     *                                                     set their own cache, or <tt>null</tt> for no default cache
120
     */
121
    public static function setDefaultCacheImpl(?CacheItemPoolInterface $cacheItemPool): void
122
    {
123
        self::$defaultCacheImpl = $cacheItemPool;
124
    }
125
126
    /**
127
     * Sets up a new database connection.
128
     *
129
     * If this is the first connection to set up, it is automatically set as the default connection.
130
     *
131
     * The connection with the database is not immediately established - just a {@link Connection} object is initialized
132
     * and returned, it connects to the database on demand.
133
     *
134
     * @param mixed $params the parameters for creating the connection;
135
     *                      anything accepted by {@link ConnectionParameters::create()} - one of the following options:
136
     *                        <ul>
137
     *                          <li>a {@link ConnectionParameters} object
138
     *                          <li>a URI, e.g., <tt>"postgresql://usr@localhost:5433/db?connect_timeout=10"</tt>
139
     *                          <li>a PostgreSQL connection string, e.g.,
140
     *                              <tt>"host=localhost port=5432 dbname=mydb connect_timeout=10"</tt>
141
     *                          <li>a map of connection parameter keywords to values, e.g., <tt>['host' => '/tmp']</tt>
142
     *                        </ul>
143
     * @param string|null $connName name for the connection;
144
     *                              if not given, the database name is considered if it is given within <tt>$params</tt>
145
     *                                or the fallback name <tt>'conn'</tt> is used;
146
     *                              if not given and if the auto-generated name is already taken, it is appended with a
147
     *                                numeric suffix, e.g., <tt>'conn1'</tt>, <tt>'conn2'</tt>, etc.
148
     * @return IConnection
149
     * @throws ConnectionException if connection name is explicitly specified but a connection with the same name
150
     *                               already exists
151
     */
152
    public static function setupNewConnection($params, ?string $connName = null): IConnection
153
    {
154
        if (!$params instanceof ConnectionParameters) {
155
            $params = ConnectionParameters::create($params);
156
        }
157
158
        if ($connName === null) {
159
            $connName = (isset($params['dbname']) ? $params['dbname'] : 'conn');
160
            $safetyBreak = 10000;
161
            for ($i = 1; $i < $safetyBreak; $i++) {
162
                if (!isset(self::$connections[$connName . $i])) {
163
                    $connName .= $i;
164
                    break;
165
                }
166
            }
167
            if ($i >= $safetyBreak) {
168
                throw new \RuntimeException('Error auto-generating name for the new connection: all suffixes taken');
169
            }
170
        }
171
172
        $conn = self::getCoreFactory()->createConnection($connName, $params);
173
174
        if (!self::$connections) {
0 ignored issues
show
Bug Best Practice introduced by
The expression self::$connections of type Ivory\Connection\IConnection[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
175
            self::useConnectionAsDefault($conn);
176
        }
177
178
        self::$connections[$connName] = $conn;
179
180
        return $conn;
181
    }
182
183
    /**
184
     * @param string|null $connName name of the connection to get; <tt>null</tt> to get the default connection
185
     * @return IConnection
186
     * @throws \RuntimeException if the default connection is requested but no connection has been setup yet, or if the
187
     *                             requested connection is not defined
188
     */
189
    public static function getConnection(?string $connName = null): IConnection
190
    {
191
        if ($connName === null) {
192
            if (self::$defaultConn) {
193
                return self::$defaultConn;
194
            } else {
195
                throw new \RuntimeException('No connection has been setup');
196
            }
197
        }
198
199
        if (isset(self::$connections[$connName])) {
200
            return self::$connections[$connName];
201
        } else {
202
            throw new \RuntimeException('Undefined connection');
203
        }
204
    }
205
206
    /**
207
     * @param IConnection|string $conn (name of) connection to use as the default connection
208
     * @throws \RuntimeException if the requested connection is not defined
209
     */
210
    public static function useConnectionAsDefault($conn): void
211
    {
212
        if (is_string($conn)) {
213
            if (isset(self::$connections[$conn])) {
214
                $conn = self::$connections[$conn];
215
            } else {
216
                throw new \RuntimeException('Undefined connection');
217
            }
218
        } elseif (!$conn instanceof IConnection) {
219
            throw new \InvalidArgumentException('conn');
220
        }
221
222
        self::$defaultConn = $conn;
223
    }
224
225
    /**
226
     * Removes a connection from the register so that it no longer occupies memory.
227
     *
228
     * Unless instructed otherwise by the `$disconnect` argument, the connection gets closed if still connected.
229
     *
230
     * @param IConnection|string|null $conn (name of) connection to remove from the register;
231
     *                                      <tt>null</tt> to remove the default connection
232
     * @param bool $disconnect if <tt>true</tt>, the connection to database will be closed
233
     * @throw \RuntimeException if the default connection is requested but no connection has been setup yet, or if the
234
     *                            requested connection is not defined
235
     */
236
    public static function dropConnection($conn = null, bool $disconnect = true): void
237
    {
238
        if ($conn instanceof IConnection) {
239
            $connName = $conn->getName();
240
        } elseif ($conn === null) {
241
            if (self::$defaultConn) {
242
                $connName = self::$defaultConn->getName();
243
            } else {
244
                throw new \RuntimeException('No connection has been setup');
245
            }
246
        } else {
247
            $connName = $conn;
248
        }
249
250
        if (isset(self::$connections[$connName])) {
251
            $droppedConn = self::$connections[$connName];
252
            unset(self::$connections[$connName]);
253
            if ($droppedConn === self::$defaultConn) {
254
                self::$defaultConn = null;
255
            }
256
            if ($disconnect) {
257
                $droppedConn->disconnect();
258
            }
259
        } else {
260
            throw new \RuntimeException('Undefined connection');
261
        }
262
    }
263
}
264