In my previous post about the experimental DOM stream wrapper, I discussed the issues that came forth when opening the stream. Now, let's take a look at another very important thing you want to do with a stream: read from and to it, and maybe move the pointer back and forth.
Reading from the stream
The DOM stream wrapper is already able to open an XML file and look for a specified node in the DOM:
$handle = fopen('dom://versions/versions/latestRelease', 'r');
Now that we have selected the node /versions/latestRelease
, we want to know it's value:
$currentValue = fread($handle, 1024);
In order to make this possible, we need to add two extra methods to the stream wrapper:
class DOMStreamWrapper
{
public function stream_read($count)
{
// read $count bytes from the node's value
}
public function stream_eof()
{
// return true if the end of the node's value has been reached, false otherwise
}
}
Providing a reading mechanism requires a few things: having a way to find out the maximum readable length and an internal pointer to keep track of the current position in the stream.
The stream_read()
method should return as many bytes from the stream as possible, up to the given number of characters. Also the internal position or pointer should be updated to the new position after reading. These are quite a few possible scenario's:
current position: 0 length of the stream: 10 number of characters to read: 10 => return 10 characters starting from the beginning, update the pointer to 10
current position: 2 length of the stream: 10 number of characters to read: 2 => return 2 characters starting from position 2, update the pointer to 4
current position: 4 length of the stream: 10 number of characters to read: 20 => return the last 6 characters of the stream, update the pointer to 10
If nothing is left, return an empty string or false
.
Retrieving the current position using ftell()
Even though in most scenarios it is not necessary to externally keep track of the stream's internal pointer, sometimes you may want to know the current value of the internal pointer. You can add the stream_tell()
method to the stream wrapper and let it return the current position:
class DOMStreamWrapper
{
public function stream_tell()
{
// return the current position in the stream
}
}
Moving around in the stream using fseek()
Using fseek()
a user can move the internal pointer without reading/writing to the stream. It accepts two arguments, an offset and an alias for the starting position that should be used (STREAM_SET
- the beginning of the stream, SEEK_CUR
- the current position, or SEEK_END
- the end of the stream).
Though it would be more intuitive, the arguments used for calling fseek()
are not immediately passed to the stream_seek()
method. PHP internally stores the current position and uses it to decide what value for "whence" it will pass through. So in case you just opened the file, and then call
fseek($handle, -2, SEEK_CUR);
PHP knows you are at the beginning of the stream and replaces SEEK_CUR
with SEEK_SET
. Then it calls the stream_seek()
with the original offset and SEEK_SET
instead of SEEK_CUR
. Though totally not intuitive, you should handle SEEK_CUR
accordingly. However, you can not leave out the implementation for SEEK_CUR
, since when the internal pointer is not at the beginning of the stream, SEEK_CUR
is still passed as the second argument.
Again, throw any exception you like, but just trigger a warning and return true
or false
in the end.
class DOMStreamWrapper
{
public function stream_seek($offset, $whence)
{
try {
// try to move the pointer to it's new position
// throw exceptions if arguments are invalid or the resulting position is out of bounds
return true;
}
catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
}
Enough for now! The full code for the DOMStreamWrapper is again to be found in my GitHub repository.