Passed
Push — master ( e99e95...936f9a )
by Ondřej
02:18
created

Ivory::flushDefaultValueComparator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
namespace Ivory;
4
5
use Ivory\Cache\ICacheControl;
6
use Ivory\Connection\ConnectionParameters;
7
use Ivory\Connection\IConnection;
8
use Ivory\Exception\ConnectionException;
9
use Ivory\Exception\StatementExceptionFactory;
10
use Ivory\Lang\SqlPattern\ISqlPatternParser;
11
use Ivory\Type\TypeRegister;
12
use Ivory\Value\Alg\IValueComparator;
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
    /** @var IValueComparator */
36
    private static $defaultValueComparator = null;
37
38
39
    private function __construct()
40
    {
41
        // the class is static - no actual instance is to be used
42
    }
43
44
    public static function getCoreFactory(): ICoreFactory
45
    {
46
        if (self::$coreFactory === null) {
47
            self::$coreFactory = new StdCoreFactory();
48
        }
49
        return self::$coreFactory;
50
    }
51
52
    /**
53
     * Provides Ivory with a factory for core objects.
54
     *
55
     * Note that {@link Ivory} caches the objects so setting another core factory is only effective at the very
56
     * beginning of working with Ivory.
57
     *
58
     * @param ICoreFactory $coreFactory
59
     */
60
    public static function setCoreFactory(ICoreFactory $coreFactory): void
61
    {
62
        self::$coreFactory = $coreFactory;
63
    }
64
65
    /**
66
     * Returns the global type register, used for getting types not defined locally for a connection.
67
     */
68
    public static function getTypeRegister(): TypeRegister
69
    {
70
        if (self::$typeRegister === null) {
71
            self::$typeRegister = self::getCoreFactory()->createGlobalTypeRegister();
72
        }
73
        return self::$typeRegister;
74
    }
75
76
    public static function getSqlPatternParser(): ISqlPatternParser
77
    {
78
        if (self::$sqlPatternParser === null) {
79
            $cacheControl = self::getGlobalCacheControl();
80
            self::$sqlPatternParser = self::getCoreFactory()->createSqlPatternParser($cacheControl);
81
        }
82
        return self::$sqlPatternParser;
83
    }
84
85
    /**
86
     * Returns the global statement exception factory.
87
     *
88
     * An exception factory is used for emitting {@link \Ivory\Exception\StatementException}s upon statement errors.
89
     * Like the type register, an overriding exception factory is also defined locally on each connection - this factory
90
     * only applies if the local one does not.
91
     */
92
    public static function getStatementExceptionFactory(): StatementExceptionFactory
93
    {
94
        if (self::$stmtExFactory === null) {
95
            self::$stmtExFactory = self::getCoreFactory()->createStatementExceptionFactory();
96
        }
97
        return self::$stmtExFactory;
98
    }
99
100
    /**
101
     * @return ICacheControl cache control used globally, independent of any specific connection
102
     */
103
    public static function getGlobalCacheControl(): ICacheControl
104
    {
105
        if (self::$globalCacheControl === null) {
106
            self::$globalCacheControl = self::getCoreFactory()->createCacheControl();
107
        }
108
        return self::$globalCacheControl;
109
    }
110
111
    /**
112
     * @return CacheItemPoolInterface|null the cache implementation to use for connections which do not set their own
113
     *                                       cache, or <tt>null</tt> if no default cache implementation is set up
114
     */
115
    public static function getDefaultCacheImpl(): ?CacheItemPoolInterface
116
    {
117
        return self::$defaultCacheImpl;
118
    }
119
120
    /**
121
     * @param CacheItemPoolInterface|null $cacheItemPool the cache implementation to use for connections which do not
122
     *                                                     set their own cache, or <tt>null</tt> for no default cache
123
     */
124
    public static function setDefaultCacheImpl(?CacheItemPoolInterface $cacheItemPool): void
125
    {
126
        self::$defaultCacheImpl = $cacheItemPool;
127
    }
128
129
    /**
130
     * Sets up a new database connection.
131
     *
132
     * If this is the first connection to set up, it is automatically set as the default connection.
133
     *
134
     * The connection with the database is not immediately established - just a {@link Connection} object is initialized
135
     * and returned, it connects to the database on demand.
136
     *
137
     * @param mixed $params the parameters for creating the connection;
138
     *                      anything accepted by {@link ConnectionParameters::create()} - one of the following options:
139
     *                        <ul>
140
     *                          <li>a {@link ConnectionParameters} object
141
     *                          <li>a URI, e.g., <tt>"postgresql://usr@localhost:5433/db?connect_timeout=10"</tt>
142
     *                          <li>a PostgreSQL connection string, e.g.,
143
     *                              <tt>"host=localhost port=5432 dbname=mydb connect_timeout=10"</tt>
144
     *                          <li>a map of connection parameter keywords to values, e.g., <tt>['host' => '/tmp']</tt>
145
     *                        </ul>
146
     * @param string|null $connName name for the connection;
147
     *                              if not given, the database name is considered if it is given within
148
     *                                <tt>$params</tt>, or the fallback name <tt>'conn'</tt> is used;
149
     *                              if not given and if the auto-generated name is already taken, it is appended with a
150
     *                                numeric suffix, e.g., <tt>'conn1'</tt>, <tt>'conn2'</tt>, etc.
151
     * @return IConnection
152
     * @throws ConnectionException if connection name is explicitly specified but a connection with the same name
153
     *                               already exists
154
     */
