<?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>DEV Community: grant horwood</title>
    <description>The latest articles on DEV Community by grant horwood (@gbhorwood).</description>
    <link>https://dev.to/gbhorwood</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%2F167937%2F5c6effa6-315b-4bec-bbec-c5fe5a5b1dcf.jpg</url>
      <title>DEV Community: grant horwood</title>
      <link>https://dev.to/gbhorwood</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gbhorwood"/>
    <language>en</language>
    <item>
      <title>curl: getting performance data with curl</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Mon, 15 Sep 2025 15:41:30 +0000</pubDate>
      <link>https://dev.to/gbhorwood/curl-getting-performance-data-with-curl-47f1</link>
      <guid>https://dev.to/gbhorwood/curl-getting-performance-data-with-curl-47f1</guid>
      <description>&lt;p&gt;the api endpoint you've written is slow. everyone says so. but &lt;em&gt;how&lt;/em&gt; slow? there are lots of packages we could install to inspect an url's performance, but why bother with all that when &lt;a href="https://curl.se/docs/manpage.html" rel="noopener noreferrer"&gt;&lt;code&gt;curl&lt;/code&gt;&lt;/a&gt; can get us the performance metrics we need?&lt;/p&gt;

&lt;p&gt;in this post, we'll be going over &lt;a href="https://dev.to/gbhorwood/just-enough-curl-for-api-devs-3i72"&gt;using &lt;code&gt;curl&lt;/code&gt; to get data&lt;/a&gt; like execution time, download speed and header values. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F09%2Fcurltiming_meme.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F09%2Fcurltiming_meme.jpg" title="the 'waiting' meme" alt="the 'waiting' meme" width="720" height="349"&gt;&lt;/a&gt;a user waits patiently for an url that was not profiled for performance&lt;/p&gt;

&lt;h2&gt;
  
  
  getting total time using &lt;code&gt;--write-out&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;most of us, when we use &lt;code&gt;curl&lt;/code&gt; to inspect an url, just throw the &lt;code&gt;-v&lt;/code&gt; switch on it for 'verbose' and then sift through the headers and body using &lt;code&gt;grep&lt;/code&gt;, looking for the data we want. this works in a banging-two-rocks-together, paleolithic kind of way, but we can craft a much more elegant and powerful solution by using the &lt;code&gt;--write-out&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;as the name implies, &lt;code&gt;write-out&lt;/code&gt; is about filtering and formatting curl's output. the &lt;code&gt;write-out&lt;/code&gt; argument accepts a string that defines the output format using pre-defined variables to represent various response data. &lt;/p&gt;

&lt;p&gt;let's look at an example of how we would get the total execution time of a request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--write-out&lt;/span&gt; &lt;span class="s2"&gt;"Total time: %{time_total} seconds"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://example.ca/path/to/endpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here we see that we provided &lt;code&gt;write-out&lt;/code&gt; with a format string that included the variable &lt;code&gt;%{time_total}&lt;/code&gt;. this variable gets interpolated in the output with the time, in seconds, that the entire process took; from the moment the request was initiated to the instant the last byte of the response was downloaded.&lt;/p&gt;

&lt;p&gt;the output looks similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total time: 0.897665 seconds
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that's useful information!&lt;/p&gt;

&lt;p&gt;note that this command also suppresses all other output to keep thing nice and clean. we use the &lt;code&gt;-s&lt;/code&gt; switch for 'silent' operation, and throw away the body of the response with &lt;code&gt;-o /dev/null&lt;/code&gt;. it just makes everything nicer and easier to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  more timing data
&lt;/h2&gt;

&lt;p&gt;getting the total round-trip time of our call is good and useful, but we might want a more detailed accounting.&lt;/p&gt;

&lt;p&gt;fortunately, &lt;code&gt;curl&lt;/code&gt; provides with seven different timing measurements. of these, there are four that are particularly useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;time_namelookup&lt;/code&gt;&lt;/strong&gt;: how long, in seconds, it took to do the name lookup. ie. dns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;time_pretransfer&lt;/code&gt;&lt;/strong&gt;: the time, in seconds, the entire request portion of the call took; all that dns and connection and ssl stuff.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;time_starttransfer&lt;/code&gt;&lt;/strong&gt;: the time, in seconds, from the start of the call to right before the first byte of the response was transferred.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;time_total&lt;/code&gt;&lt;/strong&gt;: how long, in seconds, the whole call took&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;let's look at a sample &lt;code&gt;curl&lt;/code&gt; call that gets all these data points:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--write-out&lt;/span&gt; &lt;span class="s2"&gt;"time_namelookup %{time_namelookup}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;time_pretransfer %{time_pretransfer}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;time_starttransfer %{time_starttransfer}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;time_total %{time_total}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://example.ca/ | column &lt;span class="nt"&gt;-t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you'll notice that we added a pipe out to &lt;code&gt;column -t&lt;/code&gt; to format the output into columns. this is just a little bit of display sugar to make things easier on the eyes. the output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;time_namelookup     0.001698
time_pretransfer    0.171207
time_starttransfer  0.298703
time_total          0.587096
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;looking at this output and knowing what these data points represent allow us to do a little bit of subtraction math to learn some important things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;how long our server took to calculate the result:&lt;/strong&gt; since we know that &lt;code&gt;time_pretransfer&lt;/code&gt; is the time from the start of the call to the end of the request, and that &lt;code&gt;time_starttransfer&lt;/code&gt; is the time from the start to very beginning of the response, we can determine that the time the server took to calculate the response is (approximately) &lt;code&gt;time_starttransfer - time_pretransfer&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;for instance, in the above example, our &lt;code&gt;time_pretransfer&lt;/code&gt; was 0.171207 and our &lt;code&gt;time_starttransfer&lt;/code&gt; was 0.298703 meaning that our time-on-server was about 0.127496 seconds.&lt;/p&gt;

&lt;p&gt;this value is mildly accurate and will obvious change from one request to the next. however, it is good data to have for making comparisons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;how long our download took&lt;/strong&gt;: we can also use subtraction to determine how long it took for the response data to get downloaded from the server to our test machine by subtracting &lt;code&gt;time_starttransfer&lt;/code&gt;, the time to the first response byte, from &lt;code&gt;time_total&lt;/code&gt;. however, there are better ways to inspect download speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  speeds and sizes
&lt;/h2&gt;

&lt;p&gt;if we want to know how much data we're downloading and how fast that download is, we can get that with the following &lt;code&gt;write-out&lt;/code&gt; variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;size_download&lt;/code&gt;&lt;/strong&gt;: the size of the &lt;em&gt;total&lt;/em&gt; download, in bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;size_header&lt;/code&gt;&lt;/strong&gt;: the size of just the headers downloaded, in bytes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;speed_download&lt;/code&gt;&lt;/strong&gt;: the speed of the download in bytes/second.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="n"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;X&lt;/span&gt; &lt;span class="no"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="s2"&gt;"size_download %&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;size_download&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;size_header %&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;size_header&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;speed_download %&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;speed_download&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the output of this example call looks something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;size_download   922306
size_header     704
speed_download  1186138
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  response codes and headers
&lt;/h2&gt;

&lt;p&gt;getting timing data is great for telling us what happened, but if we want to get some clues as to &lt;em&gt;why&lt;/em&gt;, we're going to want to inspect things like response codes and headers.&lt;/p&gt;

&lt;h3&gt;
  
  
  getting the response code
&lt;/h3&gt;

&lt;p&gt;if you've ever noticed that an endpoint suddenly got a lot faster because it started returning a 404, you'll understand that knowing the response code of a request is important.&lt;/p&gt;

&lt;p&gt;we can get the response code with the &lt;code&gt;%{response_code}&lt;/code&gt; variable:&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="n"&gt;curl&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nc"&gt;X&lt;/span&gt; &lt;span class="no"&gt;GET&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dev&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="s2"&gt;"response_code %&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nv"&gt;response_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="err"&gt;\&lt;/span&gt;
&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;://&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ca&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;column&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it outputs what we would expect:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  getting headers
&lt;/h3&gt;

&lt;p&gt;we can extract individual headers from the response by using the &lt;code&gt;write-out&lt;/code&gt; variable&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;%header{&amp;lt;header name&amp;gt;}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;&amp;lt;header name&amp;gt;&lt;/code&gt; here is the name of the header in all lowercase and without the trailing colon. &lt;/p&gt;

&lt;p&gt;if we wanted to test what our url's cloudflare cache status was, for instance, we could do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--write-out&lt;/span&gt; &lt;span class="s2"&gt;"cf-cache-status %header{cf-cache-status}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://example.ca/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and get as output something like&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cf-cache-status MISS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  getting all the headers as json
&lt;/h3&gt;

&lt;p&gt;we can get all the response headers in json format with &lt;code&gt;%{header_json}&lt;/code&gt;. note that this is singular: &lt;em&gt;header&lt;/em&gt;, not headers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--write-out&lt;/span&gt; &lt;span class="s2"&gt;"header_json %{header_json}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://example.ca/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the output looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;header_json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"nginx/1.18.0 (Ubuntu)"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"Thu, 11 Sep 2025 19:06:12 GMT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"text/html"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content-length"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"178"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"connection"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"keep-alive"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="s2"&gt;"https://example.ca/"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can then inspect the data in the output using a tool like &lt;a href="https://jqlang.org/" rel="noopener noreferrer"&gt;jq&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  using a format configuration file
&lt;/h2&gt;

&lt;p&gt;of course, typing out a long &lt;code&gt;write-out&lt;/code&gt; format every time we want to &lt;code&gt;curl&lt;/code&gt; an endpoint is annoying. we can save time and keystrokes by storing output formats in configuration files.&lt;/p&gt;

&lt;p&gt;first, let's create a format file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;time_namelookup %{time_namelookup}\n
time_pretransfer %{time_pretransfer}\n
time_starttransfer %{time_starttransfer}\n
time_total %{time_total}\n
size_download %{size_download}\n
size_header %{size_header}\n
speed_download %{speed_download}\n
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this file holds an output format and is the same as if we had used it directly in the command.&lt;/p&gt;

&lt;p&gt;once we have our format file, we can supply it to the &lt;code&gt;write-out&lt;/code&gt; argument using the &lt;code&gt;@&lt;/code&gt; directive, which tells curl to read input from a file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-X&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--write-out&lt;/span&gt; &lt;span class="s2"&gt;"@/path/to/file"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
https://example.ca/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the output looks like what we would expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;time_namelookup 0.037403
time_pretransfer 0.206697
time_starttransfer 0.241542
time_total 0.556894
size_download 922308
size_header 704
speed_download 1656164
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;format configuration files are good things because they allow us to easily standardize formats and write our commands more accurately and qucklly.&lt;/p&gt;

&lt;h2&gt;
  
  
  conclusion
&lt;/h2&gt;

&lt;p&gt;curl may not be the most sophisticated profiling tool out there, but it is still incredibly powerful and almost universally available. having a better understanding of its capabilities will always pay off.&lt;/p&gt;

</description>
      <category>linux</category>
      <category>curl</category>
    </item>
    <item>
      <title>php: a curl cheatsheet</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Tue, 09 Sep 2025 16:45:40 +0000</pubDate>
      <link>https://dev.to/gbhorwood/php-a-curl-cheatsheet-37em</link>
      <guid>https://dev.to/gbhorwood/php-a-curl-cheatsheet-37em</guid>
      <description>&lt;p&gt;php's implementation of &lt;code&gt;curl&lt;/code&gt; is very powerful. unfortunately, it's also frustratingly complex and the official docs can be terse and dense. every php developer who uses &lt;code&gt;curl&lt;/code&gt; frequently accumulates, over time, a directory of tried-and-true snippets for reference; a 'cheatsheet', basically. this is mine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F09%2Fphpcurl_meme.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F09%2Fphpcurl_meme.jpeg" title="the 'mr. bean cheats' meme" alt="the 'mr. bean cheats' meme" width="800" height="592"&gt;&lt;/a&gt;a php dev copies and pastes useful php curl examples from a blog post&lt;/p&gt;

&lt;h2&gt;
  
  
  flyover
&lt;/h2&gt;

&lt;p&gt;this article is going to cover (nearly) all of the basic use cases of php curl and provide examples that can be copy-pasted and modified.  the topics are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="overview"&gt;an overview of a curl request&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;a basic `GET` request&lt;/li&gt;
&lt;li&gt;reusing the pointer for multiple requests&lt;/li&gt;
&lt;li&gt;building and sending a query string&lt;/li&gt;
&lt;li&gt;getting the HTTP code with `curl_get_info`&lt;/li&gt;
&lt;li&gt;connecting to a different port&lt;/li&gt;
&lt;li&gt;error handling&lt;/li&gt;
&lt;ul&gt;
&lt;li&gt;treating http errors as curl errors&lt;/li&gt;
&lt;/ul&gt;




&lt;li&gt;sending headers&lt;/li&gt;


&lt;li&gt;getting headers&lt;/li&gt;

&lt;ul&gt;
&lt;li&gt;functions to extract response headers&lt;/li&gt;
&lt;/ul&gt;




&lt;li&gt;basic auth&lt;/li&gt;


&lt;li&gt;sending cookies&lt;/li&gt;


&lt;li&gt;getting cookies&lt;/li&gt;


&lt;li&gt;cookie jars&lt;/li&gt;


    &lt;ul&gt;


    &lt;li&gt;getting cookies into a file&lt;/li&gt;


    &lt;li&gt;sending cookies from a file&lt;/li&gt;


    &lt;li&gt;sending and getting cookies using a file&lt;/li&gt;


    &lt;/ul&gt;


&lt;li&gt;sending &lt;code&gt;POST&lt;/code&gt; requests&lt;/li&gt;


&lt;li&gt;sending json via  &lt;code&gt;POST&lt;/code&gt; &lt;/li&gt;


&lt;li&gt;sending form data via  &lt;code&gt;POST&lt;/code&gt; &lt;/li&gt;


&lt;li&gt;sending &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; &lt;/li&gt;


&lt;li&gt;downloading a file &lt;/li&gt;


&lt;li&gt;uploading a file &lt;/li&gt;




&lt;li&gt;using ftp&lt;/li&gt;


    &lt;ul&gt;


        &lt;li&gt;running ftp commands&lt;/li&gt;


        &lt;li&gt;uploading a file via ftp&lt;/li&gt;


        &lt;li&gt;downloading a file via ftp&lt;/li&gt;


    &lt;/ul&gt;


&lt;/ul&gt;
&lt;br&gt;&lt;br&gt;
&lt;a&gt;&lt;/a&gt;

&lt;h1&gt;
  
  
  an overview of a curl request
&lt;/h1&gt;

&lt;p&gt;a &lt;code&gt;curl&lt;/code&gt; script consists of four basic stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;intialization:&lt;/strong&gt; creating the curl handle using &lt;code&gt;curl_init()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;setting options:&lt;/strong&gt; using &lt;code&gt;curl_setopt()&lt;/code&gt; to set all the options for the request; things like headers and body data and more.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;execution:&lt;/strong&gt; running the curl request with &lt;code&gt;curl_exec()&lt;/code&gt; and, optionally, getting the response data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;close:&lt;/strong&gt; closing the handle we made with &lt;code&gt;curl_init()&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;there are, of course, other optional components that we may use. things like &lt;code&gt;curl_getinfo()&lt;/code&gt; to get data about a response or the &lt;code&gt;CURLFile&lt;/code&gt; object for crafting file uploads. but the core functionality of a curl request is built with of the four basic components.&lt;br&gt;
&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  a basic &lt;code&gt;GET&lt;/code&gt; request
&lt;/h2&gt;

&lt;p&gt;running a basic http &lt;code&gt;GET&lt;/code&gt; request is fairly straightforward. we initialized curl using &lt;code&gt;curl_init()&lt;/code&gt; with the url as an argument and set the &lt;code&gt;CURLOPT_RETURNTRANSFER&lt;/code&gt; option to true so that we can get the response body.&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// initialize curl with the target url&lt;/span&gt;
&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// set CURLOPT_RETURNTRANSFER so we get a response body&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// run the curl request and get response body&lt;/span&gt;
&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// close curl&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;optionally, we can specify the url with the &lt;code&gt;CURLOPT_URL&lt;/code&gt; option rather than as an argument for &lt;code&gt;curl_init()&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="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// initialize curl without an url&lt;/span&gt;
&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// set the url with CURLOPT_URL&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  reusing the curl pointer for multiple requests
&lt;/h2&gt;

&lt;p&gt;we can reuse a curl handle to make multiple requests by changing the url with the &lt;code&gt;CURLOPT_URL&lt;/code&gt; option and rerunning &lt;code&gt;curl_excec()&lt;/code&gt;. here, we create one handle, then make two different &lt;code&gt;GET&lt;/code&gt; requests to two different urls.&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;$url_bands&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$url_albums&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/albums"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// initialize curl without an url&lt;/span&gt;
&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// run first request&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$url_bands&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// run second request&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$url_albums&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  building and sending a query string
&lt;/h2&gt;

&lt;p&gt;sending a query string is straightforward: we append it to the url of the  request.&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;// url with query string&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands?name=bratmobile"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;query strings are annoying to build manually. fortunately, php provides the &lt;a href="https://www.php.net/manual/en/function.http-build-query.php" rel="noopener noreferrer"&gt;&lt;code&gt;http_build_query&lt;/code&gt;&lt;/a&gt; function to turn arrays into query strings.&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;$args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'artist'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'monk, thelonious'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'years'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="mi"&gt;1957&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;1963&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="c1"&gt;// convert array to query string&lt;/span&gt;
&lt;span class="nv"&gt;$query_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;http_build_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// append to url&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands?"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  getting HTTP code with &lt;code&gt;curl_getinfo&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;the http response code can be extracted from the curl handle with &lt;code&gt;curl_getinfo()&lt;/code&gt; 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="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$http_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HTTP_CODE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;curl_getinfo()&lt;/code&gt; can also provide other useful information. popular data includes:&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;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_CONTENT_TYPE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HTTP_VERSION&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_PRIMARY_IP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_SIZE_DOWNLOAD&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  connecting to a different port
&lt;/h2&gt;

&lt;p&gt;the port can be set with the &lt;code&gt;CURLOPT_PORT&lt;/code&gt; option:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// option to set the port&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  error handling
&lt;/h2&gt;

&lt;p&gt;curl can throw errors; it's a good idea to test for and handle them.&lt;/p&gt;

&lt;p&gt;we do this by calling &lt;code&gt;curl_errno()&lt;/code&gt; to get the error number raised by &lt;code&gt;curl_exec()&lt;/code&gt;. if the result of &lt;code&gt;curl_errno()&lt;/code&gt; is 0, no error has ocurred.&lt;/p&gt;

&lt;p&gt;if we have an error number greater than zero, we can get the human-readable error message with &lt;code&gt;curl_error()&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="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// test for error&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;curl_errno&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$error_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// handle errors&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;there are a lot of &lt;a href="https://curl.se/libcurl/c/libcurl-errors.html" rel="noopener noreferrer"&gt;potential curl errors&lt;/a&gt;. popular ones are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CURLE_UNSUPPORTED_PROTOCOL (1)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CURLE_URL_MALFORMAT (3)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CURLE_COULDNT_RESOLVE_HOST (6)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CURLE_COULDNT_CONNECT (7)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CURLE_TOO_MANY_REDIRECTS (47)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  treating http errors as curl errors
&lt;/h3&gt;

&lt;p&gt;by default, http errors are not treated as errors by curl. we can change that by setting the &lt;code&gt;CURLOPT_FAILONERROR&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;. this will cause &lt;code&gt;curl_errno&lt;/code&gt; to return 22 (&lt;code&gt;CURLE_HTTP_RETURNED_ERROR&lt;/code&gt;) if the http server returns a code of 400 or greater.&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// treat any HTTP code &amp;gt;=400 as a curl error &lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_FAILONERROR&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// test for error&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;curl_errno&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$error_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// handle errors&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  sending headers
&lt;/h2&gt;

&lt;p&gt;headers can be set with the &lt;code&gt;CURLOPT_HEADER&lt;/code&gt; option. the option value passed here is an array of headers as strings:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// set the request headers&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'X-My-Special-Header: foo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  getting headers
&lt;/h2&gt;

&lt;p&gt;response headers and body are returned all in one string. we can extract a string of just the headers using &lt;code&gt;substr&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;first, we need to explicitly tell curl to return the headers with the option &lt;code&gt;CURLOPT_HEADER&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;extracting headers requires us to find the length in bytes of the header block with:&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;$headerSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HEADER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can then use that length to write a &lt;code&gt;substr&lt;/code&gt; command to get a string from character 0 to the the header size. this is all the headers as a string.&lt;/p&gt;

&lt;p&gt;to get the body content, we again use &lt;code&gt;substr&lt;/code&gt; to get a string from the end of the headers section to the end of the returned string. &lt;/p&gt;

&lt;p&gt;it looks 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="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to get response body&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// option to get response headers&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HEADER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// size of the headers in bytes&lt;/span&gt;
&lt;span class="nv"&gt;$headerSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HEADER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// string of all headers&lt;/span&gt;
&lt;span class="nv"&gt;$headersString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&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;$headerSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// get body&lt;/span&gt;
&lt;span class="nv"&gt;$body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$headerSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;headers we get this way are a string, which is not particularly useful.&lt;/p&gt;

&lt;p&gt;to turn that string of headers into an array of header values keyed by header names, we can do a little bit of work with &lt;code&gt;explode&lt;/code&gt;, &lt;code&gt;array_map&lt;/code&gt; and &lt;code&gt;array_combine&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="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HEADER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$headerSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HEADER_SIZE&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$headersString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&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;$headerSize&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// get array of all header lines&lt;/span&gt;
&lt;span class="nv"&gt;$headerLines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&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;$headersString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/^[A-Za-z0-9\-]+:/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

&lt;span class="c1"&gt;// get header names&lt;/span&gt;
&lt;span class="nv"&gt;$headerKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&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="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;$headerLines&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// get header values&lt;/span&gt;
&lt;span class="nv"&gt;$headerValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)&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="nv"&gt;$headerLines&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// combine to array of [name] = value&lt;/span&gt;
&lt;span class="nv"&gt;$headersArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headerKeys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$headerValues&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  functions to extract response headers
&lt;/h3&gt;

