我正在开发一个命令行PHP项目,并希望能够重新创建作为我的部署工件的PHAR文件。挑战在于我无法创建两个具有相同sha1sum并且彼此间隔超过1秒的PHAR。如果输入文件相同(即来自同一个git提交),我希望能够完全重新创建我的PHAR文件。
以下代码段演示了此问题:
#!/usr/bin/php
<?php
$hashes = array();
$file_names = array('file1.phar','file2.phar');
foreach ($file_names as $name) {
if (file_exists($name)) {
unlink($name);
}
$phar = new Phar($name);
$phar->addFromString('cli.php', "cli\n");
$hashes[]=sha1_file($name);
// remove the sleep and the PHAR's are identical.
sleep(1);
}
if ($hashes[0]==$hashes[1]) {
echo "match\n";
} else {
echo "do not match\n";
}
据我所知,PHAR清单中每个文件的“修改时间”字段始终设置为当前时间,似乎没有办法或覆盖它。即使touch("phar://file1.phar/cli.php", 1413387555)
也会出错:
touch(): Can not call touch() for a non-standard stream
我在PHP 5.5.9上运行上面的代码在ubuntu trusty和RHEL5上的PHP 5.3上,两个版本的行为方式相同,无法创建相同的PHAR文件。
我正在尝试这样做,以便遵循Jez Humble和David Farley所着的持续部署一书中的建议
感谢任何帮助。
答案 0 :(得分:2)
Phar类目前不允许用户更改甚至访问修改时间。我想把你的字符串存储到一个临时文件中并使用touch
来改变mtime,但这似乎没有任何影响。因此,您必须手动更改已创建文件中的时间戳,然后重新生成存档签名。以下是使用当前PHP版本的方法:
<?php
$filename = "file1.phar";
$archive = file_get_contents($filename);
# Search for the start of the archive header
# See http://php.net/manual/de/phar.fileformat.phar.php
# This isn't the only valid way to write a PHAR archive, but it is what the Phar class
# currently does, so you should be fine (The docs say that the end-of-PHP-tag is optional)
$magic = "__HALT_COMPILER(); ?" . ">";
$end_of_code = strpos($archive, $magic) + strlen($magic);
$data_pos = $end_of_code;
# Skip that header
$data = unpack("Vmanifest_length/Vnumber_of_files/vapi_version/Vglobal_flags/Valias_length", substr($archive, $end_of_code, 18));
$data_pos += 18 + $data["alias_length"];
$metadata = unpack("Vlength", substr($archive, $data_pos, 4));
$data_pos += 4 + $metadata["length"];
for($i=0; $i<$data["number_of_files"]; $i++) {
# Now $data_pos points to the first file
# Files are explained here: http://php.net/manual/de/phar.fileformat.manifestfile.php
$filename_data = unpack("Vfilename_length", substr($archive, $data_pos, 4));
$data_pos += 4 + $filename_data["filename_length"];
$file_data = unpack("Vuncompressed_size/Vtimestamp/Vcompressed_size/VCRC32/Vflags/Vmetadata_length", substr($archive, $data_pos, 24));
# Change the timestamp to zeros (You can also use some other time here using pack("V", time()) instead of the zeros)
$archive = substr($archive, 0, $data_pos + 4) . "\0\0\0\0" . substr($archive, $data_pos + 8);
# Skip to the next file (it's _all_ the headers first, then file data)
$data_pos += 24 + $file_data["metadata_length"];
}
# Regenerate the file's signature
$sig_data = unpack("Vsigflags/C4magic", substr($archive, strlen($archive) - 8));
if($sig_data["magic1"] == ord("G") && $sig_data["magic2"] == ord("B") && $sig_data["magic3"] == ord("M") && $sig_data["magic4"] == ord("B")) {
if($sig_data["sigflags"] == 1) {
# MD5
$sig_pos = strlen($archive) - 8 - 16;
$archive = substr($archive, 0, $sig_pos) . pack("H32", md5(substr($archive, 0, $sig_pos))) . substr($archive, $sig_pos + 16);
}
else {
# SHA1
$sig_pos = strlen($archive) - 8 - 20;
$archive = substr($archive, 0, $sig_pos) . pack("H40", sha1(substr($archive, 0, $sig_pos))) . substr($archive, $sig_pos + 20);
}
# Note: The manual talks about SHA256/SHA512 support, but the according flags aren't documented yet. Currently,
# PHAR uses SHA1 by default, so there's nothing to worry about. You still might have to add those sometime.
}
file_put_contents($filename, $archive);
我已经为我的本地PHP 5.5.9版本以及上面的示例编写了这个ad-hoc。该脚本适用于与上面的示例代码类似的文件。该文档暗示了与此格式的一些有效偏差。代码中的相应行有注释;如果你想支持一般的Phar文件,你可能需要在那里添加一些东西。