Completed
Push — master ( a3e720...45352f )
by Jaap
10s
created

AbstractEntityService::flush()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 6
cts 6
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 5
nc 2
nop 1
crap 2
1
<?php
2
/**
3
 * Polder Knowledge / entityservice (https://polderknowledge.com)
4
 *
5
 * @link https://github.com/polderknowledge/entityservice for the canonical source repository
6
 * @copyright Copyright (c) 2016 Polder Knowledge (https://polderknowledge.com)
7
 * @license https://github.com/polderknowledge/entityservice/blob/master/LICENSE.md MIT
8
 */
9
10
namespace PolderKnowledge\EntityService;
11
12
use Doctrine\Common\Collections\Collection;
13
use Doctrine\Common\Collections\Criteria;
14
use PolderKnowledge\EntityService\Event\EntityEvent;
15
use PolderKnowledge\EntityService\Exception\RuntimeException;
16
use PolderKnowledge\EntityService\Exception\ServiceException;
17
use PolderKnowledge\EntityService\Feature\TransactionAwareInterface;
18
use PolderKnowledge\EntityService\Repository\EntityRepositoryInterface;
19
use PolderKnowledge\EntityService\Repository\Feature\DeletableInterface;
20
use PolderKnowledge\EntityService\Repository\Feature\FlushableInterface;
21
use PolderKnowledge\EntityService\Repository\Feature\ReadableInterface;
22
use PolderKnowledge\EntityService\Repository\Feature\WritableInterface;
23
use Traversable;
24
use Zend\EventManager\AbstractListenerAggregate;
25
use Zend\EventManager\EventManager;
26
use Zend\EventManager\EventManagerInterface;
27
28
/**
29
 * Base class for application specific EntityServices. This is a fully event driven class.
30
 * Each method trigger a method for extendability.
31
 */
32
abstract class AbstractEntityService extends AbstractListenerAggregate implements
33
    EntityServiceInterface,
34
    TransactionAwareInterface
