Completed
Push — master ( 0a999d...b4b35b )
by Peter
13:24 queued 02:45
created

Mangan::getDbInstance()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 8.7414

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 3
cts 9
cp 0.3333
rs 8.7972
c 0
b 0
f 0
cc 4
eloc 11
nc 4
nop 0
crap 8.7414
1
<?php
2
3
/**
4
 * This software package is licensed under AGPL or Commercial license.
5
 *
6
 * @package maslosoft/mangan
7
 * @licence AGPL or Commercial
8
 * @copyright Copyright (c) Piotr Masełkowski <[email protected]>
9
 * @copyright Copyright (c) Maslosoft
10
 * @copyright Copyright (c) Others as mentioned in code
11
 * @link https://maslosoft.com/mangan/
12
 */
13
14
namespace Maslosoft\Mangan;
15
16
use Exception;
17
use Maslosoft\Addendum\Interfaces\AnnotatedInterface;
18
use Maslosoft\EmbeDi\EmbeDi;
19
use Maslosoft\Mangan\Exceptions\ManganException;
20
use Maslosoft\Mangan\Helpers\ConnectionStorage;
21
use Maslosoft\Mangan\Interfaces\Exception\ExceptionCodeInterface;
22
use Maslosoft\Mangan\Interfaces\ManganAwareInterface;
23
use Maslosoft\Mangan\Interfaces\ProfilerInterface;
24
use Maslosoft\Mangan\Meta\ManganMeta;
25
use Maslosoft\Mangan\Profillers\NullProfiler;
26
use Maslosoft\Mangan\Signals\ConfigInit;
27
use Maslosoft\Mangan\Traits\Defaults\MongoClientOptions;
28
use Maslosoft\Signals\Signal;
29
use MongoClient;
30
use MongoDB;
31
use Psr\Log\LoggerAwareInterface;
32
use Psr\Log\LoggerInterface;
33
use Psr\Log\NullLogger;
34
35
/**
36
 * MongoDB
37
 *
38
 * This is merge work of tyohan, Alexander Makarov and mine
39
 * @author Ianaré Sévi
40
 * @author Dariusz Górecki <[email protected]>
41
 * @author Invenzzia Group, open-source division of CleverIT company http://www.invenzzia.org
42
 * @copyright 2011 CleverIT http://www.cleverit.com.pl
43
 * @property LoggerInterface $logger Logger
44
 * @property-read string $version Current version
45
 * @since v1.0
46
 */
