Manager::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 1 Features 1
Metric Value
c 4
b 1
f 1
dl 0
loc 8
rs 9.4285
cc 1
eloc 4
nc 1
nop 2
1
<?php
2
3
namespace Analogue\ORM\System;
4
5
use Analogue\ORM\Entity;
6
use Analogue\ORM\Plugins\AnaloguePluginInterface;
7
use Analogue\ORM\ValueMap;
8
use Exception;
9
use Analogue\ORM\EntityMap;
10
use Analogue\ORM\Repository;
11
use Illuminate\Support\Collection;
12
use Analogue\ORM\System\Wrappers\Wrapper;
13
use Illuminate\Contracts\Events\Dispatcher;
14
use Analogue\ORM\Exceptions\MappingException;
15
use Analogue\ORM\Drivers\Manager as DriverManager;
16
use Analogue\ORM\Exceptions\EntityMapNotFoundException;
17
18
/**
19
 * This class is the entry point for registering Entities and
20
 * instansiating Mappers
21
 */
22
class Manager
23
{
24
    /**
25
     * Manager instance
26
     *
27
     * @var Manager
28
     */
29
    protected static $instance;
30
31
    /**
32
     * Driver Manager
33
     *
34
     * @var \Analogue\ORM\Drivers\Manager
35
     */
36
    protected $drivers;
37
38
    /**
39
     * Registered entity classes and corresponding map objects.
40
     *
41
     * @var array
42
     */
43
    protected $entityClasses = [];
44
45
    /**
46
     * Key value store of ValueObject Classes and corresponding map classes
47
     *
48
     * @var array|ValueMap[]
49
     */
50
    protected $valueClasses = [];
51
52
    /**
53
     * Morph map
54
     */
55
    protected $morphMap = [];
56
57
    /**
58
     * Loaded Mappers
59
     *
60
     * @var array
61
     */
62
    protected $mappers = [];
63
64
    /**
65
     * Loaded Repositories
66
     *
67
     * @var array
68
     */
69
    protected $repositories = [];
70
71
    /**
72
     * Event dispatcher instance
73
     *
74
     * @var \Illuminate\Contracts\Events\Dispatcher
75
     */
76
    protected $eventDispatcher;
77
78
    /**
79
     * Available Analogue Events
80
     *
81
     * @var array
82
     */
83
    protected $events = [
84
        'initializing',
85
        'initialized',
86
        'store',
87
        'stored',
88
        'creating',
89
        'created',
90
        'updating',
91
        'updated',
92
        'deleting',
93
        'deleted',
94
    ];
95
96
    /**
97
     * If strictMode is set to true, Manager will throw
98
     * an exception if no entityMap class are registered
99
     * for a given entity class.
100
     * 
101
     * @var boolean
102
     */
103
    protected $strictMode = true;
104
105
    /**
106
     * @param \Analogue\ORM\Drivers\Manager $driverManager
107
     * @param Dispatcher                    $event
108
     */
109
    public function __construct(DriverManager $driverManager, Dispatcher $event)
110
    {
111
        $this->drivers = $driverManager;
112
113
        $this->eventDispatcher = $event;
114
115
        static::$instance = $this;
116
    }
117
118
    /**
119
     * Create a mapper for a given entity (static alias)
120
     *
121
     * @param  \Analogue\ORM\Mappable|string $entity
122
     * @param  null|EntityMap                $entityMap
123
     * @throws MappingException
124
     * @return Mapper
125
     * @throws \InvalidArgumentException
126
     */
127
    public static function getMapper($entity, $entityMap = null)
128
    {
129
        return static::$instance->mapper($entity, $entityMap);
130
    }
131
132
    /**
133
     * Create a mapper for a given entity
134
     *
135
     * @param  \Analogue\ORM\Mappable|string|array|\Traversable $entity
136
     * @param  mixed                                            $entityMap
137
     * @throws MappingException
138
     * @throws \InvalidArgumentException
139
     * @return Mapper
140
     */
141
    public function mapper($entity, $entityMap = null)
142
    {
143
        if ($entity instanceof Wrapper) {
144
            throw new MappingException('Tried to instantiate mapper on wrapped Entity');
145
        }
146
147
        $entity = $this->resolveEntityClass($entity);
148
149
        $entity = $this->getInverseMorphMap($entity);
150
151
        // Return existing mapper instance if exists.
152
        if (array_key_exists($entity, $this->mappers)) {
153
            return $this->mappers[$entity];
154
        } else {
155
            return $this->buildMapper($entity, $entityMap);
156
        }
157
    }
158
159
    /**
160
     * This method resolve entity class from mappable instances or iterators
161
     *
162
     * @param \Analogue\ORM\Mappable|string|array|\Traversable $entity
163
     * @return string
164
     *
165
     * @throws \InvalidArgumentException
166
     */
167
    protected function resolveEntityClass($entity)
168
    {
169
        // We first check if the entity is traversable and we'll resolve
170
        // the entity based on the first item of the object.   
171
        if ($this->isTraversable($entity)) {
172
            if (! count($entity)) {
173
                throw new \InvalidArgumentException('Length of Entity collection must be greater than 0');
174
            }
175
176
            $firstEntityItem = ($entity instanceof \Iterator)
177
                ? $entity->current()
178
                : current($entity);
179
180
            return $this->resolveEntityClass($firstEntityItem);
181
        }
182
            
183
        if (is_object($entity)) {
184
            return get_class($entity);
185
        }
186
187
        if (is_string($entity)) {
188
            return $entity;
189
        }
190
     
191
        throw new \InvalidArgumentException('Invalid entity type');
192
    }
193
194
    /**
195
     * @param string $key
196
     * @return string
197
     */
198
    public function getInverseMorphMap($key)
199
    {
200
        return array_key_exists($key, $this->morphMap) ? $this->morphMap[$key] : $key;
201
    }
202
203
    /**
204
     * Build a new Mapper instance for a given Entity
205
     *
206
     * @param  string $entity
207
     * @param         $entityMap
208
     * @throws MappingException
209
     * @return Mapper
210
     */
211
    protected function buildMapper($entity, $entityMap)
212
    {
213
        // If an EntityMap hasn't been manually registered by the user
214
        // register it at runtime.
215
        if (!$this->isRegisteredEntity($entity)) {
216
            $this->register($entity, $entityMap);
217
        }
218
219
        $entityMap = $this->entityClasses[$entity];
220
221
        $factory = new MapperFactory($this->drivers, $this->eventDispatcher, $this);
222
223
        $mapper = $factory->make($entity, $entityMap);
224
225
        $this->mappers[$entity] = $mapper;
226
227
        // At this point we can safely call the boot() method on the entityMap as
228
        // the mapper is now instantiated & registered within the manager.
229
230
        $mapper->getEntityMap()->boot();
231
232
        return $mapper;
233
    }
234
235
    /**
236
     * Check if the entity is already registered
237
     *
238
     * @param  string|Entity $entity
239
     * @return boolean
240
     */
241
    public function isRegisteredEntity($entity)
242
    {
243
        if (!is_string($entity)) {
244
            $entity = get_class($entity);
245
        }
246
247
        return array_key_exists($entity, $this->entityClasses);
248
    }
249
250
    /**
251
     * Return true if an object is an array or iterator
252
     *
253
     * @param  mixed $argument
254
     * @return boolean
255
     */
256
    public function isTraversable($argument)
257
    {
258
        return $argument instanceof \Traversable || is_array($argument);
259
    }
260
261
    /**
262
     * Set strict mode for entityMap instantiation
263
     * 
264
     * @param boolean $mode
265
     */
266
    public function setStrictMode($mode)
267
    {
268
        $this->strictMode = $mode;
269
    }
270
271
    /**
272
     * Register an entity
273
     *
274
     * @param  string|\Analogue\ORM\Mappable $entity    entity's class name
275
     * @param  string|EntityMap              $entityMap map's class name
276
     * @throws MappingException
277
     * @return void
278
     */
279
    public function register($entity, $entityMap = null)
280
    {
281
        // If an object is provider, get the class name from it
282
        if (!is_string($entity)) {
283
            $entity = get_class($entity);
284
        }
285
286
        if ($this->isRegisteredEntity($entity)) {
287
            throw new MappingException("Entity $entity is already registered.");
288
        }
289
290
        if (!class_exists($entity)) {
291
            throw new MappingException("Class $entity does not exists");
292
        }
293
294
        if ($entityMap === null) {
295
            $entityMap = $this->getEntityMapInstanceFor($entity);
296
        }
297
298
        if (is_string($entityMap)) {
299
            $entityMap = new $entityMap;
300
        }
301
302
        if (!$entityMap instanceof EntityMap) {
303
            throw new MappingException(get_class($entityMap) . ' must be an instance of EntityMap.');
304
        }
305
306
        $entityMap->setClass($entity);
307
308
        $entityMap->setManager($this);
309
310
        $this->entityClasses[$entity] = $entityMap;
311
    }
312
313
    /**
314
     * Get the entity map instance for a custom entity
315
     *
316
     * @param  string $entity
317
     * @return \Analogue\ORM\Mappable
318
     * @throws EntityMapNotFoundException
319
     */
320
    protected function getEntityMapInstanceFor($entity)
321
    {
322
        if (class_exists($entity . 'Map')) {
323
            $map = $entity . 'Map';
324
            $map = new $map;
325
        } else {
326
            if ($this->strictMode) {
327
                throw new EntityMapNotFoundException("No EntityMap registered for $entity");
328
            }
329
            $map = $this->getNewEntityMap();
330
        }
331
332
        return $map;
333
    }
334
335
    /**
336
     * Dynamically create an entity map for a custom entity class
337
     *
338
     * @return EntityMap
339
     */
340
    protected function getNewEntityMap()
341
    {
342
        return new EntityMap;
343
    }
344
345
    /**
346
     * Return the Singleton instance of the manager
347
     *
348
     * @return Manager
349
     */
350
    public static function getInstance()
351
    {
352
        return static::$instance;
353
    }
354
355
    /**
356
     * Return the Driver Manager's instance
357
     *
358
     * @return \Analogue\ORM\Drivers\Manager
359
     */
360
    public function getDriverManager()
361
    {
362
        return $this->drivers;
363
    }
364
365
    /**
366
     * Get the Repository instance for the given Entity
367
     *
368
     * @param  \Analogue\ORM\Mappable|string $entity
369
     * @throws \InvalidArgumentException
370
     * @throws MappingException
371
     * @return \Analogue\ORM\Repository
372
     */
373
    public function repository($entity)
374
    {
375
        if (!is_string($entity)) {
376
            $entity = get_class($entity);
377
        }
378
379
        // First we check if the repository is not already created.
380
        if (array_key_exists($entity, $this->repositories)) {
381
            return $this->repositories[$entity];
382
        }
383
384
        $this->repositories[$entity] = new Repository($this->mapper($entity));
385
386
        return $this->repositories[$entity];
387
    }
388
389
    /**
390
     * Return true is the object is registered as value object
391
     *
392
     * @param  mixed $object
393
     * @return boolean
394
     */
395
    public function isValueObject($object)
396
    {
397
        if (!is_string($object)) {
398
            $object = get_class($object);
399
        }
400
401
        return array_key_exists($object, $this->valueClasses);
402
    }
403
404
    /**
405
     * Get the Value Map for a given Value Object Class
406
     *
407
     * @param  string $valueObject
408
     * @throws MappingException
409
     * @return \Analogue\ORM\ValueMap
410
     */
411
    public function getValueMap($valueObject)
412
    {
413
        if (!is_string($valueObject)) {
414
            $valueObject = get_class($valueObject);
415
        }
416
417
        if (!array_key_exists($valueObject, $this->valueClasses)) {
418
            $this->registerValueObject($valueObject);
419
        }
420
421
        /** @var ValueMap $valueMap */
422
        $valueMap = new $this->valueClasses[$valueObject];
423
424
        $valueMap->setClass($valueObject);
425
426
        return $valueMap;
427
    }
428
429
    /**
430
     * Register a Value Object
431
     *
432
     * @param  string $valueObject
433
     * @param  string $valueMap
434
     * @throws MappingException
435
     * @return void
436
     */
437
    public function registerValueObject($valueObject, $valueMap = null)
438
    {
439
        if (!is_string($valueObject)) {
440
            $valueObject = get_class($valueObject);
441
        }
442
443
        if ($valueMap === null) {
444
            $valueMap = $valueObject . 'Map';
445
        }
446
447
        if (!class_exists($valueMap)) {
448
            throw new MappingException("$valueMap doesn't exists");
449
        }
450
451
        $this->valueClasses[$valueObject] = $valueMap;
452
    }
453
454
    /**
455
     * Instantiate a new Value Object instance
456
     *
457
     * @param  string $valueObject
458
     * @return \Analogue\ORM\ValueObject
459
     */
460
    public function getValueObjectInstance($valueObject)
461
    {
462
        $prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($valueObject), $valueObject));