35
{
36
    /**
37
     * The repository that is used for this entity service.
38
     *
39
     * @var EntityRepositoryInterface
40
     */
41
    private $repository;
42
43
    /**
44
     * EventManager handling all events triggered by this service
45
     *
46
     * @var EventManagerInterface
47
     */
48
    private $eventManager;
49
50
    /**
51
     * Initialized Event
52
     *
53
     * @var EntityEvent
54
     */
55
    private $event;
56
57
    /**
58
     * Initializes a new instance of this class.
59
     *
60 90
     * @param EntityRepositoryInterface $repository The repository that is used to communicate with.
61
     * @param string $entityClassName The FQCN of the entity.
62 90
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
63 90
     */
64
    public function __construct(EntityRepositoryInterface $repository, $entityClassName)
65 90
    {
66 90
        $this->repository = $repository;
67
        $this->listeners = [];
68
69
        $this->getEvent()->setEntityClassName($entityClassName);
70
    }
71
72
    /**
73 75
     * Gets the repository that is used by the service.
74
     *
75 75
     * @return EntityRepositoryInterface
76
     */
77
    protected function getRepository()
78
    {
79
        return $this->repository;
80
    }
81
82
    /**
83 90
     * Get the pre initialized event object
84
     *
85 90
     * @return EntityEvent
86 90
     */
87
    protected function getEvent()
88 90
    {
89 90
        if (null === $this->event) {
90
            $this->event = $event = new EntityEvent;
91 90
92
            $event->setTarget($this);
93
        }
94
95
        return $this->event;
96
    }
97
98
    /**
99
     * Will create an EventManager when no EventManager was provided.
100 45
     * The returned EventManager is used to handle events triggered by this service instance.
101
     *
102 45
     * @return EventManagerInterface
103 36
     */
104 36
    public function getEventManager()
105
    {
106 45
        if (null === $this->eventManager) {
107
            $this->setEventManager(new EventManager);
108
        }
109
110
        return $this->eventManager;
111
    }
112
113
    /**
114
     * Set the EventManager used by this service instance to handle its events.
115
     * It will take care of disabling the old EventManager and will subscribe the internal
116 45
     * listeners to the new EventManager
117
     *
118 45
     * @param EventManagerInterface $eventManager
119 3
     */
120
    public function setEventManager(EventManagerInterface $eventManager)
121
    {
122 45
        if ($this->eventManager === $eventManager || $eventManager === null) {
123 3
            return;
124 3
        }
125
126 45
        if ($this->eventManager !== null) {
127 45
            $this->detach($this->eventManager);
128 45
        }
129 45
130 45
        $this->eventManager = $eventManager;
131 45
        $this->eventManager->addIdentifiers([
132 45
            'EntityService',
133
            'PolderKnowledge\EntityService\Service\EntityService',
134 45
            $this->getEntityServiceName(),
135 45
            trim($this->getEntityServiceName(), '\\'),
136
        ]);
137
138
        $this->attach($this->eventManager);
139
    }
140 45
141
    /**
142
     * {@inheritDoc}
143 27
     */
144
    public function attach(EventManagerInterface $events, $priority = 1)
145 27
    {
146 27 View Code Duplication
        $callback = function (EntityEvent $event) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147 27
            $repository = $event->getTarget()->getRepository();
148 27
149 45
            $event->setResult(call_user_func_array(
150
                [$repository, $event->getName()],
151 45
                $event->getParams()
152 45
            ));
153 45
        };
154 45
155 45
        $this->listeners[] = $events->attach('countBy', $callback, 0);
156 45
        $this->listeners[] = $events->attach('delete', $callback, 0);
157 45
        $this->listeners[] = $events->attach('deleteBy', $callback, 0);
158 45
        $this->listeners[] = $events->attach('find', $callback, 0);
159 45
        $this->listeners[] = $events->attach('findAll', $callback, 0);
160
        $this->listeners[] = $events->attach('findBy', $callback, 0);
161 45
        $this->listeners[] = $events->attach('findOneBy', $callback, 0);
162 27
        $this->listeners[] = $events->attach(
163 45
            'persist',
164 45 View Code Duplication
            function (EntityEvent $event) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
                $repository = $event->getTarget()->getRepository();
166
167
                call_user_func_array([$repository, 'persist'], $event->getParams());
168
169
                if ($repository instanceof FlushableInterface) {
170
                    $repository->flush();
171 81
                }
172
            },
173 81
            0
174
        );
175
        $this->listeners[] = $events->attach(
176
            'multiPersist',
177
            function (EntityEvent $event) {
178
                $repository = $event->getTarget()->getRepository();
179
180
                $entities = current($event->getParams());
181
182 6
                foreach ($entities as $entity) {
183
                    call_user_func_array([$repository, 'persist'], [$entity]);
184 6
                }
185 3
186
                if ($repository instanceof FlushableInterface) {
187
                    $repository->flush();
188 3
                }
189 3
            },
190 3
            0
191
        );
192
193
        $this->listeners[] = $events->attach('*', function (EntityEvent $event) {
194
            $event->disableStoppingOfPropagation();
195
        }, -1);
196
    }
197
198
    /**
199 6
     * Returns the FQCN of the entity handled by this service.
200
     *
201 6
     * @return string
202 3
     */
203
    protected function getEntityServiceName()
204
    {
205 3
        return $this->getEvent()->getEntityClassName();
206 3
    }
207 3
208
    /**
209
     * Deletes the given object from the repository
210
     *
211
     * @param object $entity The entity to delete.
212
     * @return mixed
213
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
214
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
215
     */
216 6
    public function delete($entity)
217
    {
218 6
        if (!$this->isRepositoryDeletable()) {
219 3
            throw $this->createNotDeletableException();
220
        }
221
222 3
        return $this->trigger(__FUNCTION__, [
223 3
            'entity' => $entity,
224 3
        ]);
225
    }
226
227
    /**
228
     * Deletes all objects matching the criteria from the repository
229
     *
230
     * @param array|Criteria $criteria The criteria values to match on.
231
     * @return mixed
232
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
233 9
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
234
     */
235 9
    public function deleteBy($criteria)