155
    public static function setupNewConnection($params, ?string $connName = null): IConnection
156
    {
157
        if (!$params instanceof ConnectionParameters) {
158
            $params = ConnectionParameters::create($params);
159
        }
160
161
        if ($connName === null) {
162
            $connName = ($params->getDbName() ?? 'conn');
163
164
            if (isset(self::$connections[$connName])) {
165
                $safetyBreak = 10000;
166
                for ($i = 1; $i < $safetyBreak; $i++) {
167
                    if (!isset(self::$connections[$connName . $i])) {
168
                        $connName .= $i;
169
                        break;
170
                    }
171
                }
172
                if ($i >= $safetyBreak) {
173
                    throw new \RuntimeException('Error auto-generating name for the new connection: all suffixes taken');
174
                }
175
            }
176
        }
177
178
        $conn = self::getCoreFactory()->createConnection($connName, $params);
179
180
        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...
181
            self::useConnectionAsDefault($conn);
182
        }
183
184
        self::$connections[$connName] = $conn;
185
186
        return $conn;
187
    }
188
189
    /**
190
     * @param string|null $connName name of the connection to get; <tt>null</tt> to get the default connection
191
     * @return IConnection
192
     * @throws \RuntimeException if the default connection is requested but no connection has been setup yet, or if the
193
     *                             requested connection is not defined
194
     */
195
    public static function getConnection(?string $connName = null): IConnection
196
    {
197
        if ($connName === null) {
198
            if (self::$defaultConn) {
199
                return self::$defaultConn;
200
            } else {
201
                throw new \RuntimeException('No connection has been setup');
202
            }
203
        }
204
205
        if (isset(self::$connections[$connName])) {
206
            return self::$connections[$connName];
207
        } else {
208
            throw new \RuntimeException("Undefined connection: '$connName'");
209
        }
210
    }
211
212
    /**
213
     * @param IConnection|string $conn (name of) connection to use as the default connection
214
     * @throws \RuntimeException if the requested connection is not defined
215
     */
216
    public static function useConnectionAsDefault($conn): void
217
    {
218
        if (is_string($conn)) {
219
            if (isset(self::$connections[$conn])) {
220
                $conn = self::$connections[$conn];
221
            } else {
222
                throw new \RuntimeException('Undefined connection');
223
            }
224
        } elseif (!$conn instanceof IConnection) {
0 ignored issues
show
introduced by
The condition ! $conn instanceof Ivory\Connection\IConnection can never be true.
Loading history...
225
            throw new \InvalidArgumentException('conn');
226
        }
227
228
        self::$defaultConn = $conn;
229
    }
230
231
    /**
232
     * Removes a connection from the register so that it no longer occupies memory.
233
     *
234
     * Unless instructed otherwise by the `$disconnect` argument, the connection gets closed if still connected.
235
     *
236
     * @param IConnection|string|null $conn (name of) connection to remove from the register;
237
     *                                      <tt>null</tt> to remove the default connection
238
     * @param bool $disconnect if <tt>true</tt>, the connection to database will be closed
239
     * @throw \RuntimeException if the default connection is requested but no connection has been setup yet, or if the
240
     *                            requested connection is not defined
241
     */
242
    public static function dropConnection($conn = null, bool $disconnect = true): void
243
    {
244
        if ($conn instanceof IConnection) {
245
            $connName = $conn->getName();
246
        } elseif ($conn === null) {
247
            if (self::$defaultConn) {
248
                $connName = self::$defaultConn->getName();
249
            } else {
250
                throw new \RuntimeException('No connection has been setup');
251
            }
252
        } else {
253
            $connName = $conn;
254
        }
255
256
        if (isset(self::$connections[$connName])) {
257
            $droppedConn = self::$connections[$connName];
258
            unset(self::$connections[$connName]);
259
            if ($droppedConn === self::$defaultConn) {
260
                self::$defaultConn = null;
261
            }
262
            if ($disconnect) {
263
                $droppedConn->disconnect();
264
            }
265
        } else {
266
            throw new \RuntimeException('Undefined connection');
267
        }
268
    }
269
270
    /**
271
     * @return IValueComparator value comparator to use as the default one
272
     */
273
    public static function getDefaultValueComparator(): IValueComparator
274
    {
275
        if (self::$defaultValueComparator === null) {
276
            self::$defaultValueComparator = self::getCoreFactory()->createDefaultValueComparator();
277
        }
278
        return self::$defaultValueComparator;
279
    }
280
281
    /**
282
     * Discards the default value comparator, cached by {@link getDefaultValueComparator()}.
283
     */
284
    public static function flushDefaultValueComparator(): void
285
    {
286
        self::$defaultValueComparator = null;
287
    }
288
}
289