Issues (457)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Service/RankingSystem/RankingSystemService.php (5 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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
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
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
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
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
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,
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_merge(array_keys($this->getAdditionalFields()), $this->getAdditionalChangeFields())]);
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,
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;
351
352
  /**
353
   * Gets additional fields for this ranking type mapped to its start value
354
   * @return string[] list of additional fields
355
   */
356
  protected function getAdditionalChangeFields(): array
357
  {
358
    return [];
359
  }
360
361
  /**
362
   * Gets all ranking changes for the given entity for the given list. Must return a change for each involved player.
363
   * The field pointsAfterwards get calculated afterwards and can be left empty.
364
   * @param TournamentHierarchyEntity $entity the entity for which to compute the ranking changes
365
   * @param RankingSystemListInterface $list the list for which to compute the ranking changes
366
   * @return RankingSystemChangeInterface[] the changes
367
   */
368
  protected abstract function getChanges(TournamentHierarchyEntity $entity, RankingSystemListInterface $list): array;
369
370
  /**
371
   * Gets a query for getting the relevant entities for updating
372
   * @param RankingSystemInterface $ranking the ranking for which to get the entities
373
   * @param \DateTime $from search for entities with a time value LARGER than $from, i.e. don't search for entities with
374
   *                        time value exactly $from
375
   * @param \DateTime to search for entities with a time value SMALLER OR EQUAL than $to
376
   * @return QueryBuilder
377
   */
378
  protected abstract function getEntitiesQueryBuilder(RankingSystemInterface $ranking,
379
                                                      \DateTime $from, \DateTime $to): QueryBuilder;
380
381
  /**
382
   * Gets the level of the ranking system service (see Level Enum)
383
   * @return int
384
   */
385
  protected abstract function getLevel(): int;
386
387
  /**
388
   * Gets the start points for a new player in the ranking
389
   * @return float
390
   */
391
  protected function startPoints(): float
392
  {
393
    return 0.0;
394
  }
395
//</editor-fold desc="Protected Methods">
396
397
//<editor-fold desc="Private Methods">
398
  /**
399
   * Clones all ranking values from base and inserts them into list, furthermore removes all remaining ranking values of
400
   * list. After this method was called list and base contain exactly the same rankings.
401
   * @param RankingSystemListInterface $list the ranking list to change
402
   * @param RankingSystemListInterface $base the ranking list to use as base list, this doesn't get changed
403
   */
404
  private function cloneInto(RankingSystemListInterface $list, RankingSystemListInterface $base)
405
  {
406
    /*//first remove all entries from list
407
    foreach($list->getEntries()->toArray() as $entry)
408
    {
409
      $list->getEntries()->removeElement($entry);
410
      $this->entityManager->remove($entry);
411
    }*/
412
413
    $clonedPlayers = [];
414
415
    foreach ($base->getEntries() as $entry) {
416
      $playerId = $entry->getPlayer()->getId();
417
      $clonedPlayers[$playerId] = true;
418
      if (!$list->getEntries()->containsKey($playerId)) {
419
        //create new entry
420
        /** @var RankingSystemListEntryInterface $entry */
421
        $clone = $this->objectCreatorService->createObjectFromInterface(RankingSystemListEntryInterface::class,
422
          [[]]);
423
        $this->entityManager->persist($clone);
424
        $clone->setPlayer($entry->getPlayer());
425
        $clone->setRankingSystemList($list);
426
      }
427
      $foundEntry = $list->getEntries()[$playerId];
428
      $foundEntry->setNumberRankedEntities($entry->getNumberRankedEntities());
429
      $foundEntry->setPoints($entry->getPoints());
430
      $foundEntry->cloneSubClassDataFrom($entry);
431
    }
432
433
    //remove all unused entries from list
434
    foreach ($list->getEntries()->toArray() as $playerId => $entry) {
435
      if (!array_key_exists($playerId, $clonedPlayers)) {
436
        $this->resetListEntry($entry);
437
        //$list->getEntries()->removeElement($entry);
438
        //$this->entityManager->remove($entry);
439
      }
440
    }
441
  }
442
443
444
  /**
445
   * @param RankingSystemInterface $ranking
446
   * @param TournamentHierarchyEntity[] $entities
447
   */
