Passed
Branch merging-leagues-tournaments (1b2f12)
by Benedikt
06:40
created

RankingSystemService::getOrCreateChange()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 29
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 18
c 1
b 0
f 1
dl 0
loc 29
rs 9.0444
cc 6
nc 8
nop 3
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 DateInterval;
14
use DateTime;
15
use Doctrine\Common\Collections\Collection;
16
use Doctrine\ORM\EntityManagerInterface;
17
use Doctrine\ORM\QueryBuilder;
18
use Tfboe\FmLib\Entity\Helpers\AutomaticInstanceGeneration;
19
use Tfboe\FmLib\Entity\Helpers\TournamentHierarchyEntity;
20
use Tfboe\FmLib\Entity\Helpers\TournamentHierarchyInterface;
21
use Tfboe\FmLib\Entity\PlayerInterface;
22
use Tfboe\FmLib\Entity\RankingSystemChangeInterface;
23
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. Consider defining an alias.

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...
24
use Tfboe\FmLib\Entity\RankingSystemListEntryInterface;
25
use Tfboe\FmLib\Entity\RankingSystemListInterface;
26
use Tfboe\FmLib\Entity\TournamentInterface;
27
use Tfboe\FmLib\Exceptions\PreconditionFailedException;
28
use Tfboe\FmLib\Service\ObjectCreatorServiceInterface;
29
30
31
/**
32
 * Class RankingSystemService
33
 * @package Tfboe\FmLib\Service\RankingSystemService
34
 * @SuppressWarnings(PHPMD) TODO: refactor this class and remove suppress warnings
35
 */
