ftell

Returns the current byte position of the read/write pointer in an open stream.

Signature
ftell(resource $stream): int|false
  • Added in PHP 4

Parameters

ParameterTypeDescription
$streamresourceAn open stream handle, typically returned by fopen() or popen().

Return value

int|falseThe current position of the file pointer as a byte offset from the start of the stream, or false on error.

Errors

Throws a TypeError if $stream is not an open stream resource; emits E_WARNING and returns false for streams that cannot report a position.

ftell reports how many bytes into a stream the pointer currently sits. Every read and write moves it, so it is the natural companion to fseek: remember a position, do some work, seek back.

When to reach for it

Use it to build an index — record ftell before each line so you can jump straight to line 40 000 later. Use it to report progress while streaming a large upload or download. Use it to measure how many bytes a parser consumed, by taking the difference between two calls. And use it to save your place before peeking ahead:

$mark = ftell($fp);
$peek = fread($fp, 4);
fseek($fp, $mark);

What trips people up

Append mode is the big one. For a handle opened with a or a+, the documented behaviour is undefined and in practice ftell often reports 0 until the first write, because the kernel only moves to the end at write time. Never use it to discover a file's size on an append handle — use fstat.

On Windows, opening in text mode (t) makes PHP translate \r\n to \n, so the position ftell reports drifts from the real byte offset. Always add b to the mode when offsets matter.

Finally, buffering means a position taken on a write handle reflects logical bytes written, not necessarily bytes flushed to disk.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Build a line-offset index for a huge log so later lookups can fseek straight to a line.
  • Report percentage progress while reading a large file in chunks.
  • Save the pointer, peek at the next few bytes to decide on a parser, then seek back.

Frequently asked questions

Why does ftell return 0 after opening a file in append mode?

Append handles ('a', 'a+') have undefined ftell behaviour and typically report 0 until the first write moves the pointer to the end. Use fstat($fp)['size'] if you need the file's size.

What is the difference between ftell and fseek?

ftell reads the current pointer position, fseek sets it. They are usually used together: store ftell's value, move around with fseek, then seek back to the stored value.

Does ftell work on network streams and pipes?

It returns a position for many of them but the value is only meaningful for seekable streams; check stream_get_meta_data($fp)['seekable'] before relying on it, because non-seekable streams cannot be repositioned anyway.

More examples

Building a line index

The offsets array holds the byte position where each line begins (0, 6, 12, 20). Seeking to offsets[2] jumps straight to "charlie" without re-reading the earlier lines — the same trick database and log-viewer tools use.

Example · php
<?php
$file = "indexed.txt";
file_put_contents($file, "alpha\nbravo\ncharlie\ndelta\n");

$fp = fopen($file, "rb");
$offsets = [];
while (!feof($fp)) {
    $offsets[] = ftell($fp);
    if (fgets($fp) === false) { array_pop($offsets); }
}
fseek($fp, $offsets[2]);
echo "line 3 = ", trim(fgets($fp)), "\n";
print_r($offsets);
fclose($fp);
unlink($file);

Peeking without losing your place

The mark/seek pair restores the pointer to 0 after the peek, so the next read starts from the beginning again. Without ftell you would have to hard-code the offset, which breaks as soon as the peek length changes.

Example · php
<?php
$fp = fopen("php://memory", "r+b");
fwrite($fp, "PK\x03\x04rest of archive");
rewind($fp);

$mark = ftell($fp);
$magic = fread($fp, 2);
fseek($fp, $mark);

echo "magic: $magic\n";
echo "back at: ", ftell($fp), "\n";
echo "full read: ", fread($fp, 4), "\n";
fclose($fp);

Progress reporting during a chunked read

ftell after each 25-byte read gives bytes consumed so far, which divided by the total size yields 25%, 50%, 75%, 100%. This is exactly how a CLI progress bar over a stream is driven.

Example · php
<?php
$file = "big.txt";
file_put_contents($file, str_repeat("0123456789", 10));
$total = filesize($file);

$fp = fopen($file, "rb");
while (!feof($fp)) {
    fread($fp, 25);
    printf("%d%%\n", (int) (ftell($fp) / $total * 100));
}
fclose($fp);
unlink($file);

Discussion

  • Be the first to comment on this lesson.