GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

DoctrineWorkflowStory::markFinished()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 14
rs 9.4285
cc 1
eloc 9
nc 1
nop 5
1
<?php
2
/**
3
 * @link    https://github.com/old-town/workflow-doctrine
4
 * @author  Malofeykin Andrey  <[email protected]>
5
 */
6
namespace OldTown\Workflow\Spi\Doctrine;
7
8
use DateTime;
9
use OldTown\PropertySet\PropertySetManager;
10
use ReflectionClass;
11
use OldTown\Workflow\Query\WorkflowExpressionQuery;
12
use OldTown\Workflow\Spi\StepInterface;
13
use OldTown\Workflow\Spi\WorkflowStoreInterface;
14
use Doctrine\ORM\EntityManagerInterface;
15
use OldTown\Workflow\Spi\Doctrine\EntityManagerFactory\EntityManagerFactoryInterface;
16
use OldTown\Workflow\Spi\WorkflowEntryInterface;
17
use OldTown\Workflow\Spi\Doctrine\Entity\DefaultEntry;
18
use OldTown\Workflow\Spi\Doctrine\EntityRepository\StepRepository;
19
use OldTown\Workflow\Spi\Doctrine\Entity\EntryInterface;
20
use SplObjectStorage;
21
use OldTown\Workflow\Spi\Doctrine\Entity\Step;
22
use OldTown\Workflow\Spi\Doctrine\Entity\StepInterface as BaseStepInterface;
23
24
25
26
/**
27
 * Class DoctrineWorkflowStory
28
 *
29
 * @package OldTown\Workflow\Spi\Doctrine
30
 */
