<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Gabriel Olivério</title>
    <description>The latest articles on Forem by Gabriel Olivério (@gabrieloliverio).</description>
    <link>https://forem.com/gabrieloliverio</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F235515%2F348ce420-f4a6-4621-aedb-a56cb46056fa.jpg</url>
      <title>Forem: Gabriel Olivério</title>
      <link>https://forem.com/gabrieloliverio</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/gabrieloliverio"/>
    <language>en</language>
    <item>
      <title>Streams in PHP: What you really need to know</title>
      <dc:creator>Gabriel Olivério</dc:creator>
      <pubDate>Sun, 25 Aug 2024 18:28:10 +0000</pubDate>
      <link>https://forem.com/gabrieloliverio/streams-in-php-55kb</link>
      <guid>https://forem.com/gabrieloliverio/streams-in-php-55kb</guid>
      <description>&lt;p&gt;Whether you've ever had to deal with local files, HTTP requests or compressed files, you dealt with streams but... did you really get to know them? &lt;/p&gt;

&lt;p&gt;I think that this is one of the most misunderstood concepts in PHP, and, as a consequence I've seen quite a few bugs being introduced because the lack of some fundamental knowledge. &lt;/p&gt;

&lt;p&gt;In this article I'll try to explain what streams really are and how to work with them. We will see many functions used to work with streams as well as lots of examples, but it is not my intention to "redocument" &lt;a href="https://www.php.net/manual/en/ref.stream.php" rel="noopener noreferrer"&gt;all of them&lt;/a&gt; in any way.   &lt;/p&gt;

&lt;p&gt;Before learning what streams are, we first need to approach resources. &lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;p&gt;Resources are simply references or pointers to external resources, such as a file, a database, network or SSH connection, for example.&lt;/p&gt;

&lt;p&gt;There are &lt;a href="https://www.php.net/manual/en/resource.php" rel="noopener noreferrer"&gt;several types of resources&lt;/a&gt;, such as &lt;code&gt;curl&lt;/code&gt; - created by &lt;code&gt;curl_init()&lt;/code&gt;, &lt;code&gt;process&lt;/code&gt;, created by &lt;code&gt;proc_open&lt;/code&gt; and &lt;code&gt;stream&lt;/code&gt;, created by functions like &lt;code&gt;fopen()&lt;/code&gt;, &lt;code&gt;opendir&lt;/code&gt; among others. &lt;/p&gt;

&lt;h2&gt;
  
  
  Streams
&lt;/h2&gt;

&lt;p&gt;Streams are the way that PHP generalises types of resources that have common behaviour, that is, resources can be read from and written to linearly, like a cassette tape (damn, I am getting old). Some examples of streams are file resources, HTTP response bodies and compressed files, just to name a few.&lt;/p&gt;

&lt;p&gt;Streams are incredibly useful as they enable us to work with resources that range from a few bytes to several GBs in size and, an attempt of read them entirely, for example, would exhaust our available memory. &lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a stream with &lt;code&gt;fopen&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$mode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="nv"&gt;$use_include_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fopen&lt;/code&gt; opens a &lt;strong&gt;file&lt;/strong&gt; or &lt;strong&gt;network resource&lt;/strong&gt;[1], depending on the path provided to its first parameter. As said before, this resource is of type stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;get_resource_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'stream'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;$filename&lt;/code&gt; is provided in the form &lt;code&gt;scheme://&lt;/code&gt;, it is assumed to be a URL and PHP will try to find a &lt;a href="https://www.php.net/manual/en/wrappers.php" rel="noopener noreferrer"&gt;supported protocol handlers/wrappers&lt;/a&gt; that matches the path, such as &lt;code&gt;file://&lt;/code&gt; - to handle local files, &lt;code&gt;http://&lt;/code&gt; - to work on remote HTTP/S resources, &lt;code&gt;ssh2://&lt;/code&gt; - to handle SSH connections or &lt;code&gt;php://&lt;/code&gt; - that allows us access PHP's &lt;a href="https://www.php.net/manual/en/wrappers.php.php" rel="noopener noreferrer"&gt;own input and output streams&lt;/a&gt;, such as &lt;code&gt;php://stdin&lt;/code&gt;, &lt;code&gt;php://stdout&lt;/code&gt; and &lt;code&gt;php://stderr&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$mode&lt;/code&gt; defines the type of access you require to the stream, that is, whether you need only read access, only write, read and write, read/write from the beginning of the stream or end, and so on.&lt;/p&gt;

&lt;p&gt;The mode also depends on the type of resource you are working on. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$networkStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://google.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Opening a writable stream using the wrapper &lt;code&gt;https://&lt;/code&gt;, for example, does not work:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://google.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Failed to open stream: HTTP wrapper does not support writeable connections&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;[1] Using &lt;code&gt;fopen&lt;/code&gt; with network or remote resources only works when &lt;code&gt;allow_url_fopen&lt;/code&gt; is enabled on &lt;code&gt;php.ini&lt;/code&gt;. For more information, &lt;a href="https://www.php.net/manual/en/features.remote-files.php" rel="noopener noreferrer"&gt;check the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, now we've got a stream resource, what can we do with them?&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing into a file stream with &lt;code&gt;fwrite&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$length&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fwrite&lt;/code&gt; enables us to write the contents provided to &lt;code&gt;$data&lt;/code&gt; into a stream. If &lt;code&gt;$length&lt;/code&gt; is supplied, it writes only the given supplied number of bytes. Let's see an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s2"&gt;"The quick brown fox jumps over the lazy dog"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, as we provided &lt;code&gt;$length = 10&lt;/code&gt;, so only part of the content was written - "The quick " - ignoring the rest.&lt;/p&gt;

&lt;p&gt;Notice that we opened the file stream with &lt;code&gt;$mode = 'w'&lt;/code&gt;, which enabled us to write content into the file. If, instead, we had opened the file with &lt;code&gt;$mode = 'r'&lt;/code&gt;, we would get a message such as &lt;code&gt;fwrite(): Write of 8192 bytes failed with errno=9 Bad file descriptor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's see another example, now writing the whole content into the file stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="s2"&gt;"The quick brown fox jumps over the lazy dog"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, since we haven't provided &lt;code&gt;$length&lt;/code&gt;, the whole content was written into the file. &lt;/p&gt;

&lt;p&gt;Writing into a stream moves the position of the read/write pointer to end end of the sequence. In this case, the string written into the stream has 44 characters, therefore, the pointer's position now should be 43.&lt;/p&gt;