236 3
    {
237
        if (!$this->isRepositoryDeletable()) {
238
            throw $this->createNotDeletableException();
239 6
        }
240 6
241 6
        return $this->trigger(__FUNCTION__, [
242
            'criteria' => $criteria,
243
        ]);
244
    }
245
246
    /**
247
     * Count the objects matching the criteria respecting the order, limit and offset.
248
     *
249 6
     * @param array|Criteria $criteria The criteria values to match on.
250
     * @return int
251 6
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
252 3
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
253
     */
254
    public function countBy($criteria)
255 3
    {
256
        if (!$this->isRepositoryReadable()) {
257
            throw $this->createNotReadableException();
258
        }
259
260
        return $this->trigger(__FUNCTION__, [
261
            'criteria' => $criteria,
262
        ]);
263
    }
264 6
265
    /**
266 6
     * Find one object in the repository matching the $id
267 3
     *
268
     * @param mixed $id The id of the entity.
269
     * @return object|null
270 3
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
271 3
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
272 3
     */
273
    public function find($id)
274
    {
275
        if (!$this->isRepositoryReadable()) {
276
            throw $this->createNotReadableException();
277
        }
278
279
        return $this->trigger(__FUNCTION__, [
280
            'id' => $id,
281 6
        ]);
282
    }
283 6
284 3
    /**
285
     * Finds all entities in the repository.
286
     *
287 3
     * @return array Returns the entities that exist.
288 3
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
289 3
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
290
     */
291
    public function findAll()
292
    {
293
        if (!$this->isRepositoryReadable()) {
294
            throw $this->createNotReadableException();
295
        }
296
297
        return $this->trigger(__FUNCTION__, []);
298
    }
299 6
300
    /**
301 6
     * Find one or more objects in the repository matching the criteria respecting the order, limit and offset
302 3
     *
303
     * @param array|Criteria $criteria The array with criteria to search on.
304
     * @return array
305 3
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
306 3
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
307 3
     */
308
    public function findBy($criteria)
309
    {
310
        if (!$this->isRepositoryReadable()) {
311
            throw $this->createNotReadableException();
312
        }
313
314
        return $this->trigger(__FUNCTION__, [
315
            'criteria' => $criteria,
316
        ]);
317 6
    }
318
319 6
    /**
320 3
     * Find one object in the repository matching the criteria
321
     *
322
     * @param array|Criteria $criteria The criteria values to match on.
323 3
     * @return object|null
324 3
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
325 3
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
326
     */
327
    public function findOneBy($criteria)
328
    {
329
        if (!$this->isRepositoryReadable()) {
330
            throw $this->createNotReadableException();
331
        }
332
333
        return $this->trigger(__FUNCTION__, [
334
            'criteria' => $criteria,
335 30
        ]);
336
    }
337 30
338 30
    /**
339 30
     * Persist the given entity
340
     *
341 30
     * @param object $entity
342
     * @return mixed
343 30
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
344 3
     * @throws RuntimeException
345
     */
346
    public function persist($entity)
347 27
    {
348
        if (!$this->isRepositoryWritable()) {
349
            throw $this->createNotWritableException();
350
        }
351
352
        return $this->trigger(__FUNCTION__, [
353
            'entity' => $entity,
354
        ]);
355 6
    }
356
357 6
    /**
358 3
     * Persist the given object and flushes it to the storage device.
359 3
     *
360 3
     * @param array|Collection|Traversable $entities The entities to persist.
361 3
     * @return mixed
362
     * @throws RuntimeException
363
     */
364 3
    public function multiPersist($entities)
365 3
    {
366
        if (!$this->isRepositoryWritable()) {
367
            throw $this->createNotWritableException();
368
        }
369
370
        return $this->trigger(__FUNCTION__, [
371
            'entities' => $entities,
372 6
        ]);
373
    }
374 6
375 3
    /**
376 3
     * will prepare the event object and trigger the event using the internal EventManager
377 3
     *
378 3
     * @param  string $name
379
     * @param  array $params
380
     * @return mixed
381 3
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
382 3
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
383
     */
384
    protected function trigger($name, array $params)
