Completed
Push — 5.1 ( f422a4...a89502 )
by Rémi
8s
created

Manager::resolveEntityClass()   C

Complexity

Conditions 7
Paths 8

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 24
rs 6.7272
cc 7
eloc 15
nc 8
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 Analogue\ORM\System\Wrappers\Wrapper;
9
use Illuminate\Contracts\Events\Dispatcher;
10
use Analogue\ORM\Exceptions\MappingException;
11
use Analogue\ORM\Drivers\Manager as DriverManager;
12
13
/**
14
 * This class keeps track of instantiated mappers, and entity <-> entityMap associations
15
 */
16
class Manager
17
{
18
    /**
19
     * Manager instance
20
     *
21
     * @var Manager
22
     */
23
    protected static $instance;
24
25
    /**
26
     * Driver Manager
27
     *
28
     * @var \Analogue\ORM\Drivers\Manager
29
     */
30
    protected $drivers;
31
32
    /**
33
     * Registered entity classes and corresponding map objects.
34
     *
35
     * @var array
36
     */
37
    protected $entityClasses = [];
38
39
    /**
40
     * Key value store of ValueObject Classes and corresponding map classes
41
     *
42
     * @var array
43
     */
44
    protected $valueClasses = [];
45
46
    /**
47
     * Morph map
48
     */
49
    protected $morphMap = [];
50
51
    /**
52
     * Loaded Mappers
53
     *
54
     * @var array
55
     */
56
    protected $mappers = [];
57
58
    /**
59
     * Loaded Repositories
60
     *
61
     * @var array
62
     */
63
    protected $repositories = [];
64
65
    /**
66
     * Event dispatcher instance
67
     *
68
     * @var \Illuminate\Contracts\Events\Dispatcher
69
     */
70
    protected $eventDispatcher;
71
72
    /**
73
     * Available Analogue Events
74
     *
75
     * @var array
76
     */
77
    protected $events = [
78
        'initializing',
79
        'initialized',
80
        'store',
81
        'stored',
82
        'creating',
83
        'created',
84
        'updating',
85
        'updated',
86
        'deleting',
87
        'deleted',
88
    ];
89
90
    /**
91
     * @param \Analogue\ORM\Drivers\Manager $driverManager
92
     * @param Dispatcher                    $event
93
     */
94
    public function __construct(DriverManager $driverManager, Dispatcher $event)
95
    {
96
        $this->drivers = $driverManager;
97
98
        $this->eventDispatcher = $event;
99
100
        static::$instance = $this;
101
    }
102
103
    /**
104
     * Create a mapper for a given entity (static alias)
105
     *
106
     * @param  \Analogue\ORM\Mappable|string $entity
107
     * @param  null|EntityMap                $entityMap
108
     * @throws MappingException
109
     * @return Mapper
110
     */
111
    public static function getMapper($entity, $entityMap = null)
112
    {
113
        return static::$instance->mapper($entity, $entityMap);
114
    }
115
116
    /**
117
     * Create a mapper for a given entity
118
     *
119
     * @param  \Analogue\ORM\Mappable|string|array|\Traversable $entity
120
     * @param  mixed                                            $entityMap
121
     * @throws MappingException
122
     * @throws \InvalidArgumentException
123
     * @return Mapper
124
     */
125
    public function mapper($entity, $entityMap = null)
126
    {
127
        if ($entity instanceof Wrapper) {
128
            throw new MappingException('Tried to instantiate mapper on wrapped Entity');
129
        }
130
131
        $entity = $this->resolveEntityClass($entity);
132
133
        $entity = $this->getInverseMorphMap($entity);
134
135
        // Return existing mapper instance if exists.
136
        if (array_key_exists($entity, $this->mappers)) {
137
            return $this->mappers[$entity];
138
        } else {
139
            return $this->buildMapper($entity, $entityMap);
140
        }
141
    }
142
143
    /**
144
     * This method resolve entity class from mappable instances or iterators
145
     *
146
     * @param \Analogue\ORM\Mappable|string|array|\Traversable $entity
147
     * @return string
148
     */
149
    protected function resolveEntityClass($entity)
150
    {
151
        switch (true) {
152
            case Support::isTraversable($entity):
153
                if (!count($entity)) {
154
                    throw new \InvalidArgumentException('Length of Entity collection must be greater than 0');
155
                }
156
157
                $firstEntityItem = ($entity instanceof \Iterator || $entity instanceof \IteratorAggregate)
158
                    ? $entity->current()
0 ignored issues
show
Bug introduced by
The method current does only exist in Iterator, but not in IteratorAggregate.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
159
                    : current($entity);
160
161
                return $this->resolveEntityClass($firstEntityItem);
162
163
            case is_object($entity):
164
                return get_class($entity);
165
166
            case !is_string($entity):
167
                throw new \InvalidArgumentException('Invalid mapper Entity type');
168
                break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
169
        }
170
171
        return $entity;
172
    }
173
174
    /**
175
     * @param string $key
176
     * @return string
177
     */
178
    public function getInverseMorphMap($key)
179
    {
180
        return array_key_exists($key, $this->morphMap) ? $this->morphMap[$key] : $key;
181
    }
182
183
    /**
184
     * Build a new Mapper instance for a given Entity
185
     *
186
     * @param  string $entity
187
     * @param         $entityMap
188
     * @throws MappingException
189
     * @return Mapper
190
     */
191
    protected function buildMapper($entity, $entityMap)
192
    {
193
        // If an EntityMap hasn't been manually registered by the user
194
        // register it at runtime.
195
        if (!$this->isRegisteredEntity($entity)) {
196
            $this->register($entity, $entityMap);
197
        }
198
199
        $entityMap = $this->entityClasses[$entity];
200
201
        $factory = new MapperFactory($this->drivers, $this->eventDispatcher, $this);
202
203
        $mapper = $factory->make($entity, $entityMap);
204
205
        $this->mappers[$entity] = $mapper;
206
207
        // At this point we can safely call the boot() method on the entityMap as
208
        // the mapper is now instantiated & registered within the manager.
209
210
        $mapper->getEntityMap()->boot();
211
212
        return $mapper;
213
    }
214
215
    /**
216
     * Check if the entity is already registered
217
     *
218
     * @param  string|object $entity
219
     * @return boolean
220
     */
221
    public function isRegisteredEntity($entity)
222
    {
223
        if (!is_string($entity)) {
224
            $entity = get_class($entity);
225
        }
226
227
        return array_key_exists($entity, $this->entityClasses);
228
    }
229
230
    /**
231
     * Register an entity
232
     *
233
     * @param  string|\Analogue\ORM\Mappable $entity    entity's class name
234
     * @param  string|EntityMap              $entityMap map's class name
235
     * @throws MappingException
236
     * @return void
237
     */
238
    public function register($entity, $entityMap = null)
239
    {
240
        // If an object is provider, get the class name from it
241
        if (!is_string($entity)) {
242
            $entity = get_class($entity);
243
        }
244
245
        if ($this->isRegisteredEntity($entity)) {
246
            throw new MappingException("Entity $entity is already registered.");
247
        }
248
249
        if (!class_exists($entity)) {
250
            throw new MappingException("Class $entity does not exists");
251
        }
252
253
        if (is_null($entityMap)) {
254
            $entityMap = $this->getEntityMapInstanceFor($entity);
255
        }
256
257
        if (is_string($entityMap)) {
258
            $entityMap = new $entityMap;
259
        }
260
261
        if (!$entityMap instanceof EntityMap) {
262
            throw new MappingException(get_class($entityMap) . ' must be an instance of EntityMap.');
263
        }
264
265
        $entityMap->setClass($entity);
266
267
        $entityMap->setManager($this);
268
269
        $this->entityClasses[$entity] = $entityMap;
270
    }
271
272
    /**
273
     * Get the entity map instance for a custom entity
274
     *
275
     * @param  string $entity
276
     * @return \Analogue\ORM\Mappable
277
     */
278
    protected function getEntityMapInstanceFor($entity)
279
    {
280
        if (class_exists($entity . 'Map')) {
281
            $map = $entity . 'Map';
282
            $map = new $map;
283
        } else {
284
            // Generate an EntityMap object
285
            $map = $this->getNewEntityMap();
286
        }
287
288
        return $map;
289
    }
290
291
    /**
292
     * Dynamically create an entity map for a custom entity class
293
     *
294
     * @return EntityMap
295
     */
296
    protected function getNewEntityMap()
297
    {
298
        return new EntityMap;
299
    }
300
301
    /**
302
     * Return the Singleton instance of the manager
303
     *
304
     * @return Manager
305
     */
306
    public static function getInstance()
307
    {
308
        return static::$instance;
309
    }
310
311
    /**
312
     * Return the Driver Manager's instance
313
     *
314
     * @return \Analogue\ORM\Drivers\Manager
315
     */
316
    public function getDriverManager()
317
    {
318
        return $this->drivers;
319
    }
320
321
    /**
322
     * Get the Repository instance for the given Entity
323
     *
324
     * @param  \Analogue\ORM\Mappable|string $entity
325
     * @throws \InvalidArgumentException
326
     * @throws MappingException
327
     * @return \Analogue\ORM\Repository
328
     */
329
    public function repository($entity)
330
    {
331
        if (!is_string($entity)) {
332
            $entity = get_class($entity);
333
        }
334
335
        // First we check if the repository is not already created.
336
        if (array_key_exists($entity, $this->repositories)) {
337
            return $this->repositories[$entity];
338
        }
339
340
        $this->repositories[$entity] = new Repository($this->mapper($entity));
341
342
        return $this->repositories[$entity];
343
    }
344
345
    /**
346
     * Return true is the object is registered as value object
347
     *
348
     * @param  mixed $object
349
     * @return boolean
350
     */
351
    public function isValueObject($object)
352
    {
353
        if (!is_string($object)) {
354
            $object = get_class($object);
355
        }
356
357
        return array_key_exists($object, $this->valueClasses);
358
    }
359
360
    /**
361
     * Get the Value Map for a given Value Object Class
362
     *
363
     * @param  string $valueObject
364
     * @throws MappingException
365
     * @return \Analogue\ORM\ValueMap
366
     */
367
    public function getValueMap($valueObject)
368
    {
369
        if (!is_string($valueObject)) {
370
            $valueObject = get_class($valueObject);
371
        }
372
373
        if (!array_key_exists($valueObject, $this->valueClasses)) {
374
            $this->registerValueObject($valueObject);
375
        }
376
        $valueMap = new $this->valueClasses[$valueObject];
377
378
        $valueMap->setClass($valueObject);
379
380
        return $valueMap;
381
    }
382
383
    /**
384
     * Register a Value Object
385
     *
386
     * @param  string $valueObject
387
     * @param  string $valueMap
388
     * @throws MappingException
389
     * @return void
390
     */
391
    public function registerValueObject($valueObject, $valueMap = null)
392
    {
393
        if (!is_string($valueObject)) {
394
            $valueObject = get_class($valueObject);
395
        }
396
397
        if (is_null($valueMap)) {
398
            $valueMap = $valueObject . 'Map';
399
        }
400
401
        if (!class_exists($valueMap)) {
402
            throw new MappingException("$valueMap doesn't exists");
403
        }
404
405
        $this->valueClasses[$valueObject] = $valueMap;
406
    }
407
408
    /**
409
     * Instantiate a new Value Object instance
410
     *
411
     * @param  string $valueObject
412
     * @return \Analogue\ORM\ValueObject
413
     */
414
    public function getValueObjectInstance($valueObject)
415
    {
416
        $prototype = unserialize(sprintf('O:%d:"%s":0:{}', strlen($valueObject), $valueObject));
417
418
        return $prototype;
419
    }
420
421
    /**
422
     * Register Analogue Plugin
423
     *
424
     * @param  string $plugin class
425
     * @return void
426
     */
427
    public function registerPlugin($plugin)
428
    {
429
        $plugin = new $plugin($this);
430
431
        $this->events = array_merge($this->events, $plugin->getCustomEvents());
432
433
        $plugin->register();
434
    }
435
436
    /**
437
     * Register event listeners that will be fired regardless the type
438
     * of the entity.
439
     *
440
     * @param  string   $event
441
     * @param  \Closure $callback
442
     * @throws \Exception
443
     * @return void
444
     */
445
    public function registerGlobalEvent($event, $callback)
446
    {
447
        if (!in_array($event, $this->events)) {
448
            throw new \Exception("Analogue : Event $event doesn't exist");
449
        }
450
        $this->eventDispatcher->listen("analogue.{$event}.*", $callback);
451
    }
452
453
    /**
454
     * Shortcut to Mapper store
455
     *
456
     * @param  mixed $entity
457
     * @throws MappingException
458
     * @return mixed
459
     */
460
    public function store($entity)
461
    {
462
        return $this->mapper($entity)->store($entity);
463
    }
464
465
    /**
466
     * Shortcut to Mapper delete
467
     *
468
     * @param  mixed $entity
469
     * @throws MappingException
470
     * @return \Illuminate\Support\Collection|null
471
     */
472
    public function delete($entity)
473
    {
474
        return $this->mapper($entity)->delete($entity);
475
    }
476
477
    /**
478
     * Shortcut to Mapper query
479
     *
480
     * @param  mixed $entity
481
     * @throws MappingException
482
     * @return Query
483
     */
484
    public function query($entity)
485
    {
486
        return $this->mapper($entity)->query();
487
    }
488
489
    /**
490
     * Shortcut to Mapper Global Query
491
     *
492
     * @param  mixed $entity
493
     * @throws MappingException
494
     * @return Query
495
     */
496
    public function globalQuery($entity)
497
    {
498
        return $this->mapper($entity)->globalQuery();
499
    }
500
501
    /**
502
     * @param array $morphMap
503
     * @return $this
504
     */
505
    public function morphMap(array $morphMap)
506
    {
507
        $this->morphMap = $morphMap;
508
509
        return $this;
510
    }
511
512
    /**
513
     * @param string $class
514
     * @return mixed
515
     */
516
    public function getMorphMap($class)
517
    {
518
        $key = array_search($class, $this->morphMap);
519
520
        return $key !== false ? $key : $class;
521
    }
522
}
523