Completed
Push — master ( e81718...2850eb )
by Chauncey
02:26
created

ObjectRevision::getRevUser()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
namespace Charcoal\Object;
4
5
use InvalidArgumentException;
6
use DateTime;
7
use DateTimeInterface;
8
9
// From Pimple
10
use Pimple\Container;
11
12
// From 'charcoal-factory'
13
use Charcoal\Factory\FactoryInterface;
14
15
// From 'charcoal-core'
16
use Charcoal\Model\AbstractModel;
17
use Charcoal\Model\ModelFactoryTrait;
18
19
// From 'charcoal-object'
20
use Charcoal\Object\ObjectRevisionInterface;
21
use Charcoal\Object\RevisionableInterface;
22
23
/**
24
 * Represents the changeset of an object.
25
 *
26
 * A revision is a record of modifications to an object.
27
 *
28
 * Intended to be used to collect all routes related to models
29
 * under a single source (e.g., database table).
30
 *
31
 * {@see Charcoal\Object\ObjectRoute} for a similar model that aggregates data
32
 * under a common source.
33
 */
34
class ObjectRevision extends AbstractModel implements ObjectRevisionInterface
0 ignored issues
show
Bug introduced by
There is at least one abstract method in this class. Maybe declare it as abstract, or implement the remaining methods: hasProperty, p, properties, property
Loading history...
35
{
36
    use ModelFactoryTrait;
37
38
    /**
39
     * Object type of this revision (required)
40
     * @var string $targetType
41
     */
42
    private $targetType;
43
44
    /**
45
     * Object ID of this revision (required)
46
     * @var mixed $objectId
47
     */
48
    private $targetId;
49
50
    /**
51
     * Revision number. Sequential integer for each object's ID. (required)
52
     * @var integer $revNum
53
     */
54
    private $revNum;
55
56
    /**
57
     * Timestamp; when this revision was created
58
     * @var DateTimeInterface|null $revTs
59
     */
60
    private $revTs;
61
62
    /**
63
     * The (admin) user that was
64
     * @var string|null $revUser
65
     */
66
    private $revUser;
67
68
    /**
69
     * @var array $dataPrev
70
     */
71
    private $dataPrev;
72
73
    /**
74
     * @var array $dataObj
75
     */
76
    private $dataObj;
77
78
    /**
79
     * @var array $dataDiff
80
     */
81
    private $dataDiff;
82
83
    /**
84
     * @param  Container $container DI Container.
85
     * @return void
86
     */
87
    protected function setDependencies(Container $container)
88
    {
89
        parent::setDependencies($container);
90
91
        $this->setModelFactory($container['model/factory']);
92
    }
93
94
    /**
95
     * @param  string $targetType The object type (type-ident).
96
     * @throws InvalidArgumentException If the obj type parameter is not a string.
97
     * @return ObjectRevision Chainable
98
     */
99
    public function setTargetType($targetType)
100
    {
101
        if (!is_string($targetType)) {
102
            throw new InvalidArgumentException(
103
                'Revisions obj type must be a string.'
104
            );
105
        }
106
        $this->targetType = $targetType;
107
        return $this;
108
    }
109
110
    /**
111
     * @return string
112
     */
113
    public function getTargetType()
114
    {
115
        return $this->targetType;
116
    }
117
118
    /**
119
     * @param  mixed $targetId The object ID.
120
     * @return ObjectRevision Chainable
121
     */
122
    public function setTargetId($targetId)
123
    {
124
        $this->targetId = $targetId;
125
        return $this;
126
    }
127
128
    /**
129
     * @return mixed
130
     */
131
    public function getTargetId()
132
    {
133
        return $this->targetId;
134
    }
135
136
    /**
137
     * @param  integer $revNum The revision number.
138
     * @throws InvalidArgumentException If the revision number argument is not numerical.
139
     * @return ObjectRevision Chainable
140
     */
141
    public function setRevNum($revNum)
142
    {
143
        if (!is_numeric($revNum)) {
144
            throw new InvalidArgumentException(
145
                'Revision number must be an integer (numeric).'
146
            );
147
        }
148
        $this->revNum = intval($revNum);
149
        return $this;
150
    }
151
152
    /**
153
     * @return integer
154
     */
155
    public function getRevNum()
156
    {
157
        return $this->revNum;
158
    }
159
160
    /**
161
     * @param  mixed $revTs The revision's timestamp.
162
     * @throws InvalidArgumentException If the timestamp is invalid.
163
     * @return ObjectRevision Chainable
164
     */
165 View Code Duplication
    public function setRevTs($revTs)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
166
    {
167
        if ($revTs === null) {
168
            $this->revTs = null;
169
            return $this;
170
        }
171
        if (is_string($revTs)) {
172
            $revTs = new DateTime($revTs);
173
        }
174
        if (!($revTs instanceof DateTimeInterface)) {
175
            throw new InvalidArgumentException(
176
                'Invalid "Revision Date" value. Must be a date/time string or a DateTimeInterface object.'
177
            );
178
        }
179
        $this->revTs = $revTs;
180
        return $this;
181
    }
182
183
    /**
184
     * @return DateTimeInterface|null
185
     */
186
    public function getRevTs()
187
    {
188
        return $this->revTs;
189
    }
190
191
    /**
192
     * @param  string $revUser The revision user ident.
193
     * @throws InvalidArgumentException If the revision user parameter is not a string.
194
     * @return ObjectRevision Chainable
195
     */
196 View Code Duplication
    public function setRevUser($revUser)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
197
    {
198
        if ($revUser === null) {
199
            $this->revUser = null;
200
            return $this;
201
        }
202
        if (!is_string($revUser)) {
203
            throw new InvalidArgumentException(
204
                'Revision user must be a string.'
205
            );
206
        }
207
        $this->revUser = $revUser;
208
        return $this;
209
    }
210
211
    /**
212
     * @return string
213
     */
214
    public function getRevUser()
215
    {
216
        return $this->revUser;
217
    }
218
219
    /**
220
     * @param  string|array|null $data The previous revision data.
221
     * @return ObjectRevision Chainable
222
     */
223 View Code Duplication
    public function setDataPrev($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
224
    {
225
        if (!is_array($data)) {
226
            $data = json_decode($data, true);
227
        }
228
        if ($data === null) {
229
            $data = [];
230
        }
231
        $this->dataPrev = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type * is incompatible with the declared type array of property $dataPrev.

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...
232
        return $this;
233
    }
234
235
    /**
236
     * @return array
237
     */
238
    public function getDataPrev()
239
    {
240
        return $this->dataPrev;
241
    }
242
243
    /**
244
     * @param  array|string|null $data The current revision (object) data.
245
     * @return ObjectRevision Chainable
246
     */
247 View Code Duplication
    public function setDataObj($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
248
    {
249
        if (!is_array($data)) {
250
            $data = json_decode($data, true);
251
        }
252
        if ($data === null) {
253
            $data = [];
254
        }
255
        $this->dataObj = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type * is incompatible with the declared type array of property $dataObj.

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...
256
        return $this;
257
    }
258
259
    /**
260
     * @return array
261
     */
262
    public function getDataObj()
263
    {
264
        return $this->dataObj;
265
    }
266
267
    /**
268
     * @param  array|string $data The data diff.
269
     * @return ObjectRevision
270
     */
271 View Code Duplication
    public function setDataDiff($data)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
272
    {
273
        if (!is_array($data)) {
274
            $data = json_decode($data, true);
275
        }
276
        if ($data === null) {
277
            $data = [];
278
        }
279
        $this->dataDiff = $data;
0 ignored issues
show
Documentation Bug introduced by
It seems like $data of type * is incompatible with the declared type array of property $dataDiff.

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...
280
        return $this;
281
    }
282
283
    /**
284
     * @return array
285
     */
286
    public function getDataDiff()
287
    {
288
        return $this->dataDiff;
289
    }
290
291
    /**
292
     * Create a new revision from an object
293
     *
294
     * 1. Load the last revision
295
     * 2. Load the current item from DB
296
     * 3. Create diff from (1) and (2).
297
     *
298
     * @param  RevisionableInterface $obj The object to create the revision from.
299
     * @return ObjectRevision Chainable
300
     */
301
    public function createFromObject(RevisionableInterface $obj)
302
    {
303
        $prevRev = $this->lastObjectRevision($obj);
304
305
        $this->setTargetType($obj->objType());
306
        $this->setTargetId($obj->id());
307
        $this->setRevNum($prevRev->getRevNum() + 1);
308
        $this->setRevTs('now');
309
310
        if (isset($obj['lastModifiedBy'])) {
311
            $this->setRevUser($obj['lastModifiedBy']);
312
        }
313
314
        $this->setDataObj($obj->data());
315
        $this->setDataPrev($prevRev->getDataObj());
316
317
        $diff = $this->createDiff();
318
        $this->setDataDiff($diff);
319
320
        return $this;
321
    }
322
323
    /**
324
     * @param array $dataPrev Optional. Previous revision data.
325
     * @param array $dataObj  Optional. Current revision (object) data.
326
     * @return array The diff data
327
     */
328
    public function createDiff(array $dataPrev = null, array $dataObj = null)
329
    {
330
        if ($dataPrev === null) {
331
            $dataPrev = $this->getDataPrev();
332
        }
333
        if ($dataObj === null) {
334
            $dataObj = $this->getDataObj();
335
        }
336
        $dataDiff = $this->recursiveDiff($dataPrev, $dataObj);
337
        return $dataDiff;
338
    }
339
340
    /**
341
     * Recursive arrayDiff.
342
     *
343
     * @param array $array1 First array.
344
     * @param array $array2 Second Array.
345
     * @return array The array diff.
346
     */
347
    public function recursiveDiff(array $array1, array $array2)
348
    {
349
        $diff = [];
350
351
        // Compare array1
352
        foreach ($array1 as $key => $value) {
353
            if (!array_key_exists($key, $array2)) {
354
                $diff[0][$key] = $value;
355
            } elseif (is_array($value)) {
356
                if (!is_array($array2[$key])) {
357
                    $diff[0][$key] = $value;
358
                    $diff[1][$key] = $array2[$key];
359
                } else {
360
                    $new = $this->recursiveDiff($value, $array2[$key]);
361
                    if ($new !== false) {
362
                        if (isset($new[0])) {
363
                            $diff[0][$key] = $new[0];
364
                        }
365
                        if (isset($new[1])) {
366
                            $diff[1][$key] = $new[1];
367
                        }
368
                    }
369
                }
370
            } elseif ($array2[$key] !== $value) {
371
                $diff[0][$key] = $value;
372
                $diff[1][$key] = $array2[$key];
373
            }
374
        }
375
376
        // Compare array2
377
        foreach ($array2 as $key => $value) {
378
            if (!array_key_exists($key, $array1)) {
379
                $diff[1][$key] = $value;
380
            }
381
        }
382
383
        return $diff;
384
    }
385
386
    /**
387
     * @param RevisionableInterface $obj The object  to load the last revision of.
388
     * @return ObjectRevision The last revision for the give object.
389
     */
390 View Code Duplication
    public function lastObjectRevision(RevisionableInterface $obj)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
391
    {
392
        if ($this->source()->tableExists() === false) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method tableExists() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
393
            $this->source()->createTable();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method createTable() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
394
        }
395
396
        $rev = $this->modelFactory()->create(self::class);
397
398
        $sql = sprintf(
399
            'SELECT * FROM `%s` WHERE `target_type` = :target_type AND `target_id` = :target_id ORDER BY `rev_ts` DESC LIMIT 1',
400
            $this->source()->table()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method table() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
401
        );
402
        $rev->loadFromQuery($sql, [
403
            'target_type' => $obj->objType(),
404
            'target_id'   => $obj->id(),
405
        ]);
406
407
        return $rev;
408
    }
409
410
    /**
411
     * Retrieve a specific object revision, by revision number.
412
     *
413
     * @param RevisionableInterface $obj    Target object.
414
     * @param integer               $revNum The revision number to load.
415
     * @return ObjectRevision
416
     */
417 View Code Duplication
    public function objectRevisionNum(RevisionableInterface $obj, $revNum)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
418
    {
419
        if ($this->source()->tableExists() === false) {
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method tableExists() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
420
            $this->source()->createTable();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method createTable() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
421
        }
422
423
        $rev = $this->modelFactory()->create(self::class);
424
425
        $sql = sprintf(
426
            'SELECT * FROM `%s` WHERE `target_type` = :target_type AND `target_id` = :target_id AND `rev_num` = :rev_num LIMIT 1',
427
            $this->source()->table()
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Charcoal\Source\SourceInterface as the method table() does only exist in the following implementations of said interface: Charcoal\Source\DatabaseSource.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
428
        );
429
        $rev->loadFromQuery($sql, [
430
            'target_type' => $obj->objType(),
431
            'target_id'   => $obj->id(),
432
            'rev_num'     => intval($revNum),
433
        ]);
434
435
        return $rev;
436
    }
437
}
438