&lt;p&gt;Besides writing into a file, &lt;code&gt;fwrite&lt;/code&gt; can write in other types of streams, such as sockets. Example extracted from the &lt;a href="https://www.php.net/manual/en/stream.examples.php" rel="noopener noreferrer"&gt;docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fsockopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ssl://secure.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$errno&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$errstr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;die&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$errstr&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="nv"&gt;$errno&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"foo="&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Value for Foo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&amp;amp;bar="&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Value for Bar"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"POST /form_action.php HTTP/1.0&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Host: secure.example.com&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Content-type: application/x-www-form-urlencoded&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Content-length: "&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;strlen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Accept: */*&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\r\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="nv"&gt;$headers&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$str&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;feof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fgets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;fclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reading streams with &lt;code&gt;fread&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;fread&lt;/code&gt; you can read &lt;strong&gt;up to&lt;/strong&gt; &lt;code&gt;$length&lt;/code&gt; bytes from a stream, starting from the current read pointer. It is binary-safe and it works with local and network resources, as we will see in the examples.&lt;/p&gt;

&lt;p&gt;Calling &lt;code&gt;fread&lt;/code&gt; consecutively will read a chunk and then move the read pointer to the end of this chunk. Example, considering the file written in the previous example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content: "The quick brown fox jumps over the lazy dog"&lt;/span&gt;
&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// 'The quick '&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 10&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;      &lt;span class="c1"&gt;// 'brown fox '&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We will come back to &lt;code&gt;ftell&lt;/code&gt; soon, but what it does is simply return the current position of the read pointer.&lt;/p&gt;

