Completed
Pull Request — master (#3)
by Jaap
14:12 queued 12:33
created

createNotWritableException()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
crap 1
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\Criteria;
13
use PolderKnowledge\EntityService\Event\EntityEvent;
14
use PolderKnowledge\EntityService\Exception\RuntimeException;
15
use PolderKnowledge\EntityService\Exception\ServiceException;
16
use PolderKnowledge\EntityService\Feature\TransactionAwareInterface;
17
use PolderKnowledge\EntityService\Repository\EntityRepositoryInterface;
18
use PolderKnowledge\EntityService\Repository\Feature\DeletableInterface;
19
use PolderKnowledge\EntityService\Repository\Feature\ReadableInterface;
20
use PolderKnowledge\EntityService\Repository\Feature\WritableInterface;
21
use Zend\EventManager\AbstractListenerAggregate;
22
use Zend\EventManager\EventManager;
23
use Zend\EventManager\EventManagerInterface;
24
25
/**
26
 * Base class for application specific EntityServices. This is a fully event driven class.
27
 * Each method trigger a method for extendability.
28
 */
29
abstract class AbstractEntityService extends AbstractListenerAggregate implements
30
    EntityServiceInterface,
31
    TransactionAwareInterface
32
{
33
    /**
34
     * The repository that is used for this entity service.
35
     *
36
     * @var EntityRepositoryInterface
37
     */
38
    private $repository;
39
40
    /**
41
     * EventManager handling all events triggered by this service
42
     *
43
     * @var EventManagerInterface
44
     */
45
    private $eventManager;
46
47
    /**
48
     * Initialized Event
49
     *
50
     * @var EntityEvent
51
     */
52
    private $event;
53
54
    /**
55
     * Initializes a new instance of this class.
56
     *
57
     * @param EntityRepositoryInterface $repository The repository that is used to communicate with.
58
     * @param string $entityClassName The FQCN of the entity.
59
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
60
     */
61 90
    public function __construct(EntityRepositoryInterface $repository, $entityClassName)
62
    {
63 90
        $this->repository = $repository;
64 90
        $this->listeners = [];
65
66 90
        $this->getEvent()->setEntityClassName($entityClassName);
67 90
    }
68
69
    /**
70
     * Gets the repository that is used by the service.
71
     *
72
     * @return EntityRepositoryInterface
73
     */
74 75
    protected function getRepository()
75
    {
76 75
        return $this->repository;
77
    }
78
79
    /**
80
     * Get the pre initialized event object
81
     *
82
     * @return EntityEvent
83
     */
84 90
    protected function getEvent()
85
    {
86 90
        if (null === $this->event) {
87 90
            $this->event = $event = new EntityEvent;
88
89 90
            $event->setTarget($this);
90 90
        }
91
92 90
        return $this->event;
93
    }
94
95
    /**
96
     * Will create an EventManager when no EventManager was provided.
97
     * The returned EventManager is used to handle events triggered by this service instance.
98
     *
99
     * @return EventManagerInterface
100
     */
101 45
    public function getEventManager()
102
    {
103 45
        if (null === $this->eventManager) {
104 36
            $this->setEventManager(new EventManager);
105 36
        }
106
107 45
        return $this->eventManager;
108
    }
109
110
    /**
111
     * Set the EventManager used by this service instance to handle its events.
112
     * It will take care of disabling the old EventManager and will subscribe the internal
113
     * listeners to the new EventManager
114
     *
115
     * @param EventManagerInterface $eventManager
116
     */
117 45
    public function setEventManager(EventManagerInterface $eventManager)
118
    {
119 45
        if ($this->eventManager === $eventManager || $eventManager === null) {
120 3
            return;
121
        }
122
123 45
        if ($this->eventManager !== null) {
124 3
            $this->detach($this->eventManager);
125 3
        }
126
127 45
        $this->eventManager = $eventManager;
128 45
        $this->eventManager->addIdentifiers([
129 45
            'EntityService',
130 45
            'PolderKnowledge\EntityService\Service\EntityService',
131 45
            $this->getEntityServiceName(),
132 45
            trim($this->getEntityServiceName(), '\\'),
133 45
        ]);
134
135 45
        $this->attach($this->eventManager);
136 45
    }
137
138
    /**
139
     * {@inheritDoc}
140
     */
141 45
    public function attach(EventManagerInterface $events, $priority = 1)
142
    {
143
        $callback = function (EntityEvent $event) {
144 27
            $repository = $event->getTarget()->getRepository();
145
146 27
            $event->setResult(call_user_func_array(
147 27
                [$repository, $event->getName()],
148 27
                $event->getParams()
149 27
            ));
150 45
        };
151
152 45
        $this->listeners[] = $events->attach('countBy', $callback, 0);
153 45
        $this->listeners[] = $events->attach('delete', $callback, 0);
154 45
        $this->listeners[] = $events->attach('deleteBy', $callback, 0);
155 45
        $this->listeners[] = $events->attach('find', $callback, 0);
156 45
        $this->listeners[] = $events->attach('findAll', $callback, 0);
157 45
        $this->listeners[] = $events->attach('findBy', $callback, 0);
158 45
        $this->listeners[] = $events->attach('findOneBy', $callback, 0);
159 45
        $this->listeners[] = $events->attach('persist', $callback, 0);
160 45
        $this->listeners[] = $events->attach('flush', $callback, 0);
161
162 45
        $this->listeners[] = $events->attach('*', function (EntityEvent $event) {
163 27
            $event->disableStoppingOfPropagation();
164 45
        }, -1);
165 45
    }
166
167
    /**
168
     * Returns the FQCN of the entity handled by this service.
169
     *
170
     * @return string
171
     */
172 81
    protected function getEntityServiceName()
173
    {
174 81
        return $this->getEvent()->getEntityClassName();
175
    }
176
177
    /**
178
     * Deletes the given object from the repository
179
     *
180
     * @param object $entity The entity to delete.
181
     * @return mixed
182
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
183
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
184
     */
185 6
    public function delete($entity)
186
    {
187 6
        if (!$this->isRepositoryDeletable()) {
188 3
            throw $this->createNotDeletableException();
189
        }
190
191 3
        return $this->trigger(__FUNCTION__, [
192 3
            'entity' => $entity,
193 3
        ]);
194
    }
195
196
    /**
197
     * Deletes all objects matching the criteria from the repository
198
     *
199
     * @param array|Criteria $criteria The criteria values to match on.
200
     * @return mixed
201
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
202
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
203
     */
204 6
    public function deleteBy($criteria)
205
    {
206 6
        if (!$this->isRepositoryDeletable()) {
207 3
            throw $this->createNotDeletableException();
208
        }
209
210 3
        return $this->trigger(__FUNCTION__, [
211 3
            'criteria' => $criteria,
212 3
        ]);
213
    }
214
215
    /**
216
     * Count the objects matching the criteria respecting the order, limit and offset.
217
     *
218
     * @param array|Criteria $criteria The criteria values to match on.
219
     * @return int
220
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
221
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
222
     */
223 6
    public function countBy($criteria)
224
    {
225 6
        if (!$this->isRepositoryReadable()) {
226 3
            throw $this->createNotReadableException();
227
        }
228
229 3
        return $this->trigger(__FUNCTION__, [
230 3
            'criteria' => $criteria,
231 3
        ]);
232
    }
233
234
    /**
235
     * Find one object in the repository matching the $id
236
     *
237
     * @param mixed $id The id of the entity.
238
     * @return object|null
239
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
240
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
241
     */
242 9
    public function find($id)
243
    {
244 9
        if (!$this->isRepositoryReadable()) {
245 3
            throw $this->createNotReadableException();
246
        }
247
248 6
        return $this->trigger(__FUNCTION__, [
249 6
            'id' => $id,
250 6
        ]);
251
    }
252
253
    /**
254
     * Finds all entities in the repository.
255
     *
256
     * @return array Returns the entities that exist.
257
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
258
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
259
     */
260 6
    public function findAll()
261
    {
262 6
        if (!$this->isRepositoryReadable()) {
263 3
            throw $this->createNotReadableException();
264
        }
265
266 3
        return $this->trigger(__FUNCTION__, []);
267
    }
268
269
    /**
270
     * Find one or more objects in the repository matching the criteria respecting the order, limit and offset
271
     *
272
     * @param array|Criteria $criteria The array with criteria to search on.
273
     * @return array
274
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
275
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
276
     */
277 6
    public function findBy($criteria)
278
    {
279 6
        if (!$this->isRepositoryReadable()) {
280 3
            throw $this->createNotReadableException();
281
        }
282
283 3
        return $this->trigger(__FUNCTION__, [
284 3
            'criteria' => $criteria,
285 3
        ]);
286
    }
287
288
    /**
289
     * Find one object in the repository matching the criteria
290
     *
291
     * @param array|Criteria $criteria The criteria values to match on.
292
     * @return object|null
293
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
294
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
295
     */
296 6
    public function findOneBy($criteria)
297
    {
298 6
        if (!$this->isRepositoryReadable()) {
299 3
            throw $this->createNotReadableException();
300
        }
301
302 3
        return $this->trigger(__FUNCTION__, [
303 3
            'criteria' => $criteria,
304 3
        ]);
305
    }
306
307
    /**
308
     * Persist the given entity
309
     *
310
     * @param object $entity
311
     * @return mixed
312
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
313
     * @throws RuntimeException
314
     */
315 6
    public function persist($entity)
316
    {
317 6
        if (!$this->isRepositoryWritable()) {
318 3
            throw $this->createNotWritableException();
319
        }
320
321 3
        return $this->trigger(__FUNCTION__, [
322 3
            'entity' => $entity,
323 3
        ]);
324
    }
325
326
    /**
327
     * Flushes the provided entity or all persisted entities when no entity is provided.
328
     *
329
     * @param object $entity
330
     * @return mixed
331
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
332
     * @throws RuntimeException
333
     */
334 6
    public function flush($entity = null)
335
    {
336 6
        if (!$this->isRepositoryWritable()) {
337 3
            throw $this->createNotWritableException();
338
        }
339
340 3
        return $this->trigger(__FUNCTION__, [
341 3
            'entity' => $entity,
342 3
        ]);
343
    }
344
345
    /**
346
     * will prepare the event object and trigger the event using the internal EventManager
347
     *
348
     * @param  string $name
349
     * @param  array $params
350
     * @return mixed
351
     * @throws \Zend\EventManager\Exception\InvalidArgumentException
352
     * @throws \PolderKnowledge\EntityService\Exception\RuntimeException
353
     */
354 30
    protected function trigger($name, array $params)
355
    {
356 30
        $event = clone $this->getEvent();
357 30
        $event->setName($name);
358 30
        $event->setParams($params);
359
360 30
        $responseCollection = $this->getEventManager()->triggerEvent($event);
361
362 30
        if ($responseCollection->stopped() && $event->isError()) {
363 3
            throw new RuntimeException($event->getError(), $event->getErrorNr());
364
        }
365
366 27
        return $event->getResult();
367
    }
368
369
    /**
370
     * Starts a new transaction.
371
     *
372
     * @throws ServiceException
373
     */
374 6
    public function beginTransaction()
375
    {
376 6
        if ($this->isTransactionEnabled() === false) {
377 3
            throw new ServiceException(sprintf(
378 3
                'The repository for %s doesn\'t support Transactions',
379 3
                $this->getEntityServiceName()
380 3
            ));
381
        }
382
383 3
        $this->getRepository()->beginTransaction();
384 3
    }
385
386
    /**
387
     * Commits a started transaction.
388
     *
389
     * @throws ServiceException
390
     */
391 6
    public function commitTransaction()
392
    {
393 6
        if ($this->isTransactionEnabled() === false) {
394 3
            throw new ServiceException(sprintf(
395 3
                'The repository for %s doesn\'t support Transactions',
396 3
                $this->getEntityServiceName()
397 3
            ));
398
        }
399
400 3
        $this->getRepository()->commitTransaction();
401 3
    }
402
403
    /**
404
     * Rolls back a started transaction.
405
     *
406
     * @throws ServiceException
407
     */
408 6
    public function rollbackTransaction()
409
    {
410 6
        if ($this->isTransactionEnabled() === false) {
411 3
            throw new ServiceException(sprintf(
412 3
                'The repository for %s doesn\'t support Transactions',
413 3
                $this->getEntityServiceName()
414 3
            ));
415
        }
416
417 3
        $this->getRepository()->rollbackTransaction();
418 3
    }
419
420
    /**
421
     * Returns true when possible to start an transaction
422
     */
423 18
    public function isTransactionEnabled()
424
    {
425 18
        return $this->getRepository() instanceof TransactionAwareInterface;
426
    }
427
428
    /**
429
     * Returns true when the repository for $entityName is writable
430
     *
431
     * @return bool
432
     */
433 12
    protected function isRepositoryWritable()
434
    {
435 12
        return $this->getRepository() instanceof WritableInterface;
436
    }
437
438
    /**
439
     * Returns true when the repository for $entityName is readable
440
     *
441
     * @return bool
442
     */
443 33
    protected function isRepositoryReadable()
444
    {
445 33
        return $this->getRepository() instanceof ReadableInterface;
446
    }
447
448
    /**
449
     * Returns true when the repository for $entityName has delete behavior
450
     *
451
     * @return bool
452
     */
453 12
    protected function isRepositoryDeletable()
454
    {
455 12
        return $this->getRepository() instanceof DeletableInterface;
456
    }
457
458
    /**
459
     * Throws an exception for cases where it's not possible to delete from the repository.
460
     *
461
     * @throws RuntimeException
462
     */
463 6
    private function createNotDeletableException()
464
    {
465 6
        throw new RuntimeException(sprintf(
466 6
            'The entities of type "%s" cannot be deleted from its repository.',
467 6
            $this->getEntityServiceName()
468 6
        ));
469
    }
470
471
    /**
472
     * Throws an exception for cases where it's not possible to read from the repository.
473
     *
474
     * @throws RuntimeException
475
     */
476 15
    private function createNotReadableException()
477
    {
478 15
        throw new RuntimeException(sprintf(
479 15
            'It is not possible to read entities of type "%s" from its repository.',
480 15
            $this->getEntityServiceName()
481 15
        ));
482
    }
483
484
    /**
485
     * Throws an exception for cases where it's not possible to write to the repository.
486
     *
487
     * @throws RuntimeException
488
     */
489 6
    private function createNotWritableException()
490
    {
491 6
        throw new RuntimeException(sprintf(
492 6
            'The entities of type "%s" cannot be written to its repository.',
493 6
            $this->getEntityServiceName()
494 6
        ));
495
    }
496
}
497