Update Composer, update everything

This commit is contained in:
Oliver Davies 2018-11-23 12:29:20 +00:00
parent ea3e94409f
commit dda5c284b6
19527 changed files with 1135420 additions and 351004 deletions

View file

@ -0,0 +1,273 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Adapter\VersionProbe\VersionProbeInterface;
use Alchemy\Zippy\Archive\Archive;
use Alchemy\Zippy\Archive\ArchiveInterface;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Resource\PathUtil;
use Alchemy\Zippy\Resource\ResourceManager;
abstract class AbstractAdapter implements AdapterInterface
{
/** @var ResourceManager */
protected $manager;
/**
* The version probe
*
* @var VersionProbeInterface
*/
protected $probe;
public function __construct(ResourceManager $manager)
{
$this->manager = $manager;
}
/**
* @inheritdoc
*/
public function open($path)
{
$this->requireSupport();
return new Archive($this->createResource($path), $this, $this->manager);
}
/**
* @inheritdoc
*/
public function create($path, $files = null, $recursive = true)
{
$this->requireSupport();
return $this->doCreate($this->makeTargetAbsolute($path), $files, $recursive);
}
/**
* @inheritdoc
*/
public function listMembers(ResourceInterface $resource)
{
$this->requireSupport();
return $this->doListMembers($resource);
}
/**
* @inheritdoc
*/
public function add(ResourceInterface $resource, $files, $recursive = true)
{
$this->requireSupport();
return $this->doAdd($resource, $files, $recursive);
}
/**
* @inheritdoc
*/
public function remove(ResourceInterface $resource, $files)
{
$this->requireSupport();
return $this->doRemove($resource, $files);
}
/**
* @inheritdoc
*/
public function extract(ResourceInterface $resource, $to = null)
{
$this->requireSupport();
return $this->doExtract($resource, $to);
}
/**
* @inheritdoc
*/
public function extractMembers(ResourceInterface $resource, $members, $to = null, $overwrite = false)
{
$this->requireSupport();
return $this->doExtractMembers($resource, $members, $to, $overwrite);
}
/**
* Returns the version probe used by this adapter
*
* @return VersionProbeInterface
*/
public function getVersionProbe()
{
return $this->probe;
}
/**
* Sets the version probe used by this adapter
*
* @param VersionProbeInterface $probe
*
* @return VersionProbeInterface
*/
public function setVersionProbe(VersionProbeInterface $probe)
{
$this->probe = $probe;
return $this;
}
/**
* @inheritdoc
*/
public function isSupported()
{
if (!$this->probe) {
throw new RuntimeException(sprintf(
'No version probe has been set on %s whereas it is required', get_class($this)
));
}
return VersionProbeInterface::PROBE_OK === $this->probe->getStatus();
}
/**
* Throws an exception is the current adapter is not supported
*
* @throws RuntimeException
*/
protected function requireSupport()
{
if (false === $this->isSupported()) {
throw new RuntimeException(sprintf('%s is not supported on your system', get_class($this)));
}
}
/**
* Change current working directory to another
*
* @param string $target the target directory
*
* @return AdapterInterface
*
* @throws RuntimeException In case of failure
*/
protected function chdir($target)
{
if (false === @chdir($target)) {
throw new RuntimeException(sprintf('Unable to chdir to `%s`', $target));
}
return $this;
}
/**
* Creates a resource given a path
*
* @param string $path
*
* @return ResourceInterface
*/
abstract protected function createResource($path);
/**
* Do the removal after having check that the current adapter is supported
*
* @param ResourceInterface $resource
* @param array $files
*
* @return array
*/
abstract protected function doRemove(ResourceInterface $resource, $files);
/**
* Do the add after having check that the current adapter is supported
*
* @param ResourceInterface $resource
* @param array $files
* @param bool $recursive
*
* @return array
*/
abstract protected function doAdd(ResourceInterface $resource, $files, $recursive);
/**
* Do the extract after having check that the current adapter is supported
*
* @param ResourceInterface $resource
* @param $to
*
* @return \SplFileInfo The extracted archive
*/
abstract protected function doExtract(ResourceInterface $resource, $to);
/**
* Do the extract members after having check that the current adapter is supported
*
* @param ResourceInterface $resource
* @param string|string[] $members
* @param string $to
* @param bool $overwrite
*
* @return \SplFileInfo The extracted archive
*/
abstract protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false);
/**
* Do the list members after having check that the current adapter is supported
*
* @param ResourceInterface $resource
*
* @return array
*/
abstract protected function doListMembers(ResourceInterface $resource);
/**
* Do the create after having check that the current adapter is supported
*
* @param string $path
* @param string $file
* @param bool $recursive
*
* @return ArchiveInterface
*/
abstract protected function doCreate($path, $file, $recursive);
/**
* Makes the target path absolute as the adapters might have a different directory
*
* @param string $path The path to convert
*
* @return string The absolute path
*
* @throws InvalidArgumentException In case the path is not writable or does not exist
*/
private function makeTargetAbsolute($path)
{
$directory = dirname($path);
if (!is_dir($directory)) {
throw new InvalidArgumentException(sprintf('Target path %s does not exist.', $directory));
}
if (!is_writable($directory)) {
throw new InvalidArgumentException(sprintf('Target path %s is not writeable.', $directory));
}
return realpath($directory) . '/' . PathUtil::basename($path);
}
}

View file

@ -0,0 +1,240 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Adapter\Resource\FileResource;
use Alchemy\Zippy\Archive\MemberInterface;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Parser\ParserFactory;
use Alchemy\Zippy\Parser\ParserInterface;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactory;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactoryInterface;
use Alchemy\Zippy\Resource\ResourceManager;
use Symfony\Component\Process\ExecutableFinder;
use Symfony\Component\Process\ProcessBuilder;
abstract class AbstractBinaryAdapter extends AbstractAdapter implements BinaryAdapterInterface
{
/**
* The parser to use to parse command output
*
* @var ParserInterface
*/
protected $parser;
/**
* The deflator process builder factory to use to build binary command line
*
* @var ProcessBuilderFactoryInterface
*/
protected $deflator;
/**
* The inflator process builder factory to use to build binary command line
*
* @var ProcessBuilderFactoryInterface
*/
protected $inflator;
/**
* Constructor
*
* @param ParserInterface $parser An output parser
* @param ResourceManager $manager A resource manager
* @param ProcessBuilderFactoryInterface $inflator A process builder factory for the inflator binary
* @param ProcessBuilderFactoryInterface $deflator A process builder factory for the deflator binary
*/
public function __construct(
ParserInterface $parser,
ResourceManager $manager,
ProcessBuilderFactoryInterface $inflator,
ProcessBuilderFactoryInterface $deflator
) {
$this->parser = $parser;
parent::__construct($manager);
$this->deflator = $deflator;
$this->inflator = $inflator;
}
/**
* @inheritdoc
*/
public function getParser()
{
return $this->parser;
}
/**
* @inheritdoc
*/
public function setParser(ParserInterface $parser)
{
$this->parser = $parser;
return $this;
}
/**
* @inheritdoc
*/
public function getDeflator()
{
return $this->deflator;
}
/**
* @inheritdoc
*/
public function getInflator()
{
return $this->inflator;
}
/**
* @inheritdoc
*/
public function setDeflator(ProcessBuilderFactoryInterface $processBuilder)
{
$this->deflator = $processBuilder;
return $this;
}
public function setInflator(ProcessBuilderFactoryInterface $processBuilder)
{
$this->inflator = $processBuilder;
return $this;
}
/**
* @inheritdoc
*/
public function getInflatorVersion()
{
$this->requireSupport();
return $this->doGetInflatorVersion();
}
/**
* @inheritdoc
*/
public function getDeflatorVersion()
{
$this->requireSupport();
return $this->doGetDeflatorVersion();
}
/**
* Returns a new instance of the invoked adapter
*
* @param ExecutableFinder $finder
* @param ResourceManager $manager
* @param string|null $inflatorBinaryName The inflator binary name to use
* @param string|null $deflatorBinaryName The deflator binary name to use
*
* @return AbstractBinaryAdapter
*/
public static function newInstance(
ExecutableFinder $finder,
ResourceManager $manager,
$inflatorBinaryName = null,
$deflatorBinaryName = null
) {
$inflator = $inflatorBinaryName instanceof ProcessBuilderFactoryInterface ? $inflatorBinaryName : self::findABinary($inflatorBinaryName,
static::getDefaultInflatorBinaryName(), $finder);
$deflator = $deflatorBinaryName instanceof ProcessBuilderFactoryInterface ? $deflatorBinaryName : self::findABinary($deflatorBinaryName,
static::getDefaultDeflatorBinaryName(), $finder);
try {
$outputParser = ParserFactory::create(static::getName());
} catch (InvalidArgumentException $e) {
throw new RuntimeException(sprintf(
'Failed to get a new instance of %s',
get_called_class()), $e->getCode(), $e
);
}
if (null === $inflator) {
throw new RuntimeException(sprintf('Unable to create the inflator'));
}
if (null === $deflator) {
throw new RuntimeException(sprintf('Unable to create the deflator'));
}
return new static($outputParser, $manager, $inflator, $deflator);
}
private static function findABinary($wish, array $defaults, ExecutableFinder $finder)
{
$possibles = $wish ? (array) $wish : $defaults;
$binary = null;
foreach ($possibles as $possible) {
if (null !== $found = $finder->find($possible)) {
$binary = new ProcessBuilderFactory($found);
break;
}
}
return $binary;
}
/**
* Adds files to argument list
*
* @param MemberInterface[]|\SplFileInfo[]|string[] $files An array of files
* @param ProcessBuilder $builder A Builder instance
*
* @return bool
*/
protected function addBuilderFileArgument(array $files, ProcessBuilder $builder)
{
$iterations = 0;
array_walk($files, function($file) use ($builder, &$iterations) {
$builder->add(
$file instanceof \SplFileInfo ?
$file->getRealPath() : ($file instanceof MemberInterface ? $file->getLocation() : $file)
);
$iterations++;
});
return 0 !== $iterations;
}
protected function createResource($path)
{
return new FileResource($path);
}
/**
* Fetch the inflator version after having check that the current adapter is supported
*
* @return string
*/
abstract protected function doGetInflatorVersion();
/**
* Fetch the Deflator version after having check that the current adapter is supported
*
* @return string
*/
abstract protected function doGetDeflatorVersion();
}

View file