&lt;p&gt;here is a convenience function for converting header strings returned by curl into nice, usable arrays:&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="cd"&gt;/**
 * Extract response headers from curl result as array keyed by header name
 *
 * @param string $result Return from curl_exec()
 * @param CurlHandle $ch  Return from curl_init()
 * @return array&amp;lt;string,string&amp;gt;
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getCurlResponseHeaders&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CurlHandle&lt;/span&gt; &lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$headersString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&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="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HEADER_SIZE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$headersLines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&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;$headersString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/^[A-Za-z0-9\-]+:/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;array_combine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&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="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;$headersLines&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)&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="nv"&gt;$headersLines&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;sample output of this function will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    [Server] =&amp;gt; nginx/1.18.0 (Ubuntu)
    [Content-Type] =&amp;gt; application/json
    [Transfer-Encoding] =&amp;gt; chunked
    [Connection] =&amp;gt; keep-alive
    [Set-Cookie] =&amp;gt; woot=bar
    [Cache-Control] =&amp;gt; no-cache, private
    [Date] =&amp;gt; Wed, 20 Aug 2025 22:18:43 GMT
    [Access-Control-Allow-Origin] =&amp;gt; *
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if you have two or more of the same header key, those elements will be overwritten with the last value. &lt;/p&gt;

&lt;p&gt;this function sets header values as arrays, not strings, to allow more than one value per header key.&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="cd"&gt;/**
 * Extract response headers from curl as associated array keyed by header name of arrays of header values
 *
 * @param  string $result The return from curl_exec
 * @param  CurlHandle $ch The return from curl_init
 * @return array&amp;lt;string, &amp;lt;array string&amp;gt;&amp;gt;
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getCurlResponseHeadersArrays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CurlHandle&lt;/span&gt; &lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$headersString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&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="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HEADER_SIZE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$headersLines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&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;$headersString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/^[A-Za-z0-9\-]+:/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="nv"&gt;$headerKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_unique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&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="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;$headersLines&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nv"&gt;$headersArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headersLines&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$headersArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&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="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;))][]&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;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$headersArray&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 looks like this. notice that there are multiple values for the &lt;code&gt;Set-Cookie&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    [Server] =&amp;gt; Array
        (
            [0] =&amp;gt; nginx/1.18.0 (Ubuntu)
        )

    [Content-Type] =&amp;gt; Array
        (
            [0] =&amp;gt; application/json
        )

    [Set-Cookie] =&amp;gt; Array
        (
            [0] =&amp;gt; favourite_year=1993
            [1] =&amp;gt; preferred_format=EP
        )

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

&lt;/div&gt;



&lt;p&gt;both of these functions are &lt;a href="https://gist.github.com/gbhorwood/41b7b9ed1036a03bf7b7ce8e197cc01d" rel="noopener noreferrer"&gt;available as gists&lt;/a&gt;.&lt;br&gt;
&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  basic auth
&lt;/h2&gt;

&lt;p&gt;making requests to sites protected by basic auth can be done by setting the &lt;code&gt;CURLOPT_HTTPAUTH&lt;/code&gt; option to &lt;code&gt;CURLAUTH_BASIC&lt;/code&gt; and setting the &lt;code&gt;CURLOPT_USERPWD&lt;/code&gt; option with the username and password:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// set to use basic auth&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPAUTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLAUTH_BASIC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// set basic auth username and password&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_USERPWD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&amp;lt;username&amp;gt;:&amp;lt;password&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;alternately, we can send an &lt;code&gt;Authorization:&lt;/code&gt; header with the username and password base64 encoded:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// set base64 of username and password in header&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'Authorization: Basic '&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;base64_encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;username&amp;gt;:&amp;lt;password&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  sending cookies
&lt;/h2&gt;

&lt;p&gt;we can send cookies to a site as a string of name=value pairs using the &lt;code&gt;CURLOPT_COOKIE&lt;/code&gt; option.&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// set cookies as string&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_COOKIE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"favourite_year=1993;preferred_format=EP"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or, if we prefer, we can send them using the &lt;code&gt;Cookie&lt;/code&gt; header:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/cookies"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// set cookies as string using headers&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'Cookie: favourite_year=1993;preferred_format=EP'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  getting cookies
&lt;/h2&gt;

&lt;p&gt;cookies set by a site are returned in the &lt;code&gt;Set-Cookie&lt;/code&gt; header. This requires us to extract them using, for instance, the &lt;code&gt;getCurlResponseHeadersArrays&lt;/code&gt; function shown in the section on getting headers above.&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to get response body&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// option to get response headers&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HEADER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Extract response headers from curl as associated array of arrays containing header values
 *
 * @param  string $result The return from curl_exec
 * @param  CurlHandle $ch The return from curl_init
 * @return array&amp;lt;string, &amp;lt;array string&amp;gt;&amp;gt;
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;getCurlResponseHeadersArrays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;CurlHandle&lt;/span&gt; &lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$headersString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&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="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_HEADER_SIZE&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nv"&gt;$headersLines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;explode&lt;/span&gt;&lt;span class="p"&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;$headersString&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;preg_match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/^[A-Za-z0-9\-]+:/'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$l&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
    &lt;span class="nv"&gt;$headerKeys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_unique&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&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="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="nv"&gt;$headersLines&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="nv"&gt;$headersArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headersLines&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$headersArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&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="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;))][]&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;substr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;strpos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$h&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;":"&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$headersArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// extract cookies from headers as array&lt;/span&gt;
&lt;span class="nv"&gt;$cookiesArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getCurlResponseHeadersArrays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;'Set-Cookie'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the output of this example is two cookies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    [0] =&amp;gt; favourite_year=1993
    [1] =&amp;gt; preferred_format=EP
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  cookie jars
&lt;/h2&gt;

&lt;p&gt;persisting cookies between requests is probably something we will need to do. this can be done by saving cookies to the filesystem in files known colloquially as 'cookie jars'&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  getting cookies into file
&lt;/h3&gt;

&lt;p&gt;to save the cookies a site has sent to a file, we set the &lt;code&gt;CURLOPT_COOKIEJAR&lt;/code&gt; to the path of the file we wish to use:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set file where downloaded cookies are stored&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_COOKIEJAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/cookiejar"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we look at the contents of a cookie jar, we can see the cookie data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Netscape HTTP Cookie File
# https://curl.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

example.ca  FALSE   /api/   FALSE   0   preferred_format    EP
example.ca  FALSE   /api/   FALSE   0   favourite_year  1993
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;like the comments say, don't edit this file.&lt;br&gt;
&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  sending cookies from a file
&lt;/h3&gt;

&lt;p&gt;to send cookies from a file, we set the &lt;code&gt;CURLOPT_COOKIEFILE&lt;/code&gt; option to the path of the file we wish to use:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set file where cookies to send are stored&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_COOKIEFILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/cookiejar"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;note:&lt;/strong&gt; the file we send cookies &lt;em&gt;from&lt;/em&gt; is defined by &lt;code&gt;CURLOPT_COOKIEFILE&lt;/code&gt;. the file we receive cookies &lt;em&gt;into&lt;/em&gt; is &lt;code&gt;CURLOPT_COOKIEJAR&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  sending and getting cookies using a file
&lt;/h3&gt;

&lt;p&gt;we can send and receive cookies using files in the same request using the same file. this is a good strategy for constantly keeping cookies fresh.&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set file where cookies to send are stored&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_COOKIEFILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/cookiejar"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set file where downloaded cookies are stored&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_COOKIEJAR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/cookiejar"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  sending &lt;code&gt;POST&lt;/code&gt; requests
&lt;/h2&gt;

&lt;p&gt;we can explicitly set the http method to &lt;code&gt;POST&lt;/code&gt; in two ways: using the &lt;code&gt;CURLOPT_POST&lt;/code&gt; or &lt;code&gt;CURLOPT_CUSTOMREQUEST&lt;/code&gt; options.&lt;/p&gt;

&lt;p&gt;to use &lt;code&gt;CURLOPT_POST&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="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set http method as POST&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_POST&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;important:&lt;/strong&gt; using the option &lt;code&gt;CURLOPT_POST&lt;/code&gt; automatically sends the header &lt;code&gt;Content-Type: application/x-www-form-urlencoded&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;if we want to avoid sending that header we can use the &lt;code&gt;CURLOPT_CUSTOMREQUEST&lt;/code&gt; option like so:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set http method as POST&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_CUSTOMREQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'POST'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  sending json via &lt;code&gt;POST&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;sending json data via post is done by setting the &lt;code&gt;CURLOPT_POSTFIELDS&lt;/code&gt; option with the json data and setting the &lt;code&gt;Content-Type: application/json&lt;/code&gt; header:&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// build json for request body&lt;/span&gt;
&lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'name'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'bratmobile'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="nv"&gt;$json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;json_encode&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;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set body of request&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_POSTFIELDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// header to set request content type as json&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;setting &lt;code&gt;CURLOPT_POSTFIELDS&lt;/code&gt; automatically sets the http method to &lt;code&gt;POST&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  sending form data via &lt;code&gt;POST&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;form data can be sent via curl by setting the option &lt;code&gt;CURLOPT_POSTFIELDS&lt;/code&gt; with the form data. form data is formatted like a query string. we can use &lt;code&gt;http_build_query&lt;/code&gt; here to help.&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;$args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;'artist'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'monk, thelonious'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;'years'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="mi"&gt;1957&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;1963&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// convert array to form data&lt;/span&gt;
&lt;span class="nv"&gt;$postFields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;http_build_query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// option to set post fields&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_POSTFIELDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$postFields&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;setting the &lt;code&gt;CURLOPT_POSTFIELDS&lt;/code&gt; option automatically sets the method to &lt;code&gt;POST&lt;/code&gt; and adds the request header &lt;code&gt;Content-Type: application/x-www-form-urlencoded&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  sending &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;the &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt; and &lt;code&gt;DELETE&lt;/code&gt; methods can be sent using the &lt;code&gt;CURLOPT_CUSTOMREQUEST&lt;/code&gt; option&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set http method as PUT&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_CUSTOMREQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'PUT'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// or PATCH or DELETE&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  downloading a file
&lt;/h2&gt;

&lt;p&gt;downloading a file requires a file write handler created by &lt;code&gt;fopen()&lt;/code&gt; to be passed to the option &lt;code&gt;CURLOPT_FILE&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="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands/bratmobile.jpg"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// file pointer for downloaded file. &lt;/span&gt;
&lt;span class="nv"&gt;$outputFp&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="s2"&gt;"/path/to/image.jpg"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"wb"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set file pointer where downloaded file will be saved&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_FILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$outputFp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  uploading a file
&lt;/h2&gt;

&lt;p&gt;uploading a file requires creating a &lt;code&gt;CURLFile&lt;/code&gt; object with the path and mimetype of the file to upload and then assigning that object to the &lt;code&gt;CURLOPT_POSTFIELDS&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;note that &lt;code&gt;postname&lt;/code&gt; here is the name of the file as the receiving server sees it.&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"http://example.ca/api/bands"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// path of file to upload&lt;/span&gt;
&lt;span class="nv"&gt;$path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/path/to/file.txt"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// get mime type of file from content&lt;/span&gt;
&lt;span class="nv"&gt;$mime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;mime_content_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// the key of the file. usually 'file'&lt;/span&gt;
&lt;span class="nv"&gt;$postname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'file'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// create CURLFile to upload in post fields&lt;/span&gt;
&lt;span class="nv"&gt;$curlFile&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;CURLFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$mime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$postname&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set CURLFile in post fields&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_POSTFIELDS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$curlFile&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;postname&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$curlFile&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  using ftp
&lt;/h2&gt;

&lt;p&gt;php has an existing  &lt;a href="https://www.php.net/manual/en/ref.ftp.php" rel="noopener noreferrer"&gt;suite of ftp commands&lt;/a&gt;  that are almost certainly more useful than curl. consider using those instead.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  running ftp commands
&lt;/h3&gt;

&lt;p&gt;we can log into an ftp server by setting the &lt;code&gt;CURLOPT_USERPWD&lt;/code&gt; option to the username and password of the account separated with a colon.&lt;/p&gt;

&lt;p&gt;sending commands to the ftp server is done with the &lt;code&gt;CURLOPT_CUSTOMREQUEST&lt;/code&gt; option.&lt;/p&gt;

&lt;p&gt;multiple ftp commands can be run per connection.&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;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ftp://ftp.example.ca"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ftp username and password&lt;/span&gt;
&lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"someftpuser"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secretpassword"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// set username and password&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_USERPWD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// optionally set verbose output for debugging&lt;/span&gt;
&lt;span class="c1"&gt;// curl_setopt($ch, CURLOPT_VERBOSE, true);&lt;/span&gt;

&lt;span class="c1"&gt;// execute HELP command&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_CUSTOMREQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'HELP'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// execute CWD command to cd to a directory&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_CUSTOMREQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'CWD /path/to/dir'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// execute LIST command to ls the current directory&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_CUSTOMREQUEST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'LIST'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$directoryList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// output results of LIST&lt;/span&gt;
&lt;span class="nb"&gt;print_r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$directoryList&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;important note:&lt;/strong&gt; sending the &lt;code&gt;HELP&lt;/code&gt; command to the ftp server will return a list of valid commands. only those commands will be accepted by the ftp server.&lt;/p&gt;

&lt;p&gt;an example of the output from the &lt;code&gt;HELP&lt;/code&gt; command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;214-The following commands are recognized.
 ABOR ACCT ALLO APPE CDUP CWD  DELE EPRT EPSV FEAT HELP LIST MDTM MKD
 MODE NLST NOOP OPTS PASS PASV PORT PWD  QUIT REIN REST RETR RMD  RNFR
 RNTO SITE SIZE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
 XPWD XRMD
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;note:&lt;/strong&gt;  setting the &lt;code&gt;CURLOPT_VERBOSE&lt;/code&gt; option to &lt;code&gt;true&lt;/code&gt; will output all ftp traffic and is useful in inspecting/debugging.  some commands, such as &lt;code&gt;HELP&lt;/code&gt; may not return output and can only be read when the verbose option is turned on.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  uploading a file via ftp
&lt;/h3&gt;

&lt;p&gt;uploading a file via ftp requires several steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;set the &lt;code&gt;CURLOPT_UPLOAD&lt;/code&gt; option to &lt;code&gt;true&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;set the &lt;code&gt;CURLOPT_INFILE&lt;/code&gt; option to a file pointer of the file created by &lt;code&gt;fopen&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;set the &lt;code&gt;CURLOPT_INFILESIZE&lt;/code&gt; option to the size of the file from &lt;code&gt;filesize()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;set the url of the request to the path and name of the uploaded file
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="c1"&gt;// full path to file to upload&lt;/span&gt;
&lt;span class="nv"&gt;$file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/tmp/filetoupload"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// open file and get pointer&lt;/span&gt;
&lt;span class="nv"&gt;$fp&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="nv"&gt;$file&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="c1"&gt;// build url including path of target file&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ftp://ftp.example.ca/path/to/dir/"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ftp username and password&lt;/span&gt;
&lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"someftpuser"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secretpassword"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// set username and password&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_USERPWD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="c1"&gt;// option to set upload&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_UPLOAD&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="c1"&gt;// option to set pointer to file to upload&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_INFILE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// option to set size of file&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_INFILESIZE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filesize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  downloading a file via ftp
&lt;/h3&gt;

&lt;p&gt;downloading a file from an ftp server involves making an ftp request to the file and then writing the result of &lt;code&gt;curl_exec&lt;/code&gt; to a file.&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;// path to remote file on server&lt;/span&gt;
&lt;span class="nv"&gt;$remoteFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/TEST_OUT/sampleout"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// path to file where the downloaded file will be saved&lt;/span&gt;
&lt;span class="nv"&gt;$localFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/tmp/downloadedfile"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// open download file and get pointer&lt;/span&gt;
&lt;span class="nv"&gt;$fp&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="nv"&gt;$localFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"w"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// url of ftp server including path of file to download&lt;/span&gt;
&lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ftp://ftp.avpbooks.com"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;$remoteFile&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ftp username and password&lt;/span&gt;
&lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"avpftpuser"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nv"&gt;$password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"RadialContentsGopherKelp98!3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// set username and password&lt;/span&gt;
&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_USERPWD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;$password&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// write result to file&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;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



</description>
      <category>php</category>
    </item>
    <item>
      <title>nginx: useful basic auth</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Wed, 13 Aug 2025 14:42:37 +0000</pubDate>
      <link>https://dev.to/gbhorwood/nginx-useful-basic-auth-8a1</link>
      <guid>https://dev.to/gbhorwood/nginx-useful-basic-auth-8a1</guid>
      <description>&lt;p&gt;basic auth doesn't get the love it deserves. sure, it's not as fancy as other authentication models out there, it's &lt;em&gt;basic&lt;/em&gt; auth, after all. but if you want to stand up some fast authentication to limit your staging site to the dev team or restrict api access to a few users, basic auth is a good choice. best of all, we can implement the whole thing in nginx so we don't have to modify our site or api's authentication.&lt;/p&gt;

&lt;p&gt;in this article, we'll be going over setting up basic auth in nginx and getting it to do useful, practical things like restrict by specific url locations, set access by username, design a way to assign roles to users, and pass usernames into our site's code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F08%2Fbasicauth_meme.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F08%2Fbasicauth_meme.jpg" title="the 'mugatu' meme" alt="the 'mugatu' meme" width="620" height="496"&gt;&lt;/a&gt;basic auth is so adequate for fast ad-hoc authentication right now&lt;/p&gt;

&lt;h2&gt;
  
  
  the flyover
&lt;/h2&gt;

&lt;p&gt;this article is divided into two main parts: creating and managing user accounts with &lt;code&gt;htpasswd&lt;/code&gt;, and configuring nginx with basic auth to restrict or allow access.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
creating accounts with htpasswd

&lt;ul&gt;
&lt;li&gt;installing &lt;code&gt;apache2-utils&lt;/code&gt; to get &lt;code&gt;htpasswd&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;creating a new htpasswd file&lt;/li&gt;
&lt;li&gt;adding accounts to htpasswd&lt;/li&gt;
&lt;li&gt;adding htpasswd accounts non-interactively&lt;/li&gt;
&lt;li&gt;deleting htpasswd accounts&lt;/li&gt;
&lt;li&gt;choosing a better hashing algorithm&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

configuring nginx with basic auth

&lt;ul&gt;
&lt;li&gt;protecting the entire site&lt;/li&gt;
&lt;li&gt;excluding locations from basic auth&lt;/li&gt;
&lt;li&gt;requiring auth for only certain locations&lt;/li&gt;
&lt;li&gt;adding further restrictions by username&lt;/li&gt;
&lt;li&gt;restricting by user role&lt;/li&gt;
&lt;li&gt;getting the username into our code&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  creating accounts with htpasswd
&lt;/h2&gt;

&lt;p&gt;basic auth stores user accounts in a flat file as a list of name/password pairs. login credentials need to be added to this file manually. we are going to do this using the &lt;code&gt;htpasswd&lt;/code&gt; utility.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  installing apache2-utils to get htpasswd
&lt;/h3&gt;

&lt;p&gt;the &lt;code&gt;htpasswd&lt;/code&gt; command is part of the &lt;code&gt;apache2-utils&lt;/code&gt; package. since we're using nginx, it may seem odd to need a tool from that &lt;em&gt;other&lt;/em&gt; webserver, but nginx's basic auth feature is designed to mimic apache's older implementation. if we &lt;em&gt;really&lt;/em&gt; want to, we can manually create a hashed password using &lt;a href="https://docs.openssl.org/1.0.2/man1/passwd/" rel="noopener noreferrer"&gt;&lt;code&gt;openssl passwd -apr1&lt;/code&gt;&lt;/a&gt; or similar and then hand edit our flat file, but why bother when &lt;code&gt;htpasswd&lt;/code&gt; is quick, easy and free?&lt;/p&gt;

&lt;p&gt;every major distro has an &lt;code&gt;apache2-utils&lt;/code&gt; package. on ubuntu-like systems we can install with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;apache2-utils
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if, for some reason, we need to build from source, we can &lt;a href="https://github.com/elswork/apache2-utils" rel="noopener noreferrer"&gt;do that from a docker file&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  creating a new .htpasswd file
&lt;/h3&gt;

&lt;p&gt;once we have &lt;code&gt;htpasswd&lt;/code&gt; installed, we can use it to create our flat file and add a user. the format for the command to do this is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-c&lt;/span&gt; &amp;lt;/path/to/.htpasswd&amp;gt; &amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we run &lt;code&gt;htpasswd&lt;/code&gt; as root and pass it the &lt;code&gt;-c&lt;/code&gt; switch to 'create' a new password file. the arguments are the full path to where we want our file to live and the username of the account we are creating.&lt;/p&gt;

&lt;p&gt;this command is interactive: &lt;code&gt;htpasswd&lt;/code&gt; will prompt us for the password we want to assign to our user. for example, to create an account for the user 'molly':&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-c&lt;/span&gt; /etc/apache2/.htpasswd molly
New password: &lt;span class="k"&gt;*******&lt;/span&gt;
Re-type new password: &lt;span class="k"&gt;*******&lt;/span&gt;
Adding password &lt;span class="k"&gt;for &lt;/span&gt;user molly
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the traditional location for our flat file of user accounts is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/etc/apache2/.htpasswd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can put it anywhere we want, but we probably shouldn't mess with tradition.&lt;/p&gt;

&lt;p&gt;if we look at our newly-created &lt;code&gt;.htpasswd&lt;/code&gt; file, we can see the account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/apache2/.htpasswd
molly:&lt;span class="nv"&gt;$apr1$LONuzAbJ$zZFjMjRDM6v3FkolReLfq0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;each account lives on one line and consists of two fields: the username, in plaintext, and the hashed password. the fields are separated by a colon.&lt;/p&gt;

&lt;p&gt;if we look at the password hash, we see that it starts with &lt;code&gt;$apr1$&lt;/code&gt;. this indicates which hashing algorithm was used. in this case 'apr1', which is an apache-specific algorithm that uses &lt;code&gt;md5&lt;/code&gt;. there are other, better, &lt;a href="https://httpd.apache.org/docs/2.4/misc/password_encryptions.html" rel="noopener noreferrer"&gt;hashing choices&lt;/a&gt; we can use. we'll go over that later.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  adding accounts to htpasswd
&lt;/h3&gt;

&lt;p&gt;the &lt;code&gt;-c&lt;/code&gt; switch creates a new flat file. if you already have a file and run &lt;code&gt;htpasswd -c&lt;/code&gt; it &lt;strong&gt;will delete your file and replace it&lt;/strong&gt;. not good.&lt;/p&gt;

&lt;p&gt;to add new accounts to the password file, we simply omit the &lt;code&gt;-c&lt;/code&gt; switch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &amp;lt;/path/to/.htpasswd&amp;gt; &amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;note that if we want to create a user with a username that contains a space, we need to enclose the username in quotes. for instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd /etc/apache2/.htpasswd &lt;span class="s2"&gt;"allison wolfe"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  adding htpasswd accounts non-interactively
&lt;/h3&gt;

&lt;p&gt;by default, &lt;code&gt;htpasswd&lt;/code&gt; prompts us for a password. this is both boring and difficult to script. &lt;/p&gt;

&lt;p&gt;if we want to add an account as one command, with the password as an argument, we can use the &lt;code&gt;-b&lt;/code&gt; switch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-b&lt;/span&gt; &amp;lt;/path/to/.htpasswd&amp;gt; &amp;lt;username&amp;gt; &amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  deleting htpasswd accounts
&lt;/h3&gt;

&lt;p&gt;if we want to delete an account, one option is to manually edit our &lt;code&gt;.htpasswd&lt;/code&gt; file. the other option is to run &lt;code&gt;htpasswd&lt;/code&gt; with the &lt;code&gt;-D&lt;/code&gt; switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-D&lt;/span&gt; &amp;lt;/path/to/.htpasswd&amp;gt; &amp;lt;username&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  choosing a better hashing algorithm
&lt;/h3&gt;

&lt;p&gt;the default hashing algorithm that &lt;code&gt;htpasswd&lt;/code&gt; uses is 'apr1'. this is an apache-specific implementation of &lt;code&gt;md5&lt;/code&gt; that does an iteration of hashes using a series of random salts. is it better than raw &lt;code&gt;md5&lt;/code&gt;? yes. but that doesn't mean it's a good or secure choice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F08%2Fmd5_meme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F08%2Fmd5_meme.png" title="the 'anakin' meme" alt="the 'anakin' meme" width="582" height="581"&gt;&lt;/a&gt;you’re not using md5 to hash passwords, right&lt;/p&gt;

&lt;p&gt;a better option is &lt;a href="https://en.wikipedia.org/wiki/Bcrypt" rel="noopener noreferrer"&gt;bcrypt&lt;/a&gt;, which we can use by passing the &lt;code&gt;-B&lt;/code&gt; switch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-bB&lt;/span&gt; &amp;lt;/path/to/.htpasswd&amp;gt; &amp;lt;username&amp;gt; &amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we want to make our bcrypt password even harder to brute-force, we can also add the &lt;code&gt;-C&lt;/code&gt; argument to set the amount of computing time required to create the hash. the longer the computing time, the longer it takes an attacker to try every possible password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-bB&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; &amp;lt;&lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;/path/to/.htpasswd&amp;gt; &amp;lt;username&amp;gt; &amp;lt;password&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;-C&lt;/code&gt; argument takes an integer value for time. the minimum is 4, the default value is 5, and the maximum is 17.  &lt;/p&gt;

&lt;p&gt;if we use the maximum &lt;code&gt;-C&lt;/code&gt; value, &lt;code&gt;htpassword&lt;/code&gt; will take several seconds to run. for instance, this command took over twelve seconds on my machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;htpasswd &lt;span class="nt"&gt;-b&lt;/span&gt; &lt;span class="nt"&gt;-B&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; 17 /etc/apache2/.htpasswd allison mysecretpassword
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;note:&lt;/strong&gt; the password hash needs to be run every time a user authenticates. if we choose a long hashing time when we create a password it will affect user performance.&lt;/p&gt;

&lt;p&gt;when we look at our &lt;code&gt;htpasswd&lt;/code&gt; file after generating accounts with bcrypt hashing, we see that the algorithm field has changed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;molly:$2y$05$2k3HbYVvpiwW0.6D5zCFVOQK/RxjquivUGcr8uyC039e0qYaef1TC
allison:$2y$17$8ODsatSNUJImOsPMnSoe0uFMkpDBcnHatc9XXOY3/N857ZyVyYPmO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, the algorithm is listed as '2y', which is bcrypt. there is also a field for the computer time value: &lt;code&gt;$05$&lt;/code&gt; for molly and &lt;code&gt;$17$&lt;/code&gt; for allison.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  configuring nginx with basic auth
&lt;/h2&gt;

&lt;p&gt;once we have an &lt;code&gt;.htpasswd&lt;/code&gt; flat file, we can use it to require authentication to access our nginx-served site. we do this by modifying nginx's configuration files.&lt;/p&gt;

&lt;p&gt;at it's core, basic auth is enabled by adding two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"&amp;lt;login&lt;/span&gt; &lt;span class="s"&gt;message&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/path/to/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the first line here, &lt;code&gt;basic_auth&lt;/code&gt;, sets the message that is shown in the login form. the second line, &lt;code&gt;basic_auth_user_file&lt;/code&gt; sets the path to our &lt;code&gt;.htpassword&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  protecting the entire site
&lt;/h3&gt;

&lt;p&gt;to protect our entire site, we add our basic auth directives to the &lt;code&gt;server&lt;/code&gt; block of nginx configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.ca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/example/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# basic auth configuratoin&lt;/span&gt;
    &lt;span class="kn"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"Login&lt;/span&gt; &lt;span class="s"&gt;required"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/etc/apache2/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/private/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/public/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;with this config, both the &lt;code&gt;private&lt;/code&gt; and &lt;code&gt;public&lt;/code&gt; locations, as well as any other uri, requires a successful login to access.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  excluding locations from basic auth
&lt;/h3&gt;

&lt;p&gt;if we want to exclude a location or locations from requiring authentication, we can use the directive&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;auth_basic&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;at the location level.&lt;/p&gt;

&lt;p&gt;here, we've turned off basic auth for the &lt;code&gt;public&lt;/code&gt; location.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.ca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/example/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# basic auth configuratoin&lt;/span&gt;
    &lt;span class="kn"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"Login&lt;/span&gt; &lt;span class="s"&gt;required"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/etc/apache2/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/private/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# no authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/public/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# turn off auth basic for this location&lt;/span&gt;
        &lt;span class="kn"&gt;auth_basic&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;all other locations require login.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  requiring auth for only certain locations
&lt;/h3&gt;

&lt;p&gt;if we configure basic auth at the location level, instead of the server level, authentication will only be required for the location.&lt;/p&gt;

&lt;p&gt;here, we have set auth for the &lt;code&gt;private&lt;/code&gt; and &lt;code&gt;alsoprivate&lt;/code&gt; locations but not for &lt;code&gt;public&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.ca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/example/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/private/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# basic auth configuratoin&lt;/span&gt;
        &lt;span class="kn"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"Login&lt;/span&gt; &lt;span class="s"&gt;required"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/etc/apache2/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/alsoprivate/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# basic auth configuratoin&lt;/span&gt;
        &lt;span class="kn"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"Login&lt;/span&gt; &lt;span class="s"&gt;required"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/etc/apache2/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# no authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/public/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  adding further restrictions by username
&lt;/h3&gt;

&lt;p&gt;normally, basic auth is a pretty blunt instrument: you're either authenticated and allowed to make a request or you aren't. &lt;/p&gt;

&lt;p&gt;if we want more fine-grained control, we can limit access to specific users by their username by using the &lt;code&gt;$remote_user&lt;/code&gt; variable.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;$remote_user&lt;/code&gt; is a built-in variable that contains the username of the user. we can combine that with an &lt;code&gt;if&lt;/code&gt; statement in a &lt;code&gt;location&lt;/code&gt; block to fine tune our permissions. in this example, we have three &lt;code&gt;location&lt;/code&gt;s:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;public&lt;/code&gt;: no authentication.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;private&lt;/code&gt;: any authenticated user who is &lt;em&gt;not&lt;/em&gt; molly&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;molly&lt;/code&gt;: only molly&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;let's look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.ca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/example/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"Login&lt;/span&gt; &lt;span class="s"&gt;required"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/etc/apache2/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# all authenticated users except molly permitted&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/private/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;# check remote user and deny molly access&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'molly')&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Molly&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="s"&gt;not&lt;/span&gt; &lt;span class="s"&gt;allowed&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# only molly permitted&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt;  &lt;span class="n"&gt;/molly/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;# check remote user and allow only molly&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;'molly')&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Only&lt;/span&gt; &lt;span class="s"&gt;Molly&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="s"&gt;allowed&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# no authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/public/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# turn off auth basic for this location&lt;/span&gt;
        &lt;span class="kn"&gt;auth_basic&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;here, in the &lt;code&gt;private&lt;/code&gt; location, we test the value of &lt;code&gt;$remote_user&lt;/code&gt; using a pretty basic &lt;code&gt;if&lt;/code&gt; statement. if it is 'molly', we return a &lt;code&gt;403&lt;/code&gt; error code and some html. all other authenticated users are permitted access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'molly')&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Molly&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="s"&gt;not&lt;/span&gt; &lt;span class="s"&gt;allowed&amp;lt;/body&amp;gt;&amp;lt;/html&amp;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;likewise, in the &lt;code&gt;molly&lt;/code&gt; location, we can test for the 'not molly' condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;'molly')&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Only&lt;/span&gt; &lt;span class="s"&gt;Molly&lt;/span&gt; &lt;span class="s"&gt;is&lt;/span&gt; &lt;span class="s"&gt;allowed&amp;lt;/body&amp;gt;&amp;lt;/html&amp;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;the &lt;code&gt;add_header&lt;/code&gt; and &lt;code&gt;return&lt;/code&gt; directives here are how we output html from nginx. if we decide (wisely) that hardcoding content in our &lt;code&gt;nginx&lt;/code&gt; config file is a bad idea, we can return content from a file using the &lt;code&gt;rewrite&lt;/code&gt; directive like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"molly")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;rewrite&lt;/span&gt; &lt;span class="s"&gt;^&lt;/span&gt; &lt;span class="n"&gt;/path/to/molly_not_allowed.html&lt;/span&gt; &lt;span class="s"&gt;break&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;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  restricting by user role
&lt;/h3&gt;

&lt;p&gt;handling permissions by user name is useful stuff, but it can get clumsy and difficult to maintain pretty fast. it would be much more convenient if we could assign 'roles' to our users and then handle access by that.&lt;/p&gt;

&lt;p&gt;doing this is a two-step process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;assigning users to roles using the &lt;code&gt;map&lt;/code&gt; directive&lt;/li&gt;
&lt;li&gt;testing the role&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;the 'assigning users to roles' is the new material here, so we'll look at that first. let's look at an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# map user names to roles&lt;/span&gt;
&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="nv"&gt;$role&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;molly&lt;/span&gt;     &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;allison&lt;/span&gt;   &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;erin&lt;/span&gt;      &lt;span class="s"&gt;"client"&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;here, we use &lt;code&gt;nginx&lt;/code&gt;'s &lt;code&gt;map&lt;/code&gt; directive to &lt;em&gt;map&lt;/em&gt; a username to a role and assign that role to a variable. if you read the article on  &lt;a href="https://dev.to/gbhorwood/nginx-putting-your-site-in-downtime-for-everyone-except-you-34g0"&gt;restricting nginx by ip address&lt;/a&gt; this should be familiar. if not, we'll go over it:&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;map&lt;/code&gt; function takes three arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;the value to test:&lt;/strong&gt; this is &lt;code&gt;$remote_user&lt;/code&gt;, the internal variable we've used before to get the username of the authenticated user. this is the value that we will be testing in the list of test cases below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the variable to set:&lt;/strong&gt; the variable that will hold the result of our mapping procedure. in this case it is &lt;code&gt;$role&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the list of test cases:&lt;/strong&gt; a list of tuples, essentially. if the left value of a tuple matches the value we are testing (&lt;code&gt;$remote_user&lt;/code&gt;) then the right value is assigned to the variable we want to set (&lt;code&gt;$role&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;knowing this, we can see that if the value of &lt;code&gt;$remote_user&lt;/code&gt; is 'molly', then our &lt;code&gt;map&lt;/code&gt; call will assign the value 'dev' to the variable &lt;code&gt;$role&lt;/code&gt;. we can then use &lt;code&gt;$role&lt;/code&gt; in an &lt;code&gt;if&lt;/code&gt; statement in our &lt;code&gt;location&lt;/code&gt; blocks.&lt;/p&gt;

&lt;p&gt;let's look at a full configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="c1"&gt;# map user names to roles&lt;/span&gt;
&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$remote_user&lt;/span&gt; &lt;span class="nv"&gt;$role&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;molly&lt;/span&gt;     &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;allison&lt;/span&gt;   &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;erin&lt;/span&gt;      &lt;span class="s"&gt;"client"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example3.ca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/example3/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"Login&lt;/span&gt; &lt;span class="s"&gt;required"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/etc/apache2/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# role 'dev' only&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/devonly/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$role&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;'dev')&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Development&lt;/span&gt; &lt;span class="s"&gt;only&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# role 'client' only&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/client/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$role&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;'client')&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Client&lt;/span&gt; &lt;span class="s"&gt;only&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/staging/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# no authentication required&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/public/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;# turn off auth basic for this location&lt;/span&gt;
        &lt;span class="kn"&gt;auth_basic&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;here, we see that in the &lt;code&gt;devonly&lt;/code&gt; location we test the value of &lt;code&gt;$role&lt;/code&gt; and if it is not &lt;code&gt;dev&lt;/code&gt;, we deny access. &lt;/p&gt;

&lt;p&gt;handling access by user role this way does require a lot of direct intervention on our behalf. we need to manually edit our &lt;code&gt;nginx&lt;/code&gt; config file and restart the daemon every time we want to make a change. that's not the most convenient thing in the world, but if we have a small number of users and don't want to fiddle with the authentication model our site or api is using, it can be effective.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  getting the username into our code
&lt;/h3&gt;

&lt;p&gt;one of the great things about basic auth is that you can add it &lt;em&gt;on top&lt;/em&gt; of your site or api's already-existing authentication structure. you can keep your site or api's login model the way it is; your code never even needs to know that basic auth is being used.&lt;/p&gt;

&lt;p&gt;but sometimes that might be more of a bug than a feature. maybe we &lt;em&gt;want&lt;/em&gt; our app to know the basic auth username of our visitors.&lt;/p&gt;

&lt;p&gt;we can do this using 'fastcgi params'.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;fastcgi_param&lt;/code&gt; directive is a way to assign a value to a variable in our nginx configuration and then pass that variable to the fastcgi server, allowing our code to read it. if we're using php, the value is accessible in the &lt;code&gt;$_SERVER&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fastcgi_param&lt;/code&gt; takes two arguments: first, the name of the fastcgi parameter we want to declare and, second, the value we want to give it. to assign &lt;code&gt;$remote_user&lt;/code&gt; to the fastcgi parameter &lt;code&gt;REMOTE_USER&lt;/code&gt; we would do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;REMOTE_USER&lt;/span&gt; &lt;span class="nv"&gt;$remote_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;once we have assigned that parameter, we can read it from the &lt;code&gt;$_SERVER&lt;/code&gt; array like so:&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;$_SERVER&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'REMOTE_USER'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;let's look at an example configuration for this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;example.ca&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/example/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;auth_basic&lt;/span&gt;           &lt;span class="s"&gt;"Login&lt;/span&gt; &lt;span class="s"&gt;required"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;auth_basic_user_file&lt;/span&gt; &lt;span class="n"&gt;/etc/apache2/.htpasswd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;~&lt;/span&gt; &lt;span class="sr"&gt;\.php$&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_split_path_info&lt;/span&gt; &lt;span class="s"&gt;^(.+&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s"&gt;.php)(/.+)&lt;/span&gt;$&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_pass&lt;/span&gt; &lt;span class="s"&gt;unix:/var/run/php/php8.3-fpm.sock&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_index&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;include&lt;/span&gt; &lt;span class="s"&gt;fastcgi_params&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;# set $remote_user in $_SERVER['REMOTE_USER']&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;REMOTE_USER&lt;/span&gt; &lt;span class="nv"&gt;$remote_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;fastcgi_param&lt;/span&gt; &lt;span class="s"&gt;SCRIPT_FILENAME&lt;/span&gt; &lt;span class="nv"&gt;$document_root$fastcgi_script_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_intercept_errors&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_buffer_size&lt;/span&gt; &lt;span class="mi"&gt;16k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_buffers&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="mi"&gt;16k&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_connect_timeout&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_send_timeout&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;fastcgi_read_timeout&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="n"&gt;/var/log/nginx/access.log&lt;/span&gt; &lt;span class="s"&gt;combined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="n"&gt;/var/log/nginx/error.log&lt;/span&gt; &lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;if we look inside the &lt;code&gt;location ~ \.php$&lt;/code&gt; block we can see the fastcgi parameter assignment.&lt;/p&gt;

&lt;h3&gt;
  
  
  conclusion
&lt;/h3&gt;

&lt;p&gt;basic auth isn't a replacement for a real and robust login model and if you try to use it like one, you will be disappointed. however, if you need to put together a fast or ad hoc authorization system or need to add some 'extra' auth to restrict some or all of your site for internal users, it is a powerful tool. if you even casually dabble in ops, having a strong understanding of basic auth is a good thing.&lt;/p&gt;

</description>
      <category>nginx</category>
      <category>linux</category>
    </item>
    <item>
      <title>php: manipulating file pointers with fseek and ftell</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Wed, 23 Jul 2025 14:40:33 +0000</pubDate>
      <link>https://dev.to/gbhorwood/php-manipulating-file-pointers-with-fseek-and-ftell-59ko</link>
      <guid>https://dev.to/gbhorwood/php-manipulating-file-pointers-with-fseek-and-ftell-59ko</guid>
      <description>&lt;p&gt;php developers generally don't put a lot of thought into reading files from disk. we usually just call &lt;code&gt;file&lt;/code&gt; or &lt;code&gt;file_get_contents&lt;/code&gt; or similar, shovel the whole thing into ram, and then fight with the resulting array or string. if we want to do simple things with small files, it works okay. but php can do a lot more.&lt;/p&gt;

&lt;p&gt;in this article, we're going to look at php's file pointer and how to manipulate it using commands like &lt;a href="https://www.php.net/manual/en/function.fseek.php" rel="noopener noreferrer"&gt;&lt;code&gt;fseek&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.php.net/manual/en/function.ftell.php" rel="noopener noreferrer"&gt;&lt;code&gt;ftell&lt;/code&gt;&lt;/a&gt;, and  by the end we'll be able to build a &lt;a href="https://gist.github.com/gbhorwood/865c3e85fbff170ce659963d7ff72f6c" rel="noopener noreferrer"&gt;pure php version of linux's &lt;code&gt;tail -f&lt;/code&gt; command&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F07%2Ffp_meme.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F07%2Ffp_meme.png" title="the 'no one knows' meme" alt="the 'no one knows' meme" width="500" height="501"&gt;&lt;/a&gt;no one knows we can do file pointer manipulation with fseek and ftell&lt;/p&gt;

&lt;h2&gt;
  
  
  the sample file
&lt;/h2&gt;

&lt;p&gt;any discussion of file reading needs a sample file to do that reading on. for all the examples in this article we'll be using this short, ten-line file of bands i own records by. it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bratmobile
coltrane, john
dumb
eno, brian
fall, the
gryce, gigi
hella
idles
die kreuzen
johnson, linton kwesi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  a short overview of the file pointer
&lt;/h2&gt;

&lt;p&gt;in php, when we want to open a file for reading we typically use &lt;code&gt;fopen&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="nv"&gt;$fp&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="s2"&gt;"/path/to/file"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the return value from &lt;code&gt;fopen&lt;/code&gt; is a file pointer.&lt;/p&gt;

&lt;p&gt;file pointers keep track of where we are in the file and advance as we read data from the file. when we first create our pointer with &lt;code&gt;fopen()&lt;/code&gt;, it is set to the beginning of the file. if we read one character from the file with &lt;code&gt;fgetc()&lt;/code&gt;, the pointer advances one character. if we read a line with &lt;code&gt;fgets()&lt;/code&gt;, the pointer moves to the beginning of the next line.&lt;/p&gt;

&lt;p&gt;we can see this behaviour in action by using two loops to read parts of our file:&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;$fp&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="s2"&gt;"/path/to/bands"&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="c1"&gt;// call fgets() three times&lt;/span&gt;
&lt;span class="k"&gt;for&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;0&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&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;span class="k"&gt;print&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;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// call fgets() three times, again&lt;/span&gt;
&lt;span class="k"&gt;for&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;0&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&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;span class="k"&gt;print&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;$fp&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;the second &lt;code&gt;for&lt;/code&gt; loop here does not read the first three lines again. instead, it picks up at line four and continue on from there. our output looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bratmobile
coltrane, john
dumb
eno, brian
fall, the
gryce, gigi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this is because the file pointer advances one line every time &lt;code&gt;fgets()&lt;/code&gt; gets called, regardless of which loop it is in.&lt;/p&gt;

&lt;h2&gt;
  
  
  rewinding the file pointer
&lt;/h2&gt;

&lt;p&gt;reading from a file only moves the pointer in one direction: forward. there's no way to use &lt;code&gt;fgets&lt;/code&gt; or &lt;code&gt;fgetc&lt;/code&gt; to move our pointer backwards.&lt;/p&gt;

&lt;p&gt;if we want to start reading our file from beginning again, we have to explicitly set our pointer to the start of the file with &lt;code&gt;rewind&lt;/code&gt;. let's look:&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;$fp&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="s2"&gt;"/path/to/bands"&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="c1"&gt;// call fgets() three times&lt;/span&gt;
&lt;span class="k"&gt;for&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;0&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&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;span class="k"&gt;print&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;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// set the file pointer to the beginning of the file&lt;/span&gt;
&lt;span class="nb"&gt;rewind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// call fgets() three times, again&lt;/span&gt;
&lt;span class="k"&gt;for&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;0&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&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;span class="k"&gt;print&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;$fp&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;here, we read the first three lines from our file, then set our pointer back to the file start and read the first three lines again. our output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bratmobile
coltrane, john
dumb
bratmobile
coltrane, john
dumb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  setting the file pointer with &lt;code&gt;fseek&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;if we want more control over our file pointer, we can use &lt;code&gt;fseek&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fseek&lt;/code&gt; allows us to set our pointer to an exact byte in the file. to do this, we need to tell fseek two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;where we are starting from, aka 'whence'&lt;/li&gt;
&lt;li&gt;how many bytes to move the pointer, aka 'offset'&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;let's look at 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;$fp&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="s2"&gt;"/path/to/bands"&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="c1"&gt;// move our pointer four bytes&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;$fp&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we are moving our file pointer four bytes from the beginning of our file.&lt;/p&gt;

&lt;p&gt;the 'whence' argument here is the third one. we have three possible values we can pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SEEK_SET&lt;/code&gt; the start of the file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SEEK_END&lt;/code&gt; the end of the file&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SEEK_CUR&lt;/code&gt; the current position of the file pointer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;with &lt;code&gt;fseek&lt;/code&gt; we can move our pointer anywhere we want to. for instance:&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;// advance pointer 10 bytes from current position&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;$fp&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;SEEK_CUR&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// move pointer to 15 bytes from the end of the file&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;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;15&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;// set point to 5 bytes from the start of the file&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;$fp&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_SET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;let's look at that in action. here, we move the file pointer four bytes in from the start of the file, skipping  the first four characters  of '&lt;a href="https://gbh.fruitbat.systems/the-legend-of-bratmobile/" rel="noopener noreferrer"&gt;bratmobile&lt;/a&gt;' and output the remainder of the line. then we move the pointer twelve bytes from the end of the file and output.&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;$fp&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="s2"&gt;"/tmp/bands"&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="c1"&gt;// move pointer four bytes from start of file and output line&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;$fp&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;print&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;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// move pointer twelve bytes from end of file and output line&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;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;12&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="k"&gt;print&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;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the results are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mobile
linton kwesi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  doing something useful with &lt;code&gt;fseek&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;manipulating our file pointer like this is a great trick and it will make us very popular at parties, but it's not particularly useful. let's change that.&lt;/p&gt;

&lt;p&gt;we're going to build a function that moves our file pointer to &lt;em&gt;n&lt;/em&gt; lines from the end of the file so that we can do something like output the last five or ten (or whatever) lines. if you've ever used the linux &lt;code&gt;tail&lt;/code&gt; command, you get the idea.&lt;/p&gt;

&lt;p&gt;our approach is going to be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loop backwards from the end of the file one byte at a time, using &lt;code&gt;fseek&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;test each byte to see if it's the line end character &lt;code&gt;PHP_EOL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;count our line ends&lt;/li&gt;
&lt;li&gt;when we get to &lt;em&gt;n&lt;/em&gt; lines, return the file pointer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;let's look at the implementation:&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="cd"&gt;/**
 * Updates a file pointer $fp to be n bytes from the end of the file
 * so that outputting to eof prints the last $lineCount lines.
 *
 * @param mixed $fp
 * @param int $lineCount How many lines back from the end of file
 * @return mixed The file pointer resource
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;windToTailStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$lineCount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$position&lt;/span&gt; &lt;span class="o"&gt;=&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="nv"&gt;$lineCounter&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="k"&gt;do&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;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$position&lt;/span&gt;&lt;span class="o"&gt;--&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;fgetc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$lineCounter&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;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;$lineCounter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$lineCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$fp&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;the heart of the action here is the &lt;code&gt;do while&lt;/code&gt; loop. this loop keeps running until the number of &lt;code&gt;PHP_EOL&lt;/code&gt; characters we have looped over matches &lt;code&gt;$lineCount&lt;/code&gt;, the number of lines we want our file pointer to be from the end.&lt;/p&gt;

&lt;p&gt;inside the loop, we move our our pointer one byte back from the end of the file with the line:&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;fseek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$position&lt;/span&gt;&lt;span class="o"&gt;--&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as we went over above, the &lt;code&gt;SEEK_END&lt;/code&gt; argument tells &lt;code&gt;fseek&lt;/code&gt; to start from the end of the file, and the &lt;code&gt;$position&lt;/code&gt; argument is the number of bytes to add to the starting point. since we're moving backwards here, that number is negative.&lt;/p&gt;

&lt;p&gt;we test to see if the byte we're inspecting is a line end character by using &lt;code&gt;getc&lt;/code&gt; to get one char. if that byte is &lt;code&gt;PHP_EOL&lt;/code&gt;, the line end, we increment &lt;code&gt;$lineCounter&lt;/code&gt;. when &lt;code&gt;$lineCounter&lt;/code&gt; equals the number of lines we want, we stop the loop and return the file pointer, which is now at the beginning of the line that is &lt;code&gt;$lineCount&lt;/code&gt; from the end of the file.&lt;/p&gt;

&lt;p&gt;once we have our &lt;code&gt;windToTailStart&lt;/code&gt; function written, we can implement &lt;code&gt;tail&lt;/code&gt; fairly easily:&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;// open the file&lt;/span&gt;
&lt;span class="nv"&gt;$fp&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="s2"&gt;"/path/to/bands"&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="c1"&gt;// move file pointer to 3 lines from the end&lt;/span&gt;
&lt;span class="nv"&gt;$fp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;windToTailStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&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;// output everything from $fp to the end of the file&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;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;print&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;$fp&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;when we run this, we get the last three lines of our 'bands' file. it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;idles
die kreuzen
johnson, linton kwesi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  inspecting our pointer with &lt;code&gt;ftell&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;setting our file pointer is great, but we probably also want to get it; find out exactly &lt;em&gt;where&lt;/em&gt; our pointer is. we can do that with &lt;code&gt;ftell&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ftell&lt;/code&gt; takes one argument, our pointer, and returns the number of bytes the pointer is away from file start. let's look:&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;$fp&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="s2"&gt;"/path/to/bands"&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="k"&gt;print&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;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 0&lt;/span&gt;

&lt;span class="c1"&gt;// move pointer ahead 10 bytes&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;$fp&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;SEEK_SET&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;print&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;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here we opened our file and immediately called &lt;code&gt;ftell&lt;/code&gt; on our pointer. the result, not surprisingly, is 0. the beginning of the file.&lt;/p&gt;

&lt;p&gt;if we wind the pointer ahead 10 bytes with &lt;code&gt;fseek&lt;/code&gt; and call &lt;code&gt;ftell&lt;/code&gt; again, the number we get is 10.  exactly what we expect.&lt;/p&gt;

&lt;p&gt;let's look at a slightly more practical 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;$fp&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="s2"&gt;"/path/to/bands"&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="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;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="nb"&gt;fgetc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&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="nb"&gt;ftell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;break&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we've written a short block of code that outputs the first 12 bytes of our file and then exits. the implementation is basically looping over the file, outputting each character and then running &lt;code&gt;ftell&lt;/code&gt;. when &lt;code&gt;ftell&lt;/code&gt; returns 12, we know we're done and call &lt;code&gt;break&lt;/code&gt;, exiting the loop. the output looks like this:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;that's ten bytes for 'bratmobile', one for &lt;code&gt;PHP_EOL&lt;/code&gt;, and 'c', the first letter in john coltrane's name.&lt;/p&gt;

&lt;h3&gt;
  
  
  a short note about file bounds
&lt;/h3&gt;

&lt;p&gt;back in our &lt;code&gt;windToTailStart&lt;/code&gt; function, we moved our file pointer up &lt;em&gt;n&lt;/em&gt; lines from the file end. this works great if the file we're winding over has &lt;em&gt;n&lt;/em&gt; or more lines in it, but if it does not then we end up pushing our file pointer past the start of the file. the results when we do this are not great.&lt;/p&gt;

&lt;p&gt;we can protect against that by using &lt;code&gt;ftell&lt;/code&gt; to check if we are at the start of the file. let's look:&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;function&lt;/span&gt; &lt;span class="n"&gt;windToTailStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$lineCount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;mixed&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$position&lt;/span&gt; &lt;span class="o"&gt;=&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="nv"&gt;$lineCounter&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="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// guard against running past start of file&lt;/span&gt;
        &lt;span class="k"&gt;if&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;$fp&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;;&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;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$position&lt;/span&gt;&lt;span class="o"&gt;--&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;fgetc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$fp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$lineCounter&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;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;$lineCounter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$lineCount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$fp&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;here, we've added a small &lt;code&gt;if&lt;/code&gt; statement to return our file pointer if we've reached 0, the beginning of the file. problem solved and disaster averted.&lt;/p&gt;

&lt;h2&gt;
  
  
  putting it all together: writing &lt;code&gt;tail -f&lt;/code&gt; in php
&lt;/h2&gt;

&lt;p&gt;now that we have a grip on &lt;code&gt;fseek&lt;/code&gt; and &lt;code&gt;ftell&lt;/code&gt;, let's leverage them to do a moderately powerful file-reading task: implementing linux's &lt;a href="https://www.putorius.net/linux-tail-command.html" rel="noopener noreferrer"&gt;&lt;code&gt;tail -f&lt;/code&gt;&lt;/a&gt; functionality in php.&lt;/p&gt;

&lt;p&gt;for those not familiar with &lt;code&gt;tail -f&lt;/code&gt;, it allows us to 'watch' a file. we run &lt;code&gt;tail -f /path/to/file&lt;/code&gt; and the command waits for new data to be added to that file and then immediately outputs it. it's great for watching things like logs in real time.&lt;/p&gt;

&lt;p&gt;to write this, we're going to use &lt;code&gt;ftell&lt;/code&gt; to find the end of our file and 'bookmark' it. then, in a loop, we're going to continuously check the end of the file with &lt;code&gt;fseek&lt;/code&gt;. if our new end-of-file is different than our 'bookmark', we'll know new data has been added to the file. we can then output the new lines.&lt;/p&gt;

&lt;p&gt;let's look at the implementation:&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;function&lt;/span&gt; &lt;span class="n"&gt;tailf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// open file at the end&lt;/span&gt;
    &lt;span class="nv"&gt;$fp&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="nv"&gt;$file&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;$fp&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="no"&gt;SEEK_END&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// infinite loop to keep checking file&lt;/span&gt;
    &lt;span class="k"&gt;while&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="c1"&gt;// bookmark current end of file&lt;/span&gt;
        &lt;span class="nv"&gt;$tell&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;$fp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// wait for 0.1 seconds&lt;/span&gt;
        &lt;span class="nb"&gt;usleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// move pointer to end of file&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;$fp&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="no"&gt;SEEK_END&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// if the end of file is not the same as the bookmar there is new data&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$tell&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;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// move pointer to bookmark&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;$fp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$tell&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="c1"&gt;// output everything from bookmark to end of file&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;$fp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;print&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;$fp&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="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;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;as we see here, we can monitor if new data has been added to our file by getting the pointer for the file end with &lt;code&gt;ftell&lt;/code&gt;, waiting for 0.1 seconds then seeing if our new file end is different than the previous one. if we have new data, we move the file pointer back to the old end  of file and output from there.&lt;/p&gt;

&lt;p&gt;for those who are interested, there is a gist of the &lt;a href="https://gist.github.com/gbhorwood/865c3e85fbff170ce659963d7ff72f6c" rel="noopener noreferrer"&gt;full implementation of tail -f in php&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;a more &lt;a href="https://github.com/gbhorwood/Cloverlogger/blob/main/bin/clover-tail" rel="noopener noreferrer"&gt;comprehensive example of &lt;code&gt;tail -f&lt;/code&gt; functionality&lt;/a&gt; is available in my &lt;a href="https://github.com/gbhorwood/Cloverlogger" rel="noopener noreferrer"&gt;internal-use logger package&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  wrapping up
&lt;/h2&gt;

&lt;p&gt;convenience functions like &lt;code&gt;file_get_contents&lt;/code&gt; are great; they're powerful and easy-to-use. but, ultimately, they exist for &lt;em&gt;convenience&lt;/em&gt;. the underpinning of every file operation in php is the file pointer, and once we understand how to track and manipulate that we have far greater control over our file handling.&lt;/p&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>linux: looking under the hood of neofetch</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Mon, 07 Jul 2025 15:21:22 +0000</pubDate>
      <link>https://dev.to/gbhorwood/linux-looking-under-the-hood-of-neofetch-3gnk</link>
      <guid>https://dev.to/gbhorwood/linux-looking-under-the-hood-of-neofetch-3gnk</guid>
      <description>&lt;p&gt;a couple of months ago, some semi-famous video game vlogger i've never heard of decided to get up on the linux soap box and rally his followers to ditch windows. &lt;/p&gt;

&lt;p&gt;this was one of those viral-level, meme-type event things, and the result was a literal flood of screenshots of 'riced' desktops. and while each of those arch-newbie screeners was different than the last, they all had one common element: a terminal running &lt;code&gt;neofetch&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;this article is not going to be about &lt;code&gt;neofetch&lt;/code&gt; specifically, but rather about the concepts and commands under &lt;code&gt;neofetch&lt;/code&gt;'s  hood. we're not going to rebuild this command, or go over it line-by-line. instead, we're going to look at the basic tools we can use to inspect our linux system.  by the end, we should be able to learn everything we want to about our linux-like system using standard, built-in commands and files. no &lt;code&gt;neofetch&lt;/code&gt; required.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F07%2Fneofetch_meme.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F07%2Fneofetch_meme.webp" title="the 'it's always been' meme" alt="the 'it's always been' meme" width="800" height="450"&gt;&lt;/a&gt;neofetch has always been a stack of linux commands, files and variables in a trenchcoat&lt;/p&gt;

&lt;h2&gt;
  
  
  inspecting our os with &lt;code&gt;uname&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;uname&lt;/code&gt; stands for 'unix name' and, not surprisingly, it's job is to give us the  name of our operating system. but it also does a lot more. let's start with the name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt;
GNU/Linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;-o&lt;/code&gt; switch here tells &lt;code&gt;uname&lt;/code&gt; to tell us about the 'operating system'. the response is simple, but accurate: GNU/Linux.&lt;/p&gt;

&lt;p&gt;if we want to get some basic information on our system's 'processor', we use the &lt;code&gt;-p&lt;/code&gt; switch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
x86_64
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;getting the exact kernel version we're running is accomplished with &lt;code&gt;-r&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt;
6.12.10-76061203-generic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  files with more useful os information
&lt;/h2&gt;

&lt;p&gt;the data from &lt;code&gt;uname&lt;/code&gt; is correct, but not particularly useful. if we want something more detailed,  there are some files we can look at, though.&lt;/p&gt;

&lt;h3&gt;
  
  
  the &lt;code&gt;os-release&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;the &lt;code&gt;/etc/os-release&lt;/code&gt; file contains a lot of data about our system. this file is part of the &lt;code&gt;systemd&lt;/code&gt; standard and has &lt;a href="https://0pointer.de/blog/projects/os-release" rel="noopener noreferrer"&gt;been around since 2012&lt;/a&gt;, so if you're running a moderately modern linux-like operating system with &lt;code&gt;systemd&lt;/code&gt;, it will be there.&lt;/p&gt;

&lt;p&gt;let's look at an example of its contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME="Pop!_OS"
VERSION="22.04 LTS"
ID=pop
ID_LIKE="ubuntu debian"
PRETTY_NAME="Pop!_OS 22.04 LTS"
VERSION_ID="22.04"
HOME_URL="https://pop.system76.com"
SUPPORT_URL="https://support.system76.com"
BUG_REPORT_URL="https://github.com/pop-os/pop/issues"
PRIVACY_POLICY_URL="https://system76.com/privacy"
VERSION_CODENAME=jammy
UBUNTU_CODENAME=jammy
LOGO=distributor-logo-pop-os
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;from this file, we can see that the user here is running pop_os 22.04. that's some useful information!&lt;/p&gt;

&lt;h3&gt;
  
  
  the &lt;code&gt;lsb-release&lt;/code&gt; file
&lt;/h3&gt;

&lt;p&gt;an alternative to the &lt;code&gt;os-release&lt;/code&gt; file is &lt;code&gt;/etc/lsb-release&lt;/code&gt;. the 'lsb' here stands for 'linux standard base' which was an attempt made many years ago to unify  and standardize all sorts of things across the various linux distros. stuff like the filesystem hierarchy, library versions, run levels and the like.&lt;/p&gt;

&lt;p&gt;like most attempts at unification and standardization, it fell apart and lsb hasn't been a going concern for over ten years now. however, there's still a lot of lsb stuff lingering around in modern distros, and it's useful.&lt;/p&gt;

&lt;p&gt;let's look at some sample output of &lt;code&gt;/etc/lsb-release&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DISTRIB_ID=Pop
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Pop!_OS 22.04 LTS"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;exactly the sort of data we want.&lt;/p&gt;

&lt;p&gt;our system may also include the binary &lt;code&gt;lsb_release&lt;/code&gt; (mileage may vary). if it does, we can run it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lsb_release -a
No LSB modules are available.
Distributor ID: Pop
Description:    Pop!_OS 22.04 LTS
Release:    22.04
Codename:   jammy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F07%2Fneofetch_img_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F07%2Fneofetch_img_1.png" title="the '14 competing standards' xkcd cartooon" alt="the '14 competing standards' xkcd cartoon" width="500" height="283"&gt;&lt;/a&gt;‘linux standard base’ was abandoned in 2015&lt;/p&gt;

&lt;h2&gt;
  
  
  learning about memory with &lt;code&gt;free&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;memory is complicated stuff. the traditional way to find out about free and used memory was to look in the &lt;code&gt;/proc/meminfo&lt;/code&gt; file, but it's an absolute mess in there.&lt;/p&gt;

&lt;p&gt;if you want to stick with tradition, you can use &lt;code&gt;grep&lt;/code&gt; to get just the 'good bits' from &lt;code&gt;/proc/meminfo&lt;/code&gt;. something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat /proc/meminfo | grep -i "MemTotal\|MemFree"
MemTotal:       45141040 kB
MemFree:         6225932 kB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the alternative is to use the &lt;code&gt;free&lt;/code&gt; command, which reports on 'free' memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ free -h
               total        used        free      shared  buff/cache   available
Mem:            43Gi        11Gi       6.0Gi       396Mi        25Gi        27Gi
Swap:           19Gi       6.5Gi        13Gi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here we have passed the &lt;code&gt;-h&lt;/code&gt; switch, which stands for 'human readable', ie megabytes and gigabytes instead of just raw kb.&lt;/p&gt;

&lt;p&gt;there are two rows in the standard output: 'Mem' for physical, installed memory, and 'Swap' for swap space. if you don't have a 'Swap' row, you should probably fix that. we'll focus on physical memory.&lt;/p&gt;

&lt;p&gt;the two columns we're probably the most interested in are 'total' (the total amount of installed memory), and 'free'.&lt;/p&gt;

&lt;p&gt;'free' is calculated by adding the 'used' memory and the 'buffer/cache' memory and subtracting it from the total. this means that 'used' plus 'buffer/cache' plus 'free' adds up to our total memory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ free | grep 'Mem' | awk '{print $2 " = "$3" + "$4" + "$6}'
45141040 = 12235120 + 6080968 + 26824952
$ expr 12235120 + 6080968 + 26824952
45141040
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  inspecting our hardware with &lt;code&gt;lshw&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;the command &lt;code&gt;lshw&lt;/code&gt;'s long name is 'hardware listener', but everybody just says 'list hardware' instead because that's what it does: it lists all our machine's hardware.&lt;/p&gt;

&lt;p&gt;let's run it with the &lt;code&gt;-short&lt;/code&gt; argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lshw &lt;span class="nt"&gt;-short&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the output of this is a short description of every piece of hardware in our box. everything from our motherboard to our mouse to our cd burner, if we're old enough to still have such a thing. if we want to, we can apply a little &lt;code&gt;grep&lt;/code&gt; to filter for the information we need. for instance, to get just our processor and cdrom drive we could do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;lshw &lt;span class="nt"&gt;-short&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s2"&gt;"processor&lt;/span&gt;&lt;span class="se"&gt;\|&lt;/span&gt;&lt;span class="s2"&gt;cdrom"&lt;/span&gt;
/0/4                               processor      Intel&lt;span class="o"&gt;(&lt;/span&gt;R&lt;span class="o"&gt;)&lt;/span&gt; Core&lt;span class="o"&gt;(&lt;/span&gt;TM&lt;span class="o"&gt;)&lt;/span&gt; i7-4820K CPU @ 3.70GHz
/0/100/1d/1/1/3/0.0.0  /dev/cdrom  disk           DVDRAM GP65NB60
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we run &lt;code&gt;lshw -short&lt;/code&gt; and look a the top line of the output, we see that the columns have these headings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;H/W path        Device      Class          Description
======================================================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we  want to do a more detailed exploration of our hardware, we will use the value under 'Class'. so, for instance, for our cpu, the class is 'processor'.&lt;/p&gt;

&lt;p&gt;to inspect the hardware class, we use the &lt;code&gt;-C&lt;/code&gt; switch and the class name like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lshw -C processor
  *-cpu                     
       product: Intel(R) Core(TM) i7-4820K CPU @ 3.70GHz
       vendor: Intel Corp.
       physical id: 1
       bus info: cpu@0
       version: 6.62.4
       size: 1200MHz
       capacity: 3900MHz
       width: 64 bits
       capabilities: fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp x86-64 constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm cpuid_fault epb pti ssbd ibrs ibpb stibp tpr_shadow flexpriority ept vpid fsgsbase smep erms xsaveopt dtherm ida arat pln pts vnmi md_clear flush_l1d cpufreq
       configuration: microcode=1070
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;that's a lot of information about our cpu.&lt;/p&gt;

&lt;p&gt;we can do this with any class that &lt;code&gt;lshw&lt;/code&gt; lists. for instance, if we want info on our video card, we can query the class 'display' (with a little bit of &lt;code&gt;grep&lt;/code&gt; to cut out the noise)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lshw -C display | grep "product\|vendor"
       product: GP104 [GeForce GTX 1070]
       vendor: NVIDIA Corporation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  getting info about hard disks with &lt;code&gt;df&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;we can get some basic, but useful information about our hard drives using the &lt;code&gt;df&lt;/code&gt; command. 'df' here stands for 'disk free' because most people use it to find out how much free space they have, but it does more than that. let's look at some truncated sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ df -h
Filesystem                   Size  Used Avail Use% Mounted on
tmpfs                        4.4G  2.5M  4.4G   1% /run
/dev/sda1                    1.8T  791G  946G  46% /
192.168.1.70:/volume1/music  3.6T  457G  3.2T  13% /mnt/music
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in this example we've run &lt;code&gt;df&lt;/code&gt; with the &lt;code&gt;-h&lt;/code&gt; switch to use 'human readable' units.&lt;/p&gt;

&lt;p&gt;from this we can see that there is one 1.8 terabyte hard drive mounted on the root partition and it's 46% full. there's also a network drive mounted on &lt;code&gt;mnt/music&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;for most purposes, this level of detail is sufficient. however, if we want a bit more, we can use &lt;code&gt;lsblk&lt;/code&gt; to list all our 'block devices'.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;the output of this command will show drives (including things like our cdrom) and their partitions. it will also show every single snap we have installed as a device.&lt;/p&gt;

&lt;p&gt;if we want to know the filesystems of our drives, we can pass the &lt;code&gt;--fs&lt;/code&gt; switch&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  finding our screen resolution
&lt;/h2&gt;

&lt;p&gt;if we want to get our current screen resolutions and all possible supported resolutions, we can use &lt;code&gt;xrandr&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;as that 'x' at the beginning of the name implies, &lt;code&gt;xrandr&lt;/code&gt; is an xwindow tool. if we are using wayland, instead, we'll need to find an alternate solution such as &lt;a href="https://man.archlinux.org/man/extra/wlr-randr/wlr-randr.1.en" rel="noopener noreferrer"&gt;wlr-randr&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;running &lt;code&gt;xrander&lt;/code&gt; gives us the following output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ xrandr
Screen 0: minimum 8 x 8, current 2560 x 1440, maximum 32767 x 32767
DVI-D-0 disconnected (normal left inverted right x axis y axis)
HDMI-0 connected primary 2560x1440+0+0 (normal left inverted right x axis y axis) 697mm x 392mm
   3840x2160     60.00 +  59.94    50.00    30.00    29.97    25.00    23.98  
   2560x1440     59.95* 
   1920x1080     60.00    59.94    50.00    29.97    23.98  
   1680x1050     59.95  
   1600x900      60.00  
   1440x900      59.89  
   1280x1024     75.02    60.02  
   1280x800      59.81  
   1280x720      60.00    59.94    50.00  
   1152x864      75.00  
   1024x768      75.03    70.07    60.00  
   800x600       75.00    72.19    60.32    56.25  
   720x576       50.00  
   720x480       59.94  
   640x480       75.00    72.81    59.94 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here we can see that our current resolution is 2560x1440. there's also a lengthy output of supported resolutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  deducing our desktop environment
&lt;/h2&gt;

&lt;p&gt;lots of people use graphical desktop environments in linux, so finding out what's being run is probably important. we can get that information from the &lt;code&gt;$XDG_CURRENT_DESKTOP&lt;/code&gt; environmental variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$XDG_CURRENT_DESKTOP&lt;/span&gt;
pop:GNOME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;once we know what desktop environment is being run, we can call it directly to get the version number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gnome-shell &lt;span class="nt"&gt;--version&lt;/span&gt;
GNOME Shell 42.9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for kde, we would use:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;other desktop environments will have their own commands that accept the &lt;code&gt;--version&lt;/code&gt; argument.&lt;/p&gt;

&lt;h2&gt;
  
  
  getting the current shell
&lt;/h2&gt;

&lt;p&gt;the default shell of the current user is stored in the &lt;code&gt;$SHELL&lt;/code&gt; environmental variable. we can access it like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$SHELL&lt;/span&gt;
/usr/bin/fish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we want to retrieve the version of the shell, we can use &lt;code&gt;$SHELL&lt;/code&gt; as an executable and pass the &lt;code&gt;--version&lt;/code&gt; argument&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ $SHELL&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt;
fish, version 3.7.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we need to get the shell for a user other than the one we are logged in as, we can always retrieve it from the &lt;code&gt;/etc/passwd&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /etc/passwd | &lt;span class="nb"&gt;grep &lt;/span&gt;ghorwood
ghorwood:x:1000:1000:grant horwood:/home/ghorwood:/usr/bin/fish
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  finding up time with &lt;code&gt;uptime&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;it's a ridiculous point of pride for some linux users that they never have to reboot their machines.  we can find out how long our box has been up and running with the &lt;code&gt;uptime&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uptime&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
up 2 weeks, 4 days, 18 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;eighteen days isn't bad.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;neofetch is a useful and fun tool, but learning how it finds data about our machine gives us a better understanding of our operating system.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2025/07/03/linux-looking-under-the-hood-of-neofetch/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>linux</category>
      <category>bash</category>
    </item>
    <item>
      <title>php: writing command-line applications with macrame. pt 2</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Wed, 19 Feb 2025 14:49:20 +0000</pubDate>
      <link>https://dev.to/gbhorwood/php-writing-command-line-applications-with-macrame-pt-2-5ga4</link>
      <guid>https://dev.to/gbhorwood/php-writing-command-line-applications-with-macrame-pt-2-5ga4</guid>
      <description>&lt;p&gt;in this series, we've been looking at how to write command line applications in php using &lt;a href="https://macrame.fruitbat.studio/" rel="noopener noreferrer"&gt;macrame&lt;/a&gt;. in the &lt;a href="https://dev.to/gbhorwood/php-writing-command-line-applications-with-macrame-3nk4"&gt;previous installment&lt;/a&gt;, we covered the basic structure of macrame scripts, getting user input both as text and from interactive menus, parsing command-line arguments, and styling our output text. &lt;/p&gt;

&lt;p&gt;the sample application we're building is a script that fetches a list of a mastodon user's followers and outputs the data in a nicely-formatted table. it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F01%2Fmacrameexample.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F01%2Fmacrameexample.gif" title="animated gif of the sample project running" alt="animated gif of the sample project running" width="500" height="318"&gt;&lt;/a&gt;the sample script in action&lt;/p&gt;

&lt;h2&gt;
  
  
  the flyover
&lt;/h2&gt;

&lt;p&gt;in this installment we'll be looking at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;outputting array data as a nicely-formatted ascii table&lt;/li&gt;
&lt;li&gt;running a function in the background while we show users an animated spinner&lt;/li&gt;
&lt;li&gt;writing safely to files&lt;/li&gt;
&lt;li&gt;paging long output&lt;/li&gt;
&lt;li&gt;basic notice level output&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  before we start, the (skipable) mastodon stuff
&lt;/h2&gt;

&lt;p&gt;our example script scrapes a list of one user's followers from a mastodon instance. this, of course, requires us to make some calls to the mastodon api. &lt;/p&gt;

&lt;p&gt;there are the two functions our script has that do this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;mastodonUserId()&lt;/code&gt;: accepts a mastodon user's username and calls the instance to get the user id&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mastodonFollowers()&lt;/code&gt;: accepts the user's id and returns a list of follower data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we won't be covering this functionality in this post. the full script is available as &lt;a href="https://gist.github.com/gbhorwood/729e7e4975e40474f9342f1e70467106" rel="noopener noreferrer"&gt;a github gist&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  outputting arrays in a nice table format
&lt;/h2&gt;

&lt;p&gt;macrame allows us to output array data in formatted ascii tables. if you've ever selected stuff from mysql on the command line, it looks like that.&lt;/p&gt;

&lt;p&gt;let's look at the &lt;code&gt;tableFollowers&lt;/code&gt; function that accepts an array of follower data from mastodon and returns it as a table:&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;function&lt;/span&gt; &lt;span class="n"&gt;tableFollowers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$followers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the 'acct' and 'display_name' fields from each element for use as table rows&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&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;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;acct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$followers&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Account'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Display Name'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// build the ascii table and return&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headers&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&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;we can gloss over the &lt;code&gt;array_map&lt;/code&gt; call here; all it does is extract the account and display names of the followers from the array of data from mastodon. the important line here is:&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;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headers&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we used macrame's &lt;code&gt;table()&lt;/code&gt; method to create a macrame table object. the &lt;code&gt;table()&lt;/code&gt; method takes two arguments: an array of column headers, and an array of arrays that represent the rows of our table.&lt;/p&gt;

&lt;p&gt;once we've made our table object, we use it to create and return our table by calling the &lt;code&gt;get()&lt;/code&gt; method. if we instead wanted to just write our table to the screen, we could call &lt;code&gt;write()&lt;/code&gt; instead.&lt;/p&gt;

&lt;p&gt;if we were creating this table using hardcoded data, it might look 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="nv"&gt;$headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Account'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Display Name'&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="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'ghorwood'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'grant horwood'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'thephp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'The PHP Foundation'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="nv"&gt;$table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headers&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the value in our &lt;code&gt;$table&lt;/code&gt; variable would be a string that looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----------+--------------------+
| Account  | Display Name       |
+----------+--------------------+
| ghorwood | grant horwood      |
| thephp   | The PHP Foundation |
+----------+--------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a nice, clean table.&lt;/p&gt;

&lt;p&gt;macrame also allows us to set the alignment of columns and change the border style if we so wish. for instance, if we want to right-align our &lt;code&gt;Account&lt;/code&gt; column and apply a solid border, we could write:&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;$table&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headers&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;right&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;solid&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we added a call to the &lt;code&gt;right&lt;/code&gt; method to right-align the first column (column zero). we also applied the &lt;code&gt;solid&lt;/code&gt; method to set the border style to a solid line. the output is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────┬────────────────────┐
│  Account │ Display Name       │
├──────────┼────────────────────┤
│ ghorwood │ grant horwood      │
│   thephp │ The PHP Foundation │
└──────────┴────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;macrame's table feature is based on the the &lt;a href="https://github.com/gbhorwood/Tabletown" rel="noopener noreferrer"&gt;tabletown&lt;/a&gt; library and is designed to handle multibyte content, output rows with line breaks as multi-row cells, and deal with tabs as proper tab stops.&lt;/p&gt;

&lt;p&gt;a full overview of the table feature is available &lt;a href="https://macrame.fruitbat.studio/Manual/Table_Output.html" rel="noopener noreferrer"&gt;on the table documentation page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  running code in the background and showing an animated spinner.
&lt;/h2&gt;

&lt;p&gt;it's nice to show our users an animated spinner while we do time-intensive tasks in the background so they know that the script hasn't hung and is actually doing things. doing this in php, however, is not very straightforward since it requires us to run two different functions at the same time -- one to do the work, the other to show the spinner -- and php is single threaded. macrame's &lt;code&gt;spinner&lt;/code&gt; feature allows us to do this by &lt;a href="https://gbh.fruitbat.io/2024/07/22/php-concurrency-with-processes-pt-1-using-pcntl_fork-for-fun-and-performance/" rel="noopener noreferrer"&gt;forking&lt;/a&gt; a separate process.&lt;/p&gt;

&lt;p&gt;let's look at how we could show an animated spinner to our users while we fetch and build our table of followers. this code lives in &lt;a href="https://gist.github.com/gbhorwood/729e7e4975e40474f9342f1e70467106#file-macrame_example-php-L64" rel="noopener noreferrer"&gt;the main body of our script&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;$getFollowersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// call mastodon to get the user id for the username&lt;/span&gt;
    &lt;span class="nv"&gt;$userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mastodonUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// call mastodon to get the followers for the user id&lt;/span&gt;
    &lt;span class="nv"&gt;$followers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mastodonFollowers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// format the array of followers into a table&lt;/span&gt;
    &lt;span class="nv"&gt;$followersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tableFollowers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$followers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$followersTable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Run the $getFollowersTable() function in the background and display an animated
 * spinner to the user while it is running.
 */&lt;/span&gt;
