Kirjautuminen

Haku

Tehtävät

Keskustelu: Koodit: Phar – PHP-arkistoija

tsuriga [03.01.2008 00:00:00]

#

Phar, eli PHP Archive, on PHP:n vastine Javan JAR-paketeille. Pharit ovat suoraan ajettavia, mutta myös purettavissa olevia paketteja, jotka sisältävät myös suoraan ajettavissa olevia tiedostoja. Sen lisäksi, että paketteja voi sisällyttää aivan kuten tavan PHP-tiedostojakin, PHP arkistoja voi käsitellä myös kuin mitä tahansa muuta virtaa phar stream wrapperin avulla. Tässä esitetty pakettisysteemi käyttää Phar-luokan taulukkorajapintaa tiedostojen pakettiin viemiseen. Arkistojen sisältö on mahdollista pakata ja allekirjoittaa.

Asennus

PharCreatorin vaatimukset:

Vaikka manuaali sanookin Windows binäärin löytyvän PHP:n Snapshots-sivulta, itse hain binäärini pecl4winin phar-sivulta. Valitettavasti Pecl4Winin php_phar.dll on versio 1.0.0, joten jos tiedät, mistä saisi uudemman version (kirjoitushetkellä uusin 1.2.3), kerro siitä kommenteissa.

Tallenna php_phar.dll / php_phar.so PHP:n lisäosahakemistoosi ja lisää php.ini-tiedostoon seuraavat rivit:

extension=php_phar.dll / extension=php_phar.so
phar.readonly = "0"

Kaikki Phar-lisäosan php.ini-asetukset:

NimiOletusarvoKuvaus
phar.readonly"1"Sallitaanko myös Phar-pakettien kirjoittaminen
phar.require_hash"1"Vaaditaanko Phar-paketilta allekirjoitusta
phar.extract_list""Voidaan käyttää tiedostojen purkamiseen (kts. manuaali)

*nix varianteissa Pharin voi myös asentaa PECLin kautta:
pecl config-set php_ini /path/to/php.ini
pecl install phar

Kuvaus

Koodivinkin kirjoitushetkellä konsepti tuntuisi olevan vielä kehitysvaiheessa ja kovin eloisa, ja siksipä osa Internetistä löytyvästä materiaalista (mukaanlukien itse PHP:n manuaali) ei välttämättä pidä enää paikkaansa. Poikkeuksena ei ollut myöskään pakettien luonti, jonka piti olla aivan triviaalia joidenkin lähteiden mukaan. Usean yrityksen ja erheen jälkeen tein sitten oman pakkaussysteemin. Koodivinkissä on esitetty systeemin pakkausluokka, jolla voi(nee) myös lisätä tiedostoja jo olemassa oleviin phar-paketteihin. Itse pakkaussysteemiin kuuluu lisäksi myös parametrit komentoriviltä lukeva skripti. Fun fact: pakkaussysteemillä voi pakata myös itse systeemin (kts. linkki alla).

Koodi

<?php
/* encoding=utf-8 */

/**
 * Contains PharCreator class.
 *
 * PHP version >= 5.2.0
 * Requires Phar, Zlib and BZIP2 extensions.
 *
 * @package PharCreator
 */

/**
 * Creates and updates PHP Archives.
 *
 * Static class containing a method
 * for copying the contents of a
 * directory to a PHP Archive.
 *
 * @author tsuri
 * @version 1.5
 */
final class PharCreator
{

    /**
     * @var string Path to phar.
     */
    private static $_pharPath;

    /**
     * @var string Base directory of the PHP Archive
     */
    private static $_basePath;

    /**
     * @var int Length of the base directory's absolute path
     */
    private static $_basePathLen;

    /**
     * @var bool Whether to pack code
     */
    private static $_strip;


    /**
     * @constant int Currently only for controlling debugging of pathfixer
     * @since 1.42
     */
    const PRINT_DEBUG = 1;


