Lines 87.29% 268 / 307
Methods 75.40% 46 / 61
Classes 0.00% 0 / 1
Name Lines Methods CRAP
 getDefaults 100.00% 34 / 34 100.00% 1 / 1 1
 addData 100.00% 7 / 7 100.00% 1 / 1 5
 hasProperties 100.00% 4 / 4 100.00% 1 / 1 3
 getDepartmentList 100.00% 9 / 9 100.00% 1 / 1 4
 addDepartment 83.33% 5 / 6 0.00% 0 / 1 3.04
 getDepartment 100.00% 4 / 4 100.00% 1 / 1 3
 hasDepartment 100.00% 1 / 1 100.00% 1 / 1 1
 hasScope 100.00% 1 / 1 100.00% 1 / 1 1
 setPermissions 100.00% 5 / 5 100.00% 1 / 1 3
 hasPermissions 100.00% 12 / 12 100.00% 1 / 1 10
 hasAnyPermission 100.00% 12 / 12 100.00% 1 / 1 10
 hasExclusivePermission 100.00% 17 / 17 100.00% 1 / 1 13
 getRoles 0.00% 0 / 8 0.00% 0 / 1 12
 hasRole 0.00% 0 / 1 0.00% 0 / 1 2
 testPermissions 100.00% 7 / 7 100.00% 1 / 1 3
 testAnyPermission 100.00% 7 / 7 100.00% 1 / 1 3
 isOveraged 100.00% 4 / 4 100.00% 1 / 1 2
 isSuperUser 100.00% 1 / 1 100.00% 1 / 1 1
 getDepartmentById 100.00% 4 / 4 100.00% 1 / 1 3
 getDepartmentByIds 0.00% 0 / 4 0.00% 0 / 1 12
 testDepartmentById 100.00% 6 / 6 100.00% 1 / 1 2
 setPassword 100.00% 7 / 7 100.00% 1 / 1 6
 withDepartmentList 100.00% 10 / 10 100.00% 1 / 1 4
 withCleanedUpFormData 90.00% 9 / 10 0.00% 0 / 1 8.06
 setVerifiedHash 0.00% 0 / 6 0.00% 0 / 1 20
 withVerifiedHash 0.00% 0 / 4 0.00% 0 / 1 6
 isPasswordNeedingRehash 0.00% 0 / 1 0.00% 0 / 1 2
 getHash 100.00% 2 / 2 100.00% 1 / 1 1
 withLessData 100.00% 2 / 2 100.00% 1 / 1 1
 createFromOpenidData 100.00% 7 / 7 100.00% 1 / 1 1
 getOidcProviderFromName 100.00% 4 / 4 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] __construct 100.00% 4 / 4 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] exchangeArray 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] offsetSet 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getUnflattenedArray 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] getValidator 100.00% 6 / 6 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] isValid 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] testValid 100.00% 7 / 7 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] createExample 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getExample 100.00% 5 / 5 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] readJsonSchema 66.66% 6 / 9 0.00% 0 / 1 3.33
 [BO\Zmsentities\Schema\Entity] getEntityName 100.00% 4 / 4 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] setJsonCompressLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] jsonSerialize 100.00% 11 / 11 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] __toString 100.00% 2 / 2 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] __get 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __isset 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __set 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __unset 0.00% 0 / 1 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] __clone 100.00% 3 / 3 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] withData 0.00% 0 / 3 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] hasId 100.00% 1 / 1 100.00% 1 / 1 2
 [BO\Zmsentities\Schema\Entity] getId 100.00% 2 / 2 100.00% 1 / 1 3
 [BO\Zmsentities\Schema\Entity] toProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] hasProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] getProperty 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] withProperty 0.00% 0 / 3 0.00% 0 / 1 2
 [BO\Zmsentities\Schema\Entity] getResolveLevel 100.00% 1 / 1 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] setResolveLevel 100.00% 2 / 2 100.00% 1 / 1 1
 [BO\Zmsentities\Schema\Entity] withResolveLevel 100.00% 9 / 9 100.00% 1 / 1 5
 [BO\Zmsentities\Schema\Entity] withReference 100.00% 5 / 5 100.00% 1 / 1 2