&lt;span class="nv"&gt;$followersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cycle 2'&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;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fetching '&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;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fast'&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$getFollowersTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the spinner features allows us to show an animated spinner to our user while a function is run in the background, so the first step is to construct that function.&lt;/p&gt;

&lt;p&gt;here, we have created an anonymous function and assigned it to the variable &lt;code&gt;$getFollowersTable&lt;/code&gt;. in the body of the function, we're calling mastodon to get a list of followers and formatting the response as a table. the function accepts three arguments and returns a string.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;run&lt;/code&gt; method on &lt;code&gt;spinner&lt;/code&gt; is where we actually run the function. &lt;code&gt;run&lt;/code&gt; accepts two arguments. the first is the anonymous function, and the second is an array containing the values we want to pass as arguments to that function. &lt;/p&gt;

&lt;p&gt;calling &lt;code&gt;run&lt;/code&gt; will automatically execute our anonymous function in the background while showing the animated spinner to the user. when the anonymous function terminates, &lt;code&gt;run&lt;/code&gt; will return its return value. in our example, the string containing the table of followers will be set in &lt;code&gt;$followersTable&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;spinner&lt;/code&gt; feature allows a fair amount of customization. to change the type of animation we display, we can pass the name of the animation to &lt;code&gt;spinner&lt;/code&gt; as an argument. there are &lt;a href="https://macrame.fruitbat.studio/Manual/Spinners_and_Tasks.html#choosing-a-spinner-animation" rel="noopener noreferrer"&gt;about forty animations to choose from&lt;/a&gt;. if no argument is passed to &lt;code&gt;spinner&lt;/code&gt;, the default animation is used.&lt;/p&gt;