&lt;p&gt;The reading stops (returning &lt;code&gt;false&lt;/code&gt;), as soon as one of the following occurs (&lt;a href="https://www.php.net/manual/en/function.fread.php" rel="noopener noreferrer"&gt;copied from the docs&lt;/a&gt;, you'll understand later):&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;length&lt;/code&gt; bytes have been read&lt;/li&gt;
&lt;li&gt;EOF (end of file) is reached&lt;/li&gt;
&lt;li&gt;a packet becomes available or the socket timeout occurs (for network streams)&lt;/li&gt;
&lt;li&gt;if the stream is read buffered and it does not represent a plain file, at most one read of up to a number of bytes equal to the chunk size (usually 8192) is made; depending on the previously buffered data, the size of the returned data may be larger than the chunk size.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don't know if you had the same felling, but this last part is pretty cryptic, so let's break it down.&lt;/p&gt;

&lt;h4&gt;
  
  
  "if the stream is read buffered"
&lt;/h4&gt;

&lt;p&gt;Stream reads and writes can be buffered, that is, the content may be stored internally. It is possible to disable/enable the buffering, as well as set their sizes using &lt;a href="https://www.php.net/manual/en/function.stream-set-read-buffer.php" rel="noopener noreferrer"&gt;stream_set_read_buffer&lt;/a&gt; and &lt;a href="https://www.php.net/manual/en/function.stream-set-write-buffer.php" rel="noopener noreferrer"&gt;stream-set-write-buffer&lt;/a&gt;, but according to &lt;a href="https://github.com/php/doc-en/issues/1441#issuecomment-1060592609" rel="noopener noreferrer"&gt;this comment&lt;/a&gt; on the PHP doc's Github, the description of these functions can be misleading. &lt;/p&gt;

&lt;p&gt;This is where things get interesting, as this part of the documentation is really obscure. As per the comment, setting &lt;code&gt;stream_set_read_buffer($stream, 0)&lt;/code&gt; would disable the read buffering, whereas &lt;code&gt;stream_set_read_buffer($stream, 1)&lt;/code&gt; or &lt;code&gt;stream_set_read_buffer($stream, 42)&lt;/code&gt; would simply enable it, &lt;strong&gt;ignoring&lt;/strong&gt; its size (depending on the stream wrapper, which can override this default behaviour).&lt;/p&gt;

&lt;h4&gt;
  
  
  "... at most one read of up to a number of bytes equal to the chunk size (usually 8192) is made"
&lt;/h4&gt;

&lt;p&gt;The chunk size is usually 8192 bytes or 8 KiB, as we will confirm in a bit. We can change this value using &lt;code&gt;stream_set_chunk_size&lt;/code&gt;. Let's see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-standard-3.20.2-x86_64.iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$chunkSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunkSize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$bytesRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$previousPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Iteration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Bytes read: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Iteration: 1. Bytes read: 1024
Iteration: 2. Bytes read: 1024
Iteration: 3. Bytes read: 1024
...
Iteration: 214016. Bytes read: 1024
Iteration: 214017. Bytes read: 169
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What happened in this case was clear:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We wanted up to 1024 bytes in each &lt;code&gt;fread&lt;/code&gt; call and that's what we got&lt;/li&gt;
&lt;li&gt;In the last call there were only 169 bytes remainder, which were returned&lt;/li&gt;
&lt;li&gt;When there was nothing else to return, that is, EOF was reached &lt;code&gt;fread&lt;/code&gt; returned &lt;code&gt;false&lt;/code&gt; and the loop finished.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let's increase considerably the length provided to &lt;code&gt;fread&lt;/code&gt; to 1 MiB and see what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-standard-3.20.2-x86_64.iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$chunkSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1048576&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 MiB&lt;/span&gt;
&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunkSize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$bytesRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$previousPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Iteration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Bytes read: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Iteration: 1. Bytes read: 1378
Iteration: 2. Bytes read: 1378
Iteration: 3. Bytes read: 1378
...
Iteration: 24. Bytes read: 1074
Iteration: 25. Bytes read: 8192
Iteration: 26. Bytes read: 8192
...
Iteration: 26777. Bytes read: 8192
Iteration: 26778. Bytes read: 8192
Iteration: 26779. Bytes read: 293
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, even though we tried to read 1 MiB using &lt;code&gt;fread&lt;/code&gt;, it read &lt;strong&gt;up to&lt;/strong&gt; 8192 bytes - same value that the docs said it would. Interesting. Let's see another experiment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-standard-3.20.2-x86_64.iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$chunkSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1048576&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 MiB&lt;/span&gt;
&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nb"&gt;stream_set_chunk_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunkSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Just added this line&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunkSize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$bytesRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$previousPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Iteration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Bytes read: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Iteration: 1. Bytes read: 1378
Iteration: 2. Bytes read: 1378
Iteration: 3. Bytes read: 1378
...
Iteration: 12. Bytes read: 533
Iteration: 13. Bytes read: 16384
Iteration: 14. Bytes read: 16384
...
Iteration: 13386. Bytes read: 16384
Iteration: 13387. Bytes read: 16384
Iteration: 13388. Bytes read: 13626
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that now &lt;code&gt;fread&lt;/code&gt; read up to 16 KiB - not even close to what we wanted, but we've seen that &lt;code&gt;stream_set_chunk_size&lt;/code&gt; did work, but there are some hard limits, that I suppose that depends also on the wrapper. Let's put that in practice with another experiment, using a local file this time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'alpine-standard-3.20.2-x86_64.iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$chunkSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1048576&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 1 MiB&lt;/span&gt;
&lt;span class="nv"&gt;$i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$chunkSize&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$bytesRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nv"&gt;$previousPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$previousPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Iteration: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Bytes read: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;$bytesRead&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="kc"&gt;PHP_EOL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Iteration: 1. Bytes read: 1048576
Iteration: 2. Bytes read: 1048576
...
Iteration: 208. Bytes read: 1048576
Iteration: 209. Bytes read: 1048576
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aha! So using the local file handler we were able to &lt;code&gt;fread&lt;/code&gt; 1 MiB as we wanted, and we did not even need to increase the buffer/chunk size with &lt;code&gt;stream_set_chunk_size&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Wrapping up
&lt;/h4&gt;

&lt;p&gt;I think that now the description is less cryptic, at least. Let's read it again (with some interventions):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;if the stream is read buffered ...&lt;/p&gt;

&lt;p&gt;and it does not represent a plain file &lt;em&gt;(that is, local, not a network resource)&lt;/em&gt;, ...&lt;/p&gt;

&lt;p&gt;at most one read of up to a number of bytes equal to the chunk size (usually 8192) is made &lt;em&gt;(and in our experiments we could confirm that this is true, at least one read of the chunk size was made)&lt;/em&gt;; ...&lt;/p&gt;

&lt;p&gt;depending on the previously buffered data, the size of the returned data may be larger than the chunk size &lt;em&gt;(we did not experience that, but I assume it may happen depending on the wrapper)&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;There is definitely some room to play here, but I will challenge you. What would happen if you disable the buffers while reading a file? And a network resource? What if you write into a file?&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;ftell&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ftell&lt;/code&gt; returns the position of the read/write pointer (or &lt;code&gt;null&lt;/code&gt; when the resource is not valid).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content: "The quick brown fox jumps over the lazy dog"&lt;/span&gt;
&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;# "The quick "&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;stream_get_meta_data&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nb"&gt;stream_get_meta_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt; &lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;array&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stream_get_meta_data&lt;/code&gt; returns information about the stream in form of an array. Let's see an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content: "The quick brown fox jumps over the lazy dog"&lt;/span&gt;
&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;var_dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;stream_get_meta_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous example would return in something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="k"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"timed_out"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"blocked"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eof"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"wrapper_type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"plainfile"&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"stream_type"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"STDIO"&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"r"&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"unread_bytes"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"seekable"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"uri"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="s2"&gt;"file:///tmp/test"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;a href="https://www.php.net/manual/en/function.stream-get-meta-data.php" rel="noopener noreferrer"&gt;function's documentation&lt;/a&gt; is pretty honest describing each value ;) &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;fseek&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fseek(resource $stream, int $offset, int $whence = SEEK_SET): int
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fseek&lt;/code&gt; sets the read/write pointer on the opened stream to the value provided to &lt;code&gt;$offset&lt;/code&gt;.&lt;br&gt;
The position will be updated based on &lt;code&gt;$whence&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SEEK_SET&lt;/code&gt;: Position is set to &lt;code&gt;$offset&lt;/code&gt;, that is, if you call &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SEEK_CUR&lt;/code&gt;: Position is set based on the current one, that is, current + &lt;code&gt;$offset&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SEEK_END&lt;/code&gt;: Position is set to End Of File + &lt;code&gt;$offset&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using &lt;code&gt;SEEK_END&lt;/code&gt; we can provide a negative value to &lt;code&gt;$offset&lt;/code&gt; and go backwards from EOF. Its return value can be used to assess if the position has been set successfully (0) or has failed (-1).&lt;/p&gt;

&lt;p&gt;Let's see some examples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content: "The quick brown fox jumps over the lazy dog\n"&lt;/span&gt;
&lt;span class="nv"&gt;$fileStream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r+'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SEEK_SET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// 'quick'&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// 9&lt;/span&gt;

&lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SEEK_CUR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// 16, that is, 9 + 7&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// 'fox'  &lt;/span&gt;

&lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SEEK_END&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// Sets the position past the End Of File&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// 49, that is, EOF (at 44th position) + 5&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// ''  &lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;            &lt;span class="c1"&gt;// 49, nothing to read, so read/write pointer hasn't changed&lt;/span&gt;
&lt;span class="nb"&gt;fwrite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'foo'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// 52, that is, previous position + 3&lt;/span&gt;
&lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;SEEK_END&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;                 &lt;span class="c1"&gt;// 49, that is, 52 - 3&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fileStream&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;         &lt;span class="c1"&gt;// 'foo'  &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Some important considerations
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;As we've seen in this example, it is possible we seek past the End Of File and even read in an unwritten area (which returns 0 bytes), but some types of streams do not support it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An important consideration is that &lt;strong&gt;not all streams can be seeked&lt;/strong&gt;, for instance, you cannot &lt;code&gt;fseek&lt;/code&gt; a remote resource:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://dl-cdn.alpinelinux.org/alpine/v3.20/releases/x86_64/alpine-standard-3.20.2-x86_64.iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'rb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="no"&gt;WARNING&lt;/span&gt;  &lt;span class="nb"&gt;fseek&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;does&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;support&lt;/span&gt; &lt;span class="n"&gt;seeking&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This obviously makes total sense, as we cannot "fast-forward" and set a position on a remote resource. The stream in this case is only read sequentially, like a cassette tape.&lt;br&gt;
We can determine if the stream is seekable or not via the &lt;code&gt;seekable&lt;/code&gt; value returned by &lt;code&gt;stream_get_meta_data&lt;/code&gt; that we've seen before.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We can &lt;code&gt;fseek&lt;/code&gt; a resource opened in append mode (&lt;code&gt;a&lt;/code&gt; or &lt;code&gt;a+&lt;/code&gt;), but the data will &lt;strong&gt;always&lt;/strong&gt; be appended.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  &lt;code&gt;rewind&lt;/code&gt;
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rewind(resource $stream): bool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;This is a pure analogy of rewinding a videotape before returning it to video store. As expected, &lt;code&gt;rewind&lt;/code&gt; sets the position of the read/write pointer to 0, which is basically the same as calling &lt;code&gt;fseek&lt;/code&gt; with &lt;code&gt;$offset&lt;/code&gt; 0. &lt;/p&gt;

&lt;p&gt;The same considerations we've seen for &lt;code&gt;fseek&lt;/code&gt; applies for &lt;code&gt;rewind&lt;/code&gt;, that is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You cannot &lt;code&gt;rewind&lt;/code&gt; an unseekable stream&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;rewind&lt;/code&gt; on a resource opened in append mode will still write from the current position - the write pointer is not updated.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  How about &lt;code&gt;file_get_contents&lt;/code&gt;?
&lt;/h3&gt;

&lt;p&gt;So far we've been working directly with resources. &lt;code&gt;file_get_contents&lt;/code&gt; is a bit different, as it accepts the file path and returns the whole file content as a string, that is, it implicitly opens the resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;file_get_contents(
   string $filename,
   bool $use_include_path = false,
   ?resource $context = null,
   int $offset = 0,
   ?int $length = null
): string|false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to &lt;code&gt;fread&lt;/code&gt;, &lt;code&gt;file_get_contents&lt;/code&gt; can work on local and remote resources, depending on the &lt;code&gt;$filename&lt;/code&gt; we provide:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content: "The quick brown fox jumps over the lazy dog"&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "The quick brown fox jumps over the lazy dog\n"&lt;/span&gt;