36
abstract class RankingSystemService implements \Tfboe\FmLib\Service\RankingSystem\RankingSystemInterface
37
{
38
//<editor-fold desc="Fields">
39
  /** @var EntityManagerInterface */
40
  private $entityManager;
41
  /** @var TimeServiceInterface */
42
  private $timeService;
43
  /** @var EntityComparerInterface */
44
  private $entityComparer;
45
  /**
46
   * @var RankingSystemChangeInterface[][]
47
   * first key: tournament hierarchy entity id
48
   * second key: player id
49
   */
50
  private $changes;
51
  /**
52
   * @var RankingSystemChangeInterface[][]
53
   * first key: tournament hierarchy entity id
54
   * second key: player id
55
   */
56
  private $oldChanges;
57
  /**
58
   * List of ranking systems for which update ranking got already called, indexed by id
59
   * @var RankingSystemService[]
60
   */
61
  private $updateRankingCalls;
62
63
  /** @var ObjectCreatorServiceInterface */
64
  private $objectCreatorService;
65
//</editor-fold desc="Fields">
66
67
//<editor-fold desc="Constructor">
68
  /**
69
   * RankingSystemService constructor.
70
   * @param EntityManagerInterface $entityManager
71
   * @param TimeServiceInterface $timeService
72
   * @param EntityComparerInterface $entityComparer
73
   * @param ObjectCreatorServiceInterface $objectCreatorService
74
   */
75
  public function __construct(EntityManagerInterface $entityManager, TimeServiceInterface $timeService,
76
                              EntityComparerInterface $entityComparer,
77
                              ObjectCreatorServiceInterface $objectCreatorService)
78
  {
79
    $this->entityManager = $entityManager;
80
    $this->timeService = $timeService;
81
    $this->entityComparer = $entityComparer;
82
    $this->changes = [];
83
    $this->oldChanges = [];
84
    $this->updateRankingCalls = [];
85
    $this->objectCreatorService = $objectCreatorService;
86
  }
87
//</editor-fold desc="Constructor">
88
89
//<editor-fold desc="Public Methods">
90
  /**
91
   * @inheritDoc
92
   */
93
  public function getEarliestInfluence(RankingSystemInterface $ranking, TournamentInterface $tournament): ?DateTime
94
  {
95
    return $this->getEarliestEntityInfluence($ranking, $tournament, false);
96
  }
97
98
  /**
99
   * @inheritdoc
100
   * @throws PreconditionFailedException
101
   */
102
  public function updateRankingForTournament(RankingSystemInterface $ranking, TournamentInterface $tournament,
103
                                             ?DateTime $oldInfluence)
104
  {
105
    $earliestInfluence = $this->getEarliestInfluence($ranking, $tournament);
106
    if ($oldInfluence !== null &&
107
      ($earliestInfluence === null || $oldInfluence < $earliestInfluence)) {
108
      $earliestInfluence = $oldInfluence;
109
    }
110
    if ($earliestInfluence !== null) {
111
      $this->updateRankingFrom($ranking, $earliestInfluence);
112
    }
113
  }
114
115
  /**
116
   * @inheritDoc
117
   * @throws PreconditionFailedException
118
   */
119
  public function updateRankingFrom(RankingSystemInterface $ranking, DateTime $from)
120
  {
121
    // can only be called once per ranking system!!!
122
    if (array_key_exists($ranking->getId(), $this->updateRankingCalls)) {
123
      throw new PreconditionFailedException();
124
    }
125
    $this->updateRankingCalls[$ranking->getId()] = $ranking;
126
    //find first reusable
127
    /** @var RankingSystemListInterface[] $lists */
128
    $lists = array_values($ranking->getLists()->toArray());
129
130
    $current = null;
131
    /** @var RankingSystemListInterface $lastReusable */
132
    $lastReusable = null;
133
    $toUpdate = [];
134
135
    foreach ($lists as $list) {
136
      if ($list->isCurrent()) {
137
        $current = $list;
138
      } else if ($list->getLastEntryTime() >= $from) {
139
        $toUpdate[] = $list;
140
      } else if ($lastReusable === null || $list->getLastEntryTime() > $lastReusable->getLastEntryTime()) {
141
        $lastReusable = $list;
142
      }
143
    }
144
145
    if ($current !== null && $current->getLastEntryTime() < $from) {
146
      $lastReusable = $current;
147
    }
148
149
    if ($lastReusable === null) {
150
      $lastReusable = $this->objectCreatorService->createObjectFromInterface(RankingSystemListInterface::class);
151
    }
152
153
    usort($toUpdate, function (RankingSystemListInterface $list1, RankingSystemListInterface $list2) {
154
      return $list1->getLastEntryTime() <=> $list2->getLastEntryTime();
155
    });
156
157
158
    $lastListTime = null;
159
    foreach ($toUpdate as $list) {
160
      $entities = $this->getNextEntities($ranking, $lastReusable, $list, $lastListTime);
161
      $this->recomputeBasedOn($list, $lastReusable, $entities, $lastListTime);
162
      $lastReusable = $list;
163
      $lastListTime = $lastReusable->getLastEntryTime();
164
    }
165
166
    if ($current === null) {
167
      /** @var RankingSystemListInterface $current */
168
      $current = $this->objectCreatorService->createObjectFromInterface(RankingSystemListInterface::class);
169
      $current->setCurrent(true);
170
      $this->entityManager->persist($current);
171
      $current->setRankingSystem($ranking);
172
    }
173
174
    $entities = $this->getNextEntities($ranking, $lastReusable, $current, $lastListTime);
175
    $this->recomputeBasedOn($current, $lastReusable, $entities, $lastListTime);
176
    $this->deleteOldChanges();
177
  }
178
//</editor-fold desc="Public Methods">
179
180
//<editor-fold desc="Protected Final Methods">
181
  /**
182
   * Computes the average rating of the given entries
183
   * @param RankingSystemListEntryInterface[] $entries
184
   * @return float
185
   */
186
  protected final function getAverage(array $entries): float
187
  {
188
    $sum = 0.0;
189
    foreach ($entries as $entry) {
190
      $sum += $entry->getPoints();
191
    }
192
    if (count($entries) === 0) {
193
      return 0.0;
194
    } else {
195
      return $sum / count($entries);
196
    }
197
  }
198
199
  /**
200
   * Gets the relevant entities for updating
201
   * @param RankingSystemInterface $ranking the ranking for which to get the entities
202
   * @param DateTime $from search for entities with a time value LARGER than $from, i.e. don't search for entities
203
   *                       with time value exactly $from
204
   * @param DateTime to search for entities with a time value SMALLER OR EQUAL than $to
0 ignored issues
show
Bug introduced by
The type Tfboe\FmLib\Service\RankingSystem\to was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
205
   * @return TournamentHierarchyEntity[]
206
   */
207
  protected final function getEntities(RankingSystemInterface $ranking, DateTime $from, DateTime $to): array
208
  {
209
    $query = $this->getEntitiesQueryBuilder($ranking, $from, $to);
210
    return $query->getQuery()->getResult();
211
  }
212
213
  /**
214
   * @return EntityManagerInterface
215
   */
216
  protected final function getEntityManager(): EntityManagerInterface
217
  {
218
    return $this->entityManager;
219
  }
220
221
  /**
222
   * @param Collection|PlayerInterface[] $players
223
   * @param RankingSystemListInterface $list
224
   * @return RankingSystemListEntryInterface[] $entries
225
   */
226
  protected final function getEntriesOfPlayers(Collection $players, RankingSystemListInterface $list): array
227
  {
228
    $result = [];
229
    foreach ($players as $player) {
230
      $result[] = $this->getOrCreateRankingSystemListEntry($list, $player);
231
    }
232
    return $result;
233
  }
234
235
  /** @noinspection PhpDocMissingThrowsInspection */
236
  /**
237
   * Gets or creates a tournament system change entry for the given entity, ranking and player.
238
   * @param TournamentHierarchyInterface $entity the tournament hierarchy entity to search for
239
   * @param RankingSystemInterface $ranking the ranking system to search for
240
   * @param PlayerInterface $player the player to search for
241
   * @return RankingSystemChangeInterface the found or newly created ranking system change
242
   */
243
  protected final function getOrCreateChange(TournamentHierarchyInterface $entity, RankingSystemInterface $ranking,
244
                                             PlayerInterface $player)
245
  {
246
    $key1 = $entity->getId();
247
    $key2 = $player->getId();
248
    if (!array_key_exists($key1, $this->changes)) {
249
      $this->changes[$key1] = [];
250
    }
251
    if (array_key_exists($key1, $this->oldChanges) && array_key_exists($key2, $this->oldChanges[$key1])) {
252
      $this->changes[$key1][$key2] = $this->oldChanges[$key1][$key2];
253
      unset($this->oldChanges[$key1][$key2]);
254
    }
255
    if (!array_key_exists($key2, $this->changes[$key1])) {
256
      //create new change
257
      /** @var RankingSystemChangeInterface $change */
258
      $change = $this->objectCreatorService->createObjectFromInterface(RankingSystemChangeInterface::class,
259
        [array_merge(array_keys($this->getAdditionalFields()), $this->getAdditionalChangeFields())]);
260
      foreach ($this->getAdditionalFields() as $field => $value) {
261
        // PropertyNotExistingException => we know for sure that the property exists (see 2 lines above)
262
        /** @noinspection PhpUnhandledExceptionInspection */
263
        $change->setProperty($field, 0);
264
      }
265
      $change->setHierarchyEntity($entity);
266
      $change->setRankingSystem($ranking);
267
      $change->setPlayer($player);
268
      $this->entityManager->persist($change);
269
      $this->changes[$key1][$key2] = $change;
270
    }
271
    return $this->changes[$key1][$key2];
272
  }
273
274
  /** @noinspection PhpDocMissingThrowsInspection */ //PropertyNotExistingException
275
276
  /**
277
   * @param RankingSystemListInterface $list the list in which to search for the entry or in which to add it
278
   * @param PlayerInterface $player the player to search for
279
   * @return RankingSystemListEntryInterface the found or the new entry
280
   */
281
  protected final function getOrCreateRankingSystemListEntry(RankingSystemListInterface $list,
282
                                                             PlayerInterface $player): RankingSystemListEntryInterface
283
  {
284
    $playerId = $player->getId();
285
    if (!$list->getEntries()->containsKey($playerId)) {
286
      /** @var RankingSystemListEntryInterface $entry */
287
      $entry = $this->objectCreatorService->createObjectFromInterface(RankingSystemListEntryInterface::class,
288
        [array_keys($this->getAdditionalFields())]);
289
      $entry->setPlayer($player);
290
      $entry->setRankingSystemList($list);
291
      $this->resetListEntry($entry);
292
      $this->entityManager->persist($entry);
293
    }
294
    return $list->getEntries()->get($playerId);
295
  }
296
297
  /** @noinspection PhpDocMissingThrowsInspection */ //PropertyNotExistingException
298
//</editor-fold desc="Protected Final Methods">
299
300
//<editor-fold desc="Protected Methods">
301
  /**
302
   * Gets additional fields for this ranking type mapped to its start value
303
   * @return string[] list of additional fields
304
   */
305
  protected function getAdditionalChangeFields(): array
306
  {
307
    return [];
308
  }
309
310
  /**
311
   * Gets additional fields for this ranking type mapped to its start value
312
   * @return string[] list of additional fields
313
   */
314
  protected abstract function getAdditionalFields(): array;
315
316
  /**
317
   * Gets all ranking changes for the given entity for the given list. Must return a change for each involved player.
318
   * The field pointsAfterwards get calculated afterwards and can be left empty.
319
   * @param TournamentHierarchyEntity $entity the entity for which to compute the ranking changes
320
   * @param RankingSystemListInterface $list the list for which to compute the ranking changes
321
   * @return RankingSystemChangeInterface[] the changes
322
   */
323
  protected abstract function getChanges(TournamentHierarchyEntity $entity, RankingSystemListInterface $list): array;
324
325
  /**
326
   * Gets a query for getting the relevant entities for updating
327
   * @param RankingSystemInterface $ranking the ranking for which to get the entities
328
   * @param DateTime $from search for entities with a time value LARGER than $from, i.e. don't search for entities
329
   *                       with time value exactly $from
330
   * @param DateTime to search for entities with a time value SMALLER OR EQUAL than $to
331
   * @return QueryBuilder
332
   */
333
  protected abstract function getEntitiesQueryBuilder(RankingSystemInterface $ranking,
334
                                                      DateTime $from, DateTime $to): QueryBuilder;
335
336
  /**
337
   * Gets the level of the ranking system service (see Level Enum)
338
   * @return int
339
   */
340
  protected abstract function getLevel(): int;
341
342
  /**
343
   * Gets the start points for a new player in the ranking
344
   * @return float
345
   */
346
  protected function startPoints(): float
347
  {
348
    return 0.0;
349
  }
350
//</editor-fold desc="Protected Methods">
351
352
//<editor-fold desc="Private Methods">
353
  /**
354
   * Clones all ranking values from base and inserts them into list, furthermore removes all remaining ranking values
355
   * of list. After this method was called list and base contain exactly the same rankings.
356
   * @param RankingSystemListInterface $list the ranking list to change
357
   * @param RankingSystemListInterface $base the ranking list to use as base list, this doesn't get changed
358
   */
359
  private function cloneInto(RankingSystemListInterface $list, RankingSystemListInterface $base)
360
  {
361
    /*//first remove all entries from list
362
    foreach($list->getEntries()->toArray() as $entry)
363
    {
364
      $list->getEntries()->removeElement($entry);
365
      $this->entityManager->remove($entry);
366
    }*/
367
368
    $clonedPlayers = [];
369
370
    foreach ($base->getEntries() as $entry) {
371
      $playerId = $entry->getPlayer()->getId();
372
      $clonedPlayers[$playerId] = true;
373
      if (!$list->getEntries()->containsKey($playerId)) {
374
        //create new entry
375
        /** @var RankingSystemListEntryInterface $entry */
376
        $clone = $this->objectCreatorService->createObjectFromInterface(RankingSystemListEntryInterface::class,
377
          [[]]);
378
        $this->entityManager->persist($clone);
379
        $clone->setPlayer($entry->getPlayer());
380
        $clone->setRankingSystemList($list);
381
      }
382
      $foundEntry = $list->getEntries()[$playerId];
383
      $foundEntry->setNumberRankedEntities($entry->getNumberRankedEntities());
384
      $foundEntry->setPoints($entry->getPoints());
385
      $foundEntry->cloneSubClassDataFrom($entry);
386
    }
387
388
    //remove all unused entries from list
389
    foreach ($list->getEntries()->toArray() as $playerId => $entry) {
390
      if (!array_key_exists($playerId, $clonedPlayers)) {
391
        $this->resetListEntry($entry);
392
        //$list->getEntries()->removeElement($entry);
393
        //$this->entityManager->remove($entry);
394
      }
395
    }
396
  }
397
398
  private function deleteOldChanges()
399
  {
400
    foreach ($this->oldChanges as $eId => $changes) {
401
      foreach ($changes as $pId => $change) {
402
        $this->entityManager->remove($change);
403
      }
404
    }
405
    $this->oldChanges = [];
406
  }
407
408
  /**
409
   * @param TournamentHierarchyInterface[] $entities
410
   * @param int &$current
411
   */
412
  private function flushAndForgetEntities(&$entities, &$current)
413
  {
414
    for ($i = 0; $i < $current; $i++) {
415
      $eId = $entities[$i]->getId();
416
      if (array_key_exists($eId, $this->oldChanges)) {
417
        foreach ($this->oldChanges[$eId] as $pId => $change) {
418
          $this->entityManager->remove($change);
419
        }
420
      }
421
      unset($this->oldChanges[$eId]);
422
    }
423
    $this->entityManager->flush();
424
    for ($i = 0; $i < $current; $i++) {
425
      $eId = $entities[$i]->getId();
426
      $this->entityManager->detach($entities[$i]);
427
      if (array_key_exists($eId, $this->changes)) {
428
        foreach ($this->changes[$eId] as $pId => $change) {
429
          $this->entityManager->detach($change);
430
        }
431
        unset($this->changes[$eId]);
432
      }
433
    }
434
    if ($current >= count($entities)) {
435
      $entities = [];
436
    } else {
437
      array_splice($entities, 0, $current);
438
    }
439
    $current = 0;
440
  }
441
442
  /**
443
   * Gets the earliest influence for the given entity
444
   * @param RankingSystemInterface $ranking the ranking system for which to get the influence
445
   * @param TournamentHierarchyInterface $entity the entity to analyze
446
   * @param bool $parentIsRanked true iff a predecessor contained the given ranking in its ranking systems
447
   * @return DateTime|null the earliest influence or null if $parentIsRanked is false and the entity and all its
448
   *                        successors do not have the ranking in its ranking systems
449
   */
450
  private function getEarliestEntityInfluence(RankingSystemInterface $ranking, TournamentHierarchyInterface $entity,
451
                                              bool $parentIsRanked): ?DateTime
452
  {
453
    $this->timeService->clearTimes();
454
    $entityIsRanked = $parentIsRanked || $entity->getRankingSystems()->containsKey($ranking->getId());
455
    if ($entity->getLevel() === $this->getLevel()) {
456
      if ($entityIsRanked) {
457
        return $this->timeService->getTime($entity);
458
      } else {
459
        return null;
460
      }
461
    }
462
    $result = null;
463
464
    foreach ($entity->getChildren() as $child) {
465
      $earliest = $this->getEarliestEntityInfluence($ranking, $child, $entityIsRanked);
466
      if ($result === null || ($earliest !== null && $earliest < $result)) {
467
        $result = $earliest;
468
      }
469
    }
470
    return $result;
471
  }
472
473
  /** @noinspection PhpDocMissingThrowsInspection */
474
  /**
475
   * @return DateTime
476
   */
477
  private function getMaxDate(): DateTime
478
  {
479
    /** @noinspection PhpUnhandledExceptionInspection P100Y is a valid spec */
480
    return (new DateTime())->add(new DateInterval('P100Y'));
481
  }
482
483
  /**
484
   * @param RankingSystemInterface $ranking
485
   * @param RankingSystemListInterface $lastReusable
486
   * @param RankingSystemListInterface $list
487
   * @param DateTime|null $lastListTime
488
   * @return array|TournamentHierarchyEntity[]
489
   */
490
  private function getNextEntities(RankingSystemInterface $ranking, RankingSystemListInterface $lastReusable,
491
                                   RankingSystemListInterface $list, ?DateTime &$lastListTime)
492
  {
493
    $this->deleteOldChanges();
494
    $entities = $this->getEntities($ranking, $lastReusable->getLastEntryTime(),
495
      $list->isCurrent() ? $this->getMaxDate() : $list->getLastEntryTime());
496
497
    //sort entities
498
    $this->timeService->clearTimes();
499
    usort($entities, function ($entity1, $entity2) {
500
      return $this->entityComparer->compareEntities($entity1, $entity2);
501
    });
502
503
    $this->markOldChangesAsDeleted($ranking, $entities);
504
505
    if ($lastListTime == null) {
506
      if (count($entities) > 0) {
507
        $lastListTime = max($lastReusable->getLastEntryTime(), $this->timeService->getTime($entities[0]));
508
      } else {
509
        $lastListTime = $lastReusable->getLastEntryTime();
510
      }
511
    }
512
513
    return $entities;
514
  }
515
516
  /** @noinspection PhpDocMissingThrowsInspection */ //new DateTime() does not throw an exception
517
  /**
518
   * @param DateTime $time the time of the last list
519
   * @param int $generationLevel the list generation level
520
   * @return DateTime the time of the next list generation
521
   */
522
  private function getNextGenerationTime(DateTime $time, int $generationLevel): DateTime
523
  {
524
    $year = (int)$time->format('Y');
525
    $month = (int)$time->format('m');
526
    if ($generationLevel === AutomaticInstanceGeneration::MONTHLY) {
527
      $month += 1;
528
      if ($month == 13) {
529
        $month = 1;
530
        $year += 1;
531
      }
532
    } else if ($generationLevel === AutomaticInstanceGeneration::OFF) {
533
      return $this->getMaxDate();
534
    } else {
535
      $year += 1;
536
    }
537
    return (new DateTime())->setDate($year, $month, 1)->setTime(0, 0, 0);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new DateTime()->s...h, 1)->setTime(0, 0, 0) could return the type false which is incompatible with the type-hinted return DateTime. Consider adding an additional type-check to rule them out.
Loading history...
538
  }