15class Useraccount extends Schema\Entity
16{
17    public const string PRIMARY = 'id';
18
19    public static $schema = "useraccount.json";
20
21    /**
22     * @return array<string, mixed>
23     */
24    #[\Override]
25    public function getDefaults()
26    {
27        return [
28            'permissions' => [
29                "appointment" => false,
30                "availability" => false,
31                "calldisplay" => false,
32                "capacityreport" => false,
33                "cherrypick" => false,
34                "cluster" => false,
35                "config" => false,
36                "counter" => false,
37                "customersearch" => false,
38                "dayoff" => false,
39                "department" => false,
40                "emergency" => false,
41                "finishedqueue" => false,
42                "finishedqueuepast" => false,
43                "jurisdiction" => false,
44                "logs" => false,
45                "mailtemplates" => false,
46                "missedqueue" => false,
47                "openqueue" => false,
48                "organisation" => false,
49                "overviewcalendar" => false,
50                "parkedqueue" => false,
51                "restrictedscope" => false,
52                "scope" => false,
53                "source" => false,
54                "statistic" => false,
55                "ticketprinter" => false,
56                "useraccount" => false,
57                "waitingqueue" => false,
58                "superuser" => false
59            ],
60        ];
61    }
62
63    /**
64     * @return static
65     */
66    #[\Override]
67    public function addData(array|object $mergeData): static
68    {
69        $hasDepartments = is_array($mergeData) || $mergeData instanceof \ArrayAccess
70            ? isset($mergeData['departments'])
71            : isset($mergeData->departments);
72        if ($hasDepartments && !($this['departments'] ?? null) instanceof Collection\DepartmentList) {
73            $this->departments = new Collection\DepartmentList();
74        }
75        parent::addData($mergeData);
76        return $this;
77    }
78
79    /**
80     * @return true
81     */
82    public function hasProperties(): bool
83    {
84        foreach (func_get_args() as $property) {
85            if (!$this->toProperty()->$property->get()) {
86                throw new Exception\UserAccountMissingProperties("Missing property " . htmlspecialchars($property));
87                return false;
88            }
89        }
90        return true;
91    }
92
93    public function getDepartmentList(): Collection\DepartmentList
94    {
95        if (!isset($this['departments'])) {
96            return new Collection\DepartmentList();
97        }
98        $departments = $this->departments;
99        if (!$departments instanceof Collection\DepartmentList) {
100            $departments = new Collection\DepartmentList($departments);
101            foreach ($departments as $key => $department) {
102                $departments[$key] = new Department($department);
103            }
104            $this->departments = $departments;
105        }
106        return $departments;
107    }
108
109    public function addDepartment(Department|array $department): static
110    {
111        if (!isset($this['departments'])) {
112            $this->departments = new Collection\DepartmentList();
113        } elseif (!$this->departments instanceof Collection\DepartmentList) {
114            $this->getDepartmentList();
115        }
116        $this->departments[] = $department;
117        return $this;
118    }
119
120    public function getDepartment(mixed $departmentId): \BO\Zmsentities\Department
121    {
122        foreach ($this->getDepartmentList() as $department) {
123            if ($department['id'] == $departmentId) {
124                return $department;
125            }
126        }
127        return new Department(['name' => 'Not existing']);
128    }
129
130    public function hasDepartment(mixed $departmentId): mixed
131    {
132        return $this->getDepartment($departmentId)->hasId();
133    }
134
135    public function hasScope(mixed $scopeId): mixed
136    {
137        return $this->getDepartmentList()->getUniqueScopeList()->hasEntity($scopeId);
138    }
139
140
141    public function setPermissions(): static
142    {
143        $givenPermissions = func_get_args();
144        foreach ($givenPermissions as $permission) {
145            if (Property::__keyExists($permission, $this->permissions)) {
146                $this->permissions[$permission] = true;
147            }
148        }
149        return $this;
150    }
151
152    /**
153     * Returns true when the user has all of the given permissions.
154     */
155    public function hasPermissions(array $requiredPermissions): bool
156    {
157        if ($this->isSuperUser()) {
158            return true;
159        }
160
161        $permissions = $this->toProperty()->permissions ?? null;
162
163        foreach ($requiredPermissions as $required) {
164            if ($required instanceof Useraccount\RightsInterface) {
165                if (! $required->validateUseraccount($this)) {
166                    return false;
167                }
168                continue;
169            }
170
171            $granted = $permissions?->$required?->get() ?? false;
172            if ($granted === false || $granted === null || $granted === 0 || $granted === '0' || $granted === '') {
173                return false;
174            }
175        }
176
177        return true;
178    }
179
180    /**
181     * Returns true when the user has any of the given permissions.
182     */
183    public function hasAnyPermission(array $requiredPermissions): bool
184    {
185        if ($this->isSuperUser()) {
186            return true;
187        }
188
189        $permissions = $this->toProperty()->permissions ?? null;
190
191        foreach ($requiredPermissions as $required) {
192            if ($required instanceof Useraccount\RightsInterface) {
193                if ($required->validateUseraccount($this)) {
194                    return true;
195                }
196                continue;
197            }
198
199            $granted = $permissions?->$required?->get() ?? false;
200            if ($granted !== false && $granted !== null && $granted !== 0 && $granted !== '0' && $granted !== '') {
201                return true;
202            }
203        }
204
205        return false;
206    }
207
208    /**
209     * Returns true when the user has only the given permission and no other permission.
210     */
211    public function hasExclusivePermission(string $permission): bool
212    {
213        if ($this->isSuperUser()) {
214            return false;
215        }
216
217        $permissions = $this['permissions'] ?? [];
218        $requiredPermission = $permissions[$permission] ?? false;
219        if (
220            !is_array($permissions)
221            || $requiredPermission === false
222            || $requiredPermission === null
223            || $requiredPermission === 0
224            || $requiredPermission === '0'
225            || $requiredPermission === ''
226        ) {
227            return false;
228        }
229
230        foreach ($permissions as $name => $enabled) {
231            if ($permission === $name || 'superuser' === $name) {
232                continue;
233            }
234            if ($enabled && '0' !== $enabled) {
235                return false;
236            }
237        }
238
239        return true;
240    }
241    public function getRoles(): array
242    {
243        $roles = $this->roles ?? [];
244        if (is_array($roles)) {
245            return $roles;
246        }
247        if (is_string($roles)) {
248            return array_values(array_filter(array_map('trim', explode(',', $roles)), function ($role) {
249                return $role !== '';
250            }));
251        }
252        return [];
253    }
254
255    public function hasRole(string $role): bool
256    {
257        return in_array($role, $this->getRoles(), true);
258    }
259
260    public function testPermissions(array $requiredPermissions): static
261    {
262        if (! $this->hasId()) {
263            throw new Exception\UserAccountMissingLogin();
264        }
265
266        if (! $this->hasPermissions($requiredPermissions)) {
267            throw new Exception\UserAccountMissingRights(
268                "Missing permissions " . htmlspecialchars(implode(',', $requiredPermissions))
269            );
270        }
271
272        return $this;
273    }
274
275    public function testAnyPermission(array $requiredPermissions): static
276    {
277        if (! $this->hasId()) {
278            throw new Exception\UserAccountMissingLogin();
279        }
280
281        if (! $this->hasAnyPermission($requiredPermissions)) {
282            throw new Exception\UserAccountMissingRights(
283                "Missing any of permissions " . htmlspecialchars(implode(',', $requiredPermissions))
284            );
285        }
286
287        return $this;
288    }
289
290    public function isOveraged(\DateTimeInterface $dateTime): bool
291    {
292        if (Property::__keyExists('lastLogin', $this)) {
293            $lastLogin = (new \DateTimeImmutable())->setTimestamp($this['lastLogin'])->modify('23:59:59');
294            return ($lastLogin < $dateTime);
295        }
296        return false;
297    }
298
299    public function isSuperUser(): bool
300    {
301        return $this->toProperty()->permissions?->superuser?->get() ?? false;
302    }
303
304    public function getDepartmentById(mixed $departmentId): Department
305    {
306        foreach ($this->getDepartmentList() as $department) {
307            if ($departmentId == $department['id']) {
308                return new Department($department);
309            }
310        }
311        return new Department();
312    }
313
314    public function getDepartmentByIds(array $departmentIds): Department
315    {
316        foreach ($this->getDepartmentList() as $department) {
317            if (in_array($department['id'], $departmentIds)) {
318                return new Department($department);
319            }
320        }
321        return new Department();
322    }
323
324    public function testDepartmentById(mixed $departmentId): \BO\Zmsentities\Department
325    {
326        $department = $this->getDepartmentById($departmentId);
327        if (!$department->hasId()) {
328            throw new Exception\UserAccountMissingDepartment(
329                "Missing department " . htmlspecialchars($departmentId)
330            );
331        }
332        return $department;
333    }
334
335    public function setPassword(mixed $input): static
336    {
337        if (isset($input['password']) && '' != $input['password']) {
338            $this->password = $input['password'];
339        }
340        if (isset($input['changePassword']) && 0 < count(array_filter($input['changePassword']))) {
341            if (! isset($input['password'])) {
342                $this->password = $input['changePassword'][0];
343            }
344            $this->changePassword = $input['changePassword'];
345        }
346        return $this;
347    }
348
349    public function withDepartmentList(): static
350    {
351        $departmentList = new Collection\DepartmentList();
352        $entity = clone $this;
353        foreach ($this['departments'] ?? [] as $department) {
354            if ($department instanceof Department) {
355                $departmentList->addEntity($department);
356            } else {
357                $departmentList->addEntity(new Department(
358                    is_array($department) ? $department : ['id' => $department]
359                ));
360            }
361        }
362        $entity->departments = $departmentList;
363        return $entity;
364    }
365
366    /**
367     * @return static
368     */
369    #[\Override]
370    public function withCleanedUpFormData(bool $keepPassword = false)
371    {
372        unset($this['save']);
373        if (isset($this['password']) && '' == $this['password'] && false === $keepPassword) {
374            unset($this['password']);
375        }
376        if (
377            isset($this['changePassword']) &&
378            0 == count(array_filter($this['changePassword'])) &&
379            false === $keepPassword
380        ) {
381            unset($this['changePassword']);
382        }
383        if (isset($this['oidcProvider'])) {
384            unset($this['oidcProvider']);
385        }
386
387        return $this;
388    }
389
390    /**
391     * verify hashed password and create new if needs rehash
392     *
393     * @return static $useraccount
394    */
395    public function setVerifiedHash(mixed $password): static
396    {
397        // Do you have old, turbo-legacy, non-crypt hashes?
398        if (strpos($this->password, '$') !== 0) {
399            $result = $this->password === md5($password);
400        } else {
401            $result = password_verify($password, $this->password);
402        }
403
404        // on passed validation check if the hash needs updating.
405        if ($result && $this->isPasswordNeedingRehash()) {
406            $this->password = $this->getHash($password);
407        }
408
409        return $this;
410    }
411
412    public function withVerifiedHash(mixed $password): static
413    {
414        $useraccount = clone $this;
415        if ($useraccount->isPasswordNeedingRehash()) {
416            $useraccount->setVerifiedHash($password);
417        }
418        return $useraccount;
419    }
420
421    public function isPasswordNeedingRehash(): bool
422    {
423        return password_needs_rehash($this->password, PASSWORD_DEFAULT);
424    }
425
426    /**
427     * set salted hash by string
428     *
429     * @return string $hash
430    */
431    public function getHash(string $string)
432    {
433        $hash = password_hash($string, PASSWORD_DEFAULT);
434        return $hash;
435    }
436
437    /**
438     * @return static
439     */
440    #[\Override]
441    public function withLessData()
442    {
443        unset($this->departments);
444
445        return $this;
446    }
447
448    /**
449     * create useraccount from open id input data with random password
450     *
451     * @return self
452    */
453    public function createFromOpenidData(mixed $data)
454    {
455        $entity = new self();
456        $entity->id = $data['username'];
457        $department = new Department(['id' => 0]);
458        $entity->addDepartment($department);
459        $password = substr(str_shuffle($entity->id . uniqid()), 0, 8);
460        $entity->password = $this->getHash($password);
461        return $entity;
462    }
463
464    /**
465     * get oidc provider from $entity id if it exists
466     *
467     * @return string|null $entity
468    */
469    public function getOidcProviderFromName()
470    {
471        $providerName = '';
472        if (($pos = strpos($this->id, "@")) !== false) {
473            $providerName = substr($this->id, $pos + 1);
474        }
475        return ('' !== $providerName) ? $providerName : null;
476    }
477}

