Completed
Pull Request — master (#24)
by
unknown
04:08
created

AzureAdapter::getUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
namespace League\Flysystem\Azure;
4
5
use League\Flysystem\Adapter\AbstractAdapter;
6
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
7
use League\Flysystem\Config;
8
use League\Flysystem\Util;
9
use MicrosoftAzure\Storage\Blob\Internal\IBlob;
10
use MicrosoftAzure\Storage\Blob\Models\BlobPrefix;
11
use MicrosoftAzure\Storage\Blob\Models\BlobProperties;
12
use MicrosoftAzure\Storage\Blob\Models\CopyBlobResult;
13
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
14
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
15
use MicrosoftAzure\Storage\Blob\Models\ListBlobsResult;
16
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;
17
18
class AzureAdapter extends AbstractAdapter
19
{
20
    use NotSupportingVisibilityTrait;
21
22
    /**
23
     * @var string
24
     */
25
    protected $container;
26
27
    /**
28
     * @var IBlob
29
     */
30
    protected $client;
31
32
    /**
33
     * @var string[]
34
     */
35
    protected static $metaOptions = [
36
        'CacheControl',
37
        'ContentType',
38
        'Metadata',
39
        'ContentLanguage',
40
        'ContentEncoding',
41
    ];
42
43
    /**
44
     * Constructor.
45
     *
46
     * @param IBlob  $azureClient
47
     * @param string $container
48
     * @param string $prefix
49
     */
50
    public function __construct(IBlob $azureClient, $container, $prefix = null)
51
    {
52
        $this->client = $azureClient;
53
        $this->container = $container;
54
        $this->setPathPrefix($prefix);
55
    }
56
57
    /**
58
     * {@inheritdoc}
59
     */
60
    public function write($path, $contents, Config $config)
61
    {
62
        return $this->upload($path, $contents, $config);
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68
    public function writeStream($path, $resource, Config $config)
69
    {
70
        return $this->upload($path, $resource, $config);
71
    }
72
73
    /**
74
     * {@inheritdoc}
75
     */
76
    public function update($path, $contents, Config $config)
77
    {
78
        return $this->upload($path, $contents, $config);
79
    }
80
81
    /**
82
     * {@inheritdoc}
83
     */
84
    public function updateStream($path, $resource, Config $config)
85
    {
86
        return $this->upload($path, $resource, $config);
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function rename($path, $newpath)
93
    {
94
        $this->copy($path, $newpath);
95
96
        return $this->delete($path);
97
    }
98
99
    public function copy($path, $newpath)
100
    {
101
        $path = $this->applyPathPrefix($path);
102
        $newpath = $this->applyPathPrefix($newpath);
103
104
        $this->client->copyBlob($this->container, $newpath, $this->container, $path);
105
106
        return true;
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function delete($path)
113
    {
114
        $path = $this->applyPathPrefix($path);
115
116
        $this->client->deleteBlob($this->container, $path);
117
118
        return true;
119
    }
120
121
    /**
122
     * {@inheritdoc}
123
     */
124
    public function deleteDir($dirname)
125
    {
126
        $dirname = $this->applyPathPrefix($dirname);
127
128
        $options = new ListBlobsOptions();
129
        $options->setPrefix($dirname . '/');
130
131
        /** @var ListBlobsResult $listResults */
132
        $listResults = $this->client->listBlobs($this->container, $options);
133
134
        foreach ($listResults->getBlobs() as $blob) {
135
            /** @var \MicrosoftAzure\Storage\Blob\Models\Blob $blob */
136
            $this->client->deleteBlob($this->container, $blob->getName());
137
        }
138
139
        return true;
140
    }
141
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function createDir($dirname, Config $config)
146
    {
147
        $this->write(rtrim($dirname, '/') . '/', ' ', $config);
148
149
        return ['path' => $dirname, 'type' => 'dir'];
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155
    public function has($path)
156
    {
157
        $path = $this->applyPathPrefix($path);
158
159
        try {
160
            $this->client->getBlobMetadata($this->container, $path);
161
        } catch (ServiceException $e) {
162
            if ($e->getCode() !== 404) {
163
                throw $e;
164
            }
165
166
            return false;
167
        }
168
169
        return true;
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 View Code Duplication
    public function read($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
    {
177
        $path = $this->applyPathPrefix($path);
178
179
        /** @var \MicrosoftAzure\Storage\Blob\Models\GetBlobResult $blobResult */
180
        $blobResult = $this->client->getBlob($this->container, $path);
181
        $properties = $blobResult->getProperties();
182
        $content = $this->streamContentsToString($blobResult->getContentStream());
183
184
        return $this->normalizeBlobProperties($path, $properties) + ['contents' => $content];
185
    }
186
187
    /**
188
     * {@inheritdoc}
189
     */
190 View Code Duplication
    public function readStream($path)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
    {
192
        $path = $this->applyPathPrefix($path);
193
194
        /** @var \MicrosoftAzure\Storage\Blob\Models\GetBlobResult $blobResult */
195
        $blobResult = $this->client->getBlob($this->container, $path);
196
        $properties = $blobResult->getProperties();
197
198
        return $this->normalizeBlobProperties($path, $properties) + ['stream' => $blobResult->getContentStream()];
199
    }
200
201
    /**
202
     * {@inheritdoc}
203
     */
204
    public function listContents($directory = '', $recursive = false)
205
    {
206
        $directory = $this->applyPathPrefix($directory);
207
208
        // Append trailing slash only for directory other than root (which after normalization is an empty string).
209
        // Listing for / doesn't work properly otherwise.
210
        if (strlen($directory)) {
211
            $directory = rtrim($directory, '/') . '/';
212
        }
213
214
        $options = new ListBlobsOptions();
215
        $options->setPrefix($directory);
216
217
        if ( ! $recursive) {
218
            $options->setDelimiter('/');
219
        }
220
221
        /** @var ListBlobsResult $listResults */
222
        $listResults = $this->client->listBlobs($this->container, $options);
223
224
        $contents = [];
225
226
        foreach ($listResults->getBlobs() as $blob) {
227
            $contents[] = $this->normalizeBlobProperties($blob->getName(), $blob->getProperties());
228
        }
229
230
        if ( ! $recursive) {
231
            $contents = array_merge(
232
                $contents,
233
                array_map([$this, 'normalizeBlobPrefix'], $listResults->getBlobPrefixes())
234
            );
235
        }
236
237
        return Util::emulateDirectories($contents);
238
    }
239
240
    /**
241
     * {@inheritdoc}
242
     */
243
    public function getMetadata($path)
244
    {
245
        $path = $this->applyPathPrefix($path);
246
247
        /** @var \MicrosoftAzure\Storage\Blob\Models\GetBlobPropertiesResult $result */
248
        $result = $this->client->getBlobProperties($this->container, $path);
249
250
        return $this->normalizeBlobProperties($path, $result->getProperties());
251
    }
252
253
    /**
254
     * {@inheritdoc}
255
     */
256
    public function getSize($path)
257
    {
258
        return $this->getMetadata($path);
259
    }
260
261
    /**
262
     * {@inheritdoc}
263
     */
264
    public function getMimetype($path)
265
    {
266
        return $this->getMetadata($path);
267
    }
268
269
    /**
270
     * {@inheritdoc}
271
     */
272
    public function getTimestamp($path)
273
    {
274
        return $this->getMetadata($path);
275
    }
276
277
    /**
278
     * {@inheritdoc}
279
     */
280
    public function getUrl($path)
281
    {
282
        return (string) $this->client->getPsrPrimaryUri()->withPath($this->container) . '/'. $path;
283
    }
284
285
    /**
286
     * Builds the normalized output array.
287
     *
288
     * @param string $path
289
     * @param int    $timestamp
290
     * @param mixed  $content
291
     *
292
     * @return array
293
     */
294
    protected function normalize($path, $timestamp, $content = null)
295
    {
296
        $data = [
297
            'path' => $path,
298
            'timestamp' => (int) $timestamp,
299
            'dirname' => Util::dirname($path),
300
            'type' => 'file',
301
        ];
302
303
        if (is_string($content)) {
304
            $data['contents'] = $content;
305
        }
306
307
        return $data;
308
    }
309
310
    /**
311
     * Builds the normalized output array from a Blob object.
312
     *
313
     * @param string         $path
314
     * @param BlobProperties $properties
315
     *
316
     * @return array
317
     */
318
    protected function normalizeBlobProperties($path, BlobProperties $properties)
319
    {
320
        if (substr($path, -1) === '/') {
321
            return ['type' => 'dir', 'path' => $this->removePathPrefix(rtrim($path, '/'))];
322
        }
323
324
        $path = $this->removePathPrefix($path);
325
326
        return [
327
            'path' => $path,
328
            'timestamp' => (int) $properties->getLastModified()->format('U'),
329
            'dirname' => Util::dirname($path),
330
            'mimetype' => $properties->getContentType(),
331
            'size' => $properties->getContentLength(),
332
            'type' => 'file',
333
        ];
334
    }
335
336
    /**
337
     * Builds the normalized output array from a BlobPrefix object.
338
     *
339
     * @param BlobPrefix $blobPrefix
340
     *
341
     * @return array
342
     */
343
    protected function normalizeBlobPrefix(BlobPrefix $blobPrefix)
344
    {
345
        return ['type' => 'dir', 'path' => $this->removePathPrefix(rtrim($blobPrefix->getName(), '/'))];
346
    }
347
348
    /**
349
     * Retrieves content streamed by Azure into a string.
350
     *
351
     * @param resource $resource
352
     *
353
     * @return string
354
     */
355
    protected function streamContentsToString($resource)
356
    {
357
        return stream_get_contents($resource);
358
    }
359
360
    /**
361
     * Upload a file.
362
     *
363
     * @param string          $path     Path
364
     * @param string|resource $contents Either a string or a stream.
365
     * @param Config          $config   Config
366
     *
367
     * @return array
368
     */
369
    protected function upload($path, $contents, Config $config)
370
    {
371
        $path = $this->applyPathPrefix($path);
372
        
373
        $config->set('ContentType', Util::guessMimeType($path, $contents));
374
        
375
        /** @var CopyBlobResult $result */
376
        $result = $this->client->createBlockBlob(
377
            $this->container,
378
            $path,
379
            $contents,
0 ignored issues
show
Bug introduced by
It seems like $contents defined by parameter $contents on line 369 can also be of type resource; however, MicrosoftAzure\Storage\B...Blob::createBlockBlob() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and 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...
380
            $this->getOptionsFromConfig($config)
381
        );
382
383
        return $this->normalize($path, $result->getLastModified()->format('U'), $contents);
384
    }
385
386
    /**
387
     * Retrieve options from a Config instance.
388
     *
389
     * @param Config $config
390
     *
391
     * @return CreateBlobOptions
392
     */
393
    protected function getOptionsFromConfig(Config $config)
394
    {
395
        $options = new CreateBlobOptions();
396
397
        foreach (static::$metaOptions as $option) {
398
            if ( ! $config->has($option)) {
399
                continue;
400
            }
401
402
            call_user_func([$options, "set$option"], $config->get($option));
403
        }
404
405
        if ($mimetype = $config->get('mimetype')) {
406
            $options->setContentType($mimetype);
407
        }
408
409
        return $options;
410
    }
411
}
412