448
  private function markOldChangesAsDeleted(RankingSystemInterface $ranking, array $entities)
449
  {
450
    assert(count($this->oldChanges) == 0);
451
    $this->changes = [];
452
    $queryBuilder = $this->entityManager->createQueryBuilder();
453
    /** @var RankingSystemChangeInterface[] $changes */
454
    $changes = $queryBuilder
455
      ->from(RankingSystemChangeInterface::class, 'c')
456
      ->select('c')
457
      ->where($queryBuilder->expr()->eq('c.rankingSystem', ':ranking'))
458
      ->setParameter('ranking', $ranking)
459
      ->andWhere($queryBuilder->expr()->in('c.hierarchyEntity', ':entities'))
460
      ->setParameter('entities', $entities)
461
      ->getQuery()->getResult();
462
    foreach ($changes as $change) {
463
      $eId = $change->getHierarchyEntity()->getId();
464
      $pId = $change->getPlayer()->getId();
465
      if (array_key_exists($eId, $this->oldChanges) && array_key_exists($pId, $this->oldChanges[$eId])) {
466
        //duplicate entry
467
        assert($this->oldChanges[$eId][$pId]->getRankingSystem()->getId() ===
468
          $change->getRankingSystem()->getId());
469
        $this->entityManager->remove($change);
470
      } else {
471
        $this->oldChanges[$eId][$pId] = $change;
472
      }
473
    }
474
  }
475
476
  private function deleteOldChanges()
477
  {
478
    foreach ($this->oldChanges as $eId => $changes) {
479
      foreach ($changes as $pId => $change) {
480
        $this->entityManager->remove($change);
481
      }
482
    }
483
    $this->oldChanges = [];
484
  }
485
486
  /**
487
   * Gets the earliest influence for the given entity
488
   * @param RankingSystemInterface $ranking the ranking system for which to get the influence
489
   * @param TournamentHierarchyInterface $entity the entity to analyze
490
   * @param bool $parentIsRanked true iff a predecessor contained the given ranking in its ranking systems
491
   * @return \DateTime|null the earliest influence or null if $parentIsRanked is false and the entity and all its
492
   *                        successors do not have the ranking in its ranking systems
493
   */
494
  private function getEarliestEntityInfluence(RankingSystemInterface $ranking, TournamentHierarchyInterface $entity,
495
                                              bool $parentIsRanked): ?\DateTime
496
  {
497
    $this->timeService->clearTimes();
498
    $entityIsRanked = $parentIsRanked || $entity->getRankingSystems()->containsKey($ranking->getId());
499
    if ($entity->getLevel() === $this->getLevel()) {
500
      if ($entityIsRanked) {
501
        return $this->timeService->getTime($entity);
502
      } else {
503
        return null;
504
      }
505
    }
506
    $result = null;
507
508
    foreach ($entity->getChildren() as $child) {
509
      $earliest = $this->getEarliestEntityInfluence($ranking, $child, $entityIsRanked);
510
      if ($result === null || ($earliest !== null && $earliest < $result)) {
511
        $result = $earliest;
512
      }
513
    }
514
    return $result;
515
  }
516
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);
538
  }
539
540
  /**
541
   * @return \DateTime
542
   * @throws \Exception
543
   */
544
  private function getMaxDate(): \DateTime
545
  {
546
    return (new \DateTime())->add(new \DateInterval('P100Y'));
547
  }
548
549
  /** @noinspection PhpDocMissingThrowsInspection */ //PropertyNotExistingException
550
  /**
551
   * Recomputes the given ranking list by using base as base list and applying the changes for the given entities
552
   * starting from the given index. If list is not the current list only the entities up to $list->getLastEntryTime()
553
   * are applied and the index gets changed accordingly.
554
   * @param RankingSystemListInterface $list the list to recompute
555
   * @param RankingSystemListInterface $base the list to use as base
556
   * @param TournamentHierarchyEntity[] $entities the list of entities to use for the computation
557
   * @param \DateTime $lastListTime the time of the last list or the first entry
558
   */