@ -0,0 +1,438 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Archive\Archive;
use Alchemy\Zippy\Archive\Member;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException;
abstract class AbstractTarAdapter extends AbstractBinaryAdapter
{
/**
* @inheritdoc
*/
protected function doCreate($path, $files, $recursive)
{
return $this->doTarCreate($this->getLocalOptions(), $path, $files, $recursive);
}
/**
* @inheritdoc
*/
protected function doListMembers(ResourceInterface $resource)
{
return $this->doTarListMembers($this->getLocalOptions(), $resource);
}
/**
* @inheritdoc
*/
protected function doAdd(ResourceInterface $resource, $files, $recursive)
{
return $this->doTarAdd($this->getLocalOptions(), $resource, $files, $recursive);
}
/**
* @inheritdoc
*/
protected function doRemove(ResourceInterface $resource, $files)
{
return $this->doTarRemove($this->getLocalOptions(), $resource, $files);
}
/**
* @inheritdoc
*/
protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false)
{
return $this->doTarExtractMembers($this->getLocalOptions(), $resource, $members, $to, $overwrite);
}
/**
* @inheritdoc
*/
protected function doExtract(ResourceInterface $resource, $to)
{
return $this->doTarExtract($this->getLocalOptions(), $resource, $to);
}
/**
* @inheritdoc
*/
protected function doGetInflatorVersion()
{
$process = $this
->inflator
->create()
->add('--version')
->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(), $process->getErrorOutput()
));
}
return $this->parser->parseInflatorVersion($process->getOutput() ?: '');
}
/**
* @inheritdoc
*/
protected function doGetDeflatorVersion()
{
return $this->getInflatorVersion();
}
protected function doTarCreate($options, $path, $files = null, $recursive = true)
{
$files = (array) $files;
$builder = $this
->inflator
->create();
if (!$recursive) {
$builder->add('--no-recursion');
}
$builder->add('-c');
foreach ((array) $options as $option) {
$builder->add((string) $option);
}
if (0 === count($files)) {
$nullFile = defined('PHP_WINDOWS_VERSION_BUILD') ? 'NUL' : '/dev/null';
$builder->add('-f');
$builder->add($path);
$builder->add('-T');
$builder->add($nullFile);
$process = $builder->getProcess();
$process->run();
} else {
$builder->add(sprintf('--file=%s', $path));
if (!$recursive) {
$builder->add('--no-recursion');
}
$collection = $this->manager->handle(getcwd(), $files);
$builder->setWorkingDirectory($collection->getContext());
$collection->forAll(function($i, ZippyResource $resource) use ($builder) {
return $builder->add($resource->getTarget());
});
$process = $builder->getProcess();
try {
$process->run();
} catch (ProcessException $e) {
$this->manager->cleanup($collection);
throw $e;
}
$this->manager->cleanup($collection);
}
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return new Archive($this->createResource($path), $this, $this->manager);
}
protected function doTarListMembers($options, ResourceInterface $resource)
{
$builder = $this
->inflator
->create();
foreach ($this->getListMembersOptions() as $option) {
$builder->add($option);
}
$builder
->add('--list')
->add('-v')
->add(sprintf('--file=%s', $resource->getResource()));
foreach ((array) $options as $option) {
$builder->add((string) $option);
}
$process = $builder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
$members = array();
foreach ($this->parser->parseFileListing($process->getOutput() ?: '') as $member) {
$members[] = new Member(
$resource,
$this,
$member['location'],
$member['size'],
$member['mtime'],
$member['is_dir']
);
}
return $members;
}
protected function doTarAdd($options, ResourceInterface $resource, $files, $recursive = true)
{
$files = (array) $files;
$builder = $this
->inflator
->create();
if (!$recursive) {
$builder->add('--no-recursion');
}
$builder
->add('--append')
->add(sprintf('--file=%s', $resource->getResource()));
foreach ((array) $options as $option) {
$builder->add((string) $option);
}
// there will be an issue if the file starts with a dash
// see --add-file=FILE
$collection = $this->manager->handle(getcwd(), $files);
$builder->setWorkingDirectory($collection->getContext());
$collection->forAll(function($i, ZippyResource $resource) use ($builder) {
return $builder->add($resource->getTarget());
});
$process = $builder->getProcess();
try {
$process->run();
} catch (ProcessException $e) {
$this->manager->cleanup($collection);
throw $e;
}
$this->manager->cleanup($collection);
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return $files;
}
protected function doTarRemove($options, ResourceInterface $resource, $files)
{
$files = (array) $files;
$builder = $this
->inflator
->create();
$builder
->add('--delete')
->add(sprintf('--file=%s', $resource->getResource()));
foreach ((array) $options as $option) {
$builder->add((string) $option);
}
if (!$this->addBuilderFileArgument($files, $builder)) {
throw new InvalidArgumentException('Invalid files');
}
$process = $builder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return $files;
}
protected function doTarExtract($options, ResourceInterface $resource, $to = null)
{
if (null !== $to && !is_dir($to)) {
throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
}
$builder = $this
->inflator
->create();
$builder
->add('--extract')
->add(sprintf('--file=%s', $resource->getResource()));
foreach ($this->getExtractOptions() as $option) {
$builder
->add($option);
}
foreach ((array) $options as $option) {
$builder->add((string) $option);
}
if (null !== $to) {
$builder
->add('--directory')
->add($to);
}
$process = $builder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return new \SplFileInfo($to ?: $resource->getResource());
}
/**
* @param array $options
* @param ResourceInterface $resource
* @param array $members
* @param string $to
* @param bool $overwrite
*
* @return array
*/
protected function doTarExtractMembers($options, ResourceInterface $resource, $members, $to = null, $overwrite = false)
{
if (null !== $to && !is_dir($to)) {
throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
}
$members = (array) $members;
$builder = $this
->inflator
->create();
if ($overwrite == false) {
$builder->add('-k');
}
$builder
->add('--extract')
->add(sprintf('--file=%s', $resource->getResource()));
foreach ($this->getExtractMembersOptions() as $option) {
$builder
->add($option);
}
foreach ((array) $options as $option) {
$builder->add((string) $option);
}
if (null !== $to) {
$builder
->add('--directory')
->add($to);
}
if (!$this->addBuilderFileArgument($members, $builder)) {
throw new InvalidArgumentException('Invalid files');
}
$process = $builder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return $members;
}
/**
* Returns an array of option for the listMembers command
*
* @return array
*/
abstract protected function getListMembersOptions();
/**
* Returns an array of option for the extract command
*
* @return array
*/
abstract protected function getExtractOptions();
/**
* Returns an array of option for the extractMembers command
*
* @return array
*/
abstract protected function getExtractMembersOptions();
/**
* Gets adapter specific additional options
*
* @return array
*/
abstract protected function getLocalOptions();
}

View file