47
class Mangan implements LoggerAwareInterface
48
{
49
50
	const DefaultConnectionId = 'mongodb';
51
52
	use MongoClientOptions;
53
54
	/**
55
	 * Correct syntax is:
56
	 * mongodb://[username:password@]host1[:port1][,host2[:port2:],...]
57
	 * @example mongodb://localhost:27017
58
	 * @var string host:port
59
	 * @since v1.0
60
	 */
61
	public $connectionString = 'mongodb://localhost:27017';
62
63
	/**
64
	 * Configuration of decorators for transformers
65
	 * Array key is decorator class name or interface, values are decorator class names.
66
	 * @var string[][]
67
	 */
68
	public $decorators = [];
69
70
	/**
71
	 * Configuration for finalizers.
72
	 * @see https://github.com/Maslosoft/Mangan/issues/36
73
	 * @var string[][]
74
	 */
75
	public $finalizers = [];
76
77
	/**
78
	 * Configuration of property filters for transformers
79
	 * Array key is decorator class name or interface, values are filter class names.
80
	 * @var string[][]
81
	 */
82
	public $filters = [];
83
84
	/**
85
	 * Mapping for validators. Key is validator proxy class name, value is concrete validator implementation
86
	 * @var string[]
87
	 */
88
	public $validators = [];
89
90
	/**
91
	 * Sanitizers ramapping for common scenarios.
92
	 * @var string[][]
93
	 */
94
	public $sanitizersMap = [];
95
96
	/**
97
	 * Connection ID
98
	 * @var string
99
	 */
100
	public $connectionId = 'mongodb';
101
102
	/**
103
	 * @var string $dbName name of the Mongo database to use
104
	 * @since v1.0
105
	 */
106
	public $dbName = null;
107
108
	/**
109
	 * If set to TRUE all internal DB operations will use SAFE flag with data modification requests.
110
	 *
111
	 * When SAFE flag is set to TRUE driver will wait for the response from DB, and throw an exception
112
	 * if something went wrong, is set to false, driver will only send operation to DB but will not wait
113
	 * for response from DB.
114
	 *
115
	 * MongoDB default value for this flag is: FALSE.
116
	 *
117
	 * @var boolean $safeFlag state of SAFE flag (global scope)
118
	 */
119
	public $safeFlag = false;
120
121
	/**
122
	 * If set to TRUE findAll* methods of models, will return {@see Cursor} instead of
123
	 * raw array of models.
124
	 *
125
	 * Generally you should want to have this set to TRUE as cursor use lazy-loading/instantiating of
126
	 * models, this is set to FALSE, by default to keep backwards compatibility.
127
	 *
128
	 * Note: {@see Cursor} does not implement ArrayAccess interface and cannot be used like an array,
129
	 * because offset access to cursor is highly ineffective and pointless.
130
	 *
131
	 * @var boolean $useCursor state of Use Cursor flag (global scope)
132
	 */
133
	public $useCursor = false;
134
135
	/**
136
	 * Queries profiling.
137
	 * Defaults to false. This should be mainly enabled and used during development
138
	 * to find out the bottleneck of mongo queries.
139
	 * @var boolean whether to enable profiling the mongo queries being executed.
140
	 */
141
	public $enableProfiling = false;
142
143
	/**
144
	 * Connection storage
145
	 * @var ConnectionStorage
146
	 */
147
	private $cs = null;
148
149
	/**
150
	 * Embedi instance
151
	 * @var EmbeDi
152
	 */
153
	private $di = null;
154
155
	/**
156
	 * Logger
157
	 * @var LoggerInterface
158
	 */
159
	private $_logger = null;
160
161
	/**
162
	 * Profiller
163
	 * @var ProfilerInterface
164
	 */
165
	private $_profiler = null;
166
167
	/**
168
	 * Version number holder
169
	 * @var string
170
	 */
171
	private static $_version = null;
172
173
	/**
174
	 * Instances of mangan
175
	 * @var Mangan[]
176
	 */
177
	private static $mn = [];
178
179
	/**
180
	 * Hash map of class name to id. This is to reduce overhead of Mangan::fromModel()
181
	 * @var string[]
182
	 */
183
	private static $classToId = [];
184
185
	/**
186
	 * Create new mangan instance.
187
	 *
188
	 * **NOTE: While it's ok to use constructor to create Mangan, it is recommended to use
189
	 * Mangan::fly() to create/get instance, as creating new instance has some overhead.**
190
	 * 
191
	 * @param string $connectionId
192
	 */
193 4
	public function __construct($connectionId = self::DefaultConnectionId)
194
	{
195 4
		$this->di = EmbeDi::fly($connectionId);
196
197
		// Load built-in config
198 4
		$config = ConfigManager::getDefault();
199
200
		// Gather additional config options via signals
201 4
		(new Signal)->emit(new ConfigInit($config, $connectionId));
202
203
		// Apply built-in configuration, as other configurations might not exists
204 4
		$this->di->apply($config, $this);
205
206 4
		if (empty($connectionId))
207
		{
208
			$connectionId = self::DefaultConnectionId;
209
		}
210 4
		$this->connectionId = $connectionId;
211
212
		// Apply any configurations loaded
213 4
		$this->di->configure($this);
214 4
		$this->cs = new ConnectionStorage($this, $connectionId);
215 4
		if (empty(self::$mn[$connectionId]))
216
		{
217
			self::$mn[$connectionId] = $this;
218
		}
219 4
	}
220
221
	public function __get($name)
222
	{
223
		return $this->{'get' . ucfirst($name)}();
224
	}
225
226
	public function __set($name, $value)
227
	{
228
		$this->{'set' . ucfirst($name)}($value);
229
	}
230
231
	/**
232
	 * Get mangan version
233
	 * @return string
234
	 */
235
	public function getVersion()
236
	{
237
		if (null === self::$_version)
238
		{
239
			self::$_version = require __DIR__ . '/version.php';
240
		}
241
		return self::$_version;
242
	}
243
244
	/**
245
	 * Set PSR compliant logger
246
	 * @param LoggerInterface $logger
247
	 * @return Mangan
248
	 */
249
	public function setLogger(LoggerInterface $logger)
250
	{
251
		$this->_logger = $logger;
252
		return $this;
253
	}
254
255
	/**
256
	 * Get PSR compliant logger
257
	 * @return LoggerInterface
258
	 */
259 1
	public function getLogger()
260
	{
261 1
		if (null === $this->_logger)
262
		{
263 1
			$this->_logger = new NullLogger;
264
		}
265 1
		return $this->_logger;
266
	}
267
268
	/**
269
	 * Get profiler instance. This is guaranted, if not configured will return NullProfiller.
270
	 * @see NullProfiler
271
	 * @return ProfilerInterface
272
	 */
273 72
	public function getProfiler()
274
	{
275 72
		if (null === $this->_profiler)
276
		{
277 1
			$this->_profiler = new NullProfiler;
278
		}
279 72
		if ($this->_profiler instanceof ManganAwareInterface)
280
		{
281
			$this->_profiler->setMangan($this);
282
		}
283 72
		return $this->_profiler;
284
	}
285
286
	/**
287
	 * Set profiler instance
288
	 * @param ProfilerInterface $profiller
289
	 * @return Mangan
290
	 */
291
	public function setProfiler(ProfilerInterface $profiller)
292
	{
293
		$this->_profiler = $profiller;
294
		return $this;
295
	}
296
297
	/**
298
	 * Get dependecy injector.
299
	 * @return EmbeDi
300
	 */
301 19
	public function getDi()
302
	{
303 19
		return $this->di;
304
	}
305
306
	/**
307
	 * Get flyweight instance of Mangan component.
308
	 * Only one instance will be created for each `$connectionId`.
309
	 *
310
	 * @new
311
	 * @param string $connectionId
312
	 * @return Mangan
313
	 */
314 156
	public static function fly($connectionId = self::DefaultConnectionId)
315
	{
316 156
		if (empty($connectionId))
317
		{
318
			$connectionId = self::DefaultConnectionId;
319
		}
320 156
		if (empty(self::$mn[$connectionId]))
321
		{
322
			self::$mn[$connectionId] = new static($connectionId);
323
		}
324 156
		return self::$mn[$connectionId];
325
	}
326
327
	/**
328
	 * Get instance of Mangan configured for particular model
329
	 * @param AnnotatedInterface $model
330
	 * @return static
331
	 */
332 153
	public static function fromModel(AnnotatedInterface $model)
333
	{
334 153
		$key = get_class($model);
335 153
		if (isset(self::$classToId[$key]))
336
		{
337 141
			$connectionId = self::$classToId[$key];
338
		}
339
		else
340
		{
341 59
			$connectionId = ManganMeta::create($model)->type()->connectionId;
342 59
			self::$classToId[$key] = $connectionId;
343
		}
344 153
		return self::fly($connectionId);
345
	}
346
347
	public function init()
348
	{
349
		$this->di->store($this);
350
	}
351
352
	/**
353
	 * Connect to DB if connection is already connected this method return connection status.
354
	 *
355
	 * @return bool Returns true if connected
356
	 */
357
	public function connect()
358
	{
359
		if (!$this->getConnection()->connected)
360
		{
361
			return $this->getConnection()->connect();
362
		}
363
		return $this->getConnection()->connected;
364
	}
365
366
	/**
367
	 * Returns Mongo connection instance if not exists will create new
368
	 *
369
	 * @return MongoClient
370
	 * @throws ManganException
371
	 * @since v1.0
372
	 */
373
	public function getConnection()
374
	{
375
		if ($this->cs->mongoClient === null)
376
		{
377
			if (!$this->connectionString)
378
			{
379
				throw new ManganException('Mangan.connectionString cannot be empty.');
380
			}
381
382
			$options = [];
383
			foreach ($this->_getOptionNames() as $name)
384
			{
385
				if (isset($this->$name))
386
				{
387
					$options[$name] = $this->$name;
388
				}
389
			}
390
391
			$this->cs->mongoClient = new MongoClient($this->connectionString, $options);
392
393
			return $this->cs->mongoClient;
394
		}
395
		else
396
		{
397
			return $this->cs->mongoClient;
398
		}
399
	}
400
401
	/**
402
	 * Set the connection by suppling `MongoClient` instance.
403
	 *
404
	 * Use this to set connection from external source.
405
	 * In most scenarios this does not need to be called.
406
	 *
407
	 * @param MongoClient $connection
408
	 */
409
	public function setConnection(MongoClient $connection)
410
	{
411
		$this->cs->mongoClient = $connection;
412
	}
413
414
	/**
415
	 * Get MongoDB instance
416
	 *
417
	 * @return MongoDB Mongo DB instance
418
	 */
419 98
	public function getDbInstance()
420
	{
421 98
		if ($this->cs->mongoDB === null)
422
		{
423
			if (!$this->dbName)
424
			{
425
				throw new ManganException(sprintf("Database name is required for connectionId: `%s`", $this->connectionId), ExceptionCodeInterface::RequireDbName);
426
			}
427
			try
428
			{
429
				$db = $this->getConnection()->selectDB($this->dbName);
430
			}
431
			catch (Exception $e)
432
			{
433
				throw new ManganException(sprintf('Could not select db name: `%s`, for connectionId: `%s` - %s', $this->dbName, $this->connectionId, $e->getMessage()), ExceptionCodeInterface::CouldNotSelect, $e);
434
			}
435
			return $this->cs->mongoDB = $db;
436
		}
437
		else
438
		{
439 98
			return $this->cs->mongoDB;
440
		}
441
	}
442
443
	/**
444
	 * Set MongoDB instance by suppling database name.
445
	 *
446
	 * Use this to select db from external source.
447
	 * In most scenarios this does not need to be called.
448
	 *
449
	 * @param string $name
450
	 */
451
	public function setDbInstance($name)
452
	{
453
		$this->cs->mongoDB = $this->getConnection()->selectDb($name);
454
	}
455
456
	/**
457
	 * Closes the currently active Mongo connection.
458
	 * It does nothing if the connection is already closed.
459
	 */
460
	protected function close()
461
	{
462
		if ($this->cs->mongoClient !== null)
463
		{
464
			$this->cs->mongoClient->close();
465
			$this->cs->mongoClient = null;
466
			$this->cs->mongoDB = null;
467
		}
468
	}
469
470
	/**
471
	 * Drop current database
472
	 */
473
	public function dropDb()
474
	{
475
		$this->getDbInstance()->drop();
476
	}
477
478
}
479