Completed
Pull Request — 5.1 (#99)
by Kirill
02:37
created

Manager::getEntityMapInstanceFor()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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