&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://www.php.net/images/logos/php-logo.svg'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "&amp;lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -1 100 50"&amp;gt;\n ..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;$offset&lt;/code&gt; we can set the starting point to read the content, whereas with &lt;code&gt;length&lt;/code&gt; we can get a given amount of bytes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content: "The quick brown fox jumps over the lazy dog"&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'fox'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;offset&lt;/code&gt; also accepts negative values, which counts from the end of the stream.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Content: "The quick brown fox jumps over the lazy dog"&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/tmp/test'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 'dog'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the same rules that govern &lt;code&gt;fseek&lt;/code&gt; are also applied for &lt;code&gt;$offset&lt;/code&gt;, that is - you cannot set an &lt;code&gt;$offset&lt;/code&gt; while reading remote files, as the function would be basically &lt;code&gt;fseek&lt;/code&gt; the stream, and we've seen that it does not work well.&lt;/p&gt;

&lt;p&gt;The parameter &lt;code&gt;context&lt;/code&gt; makes &lt;code&gt;file_get_contents&lt;/code&gt; really flexible, enabling us set, for example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set a different HTTP method, such as POST instead of the default GET&lt;/li&gt;
&lt;li&gt;Provide headers and content to a POST or PUT request&lt;/li&gt;
&lt;li&gt;Disable SSL verification and allow self-signed certificates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We create a context using &lt;a href="https://www.php.net/manual/en/function.stream-context-create.php" rel="noopener noreferrer"&gt;stream_context_create&lt;/a&gt;, example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;stream_context_create&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s1"&gt;'http'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'method'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;]]);&lt;/span&gt;
&lt;span class="nb"&gt;file_get_contents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://a-valid-resource.xyz'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$context&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can find the list of options you can provide to &lt;code&gt;stream_context_create&lt;/code&gt; in this &lt;a href="https://www.php.net/manual/en/context.php" rel="noopener noreferrer"&gt;page&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$networkResource&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://releases.ubuntu.com/24.04/ubuntu-24.04-desktop-amd64.iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;fread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$networkResource&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Which one to use? &lt;code&gt;fread&lt;/code&gt;, &lt;code&gt;file_get_contents&lt;/code&gt;, &lt;code&gt;fgets&lt;/code&gt;, another one?
&lt;/h3&gt;

&lt;p&gt;The list of functions that we can use to read local or remote contents is lengthy, and each function can be seen as a tool in your tool belt, suitable for a specific purpose.&lt;/p&gt;

&lt;p&gt;According to the docs, &lt;code&gt;file_get_contents&lt;/code&gt; is the preferred way of reading contents of a file into a string,  but, is it appropriate for all purposes?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What if you know that the content is large? Will it fit into memory?&lt;/li&gt;
&lt;li&gt;Do you need it entirely in memory or can you work in chunks?&lt;/li&gt;
&lt;li&gt;Are you going to work on local or remote files?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask yourself these (and other questions), make some performance benchmark tests and select the function that suits your needs the most. &lt;/p&gt;

&lt;h3&gt;
  
  
  PSR-7's &lt;code&gt;StreamInterface&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.php-fig.org/psr/psr-7/#13-streams" rel="noopener noreferrer"&gt;PSR&lt;/a&gt; defines the &lt;code&gt;StreamInterface&lt;/code&gt;, which libraries such as Guzzle use to represent request and response bodies. When you send a request, the body is an instance of &lt;code&gt;StreamInterface&lt;/code&gt;. Let's see an example, &lt;a href="https://docs.guzzlephp.org/en/stable/psr7.html#responses" rel="noopener noreferrer"&gt;extracted from the Guzzle docs&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\GuzzleHttp\Client&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$client&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'GET'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'http://httpbin.org/get'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getBody&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I suppose that the methods available on &lt;code&gt;$body&lt;/code&gt; look familiar for you now :D&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/guzzle/streams/blob/master/src/StreamInterface.php" rel="noopener noreferrer"&gt;StreamInterface&lt;/a&gt; implements methods that resemble a lot the functions we've just seen, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;seek()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tell()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eof()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;read&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;write&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isSeekable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isReadable()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isWritable&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Last but not least, we can use &lt;code&gt;GuzzleHttp\Psr7\Utils::streamFor&lt;/code&gt; to create streams from strings, resources opened with &lt;code&gt;fopen&lt;/code&gt; and instances of &lt;code&gt;StreamInterface&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;GuzzleHttp\Psr7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Psr7\Utils&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;streamFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'string data'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                   &lt;span class="c1"&gt;// string data&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;          &lt;span class="c1"&gt;// str&lt;/span&gt;
&lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;getContents&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;// ing data&lt;/span&gt;
&lt;span class="nb"&gt;var_export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;eof&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;     &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;span class="nb"&gt;var_export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stream&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;tell&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;    &lt;span class="c1"&gt;// 11&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this article we've seen what streams really are, learned how to create them, read from them, write to them, manipulate their pointers as well as clarified some obscured parts regarding read a write buffers.&lt;/p&gt;

&lt;p&gt;If I did a good job, some of the doubts you might have had regarding streams are now a little bit clearer and, from now on, you'll write code more confidently, as you know what you are doing.&lt;/p&gt;

&lt;p&gt;Should you noticed any errors, inaccuracies or there is any topic that is still unclear, let me know in the comments and I'd be glad to try to help.&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>Concurrency and parallelism in PHP (part 1)</title>
      <dc:creator>Gabriel Olivério</dc:creator>
      <pubDate>Mon, 29 Jul 2024 18:30:57 +0000</pubDate>
      <link>https://forem.com/gabrieloliverio/concurrency-and-parallelism-in-php-part-1-21m5</link>
      <guid>https://forem.com/gabrieloliverio/concurrency-and-parallelism-in-php-part-1-21m5</guid>
      <description>&lt;p&gt;You may be "Yeah, big deal, another article about Fibers", but NO! I mean, yes, you are partly right, I am also going to approach Fibers, but also other features you may not be aware of.&lt;/p&gt;

&lt;p&gt;Before we dive into how to achieve concurrency and parallelism with PHP, we need to talk about what these two things are, and no, they are not exactly the same thing. If you are a computer science sorcerer that knows everything about this topic, you can skip this first part, you'll be forgiven 😛&lt;/p&gt;

&lt;p&gt;Concurrency and parallelism are terms that are sometimes used interchangeably, however, they're conceptually different. Let's go to the kitchen and use an analogy to understand these terms.&lt;/p&gt;

&lt;h1&gt;
  
  
  First, a bit of theory
&lt;/h1&gt;

&lt;p&gt;Suppose we are functional adults and we're going to prepare hamburgers for dinner, but you are like me, who can only do one thing well at a time. Our simplified "main process" would be something like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the minced meat&lt;/li&gt;
&lt;li&gt;Get the buns&lt;/li&gt;
&lt;li&gt;Shape the hamburgers&lt;/li&gt;
&lt;li&gt;Cut the buns&lt;/li&gt;
&lt;li&gt;Separate the cheese&lt;/li&gt;
&lt;li&gt;Fry the hamburgers&lt;/li&gt;
&lt;li&gt;Add the cheese to melt&lt;/li&gt;
&lt;li&gt;Assemble our hamburgers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Following this sequential flow and having a single worker, namely "you", each task can only start once the previous one has finished. &lt;/p&gt;

