Phar — это аналог jar из мира Java, но только применительно к PHP. Phar упаковывает файлы проекта в специальный архив и позволяет легко переносить и устанавливать приложение без манипуляций с самим проектом в виде исполняемой программы.

Описание phar из официальной документации
Phar archives are best characterized as a convenient way to group several files into a single file. As such, a phar archive provides a way to distribute a complete PHP application in a single file and run it from that file without the need to extract it to disk. Additionally, phar archives can be executed by PHP as easily as any other file, both on the commandline and from a web server. Phar is kind of like a thumb drive for PHP applications.


Для создания phar файлов в PHP существует довольно развесистый API, но есть способ проще и удобнее — использовать проект Box.

Формат JSON файла


Проект Box позволяет описать процесс создания phar файла в удобном JSON формате.

Самый простой файл выглядит так:

{
    "files": ["src/Put.php"],
    "main": "bin/main",
    "output": "example.phar",
    "stub": true
}


где

files — файлы, которые должны быть включены в phar

main — файл, который будет выполнен при вызове phar файла

output — название итогового phar файла

stub — для консольных приложений устанавливается в true

Здесь можно посмотреть процесс создания phar-файла на примере простого проекта.

Здесь содержится список всех возможных параметров box.json с комментариями.

Попробуем собрать phar-файл на примере реального проекта. В качестве примера возьмем систему миграций баз данных Nasgrate. «Традиционно» данная система устанавливается либо через клонирование репозитория из GitHub, либо через Composer. Мы попробуем сделать отдельную утилиту, которую можно просто скачать и начать использовать вообще ничего не зная про PHP.

Создадим box.json в корне проекта

{
  "chmod": "0755",
  "directories": ["src","app"],
  "files": ["README.md"],
  "main": "bin/nasgrate",
  "output": "nasgrate.phar",
  "stub": true
}

directories — в данном параметре содержится список директорий, которые целиком будут включены в проект (в данном случае директория src и app).

files — если нужно включить какие-то конкретные файлы, перечисляем их здесь.

Мы бы могли например переписать box.json следующим образом

{
  "chmod": "0755",
  "directories": ["src"],
  "files": ["app/console.php", "app/index.php", "README.md"],
  "main": "bin/nasgrate",
  "output": "nasgrate.phar",
  "stub": true
}

Помимо этих двух варинтов записи есть еще возможность выборочно включить файлы в проект на основе фильтров. Например если бы в проекте активно использовались внешние библиотеки и они располагались бы в папке vendors, мы бы хотели включить файлы этих библиотек в проект, но исключить например все тесты. Тогда файл выглядел бы так

{
  "chmod": "0755",
  "directories": ["src"],
  "files": ["app/console.php", "app/index.php", "README.md"],
  "finder": [
    {
      "name": "*.php",
      "exclude": [
        "tests",
        "test"
      ],
      "in": "vendor"
    }
  ],  
  "main": "bin/nasgrate",
  "output": "nasgrate.phar",
  "stub": true
}

секция finder в данном случае говорит: “включи все файлы с расширением *.php из директрии vendor кроме папок tests и test“.

chmod — позволяет установить права на phar файл. В данном случае мы ставим 0755 чтобы сделать файл исполняемым.

Остальные парметры были описаны выше.

Установка Box


Наиболее простой (и рекомендуемый) способ установки

$ curl -LSs https://box-project.github.io/box2/installer.php | php

после этого в текущей директории появится box.phar.

Можно его запускать как php box.phar, либо назначить права на выполнение chmod 755 box.phar, переименовать в box mv box.phar box и перенести в /usr/local/bin. Тогда его можно будет запускать из любого места просто как box.

Альтернативная установка через Composer

$ composer global require kherge/box --prefer-source

или добавить его в существующий проект

{
    "require-dev": {
        "kherge/box": "~2.5"
    }
}

Проверим правильность установки

$ box -v

должен вывести описание программы и список возможных опций.

Далее необходимо проверить что в вашем php.ini файле параметр phar.readonly стоит в 0, Off или false.

Узнаем где находится наш файл, который актуален именно для консоли (если вы запустите phpinfo(); через Apache, он вам покажет другой php.ini):

$ php -i | grep php.ini
>> Configuration File (php.ini) Path => /usr/local/php5/lib
>> Loaded Configuration File => /usr/local/php5/lib/php.ini

Далее находим параметр phar.readonly и ставим его в Off.

[Phar]
; http://php.net/phar.readonly
phar.readonly = Off

Компиляция проекта


Заходим в папку с проектом (на уровень где лежит наш box.json) и запускаем компиляцию

$ box build -v

Флаг -v позволяет нам увидеть что происходит в процессе компиляции

 Removing previously built Phar...