Inherited from BO\Zmsentities\Schema\Entity

59    public function __construct(mixed $input = null, int $flags = \ArrayObject::ARRAY_AS_PROPS, string $iterator_class = "ArrayIterator")
60    {
61        /** @var class-string<\ArrayIterator> $iterator_class */
62        parent::__construct($this->getDefaults(), $flags, $iterator_class);
63        if ($input) {
64            $input = $this->getUnflattenedArray($input);
65            $this->addData($input);
66        }
67    }
70    public function exchangeArray(object|array $input): array
71    {
72        parent::exchangeArray($this->getDefaults());
73        $input = $this->getUnflattenedArray($input);
74        $this->addData($input);
75        return $this->getArrayCopy();
76    }
82    public function offsetSet(mixed $key, mixed $value): void
83    {
84        /** @psalm-suppress PossiblyNullArgument */
85        parent::offsetSet($key, $value);
86    }
88    public function getUnflattenedArray(mixed $input): mixed
89    {
90        if (!$input instanceof UnflattedArray) {
91            $input = new UnflattedArray($input);
92            $input->getUnflattenedArray();
93        }
94        $input = $input->getValue();
95        return $input;
96    }
112    public function getValidator(string $locale = 'de_DE', int $resolveLevel = 0): Validator
113    {
114        $jsonSchema = self::readJsonSchema()->withResolvedReferences($resolveLevel);
115        $data = (new Schema($this))->withoutRefs();
116        if (Property::__keyExists('$schema', $data)) {
117            unset($data['$schema']);
118        }
119        $validator = new Validator($data->toJsonObject(true), $jsonSchema, $locale);
120        return $validator;
121    }
128    public function isValid(int $resolveLevel = 0): bool
129    {
130        $validator = $this->getValidator('de_DE', $resolveLevel = 0);
131        return $validator->isValid();
132    }
140    public function testValid(string $locale = 'de_DE', int $resolveLevel = 0): bool
141    {
142        $validator = $this->getValidator($locale, $resolveLevel);
143        if (!$validator->isValid()) {
144            $exception = new \BO\Zmsentities\Exception\SchemaValidation();
145            $exception->setSchemaName($this->getEntityName());
146            $exception->setValidationError($validator->getErrors());
147            throw $exception;
148        }
149        return true;
150    }
157    public static function createExample()
158    {
159        $object = new static();
160        return $object->getExample();
161    }
168    public static function getExample()
169    {
170        $class = get_called_class();
171        $jsonSchema = self::readJsonSchema();
172        if ($jsonSchema->offsetExists('example')) {
173            return new $class($jsonSchema['example']);
174        }
175        return new $class();
176    }
178    protected static function readJsonSchema(): mixed
179    {
180        $class = get_called_class();
181        if (!array_key_exists($class, self::$schemaCache)) {
182            $schemaFile = $class::$schema;
183            if (!is_string($schemaFile)) {
184                throw new \BO\Zmsentities\Exception\SchemaMissingJsonFile(
185                    'Missing JSON-Schema file for ' . $class
186                );
187            }
188            self::$schemaCache[$class] = Loader::asArray($schemaFile);
189        }
190        return self::$schemaCache[$class];
191    }
193    public function getEntityName(): string
194    {
195        $entity = get_class($this);
196        $entity = preg_replace('#.*[\\\]#', '', $entity) ?? '';
197        $entity = strtolower($entity);
198        return $entity;
199    }
201    public function setJsonCompressLevel(mixed $jsonCompressLevel): static
202    {
203        $this->jsonCompressLevel = $jsonCompressLevel;
204        return $this;
205    }
208    public function jsonSerialize(): mixed
209    {
210        $schema = array(
211            '$schema' => 'https://schema.berlin.de/queuemanagement/' . $this->getEntityName() . '.json'
212        );
213        $schema = array_merge($schema, $this->getArrayCopy());
214        if ($this instanceof \BO\Zmsentities\Helper\NoSanitize) {
215            $serialize = $schema;
216        } else {
217            $schema = new Schema($schema);
218            $schema->setDefaults($this->getDefaults());
219            $schema->setJsonCompressLevel($this->jsonCompressLevel);
220            $serialize = $schema->toJsonObject();
221        }
222        return $serialize;
223    }
225    public function __toString()
226    {
227        $encoded = json_encode($this->jsonSerialize(), JSON_HEX_QUOT);
228        return $encoded !== false ? $encoded : '';
229    }
235    public function __get(string $name): mixed
236    {
237        return $this->offsetGet($name);
238    }
240    public function __isset(string $name): bool
241    {
242        return $this->offsetExists($name);
243    }
245    public function __set(string $name, mixed $value): void
246    {
247        $this->offsetSet($name, $value);
248    }
250    public function __unset(string $name): void
251    {
252        $this->offsetUnset($name);
253    }
255    public function __clone()
256    {
257        foreach ($this as $key => $property) {
258            if (is_object($property)) {
259                $this[$key] = clone $property;
260            }
261        }
262    }
294    public function withData(mixed $mergeData): static
295    {
296        $entity = clone $this;
297        $entity->addData($mergeData);
298        return $entity;
299    }
301    public function hasId(): bool
302    {
303        return (false !== $this->getId()) ? true : false;
304    }
306    public function getId(): mixed
307    {
308        $idName = $this::PRIMARY;
309        return ($this->offsetExists($idName) && $this[$idName]) ? $this[$idName] : false;
310    }
317    public function toProperty()
318    {
319        return new \BO\Zmsentities\Helper\Property($this);
320    }
322    public function hasProperty(mixed $propertyName): mixed
323    {
324        return $this->toProperty()->{$propertyName}->isAvailable();
325    }
327    public function getProperty(string $propertyName, mixed $default = ''): mixed
328    {
329        return $this->toProperty()->{$propertyName}->get($default);
330    }
335    public function withProperty(mixed $propertyName, mixed $newValue): static
336    {
337        $entity = clone $this;
338        $entity[$propertyName] = $newValue;
339        return $entity;
340    }
372    public function getResolveLevel()
373    {
374        return $this->resolveLevel;
375    }
381    public function setResolveLevel($resolveLevel)
382    {
383        $this->resolveLevel = $resolveLevel;
384        return $this;
385    }
393    public function withResolveLevel($resolveLevel): self|array
394    {
395        if ($resolveLevel >= 0) {
396            $entity = clone $this;
397            foreach ($entity as $key => $value) {
398                if ($value instanceof Entity || $value instanceof \BO\Zmsentities\Collection\Base) {
399                    $entity[$key] = $value->withResolveLevel($resolveLevel - 1);
400                } else {
401                    $entity[$key] = $value;
402                }
403            }
404            $entity->setResolveLevel($resolveLevel);
405            return $entity;
406        } else {
407            return $this->withReference();
408        }
409    }
417    public function withReference($additionalData = []): self|array
418    {
419        if (isset($this[$this::PRIMARY])) {
420            $additionalData['$ref'] =
421                $this::$schemaRefPrefix . $this->getEntityName() . '/' . $this[$this::PRIMARY] . '/';
422            return $additionalData;
423        } else {
424            return $this->withResolveLevel(0);
425        }
426    }