Passed
Push — master ( 3a7f22...a610f4 )
by Ondřej
03:24
created

Ivory::setupNewConnection()   C

Complexity

Conditions 8
Paths 26

Size

Total Lines 30
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

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

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
190
            if (self::$defaultConn) {
191
                return self::$defaultConn;
192
            } else {
193
                throw new \RuntimeException('No connection has been setup');
194
            }
195
        }
196
197 View Code Duplication
        if (isset(self::$connections[$connName])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
198
            return self::$connections[$connName];
199
        } else {
200
            throw new \RuntimeException('Undefined connection');
201
        }
202
    }
203
204
    /**
205
     * @param IConnection|string $conn (name of) connection to use as the default connection
206
     * @throws \RuntimeException if the requested connection is not defined
207
     */
208
    public static function useConnectionAsDefault($conn)
209
    {
210
        if (is_string($conn)) {
211 View Code Duplication
            if (isset(self::$connections[$conn])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
212
                $conn = self::$connections[$conn];
213
            } else {
214
                throw new \RuntimeException('Undefined connection');
215
            }
216
        } elseif (!$conn instanceof IConnection) {
217
            throw new \InvalidArgumentException('conn');
218
        }
219
220
        self::$defaultConn = $conn;
221
    }
222
223
    /**
224
     * Removes a connection from the register so that it no longer occupies memory.
225
     *
226
     * @param IConnection|string|null $conn (name of) connection to remove from the register;
227
     *                                      <tt>null</tt> to remove the default connection
228
     * @throw \RuntimeException if the default connection is requested but no connection has been setup yet, or if the
229
     *                            requested connection is not defined
230
     */
231
    public static function dropConnection($conn = null)
232
    {
233
        if ($conn instanceof IConnection) {
234
            $connName = $conn->getName();
235 View Code Duplication
        } elseif ($conn === null) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
236
            if (self::$defaultConn) {
237
                $connName = self::$defaultConn->getName();
238
            } else {
239
                throw new \RuntimeException('No connection has been setup');
240
            }
241
        } else {
242
            $connName = $conn;
243
        }
244
245
        if (isset(self::$connections[$connName])) {
246
            if (self::$connections[$connName] === self::$defaultConn) {
247
                self::$defaultConn = null;
248
            }
249
            unset(self::$connections[$connName]);
250
        } else {
251
            throw new \RuntimeException('Undefined connection');
252
        }
253
    }
254
}
255