MocksEntityManager   A
last analyzed

Complexity

Total Complexity 22

Size/Duplication

Total Lines 353
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
wmc 22
lcom 1
cbo 8
dl 0
loc 353
rs 10
c 0
b 0
f 0

13 Methods

Rating   Name   Duplication   Size   Complexity  
A ormGetEntityManagerInstance() 0 4 1
A ormAttributesToData() 0 10 2
A ormCreateMockedEntity() 0 15 1
A ormInitMock() 0 19 1
A ormAddResult() 0 6 1
A ormExpectFetch() 0 7 1
A ormAllowFetch() 0 15 1
A ormExpectInsert() 0 5 1
A ormAllowInsert() 0 35 4
A ormExpectUpdate() 0 5 1
A ormAllowUpdate() 0 39 4
A ormExpectDelete() 0 5 1
A ormAllowDelete() 0 22 3
1
<?php
2
3
namespace ORM\Testing;
4
5
use Mockery as m;
6
use ORM\Entity;
7
use ORM\EntityFetcher;
8
use ORM\EntityManager;
9
use ORM\Exception\IncompletePrimaryKey;
10
use PDO;
11
12
/**
13
 * A trait to mock ORM\EntityManager
14
 *
15
 * @package ORM\Testing
16
 * @author  Thomas Flori <[email protected]>
17
 */
