19 declare(strict_types=1);
36 protected \ZipArchive
$zip;
50 $this->streams = array_filter($streams, fn($stream):
bool => $stream instanceof
FileStream);
53 $this->zip_output_file = $this->ensureDirectorySeperator(
59 if (file_exists($this->zip_output_file)) {
60 unlink($this->zip_output_file);
64 $system_limit = (
int) shell_exec(
'ulimit -n') ?: 0;
66 $this->iteration_limit = $system_limit < 10 ? 100 : min(
71 $this->zip = new \ZipArchive();
72 if (!file_exists($this->zip_output_file)) {
73 touch($this->zip_output_file);
75 if ($this->zip->open($this->zip_output_file, \ZipArchive::OVERWRITE) !==
true) {
76 throw new \Exception(
"cannot open <$this->zip_output_file>\n");
82 $directory = defined(
'CLIENT_DATA_DIR') ?
\CLIENT_DATA_DIR .
'/temp' : sys_get_temp_dir();
83 $tempnam = tempnam($directory,
'zip');
84 if (is_file($tempnam)) {
87 if (is_dir($tempnam)) {
96 register_shutdown_function($c);
101 foreach ($this->streams as $path_inside_zip => $stream) {
102 $path = $stream->getMetadata(
'uri');
103 if ($this->store_counter === 0) {
104 $this->zip->open($this->zip_output_file);
106 if (is_int($path_inside_zip)) {
107 $path_inside_zip = basename((
string)
$path);
110 if (
$path ===
'php://memory') {
111 $this->zip->addFromString($path_inside_zip, (
string) $stream);
113 } elseif (is_file(
$path)) {
114 $this->zip->addFile(
$path, $path_inside_zip);
121 $this->store_counter === $this->iteration_limit
122 || count(get_resources(
'stream')) > ($this->iteration_limit * 0.9)
125 $this->store_counter = 0;
127 $this->store_counter++;
138 return Streams::ofResource(fopen($this->zip_output_file,
'rb'));
147 public function addPath(
string $path, ?
string $path_inside_zip = null): void
149 $path_inside_zip = $path_inside_zip ?? basename($path);
152 $this->zip->addEmptyDir(rtrim(dirname($path_inside_zip),
'/') .
'/');
155 Streams::ofResource(fopen($path,
'rb')),
163 if (count($this->streams) === 1 && isset($this->streams[self::DOT_EMPTY])) {
164 unset($this->streams[self::DOT_EMPTY]);
168 $this->streams[$path_inside_zip] = $stream;
171 $this->path_counter === $this->iteration_limit
172 || count(get_resources(
'stream')) > ($this->iteration_limit * 0.9)
176 $this->path_counter = 0;
178 $this->path_counter++;
188 public function addDirectory(
string $directory_to_zip):
void 190 $directory_to_zip = $this->normalizePath(rtrim($directory_to_zip,
'/'));
192 $files = new \RecursiveIteratorIterator(
194 \RecursiveIteratorIterator::SELF_FIRST
197 switch ($this->options->getDirectoryHandling()) {
198 case ZipDirectoryHandling::KEEP_STRUCTURE:
202 case ZipDirectoryHandling::ENSURE_SINGLE_TOP_DIR:
203 $prefix = basename($directory_to_zip) .
'/';
204 $pattern =
'/^' . preg_quote($prefix,
'/') .
'/';
208 foreach ($files as $file) {
209 $pathname = $file->getPathname();
210 $path_inside_zip = str_replace($directory_to_zip .
'/',
'', $pathname);
211 if ($pattern !== null) {
212 $path_inside_zip = $prefix . preg_replace($pattern,
'', $path_inside_zip);
216 if ($file->isDir()) {
218 $sub_items = array_filter(scandir($pathname),
static fn(
$d):
bool => !str_contains((
string)
$d,
'.DS_Store'));
219 if (count($sub_items) === 2) {
220 $this->zip->addEmptyDir($path_inside_zip);
225 if ($this->isPathIgnored($pathname, $this->options)) {
229 $this->
addPath(realpath($pathname), $path_inside_zip);
addStream(FileStream $stream, string $path_inside_zip)
registerShutdownFunction(\Closure $c)
addPath(string $path, ?string $path_inside_zip=null)
The base interface for all filesystem streams.
__construct(protected ZipOptions $options,... $streams)