Completed
Push — master ( e8989f...a519f2 )
by Filipe
02:35
created

QueryObject::getCollectionsMap()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 7
ccs 5
cts 5
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
crap 2
1
<?php
2
3
/**
4
 * This file is part of slick/orm package
5
 *
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
namespace Slick\Orm\Repository\QueryObject;
11
12
use League\Event\EmitterInterface;
13
use Slick\Database\Sql\Select;
14
use Slick\Orm\Entity\CollectionsMap;
15
use Slick\Orm\Entity\EntityCollection;
16
use Slick\Orm\EntityInterface;
17
use Slick\Orm\Event\Delete;
18
use Slick\Orm\Event\EntityAdded;
19
use Slick\Orm\Event\EntityChangeEventInterface;
20
use Slick\Orm\Event\EntityRemoved;
21
use Slick\Orm\Orm;
22
use Slick\Orm\RepositoryInterface;
23
24
/**
25
 * QueryObject
26
 *
27
 * @package Slick\Orm\Repository
28
 * @author  Filipe Silva <[email protected]>
29
 */
30
class QueryObject extends Select implements QueryObjectInterface
31
{
32
33
    /**
34
     * @var RepositoryInterface
35
     */
36
    protected $repository;
37
38
    /**
39
     * @var CollectionsMap
40
     */
41
    protected $collectionsMap;
42
43
    /**
44
     * For triggering events
45
     */
46
    use SelectEventTriggers;
47
48
    /**
49
     * QueryObject has a repository as a dependency.
50
     *
51
     * @param RepositoryInterface $repository
52
     */
53 16
    public function __construct(RepositoryInterface $repository)
54
    {
55 16
        $this->repository = $repository;
56 16
        $this->adapter = $repository->getAdapter();
57 16
        parent::__construct(
58 16
            $repository->getEntityDescriptor()->getTableName(),
59 16
            $repository->getEntityDescriptor()->getTableName().'.*'
60 16
        );
61 16
    }
62
63
    /**
64
     * Retrieve all records matching this select query
65
     *
66
     * @return \Slick\Database\RecordList
67
     */
68 8
    public function all()
69
    {
70 8
        return $this->query($this);
71
    }
72
73
    /**
74
     * Retrieve first record matching this select query
75
     *
76
     * @return EntityInterface|null
77
     */
78 2
    public function first()
79
    {
80 2
        $sql = clone($this);
81 2
        $sql->limit(1);
82 2
        $collection = $this->query($sql);
83 2
        return $collection->isEmpty() ? null : $collection[0];
0 ignored issues
show
Bug introduced by
The method isEmpty does only exist in Slick\Orm\Entity\EntityCollection, but not in Slick\Orm\EntityInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
84
    }
85
86
    /**
87
     * Handles collection add event and updates the cache
88
     * 
89
     * @param EntityChangeEventInterface $event
90
     */
91 4
    public function updateCollection(EntityChangeEventInterface $event)
92
    {
93 4
        $collection = $event->getCollection();
94 4
        if ($collection->getId()) {
95 4
            $collectionMap = $this->getCollectionsMap();
96
            $collectionMap
97 4
                ->set($collection->getId(), $collection);
98 4
        }
99 4
    }
100
101
    /**
102
     * Execute provided query
103
     *
104
     * @param Select $sql
105
     *
106
     * @return EntityCollection|EntityInterface|\Slick\Orm\EntityMapperInterface[]
107
     */
108 10
    protected function query(Select $sql)
109
    {
110 10
        $cid = $this->getId($sql);
111 10
        $collection = $this->repository
112 10
            ->getCollectionsMap()
113 10
            ->get($cid, false);
114
115 10
        if (false === $collection) {
116 8
            $collection = $this->getCollection($sql, $cid);
117 8
        }
118
119 10
        return $collection;
120
    }
121
122
    /**
123
     * Executes the provided query
124
     *
125
     * @param Select $sql
126
     * @param string $cid
127
     *
128
     * @return EntityCollection|EntityInterface|\Slick\Orm\EntityMapperInterface[]
129
     */
130 8
    protected function getCollection(Select $sql, $cid)
131
    {
132 8
        $this->triggerBeforeSelect(
133 8
            $sql,
134 8
            $this->getRepository()->getEntityDescriptor()
135 8
        );
136 8
        $data = $this->adapter->query($sql, $sql->getParameters());
137 8
        $collection = $this->repository->getEntityMapper()
138 8
            ->createFrom($data);
139 8
        $this->triggerAfterSelect(
140 8
            $data,
141
            $collection
0 ignored issues
show
Bug introduced by
It seems like $collection defined by $this->repository->getEn...er()->createFrom($data) on line 137 can also be of type array<integer,object<Sli...EntityMapperInterface>> or object<Slick\Orm\EntityInterface>; however, Slick\Orm\Repository\Que...s::triggerAfterSelect() does only seem to accept object<Slick\Orm\Entity\EntityCollection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
142 8
        );
143 8
        $this->registerEventsTo($collection, $cid);
0 ignored issues
show
Bug introduced by
It seems like $collection defined by $this->repository->getEn...er()->createFrom($data) on line 137 can also be of type array<integer,object<Sli...EntityMapperInterface>> or object<Slick\Orm\EntityInterface>; however, Slick\Orm\Repository\Que...ect::registerEventsTo() does only seem to accept object<Slick\Orm\Entity\EntityCollection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
144 8
        $this->getCollectionsMap()->set($cid, $collection);
0 ignored issues
show
Bug introduced by
It seems like $collection defined by $this->repository->getEn...er()->createFrom($data) on line 137 can also be of type array<integer,object<Sli...EntityMapperInterface>> or object<Slick\Orm\EntityInterface>; however, Slick\Orm\Entity\CollectionsMapInterface::set() does only seem to accept object<Slick\Orm\Entity\...ityCollectionInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
145 8
        $this->updateIdentityMap($collection);
0 ignored issues
show
Bug introduced by
It seems like $collection defined by $this->repository->getEn...er()->createFrom($data) on line 137 can also be of type array<integer,object<Sli...EntityMapperInterface>> or object<Slick\Orm\EntityInterface>; however, Slick\Orm\Repository\Que...ct::updateIdentityMap() does only seem to accept object<Slick\Orm\Entity\EntityCollection>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
146
        
147 8
        return $collection;
148
    }
149
150
    /**
151
     * Returns the collections map storage
152
     *
153
     * @return CollectionsMap|\Slick\Orm\Entity\CollectionsMapInterface
154
     */
155 10
    protected function getCollectionsMap()
156
    {
157 10
        if (null == $this->collectionsMap) {
158 10
            $this->collectionsMap = $this->repository->getCollectionsMap();
159 10
        }
160 10
        return $this->collectionsMap;
161
    }
162
163
    /**
164
     * Register entity events listeners for the provided collection
165
     * 
166
     * @param EntityCollection $collection
167
     * @param string $cid
168
     * 
169
     * @return self
170
     */
171 8
    protected function registerEventsTo(EntityCollection $collection, $cid)
172
    {
173 8
        $collection->setId($cid);
174 8
        $collection->getEmitter()
175 8
            ->addListener(
176 8
                EntityAdded::ACTION_ADD,
177 8
                [$this, 'updateCollection']
178 8
            );
179 8
        $collection->getEmitter()
180 8
            ->addListener(
181 8
                EntityRemoved::ACTION_REMOVE,
182 8
                [$this, 'updateCollection']
183 8
            );
184 8
        $entity = $this->repository->getEntityDescriptor()->className();
185 8
        Orm::addListener(
186 8
            $entity,
187 8
            Delete::ACTION_AFTER_DELETE,
188 8
            function (Delete $event) use ($collection) {
189 2
                $collection->remove($event->getEntity());
190 8
            },
191
            EmitterInterface::P_HIGH
192 8
        );
193 8
        return $this;
194
    }
195
196
    /**
197
     * Gets the id for this query
198
     *
199
     * @param Select $query
200
     *
201
     * @return string
202
     */
203 10
    protected function getId(Select $query)
204
    {
205 10
        $str = $query->getQueryString();
206 10
        $search = array_keys($query->getParameters());
207 10
        $values = array_values($query->getParameters());
208 10
        return str_replace($search, $values, $str);
209
    }
210
211
    /**
212
     * Registers every entity in collection to the repository identity map
213
     *
214
     * @param EntityCollection $collection
215
     *
216
     * @return self
217
     */
218 8
    protected function updateIdentityMap(EntityCollection $collection)
219
    {
220 8
        if ($collection->isEmpty()) {
221 2
            return $this;
222
        }
223
224 6
        foreach ($collection as $entity)
225
        {
226 6
            $this->repository->getIdentityMap()->set($entity);
227 6
        }
228 6
        return $this;
229
    }
230
231
    /**
232
     * Returns the repository that is using this query object
233
     *
234
     * @return RepositoryInterface
235
     */
236 14
    public function getRepository()
237
    {
238 14
        return $this->repository;
239
    }
240
241
    /**
242
     * Gets current entity class name
243
     *
244
     * @return string
245
     */
246 8
    public function getEntityClassName()
247
    {
248 8
        return $this->repository->getEntityDescriptor()->className();
249
    }
250
}