Thanks Jason -- I found the problem: my own stupidity. Many of the examples on the PHP site were incomplete: I was creating the zip file, but I wasn't returning it via readfile -- it's obvious in retrospect that you need to do this, but so many examples online skipped that step.
Just for the record, here is the simplified Snippet:
<?php
$file = '/tmp/my-zip.zip';
$zip = new ZipArchive();
$res = $zip->open($file, ZipArchive::CREATE);
if ($res === true) {
// Add a file generated from a Snippet
$xls_contents = $modx->runSnippet('createXLS',array('something' => 'parameter'));
$zip->addFromString('My-File.xls', $xls_contents);
$zip->close();
// Barf out the file
readfile($file);
unlink($file);
} else {
$modx->log(xPDO::LOG_LEVEL_ERROR, "downloadZip: ZipArchive failed to create a zip file");
return 'An error occurred while trying to create the zip file.';
}
As long as the page containing that Snippet call is generating the appropriate headers, this downloads just fine. Using this code in a CMP, you have to add some headers before barfing out the file. I added a few headers right before the readfile() bit:
header("Content-Type: application/zip");
header("Content-Length: " . filesize($file));
header("Content-Disposition: attachment; filename=".basename($file));
All working now. Thanks!