* Building...
? Output path: /Users/dlevsha/Sites/nasgrate/nasgrate.phar
? Adding directories...
  + /Users/dlevsha/Sites/nasgrate/src/config.php
  + /Users/dlevsha/Sites/nasgrate/src/Driver/Base/Dump.php
  + /Users/dlevsha/Sites/nasgrate/src/Driver/Base/Generator.php
  + /Users/dlevsha/Sites/nasgrate/src/Driver/Base/Helper.php
  + /Users/dlevsha/Sites/nasgrate/src/Driver/Base/Migration.php
  + /Users/dlevsha/Sites/nasgrate/src/Driver/Mysql/Dump.php
  + /Users/dlevsha/Sites/nasgrate/src/Driver/Mysql/Generator.php
  + /Users/dlevsha/Sites/nasgrate/src/Driver/Mysql/Helper.php
  + /Users/dlevsha/Sites/nasgrate/src/Process/Base.php
  + /Users/dlevsha/Sites/nasgrate/src/Process/Console.php
  + /Users/dlevsha/Sites/nasgrate/src/Process/Server.php
  + /Users/dlevsha/Sites/nasgrate/src/template.sql
  + /Users/dlevsha/Sites/nasgrate/src/Util/Console.php
  + /Users/dlevsha/Sites/nasgrate/src/Util/Db.php
  + /Users/dlevsha/Sites/nasgrate/app/console.php
  + /Users/dlevsha/Sites/nasgrate/app/index.php
? Adding files...
  + /Users/dlevsha/Sites/nasgrate/README.md
? Adding main file: /Users/dlevsha/Sites/nasgrate/bin/nasgrate
? Generating new stub...
? Setting file permissions...
* Done.

после этого в директории проекта появится файл nasgrate.phar.

Проверим что все скомпилировалось нормально

$ ./nasgrate.phar

выведет описание утилиты

Nasgrate is a console utility that let you organise database schema migration process at a consistent and easy way.
It supports mysql, mssql, postgresql, oracle and other databases

Usage:
  php nasgrate [command] [options]

Command:
  status     - displays migration status
  generate   - creates new migration (migration file)
  up:show    - displays (but not executes) SQL-query, executed by migration update
...

Работа с внешними ресурсами


Особенностью работы phar является то, что все ресурсы он ищет внутри своего архива. В большинстве случаев это не является проблемой, поскольку именно для этого мы phar и делали, чтобы он был независимым от окружения. Но есть ряд случаев когда ресурсам внутри phar архива необходимо работать с внешними файлами.

На примере Nasgrate — необходимо считывать конфигурацию из файла .environment, писать файлы миграций и файлы состояния во внешнюю директорию.

Если мы попытаемся указать относительный путь внутри проекта, например как ../.environment, сгенерируется ошибка, поскольку такого файла внутри phar нет.

Существует два варианта решения данной проблемы:

Первый вариант смапить внешний файл во внутреннее пространство phar файла.

Phar::mount('phar://.environment', '/etc/nasgrate/.environment');

Проблема в том, что вам нужно точно знать абсолютный путь до файла.

Второй вариант — указывать путь от текущего местоположения phar файла. Примерно так:

define(DIR_RUN, dirname(Phar::running(false)));

Далее вы либо с помощью Phar::mount смапите его во внутреннее пространство phar либо просто укажите абсолютный путь до файла конфигурации. Предположим что конфигурационный файл находится в той же папке, что и сам phar

define(DIR_RUN, dirname(Phar::running(false)));
Phar::mount('phar://.environment', DIR_RUN.'/.environment');

и далее обращаетесь к нему как к локальному файлу, либо всегда обращаетесь по абсолюному пути.

Есть одно замечание, не знаю баг это или фича, но все директории подключенные через Phar::mount находятся в режиме readonly и как это поменять не очень ясно. При обращении по абсолютному пути таких проблем не возникает.

Еще один вроде как очевидный момент, но на который стоит обратить внимание. Phar файл содержит ресурсы вашего проекта и никак не регулирует наличие подключенных библиотек к самому php, его версию и т.д. То есть если вы используете какое-то расширение php, например PDO или вообще что-то установили из PECL, или используете какие-то особенности конкретной версии php, ваш phar не будет содержать информацию о среде выполнения. Если вы используете например traits из PHP 5.4, а у пользователя стоит 5.3 — то будет сгенерирована ошибка. Вы должны внутри своего приложения проверять все требуемые зависимости.

Ссылки по теме:

Комментарии (2)


  1. Rathil
    12.01.2016 15:36

    Что-то я забыл о phar-е, не подумал о нем, когда нужно было разворачивать проекты. В итоге остановился на генерации deb пакетов, но и вышло не плохо, с зависимостями очень удобно, скопом ставлю все, что нужно.


  1. popstas
    13.01.2016 01:30

    Хабр как всегда вовремя, как раз вчера собрал консольную утилитку, подумал, что надо бы phar научиться делать, а тут вы.

    Настроил интеграцию с Travis CI, теперь на каждый тег в Releases на Github появляется бинарник. Подробности в доке.

    Нужно добавить 'box build' в секцию install, потом поставить Travis client и запустить в папке проекта 'travis setup releases', он задаст пару вопросов и допишет .travis.yml.

    После него я еще добавиил условия, чтобы бинарник заливался только при сборке php 5.6 и чтобы заливался только если на коммите стоит тег, иначе сборка падала с ошибкой.

    Пример .travis.yml