Test Failed
Push — master ( 8c814b...380005 )
by Raffael
08:49
created

Files::put()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 0
cts 19
cp 0
rs 9.568
c 0
b 0
f 0
cc 1
nc 1
nop 9
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\App\Api\v2;
13
14
use Balloon\Filesystem\Acl\Exception\Forbidden as ForbiddenException;
15
use Balloon\Filesystem\Exception;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Balloon\App\Api\v2\Exception.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
use Balloon\Filesystem\Node\Collection;
17
use Balloon\Filesystem\Node\File;
18
use Balloon\Filesystem\Node\NodeInterface;
19
use Balloon\Filesystem\Storage\Adapter\AdapterInterface as StorageAdapterInterface;
20
use Balloon\Server\AttributeDecorator as RoleAttributeDecorator;
21
use Micro\Http\Response;
22
use MongoDB\BSON\ObjectId;
23
24
class Files extends Nodes
25
{
26
    /**
27
     * Get history.
28
     */
29
    public function getHistory(RoleAttributeDecorator $role_decorator, string $id): Response
30
    {
31
        $result = $this->fs->getNode($id, File::class)->getHistory();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Balloon\Filesystem\Node\NodeInterface as the method getHistory() does only exist in the following implementations of said interface: Balloon\Filesystem\Node\File.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
Deprecated Code introduced by
The method Balloon\Filesystem::getNode() has been deprecated.

This method has been deprecated.

Loading history...
32
        $body = [];
33
        foreach ($result as $version) {
34
            if ($version['user'] === null) {
35
                $user = null;
36
            } else {
37
                $user = $this->server->getUserById($version['user']);
38
                $user = $role_decorator->decorate($user, ['id', 'name', '_links']);
39
            }
40
41
            $body[] = [
42
                'version' => $version['version'],
43
                'changed' => $version['changed']->toDateTime()->format('c'),
44
                'type' => $version['type'],
45
                'size' => $version['size'],
46
                'user' => $user,
47
            ];
48
        }
49
50
        return (new Response())->setCode(200)->setBody(['data' => $body]);
51
    }
52
53
    /**
54
     * Rollback version.
55
     */
56
    public function postRestore(int $version, string $id): Response
57
    {
58
        $node = $this->fs->getNode($id, File::class);
0 ignored issues
show
Deprecated Code introduced by
The method Balloon\Filesystem::getNode() has been deprecated.

This method has been deprecated.

Loading history...
59
        $node->restore($version);
60
        $result = $this->node_decorator->decorate($node);
61
62
        return (new Response())->setCode(200)->setBody($result);
63
    }
64
65
    /**
66
     * Upload file chunk.
67
     */
68
    public function putChunk(
69
        ?ObjectId $session = null,
70
        ?string $id = null,
71
        ?string $collection = null,
72
        ?string $name = null,
73
        int $index = 1,
74
        int $chunks = 0,
75
        int $size = 0,
0 ignored issues
show
Unused Code introduced by
The parameter $size is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
76
        int $conflict = 0,
77
        ?string $changed = null,
78
        ?string $created = null,
79
        ?bool $readonly = null,
80
        ?array $meta = null,
81
        ?array $acl = null
82
    ) {
83
        ini_set('auto_detect_line_endings', '1');
84
        $input = fopen('php://input', 'rb');
85
        if ($index > $chunks) {
86
            throw new Exception\InvalidArgument('chunk index can not be greater than the total number of chunks');
87
        }
88
89
        $storage = $this->getStorage($id, $collection);
90
91
        if ($session === null) {
92
            $session = $storage->storeTemporaryFile($input, $this->server->getIdentity());
93
        } else {
94
            $storage->storeTemporaryFile($input, $this->server->getIdentity(), $session);
95
        }
96
97
        if ($index === $chunks) {
98
            $attributes = compact('changed', 'created', 'readonly', 'meta', 'acl');
99
            $attributes = array_filter($attributes, function ($attribute) {return !is_null($attribute); });
100
            $attributes = $this->_verifyAttributes($attributes);
101
102
            return $this->_put($session, $id, $collection, $name, $attributes, $conflict);
103
        }
104
105
        return (new Response())->setCode(206)->setBody([
106
                'session' => (string) $session,
107
                'chunks_left' => $chunks - $index,
108
            ]);
109
    }
110
111
    /**
112
     * Upload file.
113
     */
114
    public function put(
115
        ?string $id = null,
116
        ?string $collection = null,
117
        ?string $name = null,
118
        int $conflict = 0,
119
        ?string $changed = null,
120
        ?string $created = null,
121
        ?bool $readonly = null,
122
        ?array $meta = null,
123
        ?array $acl = null
124
    ): Response {
125
        ini_set('auto_detect_line_endings', '1');
126
        $input = fopen('php://input', 'rb');
127
128
        $storage = $this->getStorage($id, $collection);
129
        $session = $storage->storeTemporaryFile($input, $this->server->getIdentity());
130
        $attributes = compact('changed', 'created', 'readonly', 'meta', 'acl');
131
        $attributes = array_filter($attributes, function ($attribute) {return !is_null($attribute); });
132
        $attributes = $this->_verifyAttributes($attributes);
133
134
        return $this->_put($session, $id, $collection, $name, $attributes, $conflict);
135
    }
136
137
    /**
138
     * Get storage.
139
     */
140
    protected function getStorage($id, $collection): StorageAdapterInterface
141
    {
142
        if ($id !== null) {
143
            return $this->_getNode($id)->getParent()->getStorage();
144
        }
145
146
        if ($id === null && $collection === null) {
147
            return $this->server->getFilesystem()->getRoot()->getStorage();
148
        }
149
150
        return $this->fs->getNode($collection, Collection::class)->getStorage();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Balloon\Filesystem\Node\NodeInterface as the method getStorage() does only exist in the following implementations of said interface: Balloon\Filesystem\Node\Collection.

Let’s take a look at an example:

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

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

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

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

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

Available Fixes

  1. Change the type-hint for the parameter:

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

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

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
Deprecated Code introduced by
The method Balloon\Filesystem::getNode() has been deprecated.

This method has been deprecated.

Loading history...
151
    }
152
153
    /**
154
     * Add or update file.
155
     */
156
    protected function _put(
157
        ObjectId $session,
158
        ?string $id = null,
159
        ?string $collection = null,
160
        ?string $name = null,
161
        array $attributes = [],
162
        int $conflict = 0
0 ignored issues
show
Unused Code introduced by
The parameter $conflict is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
163
    ): Response {
164
        if (null === $id && null === $name) {
165
            throw new Exception\InvalidArgument('neither id nor name was set');
166
        }
167
168
        try {
169
            if (null !== $id && null === $collection) {
170
                $node = $this->_getNode($id);
171
                $node->setContent($session, $attributes);
172
                $result = $this->node_decorator->decorate($node);
173
174
                return (new Response())->setCode(200)->setBody($result);
175
            }
176
            if (null === $id && null !== $name) {
177
                $collection = $this->fs->getNode($collection, Collection::class, false, true);
0 ignored issues
show
Deprecated Code introduced by
The method Balloon\Filesystem::getNode() has been deprecated.

This method has been deprecated.

Loading history...
178
179
                if ($collection->childExists($name, NodeInterface::DELETED_INCLUDE, ['directory' => false])) {
180
                    $child = $collection->getChild($name, NodeInterface::DELETED_INCLUDE, ['directory' => false]);
181
                    $child->setContent($session, $attributes);
182
                    $result = $this->node_decorator->decorate($child);
183
184
                    return (new Response())->setCode(200)->setBody($result);
185
                }
186
187
                if (!is_string($name) || empty($name)) {
188
                    throw new Exception\InvalidArgument('name must be a valid string');
189
                }
190
191
                $result = $collection->addFile($name, $session, $attributes);
192
                $result = $this->node_decorator->decorate($result);
193
194
                return (new Response())->setCode(201)->setBody($result);
195
            }
196
        } catch (ForbiddenException $e) {
197
            throw new Exception\Conflict(
198
                'a node called '.$name.' does already exists in this collection',
199
                Exception\Conflict::NODE_WITH_SAME_NAME_ALREADY_EXISTS,
200
                $e
201
            );
202
        }
203
    }
204
}
205