31
class DoctrineWorkflowStory implements WorkflowStoreInterface
32
{
33
    /**
34
     * Ключ по котормоу можно получить настройки для работы с фабрикой создающей менеджер сущностей
35
     *
36
     * @var string
37
     */
38
    const ENTITY_MANAGER_FACTORY = 'entityManagerFactory';
39
40
    /**
41
     * Ключ по котормоу можно получить имя класса фабрика создающей менеджер сущностей
42
     *
43
     * @var string
44
     */
45
    const ENTITY_MANAGER_FACTORY_NAME = 'name';
46
47
    /**
48
     * Ключ по котормоу можно получить параметры для создания менеджера сущностей
49
     *
50
     * @var string
51
     */
52
    const ENTITY_MANAGER_FACTORY_OPTIONS = 'options';
53
54
    /**
55
     * @var EntityManagerInterface
56
     */
57
    protected $entityManager;
58
59
    /**
60
     * @var EntityManagerFactoryInterface
61
     */
62
    protected $entityManagerFactory;
63
64
    /**
65
     * @var string
66
     */
67
    protected $entityManagerFactoryName;
68
69
    /**
70
     * @var array
71
     */
72
    protected $entityManagerFactoryOptions = [];
73
74
    /**
75
     * @var array
76
     */
77
    protected $entityMap = [
78
        'entry'       => DefaultEntry::class,
79
        'step' => Step::class,
80
81
    ];
82
83
    /**
84
     * Инициализация хранилища
85
     *
86
     * @param array $props
87
     *
88
     * @throws Exception\InvalidArgumentException
89
     * @throws Exception\RuntimeException
90
     */
91
    public function init(array $props = [])
92
    {
93
        $this->entityManagerFactory = null;
94
        $this->entityManager = null;
95
96
        if (!array_key_exists(static::ENTITY_MANAGER_FACTORY, $props)) {
97
            $errMsg = sprintf('Option %s not found', static::ENTITY_MANAGER_FACTORY);
98
            throw new Exception\InvalidArgumentException($errMsg);
99
        }
100
101
        $emFactoryOptions = $props[static::ENTITY_MANAGER_FACTORY];
102 View Code Duplication
        if (!is_array($emFactoryOptions)) {
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...
103
            $errMsg = sprintf('Option %s is not array', static::ENTITY_MANAGER_FACTORY);
104
            throw new Exception\InvalidArgumentException($errMsg);
105
        }
106
107
108 View Code Duplication
        if (!array_key_exists(static::ENTITY_MANAGER_FACTORY_NAME, $emFactoryOptions)) {
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...
109
            $errMsg = sprintf('Option %s->%s not found', static::ENTITY_MANAGER_FACTORY, static::ENTITY_MANAGER_FACTORY_NAME);
110
            throw new Exception\InvalidArgumentException($errMsg);
111
        }
112
        $this->entityManagerFactoryName = $emFactoryOptions[static::ENTITY_MANAGER_FACTORY_NAME];
113
114
        if (array_key_exists(static::ENTITY_MANAGER_FACTORY_OPTIONS, $emFactoryOptions) && is_array($emFactoryOptions[static::ENTITY_MANAGER_FACTORY_OPTIONS])) {
115
            $this->entityManagerFactoryOptions = $emFactoryOptions[static::ENTITY_MANAGER_FACTORY_OPTIONS];
116
        }
117
    }
118
119
    /**
120
     * @return EntityManagerFactoryInterface
121
     *
122
     * @throws Exception\InvalidArgumentException
123
     * @throws Exception\RuntimeException
124
     */
125
    public function getEntityManagerFactory()
126
    {
127
        if ($this->entityManagerFactory) {
128
            return $this->entityManagerFactory;
129
        }
130
131
        if (!class_exists($this->entityManagerFactoryName)) {
132
            $errMsg = sprintf('Entity manager factory "%s" not found', $this->entityManagerFactoryName);
133
            throw new Exception\RuntimeException($errMsg);
134
        }
135
136
        $r = new ReflectionClass($this->entityManagerFactoryName);
137
        if (!$r->implementsInterface(EntityManagerFactoryInterface::class)) {
138
            $errMsg = sprintf('Factory not implement %s', EntityManagerFactoryInterface::class);
139
            throw new Exception\RuntimeException($errMsg);
140
        }
141
142
        /** @var EntityManagerFactoryInterface $factory */
143
        $this->entityManagerFactory = $r->newInstance();
144
145
        return $this->entityManagerFactory;
146
    }
147
148
    /**
149
     * @return EntityManagerInterface
150
     *
151
     * @throws Exception\DoctrineRuntimeException
152
     */
153
    public function getEntityManager()
154
    {
155
        if ($this->entityManager) {
156
            return $this->entityManager;
157
        }
158
159
        try {
160
            $em = $this->getEntityManagerFactory()->factory($this->entityManagerFactoryOptions);
161
162
            if (!$em instanceof EntityManagerInterface) {
163
                $errMsg = sprintf('EntityManager not implement %s', EntityManagerInterface::class);
164
                throw new Exception\RuntimeException($errMsg);
165
            }
166
            $this->entityManager = $em;
167
        } catch (\Exception $e) {
168
            throw new Exception\DoctrineRuntimeException($e->getMessage(), $e->getCode(), $e);
169
        }
170
171
        return $this->entityManager;
172
    }
173
174
175
    /**
176
     * @param string $workflowName
177
     *
178
     * @return WorkflowEntryInterface
179
     *
180
     * @throws Exception\DoctrineRuntimeException
181
     * @throws  Exception\InvalidArgumentException
182
     */
183
    public function createEntry($workflowName)
184
    {
185
        $entryClassName = $this->getEntityClassName('entry');
186
187
        $r = new ReflectionClass($entryClassName);
188
189
        $workflowEntry = $r->newInstance();
190
        $workflowEntry->setWorkflowName($workflowName);
191
        $workflowEntry->setState(WorkflowEntryInterface::CREATED);
192
193
        $em = $this->getEntityManager();
194
        $em->persist($workflowEntry);
195
        $em->flush();
196
197
        return $workflowEntry;
198
    }
199
200
    /**
201
     * @param int $entryId
202
     * @param int $state
203
     *
204
     * @throws Exception\DoctrineRuntimeException
205
     * @throws Exception\InvalidArgumentException
206
     */
207
    public function setEntryState($entryId, $state)
208
    {
209
        $em = $this->getEntityManager();
210
211
        $entryClassName = $this->getEntityClassName('entry');
212
213
        /** @var EntryInterface $entry */
214
        $entry = $em->getRepository($entryClassName)->find($entryId);
215
        $entry->setState($state);
216
217
        $em->flush();
218
    }
219
220
    /**
221
     *
222
     *
223
     * @param int      $entryId
224
     * @param int      $stepId
225
     * @param string   $owner
226
     * @param DateTime $startDate
227
     * @param DateTime $dueDate
228
     * @param string   $status
229
     * @param array    $previousIds
230
     *
231
     * @return StepInterface|void
232
     *
233
     * @throws Exception\DoctrineRuntimeException
234
     * @throws \OldTown\Workflow\Spi\Doctrine\EntityRepository\Exception\RuntimeException
235
     * @throws \OldTown\Workflow\Spi\Doctrine\Entity\Exception\InvalidArgumentException
236
     * @throws  Exception\InvalidArgumentException
237
     */
238
    public function createCurrentStep($entryId, $stepId, $owner = null, DateTime $startDate, DateTime $dueDate = null, $status, array $previousIds = [])
239
    {
240
        $em = $this->getEntityManager();
241
242
        $entryClassName = $this->getEntityClassName('entry');
243
244
        /** @var EntryInterface $entry */
245
        $entry = $em->getRepository($entryClassName)->find($entryId);
246
247
        $currentStepClassName = $this->getEntityClassName('step');
248
        $r = new ReflectionClass($currentStepClassName);
249
250
        /** @var BaseStepInterface $currentStep */
251
        $currentStep = $r->newInstance($currentStepClassName);
252
        $currentStep->setEntry($entry);
253
        $currentStep->setStepId($stepId);
254
        $currentStep->setOwner($owner);
255
        $currentStep->setStartDate($startDate);
256
        $currentStep->setDueDate($dueDate);
257
        $currentStep->setStatus($status);
258
        $currentStep->setType(BaseStepInterface::CURRENT_STEP);
259
260
        if (count($previousIds) > 0) {
261
            /** @var StepRepository $previousStepRepository */
262
            $previousStepRepository = $em->getRepository(Step::class);
263
            $previousSteps = $previousStepRepository->findByIds($previousIds);
264
            $currentStep->setPreviousSteps($previousSteps);
265
        }
266
        $em->persist($currentStep);
267
        $em->flush();
268
269
        return $currentStep->getId();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $currentStep->getId(); (integer) is incompatible with the return type declared by the interface OldTown\Workflow\Spi\Wor...face::createCurrentStep of type OldTown\Workflow\Spi\StepInterface.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
270
    }
271
272
    /**
273
     * @param int $entryId
274
     *
275
     * @return \Doctrine\Common\Collections\ArrayCollection|Entity\StepInterface[]
276
     *
277
     * @return StepInterface[]
278
     *
279
     * @throws Exception\DoctrineRuntimeException
280
     * @throws Exception\InvalidArgumentException
281
     */
282 View Code Duplication
    public function findCurrentSteps($entryId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
283
    {
284
        $em = $this->getEntityManager();
285
286
        $entryClassName = $this->getEntityClassName('entry');
287
288
        /** @var EntryInterface $entry */
289
        $entry = $em->getRepository($entryClassName)->find($entryId);
290
291
        $stepClassName = $this->getEntityClassName('step');
292
        /** @var StepRepository $stepRepo */
293
        $stepRepo = $em->getRepository($stepClassName);
294
295
        $currentSteps = $stepRepo->findCurrentSteps($entry);
296
        $result = new SplObjectStorage();
297
        foreach ($currentSteps as $currentStep) {
298
            $result->attach($currentStep);
299
        }
300
301
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (SplObjectStorage) is incompatible with the return type declared by the interface OldTown\Workflow\Spi\Wor...rface::findCurrentSteps of type OldTown\Workflow\Spi\StepInterface[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
302
    }
303
304
305
    /**
306
     * @param integer $entryId
307
     *
308
     * @return WorkflowEntryInterface
309
     *
310
     * @throws Exception\DoctrineRuntimeException
311
     * @throws Exception\InvalidArgumentException
312
     */
313
    public function findEntry($entryId)
314
    {
315
        $em = $this->getEntityManager();
316
317
        $entryClassName = $this->getEntityClassName('entry');
318
319
        return $em->getRepository($entryClassName)->find($entryId);
320
    }
321
322
    /**
323
     * Помечает шаг как выполенный
324
     *
325
     * @param StepInterface $step
326
     * @param int           $actionId
327
     * @param DateTime      $finishDate
328
     * @param string        $status
329
     * @param string        $caller
330
     *
331
     * @return StepInterface
332
     *
333
     * @throws Exception\DoctrineRuntimeException
334
     */
335
    public function markFinished(StepInterface $step, $actionId, DateTime $finishDate, $status, $caller)
336
    {
337
        $step->setActionId($actionId);
338
        $step->setFinishDate($finishDate);
339
        $step->setStatus($status);
340
        $step->setCaller($caller);
341
342
        $em = $this->getEntityManager();
343
344
        $em->persist($step);
345
        $em->flush();
346
347
        return $step;
348
    }
349
350
351
    /**
352
     * Перемещает шаг в архив
353
     *
354
     * @param StepInterface $step
355
     *
356
     * @return $this|void
357
     *
358
     * @throws Exception\InvalidArgumentException
359
     * @throws \OldTown\Workflow\Spi\Doctrine\Exception\DoctrineRuntimeException
360
     * @throws \OldTown\Workflow\Spi\Doctrine\Entity\Exception\InvalidArgumentException
361
     */
362
    public function moveToHistory(StepInterface $step)
363
    {
364
        if (!$step instanceof BaseStepInterface) {
365
            $errMsg = sprintf('Step not implement %s', BaseStepInterface::class);
366
            throw new Exception\InvalidArgumentException($errMsg);
367
        }
368
        $em = $this->getEntityManager();
369
        $step->setType(BaseStepInterface::HISTORY_STEP);
370
371
        $em->flush();
372
    }
373
374
    /**
375
     * Поиск уже пройденных шагов для процесса workflow с заданным id
376
     *
377
     * @param $entryId
378
     *
379
     * @return \OldTown\Workflow\Spi\StepInterface[]|\Doctrine\ORM\PersistentCollection|void
380
     *
381
     * @throws \OldTown\Workflow\Spi\Doctrine\Exception\DoctrineRuntimeException
382
     * @throws Exception\InvalidArgumentException
383
     */
384 View Code Duplication
    public function findHistorySteps($entryId)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
385
    {
386
        $em = $this->getEntityManager();
387
388
        $entryClassName = $this->getEntityClassName('entry');
389
390
        /** @var EntryInterface $entry */
391
        $entry = $em->getRepository($entryClassName)->find($entryId);
392
393
        $stepClassName = $this->getEntityClassName('step');
394
        /** @var StepRepository $stepRepo */
395
        $stepRepo = $em->getRepository($stepClassName);
396
397
        $historySteps = $stepRepo->findHistorySteps($entry);
398
        $result = new SplObjectStorage();
399
        foreach ($historySteps as $currentStep) {
400
            $result->attach($currentStep);
401
        }
402
403
        return $result;
404
    }
405
406
    /**
407
     * @todo Реализовать функционал
408
     *
409
     * @param WorkflowExpressionQuery $query
410
     *
411
     * @return array|void
412
     *
413
     * @throws Exception\RuntimeException
414
     */
415
    public function query(WorkflowExpressionQuery $query)
416
    {
417
        $errMsg = sprintf('Method %s not supported', __METHOD__);
418
        throw new Exception\RuntimeException($errMsg);
419
    }
420
421
    /**
422
     * @todo Покрыть тестами и отрефакторить
423
     *
424
     * @param int $entryId
425
     *
426
     * @return \OldTown\PropertySet\PropertySetInterface
427
     * @throws \OldTown\PropertySet\Exception\RuntimeException
428
     */
429
    public function getPropertySet($entryId)
430
    {
431
        return PropertySetManager::getInstance('memory');
432
    }
433
434
    /**
435
     * Получение сущности по ее псевдониму
436
     *
437
     * @param $alias
438
     *
439
     * @return mixed
440
     *
441
     * @throws  Exception\InvalidArgumentException
442
     */
443
    public function getEntityClassName($alias)
444
    {
445
        if (array_key_exists($alias, $this->entityMap)) {
446
            return $this->entityMap[$alias];
447
        }
448
449
        $errMsg = sprintf('Invalid entity name: %s', $alias);
450
        throw new Exception\InvalidArgumentException($errMsg);
451
    }
452
453
    /**
454
     * @param $alias
455
     * @param $className
456
     *
457
     * @return $this
458
     */
459
    public function setEntityClassName($alias, $className)
460
    {
461
        $this->entityMap[$alias] = $className;
462
463
        return $this;
464
    }
465
}
466