&lt;p&gt;we can also add a string that will preceed the animation by adding an optional call to &lt;code&gt;prompt&lt;/code&gt;. in the example above, we are outputting the word 'fetching' in front of the spinner.&lt;/p&gt;

&lt;p&gt;lastly, we can adjust the speed of the animation with the optional &lt;code&gt;speed&lt;/code&gt; method. we can pass one of four strings to &lt;code&gt;speed&lt;/code&gt;: 'slow', 'medium', 'fast', or 'very fast'.&lt;/p&gt;

&lt;p&gt;there is a full overview of the spinner feature in &lt;a href="https://macrame.fruitbat.studio/Manual/Spinners_and_Tasks.html" rel="noopener noreferrer"&gt;the manual&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  writing to files safely
&lt;/h2&gt;

&lt;p&gt;when we write php for the web, we don't normally need to put a lot of thought into file access. we control the server, after all, so we're confident about things like the directory structure, permissions, how much disk space we have and so on. that's not the case for command line scripts that are going to be run on some random user's computer.&lt;/p&gt;

&lt;p&gt;macrame's &lt;code&gt;file&lt;/code&gt; feature has error checking for common read and write problems built in. let's look at how we would write a feature for our script that allows users to output the list of mastodon followers to a file:&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;// get optional outfile file path argument&lt;/span&gt;
&lt;span class="nv"&gt;$outfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'outfile'&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;first&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="nv"&gt;$outfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$outfile&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;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$followersTable&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;here, we're accepting a path to an output file as a command line arg. we covered how to do this in &lt;a href="https://gbh.fruitbat.io/2025/01/29/php-writing-command-line-applications-with-macrame/" rel="noopener noreferrer"&gt;part one of this series&lt;/a&gt;. we're then taking that file path and writing our table of followers to it.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;file&lt;/code&gt; method here takes as an argument the path to the file we want to write to. the &lt;code&gt;write&lt;/code&gt; method's argument is the contents we wish to write. if we wanted to append to the file, we would use the &lt;code&gt;append&lt;/code&gt; method instead.&lt;/p&gt;

&lt;p&gt;when we call &lt;code&gt;write&lt;/code&gt; or &lt;code&gt;append&lt;/code&gt;, macrame checks first to see if we can write to the file. the checks performed are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;if the path is valid&lt;/li&gt;
&lt;li&gt;permissions on the file if the file exists&lt;/li&gt;
&lt;li&gt;permissions on the directory if the file is to be created&lt;/li&gt;
&lt;li&gt;if there is sufficient disk space on the target partition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if any of these checks fail, error messages will be printed to the screen and the method will return &lt;code&gt;false&lt;/code&gt;. if everything goes well, &lt;code&gt;true&lt;/code&gt; is returned and our content is written to file.&lt;/p&gt;

&lt;p&gt;we can also perform the checks manually if we want to override the default behaviour:&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;// bool. Test if file can be written to&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/path/to/file'&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;writable&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// error. cannot write due to permissions&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/path/to/file'&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;enoughSpace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'content'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// error. cannot write due to lack of disk space&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we're checking the permissions of our target file (or directory if the file does not exist yet), and confirming that we have sufficient space to write '$content'.&lt;/p&gt;

&lt;p&gt;reading files operates similarly to writing them:&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;$contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/path/to/file'&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;read&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if there are any errors on this attempted read, they will be displayed to the user and the method will return &lt;code&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;macrame file paths are automatically expand the &lt;code&gt;~&lt;/code&gt; character to the user's home directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  temporary files
&lt;/h3&gt;

&lt;p&gt;if we want to create temporary files that are automatically deleted when the script terminates, we can apply the &lt;code&gt;deleteOnExit&lt;/code&gt; method:&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/path/to/file'&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;deleteOnExit&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;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'contents'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;doing this flags the file to be removed when our script calls:&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the full list of file features is covered in &lt;a href="https://macrame.fruitbat.studio/Manual/File_Read_and_Write.html" rel="noopener noreferrer"&gt;the manual&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  paging long output
&lt;/h2&gt;

&lt;p&gt;our list of followers might be too long to fit all in one screen. we &lt;em&gt;could&lt;/em&gt; just let the output zip by and let whoever's running our script use the mouse wheel to get to the top, but that's rude.&lt;/p&gt;

&lt;p&gt;macrame allows us to page long output with the &lt;code&gt;page&lt;/code&gt; method. let's apply paging to our table of followers:&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$followersTable&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;page&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we create a text object of our table string using &lt;code&gt;text&lt;/code&gt; and then, instead of calling &lt;code&gt;write&lt;/code&gt; to print it to the screen, we call &lt;code&gt;page&lt;/code&gt; to print it to the screen one page at a time.&lt;/p&gt;

&lt;p&gt;our user's will be able to advance the output one page using the &lt;code&gt;&amp;lt;SPACE&amp;gt;&lt;/code&gt; bar. hitting &lt;code&gt;&amp;lt;RETURN&amp;gt;&lt;/code&gt; will move it forward one line.&lt;/p&gt;

&lt;h2&gt;
  
  
  outputting notices
&lt;/h2&gt;

&lt;p&gt;keeping users informed of how the script is running is a good thing to do. if a tasks is completed successfully, it's nice to output an 'OK' message. if there's an error, we should show them some 'ERROR' text, preferably with some red in there.&lt;/p&gt;

&lt;p&gt;macrame provides a number of convenience methods for &lt;code&gt;text&lt;/code&gt; to allow us to write notices to the screen.&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'things went well'&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;ok&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'things did not go well'&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;error&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the first line here prints our message to the screen with a nice, green &lt;code&gt;OK&lt;/code&gt;. the second one has a red &lt;code&gt;ERROR&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;these convenience methods follow (loosely) the levels outlined in &lt;a href="https://www.rfc-editor.org/rfc/rfc5424" rel="noopener noreferrer"&gt;RFC 5424&lt;/a&gt;. there's a full list of them in &lt;a href="https://macrame.fruitbat.studio/Manual/Styled_Text_Output.html#notice-levels" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  putting it all together
&lt;/h2&gt;

&lt;p&gt;now that we have a grip on building command line interfaces, we can assemble the mastodon follower app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;#!/usr/bin/env php 
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Example Macrame script that fetches mastodon followers.
 *
 * https://macrame.fruitbat.studio/Installation.html
 * https://github.com/gbhorwood/Macrame
 */&lt;/span&gt;

&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Gbhorwood\Macrame\Macrame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Instantiate a Macrame object.
 * The argument is the name of the script as seen by ps(1)
 */&lt;/span&gt;
