雖然 Twitter API 已升級至 v2,但本封裝類展示了使用 cURL 呼叫 REST API、XML 解析、實體連結化等核心技術,可作為社交媒體 API 整合的參考架構。
<?php
class TwitterClient {
private const API_BASE = 'https://api.twitter.com/1.1/statuses/user_timeline.xml';
private const ENTITY_REGEX_URL = '@(https?://([-\w\.]+)+(:\d+)?(/([\w/_\.]*(\?\S+)?)?)?)@';
private const ENTITY_REGEX_MENTION = "/(^|[\n ])@([^ \"\t\n\r<]*)/";
private const ENTITY_REGEX_HASHTAG = "/(^|[\n ])#([^ \"\t\n\r<]*)/";
/**
* 獲取用戶推文並生成 HTML
* @param string $screenName Twitter 用戶名(不含 @)
* @param int $count 獲取條數(最大 200)
* @param string $timezone 時區識別碼
* @param bool $includeReplies 是否包含回覆推文
* @return string HTML 片段
*/
public function getUserTimelineHtml(string $screenName, int $count = 3, string $timezone = 'Asia/Taipei', bool $includeReplies = false): string {
date_default_timezone_set($timezone);
$xml = $this->fetchTimelineXml($screenName, $count);
if(!$xml) {
return $this->renderError('Twitter 服務暫時無法訪問,請稍後再試。');
}
$html = '<div id="tweets">' . "\n";
$tweetCount = 0;
foreach($xml->status as $status) {
// 過濾回覆推文
if(!$includeReplies && $this->isReply($status->text)) continue;
if($tweetCount >= $count) break;
$tweetText = $this->processEntities($status->text);
$createdAt = date('D jS M Y H:i', strtotime($status->created_at));
$html .= '<div class="status">' . "\n";
$html .= $tweetText . "\n";
$html .= '</div>' . "\n";
$html .= '<div class="details">' . $createdAt . '</div>' . "\n\n";
$tweetCount++;
}
$html .= '</div>' . "\n";
return $html;
}
/**
* 呼叫 API 獲取 XML 回應
*/
private function fetchTimelineXml(string $screenName, int $count): ?SimpleXMLElement {
$url = self::API_BASE . "?screen_name=" . urlencode($screenName) . "&count=" . min($count, 200);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_USERAGENT => 'PHP-TwitterClient/1.0',
]);
$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if($httpCode !== 200 || !$response) {
error_log("Twitter API error: HTTP $httpCode for $screenName");
return null;
}
try {
return new SimpleXMLElement($response);
} catch(Exception $e) {
error_log("Twitter XML parse error: " . $e->getMessage());
return null;
}
}
/**
* 處理推文中的實體:URL、@提及、#標籤轉為可點擊連結
*/
private function processEntities(string $text): string {
$text = utf8_decode($text);
// URL 連結化
$text = preg_replace(self::ENTITY_REGEX_URL, '<a href="$1" target="_blank" rel="noopener">$1</a>', $text);
// @提及 連結化
$text = preg_replace_callback(self::ENTITY_REGEX_MENTION, function($m) {
return $m[1] . '<a href="https://twitter.com/' . urlencode($m[2]) . '" target="_blank" rel="noopener">@' . $m[2] . '</a>';
}, $text);
// #標籤 連結化
$text = preg_replace_callback(self::ENTITY_REGEX_HASHTAG, function($m) {
return $m[1] . '<a href="https://twitter.com/hashtag/' . urlencode($m[2]) . '" target="_blank" rel="noopener">#' . $m[2] . '</a>';
}, $text);
return $text;
}
/**
* 判斷是否為回覆推文(以 @ 開頭)
*/
private function isReply(string $text): bool {
return strpos(ltrim($text), '@') === 0;
}
private function renderError(string $message): string {
return '<div id="tweets"><div class="status">' . htmlspecialchars($message) . '</div></div>';
}
}
// 使用範例
$twitter = new TwitterClient();
echo $twitter->getUserTimelineHtml('php_net', 5, 'Asia/Taipei');
?>
#tweets { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; max-width: 500px; }
.status { padding: 12px 0; border-bottom: 1px solid #e1e8ed; line-height: 1.5; word-wrap: break-word; }
.status a { color: #1da1f2; text-decoration: none; }
.status a:hover { text-decoration: underline; }
.details { font-size: 0.85em; color: #657786; margin-top: 4px; }
https://api.twitter.com/2/users/:id/tweetscreated_at 格式不同)expansions、tweet.fields、media.fields)// v2 版本獲取用戶推文(需 Bearer Token)
private function fetchTimelineV2(string $userId, int $count): ?array {
$url = "https://api.twitter.com/2/users/$userId/tweets?max_results=" . min($count, 100)
. "&tweet.fields=created_at,public_metrics,entities";
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $this->bearerToken],
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
curl_close($ch);
return $response ? json_decode($response, true) : null;
}