Test Failed
Push — master ( bca3c6...21ca38 )
by Raffael
09:03
created

Collection::unshare()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 62

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 62
ccs 0
cts 31
cp 0
rs 8.5178
c 0
b 0
f 0
cc 5
nc 6
nop 0
crap 30

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * balloon
7
 *
8
 * @copyright   Copryright (c) 2012-2019 gyselroth GmbH (https://gyselroth.com)
9
 * @license     GPL-3.0 https://opensource.org/licenses/GPL-3.0
10
 */
11
12
namespace Balloon\Filesystem\Node;
13
14
use Balloon\Filesystem;
15
use Balloon\Filesystem\Acl;
16
use Balloon\Filesystem\Acl\Exception\Forbidden as ForbiddenException;
17
use Balloon\Filesystem\Exception;
18
use Balloon\Filesystem\Storage\Adapter\AdapterInterface as StorageAdapterInterface;
19
use Balloon\Hook;
20
use Balloon\Server\User;
21
use Generator;
22
use MimeType\MimeType;
23
use function MongoDB\BSON\fromJSON;
24
use MongoDB\BSON\ObjectId;
25
use MongoDB\BSON\Regex;
26
use function MongoDB\BSON\toPHP;
27
use MongoDB\BSON\UTCDateTime;
28
use Psr\Log\LoggerInterface;
29
use Sabre\DAV\IQuota;
30
31
class Collection extends AbstractNode implements IQuota
32
{
33
    /**
34
     * Root folder.
35
     */
36
    const ROOT_FOLDER = '/';
37
38
    /**
39
     * Share acl.
40
     *
41
     * @var array
42
     */
43
    protected $acl = [];
44
45
    /**
46
     * Share name.
47
     *
48
     * @var string
49
     */
50
    protected $share_name;
51
52
    /**
53
     * filter.
54
     *
55
     * @var string
56
     */
57
    protected $filter;
58
59
    /**
60
     * Storage.
61
     *
62
     * @var StorageAdapterInterface
63
     */
64
    protected $_storage;
65
66
    /**
67
     * Initialize.
68
     */
69
    public function __construct(array $attributes, Filesystem $fs, LoggerInterface $logger, Hook $hook, Acl $acl, ?Collection $parent, StorageAdapterInterface $storage)
70
    {
71
        $this->_fs = $fs;
72
        $this->_server = $fs->getServer();
73
        $this->_db = $fs->getDatabase();
74
        $this->_user = $fs->getUser();
75
        $this->_logger = $logger;
76
        $this->_hook = $hook;
77
        $this->_acl = $acl;
78
        $this->_storage = $storage;
79
        $this->_parent = $parent;
80
81
        foreach ($attributes as $attr => $value) {
82
            $this->{$attr} = $value;
83
        }
84
85
        $this->mime = 'inode/directory';
86
        $this->raw_attributes = $attributes;
87
    }
88
89
    /**
90
     * Set storage adapter.
91
     */
92
    public function setStorage(StorageAdapterInterface $adapter): self
93
    {
94
        $this->_storage = $adapter;
95
96
        return $this;
97
    }
98
99
    /**
100
     * Get storage adapter.
101
     */
102
    public function getStorage(): StorageAdapterInterface
103
    {
104
        return $this->_storage;
105
    }
106
107
    /**
108
     * Copy node with children.
109
     */
110
    public function copyTo(self $parent, int $conflict = NodeInterface::CONFLICT_NOACTION, ?string $recursion = null, bool $recursion_first = true, int $deleted = NodeInterface::DELETED_EXCLUDE): NodeInterface
111
    {
112
        if (null === $recursion) {
113
            $recursion_first = true;
114
            $recursion = uniqid();
115
        } else {
116
            $recursion_first = false;
117
        }
118
119
        $this->_hook->run(
120
            'preCopyCollection',
121
            [$this, $parent, &$conflict, &$recursion, &$recursion_first]
122
        );
123
124
        if (NodeInterface::CONFLICT_RENAME === $conflict && $parent->childExists($this->name)) {
125
            $name = $this->getDuplicateName();
126
        } else {
127
            $name = $this->name;
128
        }
129
130
        if ($this->_id === $parent->getId()) {
131
            throw new Exception\Conflict(
132
                'can not copy node into itself',
133
                Exception\Conflict::CANT_COPY_INTO_ITSELF
134
            );
135
        }
136
137
        if (NodeInterface::CONFLICT_MERGE === $conflict && $parent->childExists($this->name)) {
138
            $new_parent = $parent->getChild($this->name);
139
140
            if ($new_parent instanceof File) {
141
                $new_parent = $this;
142
            }
143
        } else {
144
            $new_parent = $parent->addDirectory($name, [
145
                'created' => $this->created,
146
                'changed' => $this->changed,
147
                'filter' => $this->filter,
148
                'meta' => $this->meta,
149
            ], NodeInterface::CONFLICT_NOACTION, true);
150
        }
151
152
        foreach ($this->getChildNodes($deleted) as $child) {
153
            $child->copyTo($new_parent, $conflict, $recursion, false, $deleted);
154
        }
155
156
        $this->_hook->run(
157
            'postCopyCollection',
158
            [$this, $parent, $new_parent, $conflict, $recursion, $recursion_first]
159
        );
160
161
        return $new_parent;
162
    }
163
164
    /**
165
     * Is mount.
166
     */
167
    public function isMounted(): bool
168
    {
169
        return count($this->mount) > 0;
170
    }
171
172
    /**
173
     * Get Share name.
174
     */
175
    public function getShareName(): string
176
    {
177
        if ($this->isShare()) {
178
            return $this->share_name;
179
        }
180
181
        return $this->_fs->findRawNode($this->getShareId())['share_name'];
0 ignored issues
show
Bug introduced by
It seems like $this->getShareId() targeting Balloon\Filesystem\Node\AbstractNode::getShareId() can also be of type boolean or null; however, Balloon\Filesystem::findRawNode() does only seem to accept object<MongoDB\BSON\ObjectId>, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
182
    }
183
184
    /**
185
     * Get Attributes.
186
     */
187
    public function getAttributes(): array
188
    {
189
        return [
190
            '_id' => $this->_id,
191
            'name' => $this->name,
192
            'shared' => $this->shared,
193
            'share_name' => $this->share_name,
194
            'acl' => $this->acl,
195
            'directory' => true,
196
            'reference' => $this->reference,
197
            'parent' => $this->parent,
198
            'app' => $this->app,
199
            'owner' => $this->owner,
200
            'meta' => $this->meta,
201
            'mime' => $this->mime,
202
            'filter' => $this->filter,
203
            'deleted' => $this->deleted,
204
            'changed' => $this->changed,
205
            'created' => $this->created,
206
            'destroy' => $this->destroy,
207
            'readonly' => $this->readonly,
208
            'mount' => $this->mount,
209
            'storage_reference' => $this->storage_reference,
210
            'storage' => $this->storage,
211
        ];
212
    }
213
214
    /**
215
     * Set collection filter.
216
     */
217
    public function setFilter(?array $filter = null): bool
218
    {
219
        $this->filter = json_encode($filter);
220
221
        return $this->save('filter');
222
    }
223
224
    /**
225
     * Get collection.
226
     */
227
    public function get(): void
228
    {
229
        $this->getZip();
230
    }
231
232
    /**
233
     * Fetch children items of this collection.
234
     *
235
     * Deleted:
236
     *  0 - Exclude deleted
237
     *  1 - Only deleted
238
     *  2 - Include deleted
239
     */
240
    public function getChildNodes(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = [], ?int $offset = null, ?int $limit = null, bool $recursive = false): Generator
241
    {
242
        $filter = $this->getChildrenFilter($deleted, $filter);
243
244
        if ($recursive === false) {
245
            return $this->_fs->findNodesByFilter($filter, $offset, $limit);
246
        }
247
248
        unset($filter['parent']);
249
250
        return $this->_fs->findNodesByFilterRecursive($this, $filter, $offset, $limit);
251
    }
252
253
    /**
254
     * Fetch children items of this collection (as array).
255
     *
256
     * Deleted:
257
     *  0 - Exclude deleted
258
     *  1 - Only deleted
259
     *  2 - Include deleted
260
     */
261
    public function getChildren(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
262
    {
263
        return iterator_to_array($this->getChildNodes($deleted, $filter));
264
    }
265
266
    /**
267
     * Is custom filter node.
268
     */
269
    public function isFiltered(): bool
270
    {
271
        return !empty($this->filter);
272
    }
273
274
    /**
275
     * Get number of children.
276
     */
277
    public function getSize(): int
278
    {
279
        return count($this->getChildren());
280
    }
281
282
    /**
283
     * Get real id (reference).
284
     *
285
     * @return ObjectId
286
     */
287
    public function getRealId(): ?ObjectId
288
    {
289
        if (true === $this->shared && $this->isReference()) {
290
            return $this->reference;
291
        }
292
293
        return $this->_id;
294
    }
295
296
    /**
297
     * Get user quota information.
298
     */
299
    public function getQuotaInfo(): array
300
    {
301
        $quota = $this->_user->getQuotaUsage();
302
303
        return [
304
            $quota['used'],
305
            $quota['available'],
306
        ];
307
    }
308
309
    /**
310
     * Fetch children items of this collection.
311
     */
312
    public function getChild($name, int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): NodeInterface
313
    {
314
        $name = $this->checkName($name);
315
        $filter = $this->getChildrenFilter($deleted, $filter);
316
        $filter['name'] = new Regex('^'.preg_quote($name).'$', 'i');
317
        $node = $this->_db->storage->findOne($filter);
318
319
        if (null === $node) {
320
            throw new Exception\NotFound(
321
                'node called '.$name.' does not exists here',
322
                Exception\NotFound::NODE_NOT_FOUND
323
            );
324
        }
325
326
        $this->_logger->debug('loaded node ['.$node['_id'].' from parent node ['.$this->getRealId().']', [
327
            'category' => get_class($this),
328
        ]);
329
330
        return $this->_fs->initNode($node);
331
    }
332
333
    /**
334
     * Delete node.
335
     *
336
     * Actually the node will not be deleted (Just set a delete flag), set $force=true to
337
     * delete finally
338
     */
339
    public function delete(bool $force = false, ?string $recursion = null, bool $recursion_first = true): bool
340
    {
341
        if (!$this->isReference() && !$this->_acl->isAllowed($this, 'w')) {
342
            throw new ForbiddenException(
343
                'not allowed to delete node '.$this->name,
344
                ForbiddenException::NOT_ALLOWED_TO_DELETE
345
            );
346
        }
347
348
        if (null === $recursion) {
349
            $recursion_first = true;
350
            $recursion = uniqid();
351
        } else {
352
            $recursion_first = false;
353
        }
354
355
        $this->_hook->run(
356
            'preDeleteCollection',
357
            [$this, &$force, &$recursion, &$recursion_first]
358
        );
359
360
        if (true === $force) {
361
            return $this->_forceDelete($recursion, $recursion_first);
362
        }
363
364
        $this->deleted = new UTCDateTime();
365
        $this->storage = $this->_parent->getStorage()->deleteCollection($this);
366
367
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
368
            $this->doRecursiveAction(function ($node) use ($recursion) {
369
                $node->delete(false, $recursion, false);
370
            }, NodeInterface::DELETED_EXCLUDE);
371
        }
372
373
        if (null !== $this->_id) {
374
            $result = $this->save([
375
                'deleted', 'storage',
376
            ], [], $recursion, false);
377
        } else {
378
            $result = true;
379
        }
380
381
        $this->_hook->run(
382
            'postDeleteCollection',
383
            [$this, $force, $recursion, $recursion_first]
384
        );
385
386
        return $result;
387
    }
388
389
    /**
390
     * Check if this collection has child named $name.
391
     *
392
     * deleted:
393
     *
394
     *  0 - Exclude deleted
395
     *  1 - Only deleted
396
     *  2 - Include deleted
397
     *
398
     * @param string $name
399
     * @param int    $deleted
400
     */
401
    public function childExists($name, $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): bool
402
    {
403
        $name = $this->checkName($name);
404
405
        $find = [
406
            'parent' => $this->getRealId(),
407
            'name' => new Regex('^'.preg_quote($name).'$', 'i'),
408
        ];
409
410
        if (null !== $this->_user) {
411
            $find['owner'] = $this->_user->getId();
412
        }
413
414
        switch ($deleted) {
415
            case NodeInterface::DELETED_EXCLUDE:
416
                $find['deleted'] = false;
417
418
                break;
419
            case NodeInterface::DELETED_ONLY:
420
                $find['deleted'] = ['$type' => 9];
421
422
                break;
423
        }
424
425
        $find = array_merge($filter, $find);
426
427
        if ($this->isSpecial()) {
428
            unset($find['owner']);
429
        }
430
431
        $node = $this->_db->storage->findOne($find);
432
433
        return (bool) $node;
434
    }
435
436
    /**
437
     * Share collection.
438
     */
439
    public function share(array $acl, string $name): bool
440
    {
441
        if ($this->isShareMember()) {
442
            throw new Exception('a sub node of a share can not be shared');
443
        }
444
445
        $this->checkName($name);
446
        $this->_acl->validateAcl($this->_server, $acl);
447
448
        $action = [
449
            '$set' => [
450
                'shared' => $this->getRealId(),
451
            ],
452
        ];
453
454
        $query = [
455
            '$or' => [
456
                ['reference' => ['exists' => true]],
457
                ['shared' => true],
458
            ],
459
        ];
460
461
        if (iterator_count($this->_fs->findNodesByFilterRecursive($this, $query, 0, 1)) !== 0) {
462
            throw new Exception\Conflict(
463
                'folder contains a shared folder',
464
                Exception\Conflict::NODE_CONTAINS_SHARED_NODE
465
            );
466
        }
467
468
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
469
        $this->_db->storage->updateMany([
470
            '_id' => [
471
                '$in' => $toset,
472
            ],
473
        ], $action);
474
475
        $this->_db->delta->updateMany([
476
            '_id' => [
477
                '$in' => $toset,
478
            ],
479
        ], $action);
480
481
        if ($this->getRealId() === $this->_id) {
482
            $this->acl = $acl;
483
            $this->shared = true;
484
            $this->share_name = $name;
485
            $this->save(['acl', 'shared', 'share_name']);
486
        } else {
487
            $this->_db->storage->updateOne([
488
                '_id' => $this->getRealId(),
489
            ], [
490
                '$set' => [
491
                    'share_name' => $name,
492
                    'acl' => $acl,
493
                ],
494
            ]);
495
        }
496
497
        return true;
498
    }
499
500
    /**
501
     * Unshare collection.
502
     */
503
    public function unshare(): Collection
504
    {
505
        if (!$this->_acl->isAllowed($this, 'm')) {
506
            throw new ForbiddenException(
507
                'not allowed to share node',
508
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
509
            );
510
        }
511
512
        if (true !== $this->shared) {
513
            throw new Exception\Conflict(
514
                'Can not unshare a none shared collection',
515
                Exception\Conflict::NOT_SHARED
516
            );
517
        }
518
519
        $real = $this->getRealId();
520
        $copy = null;
521
522
        if ($real !== $this->_id) {
523
            $copy = $this->copyTo($this->getParent(), NodeInterface::CONFLICT_RENAME);
524
        }
525
526
        $action = [
527
            '$set' => [
528
                'owner' => $this->_fs->findRawNode($real)['owner'],
529
                'shared' => false,
530
            ],
531
        ];
532
533
        $this->shared = false;
534
        $this->share_name = null;
535
        $this->acl = [];
536
537
        $toset = $this->_fs->findNodesByFilterRecursiveToArray($this);
538
        $this->_db->storage->updateMany([
539
            '_id' => [
540
                '$in' => $toset,
541
            ],
542
        ], $action);
543
544
        if ($real === $this->_id) {
545
            $result = $this->save(['shared'], ['acl', 'share_name']);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
546
        } else {
547
            $this->_db->storage->updateOne([
548
                '_id' => $real,
549
            ], [
550
                '$set' => [
551
                    'share_name' => null,
552
                    'acl' => [],
553
                    'shared' => false,
554
                ],
555
            ]);
556
557
            $this->delete();
558
            $copy->setName($this->getName());
0 ignored issues
show
Bug introduced by
Consider using $this->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
559
560
            return $copy;
561
        }
562
563
        return $this;
564
    }
565
566
    /**
567
     * Create new directory.
568
     */
569
    public function addDirectory($name, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): self
570
    {
571
        if (!$this->_acl->isAllowed($this, 'w')) {
572
            throw new ForbiddenException(
573
                'not allowed to create new node here',
574
                ForbiddenException::NOT_ALLOWED_TO_CREATE
575
            );
576
        }
577
578
        $this->_hook->run('preCreateCollection', [$this, &$name, &$attributes, &$clone]);
579
        $name = $this->validateInsert($name, $conflict, Collection::class);
580
        $id = new ObjectId();
581
582
        try {
583
            $meta = [
584
                '_id' => $id,
585
                'pointer' => $id,
586
                'name' => $name,
587
                'deleted' => false,
588
                'parent' => $this->getRealId(),
589
                'directory' => true,
590
                'created' => new UTCDateTime(),
591
                'changed' => new UTCDateTime(),
592
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
593
                'storage' => $this->_storage->createCollection($this, $name),
594
                'storage_reference' => $this->getMount(),
595
            ];
596
597
            if (null !== $this->_user) {
598
                $meta['owner'] = $this->_user->getId();
599
            }
600
601
            $save = array_merge($meta, $attributes);
602
603
            if (isset($save['filter'])) {
604
                $this->validateFilter($save['filter']);
605
            }
606
607
            if (isset($save['acl'])) {
608
                $this->validateAcl($save['acl']);
609
            }
610
611
            $result = $this->_db->storage->insertOne($save);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
612
613
            $this->_logger->info('added new collection ['.$save['_id'].'] under parent ['.$this->_id.']', [
614
                'category' => get_class($this),
615
            ]);
616
617
            $this->changed = $save['changed'];
618
            $this->save('changed');
619
620
            $new = $this->_fs->initNode($save);
621
            $this->_hook->run('postCreateCollection', [$this, $new, $clone]);
622
623
            return $new;
624
        } catch (\Exception $e) {
625
            $this->_logger->error('failed create new collection under parent ['.$this->_id.']', [
626
                'category' => get_class($this),
627
                'exception' => $e,
628
            ]);
629
630
            throw $e;
631
        }
632
    }
633
634
    /**
635
     * Create new file as a child from this collection.
636
     */
637
    public function addFile($name, ?ObjectId $session = null, array $attributes = [], int $conflict = NodeInterface::CONFLICT_NOACTION, bool $clone = false): File
638
    {
639
        if (!$this->_acl->isAllowed($this, 'w')) {
640
            throw new ForbiddenException(
641
                'not allowed to create new node here',
642
                ForbiddenException::NOT_ALLOWED_TO_CREATE
643
            );
644
        }
645
646
        $this->_hook->run('preCreateFile', [$this, &$name, &$attributes, &$clone]);
647
        $name = $this->validateInsert($name, $conflict, File::class);
648
        $id = new ObjectId();
649
650
        try {
651
            $meta = [
652
                '_id' => $id,
653
                'pointer' => $id,
654
                'name' => $name,
655
                'deleted' => false,
656
                'parent' => $this->getRealId(),
657
                'directory' => false,
658
                'hash' => null,
659
                'mime' => MimeType::getType($name),
660
                'created' => new UTCDateTime(),
661
                'changed' => new UTCDateTime(),
662
                'version' => 0,
663
                'shared' => (true === $this->shared ? $this->getRealId() : $this->shared),
664
                'storage_reference' => $this->getMount(),
665
            ];
666
667
            if (null !== $this->_user) {
668
                $meta['owner'] = $this->_user->getId();
669
            }
670
671
            $save = array_merge($meta, $attributes);
672
673
            if (isset($save['acl'])) {
674
                $this->validateAcl($save['acl']);
675
            }
676
677
            $result = $this->_db->storage->insertOne($save);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
678
679
            $this->_logger->info('added new file ['.$save['_id'].'] under parent ['.$this->_id.']', [
680
                'category' => get_class($this),
681
            ]);
682
683
            $this->changed = $save['changed'];
684
            $this->save('changed');
685
686
            $file = $this->_fs->initNode($save);
687
688
            if ($session !== null) {
689
                $file->setContent($session, $attributes);
690
            }
691
692
            $this->_hook->run('postCreateFile', [$this, $file, $clone]);
693
694
            return $file;
695
        } catch (\Exception $e) {
696
            $this->_logger->error('failed add new file under parent ['.$this->_id.']', [
697
                'category' => get_class($this),
698
                'exception' => $e,
699
            ]);
700
701
            throw $e;
702
        }
703
    }
704
705
    /**
706
     * Create new file wrapper
707
     * (Sabe\DAV compatible method, elsewhere use addFile().
708
     *
709
     * Sabre\DAV requires that createFile() returns the ETag instead the newly created file instance
710
     *
711
     * @param string $name
712
     * @param string $data
713
     */
714
    public function createFile($name, $data = null): string
715
    {
716
        $session = $this->_storage->storeTemporaryFile($data, $this->_user);
717
718
        if ($this->childExists($name, NodeInterface::DELETED_INCLUDE, ['directory' => false])) {
719
            $file = $this->getChild($name, NodeInterface::DELETED_INCLUDE, ['directory' => false]);
720
            $file->setContent($session);
721
        } else {
722
            $file = $this->addFile($name, $session);
723
        }
724
725
        return $file->getETag();
0 ignored issues
show
Bug introduced by
The method getETag does only exist in Balloon\Filesystem\Node\File, but not in Balloon\Filesystem\Node\NodeInterface.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
726
    }
727
728
    /**
729
     * Create new directory wrapper
730
     * (Sabe\DAV compatible method, elsewhere use addDirectory().
731
     *
732
     * Sabre\DAV requires that createDirectory() returns void
733
     *
734
     * @param string $name
735
     */
736
    public function createDirectory($name): void
737
    {
738
        $this->addDirectory($name);
739
    }
740
741
    /**
742
     * Do recursive Action.
743
     */
744
    public function doRecursiveAction(callable $callable, int $deleted = NodeInterface::DELETED_EXCLUDE): bool
745
    {
746
        $children = $this->getChildNodes($deleted, []);
747
748
        foreach ($children as $child) {
749
            $callable($child);
750
        }
751
752
        return true;
753
    }
754
755
    /**
756
     * Validate insert.
757
     */
758
    protected function validateInsert(string $name, int $conflict = NodeInterface::CONFLICT_NOACTION, string $type = Collection::class): string
759
    {
760
        if ($this->readonly) {
761
            throw new Exception\Conflict(
762
                'node is set as readonly, it is not possible to add new sub nodes',
763
                Exception\Conflict::READONLY
764
            );
765
        }
766
767
        $name = $this->checkName($name);
768
769
        if ($this->childExists($name)) {
770
            if (NodeInterface::CONFLICT_NOACTION === $conflict) {
771
                throw new Exception\Conflict(
772
                    'a node called '.$name.' does already exists in this collection',
773
                    Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS
774
                );
775
            }
776
            if (NodeInterface::CONFLICT_RENAME === $conflict) {
777
                $name = $this->getDuplicateName($name, $type);
778
            }
779
        }
780
781
        if ($this->isFiltered()) {
782
            throw new Exception\Conflict(
783
                'could not add node '.$name.' into a filtered parent collection',
784
                Exception\Conflict::DYNAMIC_PARENT
785
            );
786
        }
787
788
        if ($this->isDeleted()) {
789
            throw new Exception\Conflict(
790
                'could not add node '.$name.' into a deleted parent collection',
791
                Exception\Conflict::DELETED_PARENT
792
            );
793
        }
794
795
        return $name;
796
    }
797
798
    /**
799
     * Validate filtered collection query.
800
     */
801
    protected function validateFilter(string $filter): bool
802
    {
803
        $filter = toPHP(fromJSON($filter), [
804
            'root' => 'array',
805
            'document' => 'array',
806
            'array' => 'array',
807
        ]);
808
809
        $this->_db->storage->findOne($filter);
810
811
        return true;
812
    }
813
814
    /**
815
     * Validate acl.
816
     */
817
    protected function validateAcl(array $acl): bool
818
    {
819
        if (!$this->_acl->isAllowed($this, 'm')) {
820
            throw new ForbiddenException(
821
                'not allowed to set acl',
822
                ForbiddenException::NOT_ALLOWED_TO_MANAGE
823
            );
824
        }
825
826
        if (!$this->isSpecial()) {
827
            throw new Exception\Conflict('node acl may only be set on share member nodes', Exception\Conflict::NOT_SHARED);
828
        }
829
830
        $this->_acl->validateAcl($this->_server, $acl);
831
832
        return true;
833
    }
834
835
    /**
836
     * Get children query filter.
837
     *
838
     * Deleted:
839
     *  0 - Exclude deleted
840
     *  1 - Only deleted
841
     *  2 - Include deleted
842
     */
843
    protected function getChildrenFilter(int $deleted = NodeInterface::DELETED_EXCLUDE, array $filter = []): array
844
    {
845
        $search = [
846
            'parent' => $this->getRealId(),
847
        ];
848
849
        if (NodeInterface::DELETED_EXCLUDE === $deleted) {
850
            $search['deleted'] = false;
851
        } elseif (NodeInterface::DELETED_ONLY === $deleted) {
852
            $search['deleted'] = ['$type' => 9];
853
        }
854
855
        $search = array_merge($filter, $search);
856
857
        if ($this->shared) {
858
            $search = [
859
                '$and' => [
860
                    $search,
861
                    [
862
                        '$or' => [
863
                            ['shared' => $this->reference],
864
                            ['shared' => $this->shared],
865
                            ['shared' => $this->_id],
866
                        ],
867
                    ],
868
                ],
869
            ];
870
        } elseif (null !== $this->_user) {
871
            $search['owner'] = $this->_user->getId();
872
        }
873
874
        if ($this->filter !== null && $this->_user !== null) {
875
            $stored = toPHP(fromJSON($this->filter), [
876
                'root' => 'array',
877
                'document' => 'array',
878
                'array' => 'array',
879
            ]);
880
881
            $include = isset($search['deleted']) ? ['deleted' => $search['deleted']] : [];
882
            $stored_filter = ['$and' => [
883
                array_merge(
884
                    $include,
885
                    $stored,
886
                    $filter
887
                ),
888
                ['$or' => [
889
                    ['owner' => $this->_user->getId()],
890
                    ['shared' => ['$in' => $this->_user->getShares()]],
891
                ]],
892
                [
893
                    '_id' => ['$ne' => $this->_id],
894
                ],
895
            ]];
896
897
            $search = ['$or' => [
898
                $search,
899
                $stored_filter,
900
            ]];
901
        }
902
903
        return $search;
904
    }
905
906
    /**
907
     * Completely remove node.
908
     */
909
    protected function _forceDelete(?string $recursion = null, bool $recursion_first = true): bool
910
    {
911
        if (!$this->isReference() && !$this->isMounted() && !$this->isFiltered()) {
912
            $this->doRecursiveAction(function ($node) use ($recursion) {
913
                $node->delete(true, $recursion, false);
914
            }, NodeInterface::DELETED_INCLUDE);
915
        }
916
917
        try {
918
            $this->_parent->getStorage()->forceDeleteCollection($this);
919
            $result = $this->_db->storage->deleteOne(['_id' => $this->_id]);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
920
921
            if ($this->isShared()) {
922
                $result = $this->_db->storage->deleteMany(['reference' => $this->_id]);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
923
            }
924
925
            $this->_logger->info('force removed collection ['.$this->_id.']', [
926
                'category' => get_class($this),
927
            ]);
928
929
            $this->_hook->run(
930
                'postDeleteCollection',
931
                [$this, true, $recursion, $recursion_first]
932
            );
933
        } catch (\Exception $e) {
934
            $this->_logger->error('failed force remove collection ['.$this->_id.']', [
935
                'category' => get_class($this),
936
                'exception' => $e,
937
            ]);
938
939
            throw $e;
940
        }
941
942
        return true;
943
    }
944
}
945