Completed
Push — master ( be2975...5e2308 )
by Peter
06:45
created

Mangan::getDi()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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