Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.36% covered (warning)
89.36%
42 / 47
70.59% covered (warning)
70.59%
12 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
StringStream
89.36% covered (warning)
89.36%
42 / 47
70.59% covered (warning)
70.59%
12 / 17
30.01
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 copyToStream
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 close
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 detach
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 tell
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 eof
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isSeekable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 seek
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
7.02
 rewind
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isWritable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 write
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 isReadable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 read
85.71% covered (warning)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
4.05
 getContents
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
3
 getMetadata
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3namespace MediaWiki\Rest;
4
5use InvalidArgumentException;
6use Stringable;
7
8/**
9 * A stream class which uses a string as the underlying storage. Surprisingly,
10 * Guzzle does not appear to have one of these. BufferStream does not do what
11 * we want.
12 *
13 * The normal use of this class should be to first write to the stream, then
14 * rewind, then read back the whole buffer with getContents().
15 *
16 * Seeking is supported, however seeking past the end of the string does not
17 * fill with null bytes as in a real file, it throws an exception instead.
18 */
19class StringStream implements Stringable, CopyableStreamInterface {
20
21    /** @var string */
22    private $contents;
23    /** @var int */
24    private $offset = 0;
25
26    /**
27     * Construct a StringStream with the given contents.
28     *
29     * The offset will start at 0, ready for reading. If appending to the
30     * given string is desired, you should first seek to the end.
31     *
32     * @param string $contents
33     */
34    public function __construct( $contents = '' ) {
35        $this->contents = $contents;
36    }
37
38    /** @inheritDoc */
39    public function copyToStream( $stream ) {
40        fwrite( $stream, $this->getContents() );
41    }
42
43    public function __toString(): string {
44        return $this->contents;
45    }
46
47    public function close(): void {
48    }
49
50    /** @inheritDoc */
51    public function detach() {
52        return null;
53    }
54
55    /** @inheritDoc */
56    public function getSize(): ?int {
57        return strlen( $this->contents );
58    }
59
60    /** @inheritDoc */
61    public function tell(): int {
62        return $this->offset;
63    }
64
65    /** @inheritDoc */
66    public function eof(): bool {
67        return $this->offset >= strlen( $this->contents );
68    }
69
70    /** @inheritDoc */
71    public function isSeekable(): bool {
72        return true;
73    }
74
75    /** @inheritDoc */
76    public function seek( int $offset, int $whence = SEEK_SET ): void {
77        switch ( $whence ) {
78            case SEEK_SET:
79                $this->offset = $offset;
80                break;
81
82            case SEEK_CUR:
83                $this->offset += $offset;
84                break;
85
86            case SEEK_END:
87                $this->offset = strlen( $this->contents ) + $offset;
88                break;
89
90            default:
91                throw new InvalidArgumentException( "Invalid value for \$whence" );
92        }
93        if ( $this->offset > strlen( $this->contents ) ) {
94            throw new InvalidArgumentException( "Cannot seek beyond the end of a StringStream" );
95        }
96        if ( $this->offset < 0 ) {
97            throw new InvalidArgumentException( "Cannot seek before the start of a StringStream" );
98        }
99    }
100
101    /** @inheritDoc */
102    public function rewind(): void {
103        $this->offset = 0;
104    }
105
106    /** @inheritDoc */
107    public function isWritable(): bool {
108        return true;
109    }
110
111    /** @inheritDoc */
112    public function write( string $string ): int {
113        if ( $this->offset === strlen( $this->contents ) ) {
114            $this->contents .= $string;
115        } else {
116            $this->contents = substr_replace( $this->contents, $string,
117                $this->offset, strlen( $string ) );
118        }
119        $this->offset += strlen( $string );
120        return strlen( $string );
121    }
122
123    /** @inheritDoc */
124    public function isReadable(): bool {
125        return true;
126    }
127
128    /** @inheritDoc */
129    public function read( int $length ): string {
130        if ( $this->offset === 0 && $length >= strlen( $this->contents ) ) {
131            $ret = $this->contents;
132        } elseif ( $this->offset >= strlen( $this->contents ) ) {
133            $ret = '';
134        } else {
135            $ret = substr( $this->contents, $this->offset, $length );
136        }
137        $this->offset += strlen( $ret );
138        return $ret;
139    }
140
141    /** @inheritDoc */
142    public function getContents(): string {
143        if ( $this->offset === 0 ) {
144            $ret = $this->contents;
145        } elseif ( $this->offset >= strlen( $this->contents ) ) {
146            $ret = '';
147        } else {
148            $ret = substr( $this->contents, $this->offset );
149        }
150        $this->offset = strlen( $this->contents );
151        return $ret;
152    }
153
154    /** @inheritDoc */
155    public function getMetadata( ?string $key = null ) {
156        return null;
157    }
158}