1

I have been working on creating a quick twitter widget for myself to re use, just something simple that caches the data from the public api. Since twitter only allows X requests for a certain time period and I have been testing this on a shared host, Its very often that I run out of requests and twitter denies my request. Thus I check first whether my request is denied, before writing an updated cached file.

Unfortunately I seem to lose this file from time to time, as I often see the 'temp file not written' message. Which should only appear if the file doesn't exist.

Here is the full php function:

function getTweets($num)
{
    $cfile = sys_get_temp_dir().'/e1z'. $type . md5 ( 'something' );

    if (is_file ( $cfile ) == false) {
        $cfile_time = strtotime ( '1983-04-30 07:15:00' );
    } else {
        $cfile_time = filemtime ( $cfile );
    }    

    $difference = strtotime ( date ( 'Y-m-d H:i:s' ) ) - $cfile_time;

    if ($difference >= 100) {

        $tags = array("created_at", "text", "screen_name", "profile_image_url"); // twitter names
        $local = array("time", "msg", "user", "image"); // local names

        $reader = new XMLReader();
        $url = 'http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=boriskourt&include_rts=true&count=' . $num;

        $headers = get_headers($url, 1);
        if ($headers[0] == 'HTTP/1.0 400 Bad Request'){

                if (is_file ( $cfile ) == true) {
                        $returner = file_get_contents ( $cfile );                   
                        touch ( $cfile );
                        file_put_contents ( $cfile, strval($returner) );
                        $returner = file_get_contents ( $cfile );
                        return  $returner;
                } else {
                        $returner = "<li><span>Temp file not written</span></li>";
                        return  $returner;
                }

        } else { 
            $reader->open($url);

            $i = 0;
            $k = 1;

            while ($i < $num)
            {
                $j = 0;

                while ($reader->read() && $j < 4) // run through each tweet
                {
                    if ($reader->nodeType == XMLReader::ELEMENT && $reader->name == $tags[$j])
                    {
                        while ($reader->read())
                        {
                            if ($reader->nodeType == XMLReader::TEXT && $j == 0)
                            {
                                if ($k) {
                                $tweets[$i][$local[$j]] = $reader->value;
                                $j++;
                                $k=0;}
                                else {$k=1;}
                                break;
                            }
                            else if ($reader->nodeType == XMLReader::TEXT)
                            {
                                $tweets[$i][$local[$j]] = $reader->value;
                                $j++;
                                break;
                            }
                        }
                    }
                }

                $i++;
            }

            $returner = "";

            foreach ($tweets as $value) {
                if ($value[user] != 'fugataquintet') {
                    $returner .= '<li class="retweet">';
                } else {
                    $returner .= '<li>';
                }
                $messager = $value[msg];
                $messager = " ".preg_replace( "/(([[:alnum:]]+:\/\/)|www\.)([^[:space:]]*)"."([[:alnum:]#?\/&=])/i", "<a href=\"\\1\\3\\4\" target=\"_blank\">"."\\1\\3\\4</a>", $messager);
                $messager =  preg_replace( "/ +@([a-z0-9_]*) ?/i", " <a href=\"http://twitter.com/#!/\\1\" target=\"_blank\">@\\1</a> ", $messager);
                $messager = preg_replace( "/ +#([a-z0-9_]*) ?/i", " <a href=\"http://twitter.com/search?q=%23\\1\" target=\"_blank\">#\\1</a> ", $messager);
                $returner .= '<span>'.$messager.'</span><a class="datereplace" href="http://twitter.com/#!/fugataquintet" title="'.$value[time].'">'.$value[time].'</a></li>';
            }

            touch ( $cfile );
            file_put_contents ( $cfile, strval($returner) );
            return  $returner;    
        }    
    } else {    
        $returner = file_get_contents ( $cfile );
        return  $returner;    
    }
}
9
  • Does $cfile even exist? You're checking if it exists, but has it been created? Commented May 2, 2012 at 4:44
  • what is this > strtotime(date('Y-m-d H:i:s')) Commented May 2, 2012 at 4:53
  • $cfile does not exist when that message fires, that is what I check for there. It does get created though, and if the file is not 'old' [ not >= 100 ] the script does pull directly from $cfile and displays the tweets properly. But it seems that something removes the file, ether outside the script or due to the way I create it. Is this some normal behavior? Commented May 2, 2012 at 5:02
  • @scuzzy : php.net/manual/en/function.strtotime.php Commented May 2, 2012 at 5:04
  • 1
    @BorisKourt time() should be sufficient. Commented May 2, 2012 at 5:14

2 Answers 2

2

The code posted suffers from a classic race condition for dealing with filesystems; one description lives at OWASP: https://www.owasp.org/index.php/File_Access_Race_Condition:_TOCTOU

As you are on a shared host, someone else may be periodically cleaning out the system temp directory. If you need a more permanent cache, try saving the file somewhere else.

The following code checks if the file exists, and if not, creates it, and keeps it open. This prevents the file from being deleted by another process (eg temp directory emptier) until the function exits.

<? //PHP 5.4+
function getTweets($num){
    //This will keep the file open, 
    //so that the file cannot be deleted during when this function executes.
    $file = new \SplFileObject(
        \sys_get_temp_dir() . '/e1z' . $type . \sha1('something'),
        'c+' //
    );

    if ($file->getSize() !== 0 && 
        \time() - $file->getMTime() < 100)
    {
        $contents = '';
        foreach($file as $line){
            $contents .= $line;
        }
        return $contents;
    }

    //Get data from twitter
    //Write it to $file
    //return data from twitter
}
?>
Sign up to request clarification or add additional context in comments.

Comments

0

Could you try this simplified version of your expiration checking?

if(is_file($cfile) == false OR filemtime($cfile) < time() - 100)
{
  // fetch here
}
else
{
  // load from cache here
}

Edit: I've also made a cut down version that fetches

function CacheTweetsXML($num)
{
  $cfile = sys_get_temp_dir().'/e1z'. $type . md5 ( 'something' );
  if(is_file($cfile) == false OR filemtime($cfile) < time() - 100)
  {
    // fetch here
    $url = 'http://api.twitter.com/1/statuses/user_timeline.xml?screen_name=boriskourt&include_rts=true&count=' . $num;
    if($xml = @file_get_contents($url) and @file_put_contents($cfile,$xml))
    {
      return $xml;
    }
    elseif(is_file($cfile)) // can't load, return from cache
    {
      return file_get_contents($cfile);
    }
    else // cant load and isn't cached, return false
    {
      return false;
    }
  }
  else // load from cache here
  {
    return file_get_contents($cfile);
  }
}

also don't forget to clean up $type

1 Comment

I will modify my code accordingly, but the main issue has been clarified above if interested. Thank you! Note: I can't vote up yet, need 15 rep. But I will once I get there.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.