1

I am trying to fetch my CSS from external CDN service let's say http://cdn.example.com/.

This code is suppose to check if file exists on external CDN and if it does, then get that file else get it from a local folder.

Currently this is fetching only the local copy even though the file exists on external CDN.

Please help me correct this code.

function getCSSfile($filename)
{
    $externalcss = EXTERNAL_CDN.'css/'.$filename.'.css';
    $localcss = LOCAL_CDN.'css/'.$filename.'.css';

    $ctx = stream_context_create(array(
        'http' => array(
            'method' => 'HEAD'
        )
    ));

    if (file_get_contents($externalcss, false, $ctx) !== false) {
        echo '<link href="' . $externalcss . '" rel="stylesheet" type="text/css" media="all"/>';
    } else if (file_exists($localcss)) {
        echo '<link href="' . $localcss . '" rel="stylesheet" type="text/css" media="all"/>';
    } else {
        echo 'Error loading CSS File'; 
    }
}

UPDATE:

Updated the code as per @DaveRandom's suggestion. Currently code runs fine it fetches the copy from external CDN but if the external CDN is unavailable it does fetches the local copy BUT throws the following error too:

Warning: file_get_contents(http://cdn.localtest.com/css/core.css): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in D:\xampp\htdocs\localtest2\core\core.php on line 165

and in line 165 this is the code

if (file_get_contents($externalcss, false, $ctx) !== false) {

I am testing it locally and created some domains on my XAMPP Server, hence cannot share a live link.

Vikram Rao
  • 1,068
  • 4
  • 24
  • 62
  • Well are you sure all the URLs are correct? what is the actual URL you are using? – raam86 Jul 08 '13 at 17:46
  • 2
    Side note: the last `else` statement should't be there, since that would echo the error into the HTML. Alternatively, you could echo `''` to produce an HTML comment. – Alfred Xing Jul 08 '13 at 17:47
  • @Alfred Thanks for the suggestion. I corrected and updated my answer with it. However the issue still persists. – Vikram Rao Jul 08 '13 at 17:57

5 Answers5

2

I don't believe you can use file_exists on a URL, this answer might shed some light on the code you could use to properly check in the URL returned a file to you.

But to summarize, you want to check the status code that is returned to you, rather than using file_exists.

Community
  • 1
  • 1
Hanna
  • 10,315
  • 11
  • 56
  • 89
  • You can with some urls (`ftp`), but not with `http` or `https` urls – Paul Jul 08 '13 at 17:47
  • You'd know better than me, I don't actually know PHP I just flexed my Google muscles and see if I could find anything useful. – Hanna Jul 08 '13 at 17:49
1

The problem with checking if the file can be accessed through PHP is that the check is done from your server, not the client loading the files. Just because your server can access the CSS file from the CDN does not mean the client can.

If you are going to do a check with a fallback, it is safer to do it on the client side using javascript.

How to fallback to local stylesheet if CDN fails

Community
  • 1
  • 1
Jeremy Gallant
  • 764
  • 3
  • 12
1

Another potential problem (in addition to @Johannes' answer) would be your constants. In the question you structured your CDN URL as http://cdn.example.com. Appending this to css/ (as in your code) would produce http://cdn.example.comcss/, which isn't a valid URL.

Make sure that the constants (base URL's) are structured as http://cdn.example.com/.


Edit: To add to @Johannes' answer, I found this link from another answer on SO: http://www.php.net/manual/en/function.file-exists.php#75064

To put the code here:

$file = 'http://www.domain.com/somefile.jpg';
$file_headers = @get_headers($file);
if($file_headers[0] == 'HTTP/1.1 404 Not Found') {
    $exists = false;
}
else {
    $exists = true;
}
Alfred Xing
  • 4,406
  • 2
  • 23
  • 34
1

The way I look at this, you have this process the wrong way round - you should serve your local copy if it exists (and is not stale, a mechanism which needs to be defined) and fall back to the CDN. This means you are behaving like a cache.

But to answer the question directly, your best bet would be to use a HEAD request to the CDN to see whether it exists. It's tempting to use get_headers() for this, but actually this causes a GET method request unless you alter the default stream context - this creates potentially unwanted global state in your application.

So my preferred approach would be to create localised stream context and use it in conjuction with a function that will accept it as an argument. For the sake of simplicity, in the below example I'll use file_get_contents().

function getCSSfile($filename)
{
    $externalcss = EXTERNAL_CDN.'css/'.$filename.'.css';
    $localcss = LOCAL_CDN.'css/'.$filename.'.css';

    $ctx = stream_context_create(array(
        'http' => array(
            'method' => 'HEAD'
        )
    ));

    if (file_get_contents($externalcss, false, $ctx) !== false) {
        echo '<link href="' . $externalcss . '" rel="stylesheet" type="text/css" media="all"/>';
    } else if (file_exists($localcss)) {
        echo '<link href="' . $localcss . '" rel="stylesheet" type="text/css" media="all"/>';
    } else {
        echo 'Error loading CSS File'; 
    }
}

Now, this is not perfect by a long way, but I've recycled a lot of mechanism from your original code. There is still global state (the use of user-defined constants in functions like this is a BIG no-no for me) and you still have a function which produces output directly instead of returning control to the caller.

I would prefer to pass $externalcss and $localcss in as arguments, and simply return the chosen URL string instead of formatting it into HTML and echoing it. This way, the code is isolated and doesn't rely on anything external, and by returning the string you allow the caller to further manipulate the data if necessary. In essence, it makes the code infinitely more re-usable.

Side note: Defining functions within functions, while possible, isn't recommended. This is because the second call will cause a Cannot redeclare function fatal error.

DaveRandom
  • 87,921
  • 11
  • 154
  • 174
  • OK this code runs fine but there is an issue. `Warning: file_get_contents(http://cdn.localtest.com/css/core.css): failed to open stream: HTTP request failed! HTTP/1.1 404 Not Found in D:\xampp\htdocs\localtest2\core\core.php on line 165` and in line 165 this is the code `if (file_get_contents($externalcss, false, $ctx) !== false) {` I am testing it locally and created some domains on my XAMPP Server, hence cannot share a live link. – Vikram Rao Jul 08 '13 at 18:16
  • I think the OP means "local copy" as in the local copy on the server, not the local copy on the client's computer. Since the server might be further away and/or a worse performer than the server on the CDN, he wants to serve the CDN one first. – Alfred Xing Oct 05 '13 at 16:25
0

Okay since the error solution was not explained by anyone... I decided to answer my own question. This is the solution I found:

function getCSSfile($filename)
{
    $externalcss = EXTERNAL_CDN.'css/'.$filename.'.css';
    $localcss = LOCAL_CDN.'css/'.$filename.'.css';

    $ctx = stream_context_create(array(
        'http' => array(
            'method' => 'HEAD'
        )
    ));

    if (@file_get_contents($externalcss, false, $ctx) !== false) {
        echo '<link href="' . $externalcss . '" rel="stylesheet" type="text/css" media="all"/>';
    } else if (file_exists($localcss)) {
        echo '<link href="' . $localcss . '" rel="stylesheet" type="text/css" media="all"/>';
    } else {
        echo 'Error loading CSS File'; 
    }
}

As suggested by Mark B

As stated in the docs, file_get_contents() will throw a warning if the stated resource cannot be found.

This is one of the few cases in which using the @ error suppression operator may be justified, e.g.

if (@file_get_contents($externalcss, false, $ctx) !== false)

to prevent the warning from mucking up the output.

Community
  • 1
  • 1
Vikram Rao
  • 1,068
  • 4
  • 24
  • 62