The quest for a decent PHP request

WARNING: rant ahead

So, today I needed to put recaptcha on a PHP site, and I stumbled into the trivial ( for today’s standards) problem of having to perform an HTTP query from PHP. If you are not familiar with PHP, traditionally this was done in two ugly ways. One was to use libcURL. Which is a library with a rather ugly syntax:

$ch = curl_init();

curl_setopt($ch,CURLOPT_URL, $your_url_goes_here);
curl_setopt($ch,CURLOPT_POST, count($form_data));
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($form_data));
$result = curl_exec($ch);

curl_close($ch);

this is certainly ugly for many reasons, it’s non intuitive, curl has many obscure options and the most sane default (which would be to return the response contents as an object or plaintext) is disabled by default (that’s why RETURNTRANSFER is important).

The other approach is using file_get_contents()/file_put_contents() I’ll let you figure out by yourself why this sucks and it’s not a “code smell” it’s a rotting corpse with an unholy stench of non-intuitive crap. Also you are dealing with a file ¿? not an actual HTTP Request, you don’t have proper access to response codes.

// Create a stream
$opts = array(
  'http'=>array(
    'method'=>"GET",
    'header'=>"Accept-language: en\r\n" .
              "Cookie: foo=bar\r\n"
  )
);

$context = stream_context_create($opts);

// Open the file using the HTTP headers set above
$file = file_get_contents('http://www.example.com/', false, $context);


Enter HttpRequest

Of course you can use HttpRequest from the pecl_http extension. Yeah… a language that basically revolves around http requests doesn’t have a proper HttpRequest implementation in it’s STL, I’ll give you a few minutes to try to swallow that fact. The use case is explained by the PHP manual as follows:


$r = new HttpRequest('http://example.com/form.php', HttpRequest::METH_POST);
$r->setOptions(array('cookies' => array('lang' => 'de')));
$r->addPostFields(array('user' => 'mike', 'pass' => 's3c|r3t'));
$r->addPostFile('image', 'profile.jpg', 'image/jpeg');
try {
    echo $r->send()->getBody();
} catch (HttpException $ex) {
    echo $ex;
}

Which is not too bad, except for the ugly constant name METH_POST, which I start to suspect is a reference to the drugs that many PHP developers have started using after dealing with this kind of thing. Except for one more problem, this is the implementation for an old as heck pecl_http extensions. Because now we have a better PHP and we have namespaces so we had to make it more fancier, modular whatever. Lurking I find that this is now the proper implementation of a HTTP POST Request :


$request = new http\Client\Request("POST",
    "http://localhost/post.php",
    ["Content-Type" => "application/x-www-form-urlencoded"]
);
$request->getBody()->append(new http\QueryString([
    "user" => "mike",
    "name" => "Michael Wallner"
]));

$client = new http\Client;
$client->enqueue($request)->send();

// ask for the response for this specific request
$response = $client->getResponse($request);
printf("-> %s\n", $response->getInfo());


Holy smokes! $request->getBody()->append() ?? what is this? are we dealing with a request or a JavaScript DOM manipulation function? Now we have to deal with an HTTP Client, a Request, a querystring object and a the same ugly curlopts wrapped in an almost equal ugly way

oh my god.. this is utter garbage.

For the sake of comparision this is how other languages do it:

Go is still verbose, it still works with a request + client, but is a lot more clear(example taken from here ):

import (
    "bytes"
    "fmt"
    "net/http"
)


func main() {
    url := "http://xxx/yyy"
    fmt.Println("URL:>", url)

    var query = []byte(`your query`)
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(query))
    req.Header.Set("X-Custom-Header", "myvalue")
    req.Header.Set("Content-Type", "text/plain")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    fmt.Println("response Status:", resp.Status)
    fmt.Println("response Headers:", resp.Header)
    body, _ := ioutil.ReadAll(resp.Body)
    fmt.Println("response Body:", string(body))
}

Ruby… well there isn’t much to see:

uri = URI('http://www.example.com/search.cgi')
res = Net::HTTP.post_form(uri, 'q' => ['ruby', 'perl'], 'max' => '50')
puts res.body if res.is_a?(Net::HTTPSuccess)

And even Javascript which I hate (taken from my blog file upload):

  var uploadedFiles = document.getElementById("js-upload-files");   
    var fd = new FormData();
fd.append("media",uploadedFiles.files[i]);
        var request = new XMLHttpRequest();
        request.open("POST", "/admin/media/create");
        request.send(fd);
            request.onreadystatechange = function(){
         if(request.readyState == 4 && request.status == 200){ 
... do stuf....

                        } 
             }

Conclusion

So I won’t claim PHP is doooooooomed, like many people like to say. Nor I’ll talk badly about any PHP project just because it’s PHP like many people do. I still believe that a good programmer will be able to do great things with it, and lousy programmers will manage to do really ugly things on any language they touch.

The problem is that PHP is catching up from many years of stagnation, but in many cases is borrowing really ugly ways from other languages because it doesn’t seem to know what it wants to be, every other language seems to be coherent in the way that they express themselves, yet PHP seems to find a way that is convoluted and extremely verbose to do pretty much the same thing; and this matters not because it doesn’t allow the programmer to code well, but because it certainly discourages them to adopt the newer and supposedly better practices. After trying to figure out a proper way with the code samples above, it’s highly probably that we just decide to say “screw it” and do the things in the old and ugly but familiar way, and that in my opinion, sucks.

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket