Completed
Pull Request — develop (#311)
by
unknown
17:12
created

AttachedEntitiesCollection   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 179
c 0
b 0
f 0
wmc 25
lcom 1
cbo 5
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
B setAttachedEntity() 0 28 4
A getAttachedEntity() 0 17 3
A removeAttachedEntity() 0 17 3
A doGetEntity() 0 5 1
A map() 0 4 1
A filter() 0 4 1
A partition() 0 15 3
C matching() 0 28 7
1
<?php
2
/**
3
 * YAWIK
4
*
5
* @filesource
6
* @copyright (c) 2013 - 2016 Cross Solution (http://cross-solution.de)
7
* @license   MIT
8
* @author Miroslav Fedeleš <[email protected]>
9
* @since 0.28
10
*/
11
12
namespace Core\Entity\Collection;
13
14
use Core\Repository\RepositoryService;
15
use Core\Entity\AttachedEntityReference;
16
use Core\Entity\IdentifiableEntityInterface;
17
use InvalidArgumentException;
18
use Doctrine\Common\Collections\ArrayCollection;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Core\Entity\Collection\ArrayCollection.

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...
19
use Closure;
20
use Doctrine\Common\Collections\Expr\ClosureExpressionVisitor;
21
use Doctrine\Common\Collections\Criteria;
22
23
class AttachedEntitiesCollection extends ArrayCollection
24
{
25
26
    /**
27
     * @var RepositoryService
28
     */
29
    protected $repositories;
30
31
    /**
32
     * @var AttachedEntityReference
33
     */
34
    protected $referencePrototype;
35
36
    /**
37
     * @param RepositoryService $repositories
38
     * @param AttachedEntityReference $referencePrototype
0 ignored issues
show
Documentation introduced by
Should the type for parameter $referencePrototype not be null|AttachedEntityReference?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
39
     * @param array $elements
40
     */
41
    public function __construct(RepositoryService $repositories, AttachedEntityReference $referencePrototype = null, array $elements = [])
42
    {
43
        parent::__construct($elements);
44
        $this->repositories = $repositories;
45
        $this->referencePrototype = $referencePrototype ?: new AttachedEntityReference();
46
    }
47
48
    /**
49
     * {@inheritDoc}
50
     * @see \Doctrine\Common\Collections\ArrayCollection::set()
51
     */
52
    public function setAttachedEntity($key, $entity)
53
    {
54
        if (! $entity instanceof IdentifiableEntityInterface) {
55
            throw new InvalidArgumentException(sprintf('$entity must be instance of %s', IdentifiableEntityInterface::class));
56
        }
57
        
58
        $className = get_class($entity);
59
        
60
        if (null === $key) {
61
            $key = $className;
62
        }
63
        
64
        $reference = clone $this->referencePrototype;
65
        $entityId = $entity->getId();
66
        
67
        // check if entity is not persisted
68
        if (! $entityId) {
69
            // persist entity & retrieve its ID
70
            $this->repositories->getRepository($className)
0 ignored issues
show
Documentation Bug introduced by
The method getRepository does not exist on object<Core\Repository\RepositoryService>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
71
                ->store($entity);
72
            $entityId = $entity->getId();
73
        }
74
        
75
        $reference->setRepository($className)
76
            ->setEntityId($entityId);
77
        
78
        return parent::set($key, $reference);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (set() instead of setAttachedEntity()). Are you sure this is correct? If so, you might want to change this to $this->set().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
79
    }
80
81
    /**
82
     * {@inheritDoc}
83
     * @see \Doctrine\Common\Collections\ArrayCollection::get()
84
     */
85
    public function getAttachedEntity($key)
86
    {
87
        $reference = parent::get($key);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (get() instead of getAttachedEntity()). Are you sure this is correct? If so, you might want to change this to $this->get().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
88
        
89
        if (! $reference) {
90
            return;
91
        }
92
        
93
        $entity = $this->doGetEntity($reference);
94
        
95
        if (! $entity) {
96
            // remove reference if entity does not exists
97
            unset($this->elements[$key]);
98
        }
99
        
100
        return $entity;
101
    }
102
103
    /**
104
     * {@inheritDoc}
105
     * @see \Doctrine\Common\Collections\ArrayCollection::remove()
106
     */
107
    public function removeAttachedEntity($key)
108
    {
109
        $reference = parent::remove($key);
0 ignored issues
show
Comprehensibility Bug introduced by
It seems like you call parent on a different method (remove() instead of removeAttachedEntity()). Are you sure this is correct? If so, you might want to change this to $this->remove().

This check looks for a call to a parent method whose name is different than the method from which it is called.

Consider the following code:

class Daddy
{
    protected function getFirstName()
    {
        return "Eidur";
    }

    protected function getSurName()
    {
        return "Gudjohnsen";
    }
}

class Son
{
    public function getFirstName()
    {
        return parent::getSurname();
    }
}

The getFirstName() method in the Son calls the wrong method in the parent class.

Loading history...
110
        
111
        if (! $reference) {
112
            return;
113
        }
114
        
115
        $entity = $this->doGetEntity($reference);
116
        
117
        if ($entity) {
118
            $this->repositories->getRepository($reference->getRepository())
0 ignored issues
show
Documentation Bug introduced by
The method getRepository does not exist on object<Core\Repository\RepositoryService>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
119
                ->remove($entity);
120
        }
121
        
122
        return $reference;
123
    }
124
125
    /**
126
     * @param AttachedEntityReference $reference
127
     * @return object|NULL
128
     */
129
    protected function doGetEntity(AttachedEntityReference $reference)
130
    {
131
        return $this->repositories->getRepository($reference->getRepository())
0 ignored issues
show
Documentation Bug introduced by
The method getRepository does not exist on object<Core\Repository\RepositoryService>? Since you implemented __call, maybe consider adding a @method annotation.

If you implement __call and you know which methods are available, you can improve IDE auto-completion and static analysis by adding a @method annotation to the class.

This is often the case, when __call is implemented by a parent class and only the child class knows which methods exist:

class ParentClass {
    private $data = array();

    public function __call($method, array $args) {
        if (0 === strpos($method, 'get')) {
            return $this->data[strtolower(substr($method, 3))];
        }

        throw new \LogicException(sprintf('Unsupported method: %s', $method));
    }
}

/**
 * If this class knows which fields exist, you can specify the methods here:
 *
 * @method string getName()
 */
class SomeClass extends ParentClass { }
Loading history...
132
            ->find($reference->getEntityId());
133
    }
134
    
135
    /**
136
     * {@inheritDoc}
137
     */
138
    public function map(Closure $func)
139
    {
140
        return new static($this->repositories, $this->referencePrototype, array_map($func, $this->toArray()));
141
    }
142
    
143
    /**
144
     * {@inheritDoc}
145
     */
146
    public function filter(Closure $p)
147
    {
148
        return new static($this->repositories, $this->referencePrototype, array_filter($this->toArray(), $p));
149
    }
150
    
151
    /**
152
     * {@inheritDoc}
153
     */
154
    public function partition(Closure $p)
155
    {
156
        $matches = $noMatches = array();
157
    
158
        foreach ($this->toArray() as $key => $element) {
159
            if ($p($key, $element)) {
160
                $matches[$key] = $element;
161
            } else {
162
                $noMatches[$key] = $element;
163
            }
164
        }
165
    
166
        return array(new static($this->repositories, $this->referencePrototype, $matches),
167
            new static($this->repositories, $this->referencePrototype, $noMatches));
168
    }
169
    
170
    /**
171
     * {@inheritDoc}
172
     */
173
    public function matching(Criteria $criteria)
174
    {
175
        $expr     = $criteria->getWhereExpression();
176
        $filtered = $this->toArray();
177
    
178
        if ($expr) {
179
            $visitor  = new ClosureExpressionVisitor();
180
            $filter   = $visitor->dispatch($expr);
181
            $filtered = array_filter($filtered, $filter);
182
        }
183
    
184
        if ($orderings = $criteria->getOrderings()) {
185
            foreach (array_reverse($orderings) as $field => $ordering) {
186
                $next = ClosureExpressionVisitor::sortByField($field, $ordering == Criteria::DESC ? -1 : 1);
187
            }
188
    
189
            uasort($filtered, $next);
0 ignored issues
show
Bug introduced by
The variable $next does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
190
        }
191
    
192
        $offset = $criteria->getFirstResult();
193
        $length = $criteria->getMaxResults();
194
    
195
        if ($offset || $length) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $offset of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $length of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
196
            $filtered = array_slice($filtered, (int)$offset, $length);
197
        }
198
    
199
        return new static($this->repositories, $this->referencePrototype, $filtered);
200
    }
201
}
202