Completed
Pull Request — master (#586)
by Maxence
03:47
created

Member::getInheritanceFrom()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\Model;
33
34
use daita\MySmallPhpTools\Db\Nextcloud\nc22\INC22QueryRow;
35
use daita\MySmallPhpTools\Exceptions\InvalidItemException;
36
use daita\MySmallPhpTools\IDeserializable;
37
use daita\MySmallPhpTools\Traits\Nextcloud\nc22\TNC22Deserialize;
38
use daita\MySmallPhpTools\Traits\TArrayTools;
39
use DateTime;
40
use JsonSerializable;
41
use OCA\Circles\AppInfo\Capabilities;
42
use OCA\Circles\Exceptions\MemberNotFoundException;
43
use OCA\Circles\Exceptions\ParseMemberLevelException;
44
use OCA\Circles\Exceptions\UserTypeNotFoundException;
45
use OCA\Circles\IFederatedUser;
46
use OCA\Circles\IMemberships;
47
use OCA\Circles\Model\Federated\RemoteInstance;
48
49
50
/**
51
 * Class Member
52
 *
53
 * @package OCA\Circles\Model
54
 */
55
class Member extends ManagedModel implements
56
	IMemberships,
57
	IFederatedUser,
58
	IDeserializable,
59
	INC22QueryRow,
60
	JsonSerializable {
61
62
63
	use TArrayTools;
64
	use TNC22Deserialize;
65
66
67
	const LEVEL_NONE = 0;
68
	const LEVEL_MEMBER = 1;
69
	const LEVEL_MODERATOR = 4;
70
	const LEVEL_ADMIN = 8;
71
	const LEVEL_OWNER = 9;
72
73
	const TYPE_SINGLE = 0;
74
	const TYPE_USER = 1;
75
	const TYPE_GROUP = 2;
76
	const TYPE_MAIL = 4;
77
	const TYPE_CONTACT = 8;
78
	const TYPE_CIRCLE = 16;
79
	const TYPE_APP = 10000;
80
	const APP_CIRCLES = 10001;
81
82
83
	public static $TYPE = [
84
		0     => 'single',
85
		1     => 'user',
86
		2     => 'group',
87
		4     => 'mail',
88
		8     => 'contact',
89
		16    => 'circle',
90
		10000 => 'app'
91
	];
92
93
	/**
94
	 * Note: When editing those values, update lib/Application/Capabilities.php
95
	 *
96
	 * @see Capabilities::generateConstantsMember()
97
	 */
98
	const STATUS_INVITED = 'Invited';
99
	const STATUS_REQUEST = 'Requesting';
100
	const STATUS_MEMBER = 'Member';
101
	const STATUS_BLOCKED = 'Blocked';
102
103
104
	/**
105
	 * Note: When editing those values, update lib/Application/Capabilities.php
106
	 *
107
	 * @see Capabilities::generateConstantsMember()
108
	 * @var array
109
	 */
110
	public static $DEF_LEVEL = [
111
		1 => 'Member',
112
		4 => 'Moderator',
113
		8 => 'Admin',
114
		9 => 'Owner'
115
	];
116
117
118
	public static $DEF_TYPE_MAX = 31;
119
120
121
	/** @var string */
122
	private $id = '';
123
124
	/** @var string */
125
	private $circleId = '';
126
127
	/** @var string */
128
	private $singleId = '';
129
130
	/** @var string */
131
	private $userId = '';
132
133
	/** @var int */
134
	private $userType = 0;
135
136
	/** @var Circle */
137
	private $basedOn;
138
139
	/** @var Member */
140
	private $inheritanceFrom;
141
142
	/** @var FederatedUser */
143
	private $inheritedBy;
144
145
	/** @var string */
146
	private $instance = '';
147
148
	/** @var RemoteInstance */
149
	private $remoteInstance;
150
151
	/** @var bool */
152
	private $local = false;
153
154
	/** @var int */
155
	private $level = 0;
156
157
	/** @var string */
158
	private $status = 'Unknown';
159
160
	/** @var string */
161
	private $note = '';
162
163
	/** @var string */
164
	private $displayName = '';
165
166
	/** @var int */
167
	private $displayUpdate = 0;
168
169
	/** @var string */
170
	private $contactId = '';
171
172
	/** @var string */
173
	private $contactMeta = '';
174
175
	/** @var Circle */
176
	private $circle;
177
178
	/** @var int */
179
	private $joined = 0;
180
181
182
	/** @var Member[] */
183
	private $members = null;
184
185
	/** @var Member[] */
186
	private $inheritedMembers = null;
187
188
	/** @var bool */
189
	private $detailedInheritedMember = false;
190
191
	/** @var Membership[] */
192
	private $memberships = null;
193
194
195
	/**
196
	 * Member constructor.
197
	 */
198
	public function __construct() {
199
	}
200
201
202
	/**
203
	 * @param string $id
204
	 *
205
	 * @return $this
206
	 */
207
	public function setId(string $id): self {
208
		$this->id = $id;
209
210
		return $this;
211
	}
212
213
	/**
214
	 * @return string
215
	 */
216
	public function getId(): string {
217
		return $this->id;
218
	}
219
220
221
	/**
222
	 * @param string $circleId
223
	 *
224
	 * @return Member
225
	 */
226
	public function setCircleId(string $circleId): self {
227
		$this->circleId = $circleId;
228
229
		return $this;
230
	}
231
232
	/**
233
	 * @return string
234
	 */
235
	public function getCircleId(): string {
236
		return $this->circleId;
237
	}
238
239
240
	/**
241
	 * This should replace user_id, user_type and instance; and will use the data from Circle with
242
	 * Config=CFG_SINGLE
243
	 *
244
	 * @param string $singleId
245
	 *
246
	 * @return $this
247
	 */
248
	public function setSingleId(string $singleId): self {
249
		$this->singleId = $singleId;
250
251
		return $this;
252
	}
253
254
	/**
255
	 * @return string
256
	 */
257
	public function getSingleId(): string {
258
		return $this->singleId;
259
	}
260
261
262
	/**
263
	 * @param string $userId
264
	 *
265
	 * @return Member
266
	 */
267
	public function setUserId(string $userId): self {
268
		$this->userId = $userId;
269
		if ($this->displayName === '') {
270
			$this->displayName = $userId;
271
		}
272
273
		return $this;
274
	}
275
276
	/**
277
	 * @return string
278
	 */
279
	public function getUserId(): string {
280
		return $this->userId;
281
	}
282
283
284
	/**
285
	 * @param int $userType
286
	 *
287
	 * @return Member
288
	 */
289
	public function setUserType(int $userType): self {
290
		$this->userType = $userType;
291
292
		return $this;
293
	}
294
295
	/**
296
	 * @return int
297
	 */
298
	public function getUserType(): int {
299
		return $this->userType;
300
	}
301
302
303
	/**
304
	 * @param string $instance
305
	 *
306
	 * @return Member
307
	 */
308
	public function setInstance(string $instance): self {
309
		$this->instance = $instance;
310
311
		return $this;
312
	}
313
314
	/**
315
	 * @return string
316
	 */
317
	public function getInstance(): string {
318
		return $this->instance;
319
	}
320
321
322
	/**
323
	 * @return bool
324
	 */
325
	public function hasRemoteInstance(): bool {
326
		return !is_null($this->remoteInstance);
327
	}
328
329
	/**
330
	 * @param RemoteInstance $remoteInstance
331
	 *
332
	 * @return Member
333
	 */
334
	public function setRemoteInstance(RemoteInstance $remoteInstance): self {
335
		$this->remoteInstance = $remoteInstance;
336
337
		return $this;
338
	}
339
340
	/**
341
	 * @return RemoteInstance
342
	 */
343
	public function getRemoteInstance(): RemoteInstance {
344
		return $this->remoteInstance;
345
	}
346
347
348
	/**
349
	 * @return bool
350
	 */
351
	public function hasBasedOn(): bool {
352
		return !is_null($this->basedOn);
353
	}
354
355
	/**
356
	 * @param Circle $basedOn
357
	 *
358
	 * @return $this
359
	 */
360
	public function setBasedOn(Circle $basedOn): self {
361
		$this->basedOn = $basedOn;
362
363
		return $this;
364
	}
365
366
	/**
367
	 * @return Circle
368
	 */
369
	public function getBasedOn(): Circle {
370
		return $this->basedOn;
371
	}
372
373
374
	/**
375
	 * @return bool
376
	 */
377
	public function hasInheritedBy(): bool {
378
		return !is_null($this->inheritedBy);
379
	}
380
381
	/**
382
	 * @param FederatedUser $inheritedBy
383
	 *
384
	 * @return $this
385
	 */
386
	public function setInheritedBy(FederatedUser $inheritedBy): self {
387
		$this->inheritedBy = $inheritedBy;
388
389
		return $this;
390
	}
391
392
	/**
393
	 * @return FederatedUser
394
	 */
395
	public function getInheritedBy(): FederatedUser {
396
		return $this->inheritedBy;
397
	}
398
399
400
	/**
401
	 * @return bool
402
	 */
403
	public function hasInheritanceFrom(): bool {
404
		return !is_null($this->inheritanceFrom);
405
	}
406
407
	/**
408
	 * @param Member $inheritanceFrom
409
	 *
410
	 * @return $this
411
	 */
412
	public function setInheritanceFrom(Member $inheritanceFrom): self {
413
		$this->inheritanceFrom = $inheritanceFrom;
414
415
		return $this;
416
	}
417
418
	/**
419
	 * @return Member|null
420
	 */
421
	public function getInheritanceFrom(): ?Member {
422
		return $this->inheritanceFrom;
423
	}
424
425
426
	/**
427
	 * @param bool $local
428
	 *
429
	 * @return Member
430
	 */
431
	public function setLocal(bool $local): self {
432
		$this->local = $local;
433
434
		return $this;
435
	}
436
437
	/**
438
	 * @return bool
439
	 */
440
	public function isLocal(): bool {
441
		return $this->local;
442
	}
443
444
445
	/**
446
	 * @param int $level
447
	 *
448
	 * @return Member
449
	 */
450
	public function setLevel(int $level): self {
451
		$this->level = $level;
452
453
		return $this;
454
	}
455
456
	/**
457
	 * @return int
458
	 */
459
	public function getLevel(): int {
460
		return $this->level;
461
	}
462
463
464
	/**
465
	 * @param string $status
466
	 *
467
	 * @return Member
468
	 */
469
	public function setStatus(string $status): self {
470
		$this->status = $status;
471
472
		return $this;
473
	}
474
475
	/**
476
	 * @return string
477
	 */
478
	public function getStatus(): string {
479
		return $this->status;
480
	}
481
482
483
	/**
484
	 * @param string $note
485
	 *
486
	 * @return Member
487
	 */
488
	public function setNote(string $note): self {
489
		$this->note = $note;
490
491
		return $this;
492
	}
493
494
	/**
495
	 * @return string
496
	 */
497
	public function getNote(): string {
498
		return $this->note;
499
	}
500
501
502
	/**
503
	 * @param string $displayName
504
	 *
505
	 * @return Member
506
	 */
507
	public function setDisplayName(string $displayName): self {
508
		if ($displayName !== '') {
509
			$this->displayName = $displayName;
510
		}
511
512
		return $this;
513
	}
514
515
516
	/**
517
	 * @param int $displayUpdate
518
	 *
519
	 * @return Member
520
	 */
521
	public function setDisplayUpdate(int $displayUpdate): self {
522
		$this->displayUpdate = $displayUpdate;
523
524
		return $this;
525
	}
526
527
	/**
528
	 * @return int
529
	 */
530
	public function getDisplayUpdate(): int {
531
		return $this->displayUpdate;
532
	}
533
534
535
	/**
536
	 * @return string
537
	 */
538
	public function getDisplayName(): string {
539
		return $this->displayName;
540
	}
541
542
543
	/**
544
	 * @param string $contactId
545
	 *
546
	 * @return Member
547
	 */
548
	public function setContactId(string $contactId): self {
549
		$this->contactId = $contactId;
550
551
		return $this;
552
	}
553
554
	/**
555
	 * @return string
556
	 */
557
	public function getContactId(): string {
558
		return $this->contactId;
559
	}
560
561
562
	/**
563
	 * @param string $contactMeta
564
	 *
565
	 * @return Member
566
	 */
567
	public function setContactMeta(string $contactMeta): self {
568
		$this->contactMeta = $contactMeta;
569
570
		return $this;
571
	}
572
573
	/**
574
	 * @return string
575
	 */
576
	public function getContactMeta(): string {
577
		return $this->contactMeta;
578
	}
579
580
581
	/**
582
	 * @param Circle $circle
583
	 *
584
	 * @return self
585
	 */
586
	public function setCircle(Circle $circle): self {
587
		$this->circle = $circle;
588
589
		return $this;
590
	}
591
592
	/**
593
	 * @return Circle
594
	 */
595
	public function getCircle(): Circle {
596
		return $this->circle;
597
	}
598
599
	/**
600
	 * @return bool
601
	 */
602
	public function hasCircle(): bool {
603
		return (!is_null($this->circle));
604
	}
605
606
607
	/**
608
	 * @param int $joined
609
	 *
610
	 * @return Member
611
	 */
612
	public function setJoined(int $joined): self {
613
		$this->joined = $joined;
614
615
		return $this;
616
	}
617
618
	/**
619
	 * @return int
620
	 */
621
	public function getJoined(): int {
622
		return $this->joined;
623
	}
624
625
626
	/**    /**
627
	 * @param array $members
628
	 *
629
	 * @return self
630
	 */
631
	public function setMembers(array $members): IMemberships {
632
		$this->members = $members;
633
634
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (OCA\Circles\Model\Member) is incompatible with the return type declared by the interface OCA\Circles\IMemberships::setMembers of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
635
	}
636
637
	/**
638
	 * @return array
639
	 */
640
	public function getMembers(): array {
641
		if (is_null($this->members)) {
642
			$this->getManager()->getMembers($this);
643
		}
644
645
		return $this->members;
646
	}
647
648
649
	/**
650
	 * @param array $members
651
	 * @param bool $detailed
652
	 *
653
	 * @return self
654
	 */
655
	public function setInheritedMembers(array $members, bool $detailed): IMemberships {
656
		$this->inheritedMembers = $members;
657
		$this->detailedInheritedMember = $detailed;
658
659
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (OCA\Circles\Model\Member) is incompatible with the return type declared by the interface OCA\Circles\IMemberships::setInheritedMembers of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
660
	}
661
662
	/**
663
	 * @param bool $detailed
664
	 *
665
	 * @return array
666
	 */
667
	public function getInheritedMembers(bool $detailed = false): array {
668
		if (is_null($this->inheritedMembers)
669
			|| ($detailed && !$this->detailedInheritedMember)) {
670
			$this->getManager()->getInheritedMembers($this, $detailed);
671
		}
672
673
		return $this->inheritedMembers;
674
	}
675
676
677
	/**
678
	 * @param array $memberships
679
	 *
680
	 * @return self
681
	 */
682
	public function setMemberships(array $memberships): IMemberships {
683
		$this->memberships = $memberships;
684
685
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (OCA\Circles\Model\Member) is incompatible with the return type declared by the interface OCA\Circles\IMemberships::setMemberships of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
686
	}
687
688
	/**
689
	 * @return Membership[]
690
	 */
691
	public function getMemberships(): array {
692
		if (is_null($this->memberships)) {
693
			$this->getManager()->getMemberships($this);
694
		}
695
696
		return $this->memberships;
697
	}
698
699
700
701
702
//	/**
703
//	 * @param array $memberships
704
//	 *
705
//	 * @return self
706
//	 */
707
//	public function setMemberships(array $memberships): self {
708
//		$this->members = $memberships;
709
//
710
//		return $this;
711
//	}
712
//
713
//	/**
714
//	 * @return array
715
//	 */
716
//	public function getMemberships(): array {
717
//		if (is_null($this->members)) {
718
//			$this->getManager()->getMemberships($this);
719
//		}
720
//
721
//		return $this->members;
722
//	}
723
724
725
	/**
726
	 * @param Member $member
727
	 * @param bool $full
728
	 *
729
	 * @return bool
730
	 */
731
	public function compareWith(Member $member, bool $full = true): bool {
732
		if ($this->getId() !== $member->getId()
733
			|| $this->getCircleId() !== $member->getCircleId()
734
			|| $this->getSingleId() !== $member->getSingleId()
735
			|| $this->getUserId() !== $member->getUserId()
736
			|| $this->getUserType() <> $member->getUserType()
737
			|| $this->getInstance() !== $member->getInstance()) {
738
			return false;
739
		}
740
741
		if ($full
742
			&& ($this->getLevel() <> $member->getLevel()
743
				|| $this->getStatus() !== $member->getStatus())) {
744
			return false;
745
		}
746
747
		return true;
748
	}
749
750
751
	/**
752
	 * @param array $data
753
	 *
754
	 * @return $this
755
	 * @throws InvalidItemException
756
	 */
757
	public function import(array $data): IDeserializable {
758
		if ($this->get('userId', $data) === '') {
759
			throw new InvalidItemException();
760
		}
761
762
		$this->setId($this->get('id', $data));
763
		$this->setCircleId($this->get('circleId', $data));
764
		$this->setSingleId($this->get('singleId', $data));
765
		$this->setUserId($this->get('userId', $data));
766
		$this->setUserType($this->getInt('userType', $data));
767
		$this->setInstance($this->get('instance', $data));
768
		$this->setLocal($this->getBool('local', $data));
769
		$this->setLevel($this->getInt('level', $data));
770
		$this->setStatus($this->get('status', $data));
771
		$this->setDisplayName($this->get('displayName', $data));
772
		$this->setDisplayUpdate($this->getInt('displayUpdate', $data));
773
		$this->setNote($this->get('note', $data));
774
		$this->setContactId($this->get('contactId', $data));
775
		$this->setContactMeta($this->get('contactMeta', $data));
776
		$this->setJoined($this->getInt('joined', $data));
777
778
		try {
779
			/** @var Circle $circle */
780
			$circle = $this->deserialize($this->getArray('circle', $data), Circle::class);
781
			$this->setCircle($circle);
782
		} catch (InvalidItemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
783
		}
784
785
		try {
786
			/** @var Circle $circle */
787
			$circle = $this->deserialize($this->getArray('basedOn', $data), Circle::class);
788
			$this->setBasedOn($circle);
789
		} catch (InvalidItemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
790
		}
791
792
		try {
793
			/** @var FederatedUSer $inheritedBy */
794
			$inheritedBy = $this->deserialize($this->getArray('inheritedBy', $data), Membership::class);
795
			$this->setInheritedBy($inheritedBy);
796
		} catch (InvalidItemException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
797
		}
798
799
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (OCA\Circles\Model\Member) is incompatible with the return type declared by the interface daita\MySmallPhpTools\IDeserializable::import of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
800
	}
801
802
803
	/**
804
	 * @param array $data
805
	 * @param string $prefix
806
	 *
807
	 * @return INC22QueryRow
808
	 * @throws MemberNotFoundException
809
	 */
810
	public function importFromDatabase(array $data, string $prefix = ''): INC22QueryRow {
811
		if ($this->get($prefix . 'single_id', $data) === '') {
812
			throw new MemberNotFoundException();
813
		}
814
815
		$this->setId($this->get($prefix . 'member_id', $data));
816
		$this->setCircleId($this->get($prefix . 'circle_id', $data));
817
		$this->setSingleId($this->get($prefix . 'single_id', $data));
818
		$this->setUserId($this->get($prefix . 'user_id', $data));
819
		$this->setUserType($this->getInt($prefix . 'user_type', $data));
820
		$this->setInstance($this->get($prefix . 'instance', $data));
821
		$this->setLevel($this->getInt($prefix . 'level', $data));
822
		$this->setStatus($this->get($prefix . 'status', $data));
823
		$this->setDisplayName($this->get($prefix . 'cached_name', $data));
824
		$this->setNote($this->get($prefix . 'note', $data));
825
		$this->setContactId($this->get($prefix . 'contact_id', $data));
826
		$this->setContactMeta($this->get($prefix . 'contact_meta', $data));
827
828
		$cachedUpdate = $this->get($prefix . 'cached_update', $data);
829
		if ($cachedUpdate !== '') {
830
			$this->setDisplayUpdate(DateTime::createFromFormat('Y-m-d H:i:s', $cachedUpdate)->getTimestamp());
831
		}
832
833
		$joined = $this->get($prefix . 'joined', $data);
834
		if ($joined !== '') {
835
			$this->setJoined(DateTime::createFromFormat('Y-m-d H:i:s', $joined)->getTimestamp());
836
		}
837
838
		if ($this->getInstance() === '') {
839
			$this->setLocal(true);
840
			$this->setInstance($this->get('_params.local', $data));
841
		}
842
843
		$this->getManager()->manageImportFromDatabase($this, $data, $prefix);
844
845
		return $this;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this; (OCA\Circles\Model\Member) is incompatible with the return type declared by the interface daita\MySmallPhpTools\Db...Row::importFromDatabase of type self.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
846
	}
847
848
849
	/**
850
	 * @return string[]
851
	 */
852
	public function jsonSerialize(): array {
853
		$arr = [
854
			'id'            => $this->getId(),
855
			'circleId'      => $this->getCircleId(),
856
			'singleId'      => $this->getSingleId(),
857
			'userId'        => $this->getUserId(),
858
			'userType'      => $this->getUserType(),
859
			'instance'      => $this->getInstance(),
860
			'local'         => $this->isLocal(),
861
			'level'         => $this->getLevel(),
862
			'status'        => $this->getStatus(),
863
			'displayName'   => $this->getDisplayName(),
864
			'displayUpdate' => $this->getDisplayUpdate(),
865
			'note'          => $this->getNote(),
866
			'contactId'     => $this->getContactId(),
867
			'contactMeta'   => $this->getContactMeta(),
868
			'joined'        => $this->getJoined()
869
		];
870
871
		if ($this->hasBasedOn()) {
872
			$arr['basedOn'] = $this->getBasedOn();
873
		}
874
875
		if ($this->hasInheritedBy()) {
876
			$arr['inheritedBy'] = $this->getInheritedBy();
877
		}
878
879
		if ($this->hasInheritanceFrom()) {
880
			$arr['inheritanceFrom'] = $this->getInheritanceFrom();
881
		}
882
883
		if ($this->hasCircle()) {
884
			$arr['circle'] = $this->getCircle();
885
		}
886
887
		if (!is_null($this->members)) {
888
			$arr['members'] = $this->getMembers();
889
		}
890
891
		if (!is_null($this->inheritedMembers)) {
892
			$arr['inheritedMembers'] = $this->getInheritedMembers();
893
		}
894
895
		if (!is_null($this->memberships)) {
896
			$arr['memberships'] = $this->getMemberships();
897
		}
898
899
		if ($this->hasRemoteInstance()) {
900
			$arr['remoteInstance'] = $this->getRemoteInstance();
901
		}
902
903
		return $arr;
904
	}
905
906
907
	/**
908
	 * @param int $level
909
	 *
910
	 * @return int
911
	 * @throws ParseMemberLevelException
912
	 */
913
	public static function parseLevelInt(int $level): int {
914
		if (!array_key_exists($level, self::$DEF_LEVEL)) {
915
			$all = implode(', ', array_keys(self::$DEF_LEVEL));
916
			throw new ParseMemberLevelException('Available levels: ' . $all, 121);
917
		}
918
919
		return $level;
920
	}
921
922
923
	/**
924
	 * @param string $levelString
925
	 *
926
	 * @return int
927
	 * @throws ParseMemberLevelException
928
	 */
929
	public static function parseLevelString(string $levelString): int {
930
		$levelString = ucfirst(strtolower($levelString));
931
		$level = array_search($levelString, Member::$DEF_LEVEL);
932
933
		if (!$level) {
934
			$all = implode(', ', array_values(self::$DEF_LEVEL));
935
			throw new ParseMemberLevelException('Available levels: ' . $all, 121);
936
		}
937
938
		return (int)$level;
939
	}
940
941
	/**
942
	 * @param string $typeString
943
	 *
944
	 * @return int
945
	 * @throws UserTypeNotFoundException
946
	 */
947
	public static function parseTypeString(string $typeString): int {
948
		$typeString = strtolower($typeString);
949
		$type = array_search($typeString, Member::$TYPE);
950
951
		if ($type === false) {
952
			$all = implode(', ', array_values(self::$TYPE));
953
			throw new UserTypeNotFoundException('Available types: ' . $all);
954
		}
955
956
		return (int)$type;
957
	}
958
959
}
960
961