&lt;span class="nv"&gt;$macrame&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;Macrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Example Macrame Script"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Enforce that the script only runs if executed on the command line
 */&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// ENTRY POINT&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Validate that the host system can run Macrame scripts. Exit on failure
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handle the -v or --version arguments
     */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&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;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'version'&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;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1.0'&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;write&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handle the --instance= argument if present, or poll user for instance
     * with dynamic menu if not.
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'instance'&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;first&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;$instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;menuInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handle the --username= argument if present, or poll user for username
     * with text input if not
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'username'&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;first&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;$username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inputUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Read the value of the --outfile= argument if any
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$outfile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'outfile'&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;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * A function to fetch an ascii table of followers from mastodon for the user
     * defined by $username and $instance.
     * @param  string $username
     * @param  string $instance
     * @param  Macrame $macrame
     * @return string
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$getFollowersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// call mastodon to get the user id for the username&lt;/span&gt;
        &lt;span class="nv"&gt;$userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mastodonUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// call mastodon to get the followers for the user id&lt;/span&gt;
        &lt;span class="nv"&gt;$followers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;mastodonFollowers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// format the array of followers into a table&lt;/span&gt;
        &lt;span class="nv"&gt;$followersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tableFollowers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$followers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$followersTable&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Run the $getFollowersTable() function in the background and display an animated
     * spinner to the user while it is running.
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$followersTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;spinner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'cycle 2'&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;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fetching '&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;speed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'fast'&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;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$getFollowersTable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * If the --outfile= argument was passed, write the followers table to the file
     * otherwise write the followers table to STDOUT, paged to screen height
     */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$outfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$outfile&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;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$followersTable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$followersTable&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;page&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// exit cleanly&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Display a dynamic menu of instances to the user, return
 * the selected text.
 *
 * @param  Macrame $macrame
 * @return string
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;menuInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Select your instance"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// list of options in the menu&lt;/span&gt;
    &lt;span class="nv"&gt;$instances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'phpc.social'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'mastodon.social'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'mstdn.ca'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// display the menu, return the selected text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;menu&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;erase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// erase the menu after selection&lt;/span&gt;
                   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$instances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Display a text input to the user, return the input text.
 *
 * @param  string $instance
 * @param  Macrame $macrame
 * @return string
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;inputUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the prompt for the text input, with bold styling using tags&lt;/span&gt;
    &lt;span class="nv"&gt;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;!BOLD!&amp;gt;Username&amp;lt;!CLOSE!&amp;gt; (for &lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;): "&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cm"&gt;/* alternate method to apply bold styling:
    $prompt = $macrame-&amp;gt;text('Username ')
                      -&amp;gt;style('bold')
                      -&amp;gt;get()."(for $instance): ";
     */&lt;/span&gt;

    &lt;span class="c1"&gt;// read one line of user input, return the text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Convert the followers data returned by the mastodon instance into an ascii
 * table and return.
 *
 * @param  array $followers The array returned by mastodonFollowers()
 * @param  Macrame $macrame
 * @return string
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;tableFollowers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;array&lt;/span&gt; &lt;span class="nv"&gt;$followers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// extract the 'acct' and 'display_name' fields from each element for use as table rows&lt;/span&gt;
    &lt;span class="nv"&gt;$data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&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;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;acct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;display_name&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;$followers&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'Account'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Display Name'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// build the ascii table and return&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$headers&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="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Fetch the id of the user $username from the mastodon instance $instance
 *
 * @param  string $username
 * @param  string $instance
 * @param  Macrame $macrame
 * @return int
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;mastodonUserId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/accounts/lookup?acct=&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// make the api call and handle errors&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// display error text and exit the script&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"User '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' not found on '&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;'"&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;error&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&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="cd"&gt;/**
 * Fetch the array of the followers for the user $userId from the mastodon instance $instance
 *
 * @param  string $userId
 * @param  string $instance
 * @param  Macrame $macrame
 * @return int
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;mastodonFollowers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;/api/v1/accounts/&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="s2"&gt;/followers?limit=80"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;\Exception&lt;/span&gt; &lt;span class="nv"&gt;$e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Followers for '&lt;/span&gt;&lt;span class="nv"&gt;$username&lt;/span&gt;&lt;span class="s2"&gt;' not found on '&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;'"&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;error&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&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="cd"&gt;/**
 * Execute curl GET call to $url and return result
 *
 * @param  string $url
 * @param  Macrame $macrame
 * @return mixed
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'Accept: application/json'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="nv"&gt;$ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_CUSTOMREQUEST&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="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_HTTPHEADER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nb"&gt;curl_setopt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLOPT_RETURNTRANSFER&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="nv"&gt;$result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nv"&gt;$header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;curl_getinfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;CURLINFO_RESPONSE_CODE&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="nv"&gt;$header&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;201&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;$header&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Call to &lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt; returned &lt;/span&gt;&lt;span class="nv"&gt;$header&lt;/span&gt;&lt;span class="s2"&gt;"&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;warning&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;\Exception&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nb"&gt;curl_close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$ch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;json_decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$result&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;the complete source for this project is also available as a &lt;a href="https://gist.github.com/gbhorwood/729e7e4975e40474f9342f1e70467106" rel="noopener noreferrer"&gt;gist&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2025/02/19/php-writing-command-line-applications-with-macrame-pt-2/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>php: powerful sorting with usort</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Fri, 07 Feb 2025 16:03:39 +0000</pubDate>
      <link>https://dev.to/gbhorwood/php-powerful-sorting-with-usort-2fml</link>
      <guid>https://dev.to/gbhorwood/php-powerful-sorting-with-usort-2fml</guid>
      <description>&lt;p&gt;i recently worked on a rescue project where the original dev wanted to sort an array of item objects first by manufacturer name and then by price, descending. their 'solution' was an eighty-line &lt;code&gt;foreach&lt;/code&gt; mess of fiddling with array keys. it was difficult to read, more difficult to modify, and didn't even work correctly lots of the time. i replaced the whole thing with a five-line &lt;code&gt;usort&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.php.net/manual/en/function.usort.php" rel="noopener noreferrer"&gt;&lt;code&gt;usort&lt;/code&gt;&lt;/a&gt; stands for 'user-defined sort'. it allows us, as devs, to write our own sorting rules (plural!) and makes working with arrays of complex data easier.&lt;/p&gt;

&lt;p&gt;in this post, we're going to take a thorough look at sorting arrays with &lt;code&gt;usort&lt;/code&gt;. we'll go over the basics of how the function works, write some ascending and descending sorts on integers and strings, and then cover how to sort on multiple values.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F02%2Fmeme_usort_1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F02%2Fmeme_usort_1.png" title="the 'best i can do for you' meme" alt="the 'best i can do for you' meme" width="665" height="375"&gt;&lt;/a&gt;best other sorting functions can do for you is one rule on an array of primitives&lt;/p&gt;

&lt;h2&gt;
  
  
  our sample array
&lt;/h2&gt;

&lt;p&gt;all the examples we'll be looking at will be using an array of objects that represents a small, but well-curated, collection of vinyl record albums. it looks 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="nv"&gt;$albums&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;span class="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;
        &lt;span class="s1"&gt;'artist'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Bratmobile'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Pottymouth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'year'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1993&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="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;
        &lt;span class="s1"&gt;'artist'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Monk, Thelonious'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Brilliant Corners'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'year'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1957&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="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;
        &lt;span class="s1"&gt;'artist'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Fugazi'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'In on the kill taker'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'year'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1993&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="n"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;
        &lt;span class="s1"&gt;'artist'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'Monk, Thelonious'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'title'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'5 by Monk by 5'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'year'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1959&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  the basics of &lt;code&gt;usort&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;usort&lt;/code&gt; takes two arguments: the array that we want to sort, and a  &lt;a href="https://www.php.net/manual/en/language.types.callable.php" rel="noopener noreferrer"&gt;callable&lt;/a&gt; function that defines the rule(s) we want to use for sorting.&lt;/p&gt;

&lt;p&gt;the array argument is passed by reference. this means that the array is modified in place. &lt;code&gt;usort&lt;/code&gt; does not return a new, sorted, array. rather, the original array is sorted.&lt;/p&gt;

&lt;p&gt;the callable argument is a function that, itself, takes two arguments. these two arguments represent two arbitrary elements of the array we want to sort, and the return value of the callable is an integer that indicates which of those two arguments should be considered 'higher' in the sort order.&lt;/p&gt;

&lt;p&gt;this integer must be one of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; if the two arguments are equal&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; if the left argument is greater than the one on the right&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-1&lt;/code&gt; if the right argument is greater&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we can do any comparison we want in the body of callable. this is the power of &lt;code&gt;usort&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;let's look at some examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  basic sorting: albums by year
&lt;/h2&gt;

&lt;p&gt;let's start with doing some straightforward sorting on a nice, clean integer value: the year our albums were released.&lt;/p&gt;

&lt;p&gt;here's what that would look like:&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;// sorting callable&lt;/span&gt;
&lt;span class="nv"&gt;$sortByYearAsc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&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="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&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="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// calling usort&lt;/span&gt;
&lt;span class="nb"&gt;usort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$albums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$sortByYearAsc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the thing to pay attention to here is how we've constructed our callable. &lt;/p&gt;

&lt;p&gt;we've created an &lt;a href="https://www.php.net/manual/en/functions.anonymous.php" rel="noopener noreferrer"&gt;anonymous function&lt;/a&gt; and assigned it to a variable. our function accepts two arguments, representing two arbitrary elements: the individual album objects in the array we're sorting.&lt;/p&gt;

&lt;p&gt;in the body of the function, we do our comparison testing on the &lt;code&gt;year&lt;/code&gt; element of the album objects. if the year of the left argument is higher, we return &lt;code&gt;1&lt;/code&gt;. if the right argument is higher, &lt;code&gt;-1&lt;/code&gt;, and if the two years are equal, we return &lt;code&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;if we &lt;code&gt;print_r()&lt;/code&gt; our sorted array it will be sorted by year, exactly as we expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    [0] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Monk, Thelonious
            [title] =&amp;gt; Brilliant Corners
            [year] =&amp;gt; 1957
        )

    [1] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Monk, Thelonious
            [title] =&amp;gt; 5 by Monk by 5
            [year] =&amp;gt; 1959
        )

    [2] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Bratmobile
            [title] =&amp;gt; Pottymouth
            [year] =&amp;gt; 1993
        )

    [3] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Fugazi
            [title] =&amp;gt; In on the kill taker
            [year] =&amp;gt; 1993
        )

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  ascending and descending sorts
&lt;/h2&gt;

&lt;p&gt;our current callable sorts our albums by year ascending, ie. oldest first.&lt;/p&gt;

&lt;p&gt;if we want to change to a descending order -- put the newest albums at the top -- all we have to do is reverse our comparisons in our callable:&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;$sortByYearDesc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&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="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&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="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&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="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="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we've changed it so that instead of &lt;code&gt;1&lt;/code&gt; being returned if the left year is greater and &lt;code&gt;-1&lt;/code&gt; if the right year is greater, the inverse is true. we've basically just switched the &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;-1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;the result is that the comparisons still work, just in reverse, and we get our descending sort.&lt;/p&gt;

&lt;h2&gt;
  
  
  enter the 'spaceship operator'
&lt;/h2&gt;

&lt;p&gt;our sorting callables work, but they are &lt;em&gt;ugly&lt;/em&gt;. three &lt;code&gt;if&lt;/code&gt; statements in a row? that's code smell. &lt;/p&gt;

&lt;p&gt;sure, we could tighten them up with an &lt;code&gt;else if&lt;/code&gt; and a default return value, but there's a much cleaner solution: the &lt;a href="https://php-dictionary.readthedocs.io/en/latest/dictionary/spaceship.ini.html" rel="noopener noreferrer"&gt;spaceship operator&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;let's look at 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;$a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the spaceship operator itself is the &lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt;. if you squint, it kind of looks like a crude ascii art of a spaceship. hence the name.&lt;/p&gt;

&lt;p&gt;when we compare two values with the spaceship operator we get zero if both sides are equal, &lt;code&gt;1&lt;/code&gt; if the left side is greater, and &lt;code&gt;-1&lt;/code&gt; if the right side is greater. this is &lt;em&gt;exactly&lt;/em&gt; the sort of comparison we want to do for &lt;code&gt;usort()&lt;/code&gt;!&lt;/p&gt;

&lt;p&gt;let's rewrite our callable to use the spaceship operator:&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;$sortByYearAsc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&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;this does everything that our ugly, three-&lt;code&gt;if&lt;/code&gt; statement function did and is much cleaner and terser.&lt;/p&gt;

&lt;p&gt;if we want to, we can tighten this up even more by rewriting it as an &lt;a href="https://www.php.net/manual/en/functions.arrow.php" rel="noopener noreferrer"&gt;arrow function&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;$sortByYearAdc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and, if we want to change the sort order from ascending to descending, all we need to do is switch the operands: swap &lt;code&gt;$a&lt;/code&gt; and &lt;code&gt;$b&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  sorting on strings
&lt;/h2&gt;

&lt;p&gt;nobody sorts their album collection by release year. what we probably want to do is sort alphabetically by artist.&lt;/p&gt;

&lt;p&gt;fortunately, php has some built-in functions for doing string comparisons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.php.net/manual/en/function.strcmp.php" rel="noopener noreferrer"&gt;&lt;code&gt;strcmp&lt;/code&gt;&lt;/a&gt; compares two strings&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.php.net/manual/en/function.strcasecmp.php" rel="noopener noreferrer"&gt;&lt;code&gt;strcasecmp&lt;/code&gt;&lt;/a&gt; compares two strings without case sensitivity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;both these functions take two arguments: the strings to compare. and both behave like the spaceship operator, returning &lt;code&gt;1&lt;/code&gt; if the left argument is greater, &lt;code&gt;-1&lt;/code&gt; if the right argument is greater, and &lt;code&gt;0&lt;/code&gt; if the arguments are the same.&lt;/p&gt;

&lt;p&gt;let's look at how we would write a callable to sort on artist using &lt;code&gt;strcasecmp&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="nv"&gt;$sortByArtistAsc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;strcasecmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nb"&gt;usort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$albums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$sortByArtistAscSpaceship&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;in our callable here we have replaced our spaceship operator comparing years with a call to &lt;code&gt;strcasecmp&lt;/code&gt; to compare artists. the results are precisely what we want:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    [0] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Bratmobile
            [title] =&amp;gt; Pottymouth
            [year] =&amp;gt; 1993
        )

    [1] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Fugazi
            [title] =&amp;gt; In on the kill taker
            [year] =&amp;gt; 1993
        )

    [2] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Monk, Thelonious
            [title] =&amp;gt; 5 by Monk by 5
            [year] =&amp;gt; 1959
        )

    [3] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Monk, Thelonious
            [title] =&amp;gt; Brilliant Corners
            [year] =&amp;gt; 1957
        )

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  sorting on multiple elements
&lt;/h2&gt;

&lt;p&gt;people who really care about how their albums are organized may argue about a lot of things (is "iggy pop" filed under "i" or "p"? we have &lt;em&gt;opinions&lt;/em&gt;), but there is one thing everyone agrees on: albums are filed alphabetically by artist, and the albums of each artist are filed in chronological order of release.&lt;/p&gt;

&lt;p&gt;this means that to properly sort our array of albums, we need to sort by artist first, and then by year. we're looking here to replicate the functionality of sql's&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;artist&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;year&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this is precisely the sort of thing that &lt;code&gt;usort&lt;/code&gt; excels at. let's look at how we might do that:&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;$sortByArtistThenYear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="p"&gt;)&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="nb"&gt;strcasecmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;strcasecmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$a&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$b&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;artist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nb"&gt;usort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$albums&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$sortByArtistThenYear&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we're just taking the things we've already covered -- string comparison with &lt;code&gt;strcasecmp&lt;/code&gt; and integer comparison with the spaceship operator -- and combining them.&lt;/p&gt;

&lt;p&gt;first, we see if the two artist fields are the same by testing them with &lt;code&gt;strcasecmp&lt;/code&gt;. if they are, we know that we need to sort the albums for that artist by year. we do that with the spaceship operator.&lt;/p&gt;

&lt;p&gt;if the artist fields are not the same, we do our standard sorting on the artist field.&lt;/p&gt;

&lt;p&gt;the result  is that our array is sorted ascending by artist and, if there is more than one record with the same artist, those records are sorted ascending by year. it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Array
(
    [0] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Bratmobile
            [title] =&amp;gt; Pottymouth
            [year] =&amp;gt; 1993
        )

    [1] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Fugazi
            [title] =&amp;gt; In on the kill taker
            [year] =&amp;gt; 1993
        )

    [2] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Monk, Thelonious
            [title] =&amp;gt; Brilliant Corners
            [year] =&amp;gt; 1957
        )

    [3] =&amp;gt; stdClass Object
        (
            [artist] =&amp;gt; Monk, Thelonious
            [title] =&amp;gt; 5 by Monk by 5
            [year] =&amp;gt; 1959
        )

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;usort&lt;/code&gt; is an incredibly powerful tool and once we understand how to write sort rules for it we can order arrays of complex data any way we want to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2025/02/07/php-powerful-sorting-with-usort/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>php</category>
    </item>
    <item>
      <title>php: writing command-line applications with macrame. pt 1</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Wed, 29 Jan 2025 19:18:09 +0000</pubDate>
      <link>https://dev.to/gbhorwood/php-writing-command-line-applications-with-macrame-3nk4</link>
      <guid>https://dev.to/gbhorwood/php-writing-command-line-applications-with-macrame-3nk4</guid>
      <description>&lt;p&gt;php doesn't get a lot of attention as a command line scripting language. which is a shame, since php has a lot of features that make it a good choice for writing terminal apps.&lt;/p&gt;

&lt;p&gt;in this series, we'll be going over writing interactive command line scripts using the &lt;a href="https://github.com/gbhorwood/Macrame" rel="noopener noreferrer"&gt;macrame&lt;/a&gt; library. we'll be working through building an example project, a script that fetches a list of a mastodon user's followers, from start to end, and will cover topics such as getting and validating user input, building interactive menus, handling command line arguments, accessing files safely, styling output text, and running functions in the background while showing our users an animated spinner.&lt;/p&gt;

&lt;p&gt;further information on macrame can be found on the &lt;a href="https://macrame.fruitbat.studio/Installation.html" rel="noopener noreferrer"&gt;documentation site&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  the sample project
&lt;/h2&gt;

&lt;p&gt;the project we will be working through is a simple command-line script that returns a list of a mastodon user's followers. running it looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F01%2Fmacrameexample.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2025%2F01%2Fmacrameexample.gif" title="animated gif of the sample project running" alt="animated gif of the sample project running" width="500" height="318"&gt;&lt;/a&gt;the sample script in action&lt;/p&gt;

&lt;p&gt;the user selects the desired mastodon instance from a dynamic menu, enters the username as free text, and the script shows an animated spinner while fetching the data. the output is a nice, ascii-style table.&lt;/p&gt;

&lt;p&gt;the complete source code for this project is available &lt;a href="https://gist.github.com/gbhorwood/729e7e4975e40474f9342f1e70467106" rel="noopener noreferrer"&gt;as a gist&lt;/a&gt; for anyone who wants to skip ahead.&lt;/p&gt;

&lt;h2&gt;
  
  
  the flyover
&lt;/h2&gt;

&lt;p&gt;in this installment, we will go over how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;install macrame&lt;/li&gt;
&lt;li&gt;scaffold a blank script&lt;/li&gt;
&lt;li&gt;read command line arguments&lt;/li&gt;
&lt;li&gt;create a dynamic menu&lt;/li&gt;
&lt;li&gt;read a line of user input (with optional validation)&lt;/li&gt;
&lt;li&gt;styling output text&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  install macrame
&lt;/h2&gt;

&lt;p&gt;macrame is installed via composer&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="n"&gt;composer&lt;/span&gt; &lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="n"&gt;gbhorwood&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;macrame&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  scaffolding your script
&lt;/h2&gt;

&lt;p&gt;once we have macrame installed, we can set up a basic 'hello world' script and use that as our starting boilerplate. although this scaffolding is &lt;em&gt;technically&lt;/em&gt; not required, using it will make our script a little safer and more compliant. let's look at the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;#!/usr/bin/env php 
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Gbhorwood\Macrame\Macrame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Instantiate a Macrame object.&lt;/span&gt;
&lt;span class="c1"&gt;// The argument is the name of the script as seen by ps(1)&lt;/span&gt;
&lt;span class="nv"&gt;$macrame&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;Macrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Example Macrame script"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Enforce that the script only runs if executed on the command line&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate that the host system can run Macrame scripts. Exit on failure&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// output text to STDOUT&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"hello world"&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;write&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// exit cleanly&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&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;although this isn't a whole lot of lines, there's a lot happening here. let's go over it.&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;#!/usr/bin/env php &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this line is the '&lt;a href="https://en.wikipedia.org/wiki/Shebang_%28Unix%29" rel="noopener noreferrer"&gt;shebang&lt;/a&gt;'. basically, it tells our linux-like operating system which interpreter to use to run this script. this allows us to run our script without having to type &lt;code&gt;php&lt;/code&gt; first. the shebang &lt;em&gt;must&lt;/em&gt; be the first line in the file, even before &lt;code&gt;&amp;lt;?php&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="nv"&gt;$macrame&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;Macrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Example Macrame script"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we create a &lt;code&gt;Macrame&lt;/code&gt; object that we will use throughout the rest of our script. pretty standard stuff. the only interesting part is the argument. this is the name that the operating system will give to our script. for instance, if we run &lt;a href="https://linuxize.com/post/ps-command-in-linux/" rel="noopener noreferrer"&gt;&lt;code&gt;ps&lt;/code&gt;&lt;/a&gt; to show a list of running processes, our script will show up with this name.&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;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this statement ensures that all the code inside the block will only execute if the script has been run on the command line.&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;when we write php for the command line, we do not have as much control over the environment as we do on a webserver that we own and manage. the &lt;code&gt;preflight()&lt;/code&gt; call here tests the local php environment and, if it does not meet the minimum requirements, terminates the script with an error message. the minimum requirements are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;php 7.4 &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;posix&lt;/code&gt; extension&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;mbstring&lt;/code&gt; extension&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;note:&lt;/strong&gt; although macrame does run on php 7.4 and 8.0, due to changes in how php handles multibyte strings in 8.1, strings containing emojis may not be aligned properly in output on php pre-8.1.&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this exits the script cleanly, returning a success code of &lt;code&gt;0&lt;/code&gt;. additionally, any &lt;a href="https://macrame.fruitbat.studio/Manual/File_Read_and_Write.html#cleaning-up-files-on-exit" rel="noopener noreferrer"&gt;temporary files&lt;/a&gt; created during execution will be automatically deleted. using macrame's &lt;code&gt;exit()&lt;/code&gt; function is preferable to php's &lt;code&gt;die()&lt;/code&gt;;&lt;/p&gt;

&lt;h2&gt;
  
  
  running hello world
&lt;/h2&gt;

&lt;p&gt;once we have our basic 'hello world' script written, we can set its permissions to allow execution and run it on the command line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;755 ./examplescript.php
./examplescript.php
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  reading arguments
&lt;/h2&gt;

&lt;p&gt;macrame provides a set of tools for parsing and reading command line arguments. let's start with something straightforward: getting the version number when the script is called with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./examplescript.php &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="c"&gt;# or &lt;/span&gt;
./examplescript.php &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for this case, all we need to do is check whether either of those arguments exist. &lt;/p&gt;

&lt;p&gt;we can do this by calling the &lt;code&gt;args()&lt;/code&gt; method on our &lt;code&gt;macrame&lt;/code&gt; object, which returns an object containing all of our script's arguments and a suite of methods we can use to inspect them. to test if an argument exists, we can use the &lt;code&gt;exists()&lt;/code&gt; method like so:&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;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&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;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'version'&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;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1.0'&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;write&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// output the version number&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&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;the &lt;code&gt;exists()&lt;/code&gt; method returns a boolean.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎 macrame calls are designed to be chained.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;command line arguments can also be used to assign values to variables. for instance, to set the &lt;code&gt;username&lt;/code&gt; value, we will probably want our users to be able to call the script like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./examplescript.php &lt;span class="nt"&gt;--username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghorwood
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to get the value of this argument, in our script we can use the &lt;code&gt;first()&lt;/code&gt; method provided by &lt;code&gt;args()&lt;/code&gt; like so:&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;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'username'&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;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as the name implies, the &lt;code&gt;first()&lt;/code&gt; method returns the value of the &lt;em&gt;first&lt;/em&gt; occurrence of our argument. if we called our script like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./examplescript.php &lt;span class="nt"&gt;--username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;firstuser &lt;span class="nt"&gt;--username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;seconduser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;then &lt;code&gt;first()&lt;/code&gt; would return the value 'firstuser'. if we want the last value, we can call &lt;code&gt;last()&lt;/code&gt;. if we want &lt;em&gt;all&lt;/em&gt; the values as an array, we would use &lt;code&gt;all()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;putting this all together, our script now looks 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;#!/usr/bin/env php 
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Gbhorwood\Macrame\Macrame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nv"&gt;$macrame&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;Macrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Example Macrame script"&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="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handle the -v or --version arguments
     */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&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;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'version'&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;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1.0'&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;write&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Accept first value of --instance=
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'instance'&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;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Accept first value of --username=
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'username'&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;first&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&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;the full list of methods for handling command line args is covered in the &lt;a href="https://macrame.fruitbat.studio/Manual/Handling_Arguments.html" rel="noopener noreferrer"&gt;macrame documentation on arguments&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  creating a dynamic menu
&lt;/h1&gt;

&lt;p&gt;we're also going to want to give our users the option to use our script interactively. if they don't pass an argument on the command line, we will prompt them to input the data. for the mastodon instance value, we're going to use a menu.&lt;/p&gt;

&lt;p&gt;macrame menus are dynamic; our users naivgate them by using the arrow keys to move up or down the list and then hit &lt;code&gt;&amp;lt;RETURN&amp;gt;&lt;/code&gt; to make their selection. let's write a function that displays a menu to the user and returns the selected value:&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;function&lt;/span&gt; &lt;span class="n"&gt;menuInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Select your instance"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// list of options in the menu&lt;/span&gt;
    &lt;span class="nv"&gt;$instances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'phpc.social'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'mastodon.social'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'mstdn.ca'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// display the menu, return the selected text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;menu&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;erase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// erase the menu after selection&lt;/span&gt;
                   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$instances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$header&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;the core functionality here is a call to:&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;menu&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;interactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we provide an array of strings to display as the menu options and an optional header text for the menu to &lt;code&gt;menu()-&amp;gt;interactive()&lt;/code&gt; and the menu is automatically displayed to the user. the user's selection is returned as a string.&lt;/p&gt;

&lt;p&gt;there is also the option to erase the menu from the screen after the user has made their selection by adding a call to &lt;code&gt;erase()&lt;/code&gt; to our chain. this method is optional, but does keep things clean.&lt;/p&gt;

&lt;p&gt;once we have our menu function, we can modify how we get the mastodon instance. we will try reading it from the command line arguments and, if no value has be&lt;/p&gt;
&lt;p&gt;&lt;br&gt;
as the name implies, the &lt;code&gt;first()&lt;/code&gt; method returns the value of the first occurrence of our argument. if we called our script like this:&lt;br&gt;
&lt;/p&gt;en passed, call our &lt;code&gt;menuInstance()&lt;/code&gt; function.&lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'instance'&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;first&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;$instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;menuInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&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;h2&gt;
  
  
  a side note on styling menus
&lt;/h2&gt;

&lt;p&gt;out of the box, macrame uses the terminal's default style and colour for menus and sets the highlighted item in reverse. we can alter this if we want by adding a few extra methods to our chain. for instance, if we would prefer that our highlighted item was shown as bold, red text, we could write:&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;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;menu&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;styleSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bold'&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;colourSelected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'red'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// colorSelected() also works&lt;/span&gt;
        &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;array&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;there is a complete overview on the &lt;a href="https://macrame.fruitbat.studio/Manual/Menus_and_Such.html#styling-menu-options" rel="noopener noreferrer"&gt;menu documentation page&lt;/a&gt; of all the methods available to customize the colour, style and alignment of menus.&lt;/p&gt;

&lt;h1&gt;
  
  
  reading a line of user input
&lt;/h1&gt;

&lt;p&gt;next, we're going to modify how we get the username to also accept interactive input. in this case, we're going to read a sting of user input text using &lt;code&gt;input()-&amp;gt;readline()&lt;/code&gt;. here's the function:&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;function&lt;/span&gt; &lt;span class="n"&gt;inputUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the prompt for the text input, with bold styling using tags&lt;/span&gt;
    &lt;span class="nv"&gt;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;!BOLD!&amp;gt;Username&amp;lt;!CLOSE!&amp;gt; (for &lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;): "&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cm"&gt;/* alternate method to apply bold styling:
    $prompt = $macrame-&amp;gt;text('Username ')
                      -&amp;gt;style('bold')
                      -&amp;gt;get()."(for $instance): ";
     */&lt;/span&gt;

    &lt;span class="c1"&gt;// read one line of user input, return the text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prompt&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;the last line of this function is where we poll the user for input. the &lt;code&gt;readline()&lt;/code&gt; method accepts an optional &lt;code&gt;$prompt&lt;/code&gt; argument; the text we display to the user telling them what they should enter. the return value is the user input as a string.&lt;/p&gt;

&lt;h2&gt;
  
  
  a side note on input validation
&lt;/h2&gt;

&lt;p&gt;users make mistakes. that's why input validation is important.&lt;/p&gt;

&lt;p&gt;macrame comes with a number of pre-set methods to validate input. we can add as many of them as we want to our chain and if any of the validators fail, the user will be prompted to input again. the &lt;code&gt;input()-&amp;gt;readline()&lt;/code&gt; function will not return a value until all validators pass.&lt;/p&gt;

&lt;p&gt;let's look at 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="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&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;isLengthMin&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="s1"&gt;'must be at least 4 characters long'&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;doesNotContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'do not use the @ symbol'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
               &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prompt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we apply two validation test: the text must be four or more characters and must not contain the '@' symbol. for both those validation methods, the second argument is the error message we will show to the user if the validation fails.&lt;/p&gt;

&lt;p&gt;a full list of the pre-built validation functions is available on the &lt;a href="https://macrame.fruitbat.studio/Manual/Getting_User_Text_Input.html#validator-list" rel="noopener noreferrer"&gt;macrame input documentation page&lt;/a&gt;. if we want to write our own custom validators, &lt;a href="https://macrame.fruitbat.studio/Manual/Getting_User_Text_Input.html#adding-custom-validators" rel="noopener noreferrer"&gt;that is covered, too&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  what about 'dots echo' output?
&lt;/h2&gt;

&lt;p&gt;if our users are inputting sensitive data, like passwords, we'll probably not want to echo their keystrokes back to the terminal where nosey shoulder-surfers can read it.&lt;/p&gt;

&lt;p&gt;to address this, macrame provides a 'dots echo' version of &lt;code&gt;readline()&lt;/code&gt; called &lt;code&gt;readPassword()&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="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&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;readPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"enter sensitive data: "&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;each keystroke read by &lt;code&gt;readPassword()&lt;/code&gt; is echoed back as an asterisk.&lt;/p&gt;

&lt;h1&gt;
  
  
  styling text
&lt;/h1&gt;

&lt;p&gt;in the example of how to read a line of user text, we saw a bunch of code for styling the prompt text. let's look at that in more detail.&lt;/p&gt;

&lt;p&gt;macrame allows for styling text output to the terminal using ansi codes which allow us to apply both styles such as bold and italic, and colours to our text.&lt;/p&gt;

&lt;p&gt;we can do this in our script one of two ways. there are methods such as &lt;code&gt;style()&lt;/code&gt;, and &lt;code&gt;colour()&lt;/code&gt; (or &lt;code&gt;color()&lt;/code&gt;), or we can use a basic tagging system with text.&lt;/p&gt;

&lt;p&gt;let's look at the method approach first.&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;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Username '&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;style&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bold'&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;colour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'blue'&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;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;"(for &lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;): "&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we created a 'text' object using macrame's &lt;code&gt;text()&lt;/code&gt; method, then applied a style and colour before returning it as a string using &lt;code&gt;get()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;note that the style and colour methods are applied to all the text in the string. if we want to mix in styled and coloured text with plain text, we will have to create a number of substrings and concatenate them together. this can be cumbersome, especially if we're dealing with large amounts of text.&lt;/p&gt;

