Actualización

This commit is contained in:
Xes
2025-04-10 12:24:57 +02:00
parent 8969cc929d
commit 45420b6f0d
39760 changed files with 4303286 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
<?php
namespace Gaufrette;
/**
* Interface for the filesystem adapters.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface Adapter
{
/**
* Reads the content of the file.
*
* @param string $key
*
* @return string|bool if cannot read content
*/
public function read($key);
/**
* Writes the given content into the file.
*
* @param string $key
* @param string $content
*
* @return int|bool The number of bytes that were written into the file
*/
public function write($key, $content);
/**
* Indicates whether the file exists.
*
* @param string $key
*
* @return bool
*/
public function exists($key);
/**
* Returns an array of all keys (files and directories).
*
* @return array
*/
public function keys();
/**
* Returns the last modified time.
*
* @param string $key
*
* @return int|bool An UNIX like timestamp or false
*/
public function mtime($key);
/**
* Deletes the file.
*
* @param string $key
*
* @return bool
*/
public function delete($key);
/**
* Renames a file.
*
* @param string $sourceKey
* @param string $targetKey
*
* @return bool
*/
public function rename($sourceKey, $targetKey);
/**
* Check if key is directory.
*
* @param string $key
*
* @return bool
*/
public function isDirectory($key);
}

View File

