Completed
Push — master ( 5c1aea...9ef1c4 )
by Luke
03:03
created

Resource::isWritable()   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 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * CSVelte: Slender, elegant CSV for PHP.
4
 *
5
 * Inspired by Python's CSV module and Frictionless Data and the W3C's CSV
6
 * standardization efforts, CSVelte was written in an effort to take all the
7
 * suck out of working with CSV.
8
 *
9
 * @version   v0.2.1
10
 * @copyright Copyright (c) 2016 Luke Visinoni <[email protected]>
11
 * @author    Luke Visinoni <[email protected]>
12
 * @license   https://github.com/deni-zen/csvelte/blob/master/LICENSE The MIT License (MIT)
13
 */
14
namespace CSVelte\IO;
15
16
use CSVelte\Contract\Streamable;
17
use \InvalidArgumentException;
18
use CSVelte\Exception\IOException;
19
20
/**
21
 * Stream Resource.
22
 *
23
 * Represents a stream resource connection. May be open or closed. This allows
24
 * me to provide a nice, clean, easy-to-use interface for opening stream
25
 * resources in a particular mode as well as to lazy-open a stream.
26
 *
27
 * @package    CSVelte
28
 * @subpackage CSVelte\IO
29
 * @copyright  (c) 2016, Luke Visinoni <[email protected]>
30
 * @author     Luke Visinoni <[email protected]>
31
 * @since      v0.2.1
32
 */