&lt;p&gt;alternately, we can use macrame's tagging system to make styling our text a little easier. here's 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;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;!BOLD!&amp;gt;Username&amp;lt;!CLOSE!&amp;gt; (for &lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;): "&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the text between the &lt;code&gt;&amp;lt;!BOLD!&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;!CLOSE!&amp;gt;&lt;/code&gt; tags will, unsurprisingly, be bolded. there is a full list of all the tags &lt;a href="https://macrame.fruitbat.studio/Manual/Styled_Text_Output.html#using-tags-to-style-text" rel="noopener noreferrer"&gt;in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;an important thing to note is that the &lt;code&gt;&amp;lt;!CLOSE!&amp;gt;&lt;/code&gt; tag closes &lt;em&gt;all&lt;/em&gt; of the preceding tags. this is due to the behaviour of ANSI escape codes. &lt;/p&gt;

&lt;p&gt;this means that nesting tags does not work the way we might expect. for instance, in this example, the first &lt;code&gt;&amp;lt;!CLOSE!&amp;gt;&lt;/code&gt; tag closes &lt;em&gt;both&lt;/em&gt; the &lt;code&gt;&amp;lt;!BOLD!&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;!RED!&amp;gt;&lt;/code&gt; tags:&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="s2"&gt;"&amp;lt;!RED!&amp;gt;this is red &amp;lt;!BOLD!&amp;gt;this is red and bold&amp;lt;!CLOSE!&amp;gt; this is plain&amp;lt;!CLOSE!&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  the script so far
&lt;/h1&gt;

&lt;p&gt;our example script so far looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;#!/usr/bin/env php
&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;require&lt;/span&gt; &lt;span class="k"&gt;__DIR__&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;'/vendor/autoload.php'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Gbhorwood\Macrame\Macrame&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Instantiate a Macrame object.&lt;/span&gt;
&lt;span class="c1"&gt;// The argument is the name of the script as seen by ps(1)&lt;/span&gt;
&lt;span class="nv"&gt;$macrame&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;Macrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Example Macrame script"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Enforce that the script only runs if executed on the command line&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;running&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="c1"&gt;// Validate that the host system can run Macrame scripts. Exit on failure&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;preflight&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handle the -v or --version arguments
     */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'v'&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;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'version'&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;exists&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'1.0'&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;write&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handle the --instance= argument if present, or poll user for instance
     * with dynamic menu if not.
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'instance'&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;first&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;$instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$instance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;menuInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/**
     * Handle the --username= argument if present, or poll user for username
     * with text input if not
     */&lt;/span&gt;
    &lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;args&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'username'&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;first&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;$username&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;inputUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="c1"&gt;// exit cleanly&lt;/span&gt;
    &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="k"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Display a dynamic menu of instances to the user, return
 * the selected text.
 *
 * @param  Macrame $macrame
 * @return string
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;menuInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Select your instance"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// list of options in the menu&lt;/span&gt;
    &lt;span class="nv"&gt;$instances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="s1"&gt;'phpc.social'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'mastodon.social'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;'mstdn.ca'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;];&lt;/span&gt;

    &lt;span class="c1"&gt;// display the menu, return the selected text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;menu&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;erase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// erase the menu after selection&lt;/span&gt;
                   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;interactive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$instances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$header&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/**
 * Display a text input to the user, return the input text.
 *
 * @param  string $instance
 * @param  Macrame $macrame
 * @return string
 */&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;inputUsername&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Macrame&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the prompt for the text input, with bold styling using tags&lt;/span&gt;
    &lt;span class="nv"&gt;$prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;!BOLD!&amp;gt;Username&amp;lt;!CLOSE!&amp;gt; (for &lt;/span&gt;&lt;span class="nv"&gt;$instance&lt;/span&gt;&lt;span class="s2"&gt;): "&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;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="cm"&gt;/* alternate method to apply bold styling:
    $prompt = $macrame-&amp;gt;text('Username ')
                      -&amp;gt;style('bold')
                      -&amp;gt;get()."(for $instance): ";
     */&lt;/span&gt;

    &lt;span class="c1"&gt;// read one line of user input, return the text&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$macrame&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;input&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;doesNotContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'@'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'do not use the @ symbol'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                   &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$prompt&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;h1&gt;
  
  
  what's next
&lt;/h1&gt;

&lt;p&gt;so far, we've covered reading command line arguments, getting user input from menus and as text, and doing some basic text styling for output. in the next post, we will go over:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;running a function in the background while we show users an animated spinner&lt;/li&gt;
&lt;li&gt;writing safely to files&lt;/li&gt;
&lt;li&gt;outputting array data as a nicely-formatted ascii table&lt;/li&gt;
&lt;li&gt;paging long output &lt;/li&gt;
&lt;li&gt;basic notice level output&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2025/01/29/php-writing-command-line-applications-with-macrame/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>php</category>
      <category>cli</category>
    </item>
    <item>
      <title>vim: five tips to become a more mediocre vim user</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Mon, 30 Sep 2024 16:35:15 +0000</pubDate>
      <link>https://dev.to/gbhorwood/vim-five-tips-to-become-a-more-mediocre-vim-user-ich</link>
      <guid>https://dev.to/gbhorwood/vim-five-tips-to-become-a-more-mediocre-vim-user-ich</guid>
      <description>&lt;p&gt;i am a mediocre vim user. sure, i've been using vim for over twenty years, have handcrafted a &lt;a href="https://github.com/gbhorwood/vimrc" rel="noopener noreferrer"&gt;custom vimrc&lt;/a&gt;, and even written &lt;a href="https://github.com/gbhorwood/amber.vim" rel="noopener noreferrer"&gt;a syntax file&lt;/a&gt;, but for my day-to-day usage, my skills are resoundingly and undeniably mediocre.&lt;/p&gt;

&lt;p&gt;this is a good place to be. people who are 'power users' spend so much time fiddling with plugins and configurations to build their perfect homerolled ide that they never get any real work done. vim becomes their personality, not their tool. by contrast, novices don't have the skill or knowledge to leverage vim's power. they thrash around, treating vim like a cumbersome and difficult nano; they google how to quit. that's not good for productivity!&lt;/p&gt;

&lt;p&gt;the sweet spot is in the middle: mediocrity.&lt;/p&gt;

&lt;p&gt;this post is going to go over five vim features that will get you on the path from novice to mediocre.&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_vim5a_1.jpg" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_vim5a_1.jpg" title="the 'see nobody cares' meme" alt="the 'see nobody cares' meme"&gt;&lt;/a&gt;nobody cares if you're mediocre at vim&lt;/p&gt;

&lt;h2&gt;
  
  
  doing bookmarks with &lt;code&gt;m&lt;/code&gt; and &lt;code&gt;'&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;vim allows you to set a bookmark on a given line and jump back to that line at any time. this is a useful feature if you don't want to be like my former co-worker wes who used to keep a scratchpad of Important Line Numbers beside his keyboard.&lt;/p&gt;

&lt;p&gt;bookmarks are set by using &lt;code&gt;m&lt;/code&gt; plus a one-letter identifier in command mode. so, for instance, to set a bookmark labelled 'a', we would do&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ESC&amp;gt;ma
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;since bookmarks have a one-letter label, we can have a whole bunch of them. we can have an 'a' bookmark and a 'b' one and so on, until we run out of alphabet.&lt;/p&gt;

&lt;p&gt;to jump back to our bookmark, in command mode, we use &lt;code&gt;'&lt;/code&gt; (the single quote) plus the identifier of the bookmark we want to jump to. so, ie.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ESC&amp;gt;'a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;would bring us back to bookmark 'a'.&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_1_bookmark.gif" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_1_bookmark.gif" title="animated gif showing vim bookmarks" alt="animated gif showing vim bookmarks"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  dangerous 'power user' territory for bookmarks
&lt;/h3&gt;

&lt;p&gt;if we want to venture outside the comfortable zone of mediocrity, bookmarks have plenty of other features we can obsess over. we can jump to not just the line, but the column of our bookmark using the backtick instead of the single quote. we can list and manage our bookmarks with &lt;code&gt;:marks&lt;/code&gt;. we can delete them with &lt;code&gt;:delm&lt;/code&gt;. lots of stuff, which is all covered on the &lt;a href="https://vim.fandom.com/wiki/Using_marks" rel="noopener noreferrer"&gt;vim fandom page on bookmarks&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  doing shell escapes with &lt;code&gt;!!&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;we can dump the output of any shell command straight into our vim buffer by using a 'shell escape'.&lt;/p&gt;

&lt;p&gt;shell escapes are called by typing &lt;code&gt;!!&lt;/code&gt; in command mode, followed by the shell command we want to run. for example, if we wanted to insert the current date as output by bash's &lt;code&gt;date&lt;/code&gt; command, we would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ESC&amp;gt;!!date
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or, if we wanted to dump the contents of another file into the file we're editing, we could use &lt;code&gt;cat&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ESC&amp;gt;!!cat /path/to/file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;even better, when we shell escape with &lt;code&gt;!!&lt;/code&gt;, the content of the current line is used as &lt;em&gt;input&lt;/em&gt; for our shell command. so, if we wanted to, say, replace all the letter 'e's in the current line with the number 3, we could use &lt;code&gt;tr&lt;/code&gt; like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ESC&amp;gt;!!tr 'e' '3'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this would take the contents of the current line and pipe it into our &lt;code&gt;tr&lt;/code&gt; command. the output of &lt;code&gt;tr&lt;/code&gt; would then overwrite our line in vim.&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_2_escape_line.gif" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_2_escape_line.gif" title="animated gif showing vim shell escapes" alt="animated gif showing vim shell escapes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  doing better shell escapes with &lt;code&gt;!}&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;the &lt;code&gt;!!&lt;/code&gt; shell escape is great, but it only works on the current line. truly mediocre vim users can use &lt;code&gt;!}&lt;/code&gt; to pipe an entire paragraph into a shell escape.&lt;/p&gt;

&lt;p&gt;for example, if we have a paragraph in vim that is a list of rock bands, we can sort that entire list alphabetically by moving our cursor to the first line of the paragraph and applying bash's &lt;code&gt;sort&lt;/code&gt; command like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ESC&amp;gt;!}sort
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the entire paragraph is sent as input to &lt;code&gt;sort&lt;/code&gt;, and &lt;code&gt;sort&lt;/code&gt;'s output replaces the entire paragraph.&lt;/p&gt;

&lt;p&gt;if we have a decent understanding of shell tools like &lt;code&gt;awk&lt;/code&gt; and &lt;code&gt;sed&lt;/code&gt;, this can allow us to do &lt;em&gt;extremely&lt;/em&gt; mediocre things. for instance, if we want to take that entire list of bands and enclose each line in quotes and number them, we could do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;!}!awk '{print NR". \""$0"\""}'
&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_3_escape_paragraph.gif" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_3_escape_paragraph.gif" title="animated gif showing vim better shell escapes" alt="animated gif showing better vim shell escapes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  dangerous 'power user' territory for shell escaping
&lt;/h3&gt;

&lt;p&gt;there's lots of shell escaping power outside the comforting boundaries of mediocrity, and we can go there if we feel brave. there's stuff like using &lt;code&gt;:call system()&lt;/code&gt; or suppressing output with &lt;code&gt;:silent!!&lt;/code&gt; and other rabbit holes we can venture down instead of getting stuff done. there's a fair overview of these in this post on &lt;a href="https://www.baeldung.com/linux/vim-shell-commands-silence" rel="noopener noreferrer"&gt;executing shell commands in vim&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  doing autocomplete with &lt;code&gt;^p&lt;/code&gt; and &lt;code&gt;^n&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;autocompletion is a handy tool. we all use very descriptive and, consequently, very long names for variables and functions like good programmers, but the punishment for that is all the typing and the risk of errors. autocompletion takes that pain away.&lt;/p&gt;

&lt;p&gt;vim allows us to do autocompletion with &lt;code&gt;^p&lt;/code&gt; (that's the control key and 'p' as a 'chord') and &lt;code&gt;^n&lt;/code&gt; in insert mode.&lt;/p&gt;

&lt;p&gt;using &lt;code&gt;^p&lt;/code&gt; will search the file we're editing from the current line up until it finds the first word that matches the text we've typed. the 'p' stands for 'previous', as 'up the file'. if we like that suggestion, we can hit &lt;code&gt;&amp;lt;SPACE&amp;gt;&lt;/code&gt; and accept it. if we don't like it, we can just hit &lt;code&gt;^p&lt;/code&gt; again (and again and again) until we find what we're looking for. if our search gets to the top of the file, vim wraps around and starts searching from the bottom.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;^n&lt;/code&gt; combination works the exact same as &lt;code&gt;^p&lt;/code&gt; except is searches &lt;em&gt;down&lt;/em&gt; file. the 'n' stands for 'next'.&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_4_autocomplete.gif" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_4_autocomplete.gif" title="animated gif showing vim autocomplete" alt="animated gif showing vim autocomplete"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  a note on the dangers of &lt;code&gt;^p&lt;/code&gt; and &lt;code&gt;^n&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;if we're using a vim keybinding emulator in another program like an ide, &lt;code&gt;^p&lt;/code&gt; probably won't work. instead of doing something useful like autocompletion, &lt;code&gt;^p&lt;/code&gt; will probably be bound to a little-used feature called 'print', which is a way to make a copy of  your file onto a piece of paper so you can recycle it. likewise, &lt;code&gt;^n&lt;/code&gt; will probably create for you a 'new' file, as if editing the current file wasn't already enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  doing block indentation with visual mode
&lt;/h2&gt;

&lt;p&gt;vim does a pretty good job of autoindenting, but there are times when we make a mess of our file and need to add some manual spaces to keep things neat and tidy.&lt;/p&gt;

&lt;p&gt;vim allows us to indent entire blocks by selecting one or more lines in the visual mode and then applying &lt;code&gt;&amp;gt;&lt;/code&gt; to indent or &lt;code&gt;&amp;lt;&lt;/code&gt; to unindent. &lt;/p&gt;

&lt;p&gt;doing this is a two-step process: the selecting-lines-in-visual-mode step, and the indenting-the-selected-lines step. let's look at an example of how we would indent one block of four lines one tab stop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;ESC&amp;gt;&amp;lt;SHIFT&amp;gt;vjjj&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;let's pick this apart.&lt;/p&gt;

&lt;p&gt;to select our block of four lines, we first enter command mode with &lt;code&gt;&amp;lt;ESC&amp;gt;&lt;/code&gt;. standard stuff. from there, we enter visual mode with &lt;code&gt;&amp;lt;SHIFT&amp;gt;v&lt;/code&gt;. this highlights our current line. that line is our block. we then use &lt;code&gt;j&lt;/code&gt; to move our cursor down three lines. this adds those lines to our block. we now have four lines highlighted.&lt;/p&gt;

&lt;p&gt;we then hit the &lt;code&gt;&amp;gt;&lt;/code&gt; key. this moves the entire block of highlighted lines one tabstop to the right. indentation achieved.&lt;/p&gt;

&lt;p&gt;if we want to indent that block another tabstop, we can then type &lt;code&gt;.&lt;/code&gt; (a period). the period key tells vim to redo the previous command. we can hammer on that period as much as we want to keep indenting until our block is where we want it.&lt;/p&gt;

&lt;p&gt;to unindent, all we have to do is hit &lt;code&gt;&amp;lt;&lt;/code&gt; after selecting our block. &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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_5_indent.gif" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fvim5a_5_indent.gif" title="animated gif showing vim visual indent" alt="animated gif showing vim visual indent"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  dangerous 'power user' territory for visual blocks
&lt;/h3&gt;

&lt;p&gt;it's easy to get carried away with visual blocks; we can do a lot of stuff. in addition to using it for a block of lines, there's a character mode and a mode that allows us to create a block in the middle of a file. we can also use selected blocks as input for other vim commands with the output going back into that block. this even includes shell escapes. the 'linux handbook' has a &lt;a href="https://linuxhandbook.com/vim-visual-mode/" rel="noopener noreferrer"&gt;good overview of visual mode&lt;/a&gt; for people who want to flirt with being a power user.&lt;/p&gt;

&lt;h2&gt;
  
  
  conclusion
&lt;/h2&gt;

&lt;p&gt;vim has a reputation for being difficult. some of that reputation is earned, to be sure, especially in this modern point-and-click world, but a &lt;em&gt;lot&lt;/em&gt; of it has been fostered by power users looking to show off and novices loudly expressing their frustration at the entry barrier. &lt;/p&gt;

&lt;p&gt;but vim doesn't need to be difficult. we don't need to be those power users with forty plugins and a five hundred-line &lt;code&gt;.vimrc&lt;/code&gt;. we can edit text effectively and quickly in vim with only a limited -- mediocre -- skillset.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2024/09/30/vim-five-tips-to-become-a-more-mediocre-vim-user/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>vim</category>
    </item>
    <item>
      <title>mysql: “thai food near me”, or: doing geo distance calculations in your database.</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Fri, 20 Sep 2024 14:46:38 +0000</pubDate>
      <link>https://dev.to/gbhorwood/mysql-thai-food-near-me-or-doing-geo-distance-calculations-in-your-database-512</link>
      <guid>https://dev.to/gbhorwood/mysql-thai-food-near-me-or-doing-geo-distance-calculations-in-your-database-512</guid>
      <description>&lt;p&gt;we're all familiar with the whole "thai food near me" thing. you type that phrase into your phone and it responds with a list of thai restaurants that are, well, &lt;em&gt;near you&lt;/em&gt;. and we have a kind-of understanding of how that works under the hood: google or whoever has a database of thai restaurants with their latitudes and longitudes and knows our location from our phone and then does 'some process' to figure out which thai places are nearby. &lt;/p&gt;

&lt;p&gt;in this post,we'll be going over that 'some process' part, looking at how to use mysql to do some standard location stuff. we'll cover mysql's &lt;code&gt;POINT&lt;/code&gt; and &lt;code&gt;POLYGON&lt;/code&gt; types, finding the distance between two points on a sphere (which the earth, contrary to what you may have read on the internet, is), determining if a point is inside of a polygon defined by points, and look at things like 'spatial reference systems' which define how coordinates are plotted on the surface of the earth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R7EFjWei--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/meme_location1.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R7EFjWei--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/meme_location1.webp" title="a restaurant called thai food near me" alt="a restaurant called 'thai food near me'" width="800" height="450"&gt;&lt;/a&gt;a restaurant attempts an sql injection attack.&lt;/p&gt;

&lt;h2&gt;
  
  
  making a &lt;code&gt;POINT&lt;/code&gt; in mysql
&lt;/h2&gt;

&lt;p&gt;mysql has a whole suite of functions and data types devoted to spatial data. the number of them is dizzying and the official documentation is almost criminally dense. fortunately, we can accomplish what we want to do using only a small subset. we'll start with &lt;code&gt;POINT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;POINT&lt;/code&gt; is both a datatype and a function that returns that data type. if we wanted to define a point on a good, old-fashioned x/y graph, we can do it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;POINT&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="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the result of that query is our x/y point in a value of type &lt;code&gt;POINT&lt;/code&gt;. mysql stores &lt;code&gt;POINT&lt;/code&gt; in a binary format, so the result of our select is not particularly useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;POINT&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="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;POINT&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="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                                          &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------------------------------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;x00000000010100000000000000000008400000000000001C40&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------------------------------------------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;mysql addresses this by providing two convenience functions to extract the &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; values from a point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ST_X()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_Y()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;they both accept a &lt;code&gt;POINT&lt;/code&gt; value as an argument. for instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ST_X&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POINT&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="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_Y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POINT&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="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------+------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------+------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;------+------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;because &lt;code&gt;POINT&lt;/code&gt; is a data type, we can use it in table definitions, just like we would &lt;code&gt;INT&lt;/code&gt; or &lt;code&gt;VARCHAR&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`some_coords`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`coords`&lt;/span&gt; &lt;span class="n"&gt;POINT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we have a column of type &lt;code&gt;POINT&lt;/code&gt;, only &lt;code&gt;POINT&lt;/code&gt; data can go in there. we'll cover this more later.&lt;/p&gt;

&lt;h2&gt;
  
  
  a short digression on x, y, maps and a lack of standards
&lt;/h2&gt;

&lt;p&gt;we all learned in school how to plot points on blue-lined graph paper using the x-axis, which runs horizontally, and the y-axis, which is vertical. points were defined as &lt;code&gt;x/y&lt;/code&gt;; horizontal first, vertical second. this is the way it has been forever, and everyone agrees on it.&lt;/p&gt;

&lt;p&gt;except the people who make maps.&lt;/p&gt;

&lt;p&gt;the people who make maps define points as latitude/longitude. latitude, of course, runs north-south, which is vertical on a map. longitude, the east-west axis, is horizontal. the map people, in essence, decided to use &lt;code&gt;y/x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;obviously, this creates problems. let's look at what happens when we create a &lt;code&gt;POINT&lt;/code&gt; representing the location of the ship &amp;amp; anchor pub in central calgary, alberta (where i have been known, on occasion, to blog from)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ST_X&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;037913&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;073277&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_Y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;POINT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;037913&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;073277&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+-------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+-------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;037913&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;073277&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+-------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;latitude and longitude are mixed up; our pub is in the wrong place. what's worse, since the maximum value for latitude is 90, we've put the ship &amp;amp; anchor somewhere out in space. not good.&lt;/p&gt;

&lt;p&gt;mysql addresses this issue by providing two functions to replace &lt;code&gt;ST_X()&lt;/code&gt; and &lt;code&gt;ST_Y()&lt;/code&gt; when using points on a map or globe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ST_Latitude()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ST_Longitude()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this is good stuff, except, if we try to use them in our above query, we get this error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR 3726 (22S00): Function st_latitude is only defined for geographic spatial reference systems, but one of its arguments is in SRID 0, which is not geographic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this error looks daunting (what the hell is &lt;code&gt;SRID 0&lt;/code&gt;?), but all mysql is telling us here is that the &lt;code&gt;POINT&lt;/code&gt;s we're using haven't been defined as being &lt;em&gt;map points&lt;/em&gt;. they're just regular, old bags of x's and y's. &lt;/p&gt;

&lt;p&gt;we'll go over &lt;code&gt;SRID&lt;/code&gt;s and &lt;code&gt;SRS&lt;/code&gt;s later on. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--OLkHg-44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/meme_location3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--OLkHg-44--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/meme_location3.png" title="the it's always been ohio meme" alt="the i'ts always been ohio meme" width="800" height="450"&gt;&lt;/a&gt;latitude and longitude has always been y/x.&lt;/p&gt;

&lt;h2&gt;
  
  
  a better way to make a &lt;code&gt;POINT&lt;/code&gt;: well-known text
&lt;/h2&gt;

&lt;p&gt;so far, we've selected a value of type &lt;code&gt;POINT&lt;/code&gt; by using the function &lt;code&gt;POINT()&lt;/code&gt;. this works fine for now, but there is a better, more-flexible way to do this that will make working with &lt;code&gt;POINT&lt;/code&gt;s and &lt;code&gt;POLYGON&lt;/code&gt;s easier when things start getting more complicated.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;ST_GeomFromText()&lt;/code&gt; function takes as an argument a text expression (a string) of the geometric object we want to create (a &lt;code&gt;POINT&lt;/code&gt; in this case), and returns a value of the correct type.&lt;/p&gt;

&lt;p&gt;these text expressions are formatted using a syntax called &lt;a href="https://en.wikipedia.org/wiki/Well-known_text_representation_of_geometry" rel="noopener noreferrer"&gt;"well-known text"&lt;/a&gt;. the format is, basically, the name of the geometric object you want to create (ie. &lt;code&gt;POINT&lt;/code&gt;) and the coordinates that define it. let's look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.037913 -114.073277)'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;this looks very straightforward, but there's a glaring question: where is the comma separating the arguments in our &lt;code&gt;POINT&lt;/code&gt; call?&lt;/p&gt;

&lt;p&gt;the answer is that the well-known text here &lt;em&gt;isn't&lt;/em&gt; a call to the function &lt;code&gt;POINT()&lt;/code&gt;, it's a definition of the &lt;em&gt;data type&lt;/em&gt; &lt;code&gt;POINT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;back at the beginning of this discussion, we went over how &lt;code&gt;POINT&lt;/code&gt; is both a function and a datatype. when we use &lt;code&gt;POINT()&lt;/code&gt; as a function, the coordinates are arguments that are separated by a comma. when we define a value using &lt;code&gt;POINT&lt;/code&gt; as a type, the coordinates do not take a comma. &lt;/p&gt;

&lt;p&gt;we can use &lt;code&gt;ST_GeomFromText()&lt;/code&gt; to create any sort of geometric object that's defined in the well-known text. there aren't many of these, and we'll be sticking in this post to &lt;code&gt;POINT&lt;/code&gt;s and &lt;code&gt;POLYGON&lt;/code&gt;s (which include things like squares and triangles).&lt;/p&gt;

&lt;h2&gt;
  
  
  spatial reference systems: not all points are the same
&lt;/h2&gt;

&lt;p&gt;on my desk i have a small chess board where i occasionally work through &lt;a href="https://gameknot.com/list_annotated.pl?u=all" rel="noopener noreferrer"&gt;annotated games&lt;/a&gt;. it's my idea of "fun". that chess board is a coordinates system. i also have a large, widescreen computer monitor on my desk. it's a coordinate system as well.&lt;/p&gt;

&lt;p&gt;however, just because my chess board and monitor are both coordinate systems doesn't mean that the coordinates from one can be transferred to the other. the x/y position of my white bishop is meaningless on my monitor; that x/y point only has meaning &lt;em&gt;in the context of&lt;/em&gt; the chess board.&lt;/p&gt;

&lt;p&gt;a context defines things like the origin points, axes, units of measurement and the like. useful stuff that helps us make sense of what a coordinate actually means.&lt;/p&gt;

&lt;p&gt;when it comes to plotting points and lines and polygons on the surface of the earth, that context is called a &lt;a href="https://en.wikipedia.org/wiki/Spatial_reference_system" rel="noopener noreferrer"&gt;'spatial reference system'&lt;/a&gt;, or SRS.&lt;/p&gt;

&lt;p&gt;there are a lot of different SRSs. a &lt;em&gt;lot&lt;/em&gt;. some of them treat the earth as a sphere, others as a projected flat map. some cover the entire planet, many more only deal with a sub region, like a country. some  include a z axis from the center of the earth, most don't.&lt;/p&gt;

&lt;p&gt;if we want to peruse all the different SRSs that mysql has, we can run this select:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;INFORMATION_SCHEMA&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ST_SPATIAL_REFERENCE_SYSTEMS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;there are about five thousand of them.&lt;/p&gt;

&lt;p&gt;fortunately, we don't need to read through all of these to choose one. we're just going to use 4326, a global, unprojected coordinate system that (just about) everybody uses.&lt;/p&gt;

&lt;p&gt;that 4326 number is the id of the SRS. it's called, unsurprisingly, an &lt;code&gt;SRID&lt;/code&gt;. if we remember back to when we tried to call the &lt;code&gt;ST_Latitude()&lt;/code&gt; function on the &lt;code&gt;POINT&lt;/code&gt; we made, we got the error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR 3726 (22S00): Function st_latitude is only defined for geographic spatial reference systems, but one of its arguments is in SRID 0, which is not geographic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now that we have more of an understanding about SRSs, we can see that here mysql is complaining that we are asking for the latitude, but the SRS of our &lt;code&gt;POINT&lt;/code&gt; isn't one that uses latitude and longitude.  the SRS we are using, according to the error message, is &lt;code&gt;SRID&lt;/code&gt; 0. &lt;/p&gt;

&lt;p&gt;&lt;code&gt;SRID&lt;/code&gt; 0 is just a 'flat, cartesian plane' with no units. think of it as a sheet of that blue-lined graph paper from math class stretching off into infinity in all directions. this is a great SRS for some applications, but is not very meaningful for using latitude and longitude to map places on a spherical earth. &lt;code&gt;SRID&lt;/code&gt; 0 is the default SRS that mysql assigns to &lt;code&gt;POINT&lt;/code&gt;s (and other shapes) when one is not specified.&lt;/p&gt;

&lt;p&gt;by comparison, the 4326 SRS is specifically designed for global mapping. it treats the surface of the earth as an ellipsoid, uses degrees for measurement and defines the axes as the equator and prime meridian. exactly what we want. 4326 is, in turn, based on a big set of data about the earth called the world geodetic system 1984, or WSG84, that was compiled in that year in an effort to unify and standardize the mishmash of national mapping data.  if you're one of those 'further reading' types, you can read over a &lt;a href="https://pointonenav.com/news/world-geodetic-system/" rel="noopener noreferrer"&gt;detailed explainer on &lt;code&gt;SRID&lt;/code&gt; 4326 here&lt;/a&gt; or peruse the surprisingly-entertaining &lt;a href="https://en.wikipedia.org/wiki/World_Geodetic_System" rel="noopener noreferrer"&gt;wikipedia entry on WSG84&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ixwG92pN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/meme_location2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ixwG92pN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/meme_location2.jpg" title="the solar system where every planet is round except the flat earth" alt="the solar system where every planet is round except the flat earth" width="800" height="468"&gt;&lt;/a&gt;a developer accidentally uses &lt;code&gt;SRID 0&lt;/code&gt; for their geolocation select.&lt;/p&gt;

&lt;h2&gt;
  
  
  actually using &lt;code&gt;SRID&lt;/code&gt; 4326
&lt;/h2&gt;

&lt;p&gt;using &lt;code&gt;SRID&lt;/code&gt; 4326 as our SRS when creating a &lt;code&gt;POINT&lt;/code&gt; is pretty straightforward; we just add the &lt;code&gt;SRID&lt;/code&gt; as a second argument to &lt;code&gt;ST_GeomFromText()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.037913 -114.073277)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and, just like that, our x/y values are now treated as longitude and latitude coordinates on earth. let's try &lt;code&gt;ST_Latitude()&lt;/code&gt; again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ST_Latitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.037913 -114.073277)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;037913&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;exactly what we wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  creating a table for our &lt;code&gt;POINT&lt;/code&gt;s
&lt;/h2&gt;

