Tag Archives: PHP

PHP: preg_match_all_callback

There are several PCRE functions available, but today I looked for one that just wasn’t there: preg_match_all_callback().

Could’ve maybe used preg_replace_callback(), but felt wrong since I didn’t actually want to do any replacing. I just needed my function to be called for each match.

So I wrote it myself. Noting it here, in case I (or someone else) needs it again.

<?php
/**
 * Perform a global regular expression match
 * and calls the callback for each match.
 */

function preg_match_all_callback(
        string $pattern,
        string $subject,
        callable $callback)
{
    $r = preg_match_all($pattern, $subject, $matches, PREG_SET_ORDER);
    foreach($matches ?? [] as $match)
        $callback($match);
    return $r;
}

And, in case someone reads this post and knows it actually does exist… and if that someone is you, please do leave a comment!

And, yes, I could’ve just written those 3 lines where I needed them, but what’s the fun in that? And besides, the shorter the code where it counts, the easier what counts is to read.

Usage

preg_match_all_callback('/(\w)\w*/', 'Hello World', 'var_dump');
array (size=2)
  0 => string 'Hello' (length=5)
  1 => string 'H' (length=1)

array (size=2)
  0 => string 'World' (length=5)
  1 => string 'W' (length=1)

PHP: Get headers with actual HEAD request

PHP has a function called get_headers which, as you’d guess, gives you the headers returned from an HTTP request. However it actually uses a GET, rather than HEAD, request.

Figured out you can change this by setting a stream context, so wrapped it in a function. And posting it here in case I need it again.

Also added a cleanup of the returned array, as I found it a bit ugly when the request included redirects. See difference below code.

Note: I silence the get_headers call because it throws several warnings, e.g. if the hostname fails lookup, and I’m not really interested in why it fails.

function get_head(string $url, array $opts = [])
{
    // Store previous default context
    $prev = stream_context_get_options(stream_context_get_default());

    // Set new one with head and a small timeout
    stream_context_set_default(['http' => $opts +
        [
            'method' => 'HEAD',
            'timeout' => 2,
        ]]);

    // Do the head request
    $req = @get_headers($url, true);
    if( ! $req)
        return false;

    // Make more sane response
    foreach($req as $h => $v)
    {
        if(is_int($h))
            $headers[$h]['Status'] = $v;
        else
        {
            if(is_string($v))
                $headers[0][$h] = $v;
            else
                foreach($v as $x => $y)
                    $headers[$x][$h] = $y;
        }

    }

    // Restore previous default context and return
    stream_context_set_default($prev);
    return $headers;
}

Example response:

<?php get_head('http://geekality.net');

array (size=2)
  0 =>
    array (size=8)
      'Status' => string 'HTTP/1.1 301 Moved Permanently' (length=30)
      'Date' => string 'Mon, 06 Feb 2017 01:20:48 GMT' (length=29)
      'Server' => string 'Apache' (length=6)
      'Location' => string 'http://www.geekality.net/' (length=25)
      'Vary' => string 'Accept-Encoding' (length=15)
      'Connection' => string 'close' (length=5)
      'Content-Type' => string 'text/html; charset=iso-8859-1' (length=29)
      'Link' => string '<http://www.geekality.net/wp-json/>; rel="https://api.w.org/"' (length=61)
  1 =>
    array (size=6)
      'Date' => string 'Mon, 06 Feb 2017 01:20:48 GMT' (length=29)
      'Server' => string 'Apache' (length=6)
      'Vary' => string 'Accept-Encoding' (length=15)
      'Connection' => string 'close' (length=5)
      'Content-Type' => string 'text/html; charset=UTF-8' (length=24)
      'Status' => string 'HTTP/1.1 200 OK' (length=15)

Example response without my cleanup:

<?php get_head('http://geekality.net');

array (size=9)
  0 => string 'HTTP/1.1 301 Moved Permanently' (length=30)
  'Date' =>
    array (size=2)
      0 => string 'Mon, 06 Feb 2017 01:14:00 GMT' (length=29)
      1 => string 'Mon, 06 Feb 2017 01:14:01 GMT' (length=29)
  'Server' =>
    array (size=2)
      0 => string 'Apache' (length=6)
      1 => string 'Apache' (length=6)
  'Location' => string 'http://www.geekality.net/' (length=25)
  'Vary' =>
    array (size=2)
      0 => string 'Accept-Encoding' (length=15)
      1 => string 'Accept-Encoding' (length=15)
  'Connection' =>
    array (size=2)
      0 => string 'close' (length=5)
      1 => string 'close' (length=5)
  'Content-Type' =>
    array (size=2)
      0 => string 'text/html; charset=iso-8859-1' (length=29)
      1 => string 'text/html; charset=UTF-8' (length=24)
  1 => string 'HTTP/1.1 200 OK' (length=15)
  'Link' => string '<http://www.geekality.net/wp-json/>; rel="https://api.w.org/"' (length=61)

PHP: Convert RGB to hex and back

Just another note to self, in case I need it again:

/**
 * #rrggbb or #rgb to [r, g, b]
 */

function hex2rgb(string $hex): array
{
    $hex = ltrim($hex, '#');

    if(strlen($hex) == 3)
        return [
            hexdec($hex[0].$hex[0]),
            hexdec($hex[1].$hex[1]),
            hexdec($hex[2].$hex[2]),
        ];
    else
        return [
            hexdec($hex[0].$hex[1]),
            hexdec($hex[2].$hex[3]),
            hexdec($hex[4].$hex[5]),
        ];
}


/**
 * [r, g, b] to #rrggbb
 */

function rgb2hex(array $rgb): string
{
    return '#'
        . sprintf('%02x', $rgb[0])
        . sprintf('%02x', $rgb[1])
        . sprintf('%02x', $rgb[2]);
}

PHP: Stream a file line-by-line using a generator

This function will “stream” a file line-by-line, as a Generator.

Can be very useful if for example you need to process a big file, don’t want to read the whole thing into memory, but only process each line by itself.

function read($file)
{
    $fp = fopen($file, 'rb');

    while(($line = fgets($fp)) !== false)
        yield rtrim($line, "\r\n");

    fclose($fp);
}

// Usage
foreach(read('http://example.com') as $line)
{
    var_dump($line);
}

PHP: Delete directory recursively

function delete_directory($dir)
{
    if( ! file_exists($dir))
        return;

    $it = new RecursiveDirectoryIterator($dir);
    $it = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);

    foreach($it as $file)
        if($file->isDir())
            @rmdir($file->getRealPath());
        else
            @unlink($file->getRealPath());
}

PHP test script for cross origin proxy target

Just a script I used as target during some testing of a cross origin proxy to make sure whatever I thought should go through actually went through.

<?php // test.php
$info = [
    'method' => $_SERVER['REQUEST_METHOD'],
    'cookie' => $_COOKIE,
    'get' => $_GET,
    'post' => $_POST,
    'input' => file_get_contents('php://input'),
    'headers' => getallheaders(),
];

ob_start('ob_gzhandler');
header('Content-Type: application/json; charset=utf-8');
header('X-SomeHeader: Some value');
echo json_encode($info, JSON_PRETTY_PRINT | JSON_NUMERIC_CHECK | JSON_FORCE_OBJECT);