Completed
Push — 5.1 ( a89502...c13850 )
by Rémi
02:48
created

Manager::setStrictMode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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 = false;
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 || $entity instanceof \IteratorAggregate)
173
                ? $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...
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