DEV Community

loading...

Uploading PDF files as base64 strings in PHP and saving it in the hard-disk.

Rogerio Taques
Solo maker of mailshld.com, 3o2.co & ziip.link. Helping localize the Internet at WOVN.io. Occasionally writing on Dev.to, Medium and at rogeriotaques.wordpress.com!
・3 min read

Hi there!

If you’re reading this post, chances are you’re having some issues when trying to upload (PDF) files as base64 strings to later store them “physically” in the hard-disk. No worries, it might be simpler than you think and I got you covered in this article. 😉

I’ve spent a few hours on the other day trying to figure out how to do it right. Neither Google nor Stackoverflow helped me at that time, as I searched for a while without any luck on finding any working solution! So, I hope this can help you.

First, a bit of context.

My team and I am building a Single Page Application (SPA) CRM which has a feature where the employees are required to upload a PDF file, via an ajax call against a RESTful API. As per the requirements, once received, the PDF file should be “physically” stored in the server alongside other documents (not in the database).

The approach taken was to convert the PDF file into a base64 string and submit that alongside the complementary data via POST method. The API, then, would be responsible to get that string and reconstruct the file to be stored in the disk.

Implementing this was fairly simple, however, it took us a while until we manage to properly revert the base64 string into the PDF file to be saved.

Here is how we’ve initially coded it:


// <file>.ts

// Converting a PDF file to a base64 string with Javascript. 
// This method is part of a bigger Vue.js component.

getFileInfo(evt: any): void {
  const reader: FileReader = new FileReader();
  const files: Array<any> = evt.target.files || evt.dataTransfer.files;

  reader.addEventListener(
    'load',
    () => {
      this.form.file_path = reader.result;
    },
    false
  );

  if (files[0]) {
    reader.readAsDataURL(files[0]);
  }
} // getFileInfo

Enter fullscreen mode Exit fullscreen mode

// <file>.php
// The non-working solution for converting a base64 to PDF with PHP.

public function uploadFileFromBlobString($base64string = '', $file_name = '', $folder = '')
{
    $file_path = "";
    $result = 0;

    // Convert blob (base64 string) back to PDF
    if (!empty($base64string)) {
        $base64data = base64_decode($base64string, true);
        $file_path  = "{$folder}/{$file_name}";

        // Return the number of bytes saved, or false on failure
        $result = file_put_contents("{$this->_assets_path}/{$file_path}", $base64data);
    }

    return $result;
} // uploadFileFromBlobString

Enter fullscreen mode Exit fullscreen mode

With the above approach, everything was working fine except by the fact that the saved PDF file was getting “corrupted” and one was not been able to open it back.

The problem was that when converting the PDF file to a base64 string, it receives an extra piece of data as a prefix ( data:application/pdf;base64,) which when converted back to PDF simply corrupts the file.

So, as you may have already figured out, the solution was simply removing the base64 header from the string before converting it back to PDF, as follows:


// <file>.php
// The working solution for converting a base64 string to a PDF file with PHP. 

public function uploadFileFromBlobString($base64string = '', $file_name = '', $folder = '')
{

    $file_path = "";
    $result = 0;

    // Convert blob (base64 string) back to PDF
    if (!empty($base64string)) {

        // Detects if there is base64 encoding header in the string.
        // If so, it needs to be removed prior to saving the content to a phisical file.
        if (strpos($base64string, ',') !== false) {
            @list($encode, $base64string) = explode(',', $base64string);
        }

        $base64data = base64_decode($base64string, true);
        $file_path  = "{$folder}/{$file_name}";

        // Return the number of bytes saved, or false on failure
        $result = file_put_contents("{$this->_assets_path}/{$file_path}", $base64data);
    }

    return $result;
} // uploadFileFromBlobString

Enter fullscreen mode Exit fullscreen mode

As you may see above, to get it working we just had to include the following conditional block to the function we’ve written before:


// Detects if there is base64 encoding header in the string.
// If so, it needs to be removed prior to saving the content to a physical file.
if (strpos($base64string, ',') !== false) {
    @list($encode, $base64string) = explode(',', $base64string);
}


Enter fullscreen mode Exit fullscreen mode

And if the base64 prefix is found, all that needs to be done is removing it from the string before writing it back in the hard disk as a PDF file! 🤘

That’s pretty much it.

I hope it can help you to not be struggling with such a simple issue, as I was for a few hours until figure this out! 😉

Happy coding! 🤘

Discussion (8)

Collapse
sarneeh profile image
Jakub Sarnowski • Edited

Isn't it a lot easier to upload files using multipart/form-data? I think that using base64 is a lot of unnecessary logic.

Collapse
rogeriotaques profile image
Rogerio Taques Author

Yo! Thanks for your comment. 🙇‍♂️

I think it heavily depends on the architecture of your app.

In our case here, the app doesn't really have a form in the first place, so all the data is gathered from the component state and sent as a JSON to the API, therefore we're not actually submitting a form.

Also, on the backend, files are not stored in the hard-disk but in the database as base64 strings alongside many other meta-information which are used later on.

Collapse
sarneeh profile image
Jakub Sarnowski

You don't need a form to accept multipart data. It's a common pattern to use it in API's.

Also, storing files as base64 strings in a database is a bad idea as again you have this unnecessary encoding/decoding overhead. Why not just store the data as binary data blobs?

I just think that using base64 for that is just a performance waste for your API and your API clients.

Thread Thread
rogeriotaques profile image
Rogerio Taques Author

Sounds like a point! 🙂Tbh, I couldn't find a consensus about what would be the best-practice for that, so I'm curious about how would you do it. Would you mind to give me an example?

Thread Thread
sarneeh profile image
Jakub Sarnowski

I took example from the Google Drive API. You can easily send files + metadata (even as JSON!) without any encoding/decoding and you have binary data right in your API.

According to storing files in database as base64 strings, take a look here.

What do you think about that? 😃 I think you can gain a lot of performance this way.

Thread Thread
rogeriotaques profile image
Rogerio Taques Author

Interesting, man!

Not really sure about the real benefit of not using base64 as a transport wrapper when posting data to the API (in my particular scenario) , but I’m definitely convinced of the advantages of storing the binary instead.

Thanks a lot for the contribution. 🙌 I’ll take the opportunity with a new project here and try this approach out.

Thread Thread
sarneeh profile image
Jakub Sarnowski

Yeah I don't mean that your approach is wrong, it will surely work! I just wanted to propose a better approach 😃

Thread Thread
rogeriotaques profile image
Rogerio Taques Author

Sure, I didn’t take it wrong! 😉 It’s awesome have the chance to see what other devs would do to solve situations we’ve faced before. 🤘 Really glad you’ve commented.