385
    {
386
        $event = clone $this->getEvent();
387
        $event->setName($name);
388
        $event->setParams($params);
389 6
390
        $responseCollection = $this->getEventManager()->triggerEvent($event);
391 6
392 3
        if ($responseCollection->stopped() && $event->isError()) {
393 3
            throw new RuntimeException($event->getError(), $event->getErrorNr());
394 3
        }
395 3
396
        return $event->getResult();
397
    }
398 3
399 3
    /**
400
     * Starts a new transaction.
401
     *
402
     * @throws ServiceException
403
     */
404 18
    public function beginTransaction()
405
    {
406 18
        if ($this->isTransactionEnabled() === false) {
407
            throw new ServiceException(sprintf(
408
                'The repository for %s doesn\'t support Transactions',
409
                $this->getEntityServiceName()
410
            ));
411
        }
412
413
        $this->getRepository()->beginTransaction();
414 12
    }
415
416 12
    /**
417
     * Commits a started transaction.
418
     *
419
     * @throws ServiceException
420
     */
421
    public function commitTransaction()
422
    {
423
        if ($this->isTransactionEnabled() === false) {
424 33
            throw new ServiceException(sprintf(
425
                'The repository for %s doesn\'t support Transactions',
426 33
                $this->getEntityServiceName()
427
            ));
428
        }
429
430
        $this->getRepository()->commitTransaction();
431
    }
432
433
    /**
434 12
     * Rolls back a started transaction.
435
     *
436 12
     * @throws ServiceException
437
     */
438
    public function rollbackTransaction()
439
    {
440
        if ($this->isTransactionEnabled() === false) {
441
            throw new ServiceException(sprintf(
442
                'The repository for %s doesn\'t support Transactions',
443
                $this->getEntityServiceName()
444 6
            ));
445
        }
446 6
447 6
        $this->getRepository()->rollbackTransaction();
448 6
    }
449 6
450
    /**
451
     * Returns true when possible to start an transaction
452
     */
453
    public function isTransactionEnabled()
454
    {
455
        return $this->getRepository() instanceof TransactionAwareInterface;
456
    }
457 15
458
    /**
459 15
     * Returns true when the repository for $entityName is writable
460 15
     *
461 15
     * @return bool
462 15
     */
463
    protected function isRepositoryWritable()
464
    {
465
        return $this->getRepository() instanceof WritableInterface;
466
    }
467
468
    /**
469
     * Returns true when the repository for $entityName is readable
470 6
     *
471
     * @return bool
472 6
     */
473 6
    protected function isRepositoryReadable()
474 6
    {
475 6
        return $this->getRepository() instanceof ReadableInterface;
476
    }
477
478
    /**
479
     * Returns true when the repository for $entityName has delete behavior
480
     *
481
     * @return bool
482
     */
483
    protected function isRepositoryDeletable()
484
    {
485
        return $this->getRepository() instanceof DeletableInterface;
486
    }
487
488
    /**
489
     * Throws an exception for cases where it's not possible to delete from the repository.
490
     *
491
     * @throws RuntimeException
492
     */
493
    private function createNotDeletableException()
494
    {
495
        throw new RuntimeException(sprintf(
496
            'The entities of type "%s" cannot be deleted from its repository.',
497
            $this->getEntityServiceName()
498
        ));
499
    }
500
501
    /**
502
     * Throws an exception for cases where it's not possible to read from the repository.
503
     *
504
     * @throws RuntimeException
505
     */
506
    private function createNotReadableException()
507
    {
508
        throw new RuntimeException(sprintf(
509
            'It is not possible to read entities of type "%s" from its repository.',
510
            $this->getEntityServiceName()
511
        ));
512
    }
513
514
    /**
515
     * Throws an exception for cases where it's not possible to write to the repository.
516
     *
517
     * @throws RuntimeException
518
     */
519
    private function createNotWritableException()
520
    {
521
        throw new RuntimeException(sprintf(
522
            'The entities of type "%s" cannot be written to its repository.',
523
            $this->getEntityServiceName()
524
        ));
525
    }
526
}
527