104

I'm trying to use file_get_contents together with stream_context_create to make POST requests. My code so far:

    $options = array('http' => array(
        'method'  => 'POST',
        'content' => $data,
        'header'  => 
            "Content-Type: text/plain\r\n" .
            "Content-Length: " . strlen($data) . "\r\n"
    ));
    $context  = stream_context_create($options);
    $response = file_get_contents($url, false, $context);

It works fine, however, when an HTTP error occurs, it spits out a warning:

file_get_contents(...): failed to open stream: HTTP request failed! HTTP/1.0 400 Bad Request

and returns false. Is there a way to:

  • suppress a warning (I'm planning to throw my own exception in case of failure)
  • obtain the error information (at least, the response code) from the stream
georg
  • 211,518
  • 52
  • 313
  • 390

6 Answers6

175

http://php.net/manual/en/reserved.variables.httpresponseheader.php

$context = stream_context_create(['http' => ['ignore_errors' => true]]);
$result = file_get_contents("http://example.com", false, $context);
var_dump($http_response_header);
Your Common Sense
  • 156,878
  • 40
  • 214
  • 345
Alles
  • 1,766
  • 1
  • 11
  • 3
  • 41
    For those wondering, the answer to the first question is to add `'ignore_errors' => TRUE` to `$options`. – georg Mar 25 '13 at 16:55
37

None of the answers (including the one accepted by OP) actually satisfy the two requirements:

  • suppress a warning (I'm planning to throw my own exception in case of failure)
  • obtain the error information (at least, the response code) from the stream

Here's my take:

function fetch(string $method, string $url, string $body, array $headers = []) {
    $context = stream_context_create([
        "http" => [
            // http://docs.php.net/manual/en/context.http.php
            "method"        => $method,
            "header"        => implode("\r\n", $headers),
            "content"       => $body,
            "ignore_errors" => true,
        ],
    ]);

    $response = file_get_contents($url, false, $context);

    /**
     * @var array $http_response_header materializes out of thin air
     */

    $status_line = $http_response_header[0];

    preg_match('{HTTP\/\S*\s(\d{3})}', $status_line, $match);

    $status = $match[1];

    if ($status !== "200") {
        throw new RuntimeException("unexpected response status: {$status_line}\n" . $response);
    }

    return $response;
}

This will throw for a non-200 response, but you can easily work from there, e.g. add a simple Response class and return new Response((int) $status, $response); if that fits your use-case better.

For example, to do a JSON POST to an API endpoint:

$response = fetch(
    "POST",
    "http://example.com/",
    json_encode([
        "foo" => "bar",
    ]),
    [
        "Content-Type: application/json",
        "X-API-Key: 123456789",
    ]
);

Note the use of "ignore_errors" => true in the http context map - this will prevent the function from throwing errors for non-2xx status codes.

This is most likely the "right" amount of error-suppression for most use-cases - I do not recommend using the @ error-suppression operator, as this will also suppress errors like simply passing the wrong arguments, which could inadvertently hide a bug in calling code.

mindplay.dk
  • 7,085
  • 3
  • 44
  • 54
  • That's great! But I wonder: isn't there a less-hacky way to directly get the status code as an integer value? – at54321 Jun 25 '21 at 07:27
  • @at54321 not with `file_get_contents`, that's how it was designed (back in the deep, dark ages of PHP) but in my own client I ended up using `fopen` instead, which avoids this weird, magical API - you can see [here](https://github.com/mindplay-dk/http-client/blob/6855273e6b6b5d1620d4a48cf92068f215913580/src/HttpClient.php#L64-L84) how to do that. – mindplay.dk Aug 05 '21 at 06:25
  • [Documentation for $http_response_header](https://www.php.net/manual/en/reserved.variables.httpresponseheader.php), which does indeed materialize itself after the call to file_get_contents(). – GPHemsley Nov 15 '21 at 22:59
8

Adding few more lines to the accepted response to get the http code

function getHttpCode($http_response_header)
{
    if(is_array($http_response_header))
    {
        $parts=explode(' ',$http_response_header[0]);
        if(count($parts)>1) //HTTP/1.0 <code> <text>
            return intval($parts[1]); //Get code
    }
    return 0;
}

@file_get_contents("http://example.com");
$code=getHttpCode($http_response_header);

to hide the error output both comments are ok, ignore_errors = true or @ (I prefer @)

Hamboy75
  • 938
  • 1
  • 11
  • 22
  • 1
    it's wrong idea to "prefer @". ignore_errors = true suppresses only HTTP errors, while @ gags ALL possible errors that will prevent a programmer from learning why their code doesn't work. When HTTP wrapper is not configured for example, or parameters are not i order. **Using @ is a VERY, VERY bad practice**. I wish there was less such misinformation on Stack Overflow – Your Common Sense Aug 30 '22 at 06:19
  • I suggest @ but I said that you can use both @YourCommonSense , I can agree with you about placing @ in other php functions, but in file_get_contents..., if it doesnt work, you just have to remove the @ to check what is happening. – Hamboy75 Aug 31 '22 at 11:08
  • But without error message you will just have no idea what its file_get_contents. A pity, in four years you didn't learn the importance of error messages. – Your Common Sense Aug 31 '22 at 11:25
0

To capture the error message when file_get_contents returns FALSE, write a function which uses ob_start and ob_get_contents to capture the error message that file_get_contents writes to stderr.

function fileGetContents( $fileName )
{
  $errmsg = '' ;
  ob_start( ) ;
  $contents = file_get_contents( $fileName );
  if ( $contents === FALSE )
  {
    $errmsg = ob_get_contents( ) ;
    $errmsg .= "\nfile name:$fileName";
    $contents = '' ;
  }
  ob_end_clean( ) ;
  return (object)[ 'errmsg' => $errmsg, 'contents' => $contents ];
}
RockBoro
  • 2,163
  • 2
  • 18
  • 34
-1

I go to this page with kind of a different issue, so posting my answer. My problem was that I was just trying to suppress the warning notification and display a customized warning message for the user, so this simple and obvious fix helped me:

// Suppress the warning messages
error_reporting(0);

$contents = file_get_contents($url);
if ($contents === false) {
  print 'My warning message';
}

And if needed, turn back error reporting after that:

// Enable warning messages again
error_reporting(-1);
Kaarel
  • 9
-5

@file_get_contents and ignore_errors = true are not the same: the first doesn't return anything; the second suppresses error messages, but returns server response (e.g. 400 Bad request).

I use a function like this:

$result = file_get_contents(
  $url_of_API, 
  false, 
  stream_context_create([
    'http' => [
      'content' => json_encode(['value1' => $value1, 'value2' => $value2]), 
      'header' => 'Authorization: Basic XXXXXXXXXXXXXXX', 
      'ignore_errors' => 1, 
      'method' => 'POST', 
      'timeout' => 10
    ]
  ])
);

return json_decode($result)->status;

It returns 200 (Ok) or 400 (Bad request).

It works perfectly and it's easier than cURL.

JJJ
  • 32,902
  • 20
  • 89
  • 102
Besen
  • 1
  • 2
  • 1
    And how does this solve the question? Can you explain how to get the response code? – Nico Haase Aug 02 '18 at 10:33
  • 1
    It's still a bad one, as you parse a JSON result not and read a random field named `status` - it has no connection to the HTTP status code for the API call – Nico Haase Aug 02 '18 at 13:05
  • It's obvious that the RESTful API I connect to returns a JSON response and that what I need to know for my purpose (200 or 400) is contained in the "status" field. That's all I need and that's all I get. If you need to know the HTTP status code, just do this: return $http_response_header[0]; But note that it doesn't work with @file_get_contents. – Besen Aug 02 '18 at 13:32
  • And how does this answer the original question? Why do you think that `$http_response_header[0]`, a highly upvoted answer, does not work with `file_get_contents`? – Nico Haase Aug 02 '18 at 14:01
  • It doesn't work with **@file_get_contents** (NOT with file_get_contents). Just try it! – Besen Aug 02 '18 at 14:09
  • @Besen the `->status` is not always an option as it's not part of the spec. That is a property being exposed by whatever stream you are tapping as a courtesy (which is nice) but not standard. If the stream does not explicitly define this property your answer does not provide a viable solution. – MLK.DEV Aug 12 '19 at 21:20