LoadingService::loadEntities()   F
last analyzed

Complexity

Conditions 22
Paths 280

Size

Total Lines 79

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
nc 280
nop 2
dl 0
loc 79
rs 2.3333
c 0
b 0
f 0

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
namespace Tfboe\FmLib\Service;
5
6
use Doctrine\Common\Collections\AbstractLazyCollection;
7
use Doctrine\Common\Collections\ArrayCollection;
8
use Doctrine\Common\Collections\Collection;
9
use Doctrine\ORM\EntityManager;
10
use Doctrine\ORM\EntityManagerInterface;
11
use Tfboe\FmLib\Entity\CompetitionInterface;
12
use Tfboe\FmLib\Entity\GameInterface;
13
use Tfboe\FmLib\Entity\Helpers\IdAble;
14
use Tfboe\FmLib\Entity\MatchInterface;
15
use Tfboe\FmLib\Entity\PhaseInterface;
16
use Tfboe\FmLib\Entity\RankingInterface;
17
use Tfboe\FmLib\Entity\TeamInterface;
18
use Tfboe\FmLib\Entity\TeamMembershipInterface;
19
use Tfboe\FmLib\Entity\TournamentInterface;
20
21
22
/**
23
 * Class LoadingService
24
 * @package App\Service
25
 */
26
class LoadingService implements LoadingServiceInterface
27
{
28
//<editor-fold desc="Fields">
29
  /**
30
   * @var string
31
   */
32
  protected $defaultPropertiesToLoad;
33
  /**
34
   * @var EntityManager
35
   */
36
  private $em;
37
//</editor-fold desc="Fields">
38
39
//<editor-fold desc="Constructor">
40
  /**
41
   * LoadingService constructor.
42
   * @param EntityManagerInterface $em
43
   */
44
  public function __construct(
45
    EntityManagerInterface $em
46
  )
47
  {
48
    $this->em = $em;
0 ignored issues
show
Documentation Bug introduced by
$em is of type object<Doctrine\ORM\EntityManagerInterface>, but the property $em was declared to be of type object<Doctrine\ORM\EntityManager>. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
49
    $this->defaultPropertiesToLoad = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array(\Tfboe\FmLib\Entit...layersA', 'playersB'))) of type array<string|integer,arr...{\"0\":\"string\"}>"}>> is incompatible with the declared type string of property $defaultPropertiesToLoad.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
50
      TournamentInterface::class => [["competitions"]],
51
      CompetitionInterface::class => [["teams"], ["phases"]],
52
      TeamInterface::class => [["memberships"]],
53
      TeamMembershipInterface::class => [["player", "team"]],
54
      PhaseInterface::class => [["preQualifications"], ["postQualifications"], ["rankings"], ["matches"]],
55
      RankingInterface::class => [["teams"]],
56
      MatchInterface::class => [["rankingsA", "rankingsB"], ["games"]],
57
      GameInterface::class => [["playersA", "playersB"]],
58
    ];
59
  }
60
//</editor-fold desc="Constructor">
61
62
63
//<editor-fold desc="Public Methods">
64
  /**
65
   * @inheritDoc
66
   */
67
  public function loadEntities(array $entities, ?array $propertyMap = null)
68
  {
69
    if ($propertyMap === null) {
70
      $propertyMap = $this->defaultPropertiesToLoad;
71
    }
72
    //build groups for each type
73
    $toDoEntityIds = [];
74
    $done = [];
75
    foreach ($entities as $entity) {
76
      if (!array_key_exists($entity->getEntityId(), $done)) {
77
        $done[$entity->getEntityId()] = true;
78
        $class = $this->keyOfPropertyMap($entity, $propertyMap);
0 ignored issues
show
Documentation introduced by
$propertyMap is of type string|array, but the function expects a array<integer,array<inte...array<integer,string>>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
79
        if ($class !== null) {
80
          if (!array_key_exists($class, $toDoEntityIds)) {
81
            $toDoEntityIds[$class] = [];
82
          }
83
          $toDoEntityIds[$class][] = $entity;
84
          break;
85
        }
86
      }
87
    }
88
    while (count($toDoEntityIds) > 0) {
89
      $entities = reset($toDoEntityIds);
90
91
      $class = key($toDoEntityIds);
92
      unset($toDoEntityIds[$class]);
93
      foreach ($propertyMap[$class] as $properties) {
94
        //eliminate entities which have already loaded the properties
95
        $ids = array_map(function (IdAble $e) {
96
          return $e->getEntityId();
97
        }, array_filter($entities, function (IdAble $e) use ($properties) {
98
          foreach ($properties as $property) {
99
            $getter = "get" . ucfirst($property);
100
            $object = $e->$getter();
101
            if ($object === null) {
102
              return true;
103
            }
104
            if ($object instanceof AbstractLazyCollection) {
105
              /** @var $object AbstractLazyCollection */
106
              if (!$object->isInitialized()) {
107
                return true;
108
              }
109
            } else if (!property_exists($object, '__isInitialized__')) {
110
              return true;
111
            }
112
          }
113
          return false;
114
        }));
115
        if (count($ids) > 0) {
116
          $this->loadProperties($ids, $class, $properties);
117
        }
118
        //check loaded subproperties if they also have subproperties which need to get loaded
119
        foreach ($entities as $entity) {
120
          foreach ($properties as $property) {
121
            $getter = "get" . ucfirst($property);
122
            $object = $entity->$getter();
123
            if ($object !== null) {
124
              if (!$object instanceof Collection) {
125
                $object = new ArrayCollection([$object]);
126
              }
127
              /** @var $object Collection|IdAble[] */
128
              foreach ($object as $subObject) {
129
                if (!array_key_exists($subObject->getEntityId(), $done)) {
130
                  $done[$subObject->getEntityId()] = true;
131
                  $subClass = $this->keyOfPropertyMap($subObject, $propertyMap);
0 ignored issues
show
Documentation introduced by
$propertyMap is of type string|array, but the function expects a array<integer,array<inte...array<integer,string>>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
132
                  if ($subClass !== null) {
133
                    if (!array_key_exists($subClass, $toDoEntityIds)) {
134
                      $toDoEntityIds[$subClass] = [];
135
                    }
136
                    $toDoEntityIds[$subClass][] = $subObject;
137
                  }
138
                }
139
              }
140
            }
141
          }
142
        }
143
      }
144
    }
145
  }
146
//</editor-fold desc="Public Methods">
147
148
//<editor-fold desc="Private Methods">
149
  /**
150
   * Computes the key corresponding to the given entity in the given property map, or null if no key was found.
151
   * @param mixed $entity the entity to search the key for
152
   * @param string[][][] $propertyMap a property map which maps classes to lists of groups of properties to fetch
153
   * @return null|string
154
   */
155
  private function keyOfPropertyMap($entity, $propertyMap): ?string
156
  {
157
    foreach (array_keys($propertyMap) as $class) {
158
      if ($entity instanceof $class) {
159
        return $class;
160
      }
161
    }
162
    return null;
163
  }
164
165
  /**
166
   * @param string[] $ids a list of ids of entities to get the properties for
167
   * @param string $class the class of the entities
168
   * @param string[] $properties a list of properties to get for each entity
169
   */
170
  private function loadProperties(array $ids, string $class, array $properties): void
171
  {
172
    $builder = $this->em->createQueryBuilder()
173
      ->select("t1")
174
      ->from($class, "t1");
175
    $count = 1;
176
    foreach ($properties as $property) {
177
      $count++;
178
      $table = "t" . (string)$count;
179
      $builder->addSelect($table);
180
      $builder->leftJoin("t1." . $property, $table);
181
    }
182
    $builder->where($builder->expr()->in("t1.id", $ids));
183
    $builder->getQuery()->getResult();
184
  }
185
//</editor-fold desc="Private Methods">
186
}
187