    /**
     * Adds a directory to a new or already existing PHP Archive.
     *
     * Loads all the files under given path and appends them to a Phar package.
     * Optionally calculates signature for the newly created phar using given
     * algorithm, sets up a bootloader in the archive's stub and tries to fix
     * paths that point to files under base directory. See the README for possible
     * caveats of setting $transformPaths true.
     *
     * @param string Path to PHP Archive
     * @param string Directory to add. Ending slash required
     * @param string|bool 'GZ' or 'BZIP2' if compression is to be used, false otherwise
     * @param int|bool What algorithm to use for signature. One of Phar's signature encryption constants, or false for none
     * @param string|bool Which file to import in the stub, or false if no change
     * @param bool Whether to pack the code using PHP's strip_whitespace
     * @param bool Whether to attempt repointing the paths to the phar
     * @return bool|string True if adding the directory was a success, string containing the exception message otherwise
     * @throws PharException If an error occurred while applying changes to the archive
     * @throws Exception If bootloader file is not found
     */
    public static function addDirectory(
                                         $pharPath,
                                         $directory,
                                         $compression = false,
                                         $algorithm = false,
                                         $bootloader = false,
                                         $strip = false,
                                         $transformPaths = false
                                       )
    {

        try {

            // load new or existing phar and set signature encryption if requested
            $phar = new Phar( $pharPath );
            if ( $algorithm !== false ) {
                $phar->setSignatureAlgorithm( $algorithm );
            }

            // store some variables for later use and parse the basepath of the PHP Archive
            self::$_pharPath = $pharPath;
            self::$_basePath = realpath( $directory );
            self::$_basePathLen = strlen( self::$_basePath );

            // create iterator for gathering files under the base directory
            $iterator = new RecursiveIteratorIterator(
                                new RecursiveDirectoryIterator( $directory )
                            );

            /*
             * Loop through  files and store them in the phar.
             * If requested,  make some  rudimentary  attempts
             * at  modifying   paths  in  the  files  so  that
             * everything pointing inside the directory points
             * inside the phar.
             */
            foreach ( $iterator as $item ) {
                $filePath = str_replace(
                                         DIRECTORY_SEPARATOR, '/',
                                         basename( $item )
                                       );

                /*
                 * Transform paths and pack
                 * code if requested.
                 */
                $code = ( $strip === true ) ?
                        php_strip_whitespace( $item ):
                        file_get_contents( $item );
                if ( $transformPaths === true ) {
                    if ( self::PRINT_DEBUG === 1 ) {
                        echo "\nTransforming paths from " .
                             basename( $item ) . "...\n";
                    }
                    $code = self::_processCodeForPaths( $code, $item );
                }

                // append file to phar
                $phar[ $filePath ] = $code;
            } // foreach

            /*
             * Compress file by calling provided
             * compression method if any.
             */
            if ( $compression !== false ) {
                $methodName = 'compressAllFiles' . $compression;
                call_user_func( array( &$phar, $methodName ) );
            }

            /*
             * Write bootloader into stub
             * if proper one given.
             */
            $bootloaderPath = $directory . DIRECTORY_SEPARATOR . $bootloader;
            if ( file_exists( $bootloaderPath ) === true ) {
                $phar->setStub(
'<?php Phar::mapPhar(); require_once ( "phar://" . __FILE__ . "/' . $bootloader . '" ); __HALT_COMPILER(); ?>'
                              );
            }
            else {
                throw new Exception( 'Bootloader file not found' );
            }

        } catch ( Exception $e ) {
            return $e->getMessage();
        }

        return true;
    } // static method addDirectory


