Completed
Push — master ( 13b2e5...7e67c4 )
by Chris
03:12
created

FlysystemStreamWrapper::stream_cast()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 5
Bugs 0 Features 2
Metric Value
c 5
b 0
f 2
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 1
1
<?php
2
3
namespace Twistor;
4
5
use League\Flysystem\AdapterInterface;
6
use League\Flysystem\FileNotFoundException;
7
use League\Flysystem\FilesystemInterface;
8
use League\Flysystem\Util;
9
use Twistor\Flysystem\Exception\TriggerErrorException;
10
use Twistor\Flysystem\Plugin\ForcedRename;
11
use Twistor\Flysystem\Plugin\Mkdir;
12
use Twistor\Flysystem\Plugin\Rmdir;
13
use Twistor\Flysystem\Plugin\Stat;
14
use Twistor\Flysystem\Plugin\Touch;
15
use Twistor\StreamUtil;
16
17
/**
18
 * An adapter for Flysystem to a PHP stream wrapper.
19
 */
20
class FlysystemStreamWrapper
21
{
22
    /**
23
     * A flag to tell FlysystemStreamWrapper::url_stat() to ignore the size.
24
     *
25
     * @var int
26
     */
27
    const STREAM_URL_IGNORE_SIZE = 8;
28
29
    /**
30
     * The registered filesystems.
31
     *
32
     * @var \League\Flysystem\FilesystemInterface[]
33
     */
34
    protected static $filesystems = [];
35
36
    /**
37
     * Optional configuration.
38
     *
39
     * @var array
40
     */
41
    protected static $config = [];
42
43
    /**
44
     * The default configuration.
45
     *
46
     * @var array
47
     */
48
    protected static $defaultConfiguration = [
49
        'permissions' =>[
50
            'dir' => [
51
                'private' => 0700,
52
                'public' => 0755,
53
            ],
54
            'file' => [
55
                'private' => 0600,
56
                'public' => 0644,
57
            ],
58
        ],
59
        'metadata' => ['timestamp', 'size', 'visibility'],
60
        'public_mask' => 0044,
61
    ];
62
63
    /**
64
     * The number of bytes that have been written since the last flush.
65
     *
66
     * @var int
67
     */
68
    protected $bytesWritten = 0;
69
70
    /**
71
     * The filesystem of the current stream wrapper.
72
     *
73
     * @var \League\Flysystem\FilesystemInterface
74
     */
75
    protected $filesystem;
76
77
    /**
78
     * A generic resource handle.
79
     *
80
     * @var resource|bool
81
     */
82
    protected $handle;
83
84
    /**
85
     * Whether the handle is in append mode.
86
     *
87
     * @var bool
88
     */
89
    protected $isAppendMode = false;
90
91
    /**
92
     * Whether the handle is read-only.
93
     *
94
     * The stream returned from Flysystem may not actually be read-only, This
95
     * ensures read-only behavior.
96
     *
97
     * @var bool
98
     */
99
    protected $isReadOnly = false;
100
101
    /**
102
     * Whether the handle is write-only.
103
     *
104
     * @var bool
105
     */
106
    protected $isWriteOnly = false;
107
108
    /**
109
     * A directory listing.
110
     *
111
     * @var array
112
     */
113
    protected $listing;
114
115
    /**
116
     * Whether this handle has been verified writable.
117
     *
118
     * @var bool
119
     */
120
    protected $needsCowCheck = false;
121
122
    /**
123
     * Whether the handle should be flushed.
124
     *
125
     * @var bool
126
     */
127
    protected $needsFlush = false;
128
129
    /**
130
     * The handle used for calls to stream_lock.
131
     *
132
     * @var resource
133
     */
134
    protected $lockHandle;
135
136
    /**
137
     * If stream_set_write_buffer() is called, the arguments.
138
     *
139
     * @var int
140
     */
141
    protected $streamWriteBuffer;
142
143
    /**
144
     * Instance URI (stream).
145
     *
146
     * A stream is referenced as "protocol://target".
147
     *
148
     * @var string
149
     */
150
    protected $uri;
151
152
    /**
153
     * Registers the stream wrapper protocol if not already registered.
154
     *
155
     * @param string              $protocol      The protocol.
156
     * @param FilesystemInterface $filesystem    The filesystem.
157
     * @param array|null          $configuration Optional configuration.
158
     *
159
     * @return bool True if the protocal was registered, false if not.
160
     */
161 198
    public static function register($protocol, FilesystemInterface $filesystem, array $configuration = null)
162
    {
163 198
        if (static::streamWrapperExists($protocol)) {
164 3
            return false;
165
        }
166
167 198
        static::$config[$protocol] = $configuration ?: static::$defaultConfiguration;
168 198
        static::registerPlugins($protocol, $filesystem);
169 198
        static::$filesystems[$protocol] = $filesystem;
170
171 198
        return stream_wrapper_register($protocol, __CLASS__);
172
    }
173
174
    /**
175
     * Unegisters a stream wrapper.
176
     *
177
     * @param string $protocol The protocol.
178
     *
179
     * @return bool True if the protocal was unregistered, false if not.
180
     */
181 195
    public static function unregister($protocol)
182
    {
183 195
        if (!static::streamWrapperExists($protocol)) {
184 3
            return false;
185
        }
186
187 195
        unset(static::$filesystems[$protocol]);
188
189 195
        return stream_wrapper_unregister($protocol);
190
    }
191
192
    /**
193
     * Determines if a protocol is registered.
194
     *
195
     * @param string $protocol The protocol to check.
196
     *
197
     * @return bool True if it is registered, false if not.
198
     */
199 198
    protected static function streamWrapperExists($protocol)
200
    {
201 198
        return in_array($protocol, stream_get_wrappers(), true);
202
    }
203
204
    /**
205
     * Registers plugins on the filesystem.
206
     *
207
     * @param string              $protocol
208
     * @param FilesystemInterface $filesystem
209
     */
210 198
    protected static function registerPlugins($protocol, FilesystemInterface $filesystem)
211
    {
212 198
        $filesystem->addPlugin(new ForcedRename());
213 198
        $filesystem->addPlugin(new Mkdir());
214 198
        $filesystem->addPlugin(new Rmdir());
215
216 198
        $stat = new Stat(
217 198
            static::$config[$protocol]['permissions'],
218 198
            static::$config[$protocol]['metadata']
219 198
        );
220
221 198
        $filesystem->addPlugin($stat);
222 198
        $filesystem->addPlugin(new Touch());
223 198
    }
224
225
    /**
226
     * Closes the directory handle.
227
     *
228
     * @return bool True on success, false on failure.
229
     */
230 12
    public function dir_closedir()
231
    {
232 12
        unset($this->listing);
233
234 12
        return true;
235
    }
236
237
    /**
238
     * Opens a directory handle.
239
     *
240
     * @param string $uri     The URL that was passed to opendir().
241
     * @param int    $options Whether or not to enforce safe_mode (0x04).
242
     *
243
     * @return bool True on success, false on failure.
244
     */
245 18
    public function dir_opendir($uri, $options)
246
    {
247 18
        $this->uri = $uri;
248
249 18
        $path = Util::normalizePath($this->getTarget());
250
251 18
        $this->listing = $this->invoke($this->getFilesystem(), 'listContents', [$path], 'opendir');
252
253 18
        if ($this->listing === false) {
254 6
            return false;
255
        }
256
257 12
        if (!$dirlen = strlen($path)) {
258 6
            return true;
259
        }
260
261
        // Remove the separator /.
262 6
        $dirlen++;
263
264
        // Remove directory prefix.
265 6
        foreach ($this->listing as $delta => $item) {
266 6
            $this->listing[$delta]['path'] = substr($item['path'], $dirlen);
267 6
        }
268
269 6
        reset($this->listing);
270
271 6
        return true;
272
    }
273
274
    /**
275
     * Reads an entry from directory handle.
276
     *
277
     * @return string|bool The next filename, or false if there is no next file.
278
     */
279 12
    public function dir_readdir()
280
    {
281 12
        $current = current($this->listing);
282 12
        next($this->listing);
283
284 12
        return $current ? $current['path'] : false;
285
    }
286
287
    /**
288
     * Rewinds the directory handle.
289
     *
290
     * @return bool True on success, false on failure.
291
     */
292 12
    public function dir_rewinddir()
293
    {
294 12
        reset($this->listing);
295
296 12
        return true;
297
    }
298
299
    /**
300
     * Creates a directory.
301
     *
302
     * @param string $uri
303
     * @param int    $mode
304
     * @param int    $options
305
     *
306
     * @return bool True on success, false on failure.
307
     */
308 60
    public function mkdir($uri, $mode, $options)
309
    {
310 60
        $this->uri = $uri;
311
312 60
        return $this->invoke($this->getFilesystem(), 'mkdir', [$this->getTarget(), $mode, $options]);
313
    }
314
315
    /**
316
     * Renames a file or directory.
317
     *
318
     * @param string $uri_from
319
     * @param string $uri_to
320
     *
321
     * @return bool True on success, false on failure.
322
     */
323 36
    public function rename($uri_from, $uri_to)
324
    {
325 36
        $this->uri = $uri_from;
326 36
        $args = [$this->getTarget($uri_from), $this->getTarget($uri_to)];
327
328 36
        return $this->invoke($this->getFilesystem(), 'forcedRename', $args, 'rename');
329
    }
330
331
    /**
332
     * Removes a directory.
333
     *
334
     * @param string $uri
335
     * @param int    $options
336
     *
337
     * @return bool True on success, false on failure.
338
     */
339 18
    public function rmdir($uri, $options)
340
    {
341 18
        $this->uri = $uri;
342
343 18
        return $this->invoke($this->getFilesystem(), 'rmdir', [$this->getTarget(), $options]);
344
    }
345
346
    /**
347
     * Retrieves the underlaying resource.
348
     *
349
     * @param int $cast_as
350
     *
351
     * @return resource|bool The stream resource used by the wrapper, or false.
352
     */
353 6
    public function stream_cast($cast_as)
354
    {
355 6
        return $this->handle;
356
    }
357
358
    /**
359
     * Closes the resource.
360
     */
361 78
    public function stream_close()
362
    {
363
        // PHP 7 doesn't call flush automatically anymore for truncate() or when
364
        // writing an empty file. We need to ensure that the handle gets pushed
365
        // as needed in that case. This will be a no-op for php 5.
366 78
        $this->stream_flush();
367
368 78
        fclose($this->handle);
369 78
    }
370
371
    /**
372
     * Tests for end-of-file on a file pointer.
373
     *
374
     * @return bool True if the file is at the end, false if not.
375
     */
376 60
    public function stream_eof()
377
    {
378 60
        return feof($this->handle);
379
    }
380
381
    /**
382
     * Flushes the output.
383
     *
384
     * @return bool True on success, false on failure.
385
     */
386 78
    public function stream_flush()
387
    {
388 78
        if (!$this->needsFlush) {
389 78
            return true;
390
        }
391
392 78
        $this->needsFlush = false;
393 78
        $this->bytesWritten = 0;
394
395
        // Calling putStream() will rewind our handle. flush() shouldn't change
396
        // the position of the file.
397 78
        $pos = ftell($this->handle);
398
399 78
        $args = [$this->getTarget(), $this->handle];
400 78
        $success = $this->invoke($this->getFilesystem(), 'putStream', $args, 'fflush');
401
402 78
        fseek($this->handle, $pos);
403
404 78
        return $success;
405
    }
406
407
    /**
408
     * Advisory file locking.
409
     *
410
     * @param int $operation
411
     *
412
     * @return bool True on success, false on failure.
413
     */
414 6
    public function stream_lock($operation)
415
    {
416 6
        $operation = (int) $operation;
417
418 6
        if (($operation & \LOCK_UN) === \LOCK_UN) {
419 6
            return $this->releaseLock($operation);
420
        }
421
422
        // If the caller calls flock() twice, there's no reason to re-create the
423
        // lock handle.
424 6
        if (is_resource($this->lockHandle)) {
425 6
            return flock($this->lockHandle, $operation);
426
        }
427
428 6
        $this->lockHandle = $this->openLockHandle();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->openLockHandle() can also be of type false. However, the property $lockHandle is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
429
430 6
        return is_resource($this->lockHandle) && flock($this->lockHandle, $operation);
431
    }
432
433
    /**
434
     * Changes stream options.
435
     *
436
     * @param string $uri
437
     * @param int    $option
438
     * @param mixed  $value
439
     *
440
     * @return bool True on success, false on failure.
441
     */
442 39
    public function stream_metadata($uri, $option, $value)
443
    {
444 39
        $this->uri = $uri;
445
446
        switch ($option) {
447 39
            case STREAM_META_ACCESS:
448 15
                $permissions = octdec(substr(decoct($value), -4));
449 15
                $is_public = $permissions & $this->getConfiguration('public_mask');
450 15
                $visibility =  $is_public ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
451
452
                try {
453 15
                    return $this->getFilesystem()->setVisibility($this->getTarget(), $visibility);
454
455 9
                } catch (\LogicException $e) {
456
                    // The adapter doesn't support visibility.
457
458 9
                } catch (\Exception $e) {
459 6
                    $this->triggerError('chmod', $e);
460 6
                    return false;
461
                }
462
463 3
                return true;
464
465 27
            case STREAM_META_TOUCH:
466 27
                return $this->invoke($this->getFilesystem(), 'touch', [$this->getTarget()]);
467
468 6
            default:
469 6
                return false;
470 6
        }
471
    }
472
473
    /**
474
     * Opens file or URL.
475
     *
476
     * @param string $uri
477
     * @param string $mode
478
     * @param int    $options
479
     * @param string &$opened_path
480
     *
481
     * @return bool True on success, false on failure.
482
     */
483 96
    public function stream_open($uri, $mode, $options, &$opened_path)
484
    {
485 96
        $this->uri = $uri;
486 96
        $path = $this->getTarget();
487
488 96
        $this->isReadOnly = StreamUtil::modeIsReadOnly($mode);
489 96
        $this->isWriteOnly = StreamUtil::modeIsWriteOnly($mode);
490 96
        $this->isAppendMode = StreamUtil::modeIsAppendable($mode);
491
492 96
        $this->handle = $this->invoke($this, 'getStream', [$path, $mode], 'fopen');
493
494 96
        if ($this->handle && $options & STREAM_USE_PATH) {
495 6
            $opened_path = $path;
496 6
        }
497
498 96
        return is_resource($this->handle);
499
    }
500
501
    /**
502
     * Reads from stream.
503
     *
504
     * @param int $count
505
     *
506
     * @return string The bytes read.
507
     */
508 60
    public function stream_read($count)
509
    {
510 60
        if ($this->isWriteOnly) {
511 6
            return '';
512
        }
513
514 60
        return fread($this->handle, $count);
515
    }
516
517
    /**
518
     * Seeks to specific location in a stream.
519
     *
520
     * @param int $offset
521
     * @param int $whence
522
     *
523
     * @return bool True on success, false on failure.
524
     */
525 24
    public function stream_seek($offset, $whence = SEEK_SET)
526
    {
527 24
        return fseek($this->handle, $offset, $whence) === 0;
528
    }
529
530
    /**
531
     * Changes stream options.
532
     *
533
     * @param int $option
534
     * @param int $arg1
535
     * @param int $arg2
536
     *
537
     * @return bool True on success, false on failure.
538
     */
539 6
    public function stream_set_option($option, $arg1, $arg2)
540
    {
541
        switch ($option) {
542 6
            case STREAM_OPTION_BLOCKING:
543
                // This works for the local adapter. It doesn't do anything for
544
                // memory streams.
545 6
                return stream_set_blocking($this->handle, $arg1);
546
547 6
            case STREAM_OPTION_READ_TIMEOUT:
548 6
                return  stream_set_timeout($this->handle, $arg1, $arg2);
549
550 6
            case STREAM_OPTION_READ_BUFFER:
551 6
                if ($arg1 === STREAM_BUFFER_NONE) {
552 6
                    return stream_set_read_buffer($this->handle, 0) === 0;
553
                }
554
555 6
                return stream_set_read_buffer($this->handle, $arg2) === 0;
556
557 6
            case STREAM_OPTION_WRITE_BUFFER:
558 6
                $this->streamWriteBuffer = $arg1 === STREAM_BUFFER_NONE ? 0 : $arg2;
559
560 6
                return true;
561
        }
562
563 6
        return false;
564
    }
565
566
    /**
567
     * Retrieves information about a file resource.
568
     *
569
     * @return array A similar array to fstat().
570
     *
571
     * @see fstat()
572
     */
573 66
    public function stream_stat()
574
    {
575
        // Get metadata from original file.
576 66
        $stat = $this->url_stat($this->uri, static::STREAM_URL_IGNORE_SIZE | STREAM_URL_STAT_QUIET) ?: [];
577
578
        // Newly created file.
579 66
        if (empty($stat['mode'])) {
580 6
            $stat['mode'] = 0100000 + $this->getConfiguration('permissions')['file']['public'];
581 6
            $stat[2] = $stat['mode'];
582 6
        }
583
584
        // Use the size of our handle, since it could have been written to or
585
        // truncated.
586 66
        $stat['size'] = $stat[7] = StreamUtil::getSize($this->handle);
587
588 66
        return $stat;
589
    }
590
591
    /**
592
     * Retrieves the current position of a stream.
593
     *
594
     * @return int The current position of the stream.
595
     */
596 24
    public function stream_tell()
597
    {
598 24
        if ($this->isAppendMode) {
599 6
            return 0;
600
        }
601 18
        return ftell($this->handle);
602
    }
603
604
    /**
605
     * Truncates the stream.
606
     *
607
     * @param int $new_size
608
     *
609
     * @return bool True on success, false on failure.
610
     */
611 12
    public function stream_truncate($new_size)
612
    {
613 12
        if ($this->isReadOnly) {
614 6
            return false;
615
        }
616 12
        $this->needsFlush = true;
617 12
        $this->ensureWritableHandle();
618
619 12
        return ftruncate($this->handle, $new_size);
620
    }
621
622
    /**
623
     * Writes to the stream.
624
     *
625
     * @param string $data
626
     *
627
     * @return int The number of bytes that were successfully stored.
628
     */
629 60
    public function stream_write($data)
630
    {
631 60
        if ($this->isReadOnly) {
632 6
            return 0;
633
        }
634 60
        $this->needsFlush = true;
635 60
        $this->ensureWritableHandle();
636
637
        // Enforce append semantics.
638 60
        if ($this->isAppendMode) {
639 6
            StreamUtil::trySeek($this->handle, 0, SEEK_END);
640 6
        }
641
642 60
        $written = fwrite($this->handle, $data);
643 60
        $this->bytesWritten += $written;
644
645 60
        if (isset($this->streamWriteBuffer) && $this->bytesWritten >= $this->streamWriteBuffer) {
646 6
            $this->stream_flush();
647 6
        }
648
649 60
        return $written;
650
    }
651
652
    /**
653
     * Deletes a file.
654
     *
655
     * @param string $uri
656
     *
657
     * @return bool True on success, false on failure.
658
     */
659 12
    public function unlink($uri)
660
    {
661 12
        $this->uri = $uri;
662
663 12
        return $this->invoke($this->getFilesystem(), 'delete', [$this->getTarget()], 'unlink');
664
    }
665
666
    /**
667
     * Retrieves information about a file.
668
     *
669
     * @param string $uri
670
     * @param int    $flags
671
     *
672
     * @return array Output similar to stat().
673
     *
674
     * @see stat()
675
     */
676 84
    public function url_stat($uri, $flags)
677
    {
678 84
        $this->uri = $uri;
679
680
        try {
681 84
            return $this->getFilesystem()->stat($this->getTarget(), $flags);
682
683 36
        } catch (FileNotFoundException $e) {
684
            // File doesn't exist.
685 30
            if (!($flags & STREAM_URL_STAT_QUIET)) {
686 6
                $this->triggerError('stat', $e);
687 6
            }
688
689 36
        } catch (\Exception $e) {
690 6
            $this->triggerError('stat', $e);
691
        }
692
693 36
        return false;
694
    }
695
696
    /**
697
     * Returns a stream for a given path and mode.
698
     *
699
     * @param string $path The path to open.
700
     * @param string $mode The mode to open the stream in.
701
     *
702
     * @return resource|bool The file handle, or false.
703
     */
704 96
    protected function getStream($path, $mode)
705
    {
706 96
        switch ($mode[0]) {
707 96
            case 'r':
708 66
                $this->needsCowCheck = true;
709 66
                return $this->getFilesystem()->readStream($path);
710
711 90
            case 'w':
712 72
                $this->needsFlush = true;
713 72
                return fopen('php://temp', 'w+b');
714
715 24
            case 'a':
716 6
                return $this->getAppendStream($path);
717
718 24
            case 'x':
719 12
                return $this->getXStream($path);
720
721 12
            case 'c':
722 6
                return $this->getWritableStream($path);
723 6
        }
724
725 6
        return false;
726
    }
727
728
    /**
729
     * Returns a writable stream for a given path and mode.
730
     *
731
     * @param string $path The path to open.
732
     *
733
     * @return resource|bool The file handle, or false.
734
     */
735 6
    protected function getWritableStream($path)
736
    {
737
        try {
738 6
            $handle = $this->getFilesystem()->readStream($path);
739 6
            $this->needsCowCheck = true;
740
741 6
        } catch (FileNotFoundException $e) {
742 6
            $handle = fopen('php://temp', 'w+b');
743 6
            $this->needsFlush = true;
744
        }
745
746 6
        return $handle;
747
    }
748
749
    /**
750
     * Returns an appendable stream for a given path and mode.
751
     *
752
     * @param string $path The path to open.
753
     *
754
     * @return resource|bool The file handle, or false.
755
     */
756 6
    protected function getAppendStream($path)
757
    {
758 6
        if ($handle = $this->getWritableStream($path)) {
759 6
            StreamUtil::trySeek($handle, 0, SEEK_END);
760 6
        }
761
762 6
        return $handle;
763
    }
764
765
    /**
766
     * Returns a writable stream for a given path and mode.
767
     *
768
     * Triggers a warning if the file exists.
769
     *
770
     * @param string $path The path to open.
771
     *
772
     * @return resource|bool The file handle, or false.
773
     */
774 12
    protected function getXStream($path)
775
    {
776 12
        if ($this->getFilesystem()->has($path)) {
777 6
            trigger_error('fopen(): failed to open stream: File exists', E_USER_WARNING);
778
779 6
            return false;
780
        }
781
782 6
        $this->needsFlush = true;
783
784 6
        return fopen('php://temp', 'w+b');
785
    }
786
787
    /**
788
     * Guarantees that the handle is writable.
789
     */
790 60
    protected function ensureWritableHandle()
791
    {
792 60
        if (!$this->needsCowCheck) {
793 60
            return;
794
        }
795
796 18
        $this->needsCowCheck = false;
797
798 18
        if (StreamUtil::isWritable($this->handle)) {
799 9
            return;
800
        }
801
802 9
        $this->handle = StreamUtil::copy($this->handle);
803 9
    }
804
805
    /**
806
     * Returns the protocol from the internal URI.
807
     *
808
     * @return string The protocol.
809
     */
810 189
    protected function getProtocol()
811
    {
812 189
        return substr($this->uri, 0, strpos($this->uri, '://'));
813
    }
814
815
    /**
816
     * Returns the local writable target of the resource within the stream.
817
     *
818
     * @param string|null $uri The URI.
819
     *
820
     * @return string The path appropriate for use with Flysystem.
821
     */
822 195
    protected function getTarget($uri = null)
823
    {
824 195
        if (!isset($uri)) {
825 189
            $uri = $this->uri;
826 189
        }
827
828 195
        $target = substr($uri, strpos($uri, '://') + 3);
829
830 195
        return $target === false ? '' : $target;
831
    }
832
833
    /**
834
     * Returns the configuration.
835
     *
836
     * @param string|null $key The optional configuration key.
837
     *
838
     * @return array The requested configuration.
839
     */
840 21
    protected function getConfiguration($key = null)
841
    {
842 21
        return $key ? static::$config[$this->getProtocol()][$key] : static::$config[$this->getProtocol()];
843
    }
844
845
    /**
846
     * Returns the filesystem.
847
     *
848
     * @return \League\Flysystem\FilesystemInterface The filesystem object.
849
     */
850 189
    protected function getFilesystem()
851
    {
852 189
        if (isset($this->filesystem)) {
853 66
            return $this->filesystem;
854
        }
855
856 189
        $this->filesystem = static::$filesystems[$this->getProtocol()];
857
858 189
        return $this->filesystem;
859
    }
860
861
    /**
862
     * Calls a method on an object, catching any exceptions.
863
     *
864
     * @param object      $objet     The object to call the method on.
865
     * @param string      $method    The method name.
866
     * @param array       $args      The arguments to the method.
867
     * @param string|null $errorname The name of the calling function.
868
     *
869
     * @return mixed|false The return value of the call, or false on failure.
870
     */
871 177
    protected function invoke($objet, $method, array $args, $errorname = null)
872
    {
873
        try {
874 177
            return call_user_func_array([$objet, $method], $args);
875
876 78
        } catch (\Exception $e) {
877 78
            $errorname = $errorname ?: $method;
878 78
            $this->triggerError($errorname, $e);
879
        }
880
881 78
        return false;
882
    }
883
884
    /**
885
     * Calls trigger_error(), printing the appropriate message.
886
     *
887
     * @param string     $function
888
     * @param \Exception $e
889
     */
890 96
    protected function triggerError($function, \Exception $e)
891
    {
892 96
        if ($e instanceof TriggerErrorException) {
893 30
            trigger_error($e->formatMessage($function), E_USER_WARNING);
894 30
            return;
895
        }
896
897 72
        switch (get_class($e)) {
898 72
            case 'League\Flysystem\FileNotFoundException':
899 42
                trigger_error(sprintf('%s(): No such file or directory', $function), E_USER_WARNING);
900 42
                return;
901
902 30
            case 'League\Flysystem\RootViolationException':
903 6
                trigger_error(sprintf('%s(): Cannot remove the root directory', $function), E_USER_WARNING);
904 6
                return;
905 24
        }
906
907
        // Don't allow any exceptions to leak.
908 24
        trigger_error($e->getMessage(), E_USER_WARNING);
909 24
    }
910
911
    /**
912
     * Creates an advisory lock handle.
913
     *
914
     * @return resource|false
915
     */
916 6
    protected function openLockHandle()
917
    {
918
        // We are using md5() to avoid the file name limits, and case
919
        // insensitivity on Windows. This is not security sensitive.
920
921
        // PHP allows periods, '.', to be scheme names. Normalize the scheme
922
        // name to something that won't cause problems.
923 6
        $sub_dir = md5($this->getProtocol());
924
925
        // Since we're flattening out whole filesystems, at least create a
926
        // sub-directory for each scheme to attempt to limit the number of files
927
        // per directory.
928 6
        $temp_dir = sys_get_temp_dir() . '/flysystem-stream-wrapper/' . $sub_dir;
929
930
        // Race free directory creation. If @mkdir() fails, fopen() will fail
931
        // later, so there's no reason to test again.
932 6
        ! is_dir($temp_dir) && @mkdir($temp_dir, 0777, true);
933
934
        // Normalize paths so that locks are consistent.
935 6
        $lock_key = md5(Util::normalizePath($this->getTarget()));
936
937
        // Relay the lock to a real filesystem lock.
938 6
        return fopen($temp_dir . '/' . $lock_key, 'c');
939
    }
940
941
    /**
942
     * Releases the advisory lock.
943
     *
944
     * @param int $operation
945
     *
946
     * @return bool
947
     *
948
     * @see FlysystemStreamWrapper::stream_lock()
949
     */
950 6
    protected function releaseLock($operation)
951
    {
952 6
        $exists = is_resource($this->lockHandle);
953
954 6
        $success = $exists && flock($this->lockHandle, $operation);
955
956 6
        $exists && fclose($this->lockHandle);
957 6
        $this->lockHandle = null;
958
959 6
        return $success;
960
    }
961
}
962