Completed
Push — master ( 916c4f...7f572e )
by Benedikt
06:01
created

RankingSystemService::updateRankingFrom()   F

Complexity

Conditions 16
Paths 481

Size

Total Lines 73
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 16
eloc 45
c 0
b 0
f 0
nc 481
nop 2
dl 0
loc 73
rs 3.6229

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
declare(strict_types=1);
3
/**
4
 * Created by PhpStorm.
5
 * User: benedikt
6
 * Date: 1/2/18
7
 * Time: 2:32 PM
8
 */
9
10
namespace Tfboe\FmLib\Service\RankingSystem;
11
12
13
use Doctrine\Common\Collections\Collection;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Doctrine\ORM\QueryBuilder;
16
use Tfboe\FmLib\Entity\Helpers\AutomaticInstanceGeneration;
17
use Tfboe\FmLib\Entity\Helpers\TournamentHierarchyEntity;
18
use Tfboe\FmLib\Entity\Helpers\TournamentHierarchyInterface;
19
use Tfboe\FmLib\Entity\PlayerInterface;
20
use Tfboe\FmLib\Entity\RankingSystemChangeInterface;
21
use Tfboe\FmLib\Entity\RankingSystemInterface;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Tfboe\FmLib\Service\Rank...\RankingSystemInterface.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
22
use Tfboe\FmLib\Entity\RankingSystemListEntryInterface;
23
use Tfboe\FmLib\Entity\RankingSystemListInterface;
24
use Tfboe\FmLib\Entity\TournamentInterface;
25
use Tfboe\FmLib\Exceptions\PreconditionFailedException;
26
use Tfboe\FmLib\Service\ObjectCreatorServiceInterface;
27
28
29
/**
30
 * Class RankingSystemService
31
 * @package Tfboe\FmLib\Service\RankingSystemService
32
 * @SuppressWarnings(PHPMD) TODO: refactor this class and remove suppress warnings
33
 */