18
trait MocksEntityManager
19
{
20
    /**
21
     * Get the EntityManagerMock for $class
22
     *
23
     * @param $class
24
     * @return EntityManagerMock|m\MockInterface|EntityManager
25
     * @codeCoverageIgnore proxy method
26
     */
27
    public function ormGetEntityManagerInstance($class)
28
    {
29
        return EntityManager::getInstance($class);
30
    }
31
32
    /**
33
     * Convert an array with $attributes as keys to an array of columns for $class
34
     *
35
     * e. g. : `assertSame(['first_name' => 'John'], ormAttributesToArray(User::class, ['firstName' => 'John'])`
36
     *
37
     * *Note: this method is idempotent*
38
     *
39
     * @param string $class
40
     * @param array  $attributes
41
     * @return array
42
     */
43
    public function ormAttributesToData($class, array $attributes)
44
    {
45
        $data = [];
46
47
        foreach ($attributes as $attribute => $value) {
48
            $data[call_user_func([$class, 'getColumnName'], $attribute)] = $value;
49
        }
50
51
        return $data;
52
    }
53
54
    /**
55
     * Create a partial mock of Entity $class
56
     *
57
     * *Note: the entity will get a random primary key if not predefined.*
58
     *
59
     * @param string        $class
60
     * @param array         $data
61
     * @return m\MockInterface|Entity
62
     */
63
    public function ormCreateMockedEntity($class, $data = [])
64
    {
65
        /** @var EntityManagerMock $em */
66
        $em = $this->ormGetEntityManagerInstance($class);
67
68
        /** @var Entity|m\MockInterface $entity */
69
        $entity = m::mock($class)->makePartial();
70
        $entity->shouldReceive('validate')->andReturn(true)->byDefault();
0 ignored issues
show
Bug introduced by
The method shouldReceive does only exist in Mockery\MockInterface, but not in ORM\Entity.

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...
71
        $entity->setEntityManager($em);
72
        $entity->setOriginalData($this->ormAttributesToData($class, $data));
73
        $entity->reset();
74
75
        $em->addEntity($entity);
0 ignored issues
show
Bug introduced by
It seems like $entity defined by \Mockery::mock($class)->makePartial() on line 69 can also be of type object<Mockery\MockInterface>; however, ORM\Testing\EntityManagerMock::addEntity() does only seem to accept object<ORM\Entity>, 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...
76
        return $entity;
77
    }
78
79
    /**
80
     * Initialize an EntityManager mock object
81
     *
82
     * The mock is partial and you can map and act with it as usual. You should overwrite your dependency injector
83
     * with the returned mock object. You can also call `defineFor*()` on this mock to use this mock for specific
84
     * classes.
85
     *
86
     * The PDO object is mocked too. This object should not receive any calls except for quoting. By default it
87
     * accepts `quote(string)`, `setAttribute(*)` and `getAttribute(ATTR_DRIVER_NAME)`. To retrieve and expect other
88
     * calls you can use `getConnection()` from EntityManager mock object.
89
     *
90
     * @param array $options Options passed to EntityManager constructor
91
     * @param string $driver Database driver you are using (results in different dbal instance)
92
     * @return m\MockInterface|EntityManager
93
     */
94
    public function ormInitMock($options = [], $driver = 'mysql')
95
    {
96
        /** @var EntityManager|m\MockInterface $em */
97
        $em = m::mock(EntityManagerMock::class)->makePartial();
98
        $em->__construct($options);
99
100
        /** @var PDO|m\Mock $pdo */
101
        $pdo = m::mock(PDO::class);
102
        $pdo->shouldReceive('setAttribute')->andReturn(true)->byDefault();
0 ignored issues
show
Bug introduced by
The method shouldReceive does only exist in Mockery\Mock, but not in PDO.

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...
103
        $pdo->shouldReceive('getAttribute')->with(PDO::ATTR_DRIVER_NAME)->andReturn($driver)->byDefault();
104
        $pdo->shouldReceive('quote')->with(m::type('string'))->andReturnUsing(
105
            function ($str) {
106
                return '\'' . addcslashes($str, '\'') . '\'';
107
            }
108
        )->byDefault();
109
        $em->setConnection($pdo);
110
111
        return $em;
112
    }
113
114
    /**
115
     * Add a result to EntityFetcher for $class
116
     *
117
     * You can specify the query that you expect in the returned result.
118
     *
119
     * Example:
120
     * ```php
121
     * $this->ormAddResult(Article::class, $em, new Article(['title' => 'Foo']))
122
     *   ->where('deleted_at IS NULL')
123
     *   ->where('title', 'Foo');
124
     *
125
     * $entity = $em->fetch('Article::class')
126
     *   ->where('deleted_at IS NULL')
127
     *   ->where('title', 'Foo')
128
     *   ->one();
129
     * ```
130
     *
131
     * @param string $class The class of an Entity
132
     * @param Entity ...$entities The entities that will be returned
133
     * @return EntityFetcherMock\Result|m\MockInterface
134
     * @codeCoverageIgnore trivial code
135
     */
136
    public function ormAddResult($class, Entity ...$entities)
137
    {
138
        /** @var EntityManagerMock|m\Mock $em */
139
        $em = $this->ormGetEntityManagerInstance($class);
140
        return $em->addResult($class, ...$entities);
0 ignored issues
show
Bug introduced by
The method addResult does only exist in ORM\Testing\EntityManagerMock, but not in Mockery\Mock.

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...
141
    }
142
143
    /**
144
     * Expect fetch for $class
145
     *
146
     * Mocks and expects an EntityFetcher with $entities as result.
147
     *
148
     * @param string        $class    The class that should be fetched
149
     * @param array         $entities The entities that get returned from fetcher
150
     * @return m\Mock|EntityFetcher
151
     * @deprecated use $em->shouldReceive('fetch')->once()->passthru()
152
     */
153
    public function ormExpectFetch($class, $entities = [])
154
    {
155
        /** @var m\Mock|EntityFetcher $fetcher */
156
        list($expectation, $fetcher) = $this->ormAllowFetch($class, $entities);
0 ignored issues
show
Deprecated Code introduced by
The method ORM\Testing\MocksEntityManager::ormAllowFetch() has been deprecated with message: every fetch is allowed now (change with $em->shouldNotReceive('fetch'))

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...
157
        $expectation->once();
158
        return $fetcher;
159
    }
160
161
    /**
162
     * Allow fetch for $class
163
     *
164
     * Mocks an EntityFetcher with $entities as result.
165
     *
166
     * Returns the Expectation for fetch on entityManager and the mocked EntityFetcher
167
     *
168
     * @param string        $class    The class that should be fetched
169
     * @param array         $entities The entities that get returned from fetcher
170
     * @return m\Expectation[]|EntityFetcher[]|m\MockInterface[]
171
     * @deprecated every fetch is allowed now (change with $em->shouldNotReceive('fetch'))
172
     */
173
    public function ormAllowFetch($class, $entities = [])
174
    {
175
        /** @var EntityManager|m\Mock $em */
176
        $em = $this->ormGetEntityManagerInstance($class);
177
178
        /** @var m\MockInterface|EntityFetcher $fetcher */
179
        $fetcher = m::mock(EntityFetcher::class, [ $em, $class ])->makePartial();
180
        $expectation = $em->shouldReceive('fetch')->with($class)->andReturn($fetcher);
0 ignored issues
show
Bug introduced by
The method shouldReceive does only exist in Mockery\Mock, but not in ORM\EntityManager.

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...
181
182
        $fetcher->shouldReceive('count')->with()->andReturn(count($entities))->byDefault();
183
        array_push($entities, null);
184
        $fetcher->shouldReceive('one')->with()->andReturnValues($entities)->byDefault();
185
186
        return [$expectation, $fetcher];
187
    }
188
189
    /**
190
     * Expect an insert for $class
191
     *
192
     * Mocks and expects the calls to sync and insert as they came for `save()` method for a new Entity.
193
     *
194
     * If you omit the auto incremented id in defaultValues it is set to a random value between 1 and 2147483647.
195
     *
196
     * The EntityManager gets determined the same way as in Entity and can be overwritten by third parameter here.
197
     *
198
     * @param string        $class         The class that should get created
199
     * @param array         $defaultValues The default values that came from database (for example: the created column
200
     *                                     has by the default the current timestamp; the id is auto incremented...)
201
     */
202
    public function ormExpectInsert($class, $defaultValues = [])
203
    {
204
        $expectation = $this->ormAllowInsert($class, $defaultValues);
205
        $expectation->once();
206
    }
207
208
    /**
209
     * Allow an insert for $class
210
     *
211
     * Mocks the calls to sync and insert as they came for `save()` method for a new Entity.
212
     *
213
     * If you omit the auto incremented id in defaultValues it is set to a random value between 1 and 2147483647.
214
     *
215
     * The EntityManager gets determined the same way as in Entity and can be overwritten by third parameter here.
216
     *
217
     * @param string        $class         The class that should get created
218
     * @param array         $defaultValues The default values that came from database (for example: the created column
219
     *                                     has by the default the current timestamp; the id is auto incremented...)
220
     * @return m\Expectation
221
     */
222
    public function ormAllowInsert($class, $defaultValues = [])
223
    {
224
        /** @var EntityManager|m\MockInterface $em */
225
        $em = $this->ormGetEntityManagerInstance($class);
226
227
        return $em->shouldReceive('sync')->with(m::type($class))
228
            ->andReturnUsing(
229
                function (Entity $entity) use ($class, $defaultValues, $em) {
230
                    $expectation = $em->shouldReceive('insert')->once()
231
                        ->andReturnUsing(
232
                            function (Entity $entity, $useAutoIncrement = true) use ($class, $defaultValues, $em) {
233
                                if ($useAutoIncrement && !isset($defaultValues[$entity::getPrimaryKeyVars()[0]])) {
234
                                    $defaultValues[$entity::getPrimaryKeyVars()[0]] = mt_rand(1, pow(2, 31) - 1);
235
                                }
236
                                $entity->setOriginalData(array_merge(
237
                                    $this->ormAttributesToData($class, $defaultValues),
238
                                    $entity->getData()
239
                                ));
240
                                $entity->reset();
241
                                $em->map($entity);
242
                                return true;
243
                            }
244
                        );
245
246
                    try {
247
                        $entity->getPrimaryKey();
248
                        $expectation->with(m::type($class), false);
249
                        return false;
250
                    } catch (IncompletePrimaryKey $ex) {
251
                        $expectation->with(m::type($class));
252
                        throw $ex;
253
                    }
254
                }
255
            );
256
    }
257
258
    /**
259
     * Expect save on $entity
260
     *
261
     * Entity has to be a mock use `emCreateMockedEntity()` to create it.
262
     *
263
     * @param Entity|m\MockInterface $entity
264
     * @param array  $changingData Emulate changing data during update statement (triggers etc)
265
     * @param array  $updatedData  Emulate data changes in database
266
     */
267
    public function ormExpectUpdate(m\MockInterface $entity, $changingData = [], $updatedData = [])
268
    {
269
        $expectation = $this->ormAllowUpdate($entity, $changingData, $updatedData);
270
        $expectation->once();
271
    }
272
273
    /**
274
     * Allow save on $entity
275
     *
276
     * Entity has to be a mock use `emCreateMockedEntity()` to create it.
277
     *
278
     * @param Entity|m\MockInterface $entity
279
     * @param array $changingData Emulate changing data during update statement (triggers etc)
280
     * @param array $updatedData Emulate data changes in database
281
     * @return m\Expectation
282
     */
283
    public function ormAllowUpdate(m\MockInterface $entity, $changingData = [], $updatedData = [])
284
    {
285
        $expectation = $entity->shouldReceive('save');
286
287
        if ($expectation instanceof m\CompositeExpectation) {
288
            $expectation->andReturnUsing(
289
                function () use ($entity, $updatedData, $changingData) {
290
                    $class = get_class($entity);
291
                    // sync with database using $updatedData
292
                    if (!empty($updatedData)) {
293
                        $newData = $entity->getData();
0 ignored issues
show
Bug introduced by
The method getData() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
294
                        $entity->reset();
0 ignored issues
show
Bug introduced by
The method reset() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
295
                        $entity->setOriginalData(array_merge(
0 ignored issues
show
Bug introduced by
The method setOriginalData() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
296
                            $entity->getData(),
0 ignored issues
show
Bug introduced by
The method getData() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
297
                            $this->ormAttributesToData($class, $updatedData)
298
                        ));
299
                        $entity->fill($newData);
0 ignored issues
show
Bug introduced by
The method fill() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
300
                    }
301
302
                    if (!$entity->isDirty()) {
0 ignored issues
show
Bug introduced by
The method isDirty() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
303
                        return $entity;
304
                    }
305
306
                    // update the entity using $changingData
307
                    $entity->preUpdate();
0 ignored issues
show
Bug introduced by
The method preUpdate() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
308
                    $entity->setOriginalData(array_merge(
0 ignored issues
show
Bug introduced by
The method setOriginalData() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
309
                        $entity->getData(),
0 ignored issues
show
Bug introduced by
The method getData() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
310
                        $this->ormAttributesToData($class, $changingData)
311
                    ));
312
                    $entity->reset();
0 ignored issues
show
Bug introduced by
The method reset() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
313
                    $entity->postUpdate();
0 ignored issues
show
Bug introduced by
The method postUpdate() does not seem to exist on object<Mockery\MockInterface>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
314
315
                    return $entity;
316
                }
317
            );
318
        }
319
320
        return $expectation;
321
    }
322
323
    /**
324
     * Expect delete on $em
325
     *
326
     * If $em is not given it is determined by get_class($entity).
327
     *
328
     * If $entity is a string then it is assumed to be a class name.
329
     *
330
     * @param string|Entity $entity
331
     */
332
    public function ormExpectDelete($entity)
333
    {
334
        $expectation = $this->ormAllowDelete($entity);
335
        $expectation->once();
336
    }
337
338
    /**
339
     * Allow delete on $em
340
     *
341
     * If $em is not given it is determined by get_class($entity).
342
     *
343
     * If $entity is a string then it is assumed to be a class name.
344
     *
345
     * @param string|Entity $entity
346
     * @return m\Expectation
347
     */
348
    public function ormAllowDelete($entity)
349
    {
350
        $class = is_string($entity) ? $entity : get_class($entity);
351
352
        /** @var EntityManager|m\MockInterface $em */
353
        $em = $this->ormGetEntityManagerInstance($class);
354
355
        $expectation = $em->shouldReceive('delete');
356
        if (is_string($entity)) {
357
            $expectation->with(m::type($class));
358
        } else {
359
            $expectation->with($entity);
360
        }
361
        $expectation->once()->andReturnUsing(
362
            function (Entity $entity) {
363
                $entity->setOriginalData([]);
364
                return true;
365
            }
366
        );
367
368
        return $expectation;
369
    }
370
}
371