539
540
  /**
541
   * @param RankingSystemInterface $ranking
542
   * @param TournamentHierarchyEntity[] $entities
543
   */
544
  private function markOldChangesAsDeleted(RankingSystemInterface $ranking, array $entities)
545
  {
546
    assert(count($this->oldChanges) == 0);
547
    $this->changes = [];
548
    $queryBuilder = $this->entityManager->createQueryBuilder();
549
    /** @var RankingSystemChangeInterface[] $changes */
550
    $changes = $queryBuilder
551
      ->from(RankingSystemChangeInterface::class, 'c')
552
      ->select('c')
553
      ->where($queryBuilder->expr()->eq('c.rankingSystem', ':ranking'))
554
      ->setParameter('ranking', $ranking)
555
      ->andWhere($queryBuilder->expr()->in('c.hierarchyEntity', ':entities'))
556
      ->setParameter('entities', $entities)
557
      ->getQuery()->getResult();
558
    foreach ($changes as $change) {
559
      $eId = $change->getHierarchyEntity()->getId();
560
      $pId = $change->getPlayer()->getId();
561
      if (array_key_exists($eId, $this->oldChanges) && array_key_exists($pId, $this->oldChanges[$eId])) {
562
        //duplicate entry
563
        assert($this->oldChanges[$eId][$pId]->getRankingSystem()->getId() ===
564
          $change->getRankingSystem()->getId());
565
        $this->entityManager->remove($change);
566
      } else {
567
        $this->oldChanges[$eId][$pId] = $change;
568
      }
569
    }
570
  }
