Hi there, I hope you're doing well. Firstly, this tutorial is somewhat Laravel-specific, but I assure you that you can apply the approach outlined here with vanilla PHP or any PHP framework you prefer.
Microsoft retired the PHP Azure Storage SDK, which means there will be no maintenance. So, if this is a concern for you, you may want to consider trying the REST API approach.
In this article, we are going to quickly highlight how you can upload images, videos or any media file to Azure using Azure Blob Storage REST API.
For sheer simplicity of a simple tutorial, let's just have a php method to put our logic and send a request to azure.
Like every normal PaaS out there, we will need some basic credentials to authorize access to the azure blob upload service.
Login in to your azure portal to get these credentials and also create a container then replace the placeholders in the following code with your credentials.
Typically, you want to protect these credentials by fetching them from your .env
file. It's ideal not to hardcode your credentials.
public function uploadToAzureCloud($request)
{
$storageAccountName = config('services.azure_storage.account_name');
$containerName = config('services.azure_storage.container');
$accessKey = config('services.azure_storage.key');
}
For the following code, we want to ensure that the request has a file. Then, we obtain the original filename, create a hashed variant for the filename that we are going to use as our blob name, and also obtain the file size.
// the previous code
if ($request->hasFile('file')) {
$file = $request->file('file');
$orignalFileName = $file->getClientOriginalName();
$mimeType = $file->getMimeType();
$blobName = $file->hashName();
$fileSize = filesize($file->path());
//the upcoming code
}
Constructing an Azure Blob Signature
Next up is constructing an azure blob signature.
A Shared Access Signature (SAS) is a security token that provides restricted access to certain resources in cloud-based storage services, such as Azure Blob storage or Azure File storage. SAS enables secure and controlled access to these resources without exposing the account's access keys or credentials.
A SaS token or signature is a string that azure demands that lets us make upload media files. The following code, generates SaS token.
$dateTime = gmdate('D, d M Y H:i:s \G\M\T');
$urlResource = "/$storageAccountName/$containerName/{$blobName}";
$headerResource = "x-ms-blob-cache-control:max-age=3600\nx-ms-blob-type:BlockBlob\nx-ms-date:$dateTime\nx-ms-version:2019-12-12";
// Generate signature
$arraysign = []; // initiate an empty array (don't remove this π)
$arraysign[] = 'PUT'; /*HTTP Verb*/
$arraysign[] = ''; /*Content-Encoding*/
$arraysign[] = ''; /*Content-Language*/
$arraysign[] = $fileSize; /*Content-Length (include value when zero)*/
$arraysign[] = ''; /*Content-MD5*/
$arraysign[] = $mimeType; /*Content-Type*/
$arraysign[] = ''; /*Date*/
$arraysign[] = ''; /*If-Modified-Since */
$arraysign[] = ''; /*If-Match*/
$arraysign[] = ''; /*If-None-Match*/
$arraysign[] = ''; /*If-Unmodified-Since*/
$arraysign[] = ''; /*Range*/
$arraysign[] = $headerResource; /*CanonicalizedHeaders*/
$arraysign[] = $urlResource; /*CanonicalizedResource*/
// converts the array to a string as required by MS
$str2sign = implode("\n", $arraysign);
$sig = base64_encode(hash_hmac('sha256', utf8_encode($str2sign), base64_decode($accessKey), true));
Your major focus should be on the $sig
variable that holds the final outcome of the sas token construct. Take note of the content-Type
comment: you can adjust this field with your desired content type.Now with the SaS token signature covered, we can now make our REST API call.
Uploading media file
We are making use of guzzle for the API request.
The $url
variable we will be making the request to is in this format:
"https://<AzureStorageName>.blob.core.windows.net/<containerName>/<blobName>";
The HTTP verb to use is "PUT" followed by a header construct to pass certain header values like Authorization, etc.
// the previous code
$url = "https://$storageAccountName.blob.core.windows.net/$containerName/{$blobName}";
// use GuzzleHttp\Client;
$client = new Client();
$response = $client->request('PUT', $url, [
'headers' => [
'Authorization' => $authHeader,
'x-ms-blob-cache-control' => 'max-age=3600',
'x-ms-blob-type' => 'BlockBlob',
'x-ms-date' => $dateTime,
'x-ms-version' => '2019-12-12',
'Content-Type' => $mimeType,
'Content-Length' => $fileSize
],
'body' => fopen($file->path(), 'r'),
]);
//the next code up coming
Of course, we assigned a body to the put request. The body contains the actual file that we are trying to upload to azure.
Generating a url for our uploaded image.
If our upload is successful, Azure Blob Storage API sends a 200
response status code. If we confirm that our request is successful, we need to generate a media url to use in displaying images on the frontend.
To make sure we get a proper url that should work, we will generate a sas token from azure dashboard. This sas token is what will we use as our $mediaUrlSasToken
. Follow the steps mentioned in this Azure guide to generate one
An example of interface to generate a Sas Token
Once we have the the token, replace the token with the placeholder assigned to $urlSasToken
if ($response->getStatusCode() == 201)
{
// image sas token
$urlSasToken = 'generated_sas_token';
return response()->json(
[
'original_name' => $originalFIleName,
'media_url' => "$url?$urlSasToken",
]
);
} else {
return response() -> json (['message' => 'Something went wrong']);
}
Result
A successful request (201
status code) will look like this:
The full composite of the $mediaUrl
is as follows:
"https://<AzureStorageName>.blob.core.windows.net/<containerName>/<blobName>?<urlSasToken>";
Complete Code:
public function uploadToAzureCloud($request)
{
try {
$storageAccountName = config('services.azure_storage.account_name');
$containerName = config('services.azure_storage.container');
$accessKey = config('services.azure_storage.key');
if ($request->hasFile('file')) {
$file = $request->file('file');
$orignalFileName = $file->getClientOriginalName();
$mimeType = $file->getMimeType();
$blobName = 'folder/'. $file->hashName();
$fileSize = filesize($file->path());
$dateTime = gmdate('D, d M Y H:i:s \G\M\T');
$urlResource = "/$storageAccountName/$containerName/{$blobName}";
$headerResource = "x-ms-blob-cache-control:max-age=3600\nx-ms-blob-type:BlockBlob\nx-ms-date:$dateTime\nx-ms-version:2019-12-12";
// Generate signature
$arraysign = []; // initiate an empty array (don't remove this π)
$arraysign[] = 'PUT'; /*HTTP Verb*/
$arraysign[] = ''; /*Content-Encoding*/
$arraysign[] = ''; /*Content-Language*/
$arraysign[] = $fileSize; /*Content-Length (include value when zero)*/
$arraysign[] = ''; /*Content-MD5*/
$arraysign[] = $mimeType; /*Content-Type*/
$arraysign[] = ''; /*Date*/
$arraysign[] = ''; /*If-Modified-Since */
$arraysign[] = ''; /*If-Match*/
$arraysign[] = ''; /*If-None-Match*/
$arraysign[] = ''; /*If-Unmodified-Since*/
$arraysign[] = ''; /*Range*/
$arraysign[] = $headerResource; /*CanonicalizedHeaders*/
$arraysign[] = $urlResource; /*CanonicalizedResource*/
// converts the array to a string as required by MS
$str2sign = implode("\n", $arraysign);
$sig = base64_encode(hash_hmac('sha256', utf8_encode($str2sign), base64_decode($accessKey), true));
$url = "https://$storageAccountName.blob.core.windows.net/$containerName/{$blobName}";
// use GuzzleHttp\Client;
$client = new Client();
$response = $client->request('PUT', $url, [
'headers' => [
'Authorization' => "SharedKey $storageAccountName:$sig",
'x-ms-blob-cache-control' => 'max-age=3600',
'x-ms-blob-type' => 'BlockBlob',
'x-ms-date' => $dateTime,
'x-ms-version' => '2019-12-12',
'Content-Type' => $mimeType,
'Content-Length' => $fileSize
],
'body' => fopen($file->path(), 'r'),
]);
if ($response->getStatusCode() == 201) {
// image sas token
$urlSasToken = config('services.azure_storage.sas_token');
return
[
'original_name' => $orignalFileName,
'media_url' => "$url?$urlSasToken",
];
} else {
return response()->json(['message' => 'Something went wrong']);
}
}
} catch (RequestException $e) {
// If there's an error, log the error message
$errorMessage = $e->getMessage();
return response()->json(['error' => $errorMessage], 500);
}
}
Final Thoughts:
This is a REST API approach to uploading media and generating a media url to display the uploaded resource. You can organised your code properly into separate methods or have some blocks like .env
configurations in a separate class or however you want.
Thanks for reading, I hope you do find this resource useful.
Find me on X(Formerly Twitter ) : jovial_core
Also, you can checkout what-company-stack 1.0 , an open source project we released earlier this year.
If you don't mind, there is a sponsorship button on my github profile π
Top comments (2)
This code implementation has saved me a lot of time. Excellent post
Glad to help. I appreciate the feedback