4 * This file is part of Zippy.
6 * (c) Alchemy <info@alchemy.fr>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Alchemy\Zippy\Adapter;
14 use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
15 use Alchemy\Zippy\Adapter\Resource\ZipArchiveResource;
16 use Alchemy\Zippy\Adapter\VersionProbe\ZipExtensionVersionProbe;
17 use Alchemy\Zippy\Archive\Archive;
18 use Alchemy\Zippy\Archive\Member;
19 use Alchemy\Zippy\Exception\NotSupportedException;
20 use Alchemy\Zippy\Exception\RuntimeException;
21 use Alchemy\Zippy\Exception\InvalidArgumentException;
22 use Alchemy\Zippy\Resource\Resource as ZippyResource;
23 use Alchemy\Zippy\Resource\ResourceManager;
26 * ZipExtensionAdapter allows you to create and extract files from archives
27 * using PHP Zip extension
29 * @see http://www.php.net/manual/en/book.zip.php
31 class ZipExtensionAdapter extends AbstractAdapter
33 private $errorCodesMapping = array(
34 \ZipArchive::ER_EXISTS => "File already exists",
35 \ZipArchive::ER_INCONS => "Zip archive inconsistent",
36 \ZipArchive::ER_INVAL => "Invalid argument",
37 \ZipArchive::ER_MEMORY => "Malloc failure",
38 \ZipArchive::ER_NOENT => "No such file",
39 \ZipArchive::ER_NOZIP => "Not a zip archive",
40 \ZipArchive::ER_OPEN => "Can't open file",
41 \ZipArchive::ER_READ => "Read error",
42 \ZipArchive::ER_SEEK => "Seek error"
45 public function __construct(ResourceManager $manager)
47 parent::__construct($manager);
48 $this->probe = new ZipExtensionVersionProbe();
54 protected function doListMembers(ResourceInterface $resource)
57 for ($i = 0; $i < $resource->getResource()->numFiles; $i++) {
58 $stat = $resource->getResource()->statIndex($i);
59 $members[] = new Member(
64 new \DateTime('@' . $stat['mtime']),
65 0 === strlen($resource->getResource()->getFromIndex($i, 1))
75 public static function getName()
77 return 'zip-extension';
83 protected function doExtract(ResourceInterface $resource, $to)
85 return $this->extractMembers($resource, null, $to);
91 protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false)
94 // if no destination is given, will extract to zip current folder
95 $to = dirname(realpath($resource->getResource()->filename));
99 $resource->getResource()->close();
100 throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
103 if (!is_writable($to)) {
104 $resource->getResource()->close();
105 throw new InvalidArgumentException(sprintf("%s is not writable", $to));
108 if (null !== $members) {
109 $membersTemp = (array) $members;
110 if (empty($membersTemp)) {
111 $resource->getResource()->close();
113 throw new InvalidArgumentException("no members provided");
116 // allows $members to be an array of strings or array of Members
117 foreach ($membersTemp as $member) {
118 if ($member instanceof Member) {
119 $member = $member->getLocation();
122 if ($resource->getResource()->locateName($member) === false) {
123 $resource->getResource()->close();
125 throw new InvalidArgumentException(sprintf('%s is not in the zip file', $member));
128 if ($overwrite == false) {
129 if (file_exists($member)) {
130 $resource->getResource()->close();
132 throw new RuntimeException('Target file ' . $member . ' already exists.');
136 $members[] = $member;
140 if (!$resource->getResource()->extractTo($to, $members)) {
141 $resource->getResource()->close();
143 throw new InvalidArgumentException(sprintf('Unable to extract archive : %s', $resource->getResource()->getStatusString()));
146 return new \SplFileInfo($to);
152 protected function doRemove(ResourceInterface $resource, $files)
154 $files = (array) $files;
157 throw new InvalidArgumentException("no files provided");
160 // either remove all files or none in case of error
161 foreach ($files as $file) {
162 if ($resource->getResource()->locateName($file) === false) {
163 $resource->getResource()->unchangeAll();
164 $resource->getResource()->close();
166 throw new InvalidArgumentException(sprintf('%s is not in the zip file', $file));
168 if (!$resource->getResource()->deleteName($file)) {
169 $resource->getResource()->unchangeAll();
170 $resource->getResource()->close();
172 throw new RuntimeException(sprintf('unable to remove %s', $file));
175 $this->flush($resource->getResource());
183 protected function doAdd(ResourceInterface $resource, $files, $recursive)
185 $files = (array) $files;
187 $resource->getResource()->close();
188 throw new InvalidArgumentException("no files provided");
190 $this->addEntries($resource, $files, $recursive);
198 protected function doCreate($path, $files, $recursive)
200 $files = (array) $files;
203 throw new NotSupportedException("Cannot create an empty zip");
206 $resource = $this->getResource($path, \ZipArchive::CREATE);
207 $this->addEntries($resource, $files, $recursive);
209 return new Archive($resource, $this, $this->manager);
213 * Returns a new instance of the invoked adapter
215 * @return AbstractAdapter
217 * @throws RuntimeException In case object could not be instanciated
219 public static function newInstance()
221 return new ZipExtensionAdapter(ResourceManager::create());
224 protected function createResource($path)
226 return $this->getResource($path, \ZipArchive::CHECKCONS);
229 private function getResource($path, $mode)
231 $zip = new \ZipArchive();
232 $res = $zip->open($path, $mode);
235 throw new RuntimeException($this->errorCodesMapping[$res]);
238 return new ZipArchiveResource($zip);
241 private function addEntries(ResourceInterface $zipResource, array $files, $recursive)
243 $stack = new \SplStack();
247 $collection = $this->manager->handle($cwd, $files);
249 $this->chdir($collection->getContext());
254 $collection->forAll(function($i, ZippyResource $resource) use ($zipResource, $stack, $recursive, $adapter) {
255 $adapter->checkReadability($zipResource->getResource(), $resource->getTarget());
256 if (is_dir($resource->getTarget())) {
258 $stack->push($resource->getTarget() . ((substr($resource->getTarget(), -1) === DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR));
260 $adapter->addEmptyDir($zipResource->getResource(), $resource->getTarget());
263 $adapter->addFileToZip($zipResource->getResource(), $resource->getTarget());
269 // recursively add dirs
270 while (!$stack->isEmpty()) {
271 $dir = $stack->pop();
273 $files = array_diff(scandir($dir), array(".", ".."));
274 if (count($files) > 0) {
275 foreach ($files as $file) {
276 $file = $dir . $file;
277 $this->checkReadability($zipResource->getResource(), $file);
279 $stack->push($file . DIRECTORY_SEPARATOR);
281 $this->addFileToZip($zipResource->getResource(), $file);
285 $this->addEmptyDir($zipResource->getResource(), $dir);
288 $this->flush($zipResource->getResource());
290 $this->manager->cleanup($collection);
291 } catch (\Exception $e) {
303 * @info is public for PHP 5.3 compatibility, should be private
305 * @param \ZipArchive $zip
306 * @param string $file
308 public function checkReadability(\ZipArchive $zip, $file)
310 if (!is_readable($file)) {
314 throw new InvalidArgumentException(sprintf('could not read %s', $file));
319 * @info is public for PHP 5.3 compatibility, should be private
321 * @param \ZipArchive $zip
322 * @param string $file
324 public function addFileToZip(\ZipArchive $zip, $file)
326 if (!$zip->addFile($file)) {
330 throw new RuntimeException(sprintf('unable to add %s to the zip file', $file));
335 * @info is public for PHP 5.3 compatibility, should be private
337 * @param \ZipArchive $zip
340 public function addEmptyDir(\ZipArchive $zip, $dir)
342 if (!$zip->addEmptyDir($dir)) {
346 throw new RuntimeException(sprintf('unable to add %s to the zip file', $dir));
351 * Flushes changes to the archive
353 * @param \ZipArchive $zip
355 private function flush(\ZipArchive $zip) // flush changes by reopening the file
357 $path = $zip->filename;
359 $zip->open($path, \ZipArchive::CHECKCONS);