    /**
     * Transforms paths pointing inside the base dir
     * to phar-paths inside a file and returns
     * the resulting file's contents as a string.
     *
     * Takes a path to a file, reads the contents,
     * looks for paths that point to subfiles under
     * the basedir, replaces them with phar-paths
     * and returns the processed contents.
     *
     * @param string File to process
     * @return string Processed contents of the file
     * @since 1.3
     */
    private static function _processCodeForPaths( $code, $container )
    {
        $codeDir = dirname( $container );

        // gather all strings
        $matches = array();
        preg_match_all( '/[\'"][^\'"]{0,}[\'"]/', $code, $matches );

        // loop through strings and look for valid filepaths
        $replacements = array();
        foreach ( $matches[ 0 ] as $path ) {
            $rawPath = substr( $path, 1, -1 );

            // get the absolute path of the relative reference
            $realPath = realpath(
                                  $codeDir .
                                  DIRECTORY_SEPARATOR .
                                  $rawPath
                                );

            // if filepath points to a file, add path to replacements
            if ( is_file( $realPath ) === true ) {
                $fixedPath = self::_fixPharPath( $rawPath, $realPath, $path[ 0 ] );
                $replacements[ $path ] = $fixedPath;

                // print paths if requested
                if ( self::PRINT_DEBUG === 1 ) {
                    echo '    Old path: ' . $path . "\n";
                    echo '    New path: ' . $fixedPath . "\n";
                }
            }
        } // foreach

        // return contents of the original container with paths fixed for phar
        return str_replace( array_keys( $replacements ), $replacements, $code );
    } // static method _processFileForPaths


    /**
     * Modifies path so that it will
     * work from inside the phar.
     *
     * If the path stays within basedir,
     * appends phar-stream marker. If not,
     * eliminates excessive dir ups because
     * phars don't have directory structures.
     *
     * @param string Path to modify in relative form
     * @param string Path to modify in absolute form
     * @param string Double or single quote
     * @return string Path fixed for phar
     * @since 1.1
     */
    private static function _fixPharPath( $path, $realPath, $quote )
    {
        $pathInPhar = '';

        /*
         * If path points inside the phar, append
         * phar stream marker to it and transform
         * directory separators to forward slashes.
         */
        if ( strpos( $realPath, self::$_basePath ) === 0 ) {
            $incPath = str_replace(
                                    DIRECTORY_SEPARATOR,
                                    '/', $path
                                  );

            $pathInPhar = $quote . 'phar://' .
                          self::$_pharPath . '/' .
                          $incPath . $quote;
        }

        /*
         * If path points outside the phar, prefix
         * it with sufficient amount of relative dir
         * ups and transform directory separators to
         * forward slashes.
         */
        else {
            $dirUpCount = substr_count( self::$_basePath, DIRECTORY_SEPARATOR ) -
                          substr_count(    $realPath,     DIRECTORY_SEPARATOR );

            $clearPath = str_replace( DIRECTORY_SEPARATOR, '/', $path );
            $relativePath = str_repeat( '../', $dirUpCount ) .
                            substr( $clearPath, strrpos( $clearPath, '../' ) + 3 );

            $pathInPhar = $quote . $relativePath . $quote;
        } // else

        return $pathInPhar;
    } // static method _fixPharPath

} // class PharCreator

?>

ApE!0 [03.01.2008 19:14:54]

#

Missä tätä voi käyttää ihan käytännössä?

tsuriga [03.01.2008 20:28:35]

#

Jaa niin, että mihinkö tarkoitukseen? Voit pakata esimerkiksi koko PHP-kirjastosi yhteen tiedostoon, jonka voi ajaa PHP:llä suoraan komentamalla php my.phar - kätevää, sanoisin. Teen kyllä itsekin paketeistani aina erilliset sorsa- ja phar-versiot. Kirjaston voi sitten liittää omaan projektiin yksinkertaisesti komentamalla require 'my.phar';.

EDIT: Päivitinpä tuohon vielä parit DIRECTORY_SEPARATORit varmuuden vuoksi. Yritin myös selkeyttää koodia, aika vaikeaselkoista välillä kun en enää itsekään meinannut muistaa mitä mikin tekee.

Hakoulinen [04.01.2008 11:42:27]

#

Paketin koko paljonkin pienempi kun sourcen?

EDIT: Ja asennuksen ekassa kappaleessa taitaa olla linkissä virhe.

tsuriga [04.01.2008 20:06:44]

#

Paketin koko riippuu ihan siitä, päätätkö stripata whitespacet ja pakkaatko sisältöä. Käyttötarkoitukset ovat siis samankaltaisia kuin Javan jarreilla, mihin ikinä niitä nyt sitten käytetäänkään. Linkki korjattu, tattis.

Vastaus

Aihe on jo aika vanha, joten et voi enää vastata siihen.

Tietoa sivustosta