&lt;p&gt;selecting geometric data like &lt;code&gt;POINT&lt;/code&gt;s (or &lt;code&gt;POLYGON&lt;/code&gt;s or &lt;code&gt;LINESTRING&lt;/code&gt;s) created using literal data is fine, but what we probably want to do is persist that data in a table so we can use it later. let's do that. we'll start with creating our table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`calgary`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="nb"&gt;unsigned&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`name`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`coords`&lt;/span&gt; &lt;span class="n"&gt;POINT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here, we've defined a pretty standard-looking table of notable locations in the city of &lt;a href="https://www.cbc.ca/news/canada/calgary/calgary-third-most-liveable-city-world-economist-1.6499827" rel="noopener noreferrer"&gt;calgary, alberta&lt;/a&gt;. the interesting column here is &lt;code&gt;coords&lt;/code&gt;, which is defined as a &lt;code&gt;POINT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;that &lt;code&gt;POINT&lt;/code&gt; doesn't have an SRS associated with it. this means that on every insert, we will have to define the &lt;code&gt;SRID&lt;/code&gt; we are using for our point. this is very flexible, but if we want to we can add the SRS to the column definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="nv"&gt;`calgary`&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;`id`&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="nb"&gt;unsigned&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`name`&lt;/span&gt; &lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;`coords`&lt;/span&gt; &lt;span class="n"&gt;POINT&lt;/span&gt; &lt;span class="n"&gt;SRID&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;`id`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;ENGINE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;InnoDB&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;CHARSET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4&lt;/span&gt; &lt;span class="k"&gt;COLLATE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;utf8mb4_unicode_ci&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;by defining our &lt;code&gt;coords&lt;/code&gt; column as &lt;code&gt;POINT SRID 4326&lt;/code&gt; we are enforcing that any &lt;code&gt;POINT&lt;/code&gt; in that column &lt;em&gt;must&lt;/em&gt; be of &lt;code&gt;SRID&lt;/code&gt; 4326. if we try to insert a point that has a different &lt;code&gt;SRID&lt;/code&gt;, mysql will complain with an error like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR 3643 (HY000): The SRID of the geometry does not match the SRID of the column 'coords'. The SRID of the geometry is 0, but the SRID of the column is 4326. Consider changing the SRID of the geometry or the SRID property of the column.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;for all the examples going forward, we will be using a table with a &lt;code&gt;coords&lt;/code&gt; column that does not define the &lt;code&gt;SRID&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;now that we have a table, we can insert some rows. we'll add a list of calgary landmarks.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'calgary tower'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.044270 -114.062019)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'peace bridge'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.0542 -114.0793)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'saddledome'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.0374 -114.0519)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'national music centre'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.04250 -114.06083)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'baitun nur mosque'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.101743 -113.972039)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'olympic oval'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.07694 -114.13556)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'heritage park'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(50.98528 -114.10833)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'international avenue'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.03778 -113.98167)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'fort calgary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.045139 -114.045778)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;there's a lot of things to see in calgary!&lt;/p&gt;

&lt;p&gt;in these insert statements, we create our point using &lt;code&gt;ST_GeomFromText()&lt;/code&gt; and set the &lt;code&gt;SRID&lt;/code&gt; as 4326 like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.0542 -114.0793)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we can then select this data back, getting the latitude and longitude of each location with &lt;code&gt;ST_latitude()&lt;/code&gt; and &lt;code&gt;ST_longitude()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;  &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ST_Latitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ST_Longitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;    &lt;span class="n"&gt;calgary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----+-----------------------+-----------+-------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;                  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;   &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----+-----------------------+-----------+-------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="n"&gt;tower&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;04427&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;062019&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;peace&lt;/span&gt; &lt;span class="n"&gt;bridge&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0542&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0793&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;saddledome&lt;/span&gt;            &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0374&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0519&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="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;national&lt;/span&gt; &lt;span class="n"&gt;music&lt;/span&gt; &lt;span class="n"&gt;centre&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0425&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;06083&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;baitun&lt;/span&gt; &lt;span class="n"&gt;nur&lt;/span&gt; &lt;span class="n"&gt;mosque&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;101743&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;972039&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;olympic&lt;/span&gt; &lt;span class="n"&gt;oval&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;07694&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;13556&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;heritage&lt;/span&gt; &lt;span class="n"&gt;park&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;98528&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10833&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;international&lt;/span&gt; &lt;span class="n"&gt;avenue&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;03778&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;113&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;98167&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;fort&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;045139&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;114&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;045778&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;----+-----------------------+-----------+-------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  at last, calculating distance
&lt;/h2&gt;

&lt;p&gt;so far, we've made some spatial &lt;code&gt;POINT&lt;/code&gt;s and assigned them to &lt;code&gt;SRID&lt;/code&gt; 4326 so we can actually make sense of them as latitude and longitude. it's finally time to focus on what we really want to do: getting the distance between two points.&lt;/p&gt;

&lt;p&gt;to do this, we're going to use mysql's &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/spatial-convenience-functions.html#function_st-distance-sphere" rel="noopener noreferrer"&gt;&lt;code&gt;ST_Distance_Sphere()&lt;/code&gt;&lt;/a&gt; function. &lt;/p&gt;

&lt;p&gt;as one would expect, &lt;code&gt;ST_Distance_Sphere()&lt;/code&gt; calculates the distance between two points, provided as arguments to the function, on a sphere. the distance returned will always be the &lt;em&gt;shortest&lt;/em&gt; one (since, on a sphere, we can always go the opposite direction and travel further to get to the same place). the unit of measurement is meters.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ST_Distance_Sphere()&lt;/code&gt; takes an optional third argument: the radius of the sphere. if we do not set this argument, the value 6,370,986 meters is used. that's the radius of the earth, and is the value we almost certainly want to use.&lt;/p&gt;

&lt;p&gt;knowing all that, an example select would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ST_Distance_Sphere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POINT(51.037913 -114.073277)'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;distance_meters&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------------------+--------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;                  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;distance_meters&lt;/span&gt;    &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------------------+--------------------+&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt; &lt;span class="n"&gt;tower&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1057&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9217149476015&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;peace&lt;/span&gt; &lt;span class="n"&gt;bridge&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;1859&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;336539883446&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;saddledome&lt;/span&gt;            &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1495&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7790780297603&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;national&lt;/span&gt; &lt;span class="n"&gt;music&lt;/span&gt; &lt;span class="n"&gt;centre&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;1008&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7085120625501&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;baitun&lt;/span&gt; &lt;span class="n"&gt;nur&lt;/span&gt; &lt;span class="n"&gt;mosque&lt;/span&gt;     &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;10020&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;62038333001&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;olympic&lt;/span&gt; &lt;span class="n"&gt;oval&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="mi"&gt;6146&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6116509785015&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;heritage&lt;/span&gt; &lt;span class="n"&gt;park&lt;/span&gt;         &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;6345&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;541637300453&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;international&lt;/span&gt; &lt;span class="n"&gt;avenue&lt;/span&gt;  &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;6405&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;199613693066&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;fort&lt;/span&gt; &lt;span class="n"&gt;calgary&lt;/span&gt;          &lt;span class="o"&gt;|&lt;/span&gt;  &lt;span class="mi"&gt;2083&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;730747912871&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="c1"&gt;-----------------------+--------------------+&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here we can see that we passed two &lt;code&gt;POINT&lt;/code&gt; arguments to &lt;code&gt;ST_Distance_Sphere()&lt;/code&gt;. The first is one we constructed from literal values using &lt;code&gt;ST_GeomFromText()&lt;/code&gt;. it's the location of the ship &amp;amp; anchor pub in central calgary, where i promise i am not writing this post. the second argument is our &lt;code&gt;coords&lt;/code&gt; column.&lt;/p&gt;

&lt;p&gt;the result is the distance from our starting &lt;code&gt;POINT&lt;/code&gt;, the ship &amp;amp; anchor, to all the &lt;code&gt;POINT&lt;/code&gt;s in our table, in meters.&lt;/p&gt;

&lt;p&gt;from here, building 'near me'  functionality is just a matter of applying a &lt;code&gt;WHERE&lt;/code&gt; or &lt;code&gt;ORDER BY&lt;/code&gt; clause.&lt;/p&gt;

&lt;h2&gt;
  
  
  going regional: finding points inside a square (or any shape)
&lt;/h2&gt;

&lt;p&gt;perhaps, instead of a basic 'near me' feature, we want our users to be able to draw a square on a map and say "show me all the calgary landmarks in here."&lt;/p&gt;

&lt;p&gt;to do this, the fist step we need to take is defining a square.&lt;/p&gt;

&lt;h3&gt;
  
  
  creating a square
&lt;/h3&gt;

&lt;p&gt;a square is a type of polygon, and mysql provides a &lt;code&gt;POLYGON&lt;/code&gt; data type that we can use to describe a square (or any shape). &lt;code&gt;POLYGON&lt;/code&gt;s are defined by a set of coordinates that identify the corners of the shape. this means, to create a square, we provide &lt;code&gt;POLYGON&lt;/code&gt; with five coordinate sets.&lt;/p&gt;

&lt;p&gt;wait, five? don't we mean four? a square has four corners, after all. &lt;/p&gt;

&lt;p&gt;the important thing to note here is that a polygon &lt;em&gt;must be closed&lt;/em&gt;. this means that the first coordinate set and the last coordinate set must be the same. it completes the shape by going back to the beginning. the result is that a square is defined has having five sets of coordinates. to illustrate, let's look at this glorious ascii diagram that shows the five coordinates that create a square.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1/5 ---- 4
  |      |    
  |      |    
  2 ---- 3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with that in mind, we can create a square of latitude and longitude values. the example we'll be using is &lt;a href="http://bboxfinder.com/#51.028008,-114.094391,51.053913,-114.037743" rel="noopener noreferrer"&gt;this square covering most of downtown calgary&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zbNpRLNb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/image_location4-1024x631.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zbNpRLNb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://gbh.fruitbat.io/wp-content/uploads/2024/09/image_location4-1024x631.png" title="a map of downtown calgary with a square covering most of it" alt="a map of downtown calgary with a square drawn around most of it" width="800" height="493"&gt;&lt;/a&gt;a square covering most of downtown calgary.&lt;/p&gt;

&lt;p&gt;to select this as a &lt;code&gt;POLYGON&lt;/code&gt; in mysql, we would do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POLYGON( (  51.053913 -114.094391, 51.028008 -114.094391, 51.028008 -114.037743, 51.053913 -114.037743, 51.053913 -114.094391) )'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;given our experience creating a &lt;code&gt;POINT&lt;/code&gt;, this should be fairly straightforward. the only difference is that instead of passing one coordinate set to &lt;code&gt;POINT&lt;/code&gt;, we pass five to &lt;code&gt;POLYGON&lt;/code&gt;. the result is a geometric shape, stored in a binary format, that we can use for comparisons against &lt;code&gt;POINTS&lt;/code&gt; or, even, other &lt;code&gt;POLYGON&lt;/code&gt;s. &lt;/p&gt;

&lt;h3&gt;
  
  
  finding &lt;code&gt;POINT&lt;/code&gt;s 'within' a square
&lt;/h3&gt;

&lt;p&gt;we now have a &lt;code&gt;POLYGON&lt;/code&gt; defined from some literal values, and a table full of &lt;code&gt;POINT&lt;/code&gt;s, all that's left is to find out which &lt;code&gt;POINT&lt;/code&gt;s in our table are inside our &lt;code&gt;POLYGON&lt;/code&gt;. we can do this with the mysql function &lt;code&gt;ST_Within()&lt;/code&gt;. here's an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;  &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ST_Latitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;latitude&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ST_Longitude&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;longitude&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;    &lt;span class="n"&gt;calgary&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;   &lt;span class="n"&gt;ST_Within&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;coords&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;ST_GeomFromText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'POLYGON( (  51.053913 -114.094391, 51.028008 -114.094391, 51.028008 -114.037743, 51.053913 -114.037743, 51.053913 -114.094391) )'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4326&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;we can see that &lt;code&gt;ST_Within()&lt;/code&gt; takes two arguments: a &lt;code&gt;POINT&lt;/code&gt;, and a &lt;code&gt;POLYGON&lt;/code&gt;. if the &lt;code&gt;POINT&lt;/code&gt; is 'within' the &lt;code&gt;POLYGON&lt;/code&gt;, &lt;code&gt;ST_Within()&lt;/code&gt; returns 1. if it isn't, we get a 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  conclusion
&lt;/h2&gt;

&lt;p&gt;once we have an understanding of how to create &lt;code&gt;POINT&lt;/code&gt;s and &lt;code&gt;POLYGON&lt;/code&gt;s and use &lt;code&gt;ST_Distance_Sphere()&lt;/code&gt; and &lt;code&gt;ST_Within()&lt;/code&gt; we can combine and extrapolate them to get more complex data, like "the closest daycare in a given school district" or "all the burrito busses on this side of the river" or, even, answer the question that has driven so many of the great minds in computer science: "where is a thai restaurant near me"?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2024/09/19/mysql-thai-food-near-me-or-doing-geo-distance-calculations-in-your-database/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>mysql</category>
    </item>
    <item>
      <title>nginx: putting your site in ‘downtime’ for everyone except you</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Fri, 06 Sep 2024 18:09:47 +0000</pubDate>
      <link>https://dev.to/gbhorwood/nginx-putting-your-site-in-downtime-for-everyone-except-you-34g0</link>
      <guid>https://dev.to/gbhorwood/nginx-putting-your-site-in-downtime-for-everyone-except-you-34g0</guid>
      <description>&lt;p&gt;we've all been in that less-than-ideal situation of something going horribly awry in production and having to put the site into downtime while we fix it. that "scheduled maintenance"[sic.] page is important because it keeps users from seeing our glaring error, but it makes investigating or fixing production more difficult because, well, the site &lt;em&gt;is in downtime&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;in this post, we're going to go over a couple of ways we can use nginx to show different content to different users based on their ip address; configuring our web server so that everyone in the world gets our downtime message, except us. we get to see site as normal, allowing us to engage in the not-quite-best-practice of debugging in production.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_downtimeip1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_downtimeip1.jpg" title="the three dragons meme" alt="three dragons meme" width="680" height="544"&gt;&lt;/a&gt;two users (left) are served the well-crafted downtime page, while the developer (right) sees the real site.&lt;/p&gt;

&lt;h2&gt;
  
  
  the flyover
&lt;/h2&gt;

&lt;p&gt;we're going to go over four nginx configurations. some of them are very similar, and are presented to show how we can combine and build on these strategies to tailor them to our needs. they configurations are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;using &lt;code&gt;allow&lt;/code&gt;/&lt;code&gt;deny&lt;/code&gt; to show unapproved ip addresses a 403 with an optional custom error page&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;if&lt;/code&gt; to return a downtime html string to everyone except an approved ip address&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;if&lt;/code&gt; to return a downtime html file&lt;/li&gt;
&lt;li&gt;leveraging &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;if&lt;/code&gt; to allow multiple ip addresses access to the site; downtime for everyone else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;tl;dr&lt;/strong&gt;: if you're looking for the solution you can just copy-paste and modify, then the last one, "leveraging &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;if&lt;/code&gt;", is probably what you want.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  serving 403s with &lt;code&gt;allow&lt;/code&gt; and &lt;code&gt;deny&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;nginx has a lot of features built in to restrict and permit access. we can use it to throttle bandwidth or &lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-tcp/" rel="noopener noreferrer"&gt;limit the number of connections per address&lt;/a&gt; to mitigate ddos attacks, but here we're going to look at the &lt;code&gt;allow&lt;/code&gt; and &lt;code&gt;deny&lt;/code&gt; directives.&lt;/p&gt;

&lt;p&gt;in an nginx configuration we can, in a &lt;code&gt;location&lt;/code&gt; block, set an arbitrary number of ip addresses to be either allowed or denied. requests from allowed addresses proceed to be handled as normal. denied addresses are served an &lt;code&gt;HTTP 403&lt;/code&gt;. let's look at a complete, if somewhat terse, config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;gbhorwood.test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/gbhorwood/test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;# allow local ip&lt;/span&gt;
       &lt;span class="kn"&gt;allow&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="c1"&gt;# allow some other ip&lt;/span&gt;
       &lt;span class="kn"&gt;allow&lt;/span&gt; &lt;span class="mf"&gt;151.101&lt;/span&gt;&lt;span class="s"&gt;.2.217&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="c1"&gt;# 403 is default behaviour&lt;/span&gt;
       &lt;span class="kn"&gt;deny&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/favicon.ico&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/robots.txt&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;sendfile&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;here we see that in our &lt;code&gt;location /&lt;/code&gt; block we &lt;code&gt;allow&lt;/code&gt; two addresses. all other addresses are handled by the &lt;code&gt;deny all&lt;/code&gt; directive.&lt;/p&gt;

&lt;p&gt;the important thing to note here is that &lt;em&gt;order is important&lt;/em&gt;. nginx tests the ip address starting at the top and handles the first directive that matches. for instance, if we did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;allow&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;deny&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;a visitor from localhost would be allowed because nginx stops testing the ip at the first entry for &lt;code&gt;127.0.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;likewise, if we configured our location block with the reverse:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;deny&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;allow&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;localhost would be blocked.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;allow&lt;/code&gt; and &lt;code&gt;deny&lt;/code&gt; directives are not limited to single ip addresses; we can also use &lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/controlling-access-proxied-tcp/" rel="noopener noreferrer"&gt;cidr&lt;/a&gt; blocks. if we wanted to allow only the ips on our local network, for instance, we could do so like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;allow&lt;/span&gt; &lt;span class="mf"&gt;192.168&lt;/span&gt;&lt;span class="s"&gt;.1.1/24&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;deny&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  serving a custom downtime file instead of just 403
&lt;/h3&gt;

&lt;p&gt;of course, throwing an &lt;code&gt;HTTP 403&lt;/code&gt; to all our users probably isn't what we want. the point of a downtime page, after all, is to prevent people from seeing our terrible errors, not to serve them different ones.&lt;/p&gt;

&lt;p&gt;we can fix this by setting a custom html error page for 403 and use that for our downtime message.&lt;/p&gt;

&lt;p&gt;we'll start by creating our custom downtime page. we'll call it &lt;code&gt;downtime.html&lt;/code&gt; and, for this example, we'll put it in the &lt;code&gt;/tmp&lt;/code&gt; directory. you might (probably!) want to choose a different location. the contents of our downtime file are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;h1&amp;gt;&lt;/span&gt;downtime&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&lt;/span&gt;
this site is currently down for "scheduled" maintenance.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;once we have our downtime html file, we can configure nginx so that instead of showing the standard 403 error page, we show our &lt;code&gt;downtime.html&lt;/code&gt; file instead. here's the full config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;gbhorwood.test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/gbhorwood/test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# new stuff&lt;/span&gt;
    &lt;span class="kn"&gt;error_page&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="n"&gt;/downtime.html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/downtime.html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="n"&gt;/tmp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# end new stuff&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;# allow local ip&lt;/span&gt;
       &lt;span class="kn"&gt;allow&lt;/span&gt; &lt;span class="mf"&gt;127.0&lt;/span&gt;&lt;span class="s"&gt;.0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="c1"&gt;# allow some other ip&lt;/span&gt;
       &lt;span class="kn"&gt;allow&lt;/span&gt; &lt;span class="mf"&gt;151.101&lt;/span&gt;&lt;span class="s"&gt;.2.217&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

       &lt;span class="c1"&gt;# 403 is default behaviour&lt;/span&gt;
       &lt;span class="kn"&gt;deny&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/favicon.ico&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/robots.txt&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;sendfile&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;looking in the 'new stuff' block, we can see that we are using two directives to serve our custom downtime html file.&lt;/p&gt;

&lt;p&gt;the first directive is a call to &lt;a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#error_page" rel="noopener noreferrer"&gt;&lt;code&gt;error_page&lt;/code&gt;&lt;/a&gt;. this call takes two arguments: the HTTP code we want to assign a custom html file to, and the name of the custom html file. very straightforward.&lt;/p&gt;

&lt;p&gt;the next directive is a &lt;code&gt;location&lt;/code&gt; block for our downtime html file. we do this so that we can set the &lt;code&gt;root&lt;/code&gt; directory that nginx will look in for our &lt;code&gt;downtime.html&lt;/code&gt;. this allows us to keep our page out of our main repository and ensures that it will always be served even if we accidentally nuke every file in web page's root (these things do happen).&lt;/p&gt;

&lt;p&gt;of course, hijacking &lt;code&gt;HTTP 403&lt;/code&gt; for our own purposes is a bit of a kludge. there are more elegant ways for us to serve our downtime file. let's look at those.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_downtimeip2-1024x683.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_downtimeip2-1024x683.jpg" title="the distracted boyfriend meme" alt="distracted boyfriend meme" width="800" height="533"&gt;&lt;/a&gt;standard http error handling looks askance at a developer thinking of hijacking 403 for a downtime page&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  serving a string of 'downtime' html using &lt;code&gt;if()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;if we want to show a string of html for our downtime message to every ip address except our own, we can do that using &lt;a href="https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if" rel="noopener noreferrer"&gt;nginx's &lt;code&gt;if&lt;/code&gt; statement&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;let's look at a configuration that does that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;gbhorwood.test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/gbhorwood/test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;# show downtime html string if not whitelisted ip&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

            &lt;span class="kn"&gt;add_header&lt;/span&gt; &lt;span class="s"&gt;Content-Type&lt;/span&gt; &lt;span class="nc"&gt;text/html&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kn"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Temporarily&lt;/span&gt; &lt;span class="s"&gt;offline&lt;/span&gt; &lt;span class="s"&gt;for&lt;/span&gt; &lt;span class="s"&gt;scheduled&lt;/span&gt; &lt;span class="s"&gt;maintenance&lt;/span&gt; &lt;span class="s"&gt;and&lt;/span&gt; &lt;span class="s"&gt;upgrades&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="p"&gt;}&lt;/span&gt;


        &lt;span class="kn"&gt;try_files&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt; &lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="n"&gt;/index.php?&lt;/span&gt;&lt;span class="nv"&gt;$query_string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/favicon.ico&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/robots.txt&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;sendfile&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;the interesting stuff here is the &lt;code&gt;if&lt;/code&gt; statement inside the &lt;code&gt;location&lt;/code&gt; directive. nginx keeps the ip address of the visitor in a variable called &lt;code&gt;$remote_addr&lt;/code&gt;. we can test that against our allowed ip using a very familiar-looking &lt;code&gt;if&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;if the visitor's ip address is not allowed, we return a string of html. doing this is a two-step process: sending the &lt;code&gt;Content-Type&lt;/code&gt; header with the &lt;code&gt;add_header&lt;/code&gt; directive, and returning a string with &lt;code&gt;return&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;note that &lt;code&gt;return&lt;/code&gt; takes two arguments. the first is the http status code, &lt;code&gt;200&lt;/code&gt; is probably what we want here. the second argument is our string of html.&lt;br&gt;
&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  serving a file of 'downtime' html using &lt;code&gt;if()&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;maybe returning just one string of html for our downtime page isn't enough. maybe we want a tonne of css and some animated gifs; y'know, "rich content".&lt;/p&gt;

&lt;p&gt;we can do that by modifying the configuration above so that our &lt;code&gt;if&lt;/code&gt; statement serves a file instead of just returning a string. it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;gbhorwood.test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/gbhorwood/test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;# show downtime html file if not whitelisted ip&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remote_addr&lt;/span&gt; &lt;span class="s"&gt;!=&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;rewrite&lt;/span&gt; &lt;span class="s"&gt;^&lt;/span&gt; &lt;span class="n"&gt;/static/down.html&lt;/span&gt; &lt;span class="s"&gt;break&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="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/favicon.ico&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/robots.txt&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;sendfile&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;again, here, we're testing the visitor's ip address in &lt;code&gt;$remote_addr&lt;/code&gt; against our allowed ip in an &lt;code&gt;if&lt;/code&gt; statement. the difference is that, instead of returning a header and a string, we are using nginx's &lt;a href="https://nginx.org/en/docs/http/ngx_http_rewrite_module.html" rel="noopener noreferrer"&gt;&lt;code&gt;rewrite&lt;/code&gt;&lt;/a&gt; directive to serve a file. since the rewrite directive ends with break, nginx jumps to the end of our &lt;code&gt;location&lt;/code&gt; block, and our downtime content is served.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  using &lt;code&gt;map&lt;/code&gt; to handle multiple ip addresses more sanely
&lt;/h2&gt;

&lt;p&gt;so far we've seen how to use &lt;code&gt;if&lt;/code&gt; to test for one allowed ip address but, ideally, we would like to have a solution that allowed us to whitelist multiple ips. we could certainly attempt to do this with a regular expression, but regexes, as powerful as they are, are difficult to write and harder to read and if you suddenly have to add a seventh address on short notice it's probably not going to be a great experience.&lt;/p&gt;

