Completed
Push — master ( 9c520a...7a92ab )
by Nicolas
02:52 queued 10s
created

AzureBlobStorage::checksum()   A

Complexity

Conditions 2
Paths 4

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.7333
c 0
b 0
f 0
cc 2
nc 4
nop 1
1
<?php
2
3
namespace Gaufrette\Adapter;
4
5
use Gaufrette\Adapter;
6
use Gaufrette\Util;
7
use Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface;
8
use MicrosoftAzure\Storage\Blob\Models\Blob;
9
use MicrosoftAzure\Storage\Blob\Models\Container;
10
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
11
use MicrosoftAzure\Storage\Blob\Models\CreateBlockBlobOptions;
12
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
13
use MicrosoftAzure\Storage\Blob\Models\DeleteContainerOptions;
14
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
15
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;
16
17
/**
18
 * Microsoft Azure Blob Storage adapter.
19
 *
20
 * @author Luciano Mammino <[email protected]>
21
 * @author Paweł Czyżewski <[email protected]>
22
 */
23
class AzureBlobStorage implements Adapter,
24
                                  MetadataSupporter,
25
                                  SizeCalculator,
26
                                  ChecksumCalculator
27
28
{
29
    /**
30
     * Error constants.
31
     */
32
    const ERROR_CONTAINER_ALREADY_EXISTS = 'ContainerAlreadyExists';
33
    const ERROR_CONTAINER_NOT_FOUND = 'ContainerNotFound';
34
35
    /**
36
     * @var AzureBlobStorage\BlobProxyFactoryInterface
37
     */
38
    protected $blobProxyFactory;
39
40
    /**
41
     * @var string
42
     */
43
    protected $containerName;
44
45
    /**
46
     * @var bool
47
     */
48
    protected $detectContentType;
49
50
    /**
51
     * @var \MicrosoftAzure\Storage\Blob\Internal\IBlob
52
     */
53
    protected $blobProxy;
54
55
    /**
56
     * @var bool
57
     */
58
    protected $multiContainerMode = false;
59
60
    /**
61
     * @var CreateContainerOptions
62
     */
63
    protected $createContainerOptions;
64
65
    /**
66
     * @param AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
67
     * @param string|null                                $containerName
68
     * @param bool                                       $create
69
     * @param bool                                       $detectContentType
70
     *
71
     * @throws \RuntimeException
72
     */
73
    public function __construct(BlobProxyFactoryInterface $blobProxyFactory, $containerName = null, $create = false, $detectContentType = true)
74
    {
75
        $this->blobProxyFactory = $blobProxyFactory;
76
        $this->containerName = $containerName;
77
        $this->detectContentType = $detectContentType;
78
        if (null === $containerName) {
79
            $this->multiContainerMode = true;
80
        } elseif ($create) {
81
            $this->createContainer($containerName);
82
        }
83
    }
84
85
    /**
86
     * @return CreateContainerOptions
87
     */
88
    public function getCreateContainerOptions()
89
    {
90
        return $this->createContainerOptions;
91
    }
92
93
    /**
94
     * @param CreateContainerOptions $options
95
     */
96
    public function setCreateContainerOptions(CreateContainerOptions $options)
97
    {
98
        $this->createContainerOptions = $options;
99
    }
100
101
    /**
102
     * Creates a new container.
103
     *
104
     * @param string                                                     $containerName
105
     * @param \MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions $options
106
     *
107
     * @throws \RuntimeException if cannot create the container
108
     */
109 View Code Duplication
    public function createContainer($containerName, CreateContainerOptions $options = null)
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...
110
    {
111
        $this->init();
112
113
        if (null === $options) {
114
            $options = $this->getCreateContainerOptions();
115
        }
116
117
        try {
118
            $this->blobProxy->createContainer($containerName, $options);
119
        } catch (ServiceException $e) {
120
            $errorCode = $this->getErrorCodeFromServiceException($e);
121
122
            if ($errorCode !== self::ERROR_CONTAINER_ALREADY_EXISTS) {
123
                throw new \RuntimeException(sprintf(
124
                    'Failed to create the configured container "%s": %s (%s).',
125
                    $containerName,
126
                    $e->getErrorText(),
127
                    $errorCode
128
                ));
129
            }
130
        }
131
    }
132
133
    /**
134
     * Deletes a container.
135
     *
136
     * @param string                 $containerName
137
     * @param DeleteContainerOptions $options
138
     *
139
     * @throws \RuntimeException if cannot delete the container
140
     */
141 View Code Duplication
    public function deleteContainer($containerName, DeleteContainerOptions $options = null)
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...
142
    {
143
        $this->init();
144
145
        try {
146
            $this->blobProxy->deleteContainer($containerName, $options);
147
        } catch (ServiceException $e) {
148
            $errorCode = $this->getErrorCodeFromServiceException($e);
149
150
            if ($errorCode !== self::ERROR_CONTAINER_NOT_FOUND) {
151
                throw new \RuntimeException(sprintf(
152
                    'Failed to delete the configured container "%s": %s (%s).',
153
                    $containerName,
154
                    $e->getErrorText(),
155
                    $errorCode
156
                ), $e->getCode());
157
            }
158
        }
159
    }
160
161
    /**
162
     * {@inheritdoc}
163
     * @throws \RuntimeException
164
     * @throws \InvalidArgumentException
165
     */
166 View Code Duplication
    public function read($key)
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...
167
    {
168
        $this->init();
169
        list($containerName, $key) = $this->tokenizeKey($key);
170
171
        try {
172
            $blob = $this->blobProxy->getBlob($containerName, $key);
173
174
            return stream_get_contents($blob->getContentStream());
175
        } catch (ServiceException $e) {
176
            $this->failIfContainerNotFound($e, sprintf('read key "%s"', $key), $containerName);
177
178
            return false;
179
        }
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     * @throws \RuntimeException
185
     * @throws \InvalidArgumentException
186
     */
187
    public function write($key, $content)
188
    {
189
        $this->init();
190
        list($containerName, $key) = $this->tokenizeKey($key);
191
192
        if (class_exists(CreateBlockBlobOptions::class)) {
193
            $options = new CreateBlockBlobOptions();
194
        } else {
195
            // for microsoft/azure-storage < 1.0
196
            $options = new CreateBlobOptions();
197
        }
198
199
        if ($this->detectContentType) {
200
            $contentType = $this->guessContentType($content);
201
202
            $options->setContentType($contentType);
203
        }
204
205
        try {
206
            if ($this->multiContainerMode) {
207
                $this->createContainer($containerName);
208
            }
209
210
            $this->blobProxy->createBlockBlob($containerName, $key, $content, $options);
0 ignored issues
show
Documentation introduced by
$options is of type object<MicrosoftAzure\St...dels\CreateBlobOptions>, but the function expects a null|object<MicrosoftAzu...CreateBlockBlobOptions>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
211
        } catch (ServiceException $e) {
212
            $this->failIfContainerNotFound($e, sprintf('write content for key "%s"', $key), $containerName);
213
214
            return false;
215
        }
216
        if (is_resource($content)) {
217
            return Util\Size::fromResource($content);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \Gaufrette\Util\S...fromResource($content); (string) is incompatible with the return type declared by the interface Gaufrette\Adapter::write of type integer|boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
218
        }
219
220
        return Util\Size::fromContent($content);
221
    }
222
223
    /**
224
     * {@inheritdoc}
225
     * @throws \RuntimeException
226
     * @throws \InvalidArgumentException
227
     */
228
    public function exists($key)
229
    {
230
        $this->init();
231
        list($containerName, $key) = $this->tokenizeKey($key);
232
233
        $listBlobsOptions = new ListBlobsOptions();
234
        $listBlobsOptions->setPrefix($key);
235
236
        try {
237
            $blobsList = $this->blobProxy->listBlobs($containerName, $listBlobsOptions);
238
239
            foreach ($blobsList->getBlobs() as $blob) {
240
                if ($key === $blob->getName()) {
241
                    return true;
242
                }
243
            }
244
        } catch (ServiceException $e) {
245
            $errorCode = $this->getErrorCodeFromServiceException($e);
246
            if ($this->multiContainerMode && self::ERROR_CONTAINER_NOT_FOUND === $errorCode) {
247
                return false;
248
            }
249
            $this->failIfContainerNotFound($e, 'check if key exists', $containerName);
250
251
            throw new \RuntimeException(sprintf(
252
                'Failed to check if key "%s" exists in container "%s": %s (%s).',
253
                $key,
254
                $containerName,
255
                $e->getErrorText(),
256
                $errorCode
257
            ), $e->getCode());
258
        }
259
260
        return false;
261
    }
262
263
    /**
264
     * {@inheritdoc}
265
     * @throws \RuntimeException
266
     */
267
    public function keys()
268
    {
269
        $this->init();
270
271
        try {
272
            if ($this->multiContainerMode) {
273
                $containersList = $this->blobProxy->listContainers();
274
                return call_user_func_array('array_merge', array_map(
275
                    function(Container $container) {
276
                        $containerName = $container->getName();
277
                        return $this->fetchBlobs($containerName, $containerName);
278
                    },
279
                    $containersList->getContainers()
280
                ));
281
            }
282
283
            return $this->fetchBlobs($this->containerName);
284
        } catch (ServiceException $e) {
285
            $this->failIfContainerNotFound($e, 'retrieve keys', $this->containerName);
286
            $errorCode = $this->getErrorCodeFromServiceException($e);
287
288
            throw new \RuntimeException(sprintf(
289
                'Failed to list keys for the container "%s": %s (%s).',
290
                $this->containerName,
291
                $e->getErrorText(),
292
                $errorCode
293
            ), $e->getCode());
294
        }
295
    }
296
297
    /**
298
     * {@inheritdoc}
299
     * @throws \RuntimeException
300
     * @throws \InvalidArgumentException
301
     */
302 View Code Duplication
    public function mtime($key)
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...
303
    {
304
        $this->init();
305
        list($containerName, $key) = $this->tokenizeKey($key);
306
307
        try {
308
            $properties = $this->blobProxy->getBlobProperties($containerName, $key);
309
310
            return $properties->getProperties()->getLastModified()->getTimestamp();
311
        } catch (ServiceException $e) {
312
            $this->failIfContainerNotFound($e, sprintf('read mtime for key "%s"', $key), $containerName);
313
314
            return false;
315
        }
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321 View Code Duplication
    public function size($key)
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...
322
    {
323
        $this->init();
324
        list($containerName, $key) = $this->tokenizeKey($key);
325
326
        try {
327
            $properties = $this->blobProxy->getBlobProperties($containerName, $key);
328
329
            return $properties->getProperties()->getContentLength();
330
        } catch (ServiceException $e) {
331
            $this->failIfContainerNotFound($e, sprintf('read content length for key "%s"', $key), $containerName);
332
333
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Gaufrette\Adapter\SizeCalculator::size of type integer.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
334
        }
335
336
    }
337
338
    /**
339
     * {@inheritdoc}
340
     */
341
    public function checksum($key)
342
    {
343
        $this->init();
344
        list($containerName, $key) = $this->tokenizeKey($key);
345
346
        try {
347
            $properties = $this->blobProxy->getBlobProperties($containerName, $key);
348
            $checksumBase64 = $properties->getProperties()->getContentMD5();
349
350
            return \bin2hex(\base64_decode($checksumBase64, true));
351
        } catch (ServiceException $e) {
352
            $this->failIfContainerNotFound($e, sprintf('read content MD5 for key "%s"', $key), $containerName);
353
354
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return false; (false) is incompatible with the return type declared by the interface Gaufrette\Adapter\ChecksumCalculator::checksum of type string.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
355
        }
356
    }
357
358
    /**
359
     * {@inheritdoc}
360
     * @throws \RuntimeException
361
     * @throws \InvalidArgumentException
362
     */
363 View Code Duplication
    public function delete($key)
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...
364
    {
365
        $this->init();
366
        list($containerName, $key) = $this->tokenizeKey($key);
367
368
        try {
369
            $this->blobProxy->deleteBlob($containerName, $key);
370
371
            return true;
372
        } catch (ServiceException $e) {
373
            $this->failIfContainerNotFound($e, sprintf('delete key "%s"', $key), $containerName);
374
375
            return false;
376
        }
377
    }
378
379
    /**
380
     * {@inheritdoc}
381
     * @throws \RuntimeException
382
     * @throws \InvalidArgumentException
383
     */
384
    public function rename($sourceKey, $targetKey)
385
    {
386
        $this->init();
387
388
        list($sourceContainerName, $sourceKey) = $this->tokenizeKey($sourceKey);
389
        list($targetContainerName, $targetKey) = $this->tokenizeKey($targetKey);
390
391
        try {
392
            if ($this->multiContainerMode) {
393
                $this->createContainer($targetContainerName);
394
            }
395
            $this->blobProxy->copyBlob($targetContainerName, $targetKey, $sourceContainerName, $sourceKey);
396
            $this->blobProxy->deleteBlob($sourceContainerName, $sourceKey);
397
398
            return true;
399
        } catch (ServiceException $e) {
400
            $this->failIfContainerNotFound($e, sprintf('rename key "%s"', $sourceKey), $sourceContainerName);
401
402
            return false;
403
        }
404
    }
405
406
    /**
407
     * {@inheritdoc}
408
     */
409
    public function isDirectory($key)
410
    {
411
        // Windows Azure Blob Storage does not support directories
412
        return false;
413
    }
414
415
    /**
416
     * {@inheritdoc}
417
     * @throws \RuntimeException
418
     * @throws \InvalidArgumentException
419
     */
420 View Code Duplication
    public function setMetadata($key, $content)
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...
421
    {
422
        $this->init();
423
        list($containerName, $key) = $this->tokenizeKey($key);
424
425
        try {
426
            $this->blobProxy->setBlobMetadata($containerName, $key, $content);
427
        } catch (ServiceException $e) {
428
            $errorCode = $this->getErrorCodeFromServiceException($e);
429
430
            throw new \RuntimeException(sprintf(
431
                'Failed to set metadata for blob "%s" in container "%s": %s (%s).',
432
                $key,
433
                $containerName,
434
                $e->getErrorText(),
435
                $errorCode
436
            ), $e->getCode());
437
        }
438
    }
439
440
    /**
441
     * {@inheritdoc}
442
     * @throws \RuntimeException
443
     * @throws \InvalidArgumentException
444
     */
445 View Code Duplication
    public function getMetadata($key)
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...
446
    {
447
        $this->init();
448
        list($containerName, $key) = $this->tokenizeKey($key);
449
450
        try {
451
            $properties = $this->blobProxy->getBlobProperties($containerName, $key);
452
453
            return $properties->getMetadata();
454
        } catch (ServiceException $e) {
455
            $errorCode = $this->getErrorCodeFromServiceException($e);
456
457
            throw new \RuntimeException(sprintf(
458
                'Failed to get metadata for blob "%s" in container "%s": %s (%s).',
459
                $key,
460
                $containerName,
461
                $e->getErrorText(),
462
                $errorCode
463
            ), $e->getCode());
464
        }
465
    }
466
467
    /**
468
     * Lazy initialization, automatically called when some method is called after construction.
469
     */
470
    protected function init()
471
    {
472
        if ($this->blobProxy === null) {
473
            $this->blobProxy = $this->blobProxyFactory->create();
474
        }
475
    }
476
477
    /**
478
     * Throws a runtime exception if a give ServiceException derived from a "container not found" error.
479
     *
480
     * @param ServiceException $exception
481
     * @param string           $action
482
     * @param string           $containerName
483
     *
484
     * @throws \RuntimeException
485
     */
486
    protected function failIfContainerNotFound(ServiceException $exception, $action, $containerName)
487
    {
488
        $errorCode = $this->getErrorCodeFromServiceException($exception);
489
490
        if ($errorCode === self::ERROR_CONTAINER_NOT_FOUND) {
491
            throw new \RuntimeException(sprintf(
492
                'Failed to %s: container "%s" not found.',
493
                $action,
494
                $containerName
495
            ), $exception->getCode());
496
        }
497
    }
498
499
    /**
500
     * Extracts the error code from a service exception.
501
     *
502
     * @param ServiceException $exception
503
     *
504
     * @return string
505
     */
506
    protected function getErrorCodeFromServiceException(ServiceException $exception)
507
    {
508
        $xml = @simplexml_load_string($exception->getResponse()->getBody());
509
510
        if ($xml && isset($xml->Code)) {
511
            return (string) $xml->Code;
512
        }
513
514
        return $exception->getErrorText();
515
    }
516
517
    /**
518
     * @param string|resource $content
519
     *
520
     * @return string
521
     */
522 View Code Duplication
    private function guessContentType($content)
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...
523
    {
524
        $fileInfo = new \finfo(FILEINFO_MIME_TYPE);
525
526
        if (is_resource($content)) {
527
            return $fileInfo->file(stream_get_meta_data($content)['uri']);
528
        }
529
530
        return $fileInfo->buffer($content);
531
    }
532
533
    /**
534
     * @param string $key
535
     *
536
     * @return array
537
     * @throws \InvalidArgumentException
538
     */
539
    private function tokenizeKey($key)
540
    {
541
        $containerName = $this->containerName;
542
        if (false === $this->multiContainerMode) {
543
            return [$containerName, $key];
544
        }
545
546
        if (false === ($index = strpos($key, '/'))) {
547
            throw new \InvalidArgumentException(sprintf(
548
                'Failed to establish container name from key "%s", container name is required in multi-container mode',
549
                $key
550
            ));
551
        }
552
        $containerName = substr($key, 0, $index);
553
        $key = substr($key, $index + 1);
554
555
        return [$containerName, $key];
556
    }
557
558
    /**
559
     * @param string $containerName
560
     * @param null   $prefix
561
     *
562
     * @return array
563
     */
564
    private function fetchBlobs($containerName, $prefix = null)
565
    {
566
        $blobList = $this->blobProxy->listBlobs($containerName);
567
        return array_map(
568
            function (Blob $blob) use ($prefix) {
569
                $name = $blob->getName();
570
                if (null !== $prefix) {
571
                    $name = $prefix .'/'. $name;
572
                }
573
                return $name;
574
            },
575
            $blobList->getBlobs()
576
        );
577
    }
578
}
579