Mangan::__construct()   B
last analyzed

Complexity

Conditions 5
Paths 12

Size

Total Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 6.0718

Importance

Changes 0
Metric Value
dl 0
loc 40
ccs 13
cts 20
cp 0.65
rs 8.9688
c 0
b 0
f 0
cc 5
nc 12
nop 1
crap 6.0718
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 corresponding 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 remapping 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 guaranteed, if not configured will return NullProfiler.
314
	 * @see NullProfiler
315
	 * @return ProfilerInterface
316
	 */
317 108
	public function getProfiler()
318
	{
319 108
		if (null === $this->_profiler)
320
		{
321 2
			$this->_profiler = new NullProfiler;
322
		}
323 108
		if ($this->_profiler instanceof ManganAwareInterface)
324
		{
325
			$this->_profiler->setMangan($this);
326
		}
327 108
		return $this->_profiler;
328
	}
329
330
	/**
331
	 * Set profiler instance
332
	 * @param ProfilerInterface $profiler
333
	 * @return Mangan
334
	 */
335
	public function setProfiler(ProfilerInterface $profiler)
336
	{
337
		$this->_profiler = $profiler;
338
		return $this;
339
	}
340
341
	/**
342
	 * Get dependency injector.
343
	 * @return EmbeDi
344
	 */
345 22
	public function getDi()
346
	{
347 22
		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 232
	public static function fly($connectionId = self::DefaultConnectionId)
359
	{
360 232
		if (empty($connectionId))
361
		{
362
			$connectionId = self::DefaultConnectionId;
363
		}
364 232
		if (empty(self::$mn[$connectionId]))
365
		{
366
			self::$mn[$connectionId] = new static($connectionId);
367
		}
368 232
		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 206
	public static function fromModel($model)
377
	{
378 206
		if(is_object($model))
379
		{
380 206
			$key = get_class($model);
381
		}
382
		else
383
		{
384
			$key = $model;
385
		}
386 206
		if (isset(self::$classToId[$key]))
387
		{
388 191
			$connectionId = self::$classToId[$key];
389
		}
390
		else
391
		{
392 83
			$connectionId = ManganMeta::create($model)->type()->connectionId;
393 83
			self::$classToId[$key] = $connectionId;
394
		}
395 206
		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
	 * @throws ManganException
408
	 */
409
	public function connect()
410
	{
411
		if (!$this->getConnection()->connected)
412
		{
413
			return $this->getConnection()->connect();
414
		}
415
		return $this->getConnection()->connected;
416
	}
417
418
	/**
419
	 * Returns Mongo connection instance if not exists will create new
420
	 *
421
	 * @return MongoClient
422
	 * @throws ManganException
423
	 * @since v1.0
424
	 */
425 1
	public function getConnection()
426
	{
427 1
		if ($this->cs->mongoClient === null)
428
		{
429
			if (!$this->connectionString)
430
			{
431
				throw new ManganException('Mangan.connectionString cannot be empty.');
432
			}
433
434
			$options = [];
435
			foreach ($this->_getOptionNames() as $name)
436
			{
437
				if (isset($this->$name))
438
				{
439
					$options[$name] = $this->$name;
440
				}
441
			}
442
443
			$this->cs->mongoClient = new MongoClient($this->connectionString);
444
445
			return $this->cs->mongoClient;
446
		}
447
		else
448
		{
449 1
			return $this->cs->mongoClient;
450
		}
451
	}
452
453
	/**
454
	 * Set the connection by supplying `MongoClient` instance.
455
	 *
456
	 * Use this to set connection from external source.
457
	 * In most scenarios this does not need to be called.
458
	 *
459
	 * @param MongoClient $connection
460
	 */
461
	public function setConnection(MongoClient $connection)
462
	{
463
		$this->cs->mongoClient = $connection;
464
	}
465
466
	/**
467
	 * Get MongoDB instance
468
	 *
469
	 * @return MongoDB Mongo DB instance
470
	 * @throws ManganException
471
	 */
472 147
	public function getDbInstance()
473
	{
474 147
		if ($this->cs->mongoDB === null)
475
		{
476
			if (!$this->dbName)
477
			{
478
				throw new ManganException(sprintf("Database name (`dbName`) is required for connectionId: `%s`", $this->connectionId), ExceptionCodeInterface::RequireDbName);
479
			}
480
			try
481
			{
482
				$db = $this->getConnection()->selectDB($this->dbName);
483
			}
484
			catch (Exception $e)
485
			{
486
				throw new ManganException(sprintf('Could not select db name: `%s`, for connectionId: `%s` - %s', $this->dbName, $this->connectionId, $e->getMessage()), ExceptionCodeInterface::CouldNotSelect, $e);
487
			}
488
			return $this->cs->mongoDB = $db;
489
		}
490
		else
491
		{
492 147
			return $this->cs->mongoDB;
493
		}
494
	}
495
496
	/**
497
	 * Set MongoDB instance by supplying database name.
498
	 *
499
	 * Use this to select db from external source.
500
	 * In most scenarios this does not need to be called.
501
	 *
502
	 * @param string $name
503
	 * @throws ManganException
504
	 */
505 1
	public function setDbInstance($name)
506
	{
507 1
		$this->cs->mongoDB = $this->getConnection()->selectDb($name);
508 1
	}
509
510
	/**
511
	 * Closes the currently active Mongo connection.
512
	 * It does nothing if the connection is already closed.
513
	 */
514
	protected function close()
515
	{
516
		if ($this->cs->mongoClient !== null)
517
		{
518
			$this->cs->mongoClient->close();
519
			$this->cs->mongoClient = null;
520
			$this->cs->mongoDB = null;
521
		}
522
	}
523
524
	/**
525
	 * Change working database
526
	 * @param $name
527
	 */
528 1
	public function selectDb($name)
529
	{
530 1
		$this->setDbInstance($name);
531 1
	}
532
533
	/**
534
	 * Drop current database
535
	 */
536
	public function dropDb()
537
	{
538
		$this->getDbInstance()->drop();
539
	}
540
541
}
542