First steps with phar archives

While phar archives in PHP are nothing new I never had time nor interest in taking a look at them. Actually I think it is pretty nice to just deploy a single file if you are using PHP to do cli-based stuff. Not saying thats the only useful case for it. Let us take a look…

Assuming I’d like a php written (why would someone do something like that in PHP and not in Bash? Uhm… Dunno) cli tool…

Directory Structure

I am choosing something simple:

  • src/
    • RaidMonitor/
      • controller/
      • helper/
      • models/
      • views/
  • tests/

Putting together a first phar

At the same level where the src- and test-folders are, I create an empty generate.php, which I’ll use to generate the phar. Putting together a .phar is a nightmare if you never did that before. Most of the examples in the PHP documentation do not work as they are / out of the box.

For example:

< ?php
$phar = new Phar('rmonitor.phar', 0, 'rmonitor.phar');
$phar->buildFromIterator(
    new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator(dirname(__FILE__).'/src/')),
    dirname(__FILE__));
$phar->setStub($phar->createDefaultStub('RaidMonitor/controller/Router.Class.php'));

Should create a rmonitor.phar which recursively adds all files and directories from the src/ folder in our basedir (which I just set to dirname(__FILE__) .. so the folder I am calling the generate.php from). Now it seems there is no easy way to verify that this is correct. If I try to unpack that to the folder „foo“:

< ?php
$phar = new Phar('rmonitor.phar');
$phar->extractTo(dirname(__FILE__).'/foo', null, true);

I end up with foo/src/RaidMonitor where RaidMonitor is not a directory but just an empty file. Furthermore I get the very useful warning

Phar::extractTo(/home/jean/Projekte/websites/RaidMonitor/foo/src/RaidMonitor/helper): failed to open stream: No such file or directory in /home/jean/Projekte/websites/RaidMonitor/unpack.php on line 3

No other files in foo/src/. If I do it exactly like in the example on php.net, by defining my project dir absolutely for both the directoryiterator and the base directory:

< ?php
$phar = new Phar('rmonitor.phar', 0, 'rmonitor.phar');
$phar->buildFromIterator(
    new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator('/home/jean/Projekte/websites/RaidMonitor/src/')),
    '/home/jean/Projekte/websites/RaidMonitor/src/');
$phar->setStub($phar->createDefaultStub('RaidMonitor/controller/Router.Class.php'));

I get the fatal error:

Uncaught exception ‚UnexpectedValueException‘ with message ‚Iterator RecursiveIteratorIterator returned a path „/home/jean/Projekte/websites/RaidMonitor/src“ that is not in the base directory „/home/jean/Projekte/websites/RaidMonitor/src/“‚

Which means the example here http://php.net/manual/en/phar.buildfromiterator.php simply is wrong (or simply is an example). However, the fix for this is easy and can be found in the comments section. RecursiveDirectoryIterator seems to follow „.“ and „..“ and hence the error. You need to add: FilesystemIterator::SKIP_DOTS as parameter. So it should look like this:

< ?php
$phar = new Phar('rmonitor.phar', 0, 'rmonitor.phar');
$phar->buildFromIterator(
    new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator(dirname(__FILE__).'/src/', FilesystemIterator::SKIP_DOTS)),
    dirname(__FILE__).'/src');
$phar->setStub($phar->createDefaultStub('init.php'));

And the unpack.php does work as well. You might have noticed that I did add .’/src‘ to the base_dir, either that or I need to add src/ before RaidMonitor in my default stub. I also changed the default stub to init.php because I want to initialize my router from there.

Writing init.php

don’t laugh, it’s just an example 🙂

< ?php
namespace RaidMonitor;
 
use RaidMonitor\controller\Router;
 
require_once('helper/Autoloader.Class.php');
 
$router = new Router($_SERVER['argv']);
$router->route();

Writing the controller

again, just an example.

< ?php
/**
 *
 */
namespace RaidMonitor\controller;
 
/**
 * Class Router
 * @package RaidMonitor\controller
 */
class Router
{
    /**
     * @var string
     */
    private $action = "";
    /**
     * @var string
     */
    private $scriptName = "";
    /**
     * @var array
     */
    private $args = array();
 
    /**
     * @param array $args
     */
    public function __construct(array $args = array())
    {
        // the first element is the script name
        $this->scriptName = $args[0];
        array_shift($args);
 
        // the second element is the action
        if (isset($args[0])) {
            $this->action = $args[0];
            array_shift($args);
 
            if (isset($args[0])) {
                // all further elements are parameters
                $this->args = $args;
            }
        }
    }
 
    /**
     *
     */
    private function detectAction()
    {
        echo "detect";
    }
 
    /**
     *
     */
    private function reportAction()
    {
        echo "report";
    }
 
    /**
     *
     */
    private function missingAction()
    {
        echo "You did not provide any action. Performing controller detection";
        $this->detectAction();
    }
 
    /**
     *
     */
    private function unimplementedAction()
    {
        echo "this action has not been defined yet";
    }
 
    /**
     *
     */
    public function route()
    {
        if (empty($this->action)) {
            $this->missingAction();
        } else if ($this->action == 'detect') {
            $this->detectAction();
        } else if ($this->action == 'report') {
            $this->reportAction();
        } else {
            $this->unimplementedAction();
        }
    }
}

Running it

jean@andor ~/Projekte/websites/RaidMonitor $ php generate.php 
jean@andor ~/Projekte/websites/RaidMonitor $ php rmonitor.phar 
You did not provide any action. Performing controller detectiondetectjean@andor ~/Projekte/websites/RaidMonitor $
jean@andor ~/Projekte/websites/RaidMonitor $ php rmonitor.phar report
reportjean@andor ~/Projekte/websites/RaidMonitor $

So far for my first steps with PHAR.

No Comments

Post a Comment