&lt;p&gt;This is basically how our PHP scripts work when they do not use parallelism and concurrency features - you can only iterate over entries you obtained from the database after the script has connected to the database, executed the query, waited for the results and closed the connection. You are blocked or, I/O blocked, as some literature puts it. &lt;/p&gt;

&lt;p&gt;Back to the kitchen, now suppose you are not like me 🙂&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You shape the hamburgers&lt;/li&gt;
&lt;li&gt;Get the buns&lt;/li&gt;
&lt;li&gt;Separate the cheese&lt;/li&gt;
&lt;li&gt;Do until all the hamburgers and are assembled

&lt;ol&gt;
&lt;li&gt;Fry a hamburger&lt;/li&gt;
&lt;li&gt;While one is frying, you can cut one or more buns&lt;/li&gt;
&lt;li&gt;Assemble the hamburgers as they are fried and the buns are cut&lt;/li&gt;
&lt;li&gt;Repeat until all hamburgers are done&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Now we are capable of dealing with multiple things at once, checking visually if parts of our process have completed so we can start the other and, more importantly, we are not blocked while the hamburgers are fried!&lt;/p&gt;

&lt;p&gt;This workflow demonstrates exactly what concurrency is. In concurrency we are not necessarily doing multiple things at once, indeed, multiple things are happening at the same time, but YOU are doing only one thing at a time, either cutting buns or taking care of the hamburgers while they fry. &lt;/p&gt;

&lt;p&gt;Using concurrency features in your PHP script you would be, for example, capable of executing multiple queries into a database at once, do other processing and, when these queries return, you can then process them and keep your usual flow, for example. We are not executing each and every query sequentially and waiting for each one return results to be able to execute the next one. We are not that blocked anymore!&lt;/p&gt;

&lt;p&gt;Now back once again to the kitchen, and I promise this will be the last time. This time you are not cooking alone anymore, you have a kitchen partner. I'll be generic and call you and your partner as worker 1 and worker 2, and both are working at the same time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Worker 1&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the minced meat&lt;/li&gt;
&lt;li&gt;Shape one hamburger&lt;/li&gt;
&lt;li&gt;Start frying. Once one hamburger is in our frying pan:&lt;/li&gt;
&lt;li&gt;We can shape other hamburgers&lt;/li&gt;
&lt;li&gt;Take care of our hamburgers being fried&lt;/li&gt;
&lt;li&gt;Add the cheese that hopefully our worker 2 has separate&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Worker 2&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the buns&lt;/li&gt;
&lt;li&gt;Cut the buns&lt;/li&gt;
&lt;li&gt;Separate the cheese&lt;/li&gt;
&lt;li&gt;Assemble the hamburgers as they are fried&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now we are effectively doing multiple things at once, thanks to the help of our second worker! &lt;/p&gt;

&lt;p&gt;Notice now there is some coordinating work to do, such as checking if the other worker has finished one step before proceeding with your task. In a similar fashion, our PHP script could have two workers, for example, one performing one task while the other performs another. They can (actually they need to) communicate with one another and synchronise their tasks.&lt;/p&gt;

&lt;p&gt;Parallelism and concurrency actually have two distinct objectives.&lt;br&gt;
Concurrency aims at &lt;strong&gt;&lt;em&gt;dealing&lt;/em&gt; with multiple things at once&lt;/strong&gt;, not getting blocked whenever we need to wait on external outcomes. Parallelism, on the other hand, focuses on &lt;strong&gt;&lt;em&gt;doing&lt;/em&gt; multiple things at once, potentially maximising the performance and use of our resources&lt;/strong&gt; without (hopefully) compromising the end result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Now going back to PHP
&lt;/h2&gt;

&lt;p&gt;In this article I am only going to cover concurrency, parallelism will be explored in the second part. First, we will see what building blocks PHP itself provides to us and afterwards we will check out some libraries that can make our lives much easier.&lt;/p&gt;

&lt;h1&gt;
  
  
  Generators (and coroutines)
&lt;/h1&gt;

&lt;p&gt;According to the docs, &lt;a href="https://www.php.net/manual/en/language.generators.overview.php" rel="noopener noreferrer"&gt;Generators&lt;/a&gt; "provide an easy way to implement iterators without the overhead of creating a class that implements the Iterator interface" (and the overall complexity of managing its state). Generators are not something necessarily new, it was added back in 2013, on version 5.5. &lt;/p&gt;

&lt;p&gt;Conceptually, a Generator function is any function that contains a yield statement. The yield statement is similar to the return, except that instead of stopping the execution of the function and returning a result to the caller, it provides a value and pauses its execution.&lt;/p&gt;