559
  private function recomputeBasedOn(RankingSystemListInterface $list, RankingSystemListInterface $base,
560
                                    array &$entities, \DateTime $lastListTime)
561
  {
562
    $nextGeneration = $this->getNextGenerationTime($lastListTime, $list->getRankingSystem()->getGenerationInterval());
563
    $this->cloneInto($list, $base);
564
    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...
565
      $time = $this->timeService->getTime($entities[$i]);
566
      if (!$list->isCurrent() && $time > $list->getLastEntryTime()) {
567
        $this->flushAndForgetEntities($entities, $i);
568
        return;
569
      }
570
      if ($nextGeneration < $time) {
571
        /** @var RankingSystemListInterface $newList */
572
        $newList = $this->objectCreatorService->createObjectFromInterface(RankingSystemListInterface::class);
573
        $newList->setCurrent(false);
574
        $newList->setLastEntryTime($nextGeneration);
575
        $this->entityManager->persist($newList);
576
        $newList->setRankingSystem($list->getRankingSystem());
577
        $this->cloneInto($newList, $list);
578
        $nextGeneration = $this->getNextGenerationTime($nextGeneration,
579
          $list->getRankingSystem()->getGenerationInterval());
580
        //clear entityManager to save memory
581
        $this->flushAndForgetEntities($entities, $i);
582
      }
583
      $changes = $this->getChanges($entities[$i], $list);
0 ignored issues
show
$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...
584
      foreach ($changes as $change) {
585
        $entry = $this->getOrCreateRankingSystemListEntry($list, $change->getPlayer());
586
        $entry->setNumberRankedEntities($entry->getNumberRankedEntities() + 1);
587
        $pointsAfterwards = $entry->getPoints() + $change->getPointsChange();
588
        $entry->setPoints($pointsAfterwards);
589
        $change->setPointsAfterwards($pointsAfterwards);
590
        //apply further changes
591
        foreach ($this->getAdditionalFields() as $field => $value) {
592
          // PropertyNotExistingException => entry and field have exactly the static properties from getAdditionalFields
593
          /** @noinspection PhpUnhandledExceptionInspection */
594
          $entry->setProperty($field, $entry->getProperty($field) + $change->getProperty($field));
595
        }
596
        if ($time > $list->getLastEntryTime()) {
597
          $list->setLastEntryTime($time);
598
        }
599
        $this->entityManager->persist($change);
600
      }
601
    }
602
    $current = count($entities);
603
    $this->flushAndForgetEntities($entities, $current);
604
  }
605
606
  /**
607
   * @param TournamentHierarchyInterface[] $entities
608
   * @param int &$current
609
   */
610
  private function flushAndForgetEntities(&$entities, &$current)
611
  {
612
    for ($i = 0; $i < $current; $i++) {
613
      $eId = $entities[$i]->getId();
614
      if (array_key_exists($eId, $this->oldChanges)) {
615
        foreach ($this->oldChanges[$eId] as $pId => $change) {
616
          $this->entityManager->remove($change);
617
        }
618
      }
619
      unset($this->oldChanges[$eId]);
620
    }
621
    $this->entityManager->flush();
622
    for ($i = 0; $i < $current; $i++) {
623
      $eId = $entities[$i]->getId();
624
      $this->entityManager->detach($entities[$i]);
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\Common\Persiste...ObjectManager::detach() has been deprecated with message: Detach operation is deprecated and will be removed in Persistence 2.0. Please use {@see ObjectManager::clear()} instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
625
      if (array_key_exists($eId, $this->changes)) {
626
        foreach ($this->changes[$eId] as $pId => $change) {
627
          $this->entityManager->detach($change);
0 ignored issues
show
Deprecated Code introduced by
The method Doctrine\Common\Persiste...ObjectManager::detach() has been deprecated with message: Detach operation is deprecated and will be removed in Persistence 2.0. Please use {@see ObjectManager::clear()} instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
628
        }
629
        unset($this->changes[$eId]);
630
      }
631
    }
632
    if ($current >= count($entities)) {
633
      $entities = [];
634
    } else {
635
      array_splice($entities, 0, $current);
636
    }
637
    $current = 0;
638
  }
639
//</editor-fold desc="Private Methods">
640
}