463
464
        return $prototype;
465
    }
466
467
    /**
468
     * Register Analogue Plugin
469
     *
470
     * @param  string $plugin class
471
     * @return void
472
     */
473
    public function registerPlugin($plugin)
474
    {
475
        /** @var AnaloguePluginInterface $plugin */
476
        $plugin = new $plugin($this);
477
478
        $this->events = array_merge($this->events, $plugin->getCustomEvents());
479
480
        $plugin->register();
481
    }
482
483
    /**
484
     * Register event listeners that will be fired regardless the type
485
     * of the entity.
486
     *
487
     * @param  string   $event
488
     * @param  \Closure $callback
489
     * @throws \LogicException
490
     * @return void
491
     */
492
    public function registerGlobalEvent($event, $callback)
493
    {
494
        if (!in_array($event, $this->events, false)) {
495
            throw new \LogicException("Analogue : Event $event doesn't exist");
496
        }
497
498
        $this->eventDispatcher->listen("analogue.{$event}.*", $callback);
499
    }
500
501
    /**
502
     * Shortcut to Mapper store
503
     *
504
     * @param  mixed $entity
505
     * @throws MappingException
506
     * @return mixed
507
     * @throws \InvalidArgumentException
508
     */
509
    public function store($entity)
510
    {
511
        return $this->mapper($entity)->store($entity);
512
    }
