class CloudflareR2Deleter { private $accountId; private $accessKey; private $secretKey; private $bucketName; private $region = 'auto'; private $service = 's3'; public function __construct($accountId, $accessKey, $secretKey, $bucketName) { $this->accountId = $accountId; $this->accessKey = $accessKey; $this->secretKey = $secretKey; $this->bucketName = $bucketName; } public function delete($objectKey) { $encodedKey = $objectKey; $url = "https://{$this->accountId}.r2.cloudflarestorage.com/{$this->bucketName}/{$encodedKey}"; $date = $this->getDateString(); $timeString = $this->getTimeString(); $payloadHash = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; $host = "{$this->accountId}.r2.cloudflarestorage.com"; $canonicalHeaders = [ 'host' => $host, 'x-amz-content-sha256' => $payloadHash, 'x-amz-date' => $timeString, 'content-length' => '0' ]; ksort($canonicalHeaders); $canonicalHeadersStr = ''; foreach ($canonicalHeaders as $key => $value) { $canonicalHeadersStr .= $key . ':' . trim($value) . "\n"; } $signedHeaders = implode(';', array_keys($canonicalHeaders)); $canonicalRequest = "DELETE\n/{$this->bucketName}/{$encodedKey}\n\n" . rtrim($canonicalHeadersStr, "\n") . "\n\n" . $signedHeaders . "\n" . $payloadHash; $canonicalRequestHash = hash('sha256', $canonicalRequest); $stringToSign = "AWS4-HMAC-SHA256\n{$timeString}\n{$this->getCredentialScope()}\n{$canonicalRequestHash}"; $signature = $this->generateSignature($stringToSign); $authorizationHeader = "AWS4-HMAC-SHA256 Credential={$this->accessKey}/{$this->getCredentialScope()}, SignedHeaders={$signedHeaders}, Signature={$signature}"; $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => 'DELETE', CURLOPT_HEADER => true, CURLOPT_HTTPHEADER => [ "Authorization: {$authorizationHeader}", "x-amz-content-sha256: {$payloadHash}", "x-amz-date: {$timeString}", "Host: {$host}", "Content-Length: 0" ], CURLOPT_VERBOSE => false ]); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); $error = curl_error($ch); curl_close($ch); if ($error) { throw new Exception("cURL Error: $error"); } if ($httpCode !== 204) { throw new Exception("Failed to delete file. HTTP Code: $httpCode"); } return true; } private function generateSignature($stringToSign) { $kDate = hash_hmac('sha256', $this->getDateString(), "AWS4{$this->secretKey}", true); $kRegion = hash_hmac('sha256', $this->region, $kDate, true); $kService = hash_hmac('sha256', $this->service, $kRegion, true); $kSigning = hash_hmac('sha256', 'aws4_request', $kService, true); return hash_hmac('sha256', $stringToSign, $kSigning); } private function getDateString() { return gmdate('Ymd'); } private function getTimeString() { return gmdate('Ymd\THis\Z'); } private function getCredentialScope() { return "{$this->getDateString()}/{$this->region}/{$this->service}/aws4_request"; } }
Usage Example:
function cloudflare_delete($key) { $deleter = new CloudflareR2Deleter( $accountId, $accessKey, $accessSecret, $bucket ); try { return $deleter->delete($key); } catch (Exception $e) { echo "Error: " . $e->getMessage() . "\n"; } }