@@ -0,0 +1,342 @@
<?php
namespace Gaufrette\Adapter;
use AsyncAws\Core\Configuration;
use AsyncAws\SimpleS3\SimpleS3Client;
use Gaufrette\Adapter;
use Gaufrette\Util;
/**
* Amazon S3 adapter using the AsyncAws.
*
* @author Michael Dowling <mtdowling@gmail.com>
* @author Tobias Nyholm <tobias.nyholm@gmail.com>
*/
class AsyncAwsS3 implements Adapter, MetadataSupporter, ListKeysAware, SizeCalculator, MimeTypeProvider
{
/** @var SimpleS3Client */
protected $service;
/** @var string */
protected $bucket;
/** @var array */
protected $options;
/** @var bool */
protected $bucketExists;
/** @var array */
protected $metadata = [];
/** @var bool */
protected $detectContentType;
/**
* @param SimpleS3Client $service
* @param string $bucket
* @param array $options
* @param bool $detectContentType
*/
public function __construct(SimpleS3Client $service, $bucket, array $options = [], $detectContentType = false)
{
if (!class_exists(SimpleS3Client::class)) {
throw new \LogicException('You need to install package "async-aws/simple-s3" to use this adapter');
}
$this->service = $service;
$this->bucket = $bucket;
$this->options = array_replace(
[
'create' => false,
'directory' => '',
'acl' => 'private',
],
$options
);
$this->detectContentType = $detectContentType;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $content)
{
// BC with AmazonS3 adapter
if (isset($content['contentType'])) {
$content['ContentType'] = $content['contentType'];
unset($content['contentType']);
}
$this->metadata[$key] = $content;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
return $this->metadata[$key] ?? [];
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureBucketExists();
$options = $this->getOptions($key);
try {
// Get remote object
$object = $this->service->getObject($options);
// If there's no metadata array set up for this object, set it up
if (!array_key_exists($key, $this->metadata) || !is_array($this->metadata[$key])) {
$this->metadata[$key] = [];
}
// Make remote ContentType metadata available locally
$this->metadata[$key]['ContentType'] = $object->getContentType();
return $object->getBody()->getContentAsString();
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureBucketExists();
$options = $this->getOptions(
$targetKey,
['CopySource' => $this->bucket . '/' . $this->computePath($sourceKey)]
);
try {
$this->service->copyObject(array_merge($options, $this->getMetadata($targetKey)));
return $this->delete($sourceKey);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
* @param string|resource $content
*/
public function write($key, $content)
{
$this->ensureBucketExists();
$options = $this->getOptions($key);
unset($options['Bucket'], $options['Key']);
/*
* If the ContentType was not already set in the metadata, then we autodetect
* it to prevent everything being served up as binary/octet-stream.
*/
if (!isset($options['ContentType']) && $this->detectContentType) {
$options['ContentType'] = $this->guessContentType($content);
}
try {
$this->service->upload($this->bucket, $this->computePath($key), $content, $options);
if (is_resource($content)) {
return (int) Util\Size::fromResource($content);
}
return Util\Size::fromContent($content);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return $this->service->has($this->bucket, $this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return $result->getLastModified()->getTimestamp();
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function size($key)
{
$result = $this->service->headObject($this->getOptions($key));
return (int) $result->getContentLength();
}
public function mimeType($key)
{
$result = $this->service->headObject($this->getOptions($key));
return $result->getContentType();
}
/**
* {@inheritdoc}
*/
public function keys()
{
return $this->listKeys();
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureBucketExists();
$options = ['Bucket' => $this->bucket];
if ((string) $prefix != '') {
$options['Prefix'] = $this->computePath($prefix);
} elseif (!empty($this->options['directory'])) {
$options['Prefix'] = $this->options['directory'];
}
$keys = [];
$result = $this->service->listObjectsV2($options);
foreach ($result->getContents() as $file) {
$keys[] = $this->computeKey($file->getKey());
}
return $keys;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
$this->service->deleteObject($this->getOptions($key));
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$result = $this->service->listObjectsV2([
'Bucket' => $this->bucket,
'Prefix' => rtrim($this->computePath($key), '/') . '/',
'MaxKeys' => 1,
]);
foreach ($result->getContents(true) as $file) {
return true;
}
return false;
}
/**
* Ensures the specified bucket exists. If the bucket does not exists
* and the create option is set to true, it will try to create the
* bucket. The bucket is created using the same region as the supplied
* client object.
*
* @throws \RuntimeException if the bucket does not exists or could not be
* created
*/
protected function ensureBucketExists()
{
if ($this->bucketExists) {
return true;
}
if ($this->bucketExists = $this->service->bucketExists(['Bucket' => $this->bucket])->isSuccess()) {
return true;
}
if (!$this->options['create']) {
throw new \RuntimeException(sprintf(
'The configured bucket "%s" does not exist.',
$this->bucket
));
}
$this->service->createBucket([
'Bucket' => $this->bucket,
'CreateBucketConfiguration' => [
'LocationConstraint' => $this->service->getConfiguration()->get(Configuration::OPTION_REGION),
],
]);
$this->bucketExists = true;
return true;
}
protected function getOptions($key, array $options = [])
{
$options['ACL'] = $this->options['acl'];
$options['Bucket'] = $this->bucket;
$options['Key'] = $this->computePath($key);
/*
* Merge global options for adapter, which are set in the constructor, with metadata.
* Metadata will override global options.
*/
$options = array_merge($this->options, $options, $this->getMetadata($key));
return $options;
}
protected function computePath($key)
{
if (empty($this->options['directory'])) {
return $key;
}
return sprintf('%s/%s', $this->options['directory'], $key);
}
/**
* Computes the key from the specified path.
*
* @param string $path
*
* return string
*/
protected function computeKey($path)
{
return ltrim(substr($path, strlen($this->options['directory'])), '/');
}
/**
* @param string|resource $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
}

View File

@@ -0,0 +1,346 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Aws\S3\S3Client;
use Gaufrette\Util;
/**
* Amazon S3 adapter using the AWS SDK for PHP v2.x.
*
* @author Michael Dowling <mtdowling@gmail.com>
*/
class AwsS3 implements Adapter, MetadataSupporter, ListKeysAware, SizeCalculator, MimeTypeProvider
{
/** @var S3Client */
protected $service;
/** @var string */
protected $bucket;
/** @var array */
protected $options;
/** @var bool */
protected $bucketExists;
/** @var array */
protected $metadata = [];
/** @var bool */
protected $detectContentType;
/**
* @param S3Client $service
* @param string $bucket
* @param array $options
* @param bool $detectContentType
*/
public function __construct(S3Client $service, $bucket, array $options = [], $detectContentType = false)
{
if (!class_exists(S3Client::class)) {
throw new \LogicException('You need to install package "aws/aws-sdk-php" to use this adapter');
}
$this->service = $service;
$this->bucket = $bucket;
$this->options = array_replace(
[
'create' => false,
'directory' => '',
'acl' => 'private',
],
$options
);
$this->detectContentType = $detectContentType;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $metadata)
{
// BC with AmazonS3 adapter
if (isset($metadata['contentType'])) {
$metadata['ContentType'] = $metadata['contentType'];
unset($metadata['contentType']);
}
$this->metadata[$key] = $metadata;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
return $this->metadata[$key] ?? [];
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureBucketExists();
$options = $this->getOptions($key);
try {
// Get remote object
$object = $this->service->getObject($options);
// If there's no metadata array set up for this object, set it up
if (!array_key_exists($key, $this->metadata) || !is_array($this->metadata[$key])) {
$this->metadata[$key] = [];
}
// Make remote ContentType metadata available locally
$this->metadata[$key]['ContentType'] = $object->get('ContentType');
return (string) $object->get('Body');
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureBucketExists();
$options = $this->getOptions(
$targetKey,
['CopySource' => $this->bucket . '/' . $this->computePath($sourceKey)]
);
try {
$this->service->copyObject(array_merge($options, $this->getMetadata($targetKey)));
return $this->delete($sourceKey);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->ensureBucketExists();
$options = $this->getOptions($key, ['Body' => $content]);
/*
* If the ContentType was not already set in the metadata, then we autodetect
* it to prevent everything being served up as binary/octet-stream.
*/
if (!isset($options['ContentType']) && $this->detectContentType) {
$options['ContentType'] = $this->guessContentType($content);
}
try {
$this->service->putObject($options);
if (is_resource($content)) {
return Util\Size::fromResource($content);
}
return Util\Size::fromContent($content);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return $this->service->doesObjectExist($this->bucket, $this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return strtotime($result['LastModified']);
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function size($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return $result['ContentLength'];
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function keys()
{
return $this->listKeys();
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureBucketExists();
$options = ['Bucket' => $this->bucket];
if ((string) $prefix != '') {
$options['Prefix'] = $this->computePath($prefix);
} elseif (!empty($this->options['directory'])) {
$options['Prefix'] = $this->options['directory'];
}
$keys = [];
$iter = $this->service->getIterator('ListObjects', $options);
foreach ($iter as $file) {
$keys[] = $this->computeKey($file['Key']);
}
return $keys;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
try {
$this->service->deleteObject($this->getOptions($key));
return true;
} catch (\Exception $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$result = $this->service->listObjects([
'Bucket' => $this->bucket,
'Prefix' => rtrim($this->computePath($key), '/') . '/',
'MaxKeys' => 1,
]);
if (isset($result['Contents'])) {
if (is_array($result['Contents']) || $result['Contents'] instanceof \Countable) {
return count($result['Contents']) > 0;
}
}
return false;
}
/**
* Ensures the specified bucket exists. If the bucket does not exists
* and the create option is set to true, it will try to create the
* bucket. The bucket is created using the same region as the supplied
* client object.
*
* @throws \RuntimeException if the bucket does not exists or could not be
* created
*/
protected function ensureBucketExists()
{
if ($this->bucketExists) {
return true;
}
if ($this->bucketExists = $this->service->doesBucketExist($this->bucket)) {
return true;
}
if (!$this->options['create']) {
throw new \RuntimeException(sprintf(
'The configured bucket "%s" does not exist.',
$this->bucket
));
}
$this->service->createBucket([
'Bucket' => $this->bucket,
'LocationConstraint' => $this->service->getRegion(),
]);
$this->bucketExists = true;
return true;
}
protected function getOptions($key, array $options = [])
{
$options['ACL'] = $this->options['acl'];
$options['Bucket'] = $this->bucket;
$options['Key'] = $this->computePath($key);
/*
* Merge global options for adapter, which are set in the constructor, with metadata.
* Metadata will override global options.
*/
$options = array_merge($this->options, $options, $this->getMetadata($key));
return $options;
}
protected function computePath($key)
{
if (empty($this->options['directory'])) {
return $key;
}
return sprintf('%s/%s', $this->options['directory'], $key);
}
/**
* Computes the key from the specified path.
*
* @param string $path
*
* return string
*/
protected function computeKey($path)
{
return ltrim(substr($path, strlen($this->options['directory'])), '/');
}
/**
* @param string $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
public function mimeType($key)
{
try {
$result = $this->service->headObject($this->getOptions($key));
return ($result['ContentType']);
} catch (\Exception $e) {
return false;
}
}
}

View File

@@ -0,0 +1,598 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\Util;
use Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface;
use MicrosoftAzure\Storage\Blob\Models\Blob;
use MicrosoftAzure\Storage\Blob\Models\BlobServiceOptions;
use MicrosoftAzure\Storage\Blob\Models\Container;
use MicrosoftAzure\Storage\Blob\Models\CreateBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateBlockBlobOptions;
use MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions;
use MicrosoftAzure\Storage\Blob\Models\ListBlobsOptions;
use MicrosoftAzure\Storage\Common\Exceptions\ServiceException;
/**
* Microsoft Azure Blob Storage adapter.
*
* @author Luciano Mammino <lmammino@oryzone.com>
* @author Paweł Czyżewski <pawel.czyzewski@enginewerk.com>
*/
class AzureBlobStorage implements Adapter, MetadataSupporter, SizeCalculator, ChecksumCalculator, MimeTypeProvider
{
/**
* Error constants.
*/
const ERROR_CONTAINER_ALREADY_EXISTS = 'ContainerAlreadyExists';
const ERROR_CONTAINER_NOT_FOUND = 'ContainerNotFound';
/**
* @var AzureBlobStorage\BlobProxyFactoryInterface
*/
protected $blobProxyFactory;
/**
* @var string
*/
protected $containerName;
/**
* @var bool
*/
protected $detectContentType;
/**
* @var \MicrosoftAzure\Storage\Blob\Internal\IBlob
*/
protected $blobProxy;
/**
* @var bool
*/
protected $multiContainerMode = false;
/**
* @var CreateContainerOptions
*/
protected $createContainerOptions;
/**
* @param AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param string|null $containerName
* @param bool $create
* @param bool $detectContentType
*
* @throws \RuntimeException
*/
public function __construct(BlobProxyFactoryInterface $blobProxyFactory, $containerName = null, $create = false, $detectContentType = true)
{
$this->blobProxyFactory = $blobProxyFactory;
$this->containerName = $containerName;
$this->detectContentType = $detectContentType;
if (null === $containerName) {
$this->multiContainerMode = true;
} elseif ($create) {
$this->createContainer($containerName);
}
}
/**
* @return CreateContainerOptions
*/
public function getCreateContainerOptions()
{
return $this->createContainerOptions;
}
/**
* @param CreateContainerOptions $options
*/
public function setCreateContainerOptions(CreateContainerOptions $options)
{
$this->createContainerOptions = $options;
}
/**
* Creates a new container.
*
* @param string $containerName
* @param \MicrosoftAzure\Storage\Blob\Models\CreateContainerOptions $options
*
* @throws \RuntimeException if cannot create the container
*/
public function createContainer($containerName, CreateContainerOptions $options = null)
{
$this->init();
if (null === $options) {
$options = $this->getCreateContainerOptions();
}
try {
$this->blobProxy->createContainer($containerName, $options);
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
if ($errorCode !== self::ERROR_CONTAINER_ALREADY_EXISTS) {
throw new \RuntimeException(sprintf(
'Failed to create the configured container "%s": %s (%s).',
$containerName,
$e->getErrorText(),
$errorCode
));
}
}
}
/**
* Deletes a container.
*
* @param string $containerName
* @param BlobServiceOptions $options
*
* @throws \RuntimeException if cannot delete the container
*/
public function deleteContainer($containerName, BlobServiceOptions $options = null)
{
$this->init();
try {
$this->blobProxy->deleteContainer($containerName, $options);
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
if ($errorCode !== self::ERROR_CONTAINER_NOT_FOUND) {
throw new \RuntimeException(sprintf(
'Failed to delete the configured container "%s": %s (%s).',
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function read($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$blob = $this->blobProxy->getBlob($containerName, $key);
return stream_get_contents($blob->getContentStream());
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function write($key, $content)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
if (class_exists(CreateBlockBlobOptions::class)) {
$options = new CreateBlockBlobOptions();
} else {
// for microsoft/azure-storage < 1.0
$options = new CreateBlobOptions();
}
if ($this->detectContentType) {
$contentType = $this->guessContentType($content);
$options->setContentType($contentType);
}
$size = is_resource($content)
? Util\Size::fromResource($content)
: Util\Size::fromContent($content)
;
try {
if ($this->multiContainerMode) {
$this->createContainer($containerName);
}
$this->blobProxy->createBlockBlob($containerName, $key, $content, $options);
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('write content for key "%s"', $key), $containerName);
return false;
}
return $size;
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function exists($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
$listBlobsOptions = new ListBlobsOptions();
$listBlobsOptions->setPrefix($key);
try {
$blobsList = $this->blobProxy->listBlobs($containerName, $listBlobsOptions);
foreach ($blobsList->getBlobs() as $blob) {
if ($key === $blob->getName()) {
return true;
}
}
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
if ($this->multiContainerMode && self::ERROR_CONTAINER_NOT_FOUND === $errorCode) {
return false;
}
$this->failIfContainerNotFound($e, 'check if key exists', $containerName);
throw new \RuntimeException(sprintf(
'Failed to check if key "%s" exists in container "%s": %s (%s).',
$key,
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
return false;
}
/**
* {@inheritdoc}
* @throws \RuntimeException
*/
public function keys()
{
$this->init();
try {
if ($this->multiContainerMode) {
$containersList = $this->blobProxy->listContainers();
return call_user_func_array('array_merge', array_map(
function (Container $container) {
$containerName = $container->getName();
return $this->fetchBlobs($containerName, $containerName);
},
$containersList->getContainers()
));
}
return $this->fetchBlobs($this->containerName);
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, 'retrieve keys', $this->containerName);
$errorCode = $this->getErrorCodeFromServiceException($e);
throw new \RuntimeException(sprintf(
'Failed to list keys for the container "%s": %s (%s).',
$this->containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function mtime($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getProperties()->getLastModified()->getTimestamp();
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read mtime for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function size($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getProperties()->getContentLength();
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read content length for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function mimeType($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getProperties()->getContentType();
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read content mime type for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function checksum($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
$checksumBase64 = $properties->getProperties()->getContentMD5();
return \bin2hex(\base64_decode($checksumBase64, true));
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('read content MD5 for key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function delete($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$this->blobProxy->deleteBlob($containerName, $key);
return true;
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('delete key "%s"', $key), $containerName);
return false;
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function rename($sourceKey, $targetKey)
{
$this->init();
list($sourceContainerName, $sourceKey) = $this->tokenizeKey($sourceKey);
list($targetContainerName, $targetKey) = $this->tokenizeKey($targetKey);
try {
if ($this->multiContainerMode) {
$this->createContainer($targetContainerName);
}
$this->blobProxy->copyBlob($targetContainerName, $targetKey, $sourceContainerName, $sourceKey);
$this->blobProxy->deleteBlob($sourceContainerName, $sourceKey);
return true;
} catch (ServiceException $e) {
$this->failIfContainerNotFound($e, sprintf('rename key "%s"', $sourceKey), $sourceContainerName);
return false;
}
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
// Windows Azure Blob Storage does not support directories
return false;
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function setMetadata($key, $content)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$this->blobProxy->setBlobMetadata($containerName, $key, $content);
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
throw new \RuntimeException(sprintf(
'Failed to set metadata for blob "%s" in container "%s": %s (%s).',
$key,
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
/**
* {@inheritdoc}
* @throws \RuntimeException
* @throws \InvalidArgumentException
*/
public function getMetadata($key)
{
$this->init();
list($containerName, $key) = $this->tokenizeKey($key);
try {
$properties = $this->blobProxy->getBlobProperties($containerName, $key);
return $properties->getMetadata();
} catch (ServiceException $e) {
$errorCode = $this->getErrorCodeFromServiceException($e);
throw new \RuntimeException(sprintf(
'Failed to get metadata for blob "%s" in container "%s": %s (%s).',
$key,
$containerName,
$e->getErrorText(),
$errorCode
), $e->getCode());
}
}
/**
* Lazy initialization, automatically called when some method is called after construction.
*/
protected function init()
{
if ($this->blobProxy === null) {
$this->blobProxy = $this->blobProxyFactory->create();
}
}
/**
* Throws a runtime exception if a give ServiceException derived from a "container not found" error.
*
* @param ServiceException $exception
* @param string $action
* @param string $containerName
*
* @throws \RuntimeException
*/
protected function failIfContainerNotFound(ServiceException $exception, $action, $containerName)
{
$errorCode = $this->getErrorCodeFromServiceException($exception);
if ($errorCode === self::ERROR_CONTAINER_NOT_FOUND) {
throw new \RuntimeException(sprintf(
'Failed to %s: container "%s" not found.',
$action,
$containerName
), $exception->getCode());
}
}
/**
* Extracts the error code from a service exception.
*
* @param ServiceException $exception
*
* @return string
*/
protected function getErrorCodeFromServiceException(ServiceException $exception)
{
$xml = @simplexml_load_string($exception->getResponse()->getBody());
if ($xml && isset($xml->Code)) {
return (string) $xml->Code;
}
return $exception->getErrorText();
}
/**
* @param string|resource $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
/**
* @param string $key
*
* @return array
* @throws \InvalidArgumentException
*/
private function tokenizeKey($key)
{
$containerName = $this->containerName;
if (false === $this->multiContainerMode) {
return [$containerName, $key];
}
if (false === ($index = strpos($key, '/'))) {
throw new \InvalidArgumentException(sprintf(
'Failed to establish container name from key "%s", container name is required in multi-container mode',
$key
));
}
$containerName = substr($key, 0, $index);
$key = substr($key, $index + 1);
return [$containerName, $key];
}
/**
* @param string $containerName
* @param null $prefix
*
* @return array
*/
private function fetchBlobs($containerName, $prefix = null)
{
$blobList = $this->blobProxy->listBlobs($containerName);
return array_map(
function (Blob $blob) use ($prefix) {
$name = $blob->getName();
if (null !== $prefix) {
$name = $prefix . '/' . $name;
}
return $name;
},
$blobList->getBlobs()
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Gaufrette\Adapter\AzureBlobStorage;
use MicrosoftAzure\Storage\Blob\BlobRestProxy;
use MicrosoftAzure\Storage\Common\ServicesBuilder;
/**
* Basic implementation for a Blob proxy factory.
*
* @author Luciano Mammino <lmammino@oryzone.com>
*/
class BlobProxyFactory implements BlobProxyFactoryInterface
{
/**
* @var string
*/
protected $connectionString;
/**
* @param string $connectionString
*/
public function __construct($connectionString)
{
if (!class_exists(ServicesBuilder::class) && !class_exists(BlobRestProxy::class)) {
throw new \LogicException('You need to install package "microsoft/azure-storage-blob" to use this adapter');
}
$this->connectionString = $connectionString;
}
/**
* {@inheritdoc}
*/
public function create()
{
if (class_exists(ServicesBuilder::class)) {
// for microsoft/azure-storage < 1.0
return ServicesBuilder::getInstance()->createBlobService($this->connectionString);
}
return BlobRestProxy::createBlobService($this->connectionString);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Gaufrette\Adapter\AzureBlobStorage;
/**
* Interface to define Blob proxy factories.
*
* @author Luciano Mammino <lmammino@oryzone.com>
*/
interface BlobProxyFactoryInterface
{
/**
* Creates a new instance of the Blob proxy.
*
* @return \MicrosoftAzure\Storage\Blob\Internal\IBlob
*/
public function create();
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add checksum calculation support to adapter.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface ChecksumCalculator
{
/**
* Returns the checksum of the specified key.
*
* @param string $key
*
* @return string
*/
public function checksum($key);
}

View File

@@ -0,0 +1,228 @@
<?php
namespace Gaufrette\Adapter;
use Doctrine\DBAL\Result;
use Gaufrette\Adapter;
use Gaufrette\Util;
use Doctrine\DBAL\Connection;
/**
* Doctrine DBAL adapter.
*
* @author Markus Bachmann <markus.bachmann@bachi.biz>
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class DoctrineDbal implements Adapter, ChecksumCalculator, ListKeysAware
{
protected $connection;
protected $table;
protected $columns = [
'key' => 'key',
'content' => 'content',
'mtime' => 'mtime',
'checksum' => 'checksum',
];
/**
* @param Connection $connection The DBAL connection
* @param string $table The files table
* @param array $columns The column names
*/
public function __construct(Connection $connection, $table, array $columns = [])
{
if (!class_exists(Connection::class)) {
throw new \LogicException('You need to install package "doctrine/dbal" to use this adapter');
}
$this->connection = $connection;
$this->table = $table;
$this->columns = array_replace($this->columns, $columns);
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = [];
$stmt = $this->connection->executeQuery(sprintf(
'SELECT %s FROM %s',
$this->getQuotedColumn('key'),
$this->getQuotedTable()
));
if (class_exists(Result::class)) {
// dbal 3.x
return $stmt->fetchFirstColumn();
}
// BC layer for dbal 2.x
return $stmt->fetchAll(\PDO::FETCH_COLUMN);
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
return (boolean) $this->connection->update(
$this->table,
[$this->getQuotedColumn('key') => $targetKey],
[$this->getQuotedColumn('key') => $sourceKey]
);
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
return $this->getColumnValue($key, 'mtime');
}
/**
* {@inheritdoc}
*/
public function checksum($key)
{
return $this->getColumnValue($key, 'checksum');
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, $method)) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
return (boolean) $this->connection->$method(
sprintf(
'SELECT COUNT(%s) FROM %s WHERE %s = :key',
$this->getQuotedColumn('key'),
$this->getQuotedTable(),
$this->getQuotedColumn('key')
),
['key' => $key]
);
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->getColumnValue($key, 'content');
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
return (boolean) $this->connection->delete(
$this->table,
[$this->getQuotedColumn('key') => $key]
);
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$values = [
$this->getQuotedColumn('content') => $content,
$this->getQuotedColumn('mtime') => time(),
$this->getQuotedColumn('checksum') => Util\Checksum::fromContent($content),
];
if ($this->exists($key)) {
$this->connection->update(
$this->table,
$values,
[$this->getQuotedColumn('key') => $key]
);
} else {
$values[$this->getQuotedColumn('key')] = $key;
$this->connection->insert($this->table, $values);
}
return Util\Size::fromContent($content);
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
return false;
}
private function getColumnValue($key, $column)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, $method)) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$value = $this->connection->$method(
sprintf(
'SELECT %s FROM %s WHERE %s = :key',
$this->getQuotedColumn($column),
$this->getQuotedTable(),
$this->getQuotedColumn('key')
),
['key' => $key]
);
return $value;
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$prefix = trim($prefix);
$method = 'fetchAllAssociative'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchAll'; // BC layer for dbal 2.x
}
$keys = $this->connection->$method(
sprintf(
'SELECT %s AS _key FROM %s WHERE %s LIKE :pattern',
$this->getQuotedColumn('key'),
$this->getQuotedTable(),
$this->getQuotedColumn('key')
),
['pattern' => sprintf('%s%%', $prefix)]
);
return [
'dirs' => [],
'keys' => array_map(
function ($value) {
return $value['_key'];
},
$keys
),
];
}
private function getQuotedTable()
{
return $this->connection->quoteIdentifier($this->table);
}
private function getQuotedColumn($column)
{
return $this->connection->quoteIdentifier($this->columns[$column]);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\File;
use Gaufrette\Filesystem;
/**
* Interface for the file creation class.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface FileFactory
{
/**
* Creates a new File instance and returns it.
*
* @param string $key
* @param Filesystem $filesystem
*
* @return File
*/
public function createFile($key, Filesystem $filesystem);
}

View File

@@ -0,0 +1,125 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\Exception\UnsupportedAdapterMethodException;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Util;
class Flysystem implements Adapter, ListKeysAware
{
/**
* @var AdapterInterface
*/
private $adapter;
/**
* @var Config
*/
private $config;
/**
* @param AdapterInterface $adapter
* @param \League\Flysystem\Config|array|null $config
*/
public function __construct(AdapterInterface $adapter, $config = null)
{
if (!interface_exists(AdapterInterface::class)) {
throw new \LogicException('You need to install package "league/flysystem" to use this adapter');
}
$this->adapter = $adapter;
$this->config = Util::ensureConfig($config);
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->adapter->read($key)['contents'];
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
return $this->adapter->write($key, $content, $this->config);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return (bool) $this->adapter->has($key);
}
/**
* {@inheritdoc}
*/
public function keys()
{
return array_map(function ($content) {
return $content['path'];
}, $this->adapter->listContents());
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$dirs = [];
$keys = [];
foreach ($this->adapter->listContents() as $content) {
if (empty($prefix) || 0 === strpos($content['path'], $prefix)) {
if ('dir' === $content['type']) {
$dirs[] = $content['path'];
} else {
$keys[] = $content['path'];
}
}
}
return [
'keys' => $keys,
'dirs' => $dirs,
];
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
return $this->adapter->getTimestamp($key);
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
return $this->adapter->delete($key);
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
return $this->adapter->rename($sourceKey, $targetKey);
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
throw new UnsupportedAdapterMethodException('isDirectory is not supported by this adapter.');
}
}

View File

@@ -0,0 +1,596 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\File;
use Gaufrette\Filesystem;
/**
* Ftp adapter.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Ftp implements Adapter, FileFactory, ListKeysAware, SizeCalculator
{
/** @var null|resource|\FTP\Connection */
protected $connection = null;
protected $directory;
protected $host;
protected $port;
protected $username;
protected $password;
protected $passive;
protected $create;
protected $mode;
protected $ssl;
protected $timeout;
protected $fileData = [];
protected $utf8;
/**
* @param string $directory The directory to use in the ftp server
* @param string $host The host of the ftp server
* @param array $options The options like port, username, password, passive, create, mode
*/
public function __construct($directory, $host, $options = [])
{
if (!extension_loaded('ftp')) {
throw new \RuntimeException('Unable to use Gaufrette\Adapter\Ftp as the FTP extension is not available.');
}
$this->directory = (string) $directory;
$this->host = $host;
$this->port = $options['port'] ?? 21;
$this->username = $options['username'] ?? null;
$this->password = $options['password'] ?? null;
$this->passive = $options['passive'] ?? false;
$this->create = $options['create'] ?? false;
$this->mode = $options['mode'] ?? FTP_BINARY;
$this->ssl = $options['ssl'] ?? false;
$this->timeout = $options['timeout'] ?? 90;
$this->utf8 = $options['utf8'] ?? false;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$temp = fopen('php://temp', 'r+');
if (!ftp_fget($this->getConnection(), $temp, $this->computePath($key), $this->mode)) {
return false;
}
rewind($temp);
$contents = stream_get_contents($temp);
fclose($temp);
return $contents;
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$path = $this->computePath($key);
$directory = \Gaufrette\Util\Path::dirname($path);
$this->ensureDirectoryExists($directory, true);
$temp = fopen('php://temp', 'r+');
$size = fwrite($temp, $content);
rewind($temp);
if (!ftp_fput($this->getConnection(), $path, $temp, $this->mode)) {
fclose($temp);
return false;
}
fclose($temp);
return $size;
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$sourcePath = $this->computePath($sourceKey);
$targetPath = $this->computePath($targetKey);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true);
return ftp_rename($this->getConnection(), $sourcePath, $targetPath);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$file = $this->computePath($key);
$lines = ftp_rawlist($this->getConnection(), '-al ' . \Gaufrette\Util\Path::dirname($file));
if (false === $lines) {
return false;
}
$pattern = '{(?<!->) ' . preg_quote(basename($file)) . '( -> |$)}m';
foreach ($lines as $line) {
if (preg_match($pattern, $line)) {
return true;
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function keys()
{
$this->ensureDirectoryExists($this->directory, $this->create);
$keys = $this->fetchKeys();
return $keys['keys'];
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureDirectoryExists($this->directory, $this->create);
preg_match('/(.*?)[^\/]*$/', $prefix, $match);
$directory = rtrim($match[1], '/');
$keys = $this->fetchKeys($directory, false);
if ($directory === $prefix) {
return $keys;
}
$filteredKeys = [];
foreach (['keys', 'dirs'] as $hash) {
$filteredKeys[$hash] = [];
foreach ($keys[$hash] as $key) {
if (0 === strpos($key, $prefix)) {
$filteredKeys[$hash][] = $key;
}
}
}
return $filteredKeys;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$mtime = ftp_mdtm($this->getConnection(), $this->computePath($key));
// the server does not support this function
if (-1 === $mtime) {
throw new \RuntimeException('Server does not support ftp_mdtm function.');
}
return $mtime;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
if ($this->isDirectory($key)) {
return ftp_rmdir($this->getConnection(), $this->computePath($key));
}
return ftp_delete($this->getConnection(), $this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
return $this->isDir($this->computePath($key));
}
/**
* Lists files from the specified directory. If a pattern is
* specified, it only returns files matching it.
*
* @param string $directory The path of the directory to list from
*
* @return array An array of keys and dirs
*/
public function listDirectory($directory = '')
{
$this->ensureDirectoryExists($this->directory, $this->create);
$directory = preg_replace('/^[\/]*([^\/].*)$/', '/$1', $directory);
$items = $this->parseRawlist(
ftp_rawlist($this->getConnection(), '-al ' . $this->directory . $directory) ?: []
);
$fileData = $dirs = [];
foreach ($items as $itemData) {
if ('..' === $itemData['name'] || '.' === $itemData['name']) {
continue;
}
$item = [
'name' => $itemData['name'],
'path' => trim(($directory ? $directory . '/' : '') . $itemData['name'], '/'),
'time' => $itemData['time'],
'size' => $itemData['size'],
];
if ('-' === substr($itemData['perms'], 0, 1)) {
$fileData[$item['path']] = $item;
} elseif ('d' === substr($itemData['perms'], 0, 1)) {
$dirs[] = $item['path'];
}
}
$this->fileData = array_merge($fileData, $this->fileData);
return [
'keys' => array_keys($fileData),
'dirs' => $dirs,
];
}
/**
* {@inheritdoc}
*/
public function createFile($key, Filesystem $filesystem)
{
$this->ensureDirectoryExists($this->directory, $this->create);
$file = new File($key, $filesystem);
if (!array_key_exists($key, $this->fileData)) {
$dirname = \Gaufrette\Util\Path::dirname($key);
$directory = $dirname == '.' ? '' : $dirname;
$this->listDirectory($directory);
}
if (isset($this->fileData[$key])) {
$fileData = $this->fileData[$key];
$file->setName($fileData['name']);
$file->setSize($fileData['size']);
}
return $file;
}
/**
* @param string $key
*
* @return int
*
* @throws \RuntimeException
*/
public function size($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
if (-1 === $size = ftp_size($this->connection, $key)) {
throw new \RuntimeException(sprintf('Unable to fetch the size of "%s".', $key));
}
return $size;
}
/**
* Ensures the specified directory exists. If it does not, and the create
* parameter is set to TRUE, it tries to create it.
*
* @param string $directory
* @param bool $create Whether to create the directory if it does not
* exist
*
* @throws RuntimeException if the directory does not exist and could not
* be created
*/
protected function ensureDirectoryExists($directory, $create = false)
{
if (!$this->isDir($directory)) {
if (!$create) {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $directory));
}
$this->createDirectory($directory);
}
}
/**
* Creates the specified directory and its parent directories.
*
* @param string $directory Directory to create
*
* @throws RuntimeException if the directory could not be created
*/
protected function createDirectory($directory)
{
// create parent directory if needed
$parent = \Gaufrette\Util\Path::dirname($directory);
if (!$this->isDir($parent)) {
$this->createDirectory($parent);
}
// create the specified directory
$created = ftp_mkdir($this->getConnection(), $directory);
if (false === $created) {
throw new \RuntimeException(sprintf('Could not create the \'%s\' directory.', $directory));
}
}
/**
* @param string $directory - full directory path
*
* @return bool
*/
private function isDir($directory)
{
if ('/' === $directory) {
return true;
}
if (!@ftp_chdir($this->getConnection(), $directory)) {
return false;
}
// change directory again to return in the base directory
ftp_chdir($this->getConnection(), $this->directory);
return true;
}
private function fetchKeys($directory = '', $onlyKeys = true)
{
$directory = preg_replace('/^[\/]*([^\/].*)$/', '/$1', $directory);
$lines = ftp_rawlist($this->getConnection(), '-alR ' . $this->directory . $directory);
if (false === $lines) {
return ['keys' => [], 'dirs' => []];
}
$regexDir = '/' . preg_quote($this->directory . $directory, '/') . '\/?(.+):$/u';
$regexItem = '/^(?:([d\-\d])\S+)\s+\S+(?:(?:\s+\S+){5})?\s+(\S+)\s+(.+?)$/';
$prevLine = null;
$directories = [];
$keys = ['keys' => [], 'dirs' => []];
foreach ((array) $lines as $line) {
if ('' === $prevLine && preg_match($regexDir, $line, $match)) {
$directory = $match[1];
unset($directories[$directory]);
if ($onlyKeys) {
$keys = [
'keys' => array_merge($keys['keys'], $keys['dirs']),
'dirs' => [],
];
}
} elseif (preg_match($regexItem, $line, $tokens)) {
$name = $tokens[3];
if ('.' === $name || '..' === $name) {
continue;
}
$path = ltrim($directory . '/' . $name, '/');
if ('d' === $tokens[1] || '<dir>' === $tokens[2]) {
$keys['dirs'][] = $path;
$directories[$path] = true;
} else {
$keys['keys'][] = $path;
}
}
$prevLine = $line;
}
if ($onlyKeys) {
$keys = [
'keys' => array_merge($keys['keys'], $keys['dirs']),
'dirs' => [],
];
}
foreach (array_keys($directories) as $directory) {
$keys = array_merge_recursive($keys, $this->fetchKeys($directory, $onlyKeys));
}
return $keys;
}
/**
* Parses the given raw list.
*
* @param array $rawlist
*
* @return array
*/
private function parseRawlist(array $rawlist)
{
$parsed = [];
foreach ($rawlist as $line) {
$infos = preg_split("/[\s]+/", $line, 9);
if ($this->isLinuxListing($infos)) {
$infos[7] = (strrpos($infos[7], ':') != 2) ? ($infos[7] . ' 00:00') : (date('Y') . ' ' . $infos[7]);
if ('total' !== $infos[0]) {
$parsed[] = [
'perms' => $infos[0],
'num' => $infos[1],
'size' => $infos[4],
'time' => strtotime($infos[5] . ' ' . $infos[6] . '. ' . $infos[7]),
'name' => $infos[8],
];
}
} elseif (count($infos) >= 4) {
$isDir = (boolean) ('<dir>' === $infos[2]);
$parsed[] = [
'perms' => $isDir ? 'd' : '-',
'num' => '',
'size' => $isDir ? '' : $infos[2],
'time' => strtotime($infos[0] . ' ' . $infos[1]),
'name' => $infos[3],
];
}
}
return $parsed;
}
/**
* Computes the path for the given key.
*
* @param string $key
*/
private function computePath($key)
{
return rtrim($this->directory, '/') . '/' . $key;
}
/**
* Indicates whether the adapter has an open ftp connection.
*
* @return bool
*/
private function isConnected()
{
if (class_exists('\FTP\Connection')) {
return $this->connection instanceof \FTP\Connection;
}
return is_resource($this->connection);
}
/**
* Returns an opened ftp connection resource. If the connection is not
* already opened, it open it before.
*
* @return resource|\FTP\Connection The ftp connection
*/
private function getConnection()
{
if (!$this->isConnected()) {
$this->connect();
}
return $this->connection;
}
/**
* Opens the adapter's ftp connection.
*
* @throws RuntimeException if could not connect
*/
private function connect()
{
if ($this->ssl && !function_exists('ftp_ssl_connect')) {
throw new \RuntimeException('This Server Has No SSL-FTP Available.');
}
// open ftp connection
if (!$this->ssl) {
$this->connection = ftp_connect($this->host, $this->port, $this->timeout);
} else {
$this->connection = ftp_ssl_connect($this->host, $this->port, $this->timeout);
}
if (!$this->connection) {
throw new \RuntimeException(sprintf('Could not connect to \'%s\' (port: %s).', $this->host, $this->port));
}
if (defined('FTP_USEPASVADDRESS')) {
ftp_set_option($this->connection, FTP_USEPASVADDRESS, false);
}
$username = $this->username ?: 'anonymous';
$password = $this->password ?: '';
// login ftp user
if (!@ftp_login($this->connection, $username, $password)) {
$this->close();
throw new \RuntimeException(sprintf('Could not login as %s.', $username));
}
// switch to passive mode if needed
if ($this->passive && !ftp_pasv($this->connection, true)) {
$this->close();
throw new \RuntimeException('Could not turn passive mode on.');
}
// enable utf8 mode if configured
if ($this->utf8 == true) {
ftp_raw($this->connection, 'OPTS UTF8 ON');
}
// ensure the adapter's directory exists
if ('/' !== $this->directory) {
try {
$this->ensureDirectoryExists($this->directory, $this->create);
} catch (\RuntimeException $e) {
$this->close();
throw $e;
}
// change the current directory for the adapter's directory
if (!ftp_chdir($this->connection, $this->directory)) {
$this->close();
throw new \RuntimeException(sprintf('Could not change current directory for the \'%s\' directory.', $this->directory));
}
}
}
/**
* Closes the adapter's ftp connection.
*/
public function close()
{
if ($this->isConnected()) {
ftp_close($this->connection);
}
}
private function isLinuxListing($info)
{
return count($info) >= 9;
}
}

View File

@@ -0,0 +1,447 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Google\Service\Storage;
use Google\Service\Storage\Bucket;
use Google\Service\Storage\StorageObject;
use Google\Service\Exception as ServiceException;
use Google\Service\Storage\BucketIamConfiguration;
use Google\Service\Storage\BucketIamConfigurationUniformBucketLevelAccess;
use GuzzleHttp;
/**
* Google Cloud Storage adapter using the Google APIs Client Library for PHP.
*
* @author Patrik Karisch <patrik@karisch.guru>
*/
class GoogleCloudStorage implements Adapter, MetadataSupporter, ListKeysAware
{
public const OPTION_CREATE_BUCKET_IF_NOT_EXISTS = 'create';
public const OPTION_PROJECT_ID = 'project_id';
public const OPTION_LOCATION = 'bucket_location';
public const OPTION_STORAGE_CLASS = 'storage_class';
protected $service;
protected $bucket;
protected $options = [
self::OPTION_CREATE_BUCKET_IF_NOT_EXISTS => false,
self::OPTION_STORAGE_CLASS => 'STANDARD',
'directory' => '',
'acl' => 'private',
];
protected $bucketExists;
protected $metadata = [];
protected $detectContentType;
/**
* @param Storage $service The storage service class with authenticated
* client and full access scope
* @param string $bucket The bucket name
* @param array $options Options can be directory and acl
* @param bool $detectContentType Whether to detect the content type or not
*/
public function __construct(
Storage $service,
$bucket,
array $options = [],
$detectContentType = false
) {
if (!class_exists(Storage::class)) {
throw new \LogicException('You need to install package "google/apiclient" to use this adapter');
}
$this->service = $service;
$this->bucket = $bucket;
$this->options = array_replace(
$this->options,
$options
);
$this->detectContentType = $detectContentType;
}
/**
* @return array The actual options
*/
public function getOptions()
{
return $this->options;
}
/**
* @param array $options The new options
*/
public function setOptions($options)
{
$this->options = array_replace($this->options, $options);
}
/**
* @return string The current bucket name
*/
public function getBucket()
{
return $this->bucket;
}
/**
* Sets a new bucket name.
*
* @param string $bucket The new bucket name
*/
public function setBucket($bucket)
{
$this->bucketExists = null;
$this->bucket = $bucket;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
$object = $this->getObjectData($path);
if ($object === false) {
return false;
}
if (class_exists('Google_Http_Request')) {
$request = new \Google_Http_Request($object->getMediaLink());
$this->service->getClient()->getAuth()->sign($request);
$response = $this->service->getClient()->getIo()->executeRequest($request);
if ($response[2] == 200) {
$this->setMetadata($key, $object->getMetadata());
return $response[0];
}
} else {
$httpClient = new GuzzleHttp\Client();
$httpClient = $this->service->getClient()->authorize($httpClient);
$response = $httpClient->request('GET', $object->getMediaLink());
if ($response->getStatusCode() == 200) {
$this->setMetadata($key, $object->getMetadata());
return $response->getBody();
}
}
return false;
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
$metadata = $this->getMetadata($key);
$options = [
'uploadType' => 'multipart',
'data' => $content,
];
/*
* If the ContentType was not already set in the metadata, then we autodetect
* it to prevent everything being served up as application/octet-stream.
*/
if (!isset($metadata['ContentType']) && $this->detectContentType) {
$options['mimeType'] = $this->guessContentType($content);
unset($metadata['ContentType']);
} elseif (isset($metadata['ContentType'])) {
$options['mimeType'] = $metadata['ContentType'];
unset($metadata['ContentType']);
}
$object = new StorageObject();
$object->name = $path;
if (isset($metadata['ContentDisposition'])) {
$object->setContentDisposition($metadata['ContentDisposition']);
unset($metadata['ContentDisposition']);
}
if (isset($metadata['CacheControl'])) {
$object->setCacheControl($metadata['CacheControl']);
unset($metadata['CacheControl']);
}
if (isset($metadata['ContentLanguage'])) {
$object->setContentLanguage($metadata['ContentLanguage']);
unset($metadata['ContentLanguage']);
}
if (isset($metadata['ContentEncoding'])) {
$object->setContentEncoding($metadata['ContentEncoding']);
unset($metadata['ContentEncoding']);
}
$object->setMetadata($metadata);
try {
$object = $this->service->objects->insert($this->bucket, $object, $options);
if ($this->options['acl'] == 'public') {
$acl = new \Google_Service_Storage_ObjectAccessControl();
$acl->setEntity('allUsers');
$acl->setRole('READER');
$this->service->objectAccessControls->insert($this->bucket, $path, $acl);
}
return $object->getSize();
} catch (ServiceException $e) {
return false;
}
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
try {
$this->service->objects->get($this->bucket, $path);
} catch (ServiceException $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function keys()
{
return $this->listKeys();
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
$object = $this->getObjectData($path);
return $object ? strtotime($object->getUpdated()) : false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
$this->ensureBucketExists();
$path = $this->computePath($key);
try {
$this->service->objects->delete($this->bucket, $path);
} catch (ServiceException $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->ensureBucketExists();
$sourcePath = $this->computePath($sourceKey);
$targetPath = $this->computePath($targetKey);
$object = $this->getObjectData($sourcePath);
if ($object === false) {
return false;
}
try {
$this->service->objects->copy($this->bucket, $sourcePath, $this->bucket, $targetPath, $object);
$this->service->objects->delete($this->bucket, $sourcePath);
} catch (ServiceException $e) {
return false;
}
return true;
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
if ($this->exists($key . '/')) {
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$this->ensureBucketExists();
$options = [];
if ((string) $prefix != '') {
$options['prefix'] = $this->computePath($prefix);
} elseif (!empty($this->options['directory'])) {
$options['prefix'] = $this->options['directory'];
}
$list = $this->service->objects->listObjects($this->bucket, $options);
$keys = [];
// FIXME: Temporary workaround for google/google-api-php-client#375
$reflectionClass = new \ReflectionClass('Google_Service_Storage_Objects');
$reflectionProperty = $reflectionClass->getProperty('collection_key');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($list, 'items');
/** @var StorageObject $object */
foreach ($list as $object) {
$keys[] = $object->name;
}
sort($keys);
return $keys;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $content)
{
$path = $this->computePath($key);
$this->metadata[$path] = $content;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
$path = $this->computePath($key);
return $this->metadata[$path] ?? [];
}
/**
* Ensures the specified bucket exists.
*
* @throws \RuntimeException if the bucket does not exists
*/
protected function ensureBucketExists()
{
if ($this->bucketExists) {
return;
}
try {
$this->service->buckets->get($this->bucket);
$this->bucketExists = true;
return;
} catch (ServiceException $e) {
if ($this->options[self::OPTION_CREATE_BUCKET_IF_NOT_EXISTS]) {
if (!isset($this->options[self::OPTION_PROJECT_ID])) {
throw new \RuntimeException(
sprintf('Option "%s" missing, cannot create bucket', self::OPTION_PROJECT_ID)
);
}
if (!isset($this->options[self::OPTION_LOCATION])) {
throw new \RuntimeException(
sprintf('Option "%s" missing, cannot create bucket', self::OPTION_LOCATION)
);
}
$bucketIamConfigDetail = new BucketIamConfigurationUniformBucketLevelAccess();
$bucketIamConfigDetail->setEnabled(true);
$bucketIam = new BucketIamConfiguration();
$bucketIam->setUniformBucketLevelAccess($bucketIamConfigDetail);
$bucket = new Bucket();
$bucket->setName($this->bucket);
$bucket->setLocation($this->options[self::OPTION_LOCATION]);
$bucket->setStorageClass($this->options[self::OPTION_STORAGE_CLASS]);
$bucket->setIamConfiguration($bucketIam);
$this->service->buckets->insert(
$this->options[self::OPTION_PROJECT_ID],
$bucket
);
$this->bucketExists = true;
return;
}
$this->bucketExists = false;
throw new \RuntimeException(
sprintf(
'The configured bucket "%s" does not exist.',
$this->bucket
)
);
}
}
protected function computePath($key)
{
if (empty($this->options['directory'])) {
return $key;
}
return sprintf('%s/%s', $this->options['directory'], $key);
}
/**
* @param string $path
* @param array $options
*
* @return bool|StorageObject
*/
private function getObjectData($path, $options = [])
{
try {
return $this->service->objects->get($this->bucket, $path, $options);
} catch (ServiceException $e) {
return false;
}
}
/**
* @param string $content
*
* @return string
*/
private function guessContentType($content)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
if (is_resource($content)) {
return $fileInfo->file(stream_get_meta_data($content)['uri']);
}
return $fileInfo->buffer($content);
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use MongoDB\BSON\Regex;
use MongoDB\GridFS\Bucket;
use MongoDB\GridFS\Exception\FileNotFoundException;
/**
* Adapter for the GridFS filesystem on MongoDB database.
*
* @author Tomi Saarinen <tomi.saarinen@rohea.com>
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class GridFS implements Adapter, ChecksumCalculator, MetadataSupporter, ListKeysAware, SizeCalculator
{
/** @var array */
private $metadata = [];
/** @var Bucket */
private $bucket;
/**
* @param Bucket $bucket
*/
public function __construct(Bucket $bucket)
{
if (!class_exists(Bucket::class)) {
throw new \LogicException('You need to install package "mongodb/mongodb" to use this adapter');
}
$this->bucket = $bucket;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
try {
$stream = $this->bucket->openDownloadStreamByName($key);
} catch (FileNotFoundException $e) {
return false;
}
try {
return stream_get_contents($stream);
} finally {
fclose($stream);
}
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$stream = $this->bucket->openUploadStream($key, ['metadata' => $this->getMetadata($key)]);
try {
return fwrite($stream, $content);
} finally {
fclose($stream);
}
return false;
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
return false;
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$metadata = $this->getMetadata($sourceKey);
$writable = $this->bucket->openUploadStream($targetKey, ['metadata' => $metadata]);
try {
$this->bucket->downloadToStreamByName($sourceKey, $writable);
$this->setMetadata($targetKey, $metadata);
$this->delete($sourceKey);
} catch (FileNotFoundException $e) {
return false;
} finally {
fclose($writable);
}
return true;
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return (boolean) $this->bucket->findOne(['filename' => $key]);
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = [];
$cursor = $this->bucket->find([], ['projection' => ['filename' => 1]]);
foreach ($cursor as $file) {
$keys[] = $file['filename'];
}
return $keys;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$file = $this->bucket->findOne(['filename' => $key], ['projection' => ['uploadDate' => 1]]);
return $file ? (int) $file['uploadDate']->toDateTime()->format('U') : false;
}
/**
* {@inheritdoc}
*/
public function checksum($key)
{
$file = $this->bucket->findOne(['filename' => $key], ['projection' => ['md5' => 1]]);
return $file ? $file['md5'] : false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
if (null === $file = $this->bucket->findOne(['filename' => $key], ['projection' => ['_id' => 1]])) {
return false;
}
$this->bucket->delete($file['_id']);
return true;
}
/**
* {@inheritdoc}
*/
public function setMetadata($key, $metadata)
{
$this->metadata[$key] = $metadata;
}
/**
* {@inheritdoc}
*/
public function getMetadata($key)
{
if (isset($this->metadata[$key])) {
return $this->metadata[$key];
}
$meta = $this->bucket->findOne(['filename' => $key], ['projection' => ['metadata' => 1,'_id' => 0]]);
if ($meta === null || !isset($meta['metadata'])) {
return [];
}
$this->metadata[$key] = iterator_to_array($meta['metadata']);
return $this->metadata[$key];
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
$prefix = trim($prefix);
if ($prefix === '') {
return [
'dirs' => [],
'keys' => $this->keys(),
];
}
$regex = new Regex(sprintf('^%s', $prefix), '');
$files = $this->bucket->find(['filename' => $regex], ['projection' => ['filename' => 1]]);
$result = [
'dirs' => [],
'keys' => [],
];
foreach ($files as $file) {
$result['keys'][] = $file['filename'];
}
return $result;
}
public function size($key)
{
if (!$this->exists($key)) {
return false;
}
$size = $this->bucket->findOne(['filename' => $key], ['projection' => ['length' => 1,'_id' => 0]]);
if (!isset($size['length'])) {
return false;
}
return $size['length'];
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use Gaufrette\Util;
/**
* In memory adapter.
*
* Stores some files in memory for test purposes
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class InMemory implements Adapter, MimeTypeProvider
{
protected $files = [];
/**
* @param array $files An array of files
*/
public function __construct(array $files = [])
{
$this->setFiles($files);
}
/**
* Defines the files.
*
* @param array $files An array of files
*/
public function setFiles(array $files)
{
$this->files = [];
foreach ($files as $key => $file) {
if (!is_array($file)) {
$file = ['content' => $file];
}
$file = array_merge([
'content' => null,
'mtime' => null,
], $file);
$this->setFile($key, $file['content'], $file['mtime']);
}
}
/**
* Defines a file.
*
* @param string $key The key
* @param string $content The content
* @param int $mtime The last modified time (automatically set to now if NULL)
*/
public function setFile($key, $content = null, $mtime = null)
{
if (null === $mtime) {
$mtime = time();
}
$this->files[$key] = [
'content' => (string) $content,
'mtime' => (integer) $mtime,
];
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->files[$key]['content'];
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$content = $this->read($sourceKey);
$this->delete($sourceKey);
return (boolean) $this->write($targetKey, $content);
}
/**
* {@inheritdoc}
*/
public function write($key, $content, array $metadata = null)
{
$this->files[$key]['content'] = $content;
$this->files[$key]['mtime'] = time();
return Util\Size::fromContent($content);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return array_key_exists($key, $this->files);
}
/**
* {@inheritdoc}
*/
public function keys()
{
return array_keys($this->files);
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
return $this->files[$key]['mtime'] ?? false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
unset($this->files[$key]);
clearstatcache();
return true;
}
/**
* {@inheritdoc}
*/
public function isDirectory($path)
{
return false;
}
/**
* {@inheritdoc}
*/
public function mimeType($key)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
return $fileInfo->buffer($this->files[$key]['content']);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Gaufrette\Adapter;
/**
* interface that adds support of native listKeys to adapter.
*
* @author Andrew Tch <andrew.tchircoff@gmail.com>
*/
interface ListKeysAware
{
/**
* Lists keys beginning with pattern given
* (no wildcard / regex matching).
*
* @param string $prefix
*
* @return array
*/
public function listKeys($prefix = '');
}

View File

@@ -0,0 +1,353 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Util;
use Gaufrette\Adapter;
use Gaufrette\Stream;
/**
* Adapter for the local filesystem.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class Local implements Adapter, StreamFactory, ChecksumCalculator, SizeCalculator, MimeTypeProvider
{
protected $directory;
private $create;
private $mode;
/**
* @param string $directory Directory where the filesystem is located
* @param bool $create Whether to create the directory if it does not
* exist (default FALSE)
* @param int $mode Mode for mkdir
*
* @throws \RuntimeException if the specified directory does not exist and
* could not be created
*/
public function __construct($directory, $create = false, $mode = 0777)
{
$this->directory = Util\Path::normalize($directory);
if (is_link($this->directory)) {
$this->directory = realpath($this->directory);
}
$this->create = $create;
$this->mode = $mode;
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function read($key)
{
if ($this->isDirectory($key)) {
return false;
}
return file_get_contents($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function write($key, $content)
{
$path = $this->computePath($key);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($path), true);
return file_put_contents($path, $content);
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function rename($sourceKey, $targetKey)
{
$targetPath = $this->computePath($targetKey);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true);
return rename($this->computePath($sourceKey), $targetPath);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return is_file($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function keys()
{
$this->ensureDirectoryExists($this->directory, $this->create);
try {
$files = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$this->directory,
\FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
),
\RecursiveIteratorIterator::CHILD_FIRST
);
} catch (\Exception $e) {
$files = new \EmptyIterator();
}
$keys = [];
foreach ($files as $file) {
$keys[] = $this->computeKey($file);
}
sort($keys);
return $keys;
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function mtime($key)
{
return filemtime($this->computePath($key));
}
/**
* {@inheritdoc}
*
* Can also delete a directory recursively when the given $key matches a
* directory.
*/
public function delete($key)
{
if ($this->isDirectory($key)) {
return $this->deleteDirectory($this->computePath($key));
} elseif ($this->exists($key)) {
return unlink($this->computePath($key));
}
return false;
}
/**
* @param string $key
*
* @return bool
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function isDirectory($key)
{
return is_dir($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function createStream($key)
{
return new Stream\Local($this->computePath($key), $this->mode);
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function checksum($key)
{
return Util\Checksum::fromFile($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function size($key)
{
return Util\Size::fromFile($this->computePath($key));
}
/**
* {@inheritdoc}
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function mimeType($key)
{
$fileInfo = new \finfo(FILEINFO_MIME_TYPE);
return $fileInfo->file($this->computePath($key));
}
/**
* Computes the key from the specified path.
*
* @param $path
* @return string
*
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
public function computeKey($path)
{
$path = $this->normalizePath($path);
return ltrim(substr($path, strlen($this->directory)), '/');
}
/**
* Computes the path from the specified key.
*
* @param string $key The key which for to compute the path
*
* @return string A path
*
* @throws \InvalidArgumentException If the directory already exists
* @throws \OutOfBoundsException If the computed path is out of the directory
* @throws \RuntimeException If directory does not exists and cannot be created
*/
protected function computePath($key)
{
$this->ensureDirectoryExists($this->directory, $this->create);
return $this->normalizePath($this->directory . '/' . $key);
}
/**
* Normalizes the given path.
*
* @param string $path
*
* @return string
* @throws \OutOfBoundsException If the computed path is out of the
* directory
*/
protected function normalizePath($path)
{
$path = Util\Path::normalize($path);
if (0 !== strpos($path, $this->directory)) {
throw new \OutOfBoundsException(sprintf('The path "%s" is out of the filesystem.', $path));
}
return $path;
}
/**
* Ensures the specified directory exists, creates it if it does not.
*
* @param string $directory Path of the directory to test
* @param bool $create Whether to create the directory if it does
* not exist
*
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory does not exists and could not
* be created
*/
protected function ensureDirectoryExists($directory, $create = false)
{
if (!is_dir($directory)) {
if (!$create) {
throw new \RuntimeException(sprintf('The directory "%s" does not exist.', $directory));
}
$this->createDirectory($directory);
}
}
/**
* Creates the specified directory and its parents.
*
* @param string $directory Path of the directory to create
*
* @throws \InvalidArgumentException if the directory already exists
* @throws \RuntimeException if the directory could not be created
*/
protected function createDirectory($directory)
{
if (!@mkdir($directory, $this->mode, true) && !is_dir($directory)) {
throw new \RuntimeException(sprintf('The directory \'%s\' could not be created.', $directory));
}
}
/**
* @param string The directory's path to delete
*
* @throws \InvalidArgumentException When attempting to delete the root
* directory of this adapter.
*
* @return bool Wheter the operation succeeded or not
*/
private function deleteDirectory($directory)
{
if ($this->directory === $directory) {
throw new \InvalidArgumentException(
sprintf('Impossible to delete the root directory of this Local adapter ("%s").', $directory)
);
}
$status = true;
if (file_exists($directory)) {
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator(
$directory,
\FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS
),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $item) {
if ($item->isDir()) {
$status = $status && rmdir(strval($item));
} else {
$status = $status && unlink(strval($item));
}
}
$status = $status && rmdir($directory);
}
return $status;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add supports for metadata.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface MetadataSupporter
{
/**
* @param string $key
* @param array $content
*/
public function setMetadata($key, $content);
/**
* @param string $key
*
* @return array
*/
public function getMetadata($key);
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add mime type provider support to adapter.
*
* @author Gildas Quemener <gildas.quemener@gmail.com>
*/
interface MimeTypeProvider
{
/**
* Returns the mime type of the specified key.
*
* @param string $key
*
* @return string
*/
public function mimeType($key);
}

View File

@@ -0,0 +1,242 @@
<?php
namespace Gaufrette\Adapter;
use Gaufrette\Adapter;
use phpseclib\Net\SFTP as SecLibSFTP;
use Gaufrette\Filesystem;
use Gaufrette\File;
class PhpseclibSftp implements Adapter, FileFactory, ListKeysAware
{
protected $sftp;
protected $directory;
protected $create;
protected $initialized = false;
/**
* @param SecLibSFTP $sftp An Sftp instance
* @param string $directory The distant directory
* @param bool $create Whether to create the remote directory if it
* does not exist
*/
public function __construct(SecLibSFTP $sftp, $directory = null, $create = false)
{
if (!class_exists(SecLibSFTP::class)) {
throw new \LogicException('You need to install package "phpseclib/phpseclib" to use this adapter');
}
$this->sftp = $sftp;
$this->directory = $directory;
$this->create = $create;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
return $this->sftp->get($this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
$this->initialize();
$sourcePath = $this->computePath($sourceKey);
$targetPath = $this->computePath($targetKey);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($targetPath), true);
return $this->sftp->rename($sourcePath, $targetPath);
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
$this->initialize();
$path = $this->computePath($key);
$this->ensureDirectoryExists(\Gaufrette\Util\Path::dirname($path), true);
if ($this->sftp->put($path, $content)) {
return $this->sftp->size($path);
}
return false;
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
$this->initialize();
return false !== $this->sftp->stat($this->computePath($key));
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
$this->initialize();
$pwd = $this->sftp->pwd();
if ($this->sftp->chdir($this->computePath($key))) {
$this->sftp->chdir($pwd);
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = $this->fetchKeys();
return $keys['keys'];
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
preg_match('/(.*?)[^\/]*$/', $prefix, $match);
$directory = rtrim($match[1], '/');
$keys = $this->fetchKeys($directory, false);
if ($directory === $prefix) {
return $keys;
}
$filteredKeys = [];
foreach (['keys', 'dirs'] as $hash) {
$filteredKeys[$hash] = [];
foreach ($keys[$hash] as $key) {
if (0 === strpos($key, $prefix)) {
$filteredKeys[$hash][] = $key;
}
}
}
return $filteredKeys;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$this->initialize();
$stat = $this->sftp->stat($this->computePath($key));
return $stat['mtime'] ?? false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
return $this->sftp->delete($this->computePath($key), false);
}
/**
* {@inheritdoc}
*/
public function createFile($key, Filesystem $filesystem)
{
$file = new File($key, $filesystem);
$stat = $this->sftp->stat($this->computePath($key));
if (isset($stat['size'])) {
$file->setSize($stat['size']);
}
return $file;
}
/**
* Performs the adapter's initialization.
*
* It will ensure the root directory exists
*/
protected function initialize()
{
if ($this->initialized) {
return;
}
$this->ensureDirectoryExists($this->directory, $this->create);
$this->initialized = true;
}
protected function ensureDirectoryExists($directory, $create)
{
$pwd = $this->sftp->pwd();
if ($this->sftp->chdir($directory)) {
$this->sftp->chdir($pwd);
} elseif ($create) {
if (!$this->sftp->mkdir($directory, 0777, true)) {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist and could not be created (%s).', $this->directory, $this->sftp->getLastSFTPError()));
}
} else {
throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $this->directory));
}
}
protected function computePath($key)
{
return $this->directory . '/' . ltrim($key, '/');
}
protected function fetchKeys($directory = '', $onlyKeys = true)
{
$keys = ['keys' => [], 'dirs' => []];
$computedPath = $this->computePath($directory);
if (!$this->sftp->file_exists($computedPath)) {
return $keys;
}
$list = $this->sftp->rawlist($computedPath);
foreach ((array) $list as $filename => $stat) {
if ('.' === $filename || '..' === $filename) {
continue;
}
$path = ltrim($directory . '/' . $filename, '/');
if (isset($stat['type']) && $stat['type'] === NET_SFTP_TYPE_DIRECTORY) {
$keys['dirs'][] = $path;
} else {
$keys['keys'][] = $path;
}
}
$dirs = $keys['dirs'];
if ($onlyKeys && !empty($dirs)) {
$keys['keys'] = array_merge($keys['keys'], $dirs);
$keys['dirs'] = [];
}
foreach ($dirs as $dir) {
$keys = array_merge_recursive($keys, $this->fetchKeys($dir, $onlyKeys));
}
return $keys;
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Gaufrette\Adapter;
/**
* Safe local adapter that encodes key to avoid the use of the directories
* structure.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class SafeLocal extends Local
{
/**
* {@inheritdoc}
*/
public function computeKey($path)
{
return base64_decode(parent::computeKey($path));
}
/**
* {@inheritdoc}
*/
protected function computePath($key)
{
return parent::computePath(base64_encode($key));
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface which add size calculation support to adapter.
*
* @author Markus Poerschke <markus@eluceo.de>
*/
interface SizeCalculator
{
/**
* Returns the size of the specified key.
*
* @param string $key
*
* @return int
*/
public function size($key);
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Gaufrette\Adapter;
/**
* Interface for the stream creation class.
*
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
interface StreamFactory
{
/**
* Creates a new stream instance of the specified file.
*
* @param string $key
*
* @return \Gaufrette\Stream
*/
public function createStream($key);
}

View File

@@ -0,0 +1,231 @@
<?php
namespace Gaufrette\Adapter;
use ZipArchive;
use Gaufrette\Adapter;
use Gaufrette\Util;
/**
* ZIP Archive adapter.
*
* @author Boris Guéry <guery.b@gmail.com>
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Zip implements Adapter
{
/**
* @var string The zip archive full path
*/
protected $zipFile;
/**
* @var ZipArchive
*/
protected $zipArchive;
public function __construct($zipFile)
{
if (!extension_loaded('zip')) {
throw new \RuntimeException(sprintf('Unable to use %s as the ZIP extension is not available.', __CLASS__));
}
$this->zipFile = $zipFile;
$this->reinitZipArchive();
}
/**
* {@inheritdoc}
*/
public function read($key)
{
if (false === ($content = $this->zipArchive->getFromName($key, 0))) {
return false;
}
return $content;
}
/**
* {@inheritdoc}
*/
public function write($key, $content)
{
if (!$this->zipArchive->addFromString($key, $content)) {
return false;
}
if (!$this->save()) {
return false;
}
return Util\Size::fromContent($content);
}
/**
* {@inheritdoc}
*/
public function exists($key)
{
return (boolean) $this->getStat($key);
}
/**
* {@inheritdoc}
*/
public function keys()
{
$keys = [];
for ($i = 0; $i < $this->zipArchive->numFiles; ++$i) {
$keys[$i] = $this->zipArchive->getNameIndex($i);
}
return $keys;
}
/**
* @todo implement
*
* {@inheritdoc}
*/
public function isDirectory($key)
{
return false;
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
$stat = $this->getStat($key);
return $stat['mtime'] ?? false;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
if (!$this->zipArchive->deleteName($key)) {
return false;
}
return $this->save();
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
if (!$this->zipArchive->renameName($sourceKey, $targetKey)) {
return false;
}
return $this->save();
}
/**
* Returns the stat of a file in the zip archive
* (name, index, crc, mtime, compression size, compression method, filesize).
*
* @param $key
*
* @return array|bool
*/
public function getStat($key)
{
$stat = $this->zipArchive->statName($key);
if (false === $stat) {
return [];
}
return $stat;
}
public function __destruct()
{
if ($this->zipArchive) {
try {
$this->zipArchive->close();
} catch (\Exception $e) {
}
unset($this->zipArchive);
}
}
protected function reinitZipArchive()
{
$this->zipArchive = new ZipArchive();
if (true !== ($resultCode = $this->zipArchive->open($this->zipFile, ZipArchive::CREATE))) {
switch ($resultCode) {
case ZipArchive::ER_EXISTS:
$errMsg = 'File already exists.';
break;
case ZipArchive::ER_INCONS:
$errMsg = 'Zip archive inconsistent.';
break;
case ZipArchive::ER_INVAL:
$errMsg = 'Invalid argument.';
break;
case ZipArchive::ER_MEMORY:
$errMsg = 'Malloc failure.';
break;
case ZipArchive::ER_NOENT:
$errMsg = 'Invalid argument.';
break;
case ZipArchive::ER_NOZIP:
$errMsg = 'Not a zip archive.';
break;
case ZipArchive::ER_OPEN:
$errMsg = 'Can\'t open file.';
break;
case ZipArchive::ER_READ:
$errMsg = 'Read error.';
break;
case ZipArchive::ER_SEEK:
$errMsg = 'Seek error.';
break;
default:
$errMsg = 'Unknown error.';
break;
}
throw new \RuntimeException(sprintf('%s', $errMsg));
}
return $this;
}
/**
* Saves archive modifications and updates current ZipArchive instance.
*
* @throws \RuntimeException If file could not be saved
*/
protected function save()
{
// Close to save modification
if (!$this->zipArchive->close()) {
return false;
}
// Re-initialize to get updated version
$this->reinitZipArchive();
return true;
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace Gaufrette;
/**
* Interface for the Gaufrette related exceptions.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
interface Exception
{
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Gaufrette\Exception;
use Gaufrette\Exception;
/**
* Exception to be thrown when a file already exists.
*
* @author Benjamin Dulau <benjamin.dulau@gmail.com>
*/
class FileAlreadyExists extends \RuntimeException implements Exception
{
private $key;
public function __construct($key, $code = 0, \Exception $previous = null)
{
$this->key = $key;
parent::__construct(
sprintf('The file %s already exists and can not be overwritten.', $key),
$code,
$previous
);
}
public function getKey()
{
return $this->key;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Gaufrette\Exception;
use Gaufrette\Exception;
/**
* Exception to be thrown when a file was not found.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class FileNotFound extends \RuntimeException implements Exception
{
private $key;
public function __construct($key, $code = 0, \Exception $previous = null)
{
$this->key = $key;
parent::__construct(
sprintf('The file "%s" was not found.', $key),
$code,
$previous
);
}
public function getKey()
{
return $this->key;
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Gaufrette\Exception;
use Gaufrette\Exception;
/**
* Exception to be thrown when an unexpected file exists.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class UnexpectedFile extends \RuntimeException implements Exception
{
private $key;
public function __construct($key, $code = 0, \Exception $previous = null)
{
$this->key = $key;
parent::__construct(
sprintf('The file "%s" was not supposed to exist.', $key),
$code,
$previous
);
}
public function getKey()
{
return $this->key;
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace Gaufrette\Exception;
use Gaufrette\Exception;
class UnsupportedAdapterMethodException extends \BadMethodCallException implements Exception
{
}

View File

@@ -0,0 +1,233 @@
<?php
namespace Gaufrette;
use Gaufrette\Adapter\MetadataSupporter;
use Gaufrette\Exception\FileNotFound;
/**
* Points to a file in a filesystem.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class File
{
protected $key;
protected $filesystem;
/**
* Content variable is lazy. It will not be read from filesystem until it's requested first time.
*
* @var mixed content
*/
protected $content = null;
/**
* @var array metadata in associative array. Only for adapters that support metadata
*/
protected $metadata = null;
/**
* Human readable filename (usually the end of the key).
*
* @var string name
*/
protected $name = null;
/**
* File size in bytes.
*
* @var int size
*/
protected $size = 0;
/**
* File date modified.
*
* @var int mtime
*/
protected $mtime = null;
/**
* @param string $key
* @param FilesystemInterface $filesystem
*/
public function __construct($key, FilesystemInterface $filesystem)
{
$this->key = $key;
$this->name = $key;
$this->filesystem = $filesystem;
}
/**
* Returns the key.
*
* @return string
*/
public function getKey()
{
return $this->key;
}
/**
* Returns the content.
*
* @throws FileNotFound
*
* @param array $metadata optional metadata which should be set when read
*
* @return string
*/
public function getContent($metadata = [])
{
if (isset($this->content)) {
return $this->content;
}
$this->setMetadata($metadata);
return $this->content = $this->filesystem->read($this->key);
}
/**
* @return string name of the file
*/
public function getName()
{
return $this->name;
}
/**
* @return int size of the file
*/
public function getSize()
{
if ($this->size) {
return $this->size;
}
try {
return $this->size = $this->filesystem->size($this->getKey());
} catch (FileNotFound $exception) {
}
return 0;
}
/**
* Returns the file modified time.
*
* @return int
*/
public function getMtime()
{
return $this->mtime = $this->filesystem->mtime($this->key);
}
/**
* @param int $size size of the file
*/
public function setSize($size)
{
$this->size = $size;
}
/**
* Sets the content.
*
* @param string $content
* @param array $metadata optional metadata which should be send when write
*
* @return int The number of bytes that were written into the file, or
* FALSE on failure
*/
public function setContent($content, $metadata = [])
{
$this->content = $content;
$this->setMetadata($metadata);
return $this->size = $this->filesystem->write($this->key, $this->content, true);
}
/**
* @param string $name name of the file
*/
public function setName($name)
{
$this->name = $name;
}
/**
* Indicates whether the file exists in the filesystem.
*
* @return bool
*/
public function exists()
{
return $this->filesystem->has($this->key);
}
/**
* Deletes the file from the filesystem.
*
* @throws FileNotFound
* @throws \RuntimeException when cannot delete file
*
* @param array $metadata optional metadata which should be send when write
*
* @return bool TRUE on success
*/
public function delete($metadata = [])
{
$this->setMetadata($metadata);
return $this->filesystem->delete($this->key);
}
/**
* Creates a new file stream instance of the file.
*
* @return Stream
*/
public function createStream()
{
return $this->filesystem->createStream($this->key);
}
/**
* Rename the file and move it to its new location.
*
* @param string $newKey
*/
public function rename($newKey)
{
$this->filesystem->rename($this->key, $newKey);
$this->key = $newKey;
}
/**
* Sets the metadata array to be stored in adapters that can support it.
*
* @param array $metadata
*
* @return bool
*/
protected function setMetadata(array $metadata)
{
if ($metadata && $this->supportsMetadata()) {
$this->filesystem->getAdapter()->setMetadata($this->key, $metadata);
return true;
}
return false;
}
/**
* @return bool
*/
private function supportsMetadata()
{
return $this->filesystem->getAdapter() instanceof MetadataSupporter;
}
}

View File

@@ -0,0 +1,347 @@
<?php
namespace Gaufrette;
use Gaufrette\Adapter\ListKeysAware;
/**
* A filesystem is used to store and retrieve files.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class Filesystem implements FilesystemInterface
{
protected $adapter;
/**
* Contains File objects created with $this->createFile() method.
*
* @var array
*/
protected $fileRegister = [];
/**
* @param Adapter $adapter A configured Adapter instance
*/
public function __construct(Adapter $adapter)
{
$this->adapter = $adapter;
}
/**
* Returns the adapter.
*
* @return Adapter
*/
public function getAdapter()
{
return $this->adapter;
}
/**
* {@inheritdoc}
*/
public function has($key)
{
self::assertValidKey($key);
return $this->adapter->exists($key);
}
/**
* {@inheritdoc}
*/
public function rename($sourceKey, $targetKey)
{
self::assertValidKey($sourceKey);
self::assertValidKey($targetKey);
$this->assertHasFile($sourceKey);
if ($this->has($targetKey)) {
throw new Exception\UnexpectedFile($targetKey);
}
if (!$this->adapter->rename($sourceKey, $targetKey)) {
throw new \RuntimeException(sprintf('Could not rename the "%s" key to "%s".', $sourceKey, $targetKey));
}
if ($this->isFileInRegister($sourceKey)) {
$this->fileRegister[$targetKey] = $this->fileRegister[$sourceKey];
unset($this->fileRegister[$sourceKey]);
}
return true;
}
/**
* {@inheritdoc}
*/
public function get($key, $create = false)
{
self::assertValidKey($key);
if (!$create) {
$this->assertHasFile($key);
}
return $this->createFile($key);
}
/**
* {@inheritdoc}
*/
public function write($key, $content, $overwrite = false)
{
self::assertValidKey($key);
if (!$overwrite && $this->has($key)) {
throw new Exception\FileAlreadyExists($key);
}
$numBytes = $this->adapter->write($key, $content);
if (false === $numBytes) {
throw new \RuntimeException(sprintf('Could not write the "%s" key content.', $key));
}
return $numBytes;
}
/**
* {@inheritdoc}
*/
public function read($key)
{
self::assertValidKey($key);
$this->assertHasFile($key);
$content = $this->adapter->read($key);
if (false === $content) {
throw new \RuntimeException(sprintf('Could not read the "%s" key content.', $key));
}
return $content;
}
/**
* {@inheritdoc}
*/
public function delete($key)
{
self::assertValidKey($key);
$this->assertHasFile($key);
if ($this->adapter->delete($key)) {
$this->removeFromRegister($key);
return true;
}
throw new \RuntimeException(sprintf('Could not remove the "%s" key.', $key));
}
/**
* {@inheritdoc}
*/
public function keys()
{
return $this->adapter->keys();
}
/**
* {@inheritdoc}
*/
public function listKeys($prefix = '')
{
if ($this->adapter instanceof ListKeysAware) {
return $this->adapter->listKeys($prefix);
}
$dirs = [];
$keys = [];
foreach ($this->keys() as $key) {
if (empty($prefix) || 0 === strpos($key, $prefix)) {
if ($this->adapter->isDirectory($key)) {
$dirs[] = $key;
} else {
$keys[] = $key;
}
}
}
return [
'keys' => $keys,
'dirs' => $dirs,
];
}
/**
* {@inheritdoc}
*/
public function mtime($key)
{
self::assertValidKey($key);
$this->assertHasFile($key);
return $this->adapter->mtime($key);
}
/**
* {@inheritdoc}
*/
public function checksum($key)
{
self::assertValidKey($key);
$this->assertHasFile($key);
if ($this->adapter instanceof Adapter\ChecksumCalculator) {
return $this->adapter->checksum($key);
}
return Util\Checksum::fromContent($this->read($key));
}
/**
* {@inheritdoc}
*/
public function size($key)
{
self::assertValidKey($key);
$this->assertHasFile($key);
if ($this->adapter instanceof Adapter\SizeCalculator) {
return $this->adapter->size($key);
}
return Util\Size::fromContent($this->read($key));
}
/**
* {@inheritdoc}
*/
public function createStream($key)
{
self::assertValidKey($key);
if ($this->adapter instanceof Adapter\StreamFactory) {
return $this->adapter->createStream($key);
}
return new Stream\InMemoryBuffer($this, $key);
}
/**
* {@inheritdoc}
*/
public function createFile($key)
{
self::assertValidKey($key);
if (false === $this->isFileInRegister($key)) {
if ($this->adapter instanceof Adapter\FileFactory) {
$this->fileRegister[$key] = $this->adapter->createFile($key, $this);
} else {
$this->fileRegister[$key] = new File($key, $this);
}
}
return $this->fileRegister[$key];
}
/**
* {@inheritdoc}
*/
public function mimeType($key)
{
self::assertValidKey($key);
$this->assertHasFile($key);
if ($this->adapter instanceof Adapter\MimeTypeProvider) {
return $this->adapter->mimeType($key);
}
throw new \LogicException(sprintf(
'Adapter "%s" cannot provide MIME type',
get_class($this->adapter)
));
}
/**
* Checks if matching file by given key exists in the filesystem.
*
* Key must be non empty string, otherwise it will throw Exception\FileNotFound
* {@see http://php.net/manual/en/function.empty.php}
*
* @param string $key
*
* @throws Exception\FileNotFound when sourceKey does not exist
*/
private function assertHasFile($key)
{
if (!$this->has($key)) {
throw new Exception\FileNotFound($key);
}
}
/**
* Checks if matching File object by given key exists in the fileRegister.
*
* @param string $key
*
* @return bool
*/
private function isFileInRegister($key)
{
return array_key_exists($key, $this->fileRegister);
}
/**
* Clear files register.
*/
public function clearFileRegister()
{
$this->fileRegister = [];
}
/**
* Removes File object from register.
*
* @param string $key
*/
public function removeFromRegister($key)
{
if ($this->isFileInRegister($key)) {
unset($this->fileRegister[$key]);
}
}
/**
* {@inheritdoc}
*/
public function isDirectory($key)
{
return $this->adapter->isDirectory($key);
}
/**
* @param string $key
*
* @throws \InvalidArgumentException Given $key should not be empty
*/
private static function assertValidKey($key)
{
if (empty($key)) {
throw new \InvalidArgumentException('Object path is empty.');
}
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace Gaufrette;
interface FilesystemInterface
{
/**
* Indicates whether the file matching the specified key exists.
*
* @param string $key
*
* @return bool TRUE if the file exists, FALSE otherwise
*
* @throws \InvalidArgumentException If $key is invalid
*/
public function has($key);
/**
* Renames a file.
*
* File::rename should be preferred or you may face bad filesystem consistency.
*
* @param string $sourceKey
* @param string $targetKey
*
* @return bool TRUE if the rename was successful
*
* @throws Exception\FileNotFound when sourceKey does not exist
* @throws Exception\UnexpectedFile when targetKey exists
* @throws \RuntimeException when cannot rename
* @throws \InvalidArgumentException If $sourceKey or $targetKey are invalid
*
* @see File::rename()
*/
public function rename($sourceKey, $targetKey);
/**
* Returns the file matching the specified key.
*
* @param string $key Key of the file
* @param bool $create Whether to create the file if it does not exist
*
* @throws Exception\FileNotFound
* @throws \InvalidArgumentException If $key is invalid
*
* @return File
*/
public function get($key, $create = false);
/**
* Writes the given content into the file.
*
* @param string $key Key of the file
* @param string $content Content to write in the file
* @param bool $overwrite Whether to overwrite the file if exists
*
* @throws Exception\FileAlreadyExists When file already exists and overwrite is false
* @throws \RuntimeException When for any reason content could not be written
* @throws \InvalidArgumentException If $key is invalid
*
* @return int The number of bytes that were written into the file
*/
public function write($key, $content, $overwrite = false);
/**
* Reads the content from the file.
*
* @param string $key Key of the file
*
* @throws Exception\FileNotFound when file does not exist
* @throws \RuntimeException when cannot read file
* @throws \InvalidArgumentException If $key is invalid
*
* @return string
*/
public function read($key);
/**
* Deletes the file matching the specified key.
*
* @param string $key
*
* @throws \RuntimeException when cannot read file
* @throws \InvalidArgumentException If $key is invalid
*
* @return bool
*/
public function delete($key);
/**
* Returns an array of all keys.
*
* @return array
*/
public function keys();
/**
* Lists keys beginning with given prefix
* (no wildcard / regex matching).
*
* if adapter implements ListKeysAware interface, adapter's implementation will be used,
* in not, ALL keys will be requested and iterated through.
*
* @param string $prefix
*
* @return array
*/
public function listKeys($prefix = '');
/**
* Returns the last modified time of the specified file.
*
* @param string $key
*
* @return int An UNIX like timestamp
*
* @throws \InvalidArgumentException If $key is invalid
*/
public function mtime($key);
/**
* Returns the checksum of the specified file's content.
*
* @param string $key
*
* @return string A MD5 hash
*
* @throws \InvalidArgumentException If $key is invalid
*/
public function checksum($key);
/**
* Returns the size of the specified file's content.
*
* @param string $key
*
* @return int File size in Bytes
*
* @throws \InvalidArgumentException If $key is invalid
*/
public function size($key);
/**
* Gets a new stream instance of the specified file.
*
* @param $key
*
* @return Stream|Stream\InMemoryBuffer
*
* @throws \InvalidArgumentException If $key is invalid
*/
public function createStream($key);
/**
* Creates a new file in a filesystem.
*
* @param $key
*
* @return File
*
* @throws \InvalidArgumentException If $key is invalid
*/
public function createFile($key);
/**
* Get the mime type of the provided key.
*
* @param string $key
*
* @return string
*
* @throws \InvalidArgumentException If $key is invalid
*/
public function mimeType($key);
/**
* @param string $key
*
* @return bool
*/
public function isDirectory($key);
}

View File

@@ -0,0 +1,93 @@
<?php
namespace Gaufrette;
/**
* Associates filesystem instances to their names.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class FilesystemMap implements FilesystemMapInterface
{
private $filesystems = [];
/**
* Returns an array of all the registered filesystems where the key is the
* name and the value the filesystem.
*
* @return array
*/
public function all()
{
return $this->filesystems;
}
/**
* Register the given filesystem for the specified name.
*
* @param string $name
* @param FilesystemInterface $filesystem
*
* @throws \InvalidArgumentException when the specified name contains
* forbidden characters
*/
public function set($name, FilesystemInterface $filesystem)
{
if (!preg_match('/^[-_a-zA-Z0-9]+$/', $name)) {
throw new \InvalidArgumentException(sprintf(
'The specified name "%s" is not valid.',
$name
));
}
$this->filesystems[$name] = $filesystem;
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return isset($this->filesystems[$name]);
}
/**
* {@inheritdoc}
*/
public function get($name)
{
if (!$this->has($name)) {
throw new \InvalidArgumentException(sprintf(
'There is no filesystem defined having "%s" name.',
$name
));
}
return $this->filesystems[$name];
}
/**
* Removes the filesystem registered for the specified name.
*
* @param string $name
*/
public function remove($name)
{
if (!$this->has($name)) {
throw new \InvalidArgumentException(sprintf(
'Cannot remove the "%s" filesystem as it is not defined.',
$name
));
}
unset($this->filesystems[$name]);
}
/**
* Clears all the registered filesystems.
*/
public function clear()
{
$this->filesystems = [];
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Gaufrette;
/**
* Associates filesystem instances to their names.
*/
interface FilesystemMapInterface
{
/**
* Indicates whether there is a filesystem registered for the specified
* name.
*
* @param string $name
*
* @return bool
*/
public function has($name);
/**
* Returns the filesystem registered for the specified name.
*
* @param string $name
*
* @return FilesystemInterface
*
* @throw \InvalidArgumentException when there is no filesystem registered
* for the specified name
*/
public function get($name);
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Gaufrette;
/**
* Interface for the file streams.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
interface Stream
{
/**
* Opens the stream in the specified mode.
*
* @param StreamMode $mode
*
* @return bool TRUE on success or FALSE on failure
*/
public function open(StreamMode $mode);
/**
* Reads the specified number of bytes from the current position.
*
* If the current position is the end-of-file, you must return an empty
* string.
*
* @param int $count The number of bytes
*
* @return string
*/
public function read($count);
/**
* Writes the specified data.
*
* Don't forget to update the current position of the stream by number of
* bytes that were successfully written.
*
* @param string $data
*
* @return int The number of bytes that were successfully written
*/
public function write($data);
/**
* Closes the stream.
*
* It must free all the resources. If there is any data to flush, you
* should do so
*/
public function close();
/**
* Flushes the output.
*
* If you have cached data that is not yet stored into the underlying
* storage, you should do so now
*
* @return bool TRUE on success or FALSE on failure
*/
public function flush();
/**
* Seeks to the specified offset.
*
* @param int $offset
* @param int $whence
*
* @return bool
*/
public function seek($offset, $whence = SEEK_SET);
/**
* Returns the current position.
*
* @return int
*/
public function tell();
/**
* Indicates whether the current position is the end-of-file.
*
* @return bool
*/
public function eof();
/**
* Gathers statistics of the stream.
*
* @return array
*/
public function stat();
/**
* Retrieve the underlying resource.
*
* @param int $castAs
*
* @return mixed using resource or false
*/
public function cast($castAs);
/**
* Delete a file.
*
* @return bool TRUE on success FALSE otherwise
*/
public function unlink();
}

View File

@@ -0,0 +1,227 @@
<?php
namespace Gaufrette\Stream;
use Gaufrette\Stream;
use Gaufrette\Filesystem;
use Gaufrette\StreamMode;
use Gaufrette\Util;
class InMemoryBuffer implements Stream
{
private $filesystem;
private $key;
private $mode;
private $content;
private $numBytes;
private $position;
private $synchronized;
/**
* @param Filesystem $filesystem The filesystem managing the file to stream
* @param string $key The file key
*/
public function __construct(Filesystem $filesystem, $key)
{
$this->filesystem = $filesystem;
$this->key = $key;
}
/**
* {@inheritdoc}
*/
public function open(StreamMode $mode)
{
$this->mode = $mode;
$exists = $this->filesystem->has($this->key);
if (($exists && !$mode->allowsExistingFileOpening())
|| (!$exists && !$mode->allowsNewFileOpening())) {
return false;
}
if ($mode->impliesExistingContentDeletion()) {
$this->content = $this->writeContent('');
} elseif (!$exists && $mode->allowsNewFileOpening()) {
$this->content = $this->writeContent('');
} else {
$this->content = $this->filesystem->read($this->key);
}
$this->numBytes = Util\Size::fromContent($this->content);
$this->position = $mode->impliesPositioningCursorAtTheEnd() ? $this->numBytes : 0;
$this->synchronized = true;
return true;
}
public function read($count)
{
if (false === $this->mode->allowsRead()) {
throw new \LogicException('The stream does not allow read.');
}
$chunk = substr($this->content, $this->position, $count);
$this->position += Util\Size::fromContent($chunk);
return $chunk;
}
public function write($data)
{
if (false === $this->mode->allowsWrite()) {
throw new \LogicException('The stream does not allow write.');
}
$numWrittenBytes = Util\Size::fromContent($data);
$newPosition = $this->position + $numWrittenBytes;
$newNumBytes = $newPosition > $this->numBytes ? $newPosition : $this->numBytes;
if ($this->eof()) {
$this->numBytes += $numWrittenBytes;
if ($this->hasNewContentAtFurtherPosition()) {
$data = str_pad($data, $this->position + strlen($data), ' ', STR_PAD_LEFT);
}
$this->content .= $data;
} else {
$before = substr($this->content, 0, $this->position);
$after = $newNumBytes > $newPosition ? substr($this->content, $newPosition) : '';
$this->content = $before . $data . $after;
}
$this->position = $newPosition;
$this->numBytes = $newNumBytes;
$this->synchronized = false;
return $numWrittenBytes;
}
public function close()
{
if (!$this->synchronized) {
$this->flush();
}
}
public function seek($offset, $whence = SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
$this->position = $offset;
break;
case SEEK_CUR:
$this->position += $offset;
break;
case SEEK_END:
$this->position = $this->numBytes + $offset;
break;
default:
return false;
}
return true;
}
public function tell()
{
return $this->position;
}
public function flush()
{
if ($this->synchronized) {
return true;
}
try {
$this->writeContent($this->content);
} catch (\Exception $e) {
return false;
}
return true;
}
public function eof()
{
return $this->position >= $this->numBytes;
}
/**
* {@inheritdoc}
*/
public function stat()
{
if ($this->filesystem->has($this->key)) {
$isDirectory = $this->filesystem->isDirectory($this->key);
$time = $this->filesystem->mtime($this->key);
$stats = [
'dev' => 1,
'ino' => 0,
'mode' => $isDirectory ? 16893 : 33204,
'nlink' => 1,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $isDirectory ? 0 : Util\Size::fromContent($this->content),
'atime' => $time,
'mtime' => $time,
'ctime' => $time,
'blksize' => -1,
'blocks' => -1,
];
return array_merge(array_values($stats), $stats);
}
return false;
}
/**
* {@inheritdoc}
*/
public function cast($castAst)
{
return false;
}
/**
* {@inheritdoc}
*/
public function unlink()
{
if ($this->mode && $this->mode->impliesExistingContentDeletion()) {
return $this->filesystem->delete($this->key);
}
return false;
}
/**
* @return bool
*/
protected function hasNewContentAtFurtherPosition()
{
return $this->position > 0 && !$this->content;
}
/**
* @param string $content Empty string by default
* @param bool $overwrite Overwrite by default
*
* @return string
*/
protected function writeContent($content = '', $overwrite = true)
{
$this->filesystem->write($this->key, $content, $overwrite);
return $content;
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace Gaufrette\Stream;
use Gaufrette\Stream;
use Gaufrette\StreamMode;
/**
* Local stream.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Local implements Stream
{
private $path;
private $mode;
private $fileHandle;
private $mkdirMode;
/**
* @param string $path
* @param int $mkdirMode
*/
public function __construct($path, $mkdirMode = 0755)
{
$this->path = $path;
$this->mkdirMode = $mkdirMode;
}
/**
* {@inheritdoc}
*/
public function open(StreamMode $mode)
{
$baseDirPath = \Gaufrette\Util\Path::dirname($this->path);
if ($mode->allowsWrite() && !is_dir($baseDirPath)) {
@mkdir($baseDirPath, $this->mkdirMode, true);
}
try {
$fileHandle = @fopen($this->path, $mode->getMode());
} catch (\Exception $e) {
$fileHandle = false;
}
if (false === $fileHandle) {
throw new \RuntimeException(sprintf('File "%s" cannot be opened', $this->path));
}
$this->mode = $mode;
$this->fileHandle = $fileHandle;
return true;
}
/**
* {@inheritdoc}
*/
public function read($count)
{
if (!$this->fileHandle) {
return false;
}
if (false === $this->mode->allowsRead()) {
throw new \LogicException('The stream does not allow read.');
}
return fread($this->fileHandle, $count);
}
/**
* {@inheritdoc}
*/
public function write($data)
{
if (!$this->fileHandle) {
return false;
}
if (false === $this->mode->allowsWrite()) {
throw new \LogicException('The stream does not allow write.');
}
return fwrite($this->fileHandle, $data);
}
/**
* {@inheritdoc}
*/
public function close()
{
if (!$this->fileHandle) {
return false;
}
$closed = fclose($this->fileHandle);
if ($closed) {
$this->mode = null;
$this->fileHandle = null;
}
return $closed;
}
/**
* {@inheritdoc}
*/
public function flush()
{
if ($this->fileHandle) {
return fflush($this->fileHandle);
}
return false;
}
/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
if ($this->fileHandle) {
return 0 === fseek($this->fileHandle, $offset, $whence);
}
return false;
}
/**
* {@inheritdoc}
*/
public function tell()
{
if ($this->fileHandle) {
return ftell($this->fileHandle);
}
return false;
}
/**
* {@inheritdoc}
*/
public function eof()
{
if ($this->fileHandle) {
return feof($this->fileHandle);
}
return true;
}
/**
* {@inheritdoc}
*/
public function stat()
{
if ($this->fileHandle) {
return fstat($this->fileHandle);
} elseif (!is_resource($this->fileHandle) && is_dir($this->path)) {
return stat($this->path);
}
return false;
}
/**
* {@inheritdoc}
*/
public function cast($castAs)
{
if ($this->fileHandle) {
return $this->fileHandle;
}
return false;
}
/**
* {@inheritdoc}
*/
public function unlink()
{
if ($this->mode && $this->mode->impliesExistingContentDeletion()) {
return @unlink($this->path);
}
return false;
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace Gaufrette;
/**
* Represents a stream mode.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class StreamMode
{
private $mode;
private $base;
private $plus;
private $flag;
/**
* @param string $mode A stream mode as for the use of fopen()
*/
public function __construct($mode)
{
$this->mode = $mode;
$mode = substr($mode, 0, 3);
$rest = substr($mode, 1);
$this->base = substr($mode, 0, 1);
$this->plus = false !== strpos($rest, '+');
$this->flag = trim($rest, '+');
}
/**
* Returns the underlying mode.
*
* @return string
*/
public function getMode()
{
return $this->mode;
}
/**
* Indicates whether the mode allows to read.
*
* @return bool
*/
public function allowsRead()
{
if ($this->plus) {
return true;
}
return 'r' === $this->base;
}
/**
* Indicates whether the mode allows to write.
*
* @return bool
*/
public function allowsWrite()
{
if ($this->plus) {
return true;
}
return 'r' !== $this->base;
}
/**
* Indicates whether the mode allows to open an existing file.
*
* @return bool
*/
public function allowsExistingFileOpening()
{
return 'x' !== $this->base;
}
/**
* Indicates whether the mode allows to create a new file.
*
* @return bool
*/
public function allowsNewFileOpening()
{
return 'r' !== $this->base;
}
/**
* Indicates whether the mode implies to delete the existing content of the
* file when it already exists.
*
* @return bool
*/
public function impliesExistingContentDeletion()
{
return 'w' === $this->base;
}
/**
* Indicates whether the mode implies positioning the cursor at the
* beginning of the file.
*
* @return bool
*/
public function impliesPositioningCursorAtTheBeginning()
{
return 'a' !== $this->base;
}
/**
* Indicates whether the mode implies positioning the cursor at the end of
* the file.
*
* @return bool
*/
public function impliesPositioningCursorAtTheEnd()
{
return 'a' === $this->base;
}
/**
* Indicates whether the stream is in binary mode.
*
* @return bool
*/
public function isBinary()
{
return 'b' === $this->flag;
}
/**
* Indicates whether the stream is in text mode.
*
* @return bool
*/
public function isText()
{
return false === $this->isBinary();
}
}

View File

@@ -0,0 +1,281 @@
<?php
namespace Gaufrette;
/**
* Stream wrapper class for the Gaufrette filesystems.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
* @author Leszek Prabucki <leszek.prabucki@gmail.com>
*/
class StreamWrapper
{
private static $filesystemMap;
private $stream;
/**
* Defines the filesystem map.
*
* @param FilesystemMap $map
*/
public static function setFilesystemMap(FilesystemMap $map)
{
self::$filesystemMap = $map;
}
/**
* Returns the filesystem map.
*
* @return FilesystemMap $map
*/
public static function getFilesystemMap()
{
if (null === self::$filesystemMap) {
self::$filesystemMap = self::createFilesystemMap();
}
return self::$filesystemMap;
}
/**
* Registers the stream wrapper to handle the specified scheme.
*
* @param string $scheme Default is gaufrette
*/
public static function register($scheme = 'gaufrette')
{
self::streamWrapperUnregister($scheme);
if (!self::streamWrapperRegister($scheme, __CLASS__)) {
throw new \RuntimeException(sprintf(
'Could not register stream wrapper class %s for scheme %s.',
__CLASS__,
$scheme
));
}
}
/**
* @return FilesystemMap
*/
protected static function createFilesystemMap()
{
return new FilesystemMap();
}
/**
* @param string $scheme - protocol scheme
*/
protected static function streamWrapperUnregister($scheme)
{
if (in_array($scheme, stream_get_wrappers())) {
return stream_wrapper_unregister($scheme);
}
}
/**
* @param string $scheme - protocol scheme
* @param string $className
*
* @return bool
*/
protected static function streamWrapperRegister($scheme, $className)
{
return stream_wrapper_register($scheme, $className);
}
public function stream_open($path, $mode)
{
$this->stream = $this->createStream($path);
return $this->stream->open($this->createStreamMode($mode));
}
/**
* @param int $bytes
*
* @return mixed
*/
public function stream_read($bytes)
{
if ($this->stream) {
return $this->stream->read($bytes);
}
return false;
}
/**
* @param string $data
*
* @return int
*/
public function stream_write($data)
{
if ($this->stream) {
return $this->stream->write($data);
}
return 0;
}
public function stream_close()
{
if ($this->stream) {
$this->stream->close();
}
}
/**
* @return bool
*/
public function stream_flush()
{
if ($this->stream) {
return $this->stream->flush();
}
return false;
}
/**
* @param int $offset
* @param int $whence - one of values [SEEK_SET, SEEK_CUR, SEEK_END]
*
* @return bool
*/
public function stream_seek($offset, $whence = SEEK_SET)
{
if ($this->stream) {
return $this->stream->seek($offset, $whence);
}
return false;
}
/**
* @return mixed
*/
public function stream_tell()
{
if ($this->stream) {
return $this->stream->tell();
}
return false;
}
/**
* @return bool
*/
public function stream_eof()
{
if ($this->stream) {
return $this->stream->eof();
}
return true;
}
/**
* @return mixed
*/
public function stream_stat()
{
if ($this->stream) {
return $this->stream->stat();
}
return false;
}
/**
* @param string $path
* @param int $flags
*
* @return mixed
*
* @todo handle $flags parameter
*/
public function url_stat($path, $flags)
{
$stream = $this->createStream($path);
try {
$stream->open($this->createStreamMode('r+'));
} catch (\RuntimeException $e) {
}
return $stream->stat();
}
/**
* @param string $path
*
* @return mixed
*/
public function unlink($path)
{
$stream = $this->createStream($path);
try {
$stream->open($this->createStreamMode('w+'));
} catch (\RuntimeException $e) {
return false;
}
return $stream->unlink();
}
/**
* @return mixed
*/
public function stream_cast($castAs)
{
if ($this->stream) {
return $this->stream->cast($castAs);
}
return false;
}
protected function createStream($path)
{
$parts = array_merge(
[
'scheme' => null,
'host' => null,
'path' => null,
'query' => null,
'fragment' => null,
],
parse_url($path) ?: []
);
$domain = $parts['host'];
$key = !empty($parts['path']) ? substr($parts['path'], 1) : '';
if (null !== $parts['query']) {
$key .= '?' . $parts['query'];
}
if (null !== $parts['fragment']) {
$key .= '#' . $parts['fragment'];
}
if (empty($domain) || empty($key)) {
throw new \InvalidArgumentException(sprintf(
'The specified path (%s) is invalid.',
$path
));
}
return self::getFilesystemMap()->get($domain)->createStream($key);
}
protected function createStreamMode($mode)
{
return new StreamMode($mode);
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Gaufrette\Util;
/**
* Checksum utils.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Checksum
{
/**
* Returns the checksum of the given content.
*
* @param string $content
*
* @return string
*/
public static function fromContent($content)
{
return md5($content);
}
/**
* Returns the checksum of the specified file.
*
* @param string $filename
*
* @return string
*/
public static function fromFile($filename)
{
return md5_file($filename);
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Gaufrette\Util;
/**
* Path utils.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Path
{
/**
* Normalizes the given path.
*
* @param string $path
*
* @return string
*/
public static function normalize($path)
{
$path = str_replace('\\', '/', $path);
$prefix = static::getAbsolutePrefix($path);
$path = substr($path, strlen($prefix));
$parts = array_filter(explode('/', $path), 'strlen');
$tokens = [];
foreach ($parts as $part) {
switch ($part) {
case '.':
continue 2;
case '..':
if (0 !== count($tokens)) {
array_pop($tokens);
continue 2;
} elseif (!empty($prefix)) {
continue 2;
}
default:
$tokens[] = $part;
}
}
return $prefix . implode('/', $tokens);
}
/**
* Indicates whether the given path is absolute or not.
*
* @param string $path A normalized path
*
* @return bool
*/
public static function isAbsolute($path)
{
return '' !== static::getAbsolutePrefix($path);
}
/**
* Returns the absolute prefix of the given path.
*
* @param string $path A normalized path
*
* @return string
*/
public static function getAbsolutePrefix($path)
{
preg_match('|^(?P<prefix>([a-zA-Z]+:)?//?)|', $path, $matches);
if (empty($matches['prefix'])) {
return '';
}
return strtolower($matches['prefix']);
}
/**
* Wrap native dirname function in order to handle only UNIX-style paths
*
* @param string $path
*
* @return string
*
* @see http://php.net/manual/en/function.dirname.php
*/
public static function dirname($path)
{
return str_replace('\\', '/', \dirname($path));
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Gaufrette\Util;
/**
* Utility class for file sizes.
*
* @author Antoine Hérault <antoine.herault@gmail.com>
*/
class Size
{
/**
* Returns the size in bytes from the given content.
*
* @param string $content
*
* @return int
*
* @todo handle the case the mbstring is not loaded
*/
public static function fromContent($content)
{
// Make sure to get the real length in byte and not
// accidentally mistake some bytes as a UTF BOM.
return mb_strlen($content, '8bit');
}
/**
* Returns the size in bytes from the given file.
*
* @param string $filename
*
* @return int
*/
public static function fromFile($filename)
{
return filesize($filename);
}
/**
* Returns the size in bytes from the given resource.
*
* @param resource $handle
*
* @return string
*/
public static function fromResource($handle)
{
$cStat = fstat($handle);
// if the resource is a remote file, $cStat will be false
return $cStat ? $cStat['size'] : 0;
}
}