Completed
Pull Request — master (#29)
by
unknown
03:54
created

WebDAVAdapter   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 369
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 7

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 7
dl 0
loc 369
rs 8.6206
c 0
b 0
f 0

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A encodePath() 0 8 2
A getMetadata() 0 14 3
A has() 0 4 1
B read() 0 22 4
A write() 0 21 4
A update() 0 4 1
A rename() 0 19 4
A copy() 0 8 2
A delete() 0 12 2
B createDir() 0 28 6
A deleteDir() 0 4 1
A listContents() 0 20 4
A getSize() 0 4 1
A getTimestamp() 0 4 1
A getMimetype() 0 4 1
A getUseStreamedCopy() 0 4 1
A setUseStreamedCopy() 0 4 1
B nativeCopy() 0 24 5
A normalizeObject() 0 17 3
A isDirectory() 0 4 2

How to fix   Complexity   

Complex Class

Complex classes like WebDAVAdapter often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WebDAVAdapter, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace League\Flysystem\WebDAV;
4
5
use League\Flysystem\Adapter\AbstractAdapter;
6
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
7
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
8
use League\Flysystem\Adapter\Polyfill\StreamedTrait;
9
use League\Flysystem\Config;
10
use League\Flysystem\Util;
11
use LogicException;
12
use Sabre\DAV\Client;
13
use Sabre\DAV\Exception;
14
use Sabre\DAV\Exception\NotFound;
15
use Sabre\HTTP\ClientHttpException;
16
use Sabre\HTTP\HttpException;
17
18
class WebDAVAdapter extends AbstractAdapter
19
{
20
    const METADATA_FIELDS = [
21
        '{DAV:}displayname',
22
        '{DAV:}getcontentlength',
23
        '{DAV:}getcontenttype',
24
        '{DAV:}getlastmodified',
25
        '{DAV:}iscollection',
26
    ];
27
    use StreamedTrait;
28
    use StreamedCopyTrait {
29
        StreamedCopyTrait::copy as streamedCopy;
30
    }
31
    use NotSupportingVisibilityTrait;
32
33
    /**
34
     * @var array
35
     */
36
    protected static $resultMap = [
37
        '{DAV:}getcontentlength' => 'size',
38
        '{DAV:}getcontenttype' => 'mimetype',
39
        'content-length' => 'size',
40
        'content-type' => 'mimetype',
41
    ];
42
43
    /**
44
     * @var Client
45
     */
46
    protected $client;
47
48
    /**
49
     * @var bool
50
     */
51
    protected $useStreamedCopy = true;
52
53
    /**
54
     * Constructor.
55
     *
56
     * @param Client $client
57
     * @param string $prefix
58
     * @param bool $useStreamedCopy
59
     */
60
    public function __construct(Client $client, $prefix = null, $useStreamedCopy = true)
61
    {
62
        $this->client = $client;
63
        $this->setPathPrefix($prefix);
64
        $this->setUseStreamedCopy($useStreamedCopy);
65
    }
66
67
    /**
68
     * url encode a path
69
     *
70
     * @param string $path
71
     *
72
     * @return string
73
     */
74
    protected function encodePath($path)
75
	{
76
		$a = explode('/', $path);
77
		for ($i=0; $i<count($a); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
78
			$a[$i] = rawurlencode($a[$i]);
79
		}
80
		return implode('/', $a);
81
	}
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function getMetadata($path)
87
    {
88
        $location = $this->applyPathPrefix($this->encodePath($path));
89
90
        try {
91
            $result = $this->client->propFind($location, self::METADATA_FIELDS);
92
93
            return $this->normalizeObject($result, $path);
94
        } catch (Exception $e) {
95
            return false;
96
        } catch (HttpException $e) {
97
            return false;
98
        }
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function has($path)
105
    {
106
        return $this->getMetadata($path);
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function read($path)
113
    {
114
        $location = $this->applyPathPrefix($this->encodePath($path));
115
116
        try {
117
            $response = $this->client->request('GET', $location);
118
119
            if ($response['statusCode'] !== 200) {
120
                return false;
121
            }
122
123
            return array_merge([
124
                'contents' => $response['body'],
125
                'timestamp' => strtotime(is_array($response['headers']['last-modified'])
126
                    ? current($response['headers']['last-modified'])
127
                    : $response['headers']['last-modified']),
128
                'path' => $path,
129
            ], Util::map($response['headers'], static::$resultMap));
130
        } catch (Exception $e) {
131
            return false;
132
        }
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function write($path, $contents, Config $config)
139
    {
140
        if (!$this->createDir(Util::dirname($path), $config)) {
141
            return false;
142
        }
143
144
        $location = $this->applyPathPrefix($this->encodePath($path));
145
        $response = $this->client->request('PUT', $location, $contents);
146
147
        if ($response['statusCode'] >= 400) {
148
            return false;
149
        }
150
151
        $result = compact('path', 'contents');
152
153
        if ($config->get('visibility')) {
154
            throw new LogicException(__CLASS__.' does not support visibility settings.');
155
        }
156
157
        return $result;
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function update($path, $contents, Config $config)
164
    {
165
        return $this->write($path, $contents, $config);
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function rename($path, $newpath)
172
    {
173
        $location = $this->applyPathPrefix($this->encodePath($path));
174
        $newLocation = $this->applyPathPrefix($this->encodePath($newpath));
175
176
        try {
177
            $response = $this->client->request('MOVE', '/'.ltrim($location, '/'), null, [
178
                'Destination' => '/'.ltrim($newLocation, '/'),
179
            ]);
180
181
            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
182
                return true;
183
            }
184
        } catch (NotFound $e) {
185
            // Would have returned false here, but would be redundant
186
        }
187
188
        return false;
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function copy($path, $newpath)
195
    {
196
        if ($this->useStreamedCopy === true) {
197
            return $this->streamedCopy($path, $newpath);
198
        } else {
199
            return $this->nativeCopy($path, $newpath);
200
        }
201
    }
202
203
    /**
204
     * {@inheritdoc}
205
     */
206
    public function delete($path)
207
    {
208
        $location = $this->applyPathPrefix($this->encodePath($path));
209
210
        try {
211
            $this->client->request('DELETE', $location);
212
213
            return true;
214
        } catch (NotFound $e) {
215
            return false;
216
        }
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    public function createDir($path, Config $config)
223
    {
224
        $encodedPath = $this->encodePath($path);
225
        $path = trim($path, '/');
226
227
        $result = compact('path') + ['type' => 'dir'];
228
229
        if (Util::normalizeDirname($path) === '' || $this->has($path)) {
230
            return $result;
231
        }
232
233
        $directories = explode('/', $path);
234
        if (count($directories) > 1) {
235
            $parentDirectories = array_splice($directories, 0, count($directories) - 1);
236
            if (!$this->createDir(implode('/', $parentDirectories), $config)) {
237
                return false;
238
            }
239
        }
240
241
        $location = $this->applyPathPrefix($encodedPath);
242
        $response = $this->client->request('MKCOL', $location);
243
244
        if ($response['statusCode'] !== 201) {
245
            return false;
246
        }
247
248
        return $result;
249
    }
250
251
    /**
252
     * {@inheritdoc}
253
     */
254
    public function deleteDir($dirname)
255
    {
256
        return $this->delete($dirname);
257
    }
258
259
    /**
260
     * {@inheritdoc}
261
     */
262
    public function listContents($directory = '', $recursive = false)
263
    {
264
        $location = $this->applyPathPrefix($this->encodePath($directory));
265
        $response = $this->client->propFind($location . '/', self::METADATA_FIELDS, 1);
266
267
        array_shift($response);
268
        $result = [];
269
270
        foreach ($response as $path => $object) {
271
            $path = urldecode($this->removePathPrefix($path));
272
            $object = $this->normalizeObject($object, $path);
273
            $result[] = $object;
274
275
            if ($recursive && $object['type'] === 'dir') {
276
                $result = array_merge($result, $this->listContents($object['path'], true));
277
            }
278
        }
279
280
        return $result;
281
    }
282
283
    /**
284
     * {@inheritdoc}
285
     */
286
    public function getSize($path)
287
    {
288
        return $this->getMetadata($path);
289
    }
290
291
    /**
292
     * {@inheritdoc}
293
     */
294
    public function getTimestamp($path)
295
    {
296
        return $this->getMetadata($path);
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    public function getMimetype($path)
303
    {
304
        return $this->getMetadata($path);
305
    }
306
307
    /**
308
     * @return boolean
309
     */
310
    public function getUseStreamedCopy()
311
    {
312
        return $this->useStreamedCopy;
313
    }
314
315
    /**
316
     * @param boolean $useStreamedCopy
317
     */
318
    public function setUseStreamedCopy($useStreamedCopy)
319
    {
320
        $this->useStreamedCopy = (bool)$useStreamedCopy;
321
    }
322
323
    /**
324
     * Copy a file through WebDav COPY method.
325
     *
326
     * @param string $path
327
     * @param string $newPath
328
     *
329
     * @return bool
330
     */
331
    protected function nativeCopy($path, $newPath)
332
    {
333
        if (!$this->createDir(Util::dirname($newPath), new Config())) {
334
            return false;
335
        }
336
337
        $location = $this->applyPathPrefix($this->encodePath($path));
338
        $newLocation = $this->applyPathPrefix($this->encodePath($newPath));
339
340
        try {
341
            $destination = $this->client->getAbsoluteUrl($newLocation);
342
            $response = $this->client->request('COPY', '/'.ltrim($location, '/'), null, [
343
                'Destination' => $destination,
344
            ]);
345
346
            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
347
                return true;
348
            }
349
        } catch (NotFound $e) {
350
            // Would have returned false here, but would be redundant
351
        }
352
353
        return false;
354
    }
355
356
    /**
357
     * Normalise a WebDAV repsonse object.
358
     *
359
     * @param array  $object
360
     * @param string $path
361
     *
362
     * @return array
363
     */
364
    protected function normalizeObject(array $object, $path)
365
    {
366
        if ($this->isDirectory($object)) {
367
            return ['type' => 'dir', 'path' => trim($path, '/')];
368
        }
369
370
        $result = Util::map($object, static::$resultMap);
371
372
        if (isset($object['{DAV:}getlastmodified'])) {
373
            $result['timestamp'] = strtotime($object['{DAV:}getlastmodified']);
374
        }
375
376
        $result['type'] = 'file';
377
        $result['path'] = trim($path, '/');
378
379
        return $result;
380
    }
381
382
    private function isDirectory(array $object)
383
    {
384
        return isset($object['{DAV:}iscollection']) && $object['{DAV:}iscollection'] === '1';
385
    }
386
}
387