I’m working on a project where I needed to generate a MIME type given a file name. Not only did I need to create a solution that worked, I also needed the solution to be compatible with PHP 4/5 and not require any additional software to be installed on the host. I thought this would be a simple matter of finding a PHP function that does this. Unfortunately, things were not as simple as this.

Problems with finfo_open

I found a very helpful PHP, Mime Types and Fileinfo post on Jelly and Custard. The “Mime Types in PHP 4.x” seemed to be exactly what I wanted. I quickly tried it out on my server, and it failed instantly with the following error:

PHP Fatal error: Call to undefined function finfo_open() in ....

Since I have PHP 5.2.6 running on my dev server, I was very confused since the post said that this was a “PHP 4.x” solution. I pulled up the PHP doc on the finfo_open function and was very surprised to see that the function is a PHP 5.3.0+ function. Fortunately, my version of PHP was just before this so that I could actually catch the error.

Turns out that the Jelly and Custard post points to another post, Installing PECL Modules, where instructions are given for installing the Fileinfo PECL Package. Installing this package allows versions prior to 5.3.0 to use the finfo_* functions via PECL.

This is not what I want. I’m working toward a solution that doesn’t require the installation of any additional software, and so far I’ve only seen solutions that require this. There has to be a better way.

Problems with mime_content_type

I then found the mime_content_type function. This looks better. However, PHP has marked this function as deprecated due to the PECL Fileinfo package. Relying on a deprecated function has numerous problems: it may throw warnings if used on versions of PHP that know the function is deprecated, most likely won’t receive any updates in the future, and could possibly be removed from future versions of PHP.

In addition to the deprecated issue, the mime_content_type function is laced with problems. In order to use the function the PHP on your system must have been built with the --with-mime-magic option. The function also relies upon the mime_magic.magicfile ini configuration to tell it where to find the magic file used to detect the MIME type of the file. This magic file may or may not exist/may or may not be readable.

Initial Testing

These problems led to interesting results when I tested it on my CentOS dedicated and Hostgator servers.

On my CentOS 5.2 dedicated server, I have PHP 5.2.6. The PHP build on this server was not built with the --with-mime-magic option, so it doesn’t have access to the mime_content_type function at all. In addition to this, since the version is just before 5.3 and I haven’t installed the Fileinfo PECL package, my dedicated server does not have access to either of the official PHP solutions without installing additional software.

On my Hostgator shared server, I have access to PHP 5.2.8. Unlike my dedicated server, this server’s PHP build was built with the --with-mime-magic option. “This is great,” I thought. I ran a test, and the mime_content_type function did indeed exist. Like my CentOS server, my Hostgator server does not have the Fileinfo PECL package and does not have PHP 5.3, so the finfo_* functions are no go.

I did some more testing on my Hostgator server and was disappointed to find that the mime_content_type function exists yet is completely worthless. I tested file after file ranging from simple text files, to HTML documents, to a variety of image types. Every single test failed to produce a MIME type. When I say “failed”, I don’t mean that the program crashed with an error. The failure was worse than this, it simply returned an empty string to every single request.

I found that my Hostgator server’s PHP is set up to use the /usr/local/apache/conf/magic file to do it’s MIME magic. However, this file is not able to be read by my user. This means that the version of PHP might as well not have been built with the --with-mime-magic option at all.

It seems clear to me by now that there will not be an easy solution to this problem.

Final Solution

What I need is a solution that will first try to use the Fileinfo functions since they are the current standard. It will then fall back to using the mime_content_type function if and only if the function exists. Since the function is deprecated and PHP versions don’t actually package the replacement functions natively until 5.3.0, I need to also protect the code against conditions where neither Fileinfo nor mime_content_type are available. The final fallback will be manually generating the MIME type based upon an array match.

Using the fallback is not desirable since I probably won’t be updating the MIME types in the array very often, if at all. However, having it is better than having the code completely fail for common, present day MIME types. Hopefully, the conditions necessary to rely upon this fallback will become more and more rare as time goes on.

In addition to checking for the existence of the functions I’ll try to use, I need to make sure that the methods tried actually produce results. If a method fails to produce a non-empty value, I’ll move on to the next method.

The Code

I got the original idea for this code from a comment by svogal on the PHP doc site. I modified it to match my desired final solution.

The code is far to large to post here. You can download it here.

The array that I built for the mime types is quite large. Since I have a /etc/mime.types file (which is standard in most distros and provided by the mailcap package), I simply used it to generate my array. I quickly built a Perl script that parses through the mime.types file and outputs a file containing the PHP code to create the array. This script makes it easy for me to update the array any time the mime.types file is updated.

You can download my Perl script here. Simply run perl generateMimeTypes from the command line to build the array. The array code is put in a file called mime_type_var.code.

Examples

Simple Use

To use the code, simply include or require the code in your own script and then call the get_file_mime_type function by passing the file’s path as the parameter.

For example:

require( 'mime_type_lib.php' );

$mime_type = get_file_mime_type( '/home/user/public_html/image.jpg' );
echo "$mime_type\n";

This produces the following output:

[gaarai@work ~]$ php example.php
image/jpeg

Note that PHP’s more advanced functions have the ability to dig into the content of the file to identify the MIME type, so if your system can make use of those functions and the file is not a JPEG image, the results could vary.

Using the debug Parameter

I’ve also provided a debug parameter that allows you to also get information on what method was used to get the MIME type. This may be helpful if you need to determine how sure you are that the detected MIME type is accurate. When the debug parameter is used, an associative array will be returned with a mime_type key and a method key.

require( 'mime_type_lib.php' );

$data = get_file_mime_type( '/home/user/public_html/image.jpg', true );
echo "MIME Type: ${data['mime_type']}\n";
echo "Method: ${data['method']}\n";

This produces the following output:

[gaarai@work ~]$ php example2.php
MIME Type: image/jpeg
Method: from_array

The possible methods are: fileinfo, mime_content_type, from_array, and last_resort. The first two should be self-explanatory. from_array means that the resulting MIME type was pulled from the array built into the function. last_resort means that the type could not be identified which results in a generic MIME type of application/octet-stream being used.

Conclusion

Frankly, I’m disappointed in the solutions provided by PHP to detect MIME types. I have access to a very stable, highly-regarded shared host and an extremely powerful, up-to-date dedicated host, neither of which have the ability to use either of the PHP solutions to detect MIME types without adding additional software.

I don’t remember which version of PHP my dedicated box started with, but I did upgrade to 5.1.6 using the standard repository back in October. I was only able to update to 5.2.6 by using the Utter Ramblings repository by Jason Litka. True, I could compile and install 5.3 or the PECL package myself, but I produce software for people who don’t have a clue what a compiler is, let alone what repositories or PECL extensions are.

It’s things like this that make development a pain. I could always just be a jerk of a developer, have the line “requires PHP 5.3+”, and tell all people that can’t run the software to check the requirements. However, I think that my job is to make the end-user’s life easier, not more complicated.

Hopefully others that have had woes dealing with this situation can make use of my code, and we can all just wait a few years until the majority of hosts have a PHP version that supports these calls natively.

Did I help you? Send me a tip.