571
572
  /** @noinspection PhpDocMissingThrowsInspection */ //PropertyNotExistingException
573
574
  /**
575
   * Recomputes the given ranking list by using base as base list and applying the changes for the given entities
576
   * starting from the given index. If list is not the current list only the entities up to $list->getLastEntryTime()
577
   * are applied and the index gets changed accordingly.
578
   * @param RankingSystemListInterface $list the list to recompute
579
   * @param RankingSystemListInterface $base the list to use as base
580
   * @param TournamentHierarchyEntity[] $entities the list of entities to use for the computation
581
   * @param DateTime $lastListTime the time of the last list or the first entry
582
   */
583
  private function recomputeBasedOn(RankingSystemListInterface $list, RankingSystemListInterface $base,
584
                                    array &$entities, DateTime $lastListTime)
585
  {
586
    $nextGeneration = $this->getNextGenerationTime($lastListTime, $list->getRankingSystem()->getGenerationInterval());
587
    $this->cloneInto($list, $base);
588
    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...
589
      $time = $this->timeService->getTime($entities[$i]);
590
      if (!$list->isCurrent() && $time > $list->getLastEntryTime()) {
591
        $this->flushAndForgetEntities($entities, $i);
592
        return;
593
      }
594
      if ($nextGeneration < $time) {
595
        /** @var RankingSystemListInterface $newList */
596
        $newList = $this->objectCreatorService->createObjectFromInterface(RankingSystemListInterface::class);
597
        $newList->setCurrent(false);
598
        $newList->setLastEntryTime($nextGeneration);
599
        $this->entityManager->persist($newList);
600
        $newList->setRankingSystem($list->getRankingSystem());
601
        $this->cloneInto($newList, $list);
602
        $nextGeneration = $this->getNextGenerationTime($nextGeneration,
603
          $list->getRankingSystem()->getGenerationInterval());
604
        //clear entityManager to save memory
605
        $this->flushAndForgetEntities($entities, $i);
606
      }
607
      $changes = $this->getChanges($entities[$i], $list);
608
      foreach ($changes as $change) {
609
        $entry = $this->getOrCreateRankingSystemListEntry($list, $change->getPlayer());
610
        $entry->setNumberRankedEntities($entry->getNumberRankedEntities() + 1);
611
        $pointsAfterwards = $entry->getPoints() + $change->getPointsChange();
612
        $entry->setPoints($pointsAfterwards);
613
        $change->setPointsAfterwards($pointsAfterwards);
614
        //apply further changes
615
        foreach ($this->getAdditionalFields() as $field => $value) {
616
          // PropertyNotExistingException => entry and field have exactly the static properties from getAdditionalFields
617
          /** @noinspection PhpUnhandledExceptionInspection */
618
          $entry->setProperty($field, $entry->getProperty($field) + $change->getProperty($field));
619
        }
620
        if ($time > $list->getLastEntryTime()) {
621
          $list->setLastEntryTime($time);
622
        }
623
        $this->entityManager->persist($change);
624
      }
625
    }
626
    $current = count($entities);
627
    $this->flushAndForgetEntities($entities, $current);
628
  }
629
630
  /** @noinspection PhpDocMissingThrowsInspection */
631
  /**
632
   * @param RankingSystemListEntryInterface $entry
633
   */
634
  private function resetListEntry(RankingSystemListEntryInterface $entry)
635
  {
636
    $entry->setPoints($this->startPoints());
637
    $entry->setNumberRankedEntities(0);
638
    foreach ($this->getAdditionalFields() as $field => $value) {
639
      // PropertyNotExistingException => we know for sure that the property exists (see 2 lines above)
640
      /** @noinspection PhpUnhandledExceptionInspection */
641
      $entry->setProperty($field, $value);
642
    }
643
  }
644
//</editor-fold desc="Private Methods">
645
}