Un PHAR (PHP Archive) es un fichero que empaqueta una aplicación PHP completa —código, recursos, dependencias— en un único fichero ejecutable, de forma similar a los JARs de Java o los ZIPs de Python (wheels). Composer utiliza internamente el formato PHAR para distribuir su propio ejecutable. También es útil para distribuir herramientas CLI, scripts de migración o aplicaciones sin necesidad de instalar dependencias en el servidor destino.
Habilitar la creación de PHARs
La creación de PHARs está desactivada por defecto en php.ini por seguridad:
; php.ini
phar.readonly = Off ; necesario para crear PHARs
# O desactivar solo para el script de build:
php -d phar.readonly=Off crear_phar.php
Crear un PHAR desde un directorio
<?php
// crear_phar.php — ejecutar con: php -d phar.readonly=Off crear_phar.php
$pharFile = 'miherramienta.phar';
// Eliminar si ya existe
if (file_exists($pharFile)) {
unlink($pharFile);
}
$phar = new Phar($pharFile);
$phar->startBuffering();
// Añadir todos los ficheros del directorio src/
$phar->buildFromDirectory(__DIR__ . '/src');
// Definir el stub (punto de entrada)
$stub = <<<STUB
#!/usr/bin/env php
<?php
Phar::mapPhar('miherramienta.phar');
require 'phar://miherramienta.phar/index.php';
__HALT_COMPILER();
STUB;
$phar->setStub($stub);
$phar->stopBuffering();
// Hacer el fichero ejecutable
chmod($pharFile, 0755);
echo "PHAR creado: $pharFile (" . round(filesize($pharFile) / 1024, 1) . " KB)n";
buildFromDirectory con filtro de ficheros
<?php
$phar = new Phar('miapp.phar');
$phar->startBuffering();
// Incluir solo ficheros .php y .json, excluir tests y directorios ocultos
$phar->buildFromDirectory(__DIR__ . '/src', '/.(php|json)$/');
// O con un iterador personalizado para más control
$dirIter = new RecursiveDirectoryIterator(
__DIR__ . '/src',
RecursiveDirectoryIterator::SKIP_DOTS
);
$iter = new RecursiveIteratorIterator($dirIter);
foreach ($iter as $fichero) {
// Excluir tests y ficheros de configuración local
if (str_contains($fichero->getPathname(), '/tests/')) continue;
if ($fichero->getExtension() === 'log') continue;
// Añadir fichero con ruta relativa dentro del phar
$rutaRelativa = substr($fichero->getPathname(), strlen(__DIR__ . '/src/'));
$phar->addFile($fichero->getPathname(), $rutaRelativa);
}
// Añadir fichero de string en memoria
$phar->addFromString('version.php', '<?php define("VERSION", "1.2.0");');
$phar->setStub($phar->createDefaultStub('index.php'));
$phar->stopBuffering();
Comprimir el PHAR
<?php
$phar = new Phar('miapp.phar');
$phar->startBuffering();
$phar->buildFromDirectory(__DIR__ . '/src');
$phar->setStub($phar->createDefaultStub('index.php'));
$phar->stopBuffering();
echo "Sin comprimir: " . round(filesize('miapp.phar') / 1024, 1) . " KBn";
// Comprimir con GZ (requiere ext-zlib)
$pharGz = $phar->compress(Phar::GZ, 'miapp.phar.gz');
echo "Con GZ: " . round(filesize('miapp.phar.gz') / 1024, 1) . " KBn";
// Comprimir con BZ2 (requiere ext-bz2)
$pharBz2 = $phar->compress(Phar::BZ2, 'miapp.phar.bz2');
// Comprimir ficheros individuales dentro del phar
$phar->startBuffering();
foreach ($phar as $fichero) {
$fichero->compress(Phar::GZ);
}
$phar->stopBuffering();
Firmar un PHAR con clave privada
La firma garantiza que el PHAR no ha sido modificado. Importante para herramientas que se distribuyen como descarga:
<?php
// Generar par de claves (solo una vez)
$config = ['private_key_bits' => 2048, 'private_key_type' => OPENSSL_KEYTYPE_RSA];
$recurso = openssl_pkey_new($config);
openssl_pkey_export($recurso, $clavePrivada);
file_put_contents('firma.pem', $clavePrivada);
// Firmar el PHAR
$phar = new Phar('miapp.phar');
$phar->setSignatureAlgorithm(Phar::OPENSSL, file_get_contents('firma.pem'));
// Verificar la firma al usar el PHAR
try {
$info = Phar::getSupportedSignatures();
$firma = (new Phar('miapp.phar'))->getSignature();
echo "Tipo de firma: " . $firma['hash_type'] . "n";
echo "Hash: " . $firma['hash'] . "n";
} catch (PharException $e) {
echo "Firma inválida: " . $e->getMessage();
}
Ejemplo real: herramienta CLI en un PHAR
<?php
// src/index.php — punto de entrada del PHAR
#!/usr/bin/env php
// El autoloader funciona dentro del PHAR
spl_autoload_register(function(string $clase) {
$ruta = 'phar://miherramienta.phar/' . str_replace('\', '/', $clase) . '.php';
if (file_exists($ruta)) {
require $ruta;
}
});
// O si usas Composer, incluye el vendor del PHAR
// require 'phar://miherramienta.phar/vendor/autoload.php';
// Procesar argumentos de lÃnea de comandos
$comando = $argv[1] ?? 'help';
$args = array_slice($argv, 2);
$comandos = [
'version' => fn() => echo "miherramienta v1.2.0n",
'help' => fn() => echo "Uso: miherramienta [version|procesar|help]n",
'procesar' => function() use ($args) {
$fichero = $args[0] ?? null;
if (!$fichero || !file_exists($fichero)) {
echo "Error: fichero no encontradon";
exit(1);
}
// Procesar...
echo "Procesado: $ficheron";
},
];
if (isset($comandos[$comando])) {
($comandos[$comando])();
} else {
echo "Comando desconocido: $comandon";
exit(1);
}
<?php
// crear_phar.php
$phar = new Phar('miherramienta.phar');
$phar->startBuffering();
$phar->buildFromDirectory(__DIR__ . '/src');
// Stub con shebang para ejecutar directamente en Linux/macOS
$stub = "#!/usr/bin/env phpn" . Phar::createDefaultStub('index.php');
$phar->setStub($stub);
$phar->stopBuffering();
chmod('miherramienta.phar', 0755);
# Usar la herramienta
php miherramienta.phar version
# O directamente si tiene shebang y permisos de ejecución:
./miherramienta.phar procesar datos.csv
Leer contenido de un PHAR sin ejecutarlo
<?php
// Listar los ficheros de un PHAR
$phar = new Phar('miapp.phar');
foreach ($phar as $fichero) {
echo $fichero->getPathname() . "n";
}
// Leer un fichero especÃfico
$contenido = file_get_contents('phar://miapp.phar/config/app.json');
$config = json_decode($contenido, true);
// Extraer todos los ficheros
$phar->extractTo('/tmp/extraido/');
// Extraer ficheros especÃficos
$phar->extractTo('/tmp/', ['config/app.json', 'src/Core/Router.php']);
Consideraciones de seguridad
- Deshabilita phar.readonly en producción solo si es necesario: los PHARs pueden contener código ejecutable malicioso. La directiva
phar.readonly=On(por defecto) evita que scripts del servidor creen o modifiquen PHARs. - Deserialización en PHARs: en PHP < 8.0, acceder a metadatos de un PHAR (incluso con
file_exists('phar://...')) podÃa desencadenar la deserialización de metadatos maliciosos. Esto se resolvió requiriendo que los PHARs estén firmados. - Firma obligatoria para distribución: si distribuis un PHAR, fÃrmalo siempre. Permite que los usuarios verifiquen que no ha sido alterado.
