Completed
Push — master ( 3a2740...7b6e59 )
by Peter
27:15 queued 22:35
created

Mangan::__construct()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3.2099

Importance

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