Completed
Push — master ( d3599a...8604b6 )
by Chris
02:12
created

FlysystemStreamWrapper::triggerError()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 23
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 23
c 0
b 0
f 0
ccs 14
cts 14
cp 1
rs 8.7972
cc 4
eloc 12
nc 4
nop 2
crap 4
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 204
    public static function register($protocol, FilesystemInterface $filesystem, array $configuration = null)
162
    {
163 204
        if (static::streamWrapperExists($protocol)) {
164 3
            return false;
165
        }
166
167 204
        static::$config[$protocol] = $configuration ?: static::$defaultConfiguration;
168 204
        static::registerPlugins($protocol, $filesystem);
169 204
        static::$filesystems[$protocol] = $filesystem;
170
171 204
        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 204
    public static function unregister($protocol)
182
    {
183 204
        if ( ! static::streamWrapperExists($protocol)) {
184 3
            return false;
185
        }
186
187 204
        unset(static::$filesystems[$protocol]);
188
189 204
        return stream_wrapper_unregister($protocol);
190
    }
191
192
    /**
193
     * Unregisters all controlled stream wrappers.
194
     */
195 204
    public static function unregisterAll()
196
    {
197 204
        foreach (static::getRegisteredProtocols() as $protocol) {
198 201
            static::unregister($protocol);
199 204
        }
200 204
    }
201
202
    /**
203
     * @return array The list of registered protocols.
204
     */
205 204
    public static function getRegisteredProtocols()
206
    {
207 204
        return array_keys(static::$filesystems);
208
    }
209
210
    /**
211
     * Determines if a protocol is registered.
212
     *
213
     * @param string $protocol The protocol to check.
214
     *
215
     * @return bool True if it is registered, false if not.
216
     */
217 204
    protected static function streamWrapperExists($protocol)
218
    {
219 204
        return in_array($protocol, stream_get_wrappers(), true);
220
    }
221
222
    /**
223
     * Registers plugins on the filesystem.
224
     *
225
     * @param string              $protocol
226
     * @param FilesystemInterface $filesystem
227
     */
228 204
    protected static function registerPlugins($protocol, FilesystemInterface $filesystem)
229
    {
230 204
        $filesystem->addPlugin(new ForcedRename());
231 204
        $filesystem->addPlugin(new Mkdir());
232 204
        $filesystem->addPlugin(new Rmdir());
233
234 204
        $stat = new Stat(
235 204
            static::$config[$protocol]['permissions'],
236 204
            static::$config[$protocol]['metadata']
237 204
        );
238
239 204
        $filesystem->addPlugin($stat);
240 204
        $filesystem->addPlugin(new Touch());
241 204
    }
242
243
    /**
244
     * Closes the directory handle.
245
     *
246
     * @return bool True on success, false on failure.
247
     */
248 12
    public function dir_closedir()
249
    {
250 12
        unset($this->listing);
251
252 12
        return true;
253
    }
254
255
    /**
256
     * Opens a directory handle.
257
     *
258
     * @param string $uri     The URL that was passed to opendir().
259
     * @param int    $options Whether or not to enforce safe_mode (0x04).
260
     *
261
     * @return bool True on success, false on failure.
262
     */
263 18
    public function dir_opendir($uri, $options)
264
    {
265 18
        $this->uri = $uri;
266
267 18
        $path = Util::normalizePath($this->getTarget());
268
269 18
        $this->listing = $this->invoke($this->getFilesystem(), 'listContents', [$path], 'opendir');
270
271 18
        if ($this->listing === false) {
272 6
            return false;
273
        }
274
275 12
        if ( ! $dirlen = strlen($path)) {
276 6
            return true;
277
        }
278
279
        // Remove the separator /.
280 6
        $dirlen++;
281
282
        // Remove directory prefix.
283 6
        foreach ($this->listing as $delta => $item) {
284 6
            $this->listing[$delta]['path'] = substr($item['path'], $dirlen);
285 6
        }
286
287 6
        reset($this->listing);
288
289 6
        return true;
290
    }
291
292
    /**
293
     * Reads an entry from directory handle.
294
     *
295
     * @return string|bool The next filename, or false if there is no next file.
296
     */
297 12
    public function dir_readdir()
298
    {
299 12
        $current = current($this->listing);
300 12
        next($this->listing);
301
302 12
        return $current ? $current['path'] : false;
303
    }
304
305
    /**
306
     * Rewinds the directory handle.
307
     *
308
     * @return bool True on success, false on failure.
309
     */
310 12
    public function dir_rewinddir()
311
    {
312 12
        reset($this->listing);
313
314 12
        return true;
315
    }
316
317
    /**
318
     * Creates a directory.
319
     *
320
     * @param string $uri
321
     * @param int    $mode
322
     * @param int    $options
323
     *
324
     * @return bool True on success, false on failure.
325
     */
326 60
    public function mkdir($uri, $mode, $options)
327
    {
328 60
        $this->uri = $uri;
329
330 60
        return $this->invoke($this->getFilesystem(), 'mkdir', [$this->getTarget(), $mode, $options]);
331
    }
332
333
    /**
334
     * Renames a file or directory.
335
     *
336
     * @param string $uri_from
337
     * @param string $uri_to
338
     *
339
     * @return bool True on success, false on failure.
340
     */
341 36
    public function rename($uri_from, $uri_to)
342
    {
343 36
        $this->uri = $uri_from;
344 36
        $args = [$this->getTarget($uri_from), $this->getTarget($uri_to)];
345
346 36
        return $this->invoke($this->getFilesystem(), 'forcedRename', $args, 'rename');
347
    }
348
349
    /**
350
     * Removes a directory.
351
     *
352
     * @param string $uri
353
     * @param int    $options
354
     *
355
     * @return bool True on success, false on failure.
356
     */
357 18
    public function rmdir($uri, $options)
358
    {
359 18
        $this->uri = $uri;
360
361 18
        return $this->invoke($this->getFilesystem(), 'rmdir', [$this->getTarget(), $options]);
362
    }
363
364
    /**
365
     * Retrieves the underlaying resource.
366
     *
367
     * @param int $cast_as
368
     *
369
     * @return resource|bool The stream resource used by the wrapper, or false.
370
     */
371 6
    public function stream_cast($cast_as)
372
    {
373 6
        return $this->handle;
374
    }
375
376
    /**
377
     * Closes the resource.
378
     */
379 78
    public function stream_close()
380
    {
381
        // PHP 7 doesn't call flush automatically anymore for truncate() or when
382
        // writing an empty file. We need to ensure that the handle gets pushed
383
        // as needed in that case. This will be a no-op for php 5.
384 78
        $this->stream_flush();
385
386 78
        fclose($this->handle);
387 78
    }
388
389
    /**
390
     * Tests for end-of-file on a file pointer.
391
     *
392
     * @return bool True if the file is at the end, false if not.
393
     */
394 60
    public function stream_eof()
395
    {
396 60
        return feof($this->handle);
397
    }
398
399
    /**
400
     * Flushes the output.
401
     *
402
     * @return bool True on success, false on failure.
403
     */
404 78
    public function stream_flush()
405
    {
406 78
        if ( ! $this->needsFlush) {
407 78
            return true;
408
        }
409
410 78
        $this->needsFlush = false;
411 78
        $this->bytesWritten = 0;
412
413
        // Calling putStream() will rewind our handle. flush() shouldn't change
414
        // the position of the file.
415 78
        $pos = ftell($this->handle);
416
417 78
        $args = [$this->getTarget(), $this->handle];
418 78
        $success = $this->invoke($this->getFilesystem(), 'putStream', $args, 'fflush');
419
420 78
        fseek($this->handle, $pos);
421
422 78
        return $success;
423
    }
424
425
    /**
426
     * Advisory file locking.
427
     *
428
     * @param int $operation
429
     *
430
     * @return bool True on success, false on failure.
431
     */
432 6
    public function stream_lock($operation)
433
    {
434 6
        $operation = (int) $operation;
435
436 6
        if (($operation & \LOCK_UN) === \LOCK_UN) {
437 6
            return $this->releaseLock($operation);
438
        }
439
440
        // If the caller calls flock() twice, there's no reason to re-create the
441
        // lock handle.
442 6
        if (is_resource($this->lockHandle)) {
443 6
            return flock($this->lockHandle, $operation);
444
        }
445
446 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...
447
448 6
        return is_resource($this->lockHandle) && flock($this->lockHandle, $operation);
449
    }
450
451
    /**
452
     * Changes stream options.
453
     *
454
     * @param string $uri
455
     * @param int    $option
456
     * @param mixed  $value
457
     *
458
     * @return bool True on success, false on failure.
459
     */
460 39
    public function stream_metadata($uri, $option, $value)
461
    {
462 39
        $this->uri = $uri;
463
464
        switch ($option) {
465 39
            case STREAM_META_ACCESS:
466 15
                $permissions = octdec(substr(decoct($value), -4));
467 15
                $is_public = $permissions & $this->getConfiguration('public_mask');
468 15
                $visibility =  $is_public ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE;
469
470
                try {
471 15
                    return $this->getFilesystem()->setVisibility($this->getTarget(), $visibility);
472 9
                } catch (\LogicException $e) {
473
                    // The adapter doesn't support visibility.
474 9
                } catch (\Exception $e) {
475 6
                    $this->triggerError('chmod', $e);
476
477 6
                    return false;
478
                }
479
480 3
                return true;
481
482 27
            case STREAM_META_TOUCH:
483 27
                return $this->invoke($this->getFilesystem(), 'touch', [$this->getTarget()]);
484
485 6
            default:
486 6
                return false;
487 6
        }
488
    }
489
490
    /**
491
     * Opens file or URL.
492
     *
493
     * @param string $uri
494
     * @param string $mode
495
     * @param int    $options
496
     * @param string &$opened_path
497
     *
498
     * @return bool True on success, false on failure.
499
     */
500 96
    public function stream_open($uri, $mode, $options, &$opened_path)
501
    {
502 96
        $this->uri = $uri;
503 96
        $path = $this->getTarget();
504
505 96
        $this->isReadOnly = StreamUtil::modeIsReadOnly($mode);
506 96
        $this->isWriteOnly = StreamUtil::modeIsWriteOnly($mode);
507 96
        $this->isAppendMode = StreamUtil::modeIsAppendable($mode);
508
509 96
        $this->handle = $this->invoke($this, 'getStream', [$path, $mode], 'fopen');
510
511 96
        if ($this->handle && $options & STREAM_USE_PATH) {
512 6
            $opened_path = $path;
513 6
        }
514
515 96
        return is_resource($this->handle);
516
    }
517
518
    /**
519
     * Reads from stream.
520
     *
521
     * @param int $count
522
     *
523
     * @return string The bytes read.
524
     */
525 60
    public function stream_read($count)
526
    {
527 60
        if ($this->isWriteOnly) {
528 6
            return '';
529
        }
530
531 60
        return fread($this->handle, $count);
532
    }
533
534
    /**
535
     * Seeks to specific location in a stream.
536
     *
537
     * @param int $offset
538
     * @param int $whence
539
     *
540
     * @return bool True on success, false on failure.
541
     */
542 24
    public function stream_seek($offset, $whence = SEEK_SET)
543
    {
544 24
        return fseek($this->handle, $offset, $whence) === 0;
545
    }
546
547
    /**
548
     * Changes stream options.
549
     *
550
     * @param int $option
551
     * @param int $arg1
552
     * @param int $arg2
553
     *
554
     * @return bool True on success, false on failure.
555
     */
556 6
    public function stream_set_option($option, $arg1, $arg2)
557
    {
558
        switch ($option) {
559 6
            case STREAM_OPTION_BLOCKING:
560
                // This works for the local adapter. It doesn't do anything for
561
                // memory streams.
562 6
                return stream_set_blocking($this->handle, $arg1);
563
564 6
            case STREAM_OPTION_READ_TIMEOUT:
565 6
                return  stream_set_timeout($this->handle, $arg1, $arg2);
566
567 6
            case STREAM_OPTION_READ_BUFFER:
568 6
                if ($arg1 === STREAM_BUFFER_NONE) {
569 6
                    return stream_set_read_buffer($this->handle, 0) === 0;
570
                }
571
572 6
                return stream_set_read_buffer($this->handle, $arg2) === 0;
573
574 6
            case STREAM_OPTION_WRITE_BUFFER:
575 6
                $this->streamWriteBuffer = $arg1 === STREAM_BUFFER_NONE ? 0 : $arg2;
576
577 6
                return true;
578
        }
579
580 6
        return false;
581
    }
582
583
    /**
584
     * Retrieves information about a file resource.
585
     *
586
     * @return array A similar array to fstat().
587
     *
588
     * @see fstat()
589
     */
590 66
    public function stream_stat()
591
    {
592
        // Get metadata from original file.
593 66
        $stat = $this->url_stat($this->uri, static::STREAM_URL_IGNORE_SIZE | STREAM_URL_STAT_QUIET) ?: [];
594
595
        // Newly created file.
596 66
        if (empty($stat['mode'])) {
597 6
            $stat['mode'] = 0100000 + $this->getConfiguration('permissions')['file']['public'];
598 6
            $stat[2] = $stat['mode'];
599 6
        }
600
601
        // Use the size of our handle, since it could have been written to or
602
        // truncated.
603 66
        $stat['size'] = $stat[7] = StreamUtil::getSize($this->handle);
604
605 66
        return $stat;
606
    }
607
608
    /**
609
     * Retrieves the current position of a stream.
610
     *
611
     * @return int The current position of the stream.
612
     */
613 24
    public function stream_tell()
614
    {
615 24
        if ($this->isAppendMode) {
616 6
            return 0;
617
        }
618
619 18
        return ftell($this->handle);
620
    }
621
622
    /**
623
     * Truncates the stream.
624
     *
625
     * @param int $new_size
626
     *
627
     * @return bool True on success, false on failure.
628
     */
629 12
    public function stream_truncate($new_size)
630
    {
631 12
        if ($this->isReadOnly) {
632 6
            return false;
633
        }
634 12
        $this->needsFlush = true;
635 12
        $this->ensureWritableHandle();
636
637 12
        return ftruncate($this->handle, $new_size);
638
    }
639
640
    /**
641
     * Writes to the stream.
642
     *
643
     * @param string $data
644
     *
645
     * @return int The number of bytes that were successfully stored.
646
     */
647 60
    public function stream_write($data)
648
    {
649 60
        if ($this->isReadOnly) {
650 6
            return 0;
651
        }
652 60
        $this->needsFlush = true;
653 60
        $this->ensureWritableHandle();
654
655
        // Enforce append semantics.
656 60
        if ($this->isAppendMode) {
657 6
            StreamUtil::trySeek($this->handle, 0, SEEK_END);
658 6
        }
659
660 60
        $written = fwrite($this->handle, $data);
661 60
        $this->bytesWritten += $written;
662
663 60
        if (isset($this->streamWriteBuffer) && $this->bytesWritten >= $this->streamWriteBuffer) {
664 6
            $this->stream_flush();
665 6
        }
666
667 60
        return $written;
668
    }
669
670
    /**
671
     * Deletes a file.
672
     *
673
     * @param string $uri
674
     *
675
     * @return bool True on success, false on failure.
676
     */
677 12
    public function unlink($uri)
678
    {
679 12
        $this->uri = $uri;
680
681 12
        return $this->invoke($this->getFilesystem(), 'delete', [$this->getTarget()], 'unlink');
682
    }
683
684
    /**
685
     * Retrieves information about a file.
686
     *
687
     * @param string $uri
688
     * @param int    $flags
689
     *
690
     * @return array Output similar to stat().
691
     *
692
     * @see stat()
693
     */
694 84
    public function url_stat($uri, $flags)
695
    {
696 84
        $this->uri = $uri;
697
698
        try {
699 84
            return $this->getFilesystem()->stat($this->getTarget(), $flags);
700 36
        } catch (FileNotFoundException $e) {
701
            // File doesn't exist.
702 30
            if ( ! ($flags & STREAM_URL_STAT_QUIET)) {
703 6
                $this->triggerError('stat', $e);
704 6
            }
705 36
        } catch (\Exception $e) {
706 6
            $this->triggerError('stat', $e);
707
        }
708
709 36
        return false;
710
    }
711
712
    /**
713
     * Returns a stream for a given path and mode.
714
     *
715
     * @param string $path The path to open.
716
     * @param string $mode The mode to open the stream in.
717
     *
718
     * @return resource|bool The file handle, or false.
719
     */
720 96
    protected function getStream($path, $mode)
721
    {
722 96
        switch ($mode[0]) {
723 96
            case 'r':
724 66
                $this->needsCowCheck = true;
725
726 66
                return $this->getFilesystem()->readStream($path);
727
728 90
            case 'w':
729 72
                $this->needsFlush = true;
730
731 72
                return fopen('php://temp', 'w+b');
732
733 24
            case 'a':
734 6
                return $this->getAppendStream($path);
735
736 24
            case 'x':
737 12
                return $this->getXStream($path);
738
739 12
            case 'c':
740 6
                return $this->getWritableStream($path);
741 6
        }
742
743 6
        return false;
744
    }
745
746
    /**
747
     * Returns a writable stream for a given path and mode.
748
     *
749
     * @param string $path The path to open.
750
     *
751
     * @return resource|bool The file handle, or false.
752
     */
753 6
    protected function getWritableStream($path)
754
    {
755
        try {
756 6
            $handle = $this->getFilesystem()->readStream($path);
757 6
            $this->needsCowCheck = true;
758 6
        } catch (FileNotFoundException $e) {
759 6
            $handle = fopen('php://temp', 'w+b');
760 6
            $this->needsFlush = true;
761
        }
762
763 6
        return $handle;
764
    }
765
766
    /**
767
     * Returns an appendable stream for a given path and mode.
768
     *
769
     * @param string $path The path to open.
770
     *
771
     * @return resource|bool The file handle, or false.
772
     */
773 6
    protected function getAppendStream($path)
774
    {
775 6
        if ($handle = $this->getWritableStream($path)) {
776 6
            StreamUtil::trySeek($handle, 0, SEEK_END);
777 6
        }
778
779 6
        return $handle;
780
    }
781
782
    /**
783
     * Returns a writable stream for a given path and mode.
784
     *
785
     * Triggers a warning if the file exists.
786
     *
787
     * @param string $path The path to open.
788
     *
789
     * @return resource|bool The file handle, or false.
790
     */
791 12
    protected function getXStream($path)
792
    {
793 12
        if ($this->getFilesystem()->has($path)) {
794 6
            trigger_error('fopen(): failed to open stream: File exists', E_USER_WARNING);
795
796 6
            return false;
797
        }
798
799 6
        $this->needsFlush = true;
800
801 6
        return fopen('php://temp', 'w+b');
802
    }
803
804
    /**
805
     * Guarantees that the handle is writable.
806
     */
807 60
    protected function ensureWritableHandle()
808
    {
809 60
        if ( ! $this->needsCowCheck) {
810 60
            return;
811
        }
812
813 18
        $this->needsCowCheck = false;
814
815 18
        if (StreamUtil::isWritable($this->handle)) {
816 9
            return;
817
        }
818
819 9
        $this->handle = StreamUtil::copy($this->handle);
820 9
    }
821
822
    /**
823
     * Returns the protocol from the internal URI.
824
     *
825
     * @return string The protocol.
826
     */
827 189
    protected function getProtocol()
828
    {
829 189
        return substr($this->uri, 0, strpos($this->uri, '://'));
830
    }
831
832
    /**
833
     * Returns the local writable target of the resource within the stream.
834
     *
835
     * @param string|null $uri The URI.
836
     *
837
     * @return string The path appropriate for use with Flysystem.
838
     */
839 195
    protected function getTarget($uri = null)
840
    {
841 195
        if ( ! isset($uri)) {
842 189
            $uri = $this->uri;
843 189
        }
844
845 195
        $target = substr($uri, strpos($uri, '://') + 3);
846
847 195
        return $target === false ? '' : $target;
848
    }
849
850
    /**
851
     * Returns the configuration.
852
     *
853
     * @param string|null $key The optional configuration key.
854
     *
855
     * @return array The requested configuration.
856
     */
857 21
    protected function getConfiguration($key = null)
858
    {
859 21
        return $key ? static::$config[$this->getProtocol()][$key] : static::$config[$this->getProtocol()];
860
    }
861
862
    /**
863
     * Returns the filesystem.
864
     *
865
     * @return \League\Flysystem\FilesystemInterface The filesystem object.
866
     */
867 189
    protected function getFilesystem()
868
    {
869 189
        if (isset($this->filesystem)) {
870 66
            return $this->filesystem;
871
        }
872
873 189
        $this->filesystem = static::$filesystems[$this->getProtocol()];
874
875 189
        return $this->filesystem;
876
    }
877
878
    /**
879
     * Calls a method on an object, catching any exceptions.
880
     *
881
     * @param object      $objet     The object to call the method on.
882
     * @param string      $method    The method name.
883
     * @param array       $args      The arguments to the method.
884
     * @param string|null $errorname The name of the calling function.
885
     *
886
     * @return mixed|false The return value of the call, or false on failure.
887
     */
888 177
    protected function invoke($objet, $method, array $args, $errorname = null)
889
    {
890
        try {
891 177
            return call_user_func_array([$objet, $method], $args);
892 78
        } catch (\Exception $e) {
893 78
            $errorname = $errorname ?: $method;
894 78
            $this->triggerError($errorname, $e);
895
        }
896
897 78
        return false;
898
    }
899
900
    /**
901
     * Calls trigger_error(), printing the appropriate message.
902
     *
903
     * @param string     $function
904
     * @param \Exception $e
905
     */
906 96
    protected function triggerError($function, \Exception $e)
907
    {
908 96
        if ($e instanceof TriggerErrorException) {
909 30
            trigger_error($e->formatMessage($function), E_USER_WARNING);
910
911 30
            return;
912
        }
913
914 72
        switch (get_class($e)) {
915 72
            case 'League\Flysystem\FileNotFoundException':
916 42
                trigger_error(sprintf('%s(): No such file or directory', $function), E_USER_WARNING);
917
918 42
                return;
919
920 30
            case 'League\Flysystem\RootViolationException':
921 6
                trigger_error(sprintf('%s(): Cannot remove the root directory', $function), E_USER_WARNING);
922
923 6
                return;
924 24
        }
925
926
        // Don't allow any exceptions to leak.
927 24
        trigger_error($e->getMessage(), E_USER_WARNING);
928 24
    }
929
930
    /**
931
     * Creates an advisory lock handle.
932
     *
933
     * @return resource|false
934
     */
935 6
    protected function openLockHandle()
936
    {
937
        // PHP allows periods, '.', to be scheme names. Normalize the scheme
938
        // name to something that won't cause problems. Also, avoid problems
939
        // with case-insensitive filesystems. We use bin2hex() rather than a
940
        // hashing function since most scheme names are small, and bin2hex()
941
        // only doubles the string length.
942 6
        $sub_dir = bin2hex($this->getProtocol());
943
944
        // Since we're flattening out whole filesystems, at least create a
945
        // sub-directory for each scheme to attempt to reduce the number of
946
        // files per directory.
947 6
        $temp_dir = sys_get_temp_dir() . '/flysystem-stream-wrapper/' . $sub_dir;
948
949
        // Race free directory creation. If @mkdir() fails, fopen() will fail
950
        // later, so there's no reason to test again.
951 6
        ! is_dir($temp_dir) && @mkdir($temp_dir, 0777, true);
952
953
        // Normalize paths so that locks are consistent.
954
        // We are using sha1() to avoid the file name limits, and case
955
        // insensitivity on Windows. This is not security sensitive.
956 6
        $lock_key = sha1(Util::normalizePath($this->getTarget()));
957
958
        // Relay the lock to a real filesystem lock.
959 6
        return fopen($temp_dir . '/' . $lock_key, 'c');
960
    }
961
962
    /**
963
     * Releases the advisory lock.
964
     *
965
     * @param int $operation
966
     *
967
     * @return bool
968
     *
969
     * @see FlysystemStreamWrapper::stream_lock()
970
     */
971 6
    protected function releaseLock($operation)
972
    {
973 6
        $exists = is_resource($this->lockHandle);
974
975 6
        $success = $exists && flock($this->lockHandle, $operation);
976
977 6
        $exists && fclose($this->lockHandle);
978 6
        $this->lockHandle = null;
979
980 6
        return $success;
981
    }
982
}
983