513
514
    /**
515
     * Shortcut to Mapper delete
516
     *
517
     * @param  mixed $entity
518
     * @throws MappingException
519
     * @return \Illuminate\Support\Collection|null
520
     * @throws \InvalidArgumentException
521
     */
522
    public function delete($entity)
523
    {
524
        return $this->mapper($entity)->delete($entity);
525
    }
526
527
    /**
528
     * Shortcut to Mapper query
529
     *
530
     * @param  mixed $entity
531
     * @throws MappingException
532
     * @return Query
533
     * @throws \InvalidArgumentException
534
     */
535
    public function query($entity)
536
    {
537
        return $this->mapper($entity)->query();
538
    }
539
540
    /**
541
     * Shortcut to Mapper Global Query
542
     *
543
     * @param  mixed $entity
544
     * @throws MappingException
545
     * @return Query
546
     * @throws \InvalidArgumentException
547
     */
548
    public function globalQuery($entity)
549
    {
550
        return $this->mapper($entity)->globalQuery();
551
    }
552
553
    /**
554
     * @param array $morphMap
555
     * @return $this
556
     */
557
    public function morphMap(array $morphMap)
558
    {
559
        $this->morphMap = $morphMap;
560
561
        return $this;
562
    }
563
564
    /**
565
     * @param string $class
566
     * @return mixed
567
     */
568
    public function getMorphMap($class)
569
    {
570
        $key = array_search($class, $this->morphMap, false);
571
572
        return $key !== false ? $key : $class;
573
    }
574
}
575