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