PHP Archives

Introduction

Another feature of PHP 5.3 is PHAR. The ability to run whole php applications from one archive. In this article (you could call it workshop) I will show how they are created and how to develop a small blog application inside one of those archives.

Configuration

Configuration of PHAR is easy. As it is linked to php5.3 statically you don't have to activate it. Only thing you might want to do is map the file extension ".phar" to php. To do this just add the following line, depending on the webserver in use, to your configuration.

Apache (mod_php) AddType application/x-httpd-php .phar

Lighttpd (fastcgi) fastcgi.map-extensions = ( ".phar" => ".php" )

Note: This is not necessarily required as php runs the phar extension as long as ".phar" is part of the file extension. So "someArchive.phar.php" works!

First Archive

Now that everything works and Phar is available, we create our first archive. Create a directory called 'listing1' and put a file (let's take index.php) with the following content in there:

Listing 1 (index.php) <?php echo "I am in a PHP archive!"; ?>

This simple program will be enough to create our first archive. Create another file in your directory:

Listing 1 (createPhar.php) <?php $phar = new Phar('listing1.phar'); $phar->addFile('index.php'); ?>

Make sure the current directory is writable by the script and run it. The script above creates a new php archive named "listing1.phar" by passing the name to the constructor of the Phar class. We use addFile( $file, $local) here to add a single file to the archive. You can now call 'listing1.phar' in your browser (if the extension .phar is not associated with php you would have to rename it to 'listing1.phar.php'. '.phar' must be a part of the extension!). When calling the phar, you'll get automatically redirected to "index.php" which is the default directory index.

Similar to manifest files in Java we define a file which is used as bootstrap stub by calling the methods string createDefaultStub( void ) - to create a default stub (can be more than one) - and void setStub(string $stub) to set the created stub. The difference between a manifest and a stub is

Including images

After this I was asking myself if it's possible to include media files like images into PHP archives as well. I created a directory named listing2 by copying the first one, added an image and inserted the required html tag to show the image. I created an archive and.. indeed, it works! See listing2 for details.

Listing 2 (index.php) <?php echo "I'm an archive with an image inside.."; ?> <br /><img src="images/logo.png" alt="logo" />

Of course you will have to put an image named "logo.png" into the directory "images" if you just copy and paste the source.

Listing 2 (createPhar.php) <?php $phar = new Phar('listing2.phar'); $phar->addFile('index.php'); $phar->addFile('images/logo.png'); ?>

As you can see, there is no big difference to the first one. Only one new directive to add the image file. Create the archive and execute it.

Developing a blog module

Idea

We can use PHP archives to store whole applications, including images, templates and source. So instead of distributing a bunch of files seperately, we pack them together in an archive. Let's take a small blog application as an example.

Structure

ToDO: no SQLite inside. A blog needs a configuration file, which the user should be able to modify. We can't expect the user to modify the archive manually, so the file needs to be saved externally. The Phar class provides a way of mounting external files into an archive at runtime, which we will make use of to ease getting the configuration.

Step 1: Mounting the configuration

Before mounting a configuration we will have to create one. I choosed an XML-based configuration with the following format:

Listing 3 (config.xml= <?xml version="1.0" encoding="UTF-8"?> <config> <author>Some One</author> <title>My blog</title> <description>..about stuff</description> </config> I saved the file to a subdirectory named "data". As this file is only for testing purposes you will have to remove it later, to let the installation generate the configuration.

Listing 3 (createPhar.php) <?php $phar = new Phar('blog.phar'); $phar->addFile('index.php'); $phar->setStub("<?php if(file_exists( __DIR__ . '/data/config.xml')) { Phar::mount('config.xml', __DIR__ . '/data/config.xml'); } Phar::webPhar('blog', 'index.php'); include 'phar://blog/index.php'; __HALT_COMPILER(); ?>"); ?>

Listing 3 (index.php) <?php header('Content-type: application/xml'); echo file_get_contents('config.xml'); ?>

This time we only check if the stub does the expected work and mounts the configuration file into our archive. Since I choosed XML for our config we have to send the correct header, so that the data also gets interpreted as XML as well.

Step 2: Provide installation

When installing new applications there are always things that need to be configured. In our case we want to specify author, description and title of the blog and generate the xml configuration. We also have to check if the directory where configuration and database will be stored is writeable.

Listing 4 (install.php) <?php if(!is_writable(dirname(Phar::running(false).'/data'))) { echo "<p>No permissions to write to 'data'</p>"; exit; } ?> [..]

There is only one new function introduced here: string running( bool ). This functions either return a phar URL or the full path to the running phar archive. Since the installation script is too long to be posted here I shortened it a bit.

Listing 4 (index.php) <?php if (!file_exists(__DIR__.'config.xml')) { echo "No configuration file found. Please <a href='install.php'>install</a> first!"; exit; } echo "Your blog is now installed and ready to use!"; ?>

Step 3: The SQLite database

Every entry of our blog will be stored in a small SQLite3 database. We will use only one table to store the data.

Listing 5 (blog.sql) CREATE TABLE post( post_id INTEGER PRIMARY KEY, title TEXT, entry TEXT);

Now we will use this piece of SQL in our installation:

Listing 5 (part of install.php) if (file_exists(__DIR__.'/config.xml')) { // If the file exists, installation has already // been done. header("Status: 404 Not Found"); exit; } [..] // Creating database $db = new PDO('sqlite:'.$path.'/data/blog.db'); $sql = "CREATE TABLE post( post_id INTEGER PRIMARY KEY, title TEXT, entry TEXT)"; $db->query($sql);

We also have to check if a configuration has already been created and being mounted when starting the phar. If that is the case installation has already finished and we have to avoid re-execution of the installation.

Step 4: The blog class

A blog class has to implement several functions. We want to be able to add, edit and remove entries from the databse, so the class skeleton would look something like the following:

Listing 5 (class.blog.php) <?php class Blog { private $db; public function __construct(PDO $connection) { $this->db = $connection; } public function addEntry() { } public function editEntry($id) { } public function removeEntry($id) { } } ?>

Of course there has to be some sort of authentication method so that standard users are not able to remove or edit your entries. I don't discuss the implementation of this authentication mechanism here, just take a look at the source code.

Packing

Now that every line of code has been written we just need to pack all our stuff together and create the archive.

<?php $phar = new Phar('blog.phar'); $phar->addFile('index.php'); $phar->addFile('install.php'); $phar->addFile('class.blog.php'); $phar->setStub("<?php if(file_exists( __DIR__ . '/data/config.xml')) { Phar::mount('config.xml', __DIR__ . '/data/config.xml'); } Phar::webPhar('blog', 'index.php'); include 'phar://blog/index.php'; __HALT_COMPILER(); ?>"); ?>

Usage

To use the blog application you will have to keep in mind that there has to be a directory named "data" where configuration and database will be stored after installation. As we save the hashed password in this directory, it might be a good idea to limit access to this directory:

Apache (.htaccess) deny from all

Lighttpd 1.4.x $HTTP["url"] =~ "^/data" { url.access-deny = ("") }

Lighttpd 1.5 $PHYSICAL["path"] !~ "^/data/" { access.deny-all = "enable" }

Download

The full source code of every listing can be downloaded here: Download (.tar.gz)

Advanced usage

Compression

Depending on your build options the phar extension has compiled against bzip2 or gzip. We can use array Phar::getSupportedCompression() to check which compression methods are available. To compress an archive, we add the following lines:

Compress archive <?php $phar->compress(Phar::GZ); // For gzip compression // $phar->compress(Phar::BZ2); // For bzip2 compression ?>

You could also use in_array on Phar::getSupportedCompression() to determine which compression method to use.

Comments (6) |

Comments

Submit Comment