34
abstract class RankingSystemService implements \Tfboe\FmLib\Service\RankingSystem\RankingSystemInterface
35
{
36
//<editor-fold desc="Fields">
37
  /** @var EntityManagerInterface */
38
  private $entityManager;
39
  /** @var TimeServiceInterface */
40
  private $timeService;
41
  /** @var EntityComparerInterface */
42
  private $entityComparer;
43
  /**
44
   * @var RankingSystemChangeInterface[][]
45
   * first key: tournament hierarchy entity id
46
   * second key: player id
47
   */
48
  private $changes;
49
  /**
50
   * @var RankingSystemChangeInterface[][]
51
   * first key: tournament hierarchy entity id
52
   * second key: player id
53
   */
54
  private $oldChanges;
55
  /**
56
   * List of ranking systems for which update ranking got already called, indexed by id
57
   * @var RankingSystemService[]
58
   */
59
  private $updateRankingCalls;
60
61
  /** @var ObjectCreatorServiceInterface */
62
  private $objectCreatorService;
63
//</editor-fold desc="Fields">
64
65
//<editor-fold desc="Constructor">
66
  /**
67
   * RankingSystemService constructor.
68
   * @param EntityManagerInterface $entityManager
69
   * @param TimeServiceInterface $timeService
70
   * @param EntityComparerInterface $entityComparer
71
   * @param ObjectCreatorServiceInterface $objectCreatorService
72
   */
73
  public function __construct(EntityManagerInterface $entityManager, TimeServiceInterface $timeService,
74
                              EntityComparerInterface $entityComparer,
75
                              ObjectCreatorServiceInterface $objectCreatorService)
76
  {
77
    $this->entityManager = $entityManager;
78
    $this->timeService = $timeService;
79
    $this->entityComparer = $entityComparer;
80
    $this->changes = [];
81
    $this->oldChanges = [];
82
    $this->updateRankingCalls = [];
83
    $this->objectCreatorService = $objectCreatorService;
84
  }
85
//</editor-fold desc="Constructor">
86
87
//<editor-fold desc="Public Methods">
88
  /**
89
   * @inheritDoc
90
   */
91
  public function getEarliestInfluence(RankingSystemInterface $ranking, TournamentInterface $tournament): ?\DateTime
92
  {
93
    return $this->getEarliestEntityInfluence($ranking, $tournament, false);
94
  }
95
96
  /**
97
   * @inheritdoc
98
   */
99
  public function updateRankingForTournament(RankingSystemInterface $ranking, TournamentInterface $tournament,
100
                                             ?\DateTime $oldInfluence)
101
  {
102
    $earliestInfluence = $this->getEarliestInfluence($ranking, $tournament);
103
    if ($oldInfluence !== null &&
104
      ($earliestInfluence === null || $oldInfluence < $earliestInfluence)) {
105
      $earliestInfluence = $oldInfluence;
106
    }
107
    if ($earliestInfluence !== null) {
108
      $this->updateRankingFrom($ranking, $earliestInfluence);
109
    }
110
  }
111
112
  /**
113
   * @inheritDoc
114
   */
115
  public function updateRankingFrom(RankingSystemInterface $ranking, \DateTime $from)
116
  {
117
    // can only be called once per ranking system!!!
118
    if (array_key_exists($ranking->getId(), $this->updateRankingCalls)) {
119
      throw new PreconditionFailedException();
120
    }
121
    $this->updateRankingCalls[$ranking->getId()] = $ranking;
122
    //find first reusable
123
    /** @var RankingSystemListInterface[] $lists */
124
    $lists = array_values($ranking->getLists()->toArray());
125
126
    $current = null;
127
    /** @var RankingSystemListInterface $lastReusable */
128
    $lastReusable = null;
129
    $toUpdate = [];
130
131
    foreach ($lists as $list) {
132
      if ($list->isCurrent()) {
133
        $current = $list;
134
      } else if ($list->getLastEntryTime() >= $from) {
135
        $toUpdate[] = $list;
136
      } else if ($lastReusable === null || $list->getLastEntryTime() > $lastReusable->getLastEntryTime()) {
137
        $lastReusable = $list;
138
      }
139
    }
140
141
    if ($current !== null && $current->getLastEntryTime() < $from) {
142
      $lastReusable = $current;
143
    }
144
145
    if ($lastReusable === null) {
146
      $lastReusable = $this->objectCreatorService->createObjectFromInterface(RankingSystemListInterface::class);
147
    }
148
149
    usort($toUpdate, function (RankingSystemListInterface $list1, RankingSystemListInterface $list2) {
150
      return $list1->getLastEntryTime() <=> $list2->getLastEntryTime();
151
    });
152
153
154
    $lastListTime = null;
155
    foreach ($toUpdate as $list) {
156
      $entities = $this->getNextEntities($ranking, $lastReusable, $list);
157
      if ($lastListTime == null) {
158
        if (count($entities) > 0) {
159
          $lastListTime = max($lastReusable->getLastEntryTime(), $this->timeService->getTime($entities[0]));
160
        } else {
161
          $lastListTime = $lastReusable->getLastEntryTime();
162
        }
163
      }
164
      $this->recomputeBasedOn($list, $lastReusable, $entities, $lastListTime);
165
      $lastReusable = $list;
166
      $lastListTime = $lastReusable->getLastEntryTime();
167
    }
168
169
    if ($current === null) {
170
      /** @var RankingSystemListInterface $current */
171
      $current = $this->objectCreatorService->createObjectFromInterface(RankingSystemListInterface::class);
172
      $current->setCurrent(true);
173
      $this->entityManager->persist($current);
174
      $current->setRankingSystem($ranking);
175
    }
176
177
    $entities = $this->getNextEntities($ranking, $lastReusable, $current);
178
    if ($lastListTime == null) {
179
      if (count($entities) > 0) {
180
        $lastListTime = max($lastReusable->getLastEntryTime(), $this->timeService->getTime($entities[0]));
181
      } else {
182
        $lastListTime = $lastReusable->getLastEntryTime();
183
      }
184
    }
185
    $this->recomputeBasedOn($current, $lastReusable, $entities, $lastListTime);
186
    $this->deleteOldChanges();
187
  }
188
189
  /**
190
   * @param RankingSystemInterface $ranking
191
   * @param RankingSystemListInterface $lastReusable
192
   * @param RankingSystemListInterface $list
193
   * @return array|TournamentHierarchyEntity[]
194
   */
195
  private function getNextEntities(RankingSystemInterface $ranking, RankingSystemListInterface $lastReusable,
196
                                   RankingSystemListInterface $list)
197
  {
198
    $this->deleteOldChanges();
199
    $entities = $this->getEntities($ranking, $lastReusable->getLastEntryTime(),
200
      $list->isCurrent() ? $this->getMaxDate() : $list->getLastEntryTime());
201
202
    //sort entities
203
    $this->timeService->clearTimes();
204
    usort($entities, function ($entity1, $entity2) {
205
      return $this->entityComparer->compareEntities($entity1, $entity2);
206
    });
207
208
    $this->markOldChangesAsDeleted($ranking, $entities);
209
210
    return $entities;
211
  }
212
//</editor-fold desc="Public Methods">
213
//<editor-fold desc="Protected Final Methods">
214
  /**
215
   * Computes the average rating of the given entries
216
   * @param RankingSystemListEntryInterface[] $entries
217
   * @return float
218
   */
219
  protected final function getAverage(array $entries): float
0 ignored issues
show
Coding Style introduced by
As per PSR2, final should precede the visibility keyword.
Loading history...
220
  {
221
    $sum = 0.0;
222
    foreach ($entries as $entry) {
223
      $sum += $entry->getPoints();
224
    }
225
    if (count($entries) === 0) {
226
      return 0.0;
227
    } else {
228
      return $sum / count($entries);
229
    }
230
  }
231
232
  /**
233
   * Gets the relevant entities for updating
234
   * @param RankingSystemInterface $ranking the ranking for which to get the entities
235
   * @param \DateTime $from search for entities with a time value LARGER than $from, i.e. don't search for entities with
236
   *                        time value exactly $from
237
   * @param \DateTime to search for entities with a time value SMALLER OR EQUAL than $to
238
   * @return TournamentHierarchyEntity[]
239
   */
240
  protected final function getEntities(RankingSystemInterface $ranking, \DateTime $from, \DateTime $to): array
0 ignored issues
show
Coding Style introduced by
As per PSR2, final should precede the visibility keyword.
Loading history...
241
  {
242
    $query = $this->getEntitiesQueryBuilder($ranking, $from, $to);
243
    return $query->getQuery()->getResult();
244
  }
245
246
  /**
247
   * @return EntityManagerInterface
248
   */
249
  protected final function getEntityManager(): EntityManagerInterface
0 ignored issues
show
Coding Style introduced by
As per PSR2, final should precede the visibility keyword.
Loading history...
250
  {
251
    return $this->entityManager;
252
  }
253
254
  /**
255
   * @param Collection|PlayerInterface[] $players
256
   * @param RankingSystemListInterface $list
257
   * @return RankingSystemListEntryInterface[] $entries
258
   */
259
  protected final function getEntriesOfPlayers(Collection $players, RankingSystemListInterface $list): array
0 ignored issues
show
Coding Style introduced by
As per PSR2, final should precede the visibility keyword.
Loading history...
260
  {
261
    $result = [];
262
    foreach ($players as $player) {
263
      $result[] = $this->getOrCreateRankingSystemListEntry($list, $player);
264
    }
265
    return $result;
266
  }
267
268
  /** @noinspection PhpDocMissingThrowsInspection */ //PropertyNotExistingException
269
  /**
270
   * Gets or creates a tournament system change entry for the given entity, ranking and player.
271
   * @param TournamentHierarchyInterface $entity the tournament hierarchy entity to search for
272
   * @param RankingSystemInterface $ranking the ranking system to search for
273
   * @param PlayerInterface $player the player to search for
274
   * @return RankingSystemChangeInterface the found or newly created ranking system change
275
   */
276
  protected final function getOrCreateChange(TournamentHierarchyInterface $entity, RankingSystemInterface $ranking,
0 ignored issues
show
Coding Style introduced by
As per PSR2, final should precede the visibility keyword.
Loading history...
277
                                             PlayerInterface $player)
278
  {
279
    $key1 = $entity->getId();
280
    $key2 = $player->getId();
281
    if (!array_key_exists($key1, $this->changes)) {
282
      $this->changes[$key1] = [];
283
    }
284
    if (array_key_exists($key1, $this->oldChanges) && array_key_exists($key2, $this->oldChanges[$key1])) {
285
      $this->changes[$key1][$key2] = $this->oldChanges[$key1][$key2];
286
      unset($this->oldChanges[$key1][$key2]);
287
    }
288
    if (!array_key_exists($key2, $this->changes[$key1])) {
289
      //create new change
290
      /** @var RankingSystemChangeInterface $change */
291
      $change = $this->objectCreatorService->createObjectFromInterface(RankingSystemChangeInterface::class,
292
        [array_keys($this->getAdditionalFields())]);
293
      foreach ($this->getAdditionalFields() as $field => $value) {
294
        // PropertyNotExistingException => we know for sure that the property exists (see 2 lines above)
295
        /** @noinspection PhpUnhandledExceptionInspection */
296
        $change->setProperty($field, 0);
297
      }
298
      $change->setHierarchyEntity($entity);
299
      $change->setRankingSystem($ranking);
300
      $change->setPlayer($player);
301
      $this->entityManager->persist($change);
302
      $this->changes[$key1][$key2] = $change;
303
    }
304
    return $this->changes[$key1][$key2];
305
  }
306
307
  /** @noinspection PhpDocMissingThrowsInspection */ //PropertyNotExistingException
308
  /**
309
   * @param RankingSystemListInterface $list the list in which to search for the entry or in which to add it
310
   * @param PlayerInterface $player the player to search for
311
   * @return RankingSystemListEntryInterface the found or the new entry
312
   */
313
  protected final function getOrCreateRankingSystemListEntry(RankingSystemListInterface $list,
0 ignored issues
show
Coding Style introduced by
As per PSR2, final should precede the visibility keyword.
Loading history...
314
                                                             PlayerInterface $player): RankingSystemListEntryInterface
315
  {
316
    $playerId = $player->getId();
317
    if (!$list->getEntries()->containsKey($playerId)) {
318
      /** @var RankingSystemListEntryInterface $entry */
319
      $entry = $this->objectCreatorService->createObjectFromInterface(RankingSystemListEntryInterface::class,
320
        [array_keys($this->getAdditionalFields())]);
321
      $entry->setPlayer($player);
322
      $entry->setRankingSystemList($list);
323
      $this->resetListEntry($entry);
324
      $this->entityManager->persist($entry);
325
    }
326
    return $list->getEntries()->get($playerId);
327
  }
328
329
  /**
330
   * @param RankingSystemListEntryInterface $entry
331
   * @throws \Tfboe\FmLib\Exceptions\PropertyNotExistingException
332
   */
333
  private function resetListEntry(RankingSystemListEntryInterface $entry)
334
  {
335
    $entry->setPoints($this->startPoints());
336
    $entry->setNumberRankedEntities(0);
337
    foreach ($this->getAdditionalFields() as $field => $value) {
338
      // PropertyNotExistingException => we know for sure that the property exists (see 2 lines above)
339
      /** @noinspection PhpUnhandledExceptionInspection */
340
      $entry->setProperty($field, $value);
341
    }
342
  }
343
//</editor-fold desc="Protected Final Methods">
344
345
//<editor-fold desc="Protected Methods">
346
  /**
347
   * Gets additional fields for this ranking type mapped to its start value
348
   * @return string[] list of additional fields
349
   */
350
  protected abstract function getAdditionalFields(): array;
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
351
352
  /**
353
   * Gets all ranking changes for the given entity for the given list. Must return a change for each involved player.
354
   * The field pointsAfterwards get calculated afterwards and can be left empty.
355
   * @param TournamentHierarchyEntity $entity the entity for which to compute the ranking changes
356
   * @param RankingSystemListInterface $list the list for which to compute the ranking changes
357
   * @return RankingSystemChangeInterface[] the changes
358
   */
359
  protected abstract function getChanges(TournamentHierarchyEntity $entity, RankingSystemListInterface $list): array;
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
360
361
  /**
362
   * Gets a query for getting the relevant entities for updating
363
   * @param RankingSystemInterface $ranking the ranking for which to get the entities
364
   * @param \DateTime $from search for entities with a time value LARGER than $from, i.e. don't search for entities with
365
   *                        time value exactly $from
366
   * @param \DateTime to search for entities with a time value SMALLER OR EQUAL than $to
367
   * @return QueryBuilder
368
   */
369
  protected abstract function getEntitiesQueryBuilder(RankingSystemInterface $ranking,
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
370
                                                      \DateTime $from, \DateTime $to): QueryBuilder;
371
372
  /**
373
   * Gets the level of the ranking system service (see Level Enum)
374
   * @return int
375
   */
376
  protected abstract function getLevel(): int;
0 ignored issues
show
Coding Style introduced by
The abstract declaration must precede the visibility declaration
Loading history...
377
378
  /**
379
   * Gets the start points for a new player in the ranking
380
   * @return float
381
   */
382
  protected function startPoints(): float
383
  {
384
    return 0.0;
385
  }
386
//</editor-fold desc="Protected Methods">
387
388
//<editor-fold desc="Private Methods">
389
  /**
390
   * Clones all ranking values from base and inserts them into list, furthermore removes all remaining ranking values of
391
   * list. After this method was called list and base contain exactly the same rankings.
392
   * @param RankingSystemListInterface $list the ranking list to change
393
   * @param RankingSystemListInterface $base the ranking list to use as base list, this doesn't get changed
394
   */
395
  private function cloneInto(RankingSystemListInterface $list, RankingSystemListInterface $base)
396
  {
397
    /*//first remove all entries from list
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
398
    foreach($list->getEntries()->toArray() as $entry)
399
    {
400
      $list->getEntries()->removeElement($entry);
401
      $this->entityManager->remove($entry);
402
    }*/
403
404
    $clonedPlayers = [];
405
406
    foreach ($base->getEntries() as $entry) {
407
      $playerId = $entry->getPlayer()->getId();
408
      $clonedPlayers[$playerId] = true;
409
      if (!$list->getEntries()->containsKey($playerId)) {
410
        //create new entry
411
        /** @var RankingSystemListEntryInterface $entry */
412
        $clone = $this->objectCreatorService->createObjectFromInterface(RankingSystemListEntryInterface::class,
413
          [[]]);
414
        $this->entityManager->persist($clone);
415
        $clone->setPlayer($entry->getPlayer());
416
        $clone->setRankingSystemList($list);
417
      }
418
      $foundEntry = $list->getEntries()[$playerId];
419
      $foundEntry->setNumberRankedEntities($entry->getNumberRankedEntities());
420
      $foundEntry->setPoints($entry->getPoints());
421
      $foundEntry->cloneSubClassDataFrom($entry);
422
    }
423
424
    //remove all unused entries from list
425
    foreach ($list->getEntries()->toArray() as $playerId => $entry) {
426
      if (!array_key_exists($playerId, $clonedPlayers)) {
427
        $this->resetListEntry($entry);
428
        //$list->getEntries()->removeElement($entry);
0 ignored issues
show
Unused Code Comprehensibility introduced by
82% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
429
        //$this->entityManager->remove($entry);
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
430
      }
431
    }
432
  }
433
434
435
  /**
436
   * @param RankingSystemInterface $ranking
437
   * @param TournamentHierarchyEntity[] $entities
438
   */
439
  private function markOldChangesAsDeleted(RankingSystemInterface $ranking, array $entities)
440
  {
441
    assert(count($this->oldChanges) == 0);
442
    $this->changes = [];
443
    $queryBuilder = $this->entityManager->createQueryBuilder();
444
    /** @var RankingSystemChangeInterface[] $changes */
445
    $changes = $queryBuilder
446
      ->from(RankingSystemChangeInterface::class, 'c')
447
      ->select('c')
448
      ->where($queryBuilder->expr()->eq('c.rankingSystem', ':ranking'))
449
      ->setParameter('ranking', $ranking)
450
      ->andWhere($queryBuilder->expr()->in('c.hierarchyEntity', ':entities'))
451
      ->setParameter('entities', $entities)
452
      ->getQuery()->getResult();
453
    foreach ($changes as $change) {
454
      $eId = $change->getHierarchyEntity()->getId();
455
      $pId = $change->getPlayer()->getId();
456
      if (array_key_exists($eId, $this->oldChanges) && array_key_exists($pId, $this->oldChanges[$eId])) {
457
        //duplicate entry
458
        assert($this->oldChanges[$eId][$pId]->getRankingSystem()->getId() ===
459
          $change->getRankingSystem()->getId());
460
        $this->entityManager->remove($change);
461
      } else {
462
        $this->oldChanges[$eId][$pId] = $change;
463
      }
464
    }
465
  }
466
467
  private function deleteOldChanges()
468
  {
469
    foreach ($this->oldChanges as $eId => $changes) {
470
      foreach ($changes as $pId => $change) {
471
        $this->entityManager->remove($change);
472
      }
473
    }
474
    $this->oldChanges = [];
475
  }
476
477
  /**
478
   * Gets the earliest influence for the given entity
479
   * @param RankingSystemInterface $ranking the ranking system for which to get the influence
480
   * @param TournamentHierarchyInterface $entity the entity to analyze
481
   * @param bool $parentIsRanked true iff a predecessor contained the given ranking in its ranking systems
482
   * @return \DateTime|null the earliest influence or null if $parentIsRanked is false and the entity and all its
483
   *                        successors do not have the ranking in its ranking systems
484
   */
485
  private function getEarliestEntityInfluence(RankingSystemInterface $ranking, TournamentHierarchyInterface $entity,
486
                                              bool $parentIsRanked): ?\DateTime
487
  {
488
    $this->timeService->clearTimes();
489
    $entityIsRanked = $parentIsRanked || $entity->getRankingSystems()->containsKey($ranking->getId());
490
    if ($entity->getLevel() === $this->getLevel()) {
491
      if ($entityIsRanked) {
492
        return $this->timeService->getTime($entity);
493
      } else {
494
        return null;
495
      }
496
    }
497
    $result = null;
498
499
    foreach ($entity->getChildren() as $child) {
500
      $earliest = $this->getEarliestEntityInfluence($ranking, $child, $entityIsRanked);
501
      if ($result === null || ($earliest !== null && $earliest < $result)) {
502
        $result = $earliest;
503
      }
504
    }
505
    return $result;
506
  }
507
508
  /**
509
   * @param \DateTime $time the time of the last list
510
   * @param int $generationLevel the list generation level
511
   * @return \DateTime the time of the next list generation
512
   */
513
  private function getNextGenerationTime(\DateTime $time, int $generationLevel): \DateTime
514
  {
515
    $year = (int)$time->format('Y');
516
    $month = (int)$time->format('m');
517
    if ($generationLevel === AutomaticInstanceGeneration::MONTHLY) {
518
      $month += 1;
519
      if ($month == 13) {
520
        $month = 1;
521
        $year += 1;
522
      }
523
    } else if ($generationLevel === AutomaticInstanceGeneration::OFF) {
524
      return $this->getMaxDate();
525
    } else {
526
      $year += 1;
527
    }
528
    return (new \DateTime())->setDate($year, $month, 1)->setTime(0, 0, 0);
529
  }
530
531
  /**
532
   * @return \DateTime
533
   * @throws \Exception
534
   */
535
  private function getMaxDate(): \DateTime
536
  {
537
    return (new \DateTime())->add(new \DateInterval('P100Y'));
538
  }
539
540
  /** @noinspection PhpDocMissingThrowsInspection */ //PropertyNotExistingException
541
  /**
542
   * Recomputes the given ranking list by using base as base list and applying the changes for the given entities
543
   * starting from the given index. If list is not the current list only the entities up to $list->getLastEntryTime()
544
   * are applied and the index gets changed accordingly.
545
   * @param RankingSystemListInterface $list the list to recompute
546
   * @param RankingSystemListInterface $base the list to use as base
547
   * @param TournamentHierarchyEntity[] $entities the list of entities to use for the computation
548
   * @param \DateTime $lastListTime the time of the last list or the first entry
549
   */
550
  private function recomputeBasedOn(RankingSystemListInterface $list, RankingSystemListInterface $base,
551
                                    array &$entities, \DateTime $lastListTime)
552
  {
553
    $nextGeneration = $this->getNextGenerationTime($lastListTime, $list->getRankingSystem()->getGenerationInterval());
554
    $this->cloneInto($list, $base);
555
    for ($i = 0; $i < count($entities); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
556
      $time = $this->timeService->getTime($entities[$i]);
557
      if (!$list->isCurrent() && $time > $list->getLastEntryTime()) {
558
        $this->flushAndForgetEntities($entities, $i);
559
        return;
560
      }
561
      if ($nextGeneration < $time) {
562
        /** @var RankingSystemListInterface $newList */
563
        $newList = $this->objectCreatorService->createObjectFromInterface(RankingSystemListInterface::class);
564
        $newList->setCurrent(false);
565
        $newList->setLastEntryTime($nextGeneration);
566
        $this->entityManager->persist($newList);
567
        $newList->setRankingSystem($list->getRankingSystem());
568
        $this->cloneInto($newList, $list);
569
        $nextGeneration = $this->getNextGenerationTime($nextGeneration,
570
          $list->getRankingSystem()->getGenerationInterval());
571
        //clear entityManager to save memory
572
        $this->flushAndForgetEntities($entities, $i);
573
      }
574
      $changes = $this->getChanges($entities[$i], $list);
0 ignored issues
show
Compatibility introduced by
$entities[$i] of type object<Tfboe\FmLib\Entit...mentHierarchyInterface> is not a sub-type of object<Tfboe\FmLib\Entit...rnamentHierarchyEntity>. It seems like you assume a concrete implementation of the interface Tfboe\FmLib\Entity\Helpe...amentHierarchyInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
575
      foreach ($changes as $change) {
576
        $entry = $this->getOrCreateRankingSystemListEntry($list, $change->getPlayer());
577
        $entry->setNumberRankedEntities($entry->getNumberRankedEntities() + 1);
578
        $pointsAfterwards = $entry->getPoints() + $change->getPointsChange();
579
        $entry->setPoints($pointsAfterwards);
580
        $change->setPointsAfterwards($pointsAfterwards);
581
        //apply further changes
582
        foreach ($this->getAdditionalFields() as $field => $value) {
583
          // PropertyNotExistingException => entry and field have exactly the static properties from getAdditionalFields
584
          /** @noinspection PhpUnhandledExceptionInspection */
585
          $entry->setProperty($field, $entry->getProperty($field) + $change->getProperty($field));
586
        }
587
        if ($time > $list->getLastEntryTime()) {
588
          $list->setLastEntryTime($time);
589
        }
590
        $this->entityManager->persist($change);
591
      }
592
      $list->getRankingSystem()->setOpenSyncFrom($time);
593
    }
594
    $current = count($entities);
595
    $this->flushAndForgetEntities($entities, $current);
596
  }
597
598
  /**
599
   * @param TournamentHierarchyInterface[] $entities
600
   * @param int &$current
601
   */
602
  private function flushAndForgetEntities(&$entities, &$current)
603
  {
604
    for ($i = 0; $i < $current; $i++) {
605
      $eId = $entities[$i]->getId();
606
      if (array_key_exists($eId, $this->oldChanges)) {
607
        foreach ($this->oldChanges[$eId] as $pId => $change) {
608
          $this->entityManager->remove($change);
609
        }
610
      }
611
      unset($this->oldChanges[$eId]);
612
    }
613
    $this->entityManager->flush();
614
    for ($i = 0; $i < $current; $i++) {
615
      $eId = $entities[$i]->getId();
616
      $this->entityManager->detach($entities[$i]);
617
      if (array_key_exists($eId, $this->changes)) {
618
        foreach ($this->changes[$eId] as $pId => $change) {
619
          $this->entityManager->detach($change);
620
        }
621
        unset($this->changes[$eId]);
622
      }
623
    }
624
    if ($current >= count($entities)) {
625
      $entities = [];
626
    } else {
627
      array_splice($entities, 0, $current);
628
    }
629
    $current = 0;
630
  }
631
//</editor-fold desc="Private Methods">
632
}