Completed
Push — master ( 9dd58c...d31ca7 )
by Walter
02:40
created

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