&lt;p&gt;Since &lt;a href="https://www.php.net/manual/en/class.generator.php" rel="noopener noreferrer"&gt;Generator&lt;/a&gt; objects implement the &lt;a href="https://www.php.net/manual/en/class.iterator.php" rel="noopener noreferrer"&gt;Iterator&lt;/a&gt; interface, we can do things like this, for example (extracted from the &lt;a href="https://wiki.php.net/rfc/generators" rel="noopener noreferrer"&gt;Generators's RFC&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w5xq90h9a4fxda64xgg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3w5xq90h9a4fxda64xgg.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As &lt;code&gt;getLinesFromFile&lt;/code&gt; has a &lt;code&gt;yield&lt;/code&gt; statement, it is therefore a Generator function. &lt;code&gt;$lines&lt;/code&gt;, on the other hand, is a Generator object and, since the &lt;code&gt;Generator&lt;/code&gt; class implements the Iterator interface, it can be iterated like we did on line 14.&lt;/p&gt;

&lt;p&gt;Generators work passing control back and forth between the generator function and the caller, in our example, the &lt;code&gt;foreach&lt;/code&gt;. It works this way:&lt;/p&gt;

&lt;p&gt;When the Generator is created on line 13, it is not executed - it is only executed when we call the method &lt;a href="https://www.php.net/manual/en/generator.next.php" rel="noopener noreferrer"&gt;next&lt;/a&gt;, something that our loop does implicitly. The execution happens as usual until it reaches the &lt;code&gt;yield&lt;/code&gt; statement, on line 7. At this point, it pauses the execution and its &lt;a href="https://www.php.net/manual/en/generator.current.php" rel="noopener noreferrer"&gt;current&lt;/a&gt; value is handed over to the caller. In the next &lt;code&gt;foreach&lt;/code&gt; iteration, the method &lt;a href="https://www.php.net/manual/en/generator.next.php" rel="noopener noreferrer"&gt;next&lt;/a&gt; is called again, resuming the generator at the point it had been paused.&lt;/p&gt;

&lt;p&gt;At this point you may be thinking: So, what does generators have to do with concurrency? Turns out that, even though the articles cover more this particular aspect of Generators, they can do much more! “Hidden” in the RFC, there is the following paragraph:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Generators can also be used the other way around, i.e. instead of producing values they can also consume them. When used in this way they are often referred to as enhanced generators, reverse generators or coroutines.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's right. Since generator functions are interruptible, we can use them as part of cooperative multitasking, also known as non-preemptive multitasking. In this approach, each executing unit (the generator or coroutine, in our case) voluntarily yields control periodically when blocked or idle. It is called cooperative because every generator must cooperate in order for the whole thing to work. With this approach, we need something that executes our generators and lets them return control back to it voluntarily.&lt;/p&gt;

&lt;p&gt;Enough of chit-chat, let's check some code.&lt;/p&gt;

&lt;p&gt;You can also check out this and all the next examples in &lt;a href="https://github.com/gabrieloliverio/examples-concurrency-parallelism" rel="noopener noreferrer"&gt;this repository&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First let’s clone the repo and run a container with PHP and Apache:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

docker run -p 9000:80 -v "$(pwd)"/src:/var/www/html -d php:8.2-apache


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5op0c8uuqcug0wctmrt4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5op0c8uuqcug0wctmrt4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The class &lt;code&gt;Task&lt;/code&gt; is merely a wrapper for our tasks, it accepts an ID and a generator. The ID will be set by the &lt;code&gt;Scheduler&lt;/code&gt;, as follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzo9wslo70nzjdnq5lorn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzo9wslo70nzjdnq5lorn.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The class &lt;code&gt;Scheduler&lt;/code&gt; is responsible for executing the tasks. We use its &lt;code&gt;newTask&lt;/code&gt; method to create the task, which stores it in a queue and in map, to control which tasks to run. &lt;/p&gt;

&lt;p&gt;In each iteration, the method &lt;code&gt;run&lt;/code&gt; obtains one task from the queue, executes it and, if the task is terminated - that is, if the generator is not &lt;a href="https://www.php.net/manual/en/generator.valid.php" rel="noopener noreferrer"&gt;valid&lt;/a&gt; anymore - the task is removed from the queue.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aehtcm6g5w9hh879lkz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4aehtcm6g5w9hh879lkz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;fetchResource&lt;/code&gt;, uses &lt;code&gt;curl_multi*&lt;/code&gt; functions to fetch multiple resources at the same time. The function &lt;code&gt;curl_multi_exec&lt;/code&gt; does not block our execution while it waits for its response; if instead the function &lt;code&gt;curl_exec&lt;/code&gt; had been used, this code simply would not have worked. This is a very important point, which we will come back to shortly. &lt;/p&gt;

&lt;p&gt;We are using bare curl functions here on purpose, in order not to hide anything using libraries. As we will see later in this article, libraries such as &lt;a href="https://docs.guzzlephp.org/en/stable/quickstart.html#async-requests" rel="noopener noreferrer"&gt;Guzzle&lt;/a&gt; allow sending multiple concurrent requests, returning Promises similar to those we find in other languages.&lt;/p&gt;

&lt;p&gt;We execute our do/while loop while our request is still running, but we interrupt the function's execution and return the control back to our scheduler once the execution reaches the &lt;code&gt;yield&lt;/code&gt; statement. This interruption makes that the next loop iteration only happens after we give another task a chance to execute. The next time that the first task has been given a chance to process, it will merely check if the resource has returned, if so, it will terminate, otherwise, it will pause itself once again and return the control back to the scheduler.&lt;/p&gt;

&lt;p&gt;Executing this code you'd see something like this (shortened for brevity):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mzvui0eci69oftt8k35.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2mzvui0eci69oftt8k35.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It is beyond the scope of this article to cover all the use cases of Generators, but at this point you may have noticed that they can do much more than allow you to iterate on stuff, right?&lt;/p&gt;

&lt;p&gt;If you are interested in going deep in this subject, I strongly recommend &lt;a href="https://www.npopov.com/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html" rel="noopener noreferrer"&gt;this Nikita Popov's article&lt;/a&gt;. He is simply the Generators RFC's author and proponent of many other very cool features we all use every day. Part of the code we’ve just seen was borrowed from the aforementioned article.&lt;/p&gt;

&lt;p&gt;Even though this Generator's side is quite obscure for most PHP developers, it has been extensively explored by well-known libraries, such as &lt;a href="https://reactphp.org/" rel="noopener noreferrer"&gt;ReactPHP&lt;/a&gt; and &lt;a href="https://amphp.org/" rel="noopener noreferrer"&gt;AMPHP&lt;/a&gt;, which we will see later on.&lt;/p&gt;

&lt;h2&gt;
  
  
  A word about blocking and non-blocking functions
&lt;/h2&gt;

&lt;p&gt;Unfortunately, most PHP functions are blocking, with some rare exceptions. Besides &lt;a href="https://www.php.net/manual/en/function.curl-multi-init.php" rel="noopener noreferrer"&gt;curl_multi*&lt;/a&gt; functions that we’ve just seen, we can set streams as non-blocking, with the function &lt;a href="https://www.php.net/manual/en/function.stream-set-blocking.php" rel="noopener noreferrer"&gt;stream_set_blocking&lt;/a&gt;, and then use &lt;a href="https://www.php.net/stream_select" rel="noopener noreferrer"&gt;stream_select&lt;/a&gt; to wait for them to change status for some time.&lt;/p&gt;

&lt;p&gt;We can also work with sockets in a similar way, setting the socket as non-blocking with &lt;a href="https://www.php.net/manual/en/function.socket-set-nonblock.php" rel="noopener noreferrer"&gt;socket_set_nonblock&lt;/a&gt;, and then using &lt;a href="https://www.php.net/manual/en/function.socket-select.php" rel="noopener noreferrer"&gt;socket_select&lt;/a&gt; to wait for the sockets to change the status.&lt;/p&gt;

&lt;h1&gt;
  
  
  Fibers
&lt;/h1&gt;

&lt;p&gt;As we’ve just seen, it is already possible to achieve concurrency in our applications via Generators, so… what’s the real utility of Fibers, and what’s the difference between Fibers and Generators?&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://wiki.php.net/rfc/fibers" rel="noopener noreferrer"&gt;Fibers RFC&lt;/a&gt; was proposed in order to address one very common problem that other languages also suffer from, the problem known as the “&lt;a href="https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/" rel="noopener noreferrer"&gt;What colour is your function&lt;/a&gt;”. Summarising, this problem is characterised by the distinction we've got between synchronous and asynchronous functions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Asynchronous functions need to be called in a different way (such as with the await keyword, in JavaScript)&lt;/li&gt;
&lt;li&gt;Synchronous functions cannot call asynchronous ones (even though the other way around is possible)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have ever worked with Promises/async/await in JavaScript, you surely noticed this effect. Whenever we use &lt;code&gt;await&lt;/code&gt; in a function, this function also needs to be asynchronous and, therefore, needs to be marked as &lt;code&gt;async&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Fibers represent full-stack, interruptible functions, which can be suspended from anywhere in the call-stack and have its execution paused from within the fiber until it is finally resumed at a later time. In a sense, they are very similar to Generators, but there are a few important distinctions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fibers pause the entire execution stack, so the direct caller of the function does not need to change how it invokes the function&lt;/li&gt;
&lt;li&gt;Fibers have their own call-stack, whereas Generators do not. This allows them to be paused within deeply nested function calls&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Important to say that, in the same way as Generators, we can use Fibers to achieve concurrency, but not parallelism, that is, even though you may have multiple fibers, &lt;strong&gt;only one is actively being executed&lt;/strong&gt;. Fibers are also known as green-threads or coroutines in other languages, but don't be misled by its name - fibers exist within a single process thread, and that's the reason that only one Fiber is executed at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1 - our hello world.
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1spb55pt74ajt1acenc4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1spb55pt74ajt1acenc4.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Exactly, this is our hello world using Fibers. I admit, nothing fancy, and definitely no concurrency, but we are close, I promise. Here's what's happening:&lt;/p&gt;

&lt;p&gt;Fibers are created passing a callable to it - it is not executed though, only created. Inside the fiber, during its execution, we suspend it with a value - the string "Hello". On line 4 we print the value we have passed as an argument to the resume method, which we use to resume the fiber’s execution on line 11. &lt;/p&gt;

&lt;p&gt;On line 7 the fiber is actually executed, until we suspend it on line 2 with "Hello". The instruction on line 9 prints “Value from fiber suspending: Hello”. On line 11 we resume the fiber execution, which will execute instruction present on line 4, completing our Hello World.&lt;/p&gt;

&lt;p&gt;The whole output of our example is the following:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

Value from fiber suspending: Hello
Value used to resume fiber: World


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Fibers can be started, suspended from anywhere in the call stack, throw an exception/error and terminate. We can also check for their current states, using the &lt;code&gt;isSuspended()&lt;/code&gt;, &lt;code&gt;isStarted()&lt;/code&gt;, &lt;code&gt;isRunning()&lt;/code&gt; and &lt;code&gt;isTerminated()&lt;/code&gt;, and that is basically its API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2 - sending concurrent requests
&lt;/h2&gt;

&lt;p&gt;Let’s modify a bit our classes &lt;code&gt;Task&lt;/code&gt; and &lt;code&gt;Scheduler&lt;/code&gt; to use Fibers instead of generators.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpilana3w6i5io72f1nm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzpilana3w6i5io72f1nm.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The task now accepts a callable (not a &lt;code&gt;Generator&lt;/code&gt; object anymore) and, internally, it creates a Fiber out of the defined callable. In the run method, we first check if the fiber has been already started and, if not, we start it, otherwise, the fiber is just resumed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnhd47rx3vtpqfdta84h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmnhd47rx3vtpqfdta84h.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our simple scheduler now also accepts a callable rather than a Generator function as a parameter to the &lt;code&gt;newTask&lt;/code&gt; method. Other than that, it remains unchanged.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flimujc7qewyxto2teuap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flimujc7qewyxto2teuap.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The function &lt;code&gt;fetchResource&lt;/code&gt; is also basically the same as previously presented, but instead of yielding the control to the caller, we suspend itself, on line 17.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1p6tofjcwafi41qhu72.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh1p6tofjcwafi41qhu72.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a result, we should see the same result as we’ve seen with the Generators example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvs2ag82tidmg8spdzgq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwvs2ag82tidmg8spdzgq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A final word on Fibers
&lt;/h2&gt;

&lt;p&gt;You might have noticed that working with Fibers and Generators can add a good level of complexity to our code base if we use them directly. &lt;/p&gt;

&lt;p&gt;Even though they are the building blocks that PHP provides to us, if you notice in the &lt;a href="https://wiki.php.net/rfc/fibers" rel="noopener noreferrer"&gt;Fibers’ RFC&lt;/a&gt;, the author was quite explicit about its purpose and audience:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Fibers are an advanced feature that most users will not use directly. This feature is primarily targeted at library and framework authors to provide an event loop and an asynchronous programming API. &lt;/p&gt;

&lt;p&gt;The Fiber API is not expected to be used directly in application-level code. Fibers provide a basic, low-level flow-control API to create higher-level abstractions that are then used in application code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Having said that, let’s have a look at some libraries to make our lives a bit easier.&lt;/p&gt;

&lt;h1&gt;
  
  
  Guzzle
&lt;/h1&gt;

&lt;p&gt;As mentioned before, Guzzle provides a very convenient way to send concurrent requests, which under the hood, as this article is being written, is powered by Generators. If this is your only need for concurrency, go for it. Let's see an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnz4u1fo8q6b3qzcd20jz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnz4u1fo8q6b3qzcd20jz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The method &lt;code&gt;getAsync&lt;/code&gt;, as well as their similar &lt;code&gt;postAsync&lt;/code&gt;, &lt;code&gt;putAsync&lt;/code&gt;, &lt;code&gt;deleteAsync&lt;/code&gt; and so on, return a Promise, which implements the &lt;a href="https://promisesaplus.com/" rel="noopener noreferrer"&gt;Promises/A+ spec&lt;/a&gt;. You can chain their calls or wait for all of them concurrently, as we've done. Executing this script would result in something like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fre1t8do89xnz0f9bloy7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fre1t8do89xnz0f9bloy7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  AMPHP
&lt;/h1&gt;

&lt;p&gt;AMPHP is a collection of libraries to deal with concurrency and, as of version 3.0, is powered by Fibers.&lt;/p&gt;

&lt;p&gt;One of the nicest things about this project is that it also provides non-blocking alternatives for many PHP functions, such as for handling &lt;a href="https://amphp.org/file" rel="noopener noreferrer"&gt;files&lt;/a&gt;, executing &lt;a href="https://amphp.org/mysql" rel="noopener noreferrer"&gt;MySQL&lt;/a&gt; queries or communicating with &lt;a href="https://amphp.org/redis" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;, without requiring the installation of extensions, like Swoole does. Even though this approach may have some performance limitations, it can fit well in many scenarios.&lt;/p&gt;

&lt;p&gt;Let's have a look in an example that uses the non-blocking MySQL connector:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6carvpo4kezoxlmrirr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6carvpo4kezoxlmrirr7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We start defining the connection pool and set up our example database. Both tables, tmp1 and tmp2 are created synchronously.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;foreach&lt;/code&gt; defined in lines 17 to 20 we execute our &lt;code&gt;INSERT&lt;/code&gt; statements using the &lt;code&gt;async&lt;/code&gt; method. This method returns a &lt;code&gt;Future&lt;/code&gt; object, which represents the eventual result of an asynchronous operation. A &lt;code&gt;Future&lt;/code&gt; can be in pending, completed successfully or errored states. This is basically the same concept we find in other languages, such as JavaScript.&lt;/p&gt;

&lt;p&gt;In line 22 we use the &lt;code&gt;await&lt;/code&gt; combinator to wait for all &lt;code&gt;Future&lt;/code&gt;s to complete, then we do the same process to query the results from line 24 to 27. In line 27 we can see that the &lt;code&gt;async&lt;/code&gt; function returns an array whose keys match the ones from the iterable provided to it and their completion values.&lt;/p&gt;

&lt;p&gt;Later on we use the function &lt;code&gt;async&lt;/code&gt; and the &lt;code&gt;fetchRow&lt;/code&gt; to get the obtained results. &lt;/p&gt;

&lt;p&gt;The example is quite simple and it probably would not make sense to execute them asynchronously when we have 10 insertions to each table, but can be useful when dealing with multiple databases, for example.&lt;/p&gt;

&lt;p&gt;Similar to the &lt;code&gt;await&lt;/code&gt; combinator, we've got:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;awaitAll&lt;/code&gt;, which awaits all the &lt;code&gt;Future&lt;/code&gt; objects and returns their results as &lt;code&gt;[$errors, $values]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;awaitFirst&lt;/code&gt;, that returns the first completed &lt;code&gt;Future&lt;/code&gt; object, whether successfully completed or errored&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;awaitAny&lt;/code&gt;, which returns the first successfully completed &lt;code&gt;Future&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;awaitAnyN&lt;/code&gt;, which is similar to the await, but tolerates individual failures. It accepts the &lt;code&gt;$count&lt;/code&gt; parameter, that we can set the number of &lt;code&gt;Future&lt;/code&gt; objects that must be returned once successfully completed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next example will make use of the &lt;a href="https://amphp.org/http-client" rel="noopener noreferrer"&gt;HttpClient&lt;/a&gt; library, which is similar to Guzzle in a sense that allows us to make asynchronous requests, but with some special powers. We will try to obtain an HTTP resource and, if it is too slow for our standards, we fallback on a secondary resource:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fug4i8fgk53gjwl5xcev2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fug4i8fgk53gjwl5xcev2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As we can see in line 12, we are providing a &lt;code&gt;TimeoutCancellation&lt;/code&gt; to the &lt;code&gt;function&lt;/code&gt; await. This causes a &lt;code&gt;CancelledException&lt;/code&gt; once we reach 2 seconds of execution, making us to fall back on the secondary resource, which could be read a possibly-outdated read-only database replica, for example. Executing this script would print the "I am waaay faster", echoed by the script &lt;code&gt;fast.php&lt;/code&gt;, instead of the 10 seconds that would take if we had insisted on the main resource - &lt;code&gt;slow.php&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are other Cancellation exceptions we can use, such as the &lt;code&gt;SignalCancellation&lt;/code&gt;, which cancels the &lt;code&gt;Future&lt;/code&gt; after receiving a specified signal and &lt;code&gt;DeferredCancellation&lt;/code&gt;, which allows manual cancellation when calling the &lt;code&gt;DeferredCancellation::cancel()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It is clear that it is way easier to work with Fibers consuming AMPHP than manually, right?&lt;/p&gt;

&lt;h1&gt;
  
  
  ReactPHP
&lt;/h1&gt;

&lt;p&gt;ReactPHP is a low-level library for event-driven programming in PHP. It provides an event loop and utilities to work with streams, DNS resolver, network client/server, HTTP client/server and interaction with processes.&lt;/p&gt;

&lt;p&gt;As AMPHP, it is highly modular, so we can install only the packages we need. Let's check our first example, similar to what we've done with AMPHP and Guzzle, consuming multiple resources simultaneously:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6s61wjz8vezs8gmzodfu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6s61wjz8vezs8gmzodfu.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This example uses the &lt;a href="https://reactphp.org/http/" rel="noopener noreferrer"&gt;HTTP&lt;/a&gt; package, which provides the ability to consume and serve HTTP resources. The method get returns a &lt;code&gt;Promise&lt;/code&gt;, an implementation of &lt;a href="http://wiki.commonjs.org/wiki/Promises/A" rel="noopener noreferrer"&gt;CommonJS Promises/A&lt;/a&gt; for PHP, which is provided by one of the ReactPHP packages - &lt;a href="https://reactphp.org/promise/" rel="noopener noreferrer"&gt;Promise&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Executing this file in our terminal, we can see this output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflv7lgjyq4wnak8mormw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fflv7lgjyq4wnak8mormw.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That is, even though we have sent the request to the slow resource first, it was the promise associated with the second request that was resolved first.&lt;/p&gt;

&lt;p&gt;We can use the combinator &lt;code&gt;all&lt;/code&gt;, similarly to what we've done with AMPHP:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8uxv8qqmoh8r6pah59e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8uxv8qqmoh8r6pah59e.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or use the race function, and guess who's the winner 😛:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4mxgzuzxqwhtv3c5dcv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq4mxgzuzxqwhtv3c5dcv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Besides enabling us to send concurrent requests, ReactPHP's HTTP package enables us to write HTTP and HTTPS servers that can handle multiple concurrent requests without blocking. Let's see an example:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcx2f7mdji6fqi2fpjtj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftcx2f7mdji6fqi2fpjtj.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example we create an HttpServer object, which responds immediately to GET requests and, for any other HTTP methods, we defer the response for 2 whole seconds using the await function, provided by the package &lt;a href="https://reactphp.org/async/" rel="noopener noreferrer"&gt;Async&lt;/a&gt;, along with the function sleep, provided by the package &lt;a href="https://reactphp.org/promise-timer" rel="noopener noreferrer"&gt;PromiseTimer&lt;/a&gt;. This way we simulate an asynchronous operation, such as obtaining external HTTP resources or executing database queries.&lt;/p&gt;

&lt;p&gt;This function &lt;code&gt;sleep&lt;/code&gt;, unlike the one provided natively by PHP, is &lt;strong&gt;non-blocking&lt;/strong&gt;. Executing this PHP script will launch an HTTP server that listens to the port 9090. With the server running, we can send multiple GET and POST requests and check that, even though there is a single process running, our script is capable of dealing with multiple requests at the same time.&lt;/p&gt;

&lt;p&gt;ReactPHP is a great library and it can do much more things that I could cover with this introductory article, nor was this my intention. Should you need to process streams of any type, launch HTTP or Socket, consume HTTP resources, interact with cache, resolve DNS or any other asynchronous operation, you’ll be in good hands with ReactPHP.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;As we’ve seen, it may not be as easy as with JavaScript or Python to achieve concurrency, but it is possible, even though it requires some external packages and additional effort. This means you don’t need to rewrite your entire application if you need it to deal with concurrency, isn’t that great?&lt;/p&gt;

&lt;p&gt;There is still a lot of ground to cover, as it is not possible to explore all use cases and libraries available out in the wild in this introductory article. Only the aforementioned OpenSwoole deserves its own article!&lt;/p&gt;

&lt;p&gt;In the next part of this article we will explore parallelism with PHP. Are you excited? Cause I am.&lt;/p&gt;

</description>
      <category>php</category>
    </item>
  </channel>
</rss>
