Creating a PHP archive (phar)

Creating a PHP archive (phar)

Java has jar files. PHP has phar files.

Those files are PHP projects packaged. You may know them through usages like composer or PHPUnit. But phar files were designed by taking into consideration the nature of PHP: the web.
Therefore, we're going to try to show how to build a phar either "classic" or "web" oriented in this tutorial.

What is a phar exactly?

A phar is basically many files assembled in only one. But it is composed of the following content:

  • A stub: this is the very first piece of code that will be executed when you run your phar. It can be either automatically generated or entirely customized.
    This part in the file usually starts with <?php and ends with __HALT_COMPILER();.
    The stub is some kind of pre-front-controller. It will drive PHP to get the correct thing depending on the "entry". For that to work, it uses the Phar API.
  • Some binary containing metadata of the package: filenames, their start/end in the current file, some information about the entry point...
  • The concatenation of all your files (potentially compressed)

To generate the phar, we will need to make a builder script (in PHP) and we will use the Phar API.

Making a phar for the cli

Before you begin, you may need to enable a special capability in your php.ini file. The following configuration may be set on your installation:

[Phar]
; http://php.net/phar.readonly
phar.readonly = On
; Set it to Off

It is required to set the option readonly to Off.


The goal will be to generate a file app.phar that is executable from the cli directly. On my side I create a folder named app that contains the two following files:

<?php
// app/index.php

// This will be the entry point

echo "<h1>Hello from PHP</h1>";
<?php
// app/vendor.php

function hello() {
    echo "Im a function part of whatever vendor you may think.";
}

I will then create my builder that uses the class Phar. Here is how to process, I let you go to the documentation to learn more.

<?php
// builder.php

$pharFile = 'app.phar';

// clean up
if (file_exists($pharFile)) 
{
    unlink($pharFile);
}

$phar = new Phar($pharFile);

// First thing to do is to start the buffering
// otherwise no other action will be possible.
$phar->startBuffering();

// Here I get get the default stub for .phar files
// it's allowed to customize it entirely, but it's
// recommended only for advanced usage.
// I also specify my entry point (the front controller)
$defaultStub = $phar->createDefaultStub('index.php');

// Let's all the rest of the files
$phar->buildFromDirectory(__DIR__ . '\\app');

// Customizing the stub
// What we add here allow to execute the file
// without a call to PHP with an unix OS
// *it will not work on windows* (but will not be a problem either)
$stub = "#!/usr/bin/env php \n" . $defaultStub;

// Add the stub
$phar->setStub($stub);

// Generating the file
$phar->stopBuffering();

// Some compression option are available.
// Most common are GZ and ZIP. (for many obvious reason)
// And yes, PHP do it afterwards so it must be at the end.
$phar->compressFiles(Phar::GZ);

# Make the file executable
chmod(__DIR__ . '/app.phar', 0770);

If you run this script, you should see a file app.phar generated. Success!


Let's use this app.phar. Under Linux, as stated in the comments you should be able to use it this way:

./app.phar

If you use Windows, you need to call directly PHP this way:

php app.phar

And finally, if you want to use this phar as a library, you can use the following code:

<?php

require_once 'phar://app.phar/vendor.php';

hello();

Making a phar file for the web

That was easy, isn't it? Let's take this to the next level. If you want to make a phar file ready for the web it's possible.

If you take the last example, just remove the custom stub and execute the phar file with the following command:

php -S localhost:8000 app.phar

Then go to https://localhost:8000, the magic should happen. But there's more!

What is a website without assets? Yes, you can add them into your archive, but everything will pass through your front controller (in my case, index.php). So your code must require the assets when PHP asks for them. Here is an example of support for PNG.

<?php

// current file is index.php, the entry point

function index() {
	echo "<h1>Hello from PHP</h1>";
	echo '<img src="/images/some-image.png" />';
}


// index.php is now mostly a router (a real front controller if you prefer)
switch ($_SERVER['REQUEST_URI'] ?? '/') {
	case '/':
	case '/index.php':
		index();
		break;
	default:
		// PHP will look inside the phar!
		if (file_exists(__DIR__ . $_SERVER['REQUEST_URI'])) {
			// This simple example is crap, but you got the idea.
			if (str_ends_with($_SERVER['REQUEST_URI'], 'png')) {
				header('Content-Type: image/png');
			}
			require __DIR__ . $_SERVER['REQUEST_URI'];
			exit;
		}
		http_response_code(404);
		echo "<h1>The content does not exists";
}

If you really want to do something like this, I suggest you to use the component HttpFoundation of Symfony with its router or some vendor that will do the job for you. (yes, you can add vendors to your folder, it's PHP, it will just work fine)

Finally, I wanted to introduce you to something you can't do: edit a file inside the phar. For example, the following code will actually work:

file_put_contents(__DIR__ . DIRECTORY_SEPARATOR . 'tmp.txt', 'yolo');

But it modifies the phar file itself and corrupts it definitely. Thus you simply can't write a file in the current directory.

Other ways to generate a phar

Let me add one last thing. In every test until now, I suggested you to create a folder and add this one to a file. But there are actually 2 other methods to make a phar.

A phar based on an archive

If you have a zip (or gz or whatever) file, no problem. PHP can create a phar from it. You just need to tell PHP at the instantiation of the phar:

<?php

@unlink('app.phar');
copy('app.tar.gz', 'app.phar');
$phar = new Phar('app.phar');

A phar based on nothing

You can generate the phar file entirely from your script, here is an example:

<?php

$phar = new Phar('app.phar');
$phar->startBuffering();
$phar['index.php'] = '<?php include "config.php"; echo $username;';
$phar['config.php'] = '<?php $username = "Nek";';
// ... You already know the end

You can also combine all the methods and finally override some files with this last one.

Hope you liked it! Find out something missing? Feel free to comment!

Follow us on Twitter! https://twitter.com/swagdotind