@ -0,0 +1,222 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Adapter\BSDTar\TarBSDTarAdapter;
use Alchemy\Zippy\Adapter\BSDTar\TarBz2BSDTarAdapter;
use Alchemy\Zippy\Adapter\BSDTar\TarGzBSDTarAdapter;
use Alchemy\Zippy\Adapter\GNUTar\TarBz2GNUTarAdapter;
use Alchemy\Zippy\Adapter\GNUTar\TarGNUTarAdapter;
use Alchemy\Zippy\Adapter\GNUTar\TarGzGNUTarAdapter;
use Alchemy\Zippy\Resource\RequestMapper;
use Alchemy\Zippy\Resource\ResourceManager;
use Alchemy\Zippy\Resource\ResourceTeleporter;
use Alchemy\Zippy\Resource\TargetLocator;
use Alchemy\Zippy\Resource\TeleporterContainer;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Process\ExecutableFinder;
class AdapterContainer implements \ArrayAccess
{
private $items = array();
/**
* Builds the adapter container
*
* @return AdapterContainer
*/
public static function load()
{
$container = new static();
$container['zip.inflator'] = null;
$container['zip.deflator'] = null;
$container['resource-manager'] = function($container) {
return new ResourceManager(
$container['request-mapper'],
$container['resource-teleporter'],
$container['filesystem']
);
};
$container['executable-finder'] = function($container) {
return new ExecutableFinder();
};
$container['request-mapper'] = function($container) {
return new RequestMapper($container['target-locator']);
};
$container['target-locator'] = function() {
return new TargetLocator();
};
$container['teleporter-container'] = function($container) {
return TeleporterContainer::load();
};
$container['resource-teleporter'] = function($container) {
return new ResourceTeleporter($container['teleporter-container']);
};
$container['filesystem'] = function() {
return new Filesystem();
};
$container['Alchemy\\Zippy\\Adapter\\ZipAdapter'] = function($container) {
return ZipAdapter::newInstance(
$container['executable-finder'],
$container['resource-manager'],
$container['zip.inflator'],
$container['zip.deflator']
);
};
$container['gnu-tar.inflator'] = null;
$container['gnu-tar.deflator'] = null;
$container['Alchemy\\Zippy\\Adapter\\GNUTar\\TarGNUTarAdapter'] = function($container) {
return TarGNUTarAdapter::newInstance(
$container['executable-finder'],
$container['resource-manager'],
$container['gnu-tar.inflator'],
$container['gnu-tar.deflator']
);
};
$container['Alchemy\\Zippy\\Adapter\\GNUTar\\TarGzGNUTarAdapter'] = function($container) {
return TarGzGNUTarAdapter::newInstance(
$container['executable-finder'],
$container['resource-manager'],
$container['gnu-tar.inflator'],
$container['gnu-tar.deflator']
);
};
$container['Alchemy\\Zippy\\Adapter\\GNUTar\\TarBz2GNUTarAdapter'] = function($container) {
return TarBz2GNUTarAdapter::newInstance(
$container['executable-finder'],
$container['resource-manager'],
$container['gnu-tar.inflator'],
$container['gnu-tar.deflator']
);
};
$container['bsd-tar.inflator'] = null;
$container['bsd-tar.deflator'] = null;
$container['Alchemy\\Zippy\\Adapter\\BSDTar\\TarBSDTarAdapter'] = function($container) {
return TarBSDTarAdapter::newInstance(
$container['executable-finder'],
$container['resource-manager'],
$container['bsd-tar.inflator'],
$container['bsd-tar.deflator']
);
};
$container['Alchemy\\Zippy\\Adapter\\BSDTar\\TarGzBSDTarAdapter'] = function($container) {
return TarGzBSDTarAdapter::newInstance(
$container['executable-finder'],
$container['resource-manager'],
$container['bsd-tar.inflator'],
$container['bsd-tar.deflator']
);
};
$container['Alchemy\\Zippy\\Adapter\\BSDTar\\TarBz2BSDTarAdapter'] = function($container) {
return TarBz2BSDTarAdapter::newInstance(
$container['executable-finder'],
$container['resource-manager'],
$container['bsd-tar.inflator'],
$container['bsd-tar.deflator']);
};
$container['Alchemy\\Zippy\\Adapter\\ZipExtensionAdapter'] = function() {
return ZipExtensionAdapter::newInstance();
};
return $container;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Whether a offset exists
*
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @return bool true on success or false on failure.
* <p>The return value will be casted to boolean if non-boolean was returned.</p>
*/
public function offsetExists($offset)
{
return isset($this->items[$offset]);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to retrieve
* @link http://php.net/manual/en/arrayaccess.offsetget.php
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
* @return mixed Can return all value types.
*/
public function offsetGet($offset)
{
if (array_key_exists($offset, $this->items) && is_callable($this->items[$offset])) {
$this->items[$offset] = call_user_func($this->items[$offset], $this);
}
if (array_key_exists($offset, $this->items)) {
return $this->items[$offset];
}
throw new \InvalidArgumentException();
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to set
* @link http://php.net/manual/en/arrayaccess.offsetset.php
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
* @return void
*/
public function offsetSet($offset, $value)
{
$this->items[$offset] = $value;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to unset
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
* @param mixed $offset <p>
* The offset to unset.
* </p>
* @return void
*/
public function offsetUnset($offset)
{
unset($this->items[$offset]);
}
}

View file

@ -0,0 +1,133 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Archive\ArchiveInterface;
use Alchemy\Zippy\Exception\NotSupportedException;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\Exception\InvalidArgumentException;
Interface AdapterInterface
{
/**
* Opens an archive
*
* @param string $path The path to the archive
*
* @return ArchiveInterface
*
* @throws InvalidArgumentException In case the provided path is not valid
* @throws RuntimeException In case of failure
*/
public function open($path);
/**
* Creates a new archive
*
* Please note some adapters can not create empty archives.
* They would throw a `NotSupportedException` in case you ask to create an archive without files
*
* @param string $path The path to the archive
* @param string|string[]|\Traversable|null $files A filename, an array of files, or a \Traversable instance
* @param bool $recursive Whether to recurse or not in the provided directories
*
* @return ArchiveInterface
*
* @throws RuntimeException In case of failure
* @throws NotSupportedException In case the operation in not supported
* @throws InvalidArgumentException In case no files could be added
*/
public function create($path, $files = null, $recursive = true);
/**
* Tests if the adapter is supported by the current environment
*
* @return bool
*/
public function isSupported();
/**
* Returns the list of all archive members
*
* @param ResourceInterface $resource The path to the archive
*
* @return array
*
* @throws RuntimeException In case of failure
*/
public function listMembers(ResourceInterface $resource);
/**
* Adds a file to the archive
*
* @param ResourceInterface $resource The path to the archive
* @param string|array|\Traversable $files An array of paths to add, relative to cwd
* @param bool $recursive Whether or not to recurse in the provided directories
*
* @return array
*
* @throws RuntimeException In case of failure
* @throws InvalidArgumentException In case no files could be added
*/
public function add(ResourceInterface $resource, $files, $recursive = true);
/**
* Removes a member of the archive
*
* @param ResourceInterface $resource The path to the archive
* @param string|array|\Traversable $files A filename, an array of files, or a \Traversable instance
*
* @return array
*
* @throws RuntimeException In case of failure
* @throws InvalidArgumentException In case no files could be removed
*/
public function remove(ResourceInterface $resource, $files);
/**
* Extracts an entire archive
*
* Note that any existing files will be overwritten by the adapter
*
* @param ResourceInterface $resource The path to the archive
* @param string|null $to The path where to extract the archive
*
* @return \SplFileInfo The extracted archive
*
* @throws RuntimeException In case of failure
* @throws InvalidArgumentException In case the provided path where to extract the archive is not valid
*/
public function extract(ResourceInterface $resource, $to = null);
/**
* Extracts specific members of the archive
*
* @param ResourceInterface $resource The path to the archive
* @param string|string[] $members A path or array of paths matching the members to extract from the resource.
* @param string|null $to The path where to extract the members
* @param bool $overwrite Whether to overwrite existing files in target directory
*
* @return \SplFileInfo The extracted archive
*
* @throws RuntimeException In case of failure
* @throws InvalidArgumentException In case no members could be removed or providedd extract target directory is not valid
*/
public function extractMembers(ResourceInterface $resource, $members, $to = null, $overwrite = false);
/**
* Returns the adapter name
*
* @return string
*/
public static function getName();
}

View file

@ -0,0 +1,79 @@
<?php
namespace Alchemy\Zippy\Adapter\BSDTar;
use Alchemy\Zippy\Adapter\AbstractTarAdapter;
use Alchemy\Zippy\Adapter\VersionProbe\BSDTarVersionProbe;
use Alchemy\Zippy\Parser\ParserInterface;
use Alchemy\Zippy\Resource\ResourceManager;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactoryInterface;
/**
* BSDTAR allows you to create and extract files from archives using BSD tar
*
* @see http://people.freebsd.org/~kientzle/libarchive/man/bsdtar.1.txt
*/
class TarBSDTarAdapter extends AbstractTarAdapter
{
public function __construct(ParserInterface $parser, ResourceManager $manager, ProcessBuilderFactoryInterface $inflator, ProcessBuilderFactoryInterface $deflator)
{
parent::__construct($parser, $manager, $inflator, $deflator);
$this->probe = new BSDTarVersionProbe($inflator, $deflator);
}
/**
* @inheritdoc
*/
protected function getLocalOptions()
{
return array();
}
/**
* @inheritdoc
*/
public static function getName()
{
return 'bsd-tar';
}
/**
* @inheritdoc
*/
public static function getDefaultDeflatorBinaryName()
{
return array('bsdtar', 'tar');
}
/**
* @inheritdoc
*/
public static function getDefaultInflatorBinaryName()
{
return array('bsdtar', 'tar');
}
/**
* {@inheritdoc}
*/
protected function getListMembersOptions()
{
return array();
}
/**
* {@inheritdoc}
*/
protected function getExtractOptions()
{
return array();
}
/**
* {@inheritdoc}
*/
protected function getExtractMembersOptions()
{
return array();
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Alchemy\Zippy\Adapter\BSDTar;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Exception\NotSupportedException;
class TarBz2BSDTarAdapter extends TarBSDTarAdapter
{
/**
* @inheritdoc
*/
protected function doAdd(ResourceInterface $resource, $files, $recursive)
{
throw new NotSupportedException('Updating a compressed tar archive is not supported.');
}
/**
* @inheritdoc
*/
protected function getLocalOptions()
{
return array('--bzip2');
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Alchemy\Zippy\Adapter\BSDTar;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Exception\NotSupportedException;
class TarGzBSDTarAdapter extends TarBSDTarAdapter
{
/**
* @inheritdoc
*/
protected function doAdd(ResourceInterface $resource, $files, $recursive)
{
throw new NotSupportedException('Updating a compressed tar archive is not supported.');
}
/**
* @inheritdoc
*/
protected function getLocalOptions()
{
return array('--gzip');
}
}

View file

@ -0,0 +1,94 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Parser\ParserInterface;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactoryInterface;
interface BinaryAdapterInterface
{
/**
* Gets the output parser
*
* @return ParserInterface
*/
public function getParser();
/**
* Sets the parser
*
* @param ParserInterface $parser The parser to use
*
* @return AbstractBinaryAdapter
*/
public function setParser(ParserInterface $parser);
/**
* Returns the inflator process builder
*
* @return ProcessBuilderFactoryInterface
*/
public function getInflator();
/**
* Sets the inflator process builder
*
* @param ProcessBuilderFactoryInterface $processBuilder The parser to use
*
* @return AbstractBinaryAdapter
*/
public function setInflator(ProcessBuilderFactoryInterface $processBuilder);
/**
* Returns the deflator process builder
*
* @return ProcessBuilderFactoryInterface
*/
public function getDeflator();
/**
* Sets the deflator process builder
*
* @param ProcessBuilderFactoryInterface $processBuilder The parser to use
*
* @return AbstractBinaryAdapter
*/
public function setDeflator(ProcessBuilderFactoryInterface $processBuilder);
/**
* Returns the inflator binary version
*
* @return string
*/
public function getInflatorVersion();
/**
* Returns the deflator binary version
*
* @return string
*/
public function getDeflatorVersion();
/**
* Gets the inflator adapter binary name
*
* @return array
*/
public static function getDefaultInflatorBinaryName();
/**
* Gets the deflator adapter binary name
*
* @return array
*/
public static function getDefaultDeflatorBinaryName();
}

View file

@ -0,0 +1,25 @@
<?php
namespace Alchemy\Zippy\Adapter\GNUTar;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Exception\NotSupportedException;
class TarBz2GNUTarAdapter extends TarGNUTarAdapter
{
/**
* @inheritdoc
*/
protected function doAdd(ResourceInterface $resource, $files, $recursive)
{
throw new NotSupportedException('Updating a compressed tar archive is not supported.');
}
/**
* @inheritdoc
*/
protected function getLocalOptions()
{
return array('--bzip2');
}
}

View file

@ -0,0 +1,79 @@
<?php
namespace Alchemy\Zippy\Adapter\GNUTar;
use Alchemy\Zippy\Adapter\AbstractTarAdapter;
use Alchemy\Zippy\Adapter\VersionProbe\GNUTarVersionProbe;
use Alchemy\Zippy\Parser\ParserInterface;
use Alchemy\Zippy\Resource\ResourceManager;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactoryInterface;
/**
* GNUTarAdapter allows you to create and extract files from archives using GNU tar
*
* @see http://www.gnu.org/software/tar/manual/tar.html
*/
class TarGNUTarAdapter extends AbstractTarAdapter
{
public function __construct(ParserInterface $parser, ResourceManager $manager, ProcessBuilderFactoryInterface $inflator, ProcessBuilderFactoryInterface $deflator)
{
parent::__construct($parser, $manager, $inflator, $deflator);
$this->probe = new GNUTarVersionProbe($inflator, $deflator);
}
/**
* @inheritdoc
*/
protected function getLocalOptions()
{
return array();
}
/**
* @inheritdoc
*/
public static function getName()
{
return 'gnu-tar';
}
/**
* @inheritdoc
*/
public static function getDefaultDeflatorBinaryName()
{
return array('gnutar', 'tar');
}
/**
* @inheritdoc
*/
public static function getDefaultInflatorBinaryName()
{
return array('gnutar', 'tar');
}
/**
* {@inheritdoc}
*/
protected function getListMembersOptions()
{
return array('--utc');
}
/**
* {@inheritdoc}
*/
protected function getExtractOptions()
{
return array('--overwrite');
}
/**
* {@inheritdoc}
*/
protected function getExtractMembersOptions()
{
return array('--overwrite');
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Alchemy\Zippy\Adapter\GNUTar;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Exception\NotSupportedException;
class TarGzGNUTarAdapter extends TarGNUTarAdapter
{
/**
* @inheritdoc
*/
protected function doAdd(ResourceInterface $resource, $files, $recursive)
{
throw new NotSupportedException('Updating a compressed tar archive is not supported.');
}
/**
* @inheritdoc
*/
protected function getLocalOptions()
{
return array('--gzip');
}
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\Resource;
class FileResource implements ResourceInterface
{
private $path;
public function __construct($path)
{
$this->path = $path;
}
/**
* {@inheritdoc}
*/
public function getResource()
{
return $this->path;
}
}

View file

@ -0,0 +1,23 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\Resource;
interface ResourceInterface
{
/**
* Returns the actual resource used by an adapter
*
* @return mixed
*/
public function getResource();
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\Resource;
class ZipArchiveResource implements ResourceInterface
{
private $archive;
public function __construct(\ZipArchive $archive)
{
$this->archive = $archive;
}
/**
* {@inheritdoc}
*/
public function getResource()
{
return $this->archive;
}
}

View file

@ -0,0 +1,77 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\VersionProbe;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactoryInterface;
use Symfony\Component\Process\Process;
abstract class AbstractTarVersionProbe implements VersionProbeInterface
{
private $isSupported;
private $inflator;
private $deflator;
public function __construct(ProcessBuilderFactoryInterface $inflator, ProcessBuilderFactoryInterface $deflator)
{
$this->inflator = $inflator;
$this->deflator = $deflator;
}
/**
* {@inheritdoc}
*/
public function getStatus()
{
if (null !== $this->isSupported) {
return $this->isSupported;
}
if (null === $this->inflator || null === $this->deflator) {
return $this->isSupported = VersionProbeInterface::PROBE_NOTSUPPORTED;
}
$good = true;
foreach (array($this->inflator, $this->deflator) as $builder) {
/** @var Process $process */
$process = $builder
->create()
->add('--version')
->getProcess();
$process->run();
if (!$process->isSuccessful()) {
return $this->isSupported = VersionProbeInterface::PROBE_NOTSUPPORTED;
}
$lines = explode("\n", $process->getOutput(), 2);
$good = false !== stripos($lines[0], $this->getVersionSignature());
if (!$good) {
break;
}
}
$this->isSupported = $good ? VersionProbeInterface::PROBE_OK : VersionProbeInterface::PROBE_NOTSUPPORTED;
return $this->isSupported;
}
/**
* Returns the signature of inflator/deflator
*
* @return string
*/
abstract protected function getVersionSignature();
}

View file

@ -0,0 +1,24 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\VersionProbe;
class BSDTarVersionProbe extends AbstractTarVersionProbe
{
/**
* {@inheritdoc}
*/
protected function getVersionSignature()
{
return 'bsdtar';
}
}

View file

@ -0,0 +1,24 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\VersionProbe;
class GNUTarVersionProbe extends AbstractTarVersionProbe
{
/**
* {@inheritdoc}
*/
protected function getVersionSignature()
{
return '(gnu tar)';
}
}

View file

@ -0,0 +1,26 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\VersionProbe;
interface VersionProbeInterface
{
const PROBE_OK = 0;
const PROBE_NOTSUPPORTED = 1;
/**
* Probes for the support of an adapter.
*
* @return integer One of the self::PROBE_* constants
*/
public function getStatus();
}

View file

@ -0,0 +1,24 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\VersionProbe;
class ZipExtensionVersionProbe implements VersionProbeInterface
{
/**
* {@inheritdoc}
*/
public function getStatus()
{
return class_exists('\ZipArchive') ? VersionProbeInterface::PROBE_OK : VersionProbeInterface::PROBE_NOTSUPPORTED;
}
}

View file

@ -0,0 +1,96 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
*/
namespace Alchemy\Zippy\Adapter\VersionProbe;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactoryInterface;
class ZipVersionProbe implements VersionProbeInterface
{
private $isSupported;
private $inflator;
private $deflator;
public function __construct(ProcessBuilderFactoryInterface $inflator, ProcessBuilderFactoryInterface $deflator)
{
$this->inflator = $inflator;
$this->deflator = $deflator;
}
/**
* Set the inflator to zip
*
* @param ProcessBuilderFactoryInterface $inflator
* @return ZipVersionProbe
*/
public function setInflator(ProcessBuilderFactoryInterface $inflator)
{
$this->inflator = $inflator;
return $this;
}
/**
* Set the deflator to unzip
*
* @param ProcessBuilderFactoryInterface $deflator
* @return ZipVersionProbe
*/
public function setDeflator(ProcessBuilderFactoryInterface $deflator)
{
$this->deflator = $deflator;
return $this;
}
/**
* {@inheritdoc}
*/
public function getStatus()
{
if (null !== $this->isSupported) {
return $this->isSupported;
}
if (null === $this->inflator || null === $this->deflator) {
return $this->isSupported = VersionProbeInterface::PROBE_NOTSUPPORTED;
}
$processDeflate = $this
->deflator
->create()
->add('-h')
->getProcess();
$processDeflate->run();
$processInflate = $this
->inflator
->create()
->add('-h')
->getProcess();
$processInflate->run();
if (false === $processDeflate->isSuccessful() || false === $processInflate->isSuccessful()) {
return $this->isSupported = VersionProbeInterface::PROBE_NOTSUPPORTED;
}
$lines = explode("\n", $processInflate->getOutput(), 2);
$inflatorOk = false !== stripos($lines[0], 'Info-ZIP');
$lines = explode("\n", $processDeflate->getOutput(), 2);
$deflatorOk = false !== stripos($lines[0], 'Info-ZIP');
return $this->isSupported = ($inflatorOk && $deflatorOk) ? VersionProbeInterface::PROBE_OK : VersionProbeInterface::PROBE_NOTSUPPORTED;
}
}

View file

@ -0,0 +1,370 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Adapter\VersionProbe\ZipVersionProbe;
use Alchemy\Zippy\Archive\Archive;
use Alchemy\Zippy\Archive\Member;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Exception\NotSupportedException;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\Parser\ParserInterface;
use Alchemy\Zippy\ProcessBuilder\ProcessBuilderFactoryInterface;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceManager;
use Symfony\Component\Process\Exception\ExceptionInterface as ProcessException;
/**
* ZipAdapter allows you to create and extract files from archives using Zip
*
* @see http://www.gnu.org/software/tar/manual/tar.html
*/
class ZipAdapter extends AbstractBinaryAdapter
{
public function __construct(
ParserInterface $parser,
ResourceManager $manager,
ProcessBuilderFactoryInterface $inflator,
ProcessBuilderFactoryInterface $deflator
) {
parent::__construct($parser, $manager, $inflator, $deflator);
$this->probe = new ZipVersionProbe($inflator, $deflator);
}
/**
* @inheritdoc
*/
protected function doCreate($path, $files, $recursive)
{
$files = (array) $files;
$builder = $this
->inflator
->create();
if (0 === count($files)) {
throw new NotSupportedException('Can not create empty zip archive');
}
if ($recursive) {
$builder->add('-r');
}
$builder->add($path);
$collection = $this->manager->handle(getcwd(), $files);
$builder->setWorkingDirectory($collection->getContext());
$collection->forAll(function($i, ZippyResource $resource) use ($builder) {
return $builder->add($resource->getTarget());
});
$process = $builder->getProcess();
try {
$process->run();
} catch (ProcessException $e) {
$this->manager->cleanup($collection);
throw $e;
}
$this->manager->cleanup($collection);
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return new Archive($this->createResource($path), $this, $this->manager);
}
/**
* @inheritdoc
*/
protected function doListMembers(ResourceInterface $resource)
{
$process = $this
->deflator
->create()
->add('-l')
->add($resource->getResource())
->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
$members = array();
foreach ($this->parser->parseFileListing($process->getOutput() ?: '') as $member) {
$members[] = new Member(
$resource,
$this,
$member['location'],
$member['size'],
$member['mtime'],
$member['is_dir']
);
}
return $members;
}
/**
* @inheritdoc
*/
protected function doAdd(ResourceInterface $resource, $files, $recursive)
{
$files = (array) $files;
$builder = $this
->inflator
->create();
if ($recursive) {
$builder->add('-r');
}
$builder
->add('-u')
->add($resource->getResource());
$collection = $this->manager->handle(getcwd(), $files);
$builder->setWorkingDirectory($collection->getContext());
$collection->forAll(function($i, ZippyResource $resource) use ($builder) {
return $builder->add($resource->getTarget());
});
$process = $builder->getProcess();
try {
$process->run();
} catch (ProcessException $e) {
$this->manager->cleanup($collection);
throw $e;
}
$this->manager->cleanup($collection);
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
}
/**
* @inheritdoc
*/
protected function doGetDeflatorVersion()
{
$process = $this
->deflator
->create()
->add('-h')
->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return $this->parser->parseDeflatorVersion($process->getOutput() ?: '');
}
/**
* @inheritdoc
*/
protected function doGetInflatorVersion()
{
$process = $this
->inflator
->create()
->add('-h')
->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return $this->parser->parseInflatorVersion($process->getOutput() ?: '');
}
/**
* @inheritdoc
*/
protected function doRemove(ResourceInterface $resource, $files)
{
$files = (array) $files;
$builder = $this
->inflator
->create();
$builder
->add('-d')
->add($resource->getResource());
if (!$this->addBuilderFileArgument($files, $builder)) {
throw new InvalidArgumentException('Invalid files');
}
$process = $builder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return $files;
}
/**
* @inheritdoc
*/
public static function getName()
{
return 'zip';
}
/**
* @inheritdoc
*/
public static function getDefaultDeflatorBinaryName()
{
return array('unzip');
}
/**
* @inheritdoc
*/
public static function getDefaultInflatorBinaryName()
{
return array('zip');
}
/**
* @inheritdoc
*/
protected function doExtract(ResourceInterface $resource, $to)
{
if (null !== $to && !is_dir($to)) {
throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
}
$builder = $this
->deflator
->create();
$builder
->add('-o')
->add($resource->getResource());
if (null !== $to) {
$builder
->add('-d')
->add($to);
}
$process = $builder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return new \SplFileInfo($to ?: $resource->getResource());
}
/**
* @inheritdoc
*/
protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false)
{
if (null !== $to && !is_dir($to)) {
throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
}
$members = (array) $members;
$builder = $this
->deflator
->create();
if ((bool) $overwrite) {
$builder->add('-o');
}
$builder
->add($resource->getResource());
if (null !== $to) {
$builder
->add('-d')
->add($to);
}
if (!$this->addBuilderFileArgument($members, $builder)) {
throw new InvalidArgumentException('Invalid files');
}
$process = $builder->getProcess();
$process->run();
if (!$process->isSuccessful()) {
throw new RuntimeException(sprintf(
'Unable to execute the following command %s {output: %s}',
$process->getCommandLine(),
$process->getErrorOutput()
));
}
return $members;
}
}

View file

@ -0,0 +1,361 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Adapter;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Adapter\Resource\ZipArchiveResource;
use Alchemy\Zippy\Adapter\VersionProbe\ZipExtensionVersionProbe;
use Alchemy\Zippy\Archive\Archive;
use Alchemy\Zippy\Archive\Member;
use Alchemy\Zippy\Exception\NotSupportedException;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceManager;
/**
* ZipExtensionAdapter allows you to create and extract files from archives
* using PHP Zip extension
*
* @see http://www.php.net/manual/en/book.zip.php
*/
class ZipExtensionAdapter extends AbstractAdapter
{
private $errorCodesMapping = array(
\ZipArchive::ER_EXISTS => "File already exists",
\ZipArchive::ER_INCONS => "Zip archive inconsistent",
\ZipArchive::ER_INVAL => "Invalid argument",
\ZipArchive::ER_MEMORY => "Malloc failure",
\ZipArchive::ER_NOENT => "No such file",
\ZipArchive::ER_NOZIP => "Not a zip archive",
\ZipArchive::ER_OPEN => "Can't open file",
\ZipArchive::ER_READ => "Read error",
\ZipArchive::ER_SEEK => "Seek error"
);
public function __construct(ResourceManager $manager)
{
parent::__construct($manager);
$this->probe = new ZipExtensionVersionProbe();
}
/**
* @inheritdoc
*/
protected function doListMembers(ResourceInterface $resource)
{
$members = array();
for ($i = 0; $i < $resource->getResource()->numFiles; $i++) {
$stat = $resource->getResource()->statIndex($i);
$members[] = new Member(
$resource,
$this,
$stat['name'],
$stat['size'],
new \DateTime('@' . $stat['mtime']),
0 === strlen($resource->getResource()->getFromIndex($i, 1))
);
}
return $members;
}
/**
* @inheritdoc
*/
public static function getName()
{
return 'zip-extension';
}
/**
* @inheritdoc
*/
protected function doExtract(ResourceInterface $resource, $to)
{
return $this->extractMembers($resource, null, $to);
}
/**
* @inheritdoc
*/
protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false)
{
if (null === $to) {
// if no destination is given, will extract to zip current folder
$to = dirname(realpath($resource->getResource()->filename));
}
if (!is_dir($to)) {
$resource->getResource()->close();
throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
}
if (!is_writable($to)) {
$resource->getResource()->close();
throw new InvalidArgumentException(sprintf("%s is not writable", $to));
}
if (null !== $members) {
$membersTemp = (array) $members;
if (empty($membersTemp)) {
$resource->getResource()->close();
throw new InvalidArgumentException("no members provided");
}
$members = array();
// allows $members to be an array of strings or array of Members
foreach ($membersTemp as $member) {
if ($member instanceof Member) {
$member = $member->getLocation();
}
if ($resource->getResource()->locateName($member) === false) {
$resource->getResource()->close();
throw new InvalidArgumentException(sprintf('%s is not in the zip file', $member));
}
if ($overwrite == false) {
if (file_exists($member)) {
$resource->getResource()->close();
throw new RuntimeException('Target file ' . $member . ' already exists.');
}
}
$members[] = $member;
}
}
if (!$resource->getResource()->extractTo($to, $members)) {
$resource->getResource()->close();
throw new InvalidArgumentException(sprintf('Unable to extract archive : %s', $resource->getResource()->getStatusString()));
}
return new \SplFileInfo($to);
}
/**
* @inheritdoc
*/
protected function doRemove(ResourceInterface $resource, $files)
{
$files = (array) $files;
if (empty($files)) {
throw new InvalidArgumentException("no files provided");
}
// either remove all files or none in case of error
foreach ($files as $file) {
if ($resource->getResource()->locateName($file) === false) {
$resource->getResource()->unchangeAll();
$resource->getResource()->close();
throw new InvalidArgumentException(sprintf('%s is not in the zip file', $file));
}
if (!$resource->getResource()->deleteName($file)) {
$resource->getResource()->unchangeAll();
$resource->getResource()->close();
throw new RuntimeException(sprintf('unable to remove %s', $file));
}
}
$this->flush($resource->getResource());
return $files;
}
/**
* @inheritdoc
*/
protected function doAdd(ResourceInterface $resource, $files, $recursive)
{
$files = (array) $files;
if (empty($files)) {
$resource->getResource()->close();
throw new InvalidArgumentException("no files provided");
}
$this->addEntries($resource, $files, $recursive);
return $files;
}
/**
* @inheritdoc
*/
protected function doCreate($path, $files, $recursive)
{
$files = (array) $files;
if (empty($files)) {
throw new NotSupportedException("Cannot create an empty zip");
}
$resource = $this->getResource($path, \ZipArchive::CREATE);
$this->addEntries($resource, $files, $recursive);
return new Archive($resource, $this, $this->manager);
}
/**
* Returns a new instance of the invoked adapter
*
* @return AbstractAdapter
*
* @throws RuntimeException In case object could not be instanciated
*/
public static function newInstance()
{
return new ZipExtensionAdapter(ResourceManager::create());
}
protected function createResource($path)
{
return $this->getResource($path, \ZipArchive::CHECKCONS);
}
private function getResource($path, $mode)
{
$zip = new \ZipArchive();
$res = $zip->open($path, $mode);
if ($res !== true) {
throw new RuntimeException($this->errorCodesMapping[$res]);
}
return new ZipArchiveResource($zip);
}
private function addEntries(ResourceInterface $zipResource, array $files, $recursive)
{
$stack = new \SplStack();
$error = null;
$cwd = getcwd();
$collection = $this->manager->handle($cwd, $files);
$this->chdir($collection->getContext());
$adapter = $this;
try {
$collection->forAll(function($i, ZippyResource $resource) use ($zipResource, $stack, $recursive, $adapter) {
$adapter->checkReadability($zipResource->getResource(), $resource->getTarget());
if (is_dir($resource->getTarget())) {
if ($recursive) {
$stack->push($resource->getTarget() . ((substr($resource->getTarget(), -1) === DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR));
} else {
$adapter->addEmptyDir($zipResource->getResource(), $resource->getTarget());
}
} else {
$adapter->addFileToZip($zipResource->getResource(), $resource->getTarget());
}
return true;
});
// recursively add dirs
while (!$stack->isEmpty()) {
$dir = $stack->pop();
// removes . and ..
$files = array_diff(scandir($dir), array(".", ".."));
if (count($files) > 0) {
foreach ($files as $file) {
$file = $dir . $file;
$this->checkReadability($zipResource->getResource(), $file);
if (is_dir($file)) {
$stack->push($file . DIRECTORY_SEPARATOR);
} else {
$this->addFileToZip($zipResource->getResource(), $file);
}
}
} else {
$this->addEmptyDir($zipResource->getResource(), $dir);
}
}
$this->flush($zipResource->getResource());
$this->manager->cleanup($collection);
} catch (\Exception $e) {
$error = $e;
}
$this->chdir($cwd);
if ($error) {
throw $error;
}
}
/**
* @info is public for PHP 5.3 compatibility, should be private
*
* @param \ZipArchive $zip
* @param string $file
*/
public function checkReadability(\ZipArchive $zip, $file)
{
if (!is_readable($file)) {
$zip->unchangeAll();
$zip->close();
throw new InvalidArgumentException(sprintf('could not read %s', $file));
}
}
/**
* @info is public for PHP 5.3 compatibility, should be private
*
* @param \ZipArchive $zip
* @param string $file
*/
public function addFileToZip(\ZipArchive $zip, $file)
{
if (!$zip->addFile($file)) {
$zip->unchangeAll();
$zip->close();
throw new RuntimeException(sprintf('unable to add %s to the zip file', $file));
}
}
/**
* @info is public for PHP 5.3 compatibility, should be private
*
* @param \ZipArchive $zip
* @param string $dir
*/
public function addEmptyDir(\ZipArchive $zip, $dir)
{
if (!$zip->addEmptyDir($dir)) {
$zip->unchangeAll();
$zip->close();
throw new RuntimeException(sprintf('unable to add %s to the zip file', $dir));
}
}
/**
* Flushes changes to the archive
*
* @param \ZipArchive $zip
*/
private function flush(\ZipArchive $zip) // flush changes by reopening the file
{
$path = $zip->filename;
$zip->close();
$zip->open($path, \ZipArchive::CHECKCONS);
}
}

View file

@ -0,0 +1,136 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Archive;
use Alchemy\Zippy\Adapter\AdapterInterface;
use Alchemy\Zippy\Resource\ResourceManager;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
/**
* Represents an archive
*/
class Archive implements ArchiveInterface
{
/**
* The path to the archive
*
* @var string
*/
protected $path;
/**
* The archive adapter
*
* @var AdapterInterface
*/
protected $adapter;
/**
* An array of archive members
*
* @var MemberInterface[]
*/
protected $members = array();
/**
* @var ResourceInterface
*/
protected $resource;
/**
*
* @var ResourceManager
*/
protected $manager;
/**
* Constructor
*
* @param ResourceInterface $resource Path to the archive
* @param AdapterInterface $adapter An archive adapter
* @param ResourceManager $manager The resource manager
*/
public function __construct(ResourceInterface $resource, AdapterInterface $adapter, ResourceManager $manager)
{
$this->resource = $resource;
$this->adapter = $adapter;
$this->manager = $manager;
}
/**
* @inheritdoc
*/
public function count()
{
return count($this->getMembers());
}
/**
* Returns an Iterator for the current archive
*
* This method implements the IteratorAggregate interface.
*
* @return \ArrayIterator An iterator
*/
public function getIterator()
{
return new \ArrayIterator($this->getMembers());
}
/**
* @inheritdoc
*/
public function getMembers()
{
return $this->members = $this->adapter->listMembers($this->resource);
}
/**
* @inheritdoc
*/
public function addMembers($sources, $recursive = true)
{
$this->adapter->add($this->resource, $sources, $recursive);
return $this;
}
/**
* @inheritdoc
*/
public function removeMembers($sources)
{
$this->adapter->remove($this->resource, $sources);
return $this;
}
/**
* @inheritdoc
*/
public function extract($toDirectory)
{
$this->adapter->extract($this->resource, $toDirectory);
return $this;
}
/**
* @inheritdoc
*/
public function extractMembers($members, $toDirectory = null)
{
$this->adapter->extractMembers($this->resource, $members, $toDirectory);
return $this;
}
}

View file

@ -0,0 +1,74 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Archive;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\Exception\InvalidArgumentException;
interface ArchiveInterface extends \IteratorAggregate, \Countable
{
/**
* Adds a file or a folder into the archive
*
* @param string|array|\SplFileInfo $sources The path to the file or a folder
* @param bool $recursive Recurse into sub-directories
*
* @return ArchiveInterface
*
* @throws InvalidArgumentException In case the provided source path is not valid
* @throws RuntimeException In case of failure
*/
public function addMembers($sources, $recursive = true);
/**
* Removes a file from the archive
*
* @param string|array $sources The path to an archive or a folder member
*
* @return ArchiveInterface
*
* @throws RuntimeException In case of failure
*/
public function removeMembers($sources);
/**
* Lists files and directories archive members
*
* @return MemberInterface[] An array of File
*
* @throws RuntimeException In case archive could not be read
*/
public function getMembers();
/**
* Extracts current archive to the given directory
*
* @param string $toDirectory The path the extracted archive
*
* @return ArchiveInterface
*
* @throws RuntimeException In case archive could not be extracted
*/
public function extract($toDirectory);
/**
* Extracts specific members from the archive
*
* @param string|MemberInterface[] $members An array of members path
* @param string $toDirectory The destination $directory
*
* @return ArchiveInterface
*
* @throws RuntimeException In case member could not be extracted
*/
public function extractMembers($members, $toDirectory = null);
}

View file

@ -0,0 +1,147 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Archive;
use Alchemy\Zippy\Adapter\AdapterInterface;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
/**
* Represents a member of an archive.
*/
class Member implements MemberInterface
{
/**
* The location of the file
*
* @var string
*/
private $location;
/**
* Tells whether the archive member is a directory or not
*
* @var bool
*/
private $isDir;
/**
* The uncompressed size of the file
*
* @var int
*/
private $size;
/**
* The last modified date of the file
*
* @var \DateTime
*/
private $lastModifiedDate;
/**
* The resource to the actual archive
*
* @var string
*/
private $resource;
/**
* An adapter
*
* @var AdapterInterface
*/
private $adapter;
/**
* Constructor
*
* @param ResourceInterface $resource The path of the archive which contain the member
* @param AdapterInterface $adapter The archive adapter interface
* @param string $location The path of the archive member
* @param int $fileSize The uncompressed file size
* @param \DateTime $lastModifiedDate The last modified date of the member
* @param bool $isDir Tells whether the member is a directory or not
*/
public function __construct(
ResourceInterface $resource,
AdapterInterface $adapter,
$location,
$fileSize,
\DateTime $lastModifiedDate,
$isDir
) {
$this->resource = $resource;
$this->adapter = $adapter;
$this->location = $location;
$this->isDir = $isDir;
$this->size = $fileSize;
$this->lastModifiedDate = $lastModifiedDate;
}
/**
* {@inheritdoc}
*/
public function getLocation()
{
return $this->location;
}
/**
* {@inheritdoc}
*/
public function isDir()
{
return $this->isDir;
}
/**
* {@inheritdoc}
*/
public function getLastModifiedDate()
{
return $this->lastModifiedDate;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
return $this->size;
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->location;
}
/**
* {@inheritdoc}
*/
public function extract($to = null, $overwrite = false)
{
$this->adapter->extractMembers($this->resource, $this->location, $to, (bool) $overwrite);
return new \SplFileInfo(sprintf('%s%s', rtrim(null === $to ? getcwd() : $to, '/'), $this->location));
}
/**
* @inheritdoc
* */
public function getResource()
{
return $this->resource;
}
}

View file

@ -0,0 +1,78 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Archive;
use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Exception\RuntimeException;
interface MemberInterface
{
/**
* Gets the location of an archive member
*
* @return string
*/
public function getLocation();
/**
* Tells whether the member is a directory or not
*
* @return bool
*/
public function isDir();
/**
* Returns the last modified date of the member
*
* @return \DateTime
*/
public function getLastModifiedDate();
/**
* Returns the (uncompressed) size of the member
*
* If the size is unknown, returns -1
*
* @return integer
*/
public function getSize();
/**
* Extract the member from its archive
*
* Be careful using this method within a loop
* This will execute one extraction process for each file
* Prefer the use of ArchiveInterface::extractMembers in that use case
*
* @param string|null $to The path where to extract the member, if no path is not provided the member is extracted in the same directory of its archive
* @param bool $overwrite Whether to overwrite destination file if it already exists. Defaults to false
*
* @return \SplFileInfo The extracted file
*
* @throws RuntimeException In case of failure
* @throws InvalidArgumentException In case no members could be removed or provide extract target directory is not valid
*/
public function extract($to = null, $overwrite = false);
/**
* Get resource.
*
* @return ResourceInterface
* */
public function getResource();
/**
* @inheritdoc
*/
public function __toString();
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
interface ExceptionInterface
{
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
class FormatNotSupportedException extends RuntimeException
{
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
class IOException extends RuntimeException
{
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
{
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
class NoAdapterOnPlatformException extends RuntimeException
{
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
class NotSupportedException extends RuntimeException implements ExceptionInterface
{
}

View file

@ -0,0 +1,16 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
}

View file

@ -0,0 +1,28 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Exception;
class TargetLocatorException extends RuntimeException
{
private $resource;
public function __construct($resource, $message, $code = 0, $previous = null)
{
$this->resource = $resource;
parent::__construct($message, $code, $previous);
}
public function getResource()
{
return $this->resource;
}
}

View file

@ -0,0 +1,49 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
use Alchemy\Zippy\Adapter\AdapterContainer;
use Alchemy\Zippy\Exception\RuntimeException;
abstract class AbstractFileStrategy implements FileStrategyInterface
{
protected $container;
public function __construct(AdapterContainer $container)
{
$this->container = $container;
}
/**
* {@inheritdoc}
*/
public function getAdapters()
{
$services = array();
foreach ($this->getServiceNames() as $serviceName) {
try {
$services[] = $this->container[$serviceName];
} catch (RuntimeException $e) {
}
}
return $services;
}
/**
* Returns an array of service names that defines adapters.
*
* @return string[]
*/
abstract protected function getServiceNames();
}

View file

@ -0,0 +1,31 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
use Alchemy\Zippy\Adapter\AdapterInterface;
interface FileStrategyInterface
{
/**
* Returns an array of adapters that match the strategy
*
* @return AdapterInterface[]
*/
public function getAdapters();
/**
* Returns the file extension that match the strategy
*
* @return string
*/
public function getFileExtension();
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
class TB2FileStrategy extends AbstractFileStrategy
{
/**
* {@inheritdoc}
*/
protected function getServiceNames()
{
return array(
'Alchemy\\Zippy\\Adapter\\GNUTar\\TarBz2GNUTarAdapter',
'Alchemy\\Zippy\\Adapter\\BSDTar\\TarBz2BSDTarAdapter'
);
}
/**
* {@inheritdoc}
*/
public function getFileExtension()
{
return 'tb2';
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
class TBz2FileStrategy extends AbstractFileStrategy
{
/**
* {@inheritdoc}
*/
protected function getServiceNames()
{
return array(
'Alchemy\\Zippy\\Adapter\\GNUTar\\TarBz2GNUTarAdapter',
'Alchemy\\Zippy\\Adapter\\BSDTar\\TarBz2BSDTarAdapter'
);
}
/**
* {@inheritdoc}
*/
public function getFileExtension()
{
return 'tbz2';
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
class TGzFileStrategy extends AbstractFileStrategy
{
/**
* {@inheritdoc}
*/
protected function getServiceNames()
{
return array(
'Alchemy\\Zippy\\Adapter\\GNUTar\\TarGzGNUTarAdapter',
'Alchemy\\Zippy\\Adapter\\BSDTar\\TarGzBSDTarAdapter'
);
}
/**
* {@inheritdoc}
*/
public function getFileExtension()
{
return 'tgz';
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
class TarBz2FileStrategy extends AbstractFileStrategy
{
/**
* {@inheritdoc}
*/
protected function getServiceNames()
{
return array(
'Alchemy\\Zippy\\Adapter\\GNUTar\\TarBz2GNUTarAdapter',
'Alchemy\\Zippy\\Adapter\\BSDTar\\TarBz2BSDTarAdapter'
);
}
/**
* {@inheritdoc}
*/
public function getFileExtension()
{
return 'tar.bz2';
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
class TarFileStrategy extends AbstractFileStrategy
{
/**
* {@inheritdoc}
*/
protected function getServiceNames()
{
return array(
'Alchemy\\Zippy\\Adapter\\GNUTar\\TarGNUTarAdapter',
'Alchemy\\Zippy\\Adapter\\BSDTar\\TarBSDTarAdapter'
);
}
/**
* {@inheritdoc}
*/
public function getFileExtension()
{
return 'tar';
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
class TarGzFileStrategy extends AbstractFileStrategy
{
/**
* {@inheritdoc}
*/
protected function getServiceNames()
{
return array(
'Alchemy\\Zippy\\Adapter\\GNUTar\\TarGzGNUTarAdapter',
'Alchemy\\Zippy\\Adapter\\BSDTar\\TarGzBSDTarAdapter'
);
}
/**
* {@inheritdoc}
*/
public function getFileExtension()
{
return 'tar.gz';
}
}

View file

@ -0,0 +1,34 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\FileStrategy;
class ZipFileStrategy extends AbstractFileStrategy
{
/**
* {@inheritdoc}
*/
protected function getServiceNames()
{
return array(
'Alchemy\\Zippy\\Adapter\\ZipAdapter',
'Alchemy\\Zippy\\Adapter\\ZipExtensionAdapter'
);
}
/**
* {@inheritdoc}
*/
public function getFileExtension()
{
return 'zip';
}
}

View file

@ -0,0 +1,115 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Parser;
use Alchemy\Zippy\Exception\RuntimeException;
/**
* This class is responsible of parsing GNUTar command line output
*/
class BSDTarOutputParser implements ParserInterface
{
const PERMISSIONS = '([ldrwx-]+)';
const HARD_LINK = '(\d+)';
const OWNER = '([a-z][-a-z0-9]*)';
const GROUP = '([a-z][-a-z0-9]*)';
const FILESIZE = '(\d*)';
const DATE = '([a-zA-Z0-9]+\s+[a-z0-9]+\s+[a-z0-9:]+)';
const FILENAME = '(.*)';
/**
* @inheritdoc
*/
public function parseFileListing($output)
{
$lines = array_values(array_filter(explode("\n", $output)));
$members = array();
// BSDTar outputs two differents format of date according to the mtime
// of the member. If the member is younger than six months the year is not shown.
// On 4.5+ FreeBSD system the day is displayed first
$dateFormats = array('M d Y', 'M d H:i', 'd M Y', 'd M H:i');
foreach ($lines as $line) {
$matches = array();
// drw-rw-r-- 0 toto titi 0 Jan 3 1980 practice/
// -rw-rw-r-- 0 toto titi 10240 Jan 22 13:31 practice/records
if (!preg_match_all("#" .
self::PERMISSIONS . "\s+" . // match (drw-r--r--)
self::HARD_LINK . "\s+" . // match (1)
self::OWNER . "\s" . // match (toto)
self::GROUP . "\s+" . // match (titi)
self::FILESIZE . "\s+" . // match (0)
self::DATE . "\s+" . // match (Jan 3 1980)
self::FILENAME . // match (practice)
"#", $line, $matches, PREG_SET_ORDER
)) {
continue;
}
$chunks = array_shift($matches);
if (8 !== count($chunks)) {
continue;
}
$date = null;
foreach ($dateFormats as $format) {
$date = \DateTime::createFromFormat($format, $chunks[6]);
if (false === $date) {
continue;
} else {
break;
}
}
if (false === $date) {
throw new RuntimeException(sprintf('Failed to parse mtime date from %s', $line));
}
$members[] = array(
'location' => $chunks[7],
'size' => $chunks[5],
'mtime' => $date,
'is_dir' => 'd' === $chunks[1][0]
);
}
return $members;
}
/**
* @inheritdoc
*/
public function parseInflatorVersion($output)
{
$chunks = explode(' ', $output, 3);
if (2 > count($chunks)) {
return null;
}
list(, $version) = explode(' ', $output, 3);
return $version;
}
/**
* @inheritdoc
*/
public function parseDeflatorVersion($output)
{
return $this->parseInflatorVersion($output);
}
}

View file

@ -0,0 +1,98 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Parser;
use Alchemy\Zippy\Exception\RuntimeException;
/**
* This class is responsible of parsing GNUTar command line output
*/
class GNUTarOutputParser implements ParserInterface
{
const PERMISSIONS = '([ldrwx-]+)';
const OWNER = '([a-z][-a-z0-9]*)';
const GROUP = '([a-z][-a-z0-9]*)';
const FILESIZE = '(\d*)';
const ISO_DATE = '([0-9]+-[0-9]+-[0-9]+\s+[0-9]+:[0-9]+)';
const FILENAME = '(.*)';
/**
* @inheritdoc
*/
public function parseFileListing($output)
{
$lines = array_values(array_filter(explode("\n", $output)));
$members = array();
foreach ($lines as $line) {
$matches = array();
// -rw-r--r-- gray/staff 62373 2006-06-09 12:06 apple
if (!preg_match_all("#".
self::PERMISSIONS . "\s+" . // match (-rw-r--r--)
self::OWNER . "/" . // match (gray)
self::GROUP . "\s+" . // match (staff)
self::FILESIZE . "\s+" . // match (62373)
self::ISO_DATE . "\s+" . // match (2006-06-09 12:06)
self::FILENAME . // match (apple)
"#",
$line, $matches, PREG_SET_ORDER
)) {
continue;
}
$chunks = array_shift($matches);
if (7 !== count($chunks)) {
continue;
}
$date = \DateTime::createFromFormat("Y-m-d H:i", $chunks[5]);
if (false === $date) {
throw new RuntimeException(sprintf('Failed to parse mtime date from %s', $line));
}
$members[] = array(
'location' => $chunks[6],
'size' => $chunks[4],
'mtime' => $date,
'is_dir' => 'd' === $chunks[1][0]
);
}
return $members;
}
/**
* @inheritdoc
*/
public function parseInflatorVersion($output)
{
$chunks = explode(' ', $output, 3);
if (2 > count($chunks)) {
return null;
}
list(, $version) = $chunks;
return $version;
}
/**
* @inheritdoc
*/
public function parseDeflatorVersion($output)
{
return $this->parseInflatorVersion($output);
}
}

View file

@ -0,0 +1,56 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Parser;
use Alchemy\Zippy\Exception\InvalidArgumentException;
class ParserFactory
{
private static $zipDateFormat = 'Y-m-d H:i';
/**
* @param string $format Date format used to parse ZIP file listings
*/
public static function setZipDateFormat($format)
{
self::$zipDateFormat = $format;
}
/**
* Maps the corresponding parser to the selected adapter
*
* @param string $adapterName An adapter name
*
* @return ParserInterface
*
* @throws InvalidArgumentException In case no parser were found
*/
public static function create($adapterName)
{
switch ($adapterName) {
case 'gnu-tar':
return new GNUTarOutputParser();
break;
case 'bsd-tar':
return new BSDTarOutputParser();
break;
case 'zip':
return new ZipOutputParser(self::$zipDateFormat);
break;
default:
throw new InvalidArgumentException(sprintf('No parser available for %s adapter', $adapterName));
break;
}
}
}

View file

@ -0,0 +1,46 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Parser;
use Alchemy\Zippy\Exception\RuntimeException;
interface ParserInterface
{
/**
* Parses a file listing
*
* @param string $output The string to parse
*
* @return array An array of Member properties (location, mtime, size & is_dir)
*
* @throws RuntimeException In case the parsing process failed
*/
public function parseFileListing($output);
/**
* Parses the inflator binary version
*
* @param string $output
*
* @return string The version
*/
public function parseInflatorVersion($output);
/**
* Parses the deflator binary version
*
* @param string $output
*
* @return string The version
*/
public function parseDeflatorVersion($output);
}

View file

@ -0,0 +1,109 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Parser;
/**
* This class is responsible of parsing GNUTar command line output
*/
class ZipOutputParser implements ParserInterface
{
const LENGTH = '(\d*)';
const ISO_DATE = '([0-9]+-[0-9]+-[0-9]+\s+[0-9]+:[0-9]+)';
const FILENAME = '(.*)';
/**
* @var string
*/
private $dateFormat;
/**
* @param string $dateFormat
*/
public function __construct($dateFormat = "Y-m-d H:i")
{
$this->dateFormat = $dateFormat;
}
/**
* @inheritdoc
*/
public function parseFileListing($output)
{
$lines = array_values(array_filter(explode("\n", $output)));
$members = array();
foreach ($lines as $line) {
$matches = array();
// 785 2012-10-24 10:39 file
if (!preg_match_all("#" .
self::LENGTH . "\s+" . // match (785)
self::ISO_DATE . "\s+" . // match (2012-10-24 10:39)
self::FILENAME . // match (file)
"#",
$line, $matches, PREG_SET_ORDER
)) {
continue;
}
$chunks = array_shift($matches);
if (4 !== count($chunks)) {
continue;
}
$members[] = array(
'location' => $chunks[3],
'size' => $chunks[1],
'mtime' => \DateTime::createFromFormat($this->dateFormat, $chunks[2]),
'is_dir' => '/' === substr($chunks[3], -1)
);
}
return $members;
}
/**
* @inheritdoc
*/
public function parseInflatorVersion($output)
{
$lines = array_values(array_filter(explode("\n", $output, 3)));
$chunks = explode(' ', $lines[1], 3);
if (2 > count($chunks)) {
return null;
}
list(, $version) = $chunks;
return $version;
}
/**
* @inheritdoc
*/
public function parseDeflatorVersion($output)
{
$lines = array_values(array_filter(explode("\n", $output, 2)));
$firstLine = array_shift($lines);
$chunks = explode(' ', $firstLine, 3);
if (2 > count($chunks)) {
return null;
}
list(, $version) = $chunks;
return $version;
}
}

View file

@ -0,0 +1,71 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\ProcessBuilder;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Symfony\Component\Process\ProcessBuilder;
class ProcessBuilderFactory implements ProcessBuilderFactoryInterface
{
/**
* The binary path
*
* @var string
*/
protected $binary;
/**
* Constructor
*
* @param string $binary The path to the binary
*
* @throws InvalidArgumentException In case binary path is invalid
*/
public function __construct($binary)
{
$this->useBinary($binary);
}
/**
* @inheritdoc
*/
public function getBinary()
{
return $this->binary;
}
/**
* @inheritdoc
*/
public function useBinary($binary)
{
if (!is_executable($binary)) {
throw new InvalidArgumentException(sprintf('`%s` is not an executable binary', $binary));
}
$this->binary = $binary;
return $this;
}
/**
* @inheritdoc
*/
public function create()
{
if (null === $this->binary) {
throw new InvalidArgumentException('No binary set');
}
return ProcessBuilder::create(array($this->binary))->setTimeout(null);
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\ProcessBuilder;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Symfony\Component\Process\ProcessBuilder;
interface ProcessBuilderFactoryInterface
{
/**
* Returns a new instance of Symfony ProcessBuilder
*
* @return ProcessBuilder
*
* @throws InvalidArgumentException
*/
public function create();
/**
* Returns the binary path
*
* @return string
*/
public function getBinary();
/**
* Sets the binary path
*
* @param string $binary A binary path
*
* @return ProcessBuilderFactoryInterface
*
* @throws InvalidArgumentException In case binary is not executable
*/
public function useBinary($binary);
}

View file

@ -0,0 +1,20 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
abstract class PathUtil
{
public static function basename($path)
{
return (false === $pos = strrpos(strtr($path, '\\', '/'), '/')) ? $path : substr($path, $pos + 1);
}
}

View file

@ -0,0 +1,59 @@
<?php
namespace Alchemy\Zippy\Resource\Reader\Guzzle;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceReader;
use GuzzleHttp\ClientInterface;
class GuzzleReader implements ResourceReader
{
/**
* @var ClientInterface
*/
private $client;
/**
* @var \Alchemy\Zippy\Resource\Resource
*/
private $resource;
/**
* @param ZippyResource $resource
* @param ClientInterface $client
*/
public function __construct(ZippyResource $resource, ClientInterface $client = null)
{
$this->resource = $resource;
$this->client = $client;
}
/**
* @return string
*/
public function getContents()
{
return $this->buildRequest()->getBody()->getContents();
}
/**
* @return resource
*/
public function getContentsAsStream()
{
$response = $this->buildRequest()->getBody()->getContents();
$stream = fopen('php://temp', 'r+');
if ($response != '') {
fwrite($stream, $response);
fseek($stream, 0);
}
return $stream;
}
private function buildRequest()
{
return $this->client->request('GET', $this->resource->getOriginal());
}
}

View file

@ -0,0 +1,37 @@
<?php
namespace Alchemy\Zippy\Resource\Reader\Guzzle;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceReader;
use Alchemy\Zippy\Resource\ResourceReaderFactory;
use GuzzleHttp\Client;
use GuzzleHttp\ClientInterface;
class GuzzleReaderFactory implements ResourceReaderFactory
{
/**
* @var ClientInterface|null
*/
private $client = null;
public function __construct(ClientInterface $client = null)
{
$this->client = $client;
if (! $this->client) {
$this->client = new Client();
}
}
/**
* @param ZippyResource $resource
* @param string $context
*
* @return ResourceReader
*/
public function getReader(ZippyResource $resource, $context)
{
return new GuzzleReader($resource, $this->client);
}
}

View file

@ -0,0 +1,67 @@
<?php
namespace Alchemy\Zippy\Resource\Reader\Guzzle;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceReader;
use Guzzle\Http\Client;
use Guzzle\Http\ClientInterface;
use Guzzle\Http\EntityBodyInterface;
class LegacyGuzzleReader implements ResourceReader
{
/**
* @var ClientInterface
*/
private $client;
/**
* @var \Alchemy\Zippy\Resource\Resource $resource
*/
private $resource;
/**
* This is necessary to prevent the underlying PHP stream from being destroyed
* @link https://github.com/guzzle/guzzle/issues/366#issuecomment-20295409
* @var EntityBodyInterface|null
*/
private $stream = null;
/**
* @param ZippyResource $resource
* @param ClientInterface $client
*/
public function __construct(ZippyResource $resource, ClientInterface $client = null)
{
$this->client = $client ?: new Client();
$this->resource = $resource;
}
/**
* @return string
*/
public function getContents()
{
return $this->buildRequest()->send()->getBody(true);
}
/**
* @return resource
*/
public function getContentsAsStream()
{
if (!$this->stream) {
$this->stream = $this->buildRequest()->send()->getBody(false);
}
return $this->stream->getStream();
}
/**
* @return \Guzzle\Http\Message\RequestInterface
*/
private function buildRequest()
{
return $this->client->get($this->resource->getOriginal());
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Alchemy\Zippy\Resource\Reader\Guzzle;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceReader;
use Alchemy\Zippy\Resource\ResourceReaderFactory;
use Guzzle\Http\Client;
use Guzzle\Http\ClientInterface;
use Guzzle\Plugin\Backoff\BackoffPlugin;
use Symfony\Component\EventDispatcher\Event;
class LegacyGuzzleReaderFactory implements ResourceReaderFactory
{
/**
* @var ClientInterface|null
*/
private $client = null;
public function __construct(ClientInterface $client = null)
{
$this->client = $client;
if (!$this->client) {
$this->client = new Client();
$this->client->getEventDispatcher()->addListener('request.error', function(Event $event) {
// override guzzle default behavior of throwing exceptions
// when 4xx & 5xx responses are encountered
$event->stopPropagation();
}, -254);
$this->client->addSubscriber(BackoffPlugin::getExponentialBackoff(5, array(500, 502, 503, 408)));
}
}
/**
* @param ZippyResource $resource
* @param string $context
*
* @return ResourceReader
*/
public function getReader(ZippyResource $resource, $context)
{
return new LegacyGuzzleReader($resource, $this->client);
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Alchemy\Zippy\Resource\Reader\Stream;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceReader;
class StreamReader implements ResourceReader
{
/**
* @var ZippyResource
*/
private $resource;
/**
* @param ZippyResource $resource
*/
public function __construct(ZippyResource $resource)
{
$this->resource = $resource;
}
/**
* @return string
*/
public function getContents()
{
return file_get_contents($this->resource->getOriginal());
}
/**
* @return resource
*/
public function getContentsAsStream()
{
$stream = is_resource($this->resource->getOriginal()) ?
$this->resource->getOriginal() : @fopen($this->resource->getOriginal(), 'rb');
return $stream;
}
}

View file

@ -0,0 +1,21 @@
<?php
namespace Alchemy\Zippy\Resource\Reader\Stream;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceReader;
use Alchemy\Zippy\Resource\ResourceReaderFactory;
class StreamReaderFactory implements ResourceReaderFactory
{
/**
* @param ZippyResource $resource
* @param string $context
*
* @return ResourceReader
*/
public function getReader(ZippyResource $resource, $context)
{
return new StreamReader($resource);
}
}

View file

@ -0,0 +1,66 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
class RequestMapper
{
private $locator;
/**
* Constructor
*
* @param TargetLocator $locator
*/
public function __construct(TargetLocator $locator)
{
$this->locator = $locator;
}
/**
* Maps resources request to a ResourceCollection
*
* @param $context
* @param array $resources
*
* @return ResourceCollection
*/
public function map($context, array $resources)
{
$data = array();
foreach ($resources as $location => $resource) {
if (is_int($location)) {
$data[] = new Resource($resource, $this->locator->locate($context, $resource));
} else {
$data[] = new Resource($resource, ltrim($location, '/'));
}
}
if (count($data) === 1) {
$context = $data[0]->getOriginal();
}
$collection = new ResourceCollection($context, $data, false);
return $collection;
}
/**
* Creates the default RequestMapper
*
* @return RequestMapper
*/
public static function create()
{
return new static(new TargetLocator());
}
}

View file

@ -0,0 +1,116 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
class Resource
{
private $original;
private $target;
/**
* Constructor
*
* @param string $original
* @param string $target
*/
public function __construct($original, $target)
{
$this->original = $original;
$this->target = $target;
}
/**
* Returns the target
*
* @return string
*/
public function getTarget()
{
return $this->target;
}
/**
* Returns the original path
*
* @return string
*/
public function getOriginal()
{
return $this->original;
}
/**
* Returns whether the resource can be processed in place given a context or not.
*
* For example :
* - /path/to/file1 can be processed to file1 in /path/to context
* - /path/to/subdir/file2 can be processed to subdir/file2 in /path/to context
*
* @param string $context
*
* @return bool
*/
public function canBeProcessedInPlace($context)
{
if (!is_string($this->original)) {
return false;
}
if (!$this->isLocal()) {
return false;
}
$data = parse_url($this->original);
return sprintf('%s/%s', rtrim($context, '/'), $this->target) === $data['path'];
}
/**
* Returns a context for computing this resource in case it is possible.
*
* Useful to avoid teleporting.
*
* @return null|string
*/
public function getContextForProcessInSinglePlace()
{
if (!is_string($this->original)) {
return null;
}
if (!$this->isLocal()) {
return null;
}
if (PathUtil::basename($this->original) === $this->target) {
return dirname($this->original);
}
return null;
}
/**
* Returns true if the resource is local.
*
* @return bool
*/
private function isLocal()
{
if (!is_string($this->original)) {
return false;
}
$data = parse_url($this->original);
return isset($data['path']);
}
}

View file

@ -0,0 +1,89 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Doctrine\Common\Collections\ArrayCollection;
class ResourceCollection extends ArrayCollection
{
private $context;
/**
* @var bool
*/
private $temporary;
/**
* Constructor
* @param string $context
* @param Resource[] $elements An array of Resource
* @param bool $temporary
*/
public function __construct($context, array $elements, $temporary)
{
array_walk($elements, function($element) {
if (!$element instanceof Resource) {
throw new InvalidArgumentException('ResourceCollection only accept Resource elements');
}
});
$this->context = $context;
$this->temporary = (bool) $temporary;
parent::__construct($elements);
}
/**
* Returns the context related to the collection
*
* @return string
*/
public function getContext()
{
return $this->context;
}
/**
* Tells whether the collection is temporary or not.
*
* A ResourceCollection is temporary when it required a temporary folder to
* fetch data
*
* @return bool
*/
public function isTemporary()
{
return $this->temporary;
}
/**
* Returns true if all resources can be processed in place, false otherwise
*
* @return bool
*/
public function canBeProcessedInPlace()
{
if (count($this) === 1) {
if (null !== $context = $this->first()->getContextForProcessInSinglePlace()) {
$this->context = $context;
return true;
}
}
foreach ($this as $resource) {
if (!$resource->canBeProcessedInPlace($this->context)) {
return false;
}
}
return true;
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Alchemy\Zippy\Resource;
use Alchemy\Zippy\Resource\Resource AS ZippyResource;
class ResourceLocator
{
public function mapResourcePath(ZippyResource $resource, $context)
{
return rtrim($context, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $resource->getTarget();
}
}

View file

@ -0,0 +1,105 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
use Alchemy\Zippy\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOException as SfIOException;
class ResourceManager
{
private $mapper;
private $teleporter;
private $filesystem;
/**
* Constructor
*
* @param RequestMapper $mapper
* @param ResourceTeleporter $teleporter
* @param Filesystem $filesystem
*/
public function __construct(RequestMapper $mapper, ResourceTeleporter $teleporter, Filesystem $filesystem)
{
$this->mapper = $mapper;
$this->filesystem = $filesystem;
$this->teleporter = $teleporter;
}
/**
* Handles an archival request.
*
* The request is an array of string|streams to compute in a context (current
* working directory most of the time)
* Some keys can be associative. In these cases, the key is used the target
* for the file.
*
* @param string $context
* @param array $request
*
* @return ResourceCollection
*
* @throws IOException In case of write failure
*/
public function handle($context, array $request)
{
$collection = $this->mapper->map($context, $request);
if (!$collection->canBeProcessedInPlace()) {
$context = sprintf('%s/%s', sys_get_temp_dir(), uniqid('zippy_'));
try {
$this->filesystem->mkdir($context);
} catch (SfIOException $e) {
throw new IOException(sprintf('Could not create temporary folder %s', $context), $e->getCode(), $e);
}
foreach ($collection as $resource) {
$this->teleporter->teleport($context, $resource);
}
$collection = new ResourceCollection($context, $collection->toArray(), true);
}
return $collection;
}
/**
* This method must be called once the ResourceCollection has been processed.
*
* It will remove temporary files
*
* @todo this should be done in the __destruct method of ResourceCollection
*
* @param ResourceCollection $collection
*/
public function cleanup(ResourceCollection $collection)
{
if ($collection->isTemporary()) {
try {
$this->filesystem->remove($collection->getContext());
} catch (IOException $e) {
// log this ?
}
}
}
/**
* Creates a default ResourceManager
*
* @return ResourceManager
*/
public static function create()
{
return new static(RequestMapper::create(), ResourceTeleporter::create(), new Filesystem());
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Alchemy\Zippy\Resource;
interface ResourceReader
{
/**
* @return string
*/
public function getContents();
/**
* @return resource
*/
public function getContentsAsStream();
}

View file

@ -0,0 +1,16 @@
<?php
namespace Alchemy\Zippy\Resource;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
interface ResourceReaderFactory
{
/**
* @param ZippyResource $resource
* @param string $context
*
* @return ResourceReader
*/
public function getReader(ZippyResource $resource, $context);
}

View file

@ -0,0 +1,55 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
class ResourceTeleporter
{
private $container;
/**
* Constructor
*
* @param TeleporterContainer $container
*/
public function __construct(TeleporterContainer $container)
{
$this->container = $container;
}
/**
* Teleports a resource to its target in the context
*
* @param string $context
* @param ZippyResource $resource
*
* @return ResourceTeleporter
*/
public function teleport($context, ZippyResource $resource)
{
$this
->container
->fromResource($resource)
->teleport($resource, $context);
return $this;
}
/**
* Creates the ResourceTeleporter with the default TeleporterContainer
*
* @return ResourceTeleporter
*/
public static function create()
{
return new static(TeleporterContainer::load());
}
}

View file

@ -0,0 +1,8 @@
<?php
namespace Alchemy\Zippy\Resource;
interface ResourceWriter
{
public function writeFromReader(ResourceReader $reader, $target);
}

View file

@ -0,0 +1,158 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
use Alchemy\Zippy\Exception\TargetLocatorException;
class TargetLocator
{
/**
* Locates the target for a resource in a context
*
* For example, adding /path/to/file where the context (current working
* directory) is /path/to will return `file` as target
*
* @param string $context
* @param string|resource $resource
*
* @return string
*
* @throws TargetLocatorException when the resource is invalid
*/
public function locate($context, $resource)
{
switch (true) {
case is_resource($resource):
return $this->locateResource($resource);
case is_string($resource):
return $this->locateString($context, $resource);
case $resource instanceof \SplFileInfo:
return $this->locateString($context, $resource->getRealPath());
default:
throw new TargetLocatorException($resource, 'Unknown resource format');
}
}
/**
* Locate the target for a resource.
*
* @param resource $resource
*
* @return string
*
* @throws TargetLocatorException
*/
private function locateResource($resource)
{
$meta = stream_get_meta_data($resource);
$data = parse_url($meta['uri']);
if (!isset($data['path'])) {
throw new TargetLocatorException($resource, 'Unable to retrieve path from resource');
}
return PathUtil::basename($data['path']);
}
/**
* Locate the target for a string.
*
* @param $context
* @param string $resource
*
* @return string
*
* @throws TargetLocatorException
*/
private function locateString($context, $resource)
{
$url = parse_url($resource);
if (isset($url['scheme']) && $this->isLocalFilesystem($url['scheme'])) {
$resource = $url['path'] = $this->cleanupPath($url['path']);
}
// resource is a URI
if (isset($url['scheme'])) {
if ($this->isLocalFilesystem($url['scheme']) && $this->isFileInContext($url['path'], $context)) {
return $this->getRelativePathFromContext($url['path'], $context);
}
return PathUtil::basename($resource);
}
// resource is a local path
if ($this->isFileInContext($resource, $context)) {
$resource = $this->cleanupPath($resource);
return $this->getRelativePathFromContext($resource, $context);
} else {
return PathUtil::basename($resource);
}
}
/**
* Removes backward path sequences (..)
*
* @param string $path
*
* @return string
*
* @throws TargetLocatorException In case the path is invalid
*/
private function cleanupPath($path)
{
if (false === $cleanPath = realpath($path)) {
throw new TargetLocatorException($path, sprintf('%s is an invalid location', $path));
}
return $cleanPath;
}
/**
* Checks whether the path belong to the context
*
* @param string $path A resource path
* @param string $context
*
* @return bool
*/
private function isFileInContext($path, $context)
{
return 0 === strpos($path, $context);
}
/**
* Gets the relative path from the context for the given path
*
* @param string $path A resource path
* @param string $context
*
* @return string
*/
private function getRelativePathFromContext($path, $context)
{
return ltrim(str_replace($context, '', $path), '/\\');
}
/**
* Checks if a scheme refers to a local filesystem
*
* @param string $scheme
*
* @return bool
*/
private function isLocalFilesystem($scheme)
{
return 'plainfile' === $scheme || 'file' === $scheme;
}
}

View file

@ -0,0 +1,64 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource\Teleporter;
use Alchemy\Zippy\Exception\IOException;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
/**
* Class AbstractTeleporter
* @package Alchemy\Zippy\Resource\Teleporter
*
* @deprecated Typehint against TeleporterInterface instead and use GenericTeleporter
* with custom reader/writers instead. This class will be removed in v0.5.x
*/
abstract class AbstractTeleporter implements TeleporterInterface
{
/**
* Writes the target
*
* @param string $data
* @param ZippyResource $resource
* @param string $context
*
* @return TeleporterInterface
*
* @throws IOException
*/
protected function writeTarget($data, ZippyResource $resource, $context)
{
$target = $this->getTarget($context, $resource);
if (!file_exists(dirname($target)) && false === mkdir(dirname($target))) {
throw new IOException(sprintf('Could not create parent directory %s', dirname($target)));
}
if (false === file_put_contents($target, $data)) {
throw new IOException(sprintf('Could not write to %s', $target));
}
return $this;
}
/**
* Returns the relative target of a Resource
*
* @param string $context
* @param ZippyResource $resource
*
* @return string
*/
protected function getTarget($context, ZippyResource $resource)
{
return sprintf('%s/%s', rtrim($context, '/'), $resource->getTarget());
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Alchemy\Zippy\Resource\Teleporter;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Exception\IOException;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceLocator;
use Alchemy\Zippy\Resource\ResourceReaderFactory;
use Alchemy\Zippy\Resource\ResourceWriter;
class GenericTeleporter implements TeleporterInterface
{
/**
* @var ResourceReaderFactory
*/
private $readerFactory;
/**
* @var ResourceWriter
*/
private $resourceWriter;
/**
* @var ResourceLocator
*/
private $resourceLocator;
/**
* @param ResourceReaderFactory $readerFactory
* @param ResourceWriter $resourceWriter
* @param ResourceLocator $resourceLocator
*/
public function __construct(
ResourceReaderFactory $readerFactory,
ResourceWriter $resourceWriter,
ResourceLocator $resourceLocator = null
) {
$this->readerFactory = $readerFactory;
$this->resourceWriter = $resourceWriter;
$this->resourceLocator = $resourceLocator ?: new ResourceLocator();
}
/**
* Teleports a file from a destination to an other
*
* @param ZippyResource $resource A Resource
* @param string $context The current context
*
* @throws IOException when file could not be written on local
* @throws InvalidArgumentException when path to file is not valid
*/
public function teleport(ZippyResource $resource, $context)
{
$reader = $this->readerFactory->getReader($resource, $context);
$target = $this->resourceLocator->mapResourcePath($resource, $context);
$this->resourceWriter->writeFromReader($reader, $target);
}
}

View file

@ -0,0 +1,45 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource\Teleporter;
use Alchemy\Zippy\Resource\Reader\Guzzle\GuzzleReaderFactory;
use Alchemy\Zippy\Resource\ResourceLocator;
use Alchemy\Zippy\Resource\ResourceReaderFactory;
use Alchemy\Zippy\Resource\Writer\FilesystemWriter;
/**
* Guzzle Teleporter implementation for HTTP resources
*
* @deprecated Use \Alchemy\Zippy\Resource\GenericTeleporter instead. This class will be removed in v0.5.x
*/
class GuzzleTeleporter extends GenericTeleporter
{
/**
* @param ResourceReaderFactory $readerFactory
* @param ResourceLocator $resourceLocator
*/
public function __construct(ResourceReaderFactory $readerFactory = null, ResourceLocator $resourceLocator = null)
{
parent::__construct($readerFactory ?: new GuzzleReaderFactory(), new FilesystemWriter(), $resourceLocator);
}
/**
* Creates the GuzzleTeleporter
*
* @deprecated This method will be removed in v0.5.x
* @return GuzzleTeleporter
*/
public static function create()
{
return new static();
}
}

View file

@ -0,0 +1,51 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource\Teleporter;
use Alchemy\Zippy\Resource\Reader\Guzzle\LegacyGuzzleReaderFactory;
use Alchemy\Zippy\Resource\ResourceLocator;
use Alchemy\Zippy\Resource\ResourceReaderFactory;
use Alchemy\Zippy\Resource\Writer\FilesystemWriter;
use Guzzle\Http\Client;
/**
* Guzzle Teleporter implementation for HTTP resources
*
* @deprecated Use \Alchemy\Zippy\Resource\GenericTeleporter instead. This class will be removed in v0.5.x
*/
class LegacyGuzzleTeleporter extends GenericTeleporter
{
/**
* @param Client $client
* @param ResourceReaderFactory $readerFactory
* @param ResourceLocator $resourceLocator
*/
public function __construct(
Client $client = null,
ResourceReaderFactory $readerFactory = null,
ResourceLocator $resourceLocator = null
) {
parent::__construct($readerFactory ?: new LegacyGuzzleReaderFactory($client), new FilesystemWriter(),
$resourceLocator);
}
/**
* Creates the GuzzleTeleporter
*
* @deprecated
* @return LegacyGuzzleTeleporter
*/
public static function create()
{
return new static();
}
}

View file

@ -0,0 +1,82 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource\Teleporter;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Exception\IOException;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\ResourceLocator;
use Symfony\Component\Filesystem\Exception\IOException as SfIOException;
use Symfony\Component\Filesystem\Filesystem;
/**
* This class transports an object using the local filesystem
*/
class LocalTeleporter extends AbstractTeleporter
{
/**
* @var Filesystem
*/
private $filesystem;
/**
* @var ResourceLocator
*/
private $resourceLocator;
/**
* Constructor
*
* @param Filesystem $filesystem
*/
public function __construct(Filesystem $filesystem)
{
$this->filesystem = $filesystem;
$this->resourceLocator = new ResourceLocator();
}
/**
* {@inheritdoc}
*/
public function teleport(ZippyResource $resource, $context)
{
$target = $this->resourceLocator->mapResourcePath($resource, $context);
$path = $resource->getOriginal();
if (!file_exists($path)) {
throw new InvalidArgumentException(sprintf('Invalid path %s', $path));
}
try {
if (is_file($path)) {
$this->filesystem->copy($path, $target);
} elseif (is_dir($path)) {
$this->filesystem->mirror($path, $target);
} else {
throw new InvalidArgumentException(sprintf('Invalid file or directory %s', $path));
}
} catch (SfIOException $e) {
throw new IOException(sprintf('Could not write %s', $target), $e->getCode(), $e);
}
}
/**
* Creates the LocalTeleporter
*
* @return LocalTeleporter
* @deprecated This method will be removed in a future release (0.5.x)
*/
public static function create()
{
return new static(new Filesystem());
}
}

View file

@ -0,0 +1,38 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource\Teleporter;
use Alchemy\Zippy\Resource\Reader\Stream\StreamReaderFactory;
use Alchemy\Zippy\Resource\ResourceLocator;
use Alchemy\Zippy\Resource\Writer\StreamWriter;
/**
* This class transport an object using php stream wrapper
*/
class StreamTeleporter extends GenericTeleporter
{
public function __construct()
{
parent::__construct(new StreamReaderFactory(), new StreamWriter(), new ResourceLocator());
}
/**
* Creates the StreamTeleporter
*
* @return StreamTeleporter
* @deprecated This method will be removed in a future release (0.5.x)
*/
public static function create()
{
return new static();
}
}

View file

@ -0,0 +1,30 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource\Teleporter;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Exception\IOException;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
interface TeleporterInterface
{
/**
* Teleports a file from a destination to an other
*
* @param ZippyResource $resource A Resource
* @param string $context The current context
*
* @throws IOException when file could not be written on local
* @throws InvalidArgumentException when path to file is not valid
*/
public function teleport(ZippyResource $resource, $context);
}

View file

@ -0,0 +1,189 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy\Resource;
use Alchemy\Zippy\Exception\InvalidArgumentException;
use Alchemy\Zippy\Resource\Reader\Guzzle\GuzzleReaderFactory;
use Alchemy\Zippy\Resource\Reader\Guzzle\LegacyGuzzleReaderFactory;
use Alchemy\Zippy\Resource\Resource as ZippyResource;
use Alchemy\Zippy\Resource\Teleporter\GenericTeleporter;
use Alchemy\Zippy\Resource\Teleporter\LocalTeleporter;
use Alchemy\Zippy\Resource\Teleporter\StreamTeleporter;
use Alchemy\Zippy\Resource\Teleporter\TeleporterInterface;
use Alchemy\Zippy\Resource\Writer\FilesystemWriter;
use Symfony\Component\Filesystem\Filesystem;
/**
* A container of TeleporterInterface
*/
class TeleporterContainer implements \ArrayAccess, \Countable
{
/**
* @var TeleporterInterface[]
*/
private $teleporters = array();
/**
* @var callable[]
*/
private $factories = array();
/**
* Returns the appropriate TeleporterInterface for a given Resource
*
* @param ZippyResource $resource
*
* @return TeleporterInterface
*/
public function fromResource(ZippyResource $resource)
{
switch (true) {
case is_resource($resource->getOriginal()):
$teleporter = 'stream-teleporter';
break;
case is_string($resource->getOriginal()):
$data = parse_url($resource->getOriginal());
if (!isset($data['scheme']) || 'file' === $data['scheme']) {
$teleporter = 'local-teleporter';
} elseif (in_array($data['scheme'], array('http', 'https')) && isset($this->factories['guzzle-teleporter'])) {
$teleporter = 'guzzle-teleporter';
} else {
$teleporter = 'stream-teleporter';
}
break;
default:
throw new InvalidArgumentException('No teleporter found');
}
return $this->getTeleporter($teleporter);
}
private function getTeleporter($typeName)
{
if (!isset($this->teleporters[$typeName])) {
$factory = $this->factories[$typeName];
$this->teleporters[$typeName] = $factory();
}
return $this->teleporters[$typeName];
}
/**
* Instantiates TeleporterContainer and register default teleporters
*
* @return TeleporterContainer
*/
public static function load()
{
$container = new static();
$container->factories['stream-teleporter'] = function () {
return new StreamTeleporter();
};
$container->factories['local-teleporter'] = function () {
return new LocalTeleporter(new Filesystem());
};
if (class_exists('GuzzleHttp\Client')) {
$container->factories['guzzle-teleporter'] = function () {
return new GenericTeleporter(
new GuzzleReaderFactory(),
new FilesystemWriter(),
new ResourceLocator()
);
};
}
elseif (class_exists('Guzzle\Http\Client')) {
$container->factories['guzzle-teleporter'] = function () {
return new GenericTeleporter(
new LegacyGuzzleReaderFactory(),
new FilesystemWriter(),
new ResourceLocator()
);
};
}
return $container;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Whether a offset exists
*
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @return bool true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
*/
public function offsetExists($offset)
{
return isset($this->teleporters[$offset]);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to retrieve
* @link http://php.net/manual/en/arrayaccess.offsetget.php
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
* @return mixed Can return all value types.
*/
public function offsetGet($offset)
{
return $this->getTeleporter($offset);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to set
* @link http://php.net/manual/en/arrayaccess.offsetset.php
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
* @return void
*/
public function offsetSet($offset, $value)
{
throw new \BadMethodCallException();
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Offset to unset
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
* @param mixed $offset <p>
* The offset to unset.
* </p>
* @return void
*/
public function offsetUnset($offset)
{
throw new \BadMethodCallException();
}
public function count()
{
return count($this->teleporters);
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace Alchemy\Zippy\Resource\Writer;
use Alchemy\Zippy\Resource\ResourceReader;
use Alchemy\Zippy\Resource\ResourceWriter;
class FilesystemWriter implements ResourceWriter
{
/**
* @param ResourceReader $reader
* @param string $target
*/
public function writeFromReader(ResourceReader $reader, $target)
{
$directory = dirname($target);
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
file_put_contents($target, $reader->getContentsAsStream());
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Alchemy\Zippy\Resource\Writer;
use Alchemy\Zippy\Resource\ResourceReader;
use Alchemy\Zippy\Resource\ResourceWriter;
class StreamWriter implements ResourceWriter
{
/**
* @param ResourceReader $reader
* @param string $target
*/
public function writeFromReader(ResourceReader $reader, $target)
{
$directory = dirname($target);
if (!is_dir($directory)) {
mkdir($directory, 0777, true);
}
$targetResource = fopen($target, 'w+');
$sourceResource = $reader->getContentsAsStream();
stream_copy_to_stream($sourceResource, $targetResource);
fclose($targetResource);
}
}

220
vendor/alchemy/zippy/src/Zippy.php vendored Normal file
View file

@ -0,0 +1,220 @@
<?php
/*
* This file is part of Zippy.
*
* (c) Alchemy <info@alchemy.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Alchemy\Zippy;
use Alchemy\Zippy\Adapter\AdapterContainer;
use Alchemy\Zippy\Adapter\AdapterInterface;
use Alchemy\Zippy\Archive\ArchiveInterface;
use Alchemy\Zippy\Exception\ExceptionInterface;
use Alchemy\Zippy\Exception\FormatNotSupportedException;
use Alchemy\Zippy\Exception\NoAdapterOnPlatformException;
use Alchemy\Zippy\Exception\RuntimeException;
use Alchemy\Zippy\FileStrategy\FileStrategyInterface;
use Alchemy\Zippy\FileStrategy\TarBz2FileStrategy;
use Alchemy\Zippy\FileStrategy\TarFileStrategy;
use Alchemy\Zippy\FileStrategy\TarGzFileStrategy;
use Alchemy\Zippy\FileStrategy\TB2FileStrategy;
use Alchemy\Zippy\FileStrategy\TBz2FileStrategy;
use Alchemy\Zippy\FileStrategy\TGzFileStrategy;
use Alchemy\Zippy\FileStrategy\ZipFileStrategy;
class Zippy
{
/**
* @var AdapterContainer
*/
public $adapters;
/**
* @var FileStrategyInterface[][]
*/
private $strategies = array();
public function __construct(AdapterContainer $adapters)
{
$this->adapters = $adapters;
}
/**
* Creates an archive
*
* @param string $path
* @param string|array|\Traversable|null $files
* @param bool $recursive
* @param string|null $type
*
* @return ArchiveInterface
*
* @throws RuntimeException In case of failure
*/
public function create($path, $files = null, $recursive = true, $type = null)
{
if (null === $type) {
$type = $this->guessAdapterExtension($path);
}
try {
return $this
->getAdapterFor($this->sanitizeExtension($type))
->create($path, $files, $recursive);
} catch (ExceptionInterface $e) {
throw new RuntimeException('Unable to create archive', $e->getCode(), $e);
}
}
/**
* Opens an archive.
*
* @param string $path
*
* @return ArchiveInterface
*
* @throws RuntimeException In case of failure
*/
public function open($path)
{
$type = $this->guessAdapterExtension($path);
try {
return $this
->getAdapterFor($this->sanitizeExtension($type))
->open($path);
} catch (ExceptionInterface $e) {
throw new RuntimeException('Unable to open archive', $e->getCode(), $e);
}
}
/**
* Adds a strategy.
*
* The last strategy added is preferred over the other ones.
* You can add a strategy twice ; when doing this, the first one is removed
* when inserting the second one.
*
* @param FileStrategyInterface $strategy
*
* @return Zippy
*/
public function addStrategy(FileStrategyInterface $strategy)
{
$extension = $this->sanitizeExtension($strategy->getFileExtension());
if (!isset($this->strategies[$extension])) {
$this->strategies[$extension] = array();
}
if (false !== $key = array_search($strategy, $this->strategies[$extension], true)) {
unset($this->strategies[$extension][$key]);
}
array_unshift($this->strategies[$extension], $strategy);
return $this;
}
/**
* Returns the strategies as they are stored
*
* @return array
*/
public function getStrategies()
{
return $this->strategies;
}
/**
* Returns an adapter for a file extension
*
* @param string $extension The extension
*
* @return AdapterInterface
*
* @throws FormatNotSupportedException When no strategy is defined for this extension
* @throws NoAdapterOnPlatformException When no adapter is supported for this extension on this platform
*/
public function getAdapterFor($extension)
{
$extension = $this->sanitizeExtension($extension);
if (!$extension || !isset($this->strategies[$extension])) {
throw new FormatNotSupportedException(sprintf('No strategy for %s extension', $extension));
}
foreach ($this->strategies[$extension] as $strategy) {
foreach ($strategy->getAdapters() as $adapter) {
if ($adapter->isSupported()) {
return $adapter;
}
}
}
throw new NoAdapterOnPlatformException(sprintf('No adapter available for %s on this platform', $extension));
}
/**
* Creates Zippy and loads default strategies
*
* @return Zippy
*/
public static function load()
{
$adapters = AdapterContainer::load();
$factory = new static($adapters);
$factory->addStrategy(new ZipFileStrategy($adapters));
$factory->addStrategy(new TarFileStrategy($adapters));
$factory->addStrategy(new TarGzFileStrategy($adapters));
$factory->addStrategy(new TarBz2FileStrategy($adapters));
$factory->addStrategy(new TB2FileStrategy($adapters));
$factory->addStrategy(new TBz2FileStrategy($adapters));
$factory->addStrategy(new TGzFileStrategy($adapters));
return $factory;
}
/**
* Sanitize an extension.
*
* Strips dot from the beginning, converts to lowercase and remove trailing
* whitespaces
*
* @param string $extension
*
* @return string
*/
private function sanitizeExtension($extension)
{
return ltrim(trim(mb_strtolower($extension)), '.');
}
/**
* Finds an extension that has strategy registered given a file path
*
* Returns null if no matching strategy found.
*
* @param string $path
*
* @return string|null
*/
private function guessAdapterExtension($path)
{
$path = strtolower(trim($path));
foreach ($this->strategies as $extension => $strategy) {
if ($extension === substr($path, (strlen($extension) * -1))) {
return $extension;
}
}
return null;
}
}