&lt;p&gt;we can avoid all that by using nginx's &lt;code&gt;map&lt;/code&gt; function to, well, &lt;em&gt;map&lt;/em&gt; ip addresses to boolean values. allowed ips get set to true, all other ip addresses are set to false, and then we use our &lt;code&gt;if&lt;/code&gt; to check that boolean. it's a pretty slick solution:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_downtimeip3-1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F09%2Fmeme_downtimeip3-1.png" title="xkcd i know regular expressions" alt="xkcd i know regular expressions" width="400" height="389"&gt;&lt;/a&gt;everybody stand back, i know how to use nginx’s map function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;map&lt;/span&gt; &lt;span class="nv"&gt;$remote_addr&lt;/span&gt; &lt;span class="nv"&gt;$allow_ip&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kn"&gt;default&lt;/span&gt;       &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;127.0.0.1&lt;/span&gt;     &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kn"&gt;151.101.2.217&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;gbhorwood.test&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;root&lt;/span&gt; &lt;span class="s"&gt;"/var/www/html/gbhorwood/test"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;index&lt;/span&gt; &lt;span class="s"&gt;index.html&lt;/span&gt; &lt;span class="s"&gt;index.htm&lt;/span&gt; &lt;span class="s"&gt;index.php&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;charset&lt;/span&gt; &lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;#  show downtime html file if not whitelisted ip&lt;/span&gt;
        &lt;span class="kn"&gt;if&lt;/span&gt; &lt;span class="s"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$allow_ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0")&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kn"&gt;rewrite&lt;/span&gt; &lt;span class="s"&gt;^&lt;/span&gt; &lt;span class="n"&gt;/static/down.html&lt;/span&gt; &lt;span class="s"&gt;break&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="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/favicon.ico&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;/robots.txt&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kn"&gt;log_not_found&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kn"&gt;access_log&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;error_log&lt;/span&gt;  &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;sendfile&lt;/span&gt; &lt;span class="no"&gt;off&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;100m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;80&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;at the very top of this configuration, &lt;em&gt;outside&lt;/em&gt; of the &lt;code&gt;server&lt;/code&gt; block, we are calling &lt;code&gt;map&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;the &lt;code&gt;map&lt;/code&gt; function has three arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;the value to test&lt;/strong&gt;: this is &lt;code&gt;$remote_addr&lt;/code&gt;, nginx's internal variable that holds the ip address of the visitor. this is the value that we will be testing in the list of test cases below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the variable to set&lt;/strong&gt;: the variable that will hold the result of our tests. in this example, a boolean.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the list of test cases&lt;/strong&gt;: a list of tuples, essentially. if the left value matches the value to test, &lt;code&gt;$remot_addr&lt;/code&gt; in our example, then the right value is set in the variable &lt;code&gt;$allow_ip&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;in essence, what this call to &lt;code&gt;map&lt;/code&gt; does is look at the user's ip address held in &lt;code&gt;$remote_addr&lt;/code&gt; and then set the value of &lt;code&gt;$allowed_ip&lt;/code&gt;. it's essentially a &lt;code&gt;switch/case&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;once map has run, we can use the &lt;code&gt;$allowed_ip&lt;/code&gt; variable in our &lt;code&gt;if&lt;/code&gt; statement to serve our downtime html file to everyone in the world, except us.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2024/09/05/nginx-putting-your-site-in-downtime-for-everyone-except-you/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ngin</category>
      <category>linux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>php: concurrency with processes. pt. 2: interprocess communication with shmop</title>
      <dc:creator>grant horwood</dc:creator>
      <pubDate>Mon, 19 Aug 2024 16:32:12 +0000</pubDate>
      <link>https://dev.to/gbhorwood/php-concurrency-with-processes-pt-2-interprocess-communication-with-shmop-555k</link>
      <guid>https://dev.to/gbhorwood/php-concurrency-with-processes-pt-2-interprocess-communication-with-shmop-555k</guid>
      <description>&lt;p&gt;php isn't the sort of language where developers usually think about things like memory. we just sort of sling around variables and functions and let the internals figure out all that 'ram stuff' for us. let's change that.&lt;/p&gt;

&lt;p&gt;in the first part of this series, we built a php script that was able to run a number of tasks concurrently by forking child processes. it worked pretty well, but there was a glaring, unaddressed problem: there was no way for those child processes to send data back to the parent process.&lt;/p&gt;

&lt;p&gt;in this installment, we're going to solve that issue by using &lt;code&gt;shmop&lt;/code&gt;, php's "shared memory operations".&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F08%2Fmeme_shmop_1.jpg" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F08%2Fmeme_shmop_1.jpg" title="the aliens guy meme" alt="aliens guy meme"&gt;&lt;/a&gt;shmop!&lt;/p&gt;

&lt;h2&gt;
  
  
  contents
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;a very short and extremely optional explanation of shared memory&lt;/li&gt;
&lt;li&gt;a basic flyover of &lt;code&gt;shmop&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;an actual implementation&lt;/li&gt;
&lt;li&gt;opening a memory block with &lt;code&gt;shmop_open&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;shmop_write&lt;/code&gt; to... write&lt;/li&gt;
&lt;li&gt;reading memory with &lt;code&gt;shmop_read&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;using &lt;code&gt;shmop_delete&lt;/code&gt; to clean up&lt;/li&gt;
&lt;li&gt;handling errors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  a very short and extremely optional explanation of shared memory
&lt;/h2&gt;

&lt;p&gt;when a new process starts, the operating system assigns it a chunk of memory to use. processes can't read or write to memory that isn't their own because, well, that would be a security nightmare. perfectly reasonable.&lt;/p&gt;

&lt;p&gt;this creates an issue for us when dealing with the &lt;a href="https://gbh.fruitbat.io/2024/07/22/php-concurrency-with-processes-pt-1-using-pcntl_fork-for-fun-and-performance/" rel="noopener noreferrer"&gt;processes we created with &lt;code&gt;pcntl_fork&lt;/code&gt;&lt;/a&gt; in part one of this series, however, because it means there's no easy way for the child processes to communicate with each other or their  parent. child processes will get a copy of their parent's memory when they're created so all the variables assigned before the &lt;code&gt;fork&lt;/code&gt; are accessible, but any changes to these variables will be limited to the child process. different memory and all that. if we want the child to be able to write to a variable that the parent process can read, we have a problem.&lt;/p&gt;

&lt;p&gt;there are a number of solutions for this, all grouped under the general category of 'inter process communications' or ipc. the one we're going to use for our php script is 'shared memory'.&lt;/p&gt;

&lt;p&gt;as the name implies, shared memory is a block of memory that an arbitrary number of processes can access. shared memory blocks are identified by a (hopefully) unique key. any process that knows what the key is can access that memory block. this makes it possible for a child processes to report back to their parent process; the child will write data to a shared memory block and, after it quits, the parent will read the shared data. it's a borderline elegant solution.&lt;/p&gt;

&lt;p&gt;of course, there are a few footguns we will need to avoid when doing this: we will need to ensure that the key that identifies a shared memory block is unique, and we will need to enforce that shared memory communication only goes one way to avoid multiple processes all trying to write to the same block at the same time and causing a mess. we'll cover all this in the implementation.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  a basic flyover of &lt;code&gt;shmop&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;php has a rich and robust api for dealing with shared memory. the manual states "Shmop is an easy to use set of functions that allows PHP to read, write, create and delete Unix shared memory segments", and... it's not wrong.&lt;/p&gt;

&lt;p&gt;let's look at the core steps for using shared memory:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;create a unique key&lt;/strong&gt;: all shared memory is identified by a key. any process that knows what the key of a shared memory block is can access it. traditionally, this key is created by generating data from the filesystem (ie. a value built from the &lt;a href="https://linuxhandbook.com/inode-linux/" rel="noopener noreferrer"&gt;inode&lt;/a&gt; of an existing file) because the filesystem is something all processes have in common. we will use &lt;a href="https://www.php.net/manual/en/function.ftok.php" rel="noopener noreferrer"&gt;&lt;code&gt;ftok&lt;/code&gt;&lt;/a&gt; for this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;assign a memory block using the key&lt;/strong&gt;: a process can use the key of a shared memory block to 'open' it using &lt;a href="https://www.php.net/manual/en/function.shmop-open.php" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_open&lt;/code&gt;&lt;/a&gt;. if the shared memory block does not exist, this creates it. the return value from the open function is pointer that can be used for reading and writing. if you've ever used &lt;code&gt;fopen&lt;/code&gt; and &lt;code&gt;fwrite&lt;/code&gt; before, this process should be familiar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;write data to the memory block&lt;/strong&gt;: writing to shared memory has a very similar interface as &lt;code&gt;fwrite&lt;/code&gt;. the pointer is used, and  the string to write to memory is passed as an argument. the function to do this is called &lt;a href="https://www.php.net/manual/en/function.shmop-write.php" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_write&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;read data from the memory block&lt;/strong&gt;: reading data from shared memory is done with &lt;a href="https://www.php.net/manual/en/function.shmop-read.php" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_read&lt;/code&gt;&lt;/a&gt;, again using the pointer from &lt;code&gt;shmop_open&lt;/code&gt;. the return value is a string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;delete the memory block using the key&lt;/strong&gt;: deleting shared memory after it's no longer needed is important. this is done with &lt;a href="https://www.php.net/manual/en/function.shmop-delete.php" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_delete&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  the actual implementation
&lt;/h2&gt;

&lt;p&gt;let's start with an example. the code below works and, if you are sufficiently un-curious or a tl;dr-type, you can just copy-paste-modify this, but for everyone else, we'll go over all the &lt;code&gt;shmop&lt;/code&gt; steps and explain what they do and how they 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="c1"&gt;// the file used by ftok. can be any file.&lt;/span&gt;
&lt;span class="nv"&gt;$shmop_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/usr/bin/php8.3"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for&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;0&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&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;span class="c1"&gt;// create the fork&lt;/span&gt;
    &lt;span class="nv"&gt;$pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;pcntl_fork&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// an error has ocurred&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt; &lt;span class="o"&gt;===&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="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"error"&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="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// child process&lt;/span&gt;
    &lt;span class="k"&gt;else&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;$pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="c1"&gt;// create a random 'word' for this child to write to shared memory &lt;/span&gt;
        &lt;span class="nv"&gt;$random_word&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;array_map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'z'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="nb"&gt;rand&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="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt; &lt;span class="nb"&gt;range&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="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;

        &lt;span class="c1"&gt;// write to shmop&lt;/span&gt;
        &lt;span class="nv"&gt;$shm_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shmop_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nv"&gt;$shm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;shmop_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;0755&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="nb"&gt;shmop_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$random_word&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="k"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"child &lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt; wrote '&lt;/span&gt;&lt;span class="nv"&gt;$random_word&lt;/span&gt;&lt;span class="s2"&gt;' to shmop"&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;// terminate the child process&lt;/span&gt;
        &lt;span class="k"&gt;exit&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// wait for all child processes to finish&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;$pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;pcntl_waitpid&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;$status&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;!=&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"pid &lt;/span&gt;&lt;span class="nv"&gt;$pid&lt;/span&gt;&lt;span class="s2"&gt; finished"&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="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// read all our shared memories&lt;/span&gt;
&lt;span class="k"&gt;for&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;0&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&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;span class="c1"&gt;// recreate the shm key&lt;/span&gt;
    &lt;span class="nv"&gt;$shm_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shmop_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// read from the shared memory&lt;/span&gt;
    &lt;span class="nv"&gt;$shm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;shmop_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;0755&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="nv"&gt;$shmop_contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;shmop_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_id&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="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"reading '&lt;/span&gt;&lt;span class="nv"&gt;$shmop_contents&lt;/span&gt;&lt;span class="s2"&gt;' from child &lt;/span&gt;&lt;span class="nv"&gt;$i&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="c1"&gt;// delete the shared memory so the shm key can be reused in future runs of this script&lt;/span&gt;
    &lt;span class="nb"&gt;shmop_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_id&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;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  creating a shared memory key with &lt;code&gt;ftok&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;as we covered above, all shared memory blocks are identified by a unique integer key and before we can get down to the task of assigning memory we have create that key.&lt;/p&gt;

&lt;p&gt;in all honesty, we can use any integer we want to, so long as it is unique, however the generally accepted, canonical way to do this is by using &lt;a href="https://www.php.net/manual/en/function.ftok.php" rel="noopener noreferrer"&gt;&lt;code&gt;ftok&lt;/code&gt;&lt;/a&gt; to create an integer using an existing file in the filesystem as a reference point.&lt;/p&gt;

&lt;p&gt;the rationale for doing this is pretty straightforward. processes don't know anything about each other, which makes it difficult for them to share a mutually agreed-upon value. one of the few things all processes on a system &lt;em&gt;do&lt;/em&gt; have in common, though, is the filesystem. hence, &lt;code&gt;ftok&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;in addition to the path to an existing file, &lt;code&gt;ftok&lt;/code&gt; also takes a &lt;code&gt;project_id&lt;/code&gt; argument. this is, according to the docs, a 'one character string', what people in every other programming language would call a 'char'. the purpose of the project id is to prevent collisions when creating shared memory. if two projects by two separate vendors both decided to use &lt;code&gt;/etc/passwd&lt;/code&gt; as their argument to &lt;code&gt;ftok&lt;/code&gt;, chaos would ensue. &lt;/p&gt;

&lt;p&gt;let's look at a fairly straightforward 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;$shm_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'/usr/bin/php8.3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'j'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"shm_key = &lt;/span&gt;&lt;span class="nv"&gt;$shm_key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here we are passing the full path to a file that we know exists on the system and providing a one character &lt;code&gt;project_id&lt;/code&gt;, 'j'. if we run this, the print statement will output something like:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;that's a good integer to use for creating our shared memory!&lt;/p&gt;

&lt;p&gt;if you run this code on your system, you will almost certainly get a different return value, even though you have used the same arguments. this is because, under the hood, &lt;code&gt;ftok&lt;/code&gt; uses the &lt;a href="https://linuxhandbook.com/inode-linux/" rel="noopener noreferrer"&gt;inode&lt;/a&gt; of the file, and that is different from system to system.&lt;/p&gt;

&lt;p&gt;if, for some reason, we pass to &lt;code&gt;ftok&lt;/code&gt; a file that &lt;em&gt;doesn't&lt;/em&gt; exist, we get a warning.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHP Warning:  ftok(): ftok() failed - No such file or directory in &amp;lt;our script&amp;gt; on line &amp;lt;the line&amp;gt;
shm_key = -1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;note that this is only a warning and &lt;code&gt;ftok&lt;/code&gt; will charge ahead and give us a value of -1, which will result in problems down the road. be careful.&lt;/p&gt;

&lt;p&gt;now, let's revist our call to &lt;code&gt;ftok&lt;/code&gt; on line 20:&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;$shm_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shmop_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;here we have passed &lt;code&gt;ftok&lt;/code&gt; the path of the file we have set in &lt;code&gt;$shm_key&lt;/code&gt;, in this case &lt;code&gt;/usr/bin/php8.3&lt;/code&gt;, a file we know exists on the system.&lt;/p&gt;

&lt;p&gt;for our &lt;code&gt;project_id&lt;/code&gt; we are using &lt;code&gt;$i&lt;/code&gt;, the index of the array we are looping over. we are doing this so that each of our child processes has it's own shared memory block to store its results. remember that if more than one process tries to write to shared memory, Bad Things happen. using the index here helps us avoid that.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  opening a memory block with &lt;code&gt;shmop_open&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;if you've ever done file access with tools like php's &lt;a href=""&gt;&lt;code&gt;fopen&lt;/code&gt;&lt;/a&gt; and &lt;a href=""&gt;&lt;code&gt;fwrite&lt;/code&gt;&lt;/a&gt;, then using shmop will be very familiar.&lt;/p&gt;

&lt;p&gt;let's start with opening a shared memory block with &lt;code&gt;shmop_open&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="nv"&gt;$shm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;shmop_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'n'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;0755&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;this function takes four arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;key&lt;/strong&gt;: the unique key we created using &lt;code&gt;ftok&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mode&lt;/strong&gt;: the type of access we want. when opening a file with &lt;code&gt;fopen&lt;/code&gt; we use modes like &lt;code&gt;r&lt;/code&gt; for 'read' or &lt;code&gt;w&lt;/code&gt; for write. &lt;code&gt;shmop_open&lt;/code&gt;'s mode is &lt;em&gt;similar&lt;/em&gt; to that, but there are differences. we'll go over all those below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;permissions&lt;/strong&gt;: the read/write/execute permissions of the shared memory block in &lt;a href="https://www.redhat.com/sysadmin/linux-file-permissions-explained" rel="noopener noreferrer"&gt;octal notation&lt;/a&gt;. the interface for dealing with shared memory is strongly analogous to file access, and that includes permissions. if you're not confident with octal notation, there are &lt;a href="https://chmod-calculator.com/" rel="noopener noreferrer"&gt;file permissions calculators&lt;/a&gt; you can use. we're using &lt;code&gt;0755&lt;/code&gt; in this example, but you may want to tighten that up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;size&lt;/strong&gt;: the size of the memory block in bytes. in the example, we are assigning one megabyte, which is clearly overkill. however, note that if we overwrite our shared memory block, the value will be truncated. if we try to read more bytes from a memory block than its size, a fatal error occurs. if we are unsure of the exact size of the data we will be writing to memory, it is better to overestimate how big we need our memory block to be.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;an important note here is that calling &lt;code&gt;shmop_open&lt;/code&gt; will create a new memory block if one at that key doesn't already exist. this is similar to how &lt;code&gt;fopen&lt;/code&gt; behaves, but with &lt;code&gt;shmop_open&lt;/code&gt; this behaviour is dependent on the 'mode' argument we pass.&lt;/p&gt;

&lt;p&gt;as shown in the example, &lt;code&gt;shmop_open&lt;/code&gt; returns a pointer that can be used for access: reading or writing, depending on the mode used to open the memory block.&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F08%2Fmeme_shmop_2.jpg" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F08%2Fmeme_shmop_2.jpg" title="james bond" alt="james bond"&gt;&lt;/a&gt;007 are bad permissions for a spy&lt;/p&gt;

&lt;h3&gt;
  
  
  a little bit more about that 'mode' argument
&lt;/h3&gt;

&lt;p&gt;the mode argument that we pass to &lt;code&gt;shmop_open&lt;/code&gt; determines how we can access our shared memory block. there are four options, all covered in the &lt;a href="https://www.php.net/manual/en/function.shmop-open.php" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;, but for the sake of simplicity, we'll only look at the two we need for our purposes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;n&lt;/code&gt;&lt;/strong&gt; : the 'n' stands for 'new' and is used when we want to create a new shared memory block. there does exist a mode &lt;code&gt;c&lt;/code&gt; for 'create', but we are choosing to use &lt;code&gt;n&lt;/code&gt; here because this mode will fail if we try to open a memory block that already exists. that's a safety feature! in fact, the docs state that using &lt;code&gt;n&lt;/code&gt; to create a new shared memory block is 'useful' for 'security purposes'. the pointer returned from &lt;code&gt;shmop_open&lt;/code&gt; using mode &lt;code&gt;n&lt;/code&gt; is writeable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;a&lt;/code&gt;&lt;/strong&gt; : this is for 'access'; ie. reading. do not confuse this with &lt;code&gt;fopen&lt;/code&gt;'s &lt;code&gt;a&lt;/code&gt; mode, which is for 'append'. memory blocks opened with mode &lt;code&gt;a&lt;/code&gt; are read-only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;if we look at the example, we can see that when we open the shared memory block in the child process to write our data, we use the &lt;code&gt;n&lt;/code&gt; mode. this creates the new memory block in a safe way and returns a pointer that we can write to.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  using &lt;code&gt;shmop_write&lt;/code&gt; to... write.
&lt;/h2&gt;

&lt;p&gt;once our child process has created a new shared memory block and received a pointer to it, it can write whatever it wants there using &lt;a href="https://www.php.net/manual/en/function.shmop-write.php" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_write&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;in our example, doing this looks like:&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;shmop_write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$random_word&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the &lt;code&gt;shmop_write&lt;/code&gt; function takes three arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;the pointer&lt;/strong&gt;: the pointer returned from &lt;code&gt;shmop_open&lt;/code&gt;. note that &lt;code&gt;shmop_open&lt;/code&gt; must be called with a mode that allows writing (&lt;code&gt;n&lt;/code&gt; in our example), otherwise attempts to use &lt;code&gt;shmop_write&lt;/code&gt; will fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the value to write&lt;/strong&gt;: the string to write to the shared memory block.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;the offset&lt;/strong&gt;: the number of bytes in memory to offset the start point of the write by. using the offset can allow us to append to a value already in the shared memory block, but doing this means keeping track of bytes written and can become unmanageable pretty quickly. in our example, we use the offset &lt;code&gt;0&lt;/code&gt;; we start writing at the beginning of the memory block.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;shmop_write&lt;/code&gt; returns, as an integer, the number of bytes written.&lt;/p&gt;

&lt;h3&gt;
  
  
  a short note about &lt;code&gt;shmop_close&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;if you've done file access using &lt;code&gt;fopen&lt;/code&gt;, you're probably (hopefully!) in the habit of calling &lt;code&gt;fclose&lt;/code&gt; when you're done writing.&lt;/p&gt;

&lt;p&gt;we do not do that with &lt;code&gt;shmop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;there &lt;em&gt;is&lt;/em&gt; a &lt;a href="https://www.php.net/manual/en/function.shmop-close.php" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_close&lt;/code&gt;&lt;/a&gt; function, but it has been deprecated since php 8.0 and does nothing (other than throw a deprecation warning, that is). the standard practice with shmop is to just leave the pointer 'open' after we're done writing. we'll delete it later.&lt;/p&gt;

&lt;h2&gt;
  
  
  reading from shared memory
&lt;/h2&gt;

&lt;p&gt;once all the child processes have written their data to their respective shared memory blocks an exited, all that remains is for the parent process to read that data. the strategy for this is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;recreate the key of the shared memory block&lt;/li&gt;
&lt;li&gt;use the key to open the shared memory in 'access only' mode&lt;/li&gt;
&lt;li&gt;read the data into a variable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;let's look again at the example we have for reading shared memory.&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;// read all our shared memories&lt;/span&gt;
&lt;span class="k"&gt;for&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;0&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;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;4&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;span class="c1"&gt;// recreate the shm key&lt;/span&gt;
    &lt;span class="nv"&gt;$shm_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shmop_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// read from the shared memory&lt;/span&gt;
    &lt;span class="nv"&gt;$shm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;shmop_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mo"&gt;0755&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="nv"&gt;$shmop_contents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;shmop_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_id&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="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="s2"&gt;"reading '&lt;/span&gt;&lt;span class="nv"&gt;$shmop_contents&lt;/span&gt;&lt;span class="s2"&gt;' from child &lt;/span&gt;&lt;span class="nv"&gt;$i&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="c1"&gt;// delete the shared memory so the shm key can be reused in future runs of this script&lt;/span&gt;
    &lt;span class="nb"&gt;shmop_delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_id&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;
  
  
  recreating the shmop key
&lt;/h3&gt;

&lt;p&gt;when we made the key to create our shared memory blocks, we used &lt;code&gt;ftok&lt;/code&gt; with two arguments: the path an existing file in the filesystem, and a 'project id'. for the project id, we used the index of the array we looped over to fork multiple children.&lt;/p&gt;

&lt;p&gt;we can use the exact same strategy to recreate the keys for reading. as long as we input the same two arguments into &lt;code&gt;ftok&lt;/code&gt;, we get the same value back.&lt;/p&gt;

&lt;h3&gt;
  
  
  opening the shared memory
&lt;/h3&gt;

&lt;p&gt;we open the shared memory block for reading almost exactly the same way as we did above for writing. the only difference is the mode.&lt;/p&gt;

&lt;p&gt;for reading, we use the &lt;code&gt;a&lt;/code&gt; mode. this stands for 'access', and gives us a read-only pointer to our shared memory block. &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  reading from the shared memory block
&lt;/h3&gt;

&lt;p&gt;once we have a pointer to our shared memory block, we can read from it using &lt;a href="https://www.php.net/manual/en/function.shmop-read.php" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_read&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;shmop_read&lt;/code&gt; takes three arguments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the pointer we got from &lt;code&gt;shmop_open&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;the offset, in bytes. since we are reading the entirety of the memory block, starting at the beginning, this is &lt;code&gt;0&lt;/code&gt; in our example (and will probably be for most real-life uses, as well)&lt;/li&gt;
&lt;li&gt;the number of bytes to read. in most cases, the smart thing here is to just read the entire size of the block, in our example 1024 bytes. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the return type is a string. if there are errors reading, we get a boolean &lt;code&gt;false&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  deleting shared memory blocks
&lt;/h2&gt;

&lt;p&gt;once we are done reading our shared memory, we can delete it.&lt;/p&gt;

&lt;p&gt;this is an important step. unlike variables in our script, the memory we assigned with shmop will persist after our program has exited, hogging resources. we do not want to litter our system with blocks of unused, reserved memory, piling up higher and higher with each successive run of our script!&lt;/p&gt;

&lt;p&gt;freeing up shared memory blocks is done with &lt;a href="https://www.php.net/manual/en/function.shmop-delete" rel="noopener noreferrer"&gt;&lt;code&gt;shmop_delete&lt;/code&gt;&lt;/a&gt;. this function takes one argument: the pointer we created with &lt;code&gt;shmop_open&lt;/code&gt;, and returns a boolean &lt;code&gt;true&lt;/code&gt; on success.&lt;/p&gt;

&lt;p&gt;note that &lt;code&gt;shmop_delete&lt;/code&gt; destroys the memory block and frees up the space for other applications to use. we should only call it when we're completely done with using the memory.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  handling errors
&lt;/h2&gt;

&lt;p&gt;the example we've been going over doesn't really do any error handling. this is a decision borne out of a desire for brevity, not delusional optimism. in real applications we should certainly do some error testing!&lt;/p&gt;

&lt;p&gt;we used a path to a file as an argument for &lt;code&gt;ftok&lt;/code&gt;; we should test that it exists. &lt;code&gt;shmop_write&lt;/code&gt; will throw a &lt;a href="https://www.php.net/manual/en/class.valueerror.php" rel="noopener noreferrer"&gt;&lt;code&gt;value error&lt;/code&gt;&lt;/a&gt; if our memory block is opened read-only or we overwrite its size. that should be handled. if there's a problem reading data,  &lt;code&gt;shmop_read&lt;/code&gt; will return &lt;code&gt;false&lt;/code&gt;. test for that.&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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F08%2Fmeme_shmop_3.jpg" 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%2Fgbh.fruitbat.io%2Fwp-content%2Fuploads%2F2024%2F08%2Fmeme_shmop_3.jpg" title="the bernie sanders asking" alt="the bernie sanders asking meme"&gt;&lt;/a&gt;i am asking you to do some error handling&lt;/p&gt;

&lt;h2&gt;
  
  
  fixing 'already exists' errors with &lt;code&gt;shmop_open&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;if we open a shared memory block and then the script terminates before we call &lt;code&gt;shmop_delete&lt;/code&gt;, the memory block still exists. if we then try to open that memory block again with &lt;code&gt;shmop_open&lt;/code&gt; using the &lt;code&gt;n&lt;/code&gt; mode, we will get the error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PHP Warning:  shmop_open(): Unable to attach or create shared memory segment "File exists" in /path/to/my/script on line &amp;lt;line number&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if our script is well-designed, this shouldn't happen. but, while developing and testing we may create these orphaned memory blocks. let's go over how to delete them.&lt;/p&gt;

&lt;p&gt;the first step is to get the key of the memory block as a hex number. we do this by calling &lt;code&gt;ftok&lt;/code&gt; as normal, and then converting the returned integer from base ten to base-16 like so:&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;$shm_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;ftok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shmop_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nv"&gt;$shm_key_hex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"0x"&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;base_convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$shm_key&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="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we do this because linux comes with a number of 'interprocess communication' tools that we can use to manage shared memory blocks, and they all use hexadecimal numbers for their keys.&lt;/p&gt;

&lt;p&gt;the first command line tool we'll use is &lt;a href="https://www.man7.org/linux/man-pages/man1/ipcs.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;ipcs&lt;/code&gt;&lt;/a&gt;. we're going to use this to confirm that the shared memory block we want to delete does, in fact, exist.&lt;/p&gt;

&lt;p&gt;the &lt;code&gt;ipcs&lt;/code&gt; command, when run without arguments, will output &lt;em&gt;all&lt;/em&gt; interprocess communication channels, including all shared memory blocks. we'll narrow down that output by using &lt;code&gt;grep&lt;/code&gt; with the hexadecimal key we created above. for instance, if our shared memory block's key in hexadecimal is &lt;code&gt;0x33010024&lt;/code&gt;, we could do this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ipcs | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"0x33010024"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;if we get a line of output, the memory block exists. if nothing is returned, it does not.&lt;/p&gt;

&lt;p&gt;once we've confirm that a shared memory block exists, we can remove it with &lt;a href="https://www.man7.org/linux/man-pages/man1/ipcrm.1.html" rel="noopener noreferrer"&gt;&lt;code&gt;ipcrm&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ipcrm &lt;span class="nt"&gt;--shmem-key&lt;/span&gt; 0x33010024
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;knowing how to inspect and clean up (without resorting to a restart) shared memory allows us to develop and experiment without turning our ram into a ghost town of abandoned blocks.&lt;/p&gt;

&lt;h2&gt;
  
  
  wrapping up
&lt;/h2&gt;

&lt;p&gt;achieving concurrency in php using fork and shared memory does take some effort and knowledge (and the official manual is scant help). but it &lt;em&gt;does&lt;/em&gt; work and, if you've made it through this article and the &lt;a href=""&gt;first installment on &lt;code&gt;pcntl_fork&lt;/code&gt;&lt;/a&gt;, you should have a good base from which to start.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🔎  this post &lt;a href="https://gbh.fruitbat.io/2024/08/19/php-concurrency-with-processes-pt-2-interprocess-communication-with-shmop/" rel="noopener noreferrer"&gt;originally appeared&lt;/a&gt; in the &lt;a href="https://gbh.fruitbat.io" rel="noopener noreferrer"&gt;grant horwood technical blog&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

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