33
class Resource
34
{
35
    /**
36
     * Available base access modes
37
     * @var string base access mode must be one of these letters
38
     */
39
    protected static $bases = "rwaxc";
40
41
    /**
42
     * Hash of readable/writable stream open mode types.
43
     *
44
     * Mercilessly stolen from:
45
     * https://github.com/guzzle/streams/blob/master/src/Stream.php
46
     *
47
     * My kudos and sincere thanks go out to Michael Dowling and Graham Campbell
48
     * of the guzzle/streams PHP package. Thanks for the inspiration (in some cases)
49
     * and the not suing me for outright theft (in this case).
50
     *
51
     * @var array Hash of readable and writable stream types
52
     * @todo I think I can get rid of this by simply checking whether base is a
53
     *     particular letter OR plus is present... try it
54
     * @todo Why are x and c (alone) not even on either of these lists?
55
     *       I just figured out why... readable and writable default to false. So
56
     *       only modes that change that default behavior are listed here
57
     */
58
    protected static $readWriteHash = [
59
        'read' => [
60
            'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
61
            'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
62
            'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
63
            'x+t' => true, 'c+t' => true, 'a+' => true,
64
        ],
65
        'write' => [
66
            'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
67
            'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
68
            'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
69
            'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true,
70
        ],
71
    ];
72
73
    /**
74
     * Stream URI.
75
     *
76
     * Contains the stream URI to connect to.
77
     *
78
     * @var string The stream uri
79
     */
80
    protected $uri;
81
82
    /**
83
     * Stream resource handle.
84
     *
85
     * Contains the underlying stream resource handle (if there is one).
86
     * Otherwise it will be null.
87
     *
88
     * @var resource The stream resource handle
89
     */
90
    protected $conn;
91
92
    /**
93
     * Lazy open switch.
94
     *
95
     * Determines whether the actual fopen for this resource should be delayed
96
     * until an I/O operation is performed.
97
     *
98
     * @var boolean True if connection is lazy
99
     */
100
    protected $lazy;
101
102
    /**
103
     * Extra context to open the resource with.
104
     *
105
     * An associative array of context options and parameters.
106
     *
107
     * @var array An associative array of stream context options and params
108
     * @see http://php.net/manual/en/stream.contexts.php
109
     */
110
    protected $context = [
111
        'options' => [],
112
        'params' => []
113
    ];
114
115
    /**
116
     * Context resource handle.
117
     *
118
     * Holds a context resource handle object for $this->context
119
     *
120
     * @var resource The context resource handle
121
     */
122
    protected $crh;
123
124
    /**
125
     * Should fopen use include path?
126
     *
127
     * @var boolean True if fopen should use the include path to find potential files
128
     */
129
    protected $useIncludePath;
130
131
    /**
132
     * Base open mode.
133
     *
134
     * @var string A single character for base open mode (r, w, a, x or c)
135
     */
136
    protected $base = '';
137
138
    /**
139
     * Plus reading or plus writing.
140
     *
141
     * @var string Either a plus or an empty string
142
     */
143
    protected $plus = '';
144
145
    /**
146
     * Binary or text flag.
147
     *
148
     * @var string Either "b" or "t" for binary or text
149
     */
150
    protected $flag = '';
151
152
    /**
153
     * Does access mode string indicate readability?
154
     *
155
     * @var bool Whether access mode indicates readability
156
     */
157
    protected $readable = false;
158
159
    /**
160
     * Does access mode string indicate writability
161
     *
162
     * @var bool Whether access mode indicates writability
163
     */
164
    protected $writable = false;
165
166
    /**
167
     * Resource constructor.
168
     *
169
     * Instantiates a stream resource. If lazy is set to true, the connection
170
     * is delayed until the first call to getResource().
171
     *
172
     * @param string|resource $uri The URI to connect to OR a stream resource handle
173
     * @param string $mode The connection mode
174
     * @param boolean $lazy Whether connection should be deferred until an I/O
175
     *     operation is requested (such as read or write) on the attached stream
176
     * @param boolean|null $use_include_path
177
     * @param array|null $context_options
178
     * @param array|null $context_params
179
     * @todo Does stream_get_meta_data belong in Stream or Resource?
180
     */
181 99
    public function __construct(
182
        $uri,
183
        $mode = null,
184
        $lazy = null,
185
        $use_include_path = null,
186
        $context_options = null,
187
        $context_params = null
188
    ) {
189
        // first, check if we're wrapping an existing stream resource
190 99
        if (is_resource($uri)) {
191 6
            $this->initWithResource($uri);
192 5
            return;
193
        }
194
195
        // ok we're opening a new stream resource handle
196 93
        $this->setUri($uri)
197 92
             ->setMode($mode)
198 92
             ->setLazy($lazy)
199 92
             ->setUseIncludePath($use_include_path)
200 92
             ->setContext($context_options, $context_params);
201 92
        if (!$this->isLazy()) {
202 4
            $this->connect();
203 3
        }
204 91
    }
205
206
    /**
207
     * Initialize resource with PHP resource variable.
208
     *
209
     * Uses a PHP resource variable to initialize this class.
210
     *
211
     * @param resource $handle The stream resource to initialize
212
     * @return bool
213
     */
214 6
    protected function initWithResource($handle)
215
    {
216 6
        if (($resource_type = get_resource_type($handle)) != ($exp_resource_type = "stream")) {
217 1
            throw new InvalidArgumentException(sprintf(
218 1
                'Invalid stream resource type for %s, expected "%s", got: "%s"',
219 1
                __METHOD__,
220 1
                $exp_resource_type,
221
                $resource_type
222 1
            ));
223
        }
224
        // set all this manually
225 5
        $meta = stream_get_meta_data($handle);
226 5
        $this->setUri($meta['uri'])
227 5
            ->setMode($meta['mode']);
228 5
        $this->conn = $handle;
229 5
        return true;
230
    }
231
232
    /**
233
     * Class destructor
234
     */
235 93
    public function __destruct()
236
    {
237 93
        $this->disconnect();
238 93
    }
239
240
    /**
241
     * Invoke magic method.
242
     *
243
     * Creates and returns a Stream object for this resource
244
     *
245
     * @return Streamable A stream for this resource
246
     */
247 7
    public function __invoke()
248
    {
249 7
        return new Stream($this);
250
    }
251
252
    /**
253
     * Connect (open connection) to file/stream.
254
     *
255
     * File open is (by default) delayed until the user explicitly calls connect()
256
     * or they request the resource handle with getHandle().
257
     *
258
     * @return boolean True if connection was successful
259
     * @throws \CSVelte\Exception\IOException if connection fails
260
     */
261 82
    public function connect()
262
    {
263 82
        if (!$this->isConnected()) {
264 82
            $e = null;
265 82
            $errhandler = function () use (&$e) {
266 4
                $e = new IOException(sprintf(
267 4
                    "Could not open connection for %s using mode %s",
268 4
                    $this->getUri(),
269 4
                    $this->getMode()
270 4
                ), IOException::ERR_STREAM_CONNECTION_FAILED);
271 82
            };
272 82
            set_error_handler($errhandler->bindTo($this));
273 82
            $this->conn = fopen(
274 82
                $this->getUri(),
275 82
                $this->getMode(),
276 82
                $this->getUseIncludePath(),
277 82
                $this->getContext()
278 82
            );
279 82
            restore_error_handler();
280 82
            if ($e) throw $e;
281 78
        }
282 78
        return $this->isConnected();
283
    }
284
285
    /**
286
     * Close connection.
287
     *
288
     * Close the connection to this stream (if open).
289
     *
290
     * @return boolean|null Whether close was successful, or null if already closed
291
     */
292 93
    public function disconnect()
293
    {
294 93
        if ($this->isConnected()) {
295 83
            return fclose($this->conn);
296
        }
297
        // return null if nothing to close
298 82
        return null;
299
    }
300
301
    /**
302
     * Set stream URI.
303
     *
304
     * Set the stream URI. Can only be set if the connection isn't open yet.
305
     * If you try to set the URI on an open resource, an IOException will be thrown
306
     *
307
     * @param string $uri The URI for this stream resource to open
308
     * @return $this
309
     * @throws \InvalidArgumentException if not a valid stream uri
310
     * @throws \CSVelte\Exception\IOException if stream has already been opened
311
     * @todo I'm pretty sure that the parse_url function is too restrictive. It
312
     *     will reject URIs that are perfectly valid.
313
     */
314 98
    public function setUri($uri)
315
    {
316 98
        $this->assertNotConnected(__METHOD__);
317
318 98
        if (is_object($uri) && !method_exists($uri, '__toString')) {
319 1
            throw new InvalidArgumentException(sprintf(
320 1
                'Not a valid stream uri, expected "string", got: "%s"',
321 1
                gettype($uri)
322 1
            ));
323
        }
324
325 97
        $uri = (string) $uri;
326 97
        if (parse_url($uri)) {
327 97
            $this->uri = $uri;
328 97
            return $this;
329
        }
330
    }
331
332
    /**
333
     * Set the fopen mode.
334
     *
335
     * Thank you to GitHub user "binsoul" whose AccessMode class inspired this
336
     * Also thanks to the author(s) of Guzzle streams implementation, where the
337
     * readwritehash idea came from. Both libraries are MIT licensed, so my
338
     * merciless theft of their code is alright.
339
     *
340
     * @param string $mode A 1-3 character string determining open mode
341
     * @return $this
342
     * @throws \InvalidArgumentException if not a valid stream access mode
343
     * @throws \CSVelte\Exception\IOException if stream has already been opened
344
     * @see http://php.net/manual/en/function.fopen.php
345
     * @see https://github.com/binsoul/io-stream/blob/master/src/AccessMode.php
346
     * @see https://raw.githubusercontent.com/guzzle/streams/master/src/Stream.php
347
     * @todo convert $mode to lower case and test it
348
     */
349 97
    public function setMode($mode = null)
350
    {
351 97
        $this->assertNotConnected(__METHOD__);
352 97
        if (is_null($mode)) $mode = "r+b";
353
354 97
        $mode = substr($mode, 0, 3);
355 97
        $rest = substr($mode, 1);
356
357 97
        $base = substr($mode, 0, 1);
358 97
        $plus = (strpos($rest, '+') !== false) ? '+' : '';
359 97
        $flag = trim($rest, '+');
360
361 97
        $this->flag = '';
362 97
        $this->setBaseMode($base)
363 97
             ->setIsPlus($plus == '+')
364 97
             ->setIsText($flag == 't')
365 97
             ->setIsBinary($flag == 'b');
366
367 97
        return $this;
368
    }
369
370
    /**
371
     * Update access parameters.
372
     *
373
     * After changing any of the access mode parameters, this method must be
374
     * called in order for readable and writable to stay accurate.
375
     *
376
     * @return $this
377
     */
378 97
    protected function updateAccess()
379
    {
380 97
        $this->readable = isset(self::$readWriteHash['read'][$this->getMode()]);
381 97
        $this->writable = isset(self::$readWriteHash['write'][$this->getMode()]);
382 97
        return $this;
383
    }
384
385
    /**
386
     * Set base access mode character.
387
     *
388
     * @param string $base The base mode character (must be one of "rwaxc")
389
     * @return $this
390
     * @throws \InvalidArgumentException If passed invalid base char
391
     * @throws \CSVelte\Exception\IOException if stream has already been opened
392
     */
393 97
    public function setBaseMode($base)
394
    {
395 97
        $this->assertNotConnected(__METHOD__);
396 97
        if (strpos(self::$bases, $base) === false) {
397
            throw new InvalidArgumentException("\"{$base}\" is not a valid base stream access mode.");
398
        }
399 97
        $this->base = $base;
400 97
        return $this->updateAccess();
401
    }
402
403
    /**
404
     * Set plus mode.
405
     *
406
     * @param boolean $isPlus Whether base access mode should include the + sign
407
     * @return $this
408
     * @throws \CSVelte\Exception\IOException if stream has already been opened
409
     */
410 97
    public function setIsPlus($isPlus)
411
    {
412 97
        $this->assertNotConnected(__METHOD__);
413 97
        $this->plus = $isPlus ? '+' : '';
414 97
        return $this->updateAccess();
415
    }
416
417
    /**
418
     * Set binary-safe mode.
419
     *
420
     * @param boolean $isBinary Whether binary safe mode or not
421
     * @return $this
422
     * @throws \CSVelte\Exception\IOException if stream has already been opened
423
     */
424 97
    public function setIsBinary($isBinary)
425
    {
426 97
        $this->assertNotConnected(__METHOD__);
427 97
        if ($isBinary) {
428 70
            $this->flag = 'b';
429 70
        }
430 97
        return $this;
431
    }
432
433
    /**
434
     * Set text mode.
435
     *
436
     * @param boolean $isText Whether text mode or not
437
     * @return $this
438
     * @throws \CSVelte\Exception\IOException if stream has already been opened
439
     */
440 97
    public function setIsText($isText)
441
    {
442 97
        $this->assertNotConnected(__METHOD__);
443 97
        if ($isText) {
444 1
            $this->flag = 't';
445 1
        }
446 97
        return $this;
447
    }
448
449
    /**
450
     * Set lazy flag.
451
     *
452
     * Set the lazy flag, which tells the class whether to defer the connection
453
     * until the user specifically requests it.
454
     *
455
     * @param boolean|null Whether or not to "lazily" open the stream
456
     * @return $this
457
     */
458 92
    protected function setLazy($lazy)
459
    {
460 92
        if (is_null($lazy)) $lazy = true;
461 92
        $this->lazy = (boolean) $lazy;
462 92
        return $this;
463
    }
464
465
    /**
466
     * Set use include path flag.
467
     *
468
     * Sets whether or not fopen should search the include path for files. Can
469
     * only be set if resource isn't open already. If called when resource is
470
     * already open an exception will be thrown.
471
     *
472
     * @param boolean $use_include_path Whether to search include path for files
473
     * @throws \CSVelte\Exception\IOException
474
     * @return $this
475
     */
476 92
    public function setUseIncludePath($use_include_path)
477
    {
478 92
        $this->assertNotConnected(__METHOD__);
479 92
        $this->useIncludePath = (boolean) $use_include_path;
480 92
        return $this;
481
    }
482
483
    /**
484
     * Set stream context options and params.
485
     *
486
     * Sets arrays of stream context options and params. Check out the URI below
487
     * for more on stream contexts.
488
     *
489
     * @param array|null $options Stream context options
490
     * @param array|null $params  Stream Context params
491
     * @return $this
492
     * @see http://php.net/manual/en/stream.contexts.php
493
     */
494 92
    public function setContext($options = null, $params = null)
495
    {
496 92
        if (is_array($options)) {
497 2
            foreach ($options as $wrap => $opts) {
498 2
                $this->setContextOptions($opts, $wrap);
499 2
            }
500 2
        }
501 92
        if (!is_null($params)) {
502 2
            $this->setContextParams($params);
503 2
        }
504 92
        return $this;
505
    }
506
507
    /**
508
     * Set context resource directly
509
     * @param resource|null $context Stream context resource to set directly
510
     * @return $this
511
     * @see http://php.net/manual/en/function.stream-context-create.php
512
     * @todo Need to write a unit test for passing this method a null value
513
     */
514 78
    public function setContextResource($context)
515
    {
516 78
        if (!is_null($context)) {
517 8
            if (!is_resource($context) || get_resource_type($context) != "stream-context") {
518 4
                throw new InvalidArgumentException(sprintf(
519 4
                    "Invalid argument for %s. Expecting resource of type \"stream-context\" but got: \"%s\"",
520 4
                    __METHOD__,
521 4
                    gettype($context)
522 4
                ));
523
            }
524
            // don't need to call updateContext() because its already a context resource
525 4
            $this->crh = $context;
526 4
        }
527 74
        return $this;
528
    }
529
530
    /**
531
     * Update the stream context.
532
     *
533
     * After setting/updating stream context options and/or params, this method
534
     * must be called in order to update the stream context resource.
535
     *
536
     * @return $this
537
     */
538 2
    protected function updateContext()
539
    {
540
        // if already connected, set the options on the context resource
541
        // otherwise, it will be set at connection time
542 2
        if ($this->isConnected()) {
543
            // set options and params on existing stream resource
544 1
            stream_context_set_params(
545 1
                $this->getContext(),
546 1
                $this->getContextParams() + [
547 1
                    'options' => $this->getContextOptions()
548 1
                ]
549 1
            );
550 1
        }
551 2
        return $this;
552
    }
553
554
    /**
555
     * Set context options.
556
     *
557
     * Sets stream context options for this stream resource.
558
     *
559
     * @param array $options An array of stream context options
560
     * @param string $wrapper The wrapper these options belong to (if no wrapper
561
     *     argument, then $options should be an associative array with key being
562
     *     a wrapper name and value being its options)
563
     * @return $this
564
     * @throws \InvalidArgumentException if passed invalid options or wrapper
565
     * @see http://php.net/manual/en/stream.contexts.php
566
     */
567 2
    public function setContextOptions($options, $wrapper = null)
568
    {
569 2
        if (is_array($options)) {
570 2
            if (is_null($wrapper)) {
571
                $this->context['options'] = $options;
572
            } else {
573 2
                $this->assertValidWrapper($wrapper);
574 2
                $this->context['options'][$wrapper] = $options;
575
            }
576 2
            $this->updateContext();
577 2
            return $this;
578
        }
579
        throw new InvalidArgumentException("Context options must be an array, got: " . gettype($options));
580
    }
581
582
    /**
583
     * Set context params.
584
     *
585
     * Set the context params for this stream resource.
586
     *
587
     * @param array $params An array of stream resource params
588
     * @return $this
589
     * @throws \InvalidArgumentException if passed invalid params
590
     * @see http://php.net/manual/en/stream.contexts.php
591
     */
592 2
    public function setContextParams($params)
593
    {
594 2
        if (is_array($params)) {
595 2
            $this->context['params'] = $params;
596 2
            $this->updateContext();
597 2
            return $this;
598
        }
599
        throw new InvalidArgumentException("Context parameters must be an array, got: " . gettype($params));
600
    }
601
602
    /**
603
     * Get context options for this stream resource.
604
     *
605
     * Returns the stream context options for this stream resource. Either all
606
     * options for all wrappers, or just the options for the specified wrapper.
607
     *
608
     * @param  string $wrapper If present, return options only for this wrapper
609
     * @return array Context options (either all or for specified wrapper)
610
     * @throws \InvalidArgumentException if the wrapper doesn't exist
611
     */
612 82
    public function getContextOptions($wrapper = null)
613
    {
614 82
        if (is_null($wrapper)) {
615 82
            return $this->context['options'];
616
        }
617
        $this->assertValidWrapper($wrapper);
618
        if (isset($this->context['options'][$wrapper])) {
619
            return $this->context['options'][$wrapper];
620
        }
621
    }
622
623
    /**
624
     * Get context params for this stream resource.
625
     *
626
     * Returns the stream context params for this stream resource.
627
     *
628
     * @return array Context params for this stream resource
629
     */
630 82
    public function getContextParams()
631
    {
632 82
        return $this->context['params'];
633
    }
634
635
    /**
636
     * Get stream context resource.
637
     * @return resource|null The stream context resource
638
     */
639 85
    public function getContext()
640
    {
641
        // if context resource hasn't been created, create one
642 85
        if (is_null($this->crh)) {
643 82
            $this->crh = stream_context_create(
644 82
                $this->getContextOptions(),
645 82
                $this->getContextParams()
646 82
            );
647 82
        }
648
        // return context resource handle
649 85
        return $this->crh;
650
    }
651
652
    /**
653
     * Retrieve underlying stream resource handle.
654
     *
655
     * An accessor method for the underlying stream resource object. Also triggers
656
     * stream connection if in lazy open mode. Because this method may potentially
657
     * call the connect() method, it is possible that it may throw an exception
658
     * if there is some issue with opening the stream.
659
     *
660
     * @return resource The underlying stream resource handle
661
     * @throws \CSVelte\Exception\IOException
662
     */
663 69
    public function getHandle()
664
    {
665 69
        if (!$this->isConnected() && $this->isLazy()) {
666 7
            $this->connect();
667 6
        }
668 68
        return $this->conn;
669
    }
670
671
    /**
672
     * Is the stream connection open?
673
     *
674
     * Tells you whether this stream resource is open or not.
675
     *
676
     * @return boolean Whether the stream is open
677
     */
678 98
    public function isConnected()
679
    {
680 98
        return is_resource($this->conn);
681
    }
682
683
    /**
684
     * Get the stream URI.
685
     *
686
     * Accessor method for stream URI.
687
     *
688
     * @return string The stream URI
689
     */
690 82
    public function getUri()
691
    {
692 82
        return $this->uri;
693
    }
694
695
    /**
696
     * Get the access mode.
697
     *
698
     * Tells you what the access mode is. This is the short string of characters
699
     * that you would pass to the fopen function to tell it how to open a file/stream
700
     *
701
     * @return string The file/stream access mode
702
     * @see http://php.net/manual/en/function.fopen.php
703
     */
704 97
    public function getMode()
705
    {
706 97
        return sprintf(
707 97
            "%s%s%s",
708 97
            $this->base,
709 97
            $this->plus,
710 97
            $this->flag
711 97
        );
712
    }
713
714
    /**
715
     * Is access mode binary-safe?
716
     * @return boolean Whether binary-safe flag is set
717
     */
718 4
    public function isBinary()
719
    {
720 4
        return $this->flag == "b";
721
    }
722
723
    /**
724
     * Is stream connected in text mode?
725
     * @return boolean Whether text mode flag is set
726
     */
727 3
    public function isText()
728
    {
729 3
        return $this->flag == "t";
730
    }
731
732
    /**
733
     * Is this a lazy open resource?
734
     * @return boolean Whether this is a lazily-opened resource
735
     */
736 92
    public function isLazy()
737
    {
738 92
        return $this->lazy;
739
    }
740
741
    /**
742
     * Should fopen search include path?
743
     * @return boolean Whether fopen should search include path for files
744
     */
745 82
    public function getUseIncludePath()
746
    {
747 82
        return $this->useIncludePath;
748
    }
749
750
    /**
751
     * Does the access mode string indicate readability?
752
     *
753
     * Readable, in this context, only refers to the manner in which this stream
754
     * resource was opened (if it even is opened yet). It is no indicator about
755
     * whether or not the underlying stream actually supports read operations.
756
     * It simply refers to the access mode string passed to it by the user.
757
     *
758
     * @return boolean Whether access mode indicates readability
759
     */
760 54
    public function isReadable()
761
    {
762 54
        return $this->readable;
763
    }
764
765
    /**
766
     * Does the access mode string indicate writability?
767
     *
768
     * Writable, in this context, only refers to the manner in which this stream
769
     * resource was opened (if it even is opened yet). It is no indicator about
770
     * whether or not the underlying stream actually supports write operations.
771
     * It simply refers to the access mode string passed to it by the user.
772
     *
773
     * @return boolean Whether access mode indicates writability
774
     */
775 21
    public function isWritable()
776
    {
777 21
        return $this->writable;
778
    }
779
780
    /**
781
     * Is cursor positioned at the beginning of stream?
782
     *
783
     * Returns true if this stream resource's access mode positions the internal
784
     * cursor at the beginning of the stream.
785
     *
786
     * @return boolean Whether cursor positioned at beginning of stream
787
     */
788 2
    public function isCursorPositionedAtBeginning()
789
    {
790 2
        return $this->base != 'a';
791
    }
792
793
    /**
794
     * Is cursor positioned at the end of stream?
795
     *
796
     * Returns true if this stream resource's access mode positions the internal
797
     * cursor at the end of the stream.
798
     *
799
     * @return boolean Whether cursor positioned at end of stream
800
     */
801 2
    public function isCursorPositionedAtEnd()
802
    {
803 2
        return $this->base == 'a';
804
    }
805
806
    /**
807
     * Is content truncated to zero-length on opening?
808
     *
809
     * Returns true if this stream resource's access mode indicates truncation of
810
     * stream content to zero-length upon opening.
811
     *
812
     * @return boolean Whether stream content is truncated on opening
813
     */
814 3
    public function isTruncated()
815
    {
816 3
        return $this->base == 'w';
817
    }
818
819
    /**
820
     * Does stream access mode indicate file creation?
821
     *
822
     * Returns true if this stream's access mode implies that PHP will attempt to
823
     * create a file if none exists.
824
     *
825
     * @return boolean Whether PHP should attempt to create file at $uri
826
     */
827 1
    public function attemptsFileCreation()
828
    {
829 1
        return $this->base != 'r';
830
    }
831
832
    /**
833
     * Does stream access mode indicate the rejection of existing files?
834
     *
835
     * Returns true if this stream's access mode implies that PHP will fail to
836
     * open a file if it already exists.
837
     *
838
     * @return boolean Whether PHP should attempt to create file at $uri
839
     */
840 1
    public function rejectsExistingFiles()
841
    {
842 1
        return $this->base == 'x';
843
    }
844
845
    /**
846
     * Are write operations appended to the end of the stream?
847
     *
848
     * Returns true if write operations are appended to the end of the stream
849
     * regardless of the position of the read cursor.
850
     *
851
     * @return boolean Whether write operations ore always appended
852
     */
853 1
    public function appendsWriteOps()
854
    {
855 1
        return $this->base == 'w';
856
    }
857
858
    /**
859
     * Assert that stream resource is not open.
860
     *
861
     * Used internally to ensure that stream is not open, since some methods should
862
     * only be called on unopened stream resources.
863
     *
864
     * @param  string $method The method that is asserting
865
     * @throws IOException if stream is open
866
     */
867 98
    protected function assertNotConnected($method)
868
    {
869 98
        if ($this->isConnected()) {
870
            throw new IOException("Cannot perform this operation on a stream once it has already been opened: {$method}", IOException::ERR_STREAM_ALREADY_OPEN);
871
        }
872 98
    }
873
874
    /**
875
     * Assert that given wrapper is a valid, registered stream wrapper
876
     *
877
     * Used internally to ensure that a given stream wrapper is valid and available
878
     *
879
     * @param  string $name The name of the stream wrapper
880
     * @throws \InvalidArgumentException if wrapper doesn't exist
881
     */
882 2
    protected function assertValidWrapper($name)
883
    {
884 2
        if (!in_array($name, stream_get_wrappers())) {
885
            throw new InvalidArgumentException("{$name} is not a known stream wrapper.");
886
        }
887 2
    }
888
889
}
890