data types and standard library
Archives: Zip and Phar
Archives package files together. PHP projects use ZIP files for exports, downloads, imports, backups, generated reports, and receiving batches from other systems. Phar archives are PHP application archives, often used for distributing command-line tools.
Archive handling crosses a filesystem boundary, so safe filenames, size limits, extraction paths, and extension availability matter.
Check ZIP support
ZipArchive comes from the ZIP extension, which may not be installed everywhere.
<?php
declare(strict_types=1);
echo class_exists(ZipArchive::class) ? 'ZIP available' : 'ZIP missing';
echo PHP_EOL;
If your application creates or reads ZIP files, make this a deployment requirement.
Create a ZIP archive
Use ZipArchive to add files under controlled archive names.
<?php
declare(strict_types=1);
$path = tempnam(sys_get_temp_dir(), 'zip_');
$zip = new ZipArchive();
if ($zip->open($path, ZipArchive::OVERWRITE) !== true) {
throw new RuntimeException('Could not create zip file.');
}
$zip->addFromString('readme.txt', 'Archive created');
$zip->close();
$zip->open($path);
echo $zip->getFromName('readme.txt') . PHP_EOL;
$zip->close();
unlink($path);
// Prints:
// Archive created
This example writes to a temporary file, reads the archived entry back, and removes the archive.
Validate archive entry names
Archive entries are paths inside the archive. Do not allow absolute paths, parent-directory traversal, or empty names.
<?php
declare(strict_types=1);
function safeArchiveEntryName(string $name): string
{
$name = str_replace('\\', '/', trim($name));
if ($name === '' || str_starts_with($name, '/') || str_contains($name, '../') || str_contains($name, "\0")) {
throw new InvalidArgumentException('Archive entry name is invalid.');
}
return $name;
}
echo safeArchiveEntryName('reports/daily.txt') . PHP_EOL;
// Prints:
// reports/daily.txt
This matters when archive contents are based on user uploads, database values, or external systems.
List archive contents before extraction
Inspect entries before extracting an uploaded archive.
<?php
declare(strict_types=1);
$path = tempnam(sys_get_temp_dir(), 'zip_');
$zip = new ZipArchive();
$zip->open($path, ZipArchive::OVERWRITE);
$zip->addFromString('reports/daily.txt', 'Daily report');
$zip->addFromString('reports/weekly.txt', 'Weekly report');
$zip->close();
$zip->open($path);
for ($index = 0; $index < $zip->numFiles; $index++) {
echo $zip->getNameIndex($index) . PHP_EOL;
}
$zip->close();
unlink($path);
// Prints:
// reports/daily.txt
// reports/weekly.txt
Do validation before extraction: entry names, total file count, total uncompressed size, allowed extensions, and destination path.
Be careful with extraction
Extracting an archive can overwrite files or write outside the intended directory if entry names are unsafe. Prefer extracting only validated entries to a controlled temporary directory.
<?php
declare(strict_types=1);
function canExtractEntry(string $entryName): bool
{
try {
safeArchiveEntryName($entryName);
return true;
} catch (InvalidArgumentException) {
return false;
}
}
echo canExtractEntry('../config.php') ? 'safe' : 'blocked';
echo PHP_EOL;
// Prints:
// blocked
Archive upload features need stricter review than ordinary single-file uploads.
Understand where Phar fits
Phar packages PHP code and resources into a single archive. Tools such as PHPStan, Psalm, and Composer plugins may be distributed as Phar files.
<?php
declare(strict_types=1);
echo class_exists(Phar::class) ? 'Phar available' : 'Phar missing';
echo PHP_EOL;
Creating Phar files has runtime configuration requirements, and running unknown Phar files is a security decision. Treat them as executable code, not as ordinary document archives.
What to remember
ZIP is for packaging files. Phar is for packaging PHP applications. Validate archive entry names, inspect contents before extraction, use controlled temporary directories, check required extensions, and treat uploaded archives as high-risk input.
Practice
Task: Create and inspect a report ZIP
Write a small example that creates a ZIP archive and inspects its contents.
Requirements
- Use
declare(strict_types=1);. - Create a temporary ZIP archive with
ZipArchive. - Add two report files with safe archive entry names.
- Close and reopen the archive.
- List the archived file names.
- Read one archived file back.
- Delete the temporary archive at the end.
- Show one invalid archive entry name by catching the exception.
- Include expected output comments for the deterministic lines.
The example should validate archive entry names before adding them.
Show solution
<?php
declare(strict_types=1);
function safeArchiveEntryName(string $name): string
{
$name = str_replace('\\', '/', trim($name));
if ($name === '' || str_starts_with($name, '/') || str_contains($name, '../') || str_contains($name, "\0")) {
throw new InvalidArgumentException('Archive entry name is invalid.');
}
return $name;
}
$path = tempnam(sys_get_temp_dir(), 'reports_');
$zip = new ZipArchive();
if ($zip->open($path, ZipArchive::OVERWRITE) !== true) {
throw new RuntimeException('Could not create ZIP archive.');
}
$zip->addFromString(safeArchiveEntryName('reports/daily.txt'), 'Daily total: 120');
$zip->addFromString(safeArchiveEntryName('reports/weekly.txt'), 'Weekly total: 560');
$zip->close();
$zip->open($path);
for ($index = 0; $index < $zip->numFiles; $index++) {
echo $zip->getNameIndex($index) . PHP_EOL;
}
echo $zip->getFromName('reports/daily.txt') . PHP_EOL;
$zip->close();
unlink($path);
try {
safeArchiveEntryName('../config.php');
} catch (InvalidArgumentException $exception) {
echo $exception->getMessage() . PHP_EOL;
}
// Prints:
// reports/daily.txt
// reports/weekly.txt
// Daily total: 120
// Archive entry name is invalid.
The solution validates names before adding files, inspects the finished archive, and cleans up the temporary ZIP. The same validation habit matters even more when extracting archives supplied by users.