about 4 years ago

Okay, today's the task: pushing all of 1892 files of Yii framework to the Git
repository is a burden, and upgrading it to new version pollutes the git log
with changes to files you don't care about. Let's package it into a PHAR!

Packaging

Rasmus Schultz made a special script to package the Yii framework into
a PHAR archive
. I forked it to save for a future (at least my GitHub account
will live as long as this blog).

You need just to put this script to the root of Yii codebase (cloned github
repo, for example), and run it as usual:

$ php yii-phar.php

This will create the PHAR archive in the same directory.

Hovewer, beware the catch 1: PHP can refuse to create packed archive, emitting
the following error:

PHP Fatal error: Uncaught exception 'BadMethodCallException' with message 'unable 
to create temporary file' in /path/to/your/yii/root/yii-phar.php:142 
Stack trace: 
#0 /path/to/your/yii/root/yii-phar.php(142): Phar->compressFiles(4096) 
#1 {main} thrown in /parh/to/your/yii/root/yii-phar.php on line 142 

I decided to just remove lines 140 and 142 from the script:

echo "Compressing files ...\n\n";
    
$phar->compressFiles($mode); 

And that's all. I can bear with 20 MB file in repo, and don't really care about
compression.

Using

To connect the resulting PHAR to your Yii application, replace your usual:

require_once('/path/to/your/yii/framework/yii.php'); 

With the following:

new Phar('/path/to/yii.phar'); require_once('phar://yii/yii.php');

Note that in new Phar() invocation you should use real path to your phar
archive file, but second line should be written verbatim, as the PHAR which
becomes created is being made with alias 'yii', using feature described in
the documentation for Phar::__construct
.

However, of course, there's a catch 2: Yii built-in asset manager (CAssetManager)
has too specific publish method, unable to cope with custom PHP streams.
So, we need the fixed version.

I decided to create a descendant of CAssetManager descriptively called PharCompatibleAssetManager
with the following definition exactly:

<?php
/**
 * Created by JetBrains PhpStorm.
 * User: hijarian
 * Date: 20.07.13
 * Time: 17:13
 */

/**
 * Class PharCompatibleAssetManager
 *
 * As we use Yii packaged into .phar archive, we need to make changes into the Asset Manager,
 * according to the https://code.google.com/p/yii/issues/detail?id=3104
 *
 * Details about packaging Yii into .phar archive can be found at
 * https://gist.github.com/mindplay-dk/1607318
 */
class PharCompatibleAssetManager extends CAssetManager
{
    protected $_published = array();

    public function publish($path,$hashByName=false,$level=-1,$forceCopy=null)
    {
        if($forceCopy===null)
            $forceCopy=$this->forceCopy;
        if($forceCopy && $this->linkAssets)
            throw new CException(Yii::t('yii','The "forceCopy" and "linkAssets" cannot be both true.'));
        if(isset($this->_published[$path]))
            return $this->_published[$path];

        $isPhar = strncmp('phar://', $path, 7) === 0;
        $src = $isPhar ? $path : realpath($path);

        if ($isPhar && $this->linkAssets)
        {
            throw new CException(
                Yii::t(
                    'yii',
                    'The asset "{asset}" cannot be published using symlink, because the file resides in a phar.',
                    array('{asset}' => $path)
                )
            );
        }

        if ($src !== false || $isPhar)
        {
            $dir=$this->generatePath($src,$hashByName);
            $dstDir=$this->getBasePath().DIRECTORY_SEPARATOR.$dir;
            if(is_file($src))
            {
                $fileName=basename($src);
                $dstFile=$dstDir.DIRECTORY_SEPARATOR.$fileName;

                if(!is_dir($dstDir))
                {
                    mkdir($dstDir,$this->newDirMode,true);
                    @chmod($dstDir,$this->newDirMode);
                }

                if($this->linkAssets && !is_file($dstFile)) symlink($src,$dstFile);
                elseif(@filemtime($dstFile)<@filemtime($src))
                {
                    copy($src,$dstFile);
                    @chmod($dstFile,$this->newFileMode);
                }

                return $this->_published[$path]=$this->getBaseUrl()."/$dir/$fileName";
            }
            elseif(is_dir($src))
            {
                if($this->linkAssets && !is_dir($dstDir))
                {
                    symlink($src,$dstDir);
                }
                elseif(!is_dir($dstDir) || $forceCopy)
                {
                    CFileHelper::copyDirectory($src,$dstDir,array(
                        'exclude'=>$this->excludeFiles,
                        'level'=>$level,
                        'newDirMode'=>$this->newDirMode,
                        'newFileMode'=>$this->newFileMode,
                    ));
                }

                return $this->_published[$path]=$this->getBaseUrl().'/'.$dir;
            }
        }
        throw new CException(Yii::t('yii','The asset "{asset}" to be published does not exist.',
            array('{asset}'=>$path)));
    }
}

I'm really, really sorry that you had to read this traditionally horrible Yii
code, but that was inevitable... :(

Main change was starting from the $isPhar = strncmp('phar://', $path, 7) ===
0;
part.

Now just link this asset manager instead of built-in one:

config/main.php
'components' => [ 
    'assetManager' => [ 
        'class' => 'your.alias.path.to.PharCompatibleAssetManager'
    ]
]

Congratulations!

Now your Yii web application uses phar archive instead of huge pile of separate
files. They say that this increases performance, but my personal reasons was
just to reduce the number of files inside the repository.

 
comments powered by Disqus