Completed
Push — master ( 819ed0...a8d0dc )
by Thomas
03:06
created

MockTrait::ormCreateMockedEntity()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 17
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 9
c 1
b 0
f 1
dl 0
loc 17
ccs 3
cts 3
cp 1
rs 9.9666
cc 3
nc 2
nop 3
crap 3
1
<?php /** @noinspection PhpDocMissingThrowsInspection */
2
3
namespace ORM;
4
5
use Mockery as m;
6
use ORM\Exception\IncompletePrimaryKey;
7
8
/**
9
 * MockTrait for ORM
10
 *
11
 * @package ORM
12
 * @author  Thomas Flori <[email protected]>
13
 */
14
trait MockTrait
15
{
16
    /**
17
     * Initialize an EntityManager mock object
18
     *
19
     * The mock is partial and you can map and act with it as usual. You should overwrite your dependency injector
20
     * with the returned mock object. You can also call `defineFor*()` on this mock to use this mock for specific
21
     * classes.
22
     *
23
     * The PDO object is mocked too. This object should not receive any calls except for quoting. By default it
24
     * accepts `quote(string)`, `setAttribute(*)` and `getAttribute(ATTR_DRIVER_NAME)`. To retrieve and expect other
25
     * calls you can use `getConnection()` from EntityManager mock object.
26
     *
27
     * @param array $options Options passed to EntityManager constructor
28
     * @param string $driver Database driver you are using (results in different dbal instance)
29
     * @return m\Mock|EntityManager
30
     */
31
    public function ormInitMock($options = [], $driver = 'mysql')
32 26
    {
33
        /** @var EntityManager|m\Mock $em */
34
        $em = m::mock(EntityManager::class)->makePartial();
35 26
        $em->__construct($options);
36
        /** @var \PDO|m\Mock $pdo */
37 26
        $pdo = m::mock(\PDO::class);
38
39 26
        $pdo->shouldReceive('setAttribute')->andReturn(true)->byDefault();
40 26
        $pdo->shouldReceive('getAttribute')->with(\PDO::ATTR_DRIVER_NAME)->andReturn($driver)->byDefault();
41 26
        $pdo->shouldReceive('quote')->with(m::type('string'))->andReturnUsing(
42 26
            function ($str) {
43 1
                return '\'' . addcslashes($str, '\'') . '\'';
44 26
            }
45 26
        )->byDefault();
46
47 26
        $em->setConnection($pdo);
48 26
        return $em;
49
    }
50
51
    /**
52
     * Convert an array with $attributes as keys to an array of columns for $class
53
     *
54
     * e. g. : `assertSame(['first_name' => 'John'], ormAttributesToArray(User::class, ['firstName' => 'John'])`
55
     *
56
     * *Note: this method is idempotent*
57
     *
58
     * @param string $class
59 10
     * @param array  $attributes
60
     * @return array
61 10
     */
62
    public function ormAttributesToData($class, array $attributes)
63
    {
64 10
        $data = [];
65 10
66 10
        foreach ($attributes as $attribute => $value) {
67 10
            $data[call_user_func([$class, 'getColumnName'], $attribute)] = $value;
68
        }
69
70 10
        return $data;
71 1
    }
72
73
    /**
74 10
     * Create a partial mock of Entity $class
75
     *
76
     * @param string        $class
77
     * @param array         $data
78
     * @param EntityManager $em
79
     * @return m\Mock|Entity
80
     */
81
    public function ormCreateMockedEntity($class, $data = [], $em = null)
82
    {
83
        $em = $em ?: EntityManager::getInstance($class);
84
85
        /** @var Entity|m\Mock $entity */
86
        $entity = m::mock($class)->makePartial();
87
        $entity->setEntityManager($em);
88
        $entity->setOriginalData($this->ormAttributesToData($class, $data));
89
        $entity->reset();
90
91
        try {
92 5
            /** @scrutinizer ignore-type */
93
            $em->map($entity, true, $class);
0 ignored issues
show
Bug introduced by
$entity of type Mockery\Mock is incompatible with the type ORM\Entity expected by parameter $entity of ORM\EntityManager::map(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

93
            $em->map(/** @scrutinizer ignore-type */ $entity, true, $class);
Loading history...
94
        } catch (IncompletePrimaryKey $ex) {
95 5
            // we tried to map but ignore primary key missing
96
        }
97 5
        return $entity;
98 5
    }
99 5
100 4
    /**
101 4
     * Expect an insert for $class
102 4
     *
103 4
     * Mocks and expects the calls to sync and insert as they came for `save()` method for a new Entity.
104 1
     *
105
     * If you omit the auto incremented id in defaultValues it is set to a random value between 1 and 2147483647.
106 4
     *
107 4
     * The EntityManager gets determined the same way as in Entity and can be overwritten by third parameter here.
108 4
     *
109 4
     * @param string        $class         The class that should get created
110 4
     * @param array         $defaultValues The default values that came from database (for example: the created column
111
     *                                     has by the default the current timestamp; the id is auto incremented...)
112
     * @param EntityManager $em
113
     */
114 4
    public function ormExpectInsert($class, $defaultValues = [], $em = null)
115 1
    {
116 1
        $expectation = $this->ormAllowInsert($class, $defaultValues, $em);
117 3
        $expectation->once();
118 3
    }
119 3
120
    /**
121 5
     * Allow an insert for $class
122
     *
123 5
     * Mocks the calls to sync and insert as they came for `save()` method for a new Entity.
124
     *
125
     * If you omit the auto incremented id in defaultValues it is set to a random value between 1 and 2147483647.
126
     *
127
     * The EntityManager gets determined the same way as in Entity and can be overwritten by third parameter here.
128
     *
129
     * @param string        $class         The class that should get created
130
     * @param array         $defaultValues The default values that came from database (for example: the created column
131
     *                                     has by the default the current timestamp; the id is auto incremented...)
132
     * @param EntityManager $em
133
     * @return m\Expectation
134
     */
135
    public function ormAllowInsert($class, $defaultValues = [], $em = null)
136 6
    {
137
        /** @var EntityManager|m\Mock $em */
138
        $em = $em ?: EntityManager::getInstance($class);
139 6
140
        /** @scrutinizer ignore-call */
141
        $expectation = $em->shouldReceive('sync')->with(m::type($class))
0 ignored issues
show
Bug introduced by
The method shouldReceive() does not exist on ORM\EntityManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

141
        $expectation = $em->/** @scrutinizer ignore-call */ shouldReceive('sync')->with(m::type($class))

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...
142 6
            ->andReturnUsing(
143 6
                function (Entity $entity) use ($class, $defaultValues, $em) {
144
                    /** @scrutinizer ignore-call */
145 6
                    $expectation = $em->shouldReceive('insert')->once()
146 6
                        ->andReturnUsing(
147 6
                            function (Entity $entity, $useAutoIncrement = true) use ($class, $defaultValues, $em) {
148
                                if ($useAutoIncrement && !isset($defaultValues[$entity::getPrimaryKeyVars()[0]])) {
149 6
                                    $defaultValues[$entity::getPrimaryKeyVars()[0]] = mt_rand(1, pow(2, 31) - 1);
150
                                }
151
                                $entity->setOriginalData(array_merge(
152
                                    $this->ormAttributesToData($class, $defaultValues),
153
                                    $entity->getData()
154
                                ));
155
                                $entity->reset();
156
                                $em->map($entity);
157
                                return true;
158
                            }
159
                        );
160
161 4
                    try {
162
                        $entity->getPrimaryKey();
163 4
                        $expectation->with(m::type($class), false);
164 4
                        return false;
165
                    } catch (IncompletePrimaryKey $ex) {
166 3
                        $expectation->with(m::type($class));
167 1
                        throw $ex;
168 1
                    }
169 1
                }
170 1
            );
171
172
        return $expectation;
173 3
    }
174 2
175
    /**
176
     * Expect fetch for $class
177
     *
178 1
     * Mocks and expects an EntityFetcher with $entities as result.
179 1
     *
180 1
     * @param string        $class    The class that should be fetched
181 1
     * @param array         $entities The entities that get returned from fetcher
182
     * @param EntityManager $em
183 1
     * @return m\Mock|EntityFetcher
184 4
     */
185
    public function ormExpectFetch($class, $entities = [], $em = null)
186 4
    {
187
        list($expectation, $fetcher) = $this->ormAllowFetch($class, $entities, $em);
188
        $expectation->once();
189
        return $fetcher;
190
    }
191
192
    /**
193
     * Allow fetch for $class
194
     *
195
     * Mocks an EntityFetcher with $entities as result.
196
     *
197
     * Returns the Expectation for fetch on entityManager and the mocked EntityFetcher
198 2
     *
199
     * @param string        $class    The class that should be fetched
200 2
     * @param array         $entities The entities that get returned from fetcher
201
     * @param EntityManager $em
202
     * @return m\Expectation|EntityFetcher|m\Mock[]
203 2
     */
204
    public function ormAllowFetch($class, $entities = [], $em = null)
205 2
    {
206 2
        /** @var EntityManager|m\Mock $em */
207 1
        $em = $em ?: EntityManager::getInstance($class);
208
209 1
        /** @var m\Mock|EntityFetcher $fetcher */
210
        $fetcher = m::mock(EntityFetcher::class, [ $em, $class ])->makePartial();
211 2
        $expectation = $em->shouldReceive('fetch')->with($class)->andReturn($fetcher);
212 2
213 2
        /** @scrutinizer ignore-call */
214 2
        $fetcher->shouldReceive('count')->with()->andReturn(count($entities))->byDefault();
215 2
        array_push($entities, null);
216
        $fetcher->shouldReceive('one')->with()->andReturnValues($entities)->byDefault();
217
218
        return [$expectation, $fetcher];
0 ignored issues
show
Bug Best Practice introduced by
The expression return array($expectation, $fetcher) returns an array which contains values of type Mockery\Expectation which are incompatible with the documented value type Mockery\Mock.
Loading history...
219
    }
220
221
    /**
222
     * Expect save on $entity
223
     *
224
     * Entity has to be a mock use `emCreateMockedEntity()` to create it.
225
     *
226
     * @param Entity|m\Mock $entity
227
     * @param array  $changingData Emulate changing data during update statement (triggers etc)
228
     * @param array  $updatedData  Emulate data changes in database
229
     */
230
    public function ormExpectUpdate(Entity $entity, $changingData = [], $updatedData = [])
231
    {
232
        $expectation = $this->ormAllowUpdate($entity, $changingData, $updatedData);
233
        $expectation->once();
234
    }
235
236
    /**
237
     * Allow save on $entity
238
     *
239
     * Entity has to be a mock use `emCreateMockedEntity()` to create it.
240
     *
241
     * @param Entity|m\Mock $entity
242
     * @param array $changingData Emulate changing data during update statement (triggers etc)
243
     * @param array $updatedData Emulate data changes in database
244
     * @return m\Expectation
245
     */
246
    public function ormAllowUpdate(Entity $entity, $changingData = [], $updatedData = [])
247
    {
248
        /** @scrutinizer ignore-call */
249
        $expectation = $entity->shouldReceive('save')->andReturnUsing(
0 ignored issues
show
Bug introduced by
The method shouldReceive() does not exist on ORM\Entity. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

249
        $expectation = $entity->/** @scrutinizer ignore-call */ shouldReceive('save')->andReturnUsing(

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...
250
            function () use ($entity, $updatedData, $changingData) {
251
                $class = get_class($entity);
252
                // sync with database using $updatedData
253
                if (!empty($updatedData)) {
254
                    $newData = $entity->getData();
255
                    $entity->reset();
256
                    $entity->setOriginalData(array_merge(
257
                        $entity->getData(),
258
                        $this->ormAttributesToData($class, $updatedData)
259
                    ));
260
                    $entity->fill($newData);
261
                }
262
263
                if (!$entity->isDirty()) {
264
                    return $entity;
265
                }
266
267
                // update the entity using $changingData
268
                $entity->preUpdate();
269
                $entity->setOriginalData(array_merge(
270
                    $entity->getData(),
271
                    $this->ormAttributesToData($class, $changingData)
272
                ));
273
                $entity->reset();
274
                $entity->postUpdate();
275
276
                return $entity;
277
            }
278
        );
279
280
        return $expectation;
281
    }
282
283
    /**
284
     * Expect delete on $em
285
     *
286
     * If $em is not given it is determined by get_class($entity).
287
     *
288
     * If $entity is a string then it is assumed to be a class name.
289
     *
290
     * @param string|Entity $entity
291
     * @param EntityManager $em
292
     */
293
    public function ormExpectDelete($entity, $em = null)
294
    {
295
        $expectation = $this->ormAllowDelete($entity, $em);
296
        $expectation->once();
297
    }
298
299
    /**
300
     * Allow delete on $em
301
     *
302
     * If $em is not given it is determined by get_class($entity).
303
     *
304
     * If $entity is a string then it is assumed to be a class name.
305
     *
306
     * @param string|Entity $entity
307
     * @param EntityManager $em
308
     * @return m\Expectation
309
     */
310
    public function ormAllowDelete($entity, $em = null)
311
    {
312
        $class = is_string($entity) ? $entity : get_class($entity);
313
314
        /** @var EntityManager|m\Mock $em */
315
        $em = $em ?: EntityManager::getInstance($class);
316
317
        $expectation = $em->shouldReceive('delete');
318
        if (is_string($entity)) {
319
            $expectation->with(m::type($class));
320
        } else {
321
            $expectation->with($entity);
322
        }
323
        $expectation->once()->andReturnUsing(
324
            function (Entity $entity) {
325
                $entity->setOriginalData([]);
326
                return true;
327
            }
328
        );
329
330
        return $expectation;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $expectation returns the type Mockery\HigherOrderMessage which is incompatible with the documented return type Mockery\Expectation.
Loading history...
331
    }
332
}
333