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,2 @@
# ignore all by default
*

22
vendor/knplabs/gaufrette/.env.dist vendored Normal file
View File

@@ -0,0 +1,22 @@
AWS_KEY=
AWS_SECRET=
AWS_BUCKET=
AZURE_ACCOUNT=
AZURE_KEY=
AZURE_CONTAINER=
FTP_HOST=ftp
FTP_PORT=21
FTP_USER=gaufrette
FTP_PASSWORD=gaufrette
FTP_BASE_DIR=/gaufrette
MONGO_URI=mongodb://mongodb:27017
MONGO_DBNAME=gridfs_test
SFTP_HOST=sftp
SFTP_PORT=22
SFTP_USER=gaufrette
SFTP_PASSWORD=gaufrette
SFTP_BASE_DIR=gaufrette

210
vendor/knplabs/gaufrette/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,210 @@
v0.11.1
=======
## Added
- gitattributes file to avoid download too much files on install with composer in #679
## Fixed
- Azure integraton with stream was not working properly, it has been fixed in #683
v0.11.0
=======
## Added
- Google Cloud Storage: options to automatically create bucket if not exists
- Support for PHP 8.0+
- Support for doctrine/dbal 3.x
## Updated
- Azure seems to have break compatibility, phpstan detected it and it's now fixed (#674)
## Removed
- Adapter for OpenCloud (#669)
v0.10.0
=======
## Added
- Support for AsyncAws
## Removed
The following adapters were deprecated and have now been removed.
- AclAwareAmazonS3
- AmazonS3
- Cache
- Dropbox
- LazyOpenCloud
- MogileFS
- Sftp
We also removed the deprecated function `AwsS3::getUrl()`.
v0.9.0
======
## New features
- Add MimeTypeProvider to AzureBlobStorage (#630)
## Fixes
- Fix docker setup for dev env (#580)
- Define env vars in .env file only (#615)
- Add PHP Coding Standard check step on CI (#617)
- PHPUnit tests enhancement (#627)
Thank you @nicolasmure, @peter279k and @rgembalik for your contributions!
v0.8.3
======
## Changes
- Change Filesystem to FilesystemInterface in File's constructor (#608)
Thank you @athlan for your contribution !
v0.8.2
======
## New features
- Declare a FilesystemMapInterface (#604)
- Add SizeCalculator support to GridFS (#603)
- Local Adapter: directory deletion (#610)
## Fixes
- GridFS : return empty array when object has no metadata (#609)
Thank you @athos7933, @bsperduto and @nicolasmure for your contributions !
v0.8.1
======
## Fixes
- Fix `rename` with `GoogleCloudStorage` adapter [#598](https://github.com/KnpLabs/Gaufrette/pull/598)
Thank you @jerome-arzel for your contribution !
v0.8
====
## New features
- Implement `ChecksumCalculator` interface for `AzureBlobStorage` adapter #594
## Changes
In #593 :
- Drop support for EOL php versions (5.6 and 7.0)
- Minimim requirement is now php 7.1
- Add support for php 7.3
## Fixes
- fix opencloud tests #579
- fix appveyor build #589
- Fix `ini_get()` for boolean values #595
Thank you @andreybolonin, @damijank, @deguif and @nicolasmure for your
contributions !
v0.7
====
## Changes
- `FilesystemMap::set()` should expect `FilesystemInterface` instead of
`Filesystem` #576
## Fixes
- Add PutObjectAcl in the required permission #566
- Ensure correct return type from Flysystem adapter "exists" method #572
Thank you @andreybolonin, @clement-michelet, @jakob-stoeck, @nicolasmure,
@teohhanhui, @tristanbes for your contributions !
v0.6
====
## Changes
- Add support for major release of Azure Blob Storage SDK [#558](https://github.com/KnpLabs/Gaufrette/pull/558)
## Fixes
- Fix Dockerfile for php 7.0 [aaa66dc](https://github.com/KnpLabs/Gaufrette/commit/aaa66dcf298d313e7ae3f525714923fcfd787e94)
- Fix appveyor build [#562](https://github.com/KnpLabs/Gaufrette/pull/562)
Thank you @nicolasmure, @NiR- and @z38 for your contributions!
v0.5
====
## New features
- Added support for calculated size for Azure Blob Storage #523
- GridFS Support for Metadata Retrieval after Write #535
## Changes
- Test case for AwsS3 now inherits common test case #514
- Run azure tests on appveyor #512
- Bump PHPUnit to ^5.6.8 #529
- Use composer's autoload-dev #530
- Drop HHVM support + sync docker conf with Travis #528
- Refactoring tests to have more detailed failure messages #542
## Fixes
- Documentation #510
- Typos #506, #538
- Fix incomplete tear down phase for AwsS3Test #516
- Fix FTP tests + bug in PhpseclibSftp::fetchKeys() #527
- fix travis build for php 5.6 #543
- Quickfix for Adapter/AwsS3, check if count() call is allowed #544
Thank you @andreasschacht, @bsperduto, @carusogabriel, @dawkaa, @gerkestam2,
@GrahamCampbell, @Lctrs, @nicolasmure, @NiR- for your contributions !
v0.4
====
* Following adapters have been deprecated: AclAwareAmazonS3, AmazonS3, Apc, Cache, LazyOpenCloud, Sftp, Dropbox, MogileFS, GoogleCloudStorage (see #482)
* Improvement of test coverage during CI builds: functional tests for AzureBlobStorage, AwsS3, DoctrineDbal, Ftp, GridFS, OpenCloud and PhpseclibSftp now run on Travis (see #457, #460, #483, #484, #500, #504, #505)
* Maintained adapters now have metapackage to enforce version of 3rd party libraries, and ease installation process (see #487)
* Add FilesystemInterface and make current Filesystem implement it (see #492)
* Drop support for PHP v5.4 and v5.5 (see #503)
* File:
* Add rename method to File (see #468)
* Local adapter:
* Suppress warning if directory has been created between check and create attempt (see #331)
* Replace file_exists with is_file, to check if given path exists (see #479)
* Allow Local adapter mkdir mode to cascade to it's Stream (see #488)
* Fix phpdocs (see #489)
* AzureBlobStorage:
* Add support for multi container mode (see #486)
* AwsS3 adapter:
* Add ContentType support to AwsS3 (see #451)
* Allow aws-sdk-php v2 and v3 to be used (see #457, #462, #475)
* Provide mime type (see #491)
* Deprecate AwsS3::getUrl() method, instead use ResolvableFilesystem from [`gaufrette/extras`](https://github.com/Gaufrette/extras) (see #496)
* GridFS adapter:
* Unmaintained mongo extension has been replaced with newer mongodb extension (see #460)
* GoogleCloudStorage adapter:
* Fixed missing leading "\" before Google_Http_Request (see #471)
* Ftp adapter:
* Always ensure target directory exists before renaming (see #476)
* Don't use FTP_USEPASSVADDRESS before php 5.6.18, and 7.0.0/7.0.1 (see #477, #480, #483)
* Docs:
* Add minimum IAM roles for AwsS3 adapter, and recommend to manually create bucket (see #467)
Contributors: @NiR-, @nicolasmure, @WARrior-Alex, @zyphlar, @AntoineLelaisant, @Shivoham, @richardfullmer, @kcassam.
Also, we thank @edhgoose and @zyphlar who made patches for deprecated adapters, before those adapters were deprecated, but still did not see their respective work merged in this version.

22
vendor/knplabs/gaufrette/LICENSE vendored Normal file
View File

@@ -0,0 +1,22 @@
Copyright (c) 2010 Antoine Hérault
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

104
vendor/knplabs/gaufrette/Makefile vendored Normal file
View File

@@ -0,0 +1,104 @@
.DEFAULT_GOAL := help
PHP_VERSION ?= 7.2
export ROOT_DIR=${PWD}
#
### DOCKER
# --------
#
.PHONY: dev
docker.dev: ## Prepare the env file before running docker
cp .env.dist .env
.PHONY: build
docker.build: ## Build the PHP docker image
docker-compose build php${PHP_VERSION}
.PHONY: install-deps
docker.deps: ## Install dependencies
docker/run-task php${PHP_VERSION} composer install
.PHONY: install-all-deps
docker.all-deps: docker.deps ## Install dependencies
docker/run-task php${PHP_VERSION} composer require --no-update \
aws/aws-sdk-php:^3.158 \
google/apiclient:^2.12 \
doctrine/dbal:^3.4 \
league/flysystem:^1.0 \
microsoft/azure-storage-blob:^1.0 \
phpseclib/phpseclib:^2.0 \
mongodb/mongodb:^1.1 \
async-aws/simple-s3:^0.1.1
.PHONY: tests
docker.tests: ## Run tests
docker/run-task php${PHP_VERSION} bin/tests
.PHONY: php-cs-compare
docker.php-cs-compare: ## Run CS fixer (dry run)
docker/run-task php${PHP_VERSION} vendor/bin/php-cs-fixer fix \
--diff \
--dry-run \
--show-progress=none \
--verbose
.PHONY: php-cs-fix
docker.php-cs-fix: ## Run CS fixer
docker/run-task php${PHP_VERSION} vendor/bin/php-cs-fixer fix
#
### LOCAL TASKS
# -------
#
remove-phpspec: ## Remove adapter specs (allows you to run test suite without adapters deps)
rm spec/Gaufrette/Adapter/AsyncAwsS3Spec.php
rm spec/Gaufrette/Adapter/AwsS3Spec.php
rm spec/Gaufrette/Adapter/GoogleCloudStorageSpec.php
rm spec/Gaufrette/Adapter/DoctrineDbalSpec.php
rm spec/Gaufrette/Adapter/FlysystemSpec.php
rm -r spec/Gaufrette/Adapter/AzureBlobStorage
rm spec/Gaufrette/Adapter/GridFSSpec.php
rm spec/Gaufrette/Adapter/PhpseclibSftpSpec.php
require-all-legacy: # kept for compatibility with the old CI config, to be removed at some point
composer require --no-update \
aws/aws-sdk-php:^3.158 \
google/apiclient:^2.12 \
doctrine/dbal:^3.4 \
league/flysystem:^1.0 \
microsoft/azure-storage-blob:^1.0 \
phpseclib/phpseclib:^2.0 \
mongodb/mongodb:^1.1
require-all: require-all-legacy ## Install all dependencies for adapters
composer require --no-update async-aws/simple-s3:^1.0
.PHONY: bc-check
bc-check: ## Check for backward compatibility change
docker run -v ${ROOT_DIR}:/app --rm nyholm/roave-bc-check
.PHONY: clear
clear: ## Remove not versioned files
rm -rf vendor/ composer.lock
test.phpstan: ## Run phpstan analysis
php vendor/bin/phpstan analyze --memory-limit 1G
#
### OTHERS
# --------
#
help: SHELL=/bin/bash
help: ## Dislay this help
@IFS=$$'\n'; for line in `grep -h -E '^[a-zA-Z_#-]+:?.*?## .*$$' $(MAKEFILE_LIST)`; do if [ "$${line:0:2}" = "##" ]; then \
echo $$line | awk 'BEGIN {FS = "## "}; {printf "\n\033[33m%s\033[0m\n", $$2}'; else \
echo $$line | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'; fi; \
done; unset IFS;
@echo ""
@echo "Hint: use 'make command PHP_VERSION=X.X' to specify the PHP version with docker commands."
.PHONY: help

124
vendor/knplabs/gaufrette/README.md vendored Normal file
View File

@@ -0,0 +1,124 @@
Gaufrette
=========
[![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua)
Gaufrette provides a filesystem abstraction layer.
[![Build Status](https://github.com/KnpLabs/Gaufrette/actions/workflows/ci.yml/badge.svg)](https://github.com/KnpLabs/Gaufrette/actions)
[![Quality Score](https://img.shields.io/scrutinizer/g/KnpLabs/Gaufrette.svg?style=flat-square)](https://scrutinizer-ci.com/g/KnpLabs/Gaufrette)
[![Packagist Version](https://img.shields.io/packagist/v/KnpLabs/Gaufrette.svg?style=flat-square)](https://packagist.org/packages/KnpLabs/Gaufrette)
[![Total Downloads](https://img.shields.io/packagist/dt/KnpLabs/Gaufrette.svg?style=flat-square)](https://packagist.org/packages/KnpLabs/Gaufrette)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Join the chat at Gitter](https://img.shields.io/gitter/room/nwjs/nw.js.svg?style=flat-square)](https://gitter.im/KnpLabs/Gaufrette)
Why use Gaufrette?
------------------
Imagine you have to manage a lot of media in a PHP project. Let's see how to
take this situation to your advantage using Gaufrette.
The filesystem abstraction layer permits you to develop your application without
the need to know where all those media will be stored and how.
Another advantage of this is the possibility to update the location of the files
without any impact on the code apart from the definition of your filesystem.
In example, if your project grows up very fast and if your server reaches its
limits, you can easily move your medias in an Amazon S3 server or any other
solution.
### Documentation
Read the official [Gaufrette documentation](http://knplabs.github.io/Gaufrette/).
### Metapackages for adapters
Every maintained adapter now has a dedicated metapackage. You can [find the list on packagist](https://packagist.org/packages/gaufrette/).
**We highly recommend you to use them as they contain their own requirements**: you don't need to worry about third-party dependencies
to install before using Gaufrette anymore.
### Symfony integration
Symfony integration is available through [KnpLabs/KnpGaufretteBundle](https://github.com/KnpLabs/KnpGaufretteBundle).
### Maintainers
Here is the list of the dedicated maintainer(s) for every adapter not deprecated. If you don't receive any response to
your issue or pull request in a timely manner, ping us:
| Adapter | Referent |
|--------------------|-----------------------------|
| AsyncAws S3 | @Nyholm |
| AwsS3 | @NiR- |
| AzureBlobStorage | @NiR- |
| DoctrineDbal | @pedrotroller, @NicolasNSSM |
| Flysystem | @nicolasmure |
| Ftp | @fabschurt |
| GoogleCloudStorage | @AntoineLelaisant |
| GridFS | @NiR- |
| InMemory | |
| Local | |
| OpenCloud | @NiR- |
| PhpseclibSftp | @fabschurt |
| Zip | |
For `InMemory`, `Local`, and `Zip` adapters everyone in this list is considered as a maintainer.
### Development
Requires :
* docker-ce
* docker-compose
1) Create `.env` file :
```bash
$ make docker.dev
```
and configure it as you want.
2) Build the PHP docker image :
```bash
$ make docker.build
```
3) Install dependencies :
```bash
$ make docker.all-deps
```
4) Run tests :
```bash
$ make docker.tests
```
You can also use a different php version, simply set the `PHP_VERSION` env var
to any of these values when calling a make target :
- `7.1`
- `7.2` (default)
- `7.3` (The docker setup for PHP 7.3 is available. However, the ssh2 extension
is not installed [as it is not available for PHP 7.3 yet](https://serverpilot.io/docs/how-to-install-the-php-ssh2-extension))
See the [`docker-compose.yml`](/docker-compose.yml) file for more details.
You'll need to clear the previously installed dependencies when switching from
one version to an other. To do so, run :
```bash
$ make clear-deps
$ PHP_VERSION=<the_version_you_want_to_use> make build install-deps
```
5) Apply Coding Standards
You should check for CS violations by using
```bash
$ make php-cs-compare
```
and fix them with
```bash
$ make php-cs-fix
```
### Note
This project does not have any stable release yet but we do not want to break BC now.

59
vendor/knplabs/gaufrette/composer.json vendored Normal file
View File

@@ -0,0 +1,59 @@
{
"name": "knplabs/gaufrette",
"type": "library",
"description": "PHP library that provides a filesystem abstraction layer",
"keywords": ["file", "filesystem", "media", "abstraction"],
"homepage": "http://knplabs.com",
"license": "MIT",
"authors": [
{
"name": "KnpLabs Team",
"homepage": "http://knplabs.com"
},
{
"name": "The contributors",
"homepage": "http://github.com/knplabs/Gaufrette/contributors"
}
],
"require": {
"php": "^7.4 || ^8.0"
},
"conflict": {
"microsoft/windowsazure": "<0.4.3"
},
"require-dev": {
"phpspec/phpspec": "^7.0",
"phpunit/phpunit": "~8.0",
"mikey179/vfsstream": "v1.x-dev as 1.7.0",
"friendsofphp/php-cs-fixer": "^3.9",
"pedrotroller/php-cs-custom-fixer": "^2.28"
},
"suggest": {
"knplabs/knp-gaufrette-bundle": "to use with Symfony",
"ext-fileinfo": "This extension is used to automatically detect the content-type of a file in the AwsS3, OpenCloud, AzureBlogStorage and GoogleCloudStorage adapters"
},
"autoload": {
"psr-0": { "Gaufrette": "src/" }
},
"autoload-dev": {
"psr-0": { "Gaufrette\\Functional": "tests/" }
},
"extra": {
"branch-alias": {
"dev-master": "0.10.x-dev"
}
},
"archive": {
"exclude": [
"/tests",
"/phpunit.xml.dist",
"/.env.dist",
"/spec",
"/Makefile",
"/phpspec.yml",
"/docker-compose.yml",
"/docker",
"/.dockerignore"
]
}
}

View File

@@ -0,0 +1,57 @@
version: '3.4'
services:
php7.1:
build:
context: .
dockerfile: docker/php/Dockerfile_php_7_1
target: php7.1
env_file: .env
volumes:
- './:/app'
- './docker/php/php.ini:/usr/local/etc/php/php.ini:ro'
depends_on:
- mongodb
- sftp
- ftp
php7.2:
build:
context: .
dockerfile: docker/php/Dockerfile_php_7_2
target: php7.2
env_file: .env
volumes:
- './:/app'
- './docker/php/php.ini:/usr/local/etc/php/php.ini:ro'
depends_on:
- mongodb
- sftp
- ftp
php7.3:
build:
context: .
dockerfile: docker/php/Dockerfile_php_7_3
target: php7.3
env_file: .env
volumes:
- './:/app'
- './docker/php/php.ini:/usr/local/etc/php/php.ini:ro'
depends_on:
- mongodb
- sftp
- ftp
mongodb:
image: mongo
sftp:
image: atmoz/sftp:alpine
command: gaufrette:gaufrette:::gaufrette
ftp:
build:
context: ./docker/ftp
environment:
PUBLICHOST: 'ftp'

View File

@@ -0,0 +1,3 @@
FROM stilliard/pure-ftpd
RUN (echo gaufrette; echo gaufrette) | pure-pw useradd gaufrette -f /etc/pure-ftpd/passwd/pureftpd.passwd -m -u ftpuser -d /home/ftpusers/gaufrette

View File

@@ -0,0 +1,34 @@
# To copy composer binary file
FROM composer:1.8.5 as composer
################################################################################
FROM php:7.1-alpine as php7.1
RUN apk add --no-cache \
autoconf \
g++\
git \
libssh2-dev \
make \
zlib-dev \
&& pecl install \
mongodb \
ssh2-1.1.2
RUN docker-php-ext-install \
zip \
&& docker-php-ext-enable \
mongodb \
ssh2
COPY --from=composer /usr/bin/composer /usr/bin/composer
ENV COMPOSER_HOME /var/cache/composer
RUN mkdir /var/cache/composer \
&& chown 1000:1000 /var/cache/composer
USER 1000
RUN composer global require "hirak/prestissimo" --prefer-dist
WORKDIR /app

View File

@@ -0,0 +1,34 @@
# To copy composer binary file
FROM composer:1.8.5 as composer
################################################################################
FROM php:7.2-alpine as php7.2
RUN apk add --no-cache \
autoconf \
g++\
git \
libssh2-dev \
make \
zlib-dev \
&& pecl install \
mongodb \
ssh2-1.1.2
RUN docker-php-ext-install \
zip \
&& docker-php-ext-enable \
mongodb \
ssh2
COPY --from=composer /usr/bin/composer /usr/bin/composer
ENV COMPOSER_HOME /var/cache/composer
RUN mkdir /var/cache/composer \
&& chown 1000:1000 /var/cache/composer
USER 1000
RUN composer global require "hirak/prestissimo" --prefer-dist
WORKDIR /app

View File

@@ -0,0 +1,37 @@
# To copy composer binary file
FROM composer:1.8.5 as composer
################################################################################
FROM php:7.3-alpine as php7.3
RUN apk add --no-cache \
autoconf \
g++\
git \
# libssh2-dev \
libzip-dev \
make \
zlib-dev \
&& pecl install \
mongodb
# ssh2-1.1.2
# ssh2 extension is not availble yet for php7.3
# see https://serverpilot.io/docs/how-to-install-the-php-ssh2-extension
RUN docker-php-ext-install \
zip \
&& docker-php-ext-enable \
mongodb
# ssh2
COPY --from=composer /usr/bin/composer /usr/bin/composer
ENV COMPOSER_HOME /var/cache/composer
RUN mkdir /var/cache/composer \
&& chown 1000:1000 /var/cache/composer
USER 1000
RUN composer global require "hirak/prestissimo" --prefer-dist
WORKDIR /app

View File

@@ -0,0 +1,2 @@
apc.enable_cli = 1
date.timezone = UTC

View File

@@ -0,0 +1,15 @@
#!/bin/bash
# Usage: run-task SERVICE [CMD...]
set -o nounset
# env var used by the `docker-compose` command
COMPOSE_FILE="$(dirname $0)/docker-compose.yml"
docker-compose run --rm "${@}"
EXIT_CODE="${?}"
# stop the other services linked to the task
docker-compose down
exit "${EXIT_CODE}"

0
vendor/knplabs/gaufrette/phpspec.yml vendored Normal file
View File

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
bootstrap="vendor/autoload.php"
>
<!-- The env vars are defined in the .env file -->
<testsuites>
<testsuite name="Gaufrette Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./src</directory>
</whitelist>
</filter>
</phpunit>

View File

@@ -0,0 +1,43 @@
<?php
namespace spec\Gaufrette\Adapter;
use AsyncAws\SimpleS3\SimpleS3Client;
use Gaufrette\Adapter\MimeTypeProvider;
use PhpSpec\ObjectBehavior;
class AsyncAwsS3Spec extends ObjectBehavior
{
/**
* @param \AsyncAws\SimpleS3\SimpleS3Client $service
*/
function let(SimpleS3Client $service)
{
$this->beConstructedWith($service, 'bucketName');
}
function it_is_initializable()
{
$this->shouldHaveType('Gaufrette\Adapter\AsyncAwsS3');
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_supports_metadata()
{
$this->shouldHaveType('Gaufrette\Adapter\MetadataSupporter');
}
function it_supports_sizecalculator()
{
$this->shouldHaveType('Gaufrette\Adapter\SizeCalculator');
}
function it_provides_mime_type()
{
$this->shouldHaveType(MimeTypeProvider::class);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace spec\Gaufrette\Adapter;
use Aws\S3\S3Client;
use Gaufrette\Adapter\MimeTypeProvider;
use PhpSpec\ObjectBehavior;
class AwsS3Spec extends ObjectBehavior
{
/**
* @param \Aws\S3\S3Client $service
*/
function let(S3Client $service)
{
$this->beConstructedWith($service, 'bucketName');
}
function it_is_initializable()
{
$this->shouldHaveType('Gaufrette\Adapter\AwsS3');
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_supports_metadata()
{
$this->shouldHaveType('Gaufrette\Adapter\MetadataSupporter');
}
function it_supports_sizecalculator()
{
$this->shouldHaveType('Gaufrette\Adapter\SizeCalculator');
}
function it_provides_mime_type()
{
$this->shouldHaveType(MimeTypeProvider::class);
}
}

View File

@@ -0,0 +1,505 @@
<?php
namespace spec\Gaufrette\Adapter;
use PhpSpec\ObjectBehavior;
use WindowsAzure\Blob\Models\Blob;
use WindowsAzure\Common\ServiceException;
class AzureBlobStorage extends ObjectBehavior
{
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
*/
function let($blobProxyFactory)
{
$this->beConstructedWith($blobProxyFactory, 'containerName');
}
function it_should_be_initializable()
{
$this->shouldHaveType('Gaufrette\Adapter\AzureBlobStorage');
$this->shouldHaveType('Gaufrette\Adapter');
$this->shouldHaveType('Gaufrette\Adapter\MetadataSupporter');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
* @param \WindowsAzure\Blob\Models\GetBlobResult $getBlobResult
*/
function it_should_read_file($blobProxyFactory, $blobProxy, $getBlobResult)
{
$getBlobResult
->getContentStream()
->shouldBeCalled()
//azure blob content is handled as stream so we need to fake it
->willReturn(fopen('data://text/plain,some content', 'r'));
$blobProxy
->getBlob('containerName', 'filename')
->shouldBeCalled()
->willReturn($getBlobResult);
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->read('filename')->shouldReturn('some content');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_return_false_when_cannot_read($blobProxyFactory, $blobProxy)
{
$blobProxy
->getBlob('containerName', 'filename')
->shouldBeCalled()
->willThrow(new ServiceException(500));
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->read('filename')->shouldReturn(false);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_not_mask_exception_when_read($blobProxyFactory, $blobProxy)
{
$blobProxy
->getBlob('containerName', 'filename')
->shouldBeCalled()
->willThrow(new \RuntimeException('read'));
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->shouldThrow(new \RuntimeException('read'))->duringRead('filename');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_rename_file($blobProxyFactory, $blobProxy)
{
$blobProxy
->copyBlob('containerName', 'filename2', 'containerName', 'filename1')
->shouldBeCalled();
$blobProxy
->deleteBlob('containerName', 'filename1')
->shouldBeCalled();
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->rename('filename1', 'filename2')->shouldReturn(true);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_return_false_when_cannot_rename($blobProxyFactory, $blobProxy)
{
$blobProxy
->copyBlob('containerName', 'filename2', 'containerName', 'filename1')
->shouldBeCalled()
->willThrow(new ServiceException(500));
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->rename('filename1', 'filename2')->shouldReturn(false);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_not_mask_exception_when_rename($blobProxyFactory, $blobProxy)
{
$blobProxy
->copyBlob('containerName', 'filename2', 'containerName', 'filename1')
->shouldBeCalled()
->willThrow(new \RuntimeException('rename'));
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->shouldThrow(new \RuntimeException('rename'))->duringRename('filename1', 'filename2');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_write_file($blobProxyFactory, $blobProxy)
{
$blobProxy
->createBlockBlob(
'containerName',
'filename',
'some content',
\Mockery::type('\WindowsAzure\Blob\Models\CreateBlobOptions')
)
->shouldBeCalled();
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->write('filename', 'some content')->shouldReturn(12);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_return_false_when_cannot_write($blobProxyFactory, $blobProxy)
{
$blobProxy
->createBlockBlob(
'containerName',
'filename',
'some content',
\Mockery::type('\WindowsAzure\Blob\Models\CreateBlobOptions')
)
->willThrow(new ServiceException(500));
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->write('filename', 'some content')->shouldReturn(false);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_not_mask_exception_when_write($blobProxyFactory, $blobProxy)
{
$blobProxy
->createBlockBlob(
'containerName',
'filename',
'some content',
\Mockery::type('\WindowsAzure\Blob\Models\CreateBlobOptions')
)
->willThrow(new \RuntimeException('write'));
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$this->shouldThrow(new \RuntimeException('write'))->duringWrite('filename', 'some content');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
* @param \WindowsAzure\Blob\Models\GetBlobResult $getBlobResult
*/
function it_should_check_if_file_exists($blobProxyFactory, $blobProxy, $getBlobResult)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->getBlob('containerName', 'filename')
->shouldBeCalled()
->willThrow(new ServiceException(404));
$this->exists('filename')->shouldReturn(false);
$blobProxy
->getBlob('containerName', 'filename2')
->shouldBeCalled()
->willReturn($getBlobResult);
$this->exists('filename2')->shouldReturn(true);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_not_mask_exception_when_check_if_file_exists($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->getBlob('containerName', 'filename')
->shouldBeCalled()
->willThrow(new \RuntimeException('exists'));
$this->shouldThrow(new \RuntimeException('exists'))->duringExists('filename');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
* @param \WindowsAzure\Blob\Models\GetBlobPropertiesResult $getBlobPropertiesResult
* @param \WindowsAzure\Blob\Models\BlobProperties $blobProperties
*/
function it_should_get_file_mtime($blobProxyFactory, $blobProxy, $getBlobPropertiesResult, $blobProperties)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->getBlobProperties('containerName', 'filename')
->shouldBeCalled()
->willReturn($getBlobPropertiesResult);
$getBlobPropertiesResult
->getProperties()
->shouldBeCalled()
->willReturn($blobProperties);
$blobProperties
->getLastModified()
->shouldBeCalled()
->willReturn(new \DateTime('1987-12-28 20:00:00'));
$this->mtime('filename')->shouldReturn(strtotime('1987-12-28 20:00:00'));
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_return_false_when_cannot_mtime($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->getBlobProperties('containerName', 'filename')
->shouldBeCalled()
->willThrow(new ServiceException(500));
$this->mtime('filename')->shouldReturn(false);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_not_mask_exception_when_get_mtime($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->getBlobProperties('containerName', 'filename')
->shouldBeCalled()
->willThrow(new \RuntimeException('mtime'));
$this->shouldThrow(new \RuntimeException('mtime'))->duringMtime('filename');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_delete_file($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->deleteBlob('containerName', 'filename')
->shouldBeCalled();
$this->delete('filename')->shouldReturn(true);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_return_false_when_cannot_delete_file($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->deleteBlob('containerName', 'filename')
->shouldBeCalled()
->willThrow(new ServiceException(500));
$this->delete('filename')->shouldReturn(false);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_not_mask_exception_when_delete($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->deleteBlob('containerName', 'filename')
->shouldBeCalled()
->willThrow(new \RuntimeException('delete'));
$this->shouldThrow(new \RuntimeException('delete'))->duringDelete('filename');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
* @param \WindowsAzure\Blob\Models\ListBlobsResult $listBlobResult
*/
function it_should_get_keys($blobProxyFactory, $blobProxy, $listBlobResult)
{
$fileNames = ['aaa', 'aaa/filename', 'filename1', 'filename2'];
$blobs = [];
foreach ($fileNames as $fileName) {
$blob = new Blob();
$blob->setName($fileName);
$blobs[] = $blob;
}
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->listBlobs('containerName')
->shouldBeCalled()
->willReturn($listBlobResult);
$listBlobResult
->getBlobs()
->shouldBeCalled()
->willReturn($blobs);
$this->keys()->shouldReturn(['aaa', 'aaa/filename', 'filename1', 'filename2']);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_not_mask_exception_when_get_keys($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->listBlobs('containerName')
->shouldBeCalled()
->willThrow(new \RuntimeException('keys'));
$this->shouldThrow(new \RuntimeException('keys'))->duringKeys();
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_handle_dirs($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->getBlob('containerName', 'filename')
->shouldNotBeCalled();
$blobProxy
->getBlob('containerName', 'filename/')
->shouldBeCalled()
->willThrow(new ServiceException(404));
$blobProxy
->getBlob('containerName', 'dirname/')
->shouldBeCalled();
$this->isDirectory('filename')->shouldReturn(false);
$this->isDirectory('dirname')->shouldReturn(true);
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_create_container($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->createContainer('containerName', null)
->shouldBeCalled();
$this->createContainer('containerName');
}
/**
* @param \Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface $blobProxyFactory
* @param \WindowsAzure\Blob\Internal\IBlob $blobProxy
*/
function it_should_fail_when_cannot_create_container($blobProxyFactory, $blobProxy)
{
$blobProxyFactory
->create()
->shouldBeCalled()
->willReturn($blobProxy);
$blobProxy
->createContainer('containerName', null)
->shouldBeCalled()
->willThrow(new ServiceException(500));
$this->shouldThrow(new \RuntimeException('Failed to create the configured container "containerName": 0 ().', null))->duringCreateContainer('containerName');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace spec\Gaufrette\Adapter\AzureBlobStorage;
use PhpSpec\ObjectBehavior;
class BlobProxyFactory extends ObjectBehavior
{
/**
* @param string $connectionString
*/
function let($connectionString)
{
$this->beConstructedWith($connectionString);
}
function it_should_be_initializable()
{
$this->shouldHaveType('Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactory');
$this->shouldHaveType('Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactoryInterface');
}
}

View File

@@ -0,0 +1,270 @@
<?php
namespace spec\Gaufrette\Adapter;
//hack - mock php built-in functions
require_once 'functions.php';
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Result;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class DoctrineDbalSpec extends ObjectBehavior
{
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function let(Connection $connection)
{
$this->beConstructedWith($connection, 'someTableName');
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_is_checksum_calculator()
{
$this->shouldHaveType('Gaufrette\Adapter\ChecksumCalculator');
}
function it_does_not_handle_directories()
{
$this->isDirectory('filename')->shouldReturn(false);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_checks_if_file_exists(Connection $connection)
{
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$connection
->$method('SELECT COUNT("key") FROM "someTableName" WHERE "key" = :key', ['key' => 'filename'])
->willReturn(12);
$this->exists('filename')->shouldReturn(true);
$connection
->$method('SELECT COUNT("key") FROM "someTableName" WHERE "key" = :key', ['key' => 'filename'])
->willReturn(0);
$this->exists('filename')->shouldReturn(false);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_writes_to_new_file(Connection $connection)
{
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$connection
->$method('SELECT COUNT("key") FROM "someTableName" WHERE "key" = :key', ['key' => 'filename'])
->willReturn(false);
$connection
->insert(
'someTableName',
[
'"content"' => 'some content',
'"mtime"' => strtotime('2012-10-10 23:10:10'),
'"checksum"' => '9893532233caff98cd083a116b013c0b',
'"key"' => 'filename',
]
)
->shouldBeCalled();
$this->write('filename', 'some content')->shouldReturn(12);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_write_file(Connection $connection)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$connection
->$method('SELECT COUNT("key") FROM "someTableName" WHERE "key" = :key', ['key' => 'filename'])
->willReturn(true);
$connection
->update(
'someTableName',
[
'"content"' => 'some content',
'"mtime"' => strtotime('2012-10-10 23:10:10'),
'"checksum"' => '9893532233caff98cd083a116b013c0b',
],
[
'"key"' => 'filename',
]
)
->shouldBeCalled();
$this->write('filename', 'some content')->shouldReturn(12);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_reads_file(Connection $connection)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$connection
->$method('SELECT "content" FROM "someTableName" WHERE "key" = :key', ['key' => 'filename'])
->willReturn('some content');
$this->read('filename')->shouldReturn('some content');
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_calculates_checksum(Connection $connection)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$connection
->$method('SELECT "checksum" FROM "someTableName" WHERE "key" = :key', ['key' => 'filename'])
->willReturn(1234);
$this->checksum('filename')->shouldReturn(1234);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_gets_mtime(Connection $connection)
{
$method = 'fetchOne'; // dbal 3.x
if (!method_exists(Connection::class, 'fetchAllAssociative')) {
$method = 'fetchColumn'; // BC layer for dbal 2.x
}
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$connection
->$method('SELECT "mtime" FROM "someTableName" WHERE "key" = :key', ['key' => 'filename'])
->willReturn(1234);
$this->mtime('filename')->shouldReturn(1234);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_renames_file(Connection $connection)
{
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$connection
->update(
'someTableName',
[
'"key"' => 'newFile',
],
[
'"key"' => 'filename',
]
)
->shouldBeCalled()
->willReturn(1);
$this->rename('filename', 'newFile')->shouldReturn(true);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_get_keys(Connection $connection, $result)
{
if (class_exists(Result::class)) {
// dbal 3.x
$result->beADoubleOf(Result::class);
$result->fetchFirstColumn()->willReturn(['filename', 'filename1', 'filename2']);
} else {
// BC layer for dbal 2.x
$result->beADoubleOf(\Doctrine\DBAL\Statement::class);
$result->fetchAll(\PDO::FETCH_COLUMN)->willReturn(['filename', 'filename1', 'filename2']);
}
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$connection
->executeQuery('SELECT "key" FROM "someTableName"')
->willReturn($result);
$this->keys()->shouldReturn(['filename', 'filename1', 'filename2']);
}
/**
* @param \Doctrine\DBAL\Connection $connection
*/
function it_deletes_file(Connection $connection)
{
$connection
->quoteIdentifier(Argument::any())
->will(function ($argument) {
return sprintf('"%s"', $argument[0]);
});
$connection
->delete('someTableName', ['"key"' => 'filename'])
->shouldBeCalled()
->willReturn(1);
$this->delete('filename')->shouldReturn(true);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace spec\Gaufrette\Adapter;
use PhpSpec\ObjectBehavior;
use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;
class FlysystemSpec extends ObjectBehavior
{
function let(AdapterInterface $adapter, Config $config)
{
$this->beConstructedWith($adapter, $config);
}
function it_is_adapter()
{
$this->shouldImplement('Gaufrette\Adapter');
}
function it_is_list_keys_aware()
{
$this->shouldImplement('Gaufrette\Adapter\ListKeysAware');
}
function it_reads_file(AdapterInterface $adapter)
{
$adapter->read('filename')->willReturn(['contents' => 'Hello.']);
$this->read('filename')->shouldReturn('Hello.');
}
function it_writes_file(AdapterInterface $adapter, Config $config)
{
$adapter->write('filename', 'Hello.', $config)->willReturn([]);
$this->write('filename', 'Hello.')->shouldReturn([]);
}
function it_checks_if_file_exists(AdapterInterface $adapter)
{
$adapter->has('filename')->willReturn(true);
$this->exists('filename')->shouldReturn(true);
}
function it_checks_if_file_exists_when_flysystem_returns_array(AdapterInterface $adapter)
{
$adapter->has('filename')->willReturn(['type' => 'file']);
$this->exists('filename')->shouldReturn(true);
}
function it_fetches_keys(AdapterInterface $adapter)
{
$adapter->listContents()->willReturn([[
'path' => 'folder',
'timestamp' => 1457104978,
'size' => 22,
'type' => 'dir',
]]);
$this->keys()->shouldReturn(['folder']);
}
function it_lists_keys(AdapterInterface $adapter)
{
$adapter->listContents()->willReturn([[
'path' => 'folder',
'timestamp' => 1457104978,
'size' => 22,
'type' => 'dir',
]]);
$this->listKeys()->shouldReturn([
'keys' => [],
'dirs' => ['folder'],
]);
}
function it_fetches_mtime(AdapterInterface $adapter)
{
$adapter->getTimestamp('filename')->willReturn(1457104978);
$this->mtime('filename')->shouldReturn(1457104978);
}
function it_deletes_file(AdapterInterface $adapter)
{
$adapter->delete('filename')->willReturn(true);
$this->delete('filename')->shouldReturn(true);
}
function it_renames_file(AdapterInterface $adapter)
{
$adapter->rename('oldfilename', 'newfilename')->willReturn(true);
$this->rename('oldfilename', 'newfilename')->shouldReturn(true);
}
function it_does_not_support_is_directory(AdapterInterface $adapter)
{
$this->shouldThrow('Gaufrette\Exception\UnsupportedAdapterMethodException')->duringisDirectory('folder');
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace spec\Gaufrette\Adapter;
use PhpSpec\ObjectBehavior;
class GoogleCloudStorageSpec extends ObjectBehavior
{
function let(\Google_Service_Storage $service)
{
$this->beConstructedWith($service, 'bucketName');
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_supports_metadata()
{
$this->shouldHaveType('Gaufrette\Adapter\MetadataSupporter');
}
function it_is_list_keys_aware()
{
$this->shouldHaveType('Gaufrette\Adapter\ListKeysAware');
}
}

View File

@@ -0,0 +1,169 @@
<?php
namespace spec\Gaufrette\Adapter;
use MongoDB\BSON\UTCDateTime;
use MongoDB\GridFS\Bucket;
use MongoDB\GridFS\Exception\FileNotFoundException;
use MongoDB\Model\BSONDocument;
use PhpSpec\ObjectBehavior;
class GridFSSpec extends ObjectBehavior
{
private $resources = [];
function let(Bucket $bucket)
{
$this->beConstructedWith($bucket);
}
function letGo()
{
array_map(function ($res) {
if (is_resource($res)) {
@fclose($res);
}
}, $this->resources);
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_is_checksum_calculator()
{
$this->shouldHaveType('Gaufrette\Adapter\ChecksumCalculator');
}
function it_supports_metadata()
{
$this->shouldHaveType('Gaufrette\Adapter\MetadataSupporter');
}
function it_supports_native_list_keys()
{
$this->shouldHaveType('Gaufrette\Adapter\ListKeysAware');
}
function it_reads_file($bucket)
{
$this->resources[] = $readable = fopen('php://memory', 'rw');
fwrite($readable, 'some content');
fseek($readable, 0);
$bucket
->openDownloadStreamByName('filename')
->shouldBeCalled()
->willReturn($readable)
;
$this->read('filename')->shouldReturn('some content');
}
function it_does_not_fail_when_cannot_read($bucket)
{
$bucket->openDownloadStreamByName('filename')->willThrow(FileNotFoundException::class);
$this->read('filename')->shouldReturn(false);
}
function it_checks_if_file_exists($bucket, BSONDocument $file)
{
$bucket
->findOne(['filename' => 'filename'])
->willReturn($file)
;
$bucket
->findOne(['filename' => 'filename2'])
->willReturn(null)
;
$this->exists('filename')->shouldReturn(true);
$this->exists('filename2')->shouldReturn(false);
}
function it_deletes_file($bucket)
{
$bucket
->findOne(['filename' => 'filename'], ['projection' => ['_id' => 1]])
->willReturn($file = new BSONDocument(['_id' => 123]))
;
$bucket->delete(123)->shouldBeCalled();
$this->delete('filename')->shouldReturn(true);
}
function it_does_not_delete_file($bucket)
{
$bucket->findOne(['filename' => 'filename'], ['projection' => ['_id' => 1]])->willReturn(null);
$this->delete('filename')->shouldReturn(false);
}
function it_writes_file($bucket)
{
$this->resources[] = $writable = fopen('php://memory', 'rw');
$bucket
->openUploadStream('filename', ['metadata' => ['someother' => 'metadata']])
->willReturn($writable)
;
$this->setMetadata('filename', ['someother' => 'metadata']);
$this
->write('filename', 'some content')
->shouldReturn(12)
;
}
function it_renames_file($bucket)
{
$this->resources[] = $writable = fopen('php://memory', 'rw');
$this->resources[] = $readable = fopen('php://memory', 'rw');
fwrite($readable, 'some content');
fseek($readable, 0);
$bucket->openUploadStream('otherFilename', ['metadata' => ['some' => 'metadata']])->willReturn($writable);
$bucket->downloadToStreamByName('filename', $writable)->shouldBeCalled();
$bucket
->findOne(['filename' => 'filename'], ['projection' => ['_id' => 1]])
->willReturn($toDelete = new BSONDocument(['_id' => 1234]))
;
$bucket->delete(1234)->shouldBeCalled();
$this->setMetadata('filename', ['some' => 'metadata']);
$this->rename('filename', 'otherFilename')->shouldReturn(true);
}
function it_fetches_keys($bucket)
{
$bucket
->find([], ['projection' => ['filename' => 1]])
->willReturn([new BSONDocument(['filename' => 'filename']), new BSONDocument(['filename' => 'otherFilename'])])
;
$this->keys()->shouldReturn(['filename', 'otherFilename']);
}
function it_fetches_mtime($bucket)
{
$bucket
->findOne(['filename' => 'filename'], ['projection' => ['uploadDate' => 1]])
->willReturn(new BSONDocument(['uploadDate' => new UTCDateTime(12345000)]))
;
$this->mtime('filename')->shouldReturn(12345);
}
function it_calculates_checksum($bucket)
{
$bucket
->findOne(['filename' => 'filename'], ['projection' => ['md5' => 1]])
->willReturn(new BSONDocument(['md5' => 'md5123']))
;
$this->checksum('filename')->shouldReturn('md5123');
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace spec\Gaufrette\Adapter;
use PhpSpec\ObjectBehavior;
class InMemorySpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith([
'filename' => ['mtime' => 12345, 'content' => 'content'],
'filename2' => 'other content',
]);
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_is_a_mime_type_provider()
{
$this->shouldHaveType('Gaufrette\Adapter\MimeTypeProvider');
}
function it_gets_the_file_mime_type()
{
$this->mimeType('filename')->shouldReturn('text/plain');
}
function it_reads_file()
{
$this->read('filename')->shouldReturn('content');
}
function it_writes_file()
{
$this->write('filename', 'some content')->shouldReturn(12);
}
function it_renames_file()
{
$this->rename('filename', 'aaa/filename2')->shouldReturn(true);
$this->exists('filename')->shouldReturn(false);
$this->exists('aaa/filename2')->shouldReturn(true);
}
function it_checks_if_file_exists()
{
$this->exists('filename')->shouldReturn(true);
$this->exists('filenameTest')->shouldReturn(false);
}
function it_fetches_keys()
{
$this->keys()->shouldReturn(['filename', 'filename2']);
}
function it_fetches_mtime()
{
$this->mtime('filename')->shouldReturn(12345);
}
function it_deletes_file()
{
$this->delete('filename')->shouldReturn(true);
$this->exists('filename')->shouldReturn(false);
}
function it_does_not_handle_dirs()
{
$this->isDirectory('filename')->shouldReturn(false);
$this->isDirectory('filename2')->shouldReturn(false);
}
}

View File

@@ -0,0 +1,166 @@
<?php
namespace spec\Gaufrette\Adapter;
use org\bovigo\vfs\vfsStream;
use PhpSpec\ObjectBehavior;
class LocalSpec extends ObjectBehavior
{
function let()
{
vfsStream::setup('test');
vfsStream::copyFromFileSystem(__DIR__ . '/MockFilesystem');
$this->beConstructedWith(vfsStream::url('test'));
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_is_checksum_calculator()
{
$this->shouldHaveType('Gaufrette\Adapter\ChecksumCalculator');
}
function it_is_a_mime_type_provider()
{
$this->shouldHaveType('Gaufrette\Adapter\MimeTypeProvider');
}
function it_gets_the_file_mime_type()
{
$this->mimeType('filename')->shouldReturn('text/plain');
}
function it_is_stream_factory()
{
$this->shouldHaveType('Gaufrette\Adapter\StreamFactory');
}
function it_reads_file()
{
$this->read('filename')->shouldReturn("content\n");
}
function it_writes_file()
{
$this->write('filename', 'some content')->shouldReturn(12);
}
function it_renames_file()
{
$this->rename('filename', 'aaa/filename2')->shouldReturn(true);
}
function it_checks_if_file_exists()
{
$this->exists('filename')->shouldReturn(true);
$this->exists('filename1')->shouldReturn(false);
}
function it_fetches_keys()
{
$expectedKeys = ['filename', 'dir', 'dir/file'];
sort($expectedKeys);
$this->keys()->shouldReturn($expectedKeys);
}
function it_fetches_mtime()
{
$mtime = filemtime(vfsStream::url('test/filename'));
$this->mtime('filename')->shouldReturn($mtime);
}
function it_deletes_file()
{
$this->delete('filename')->shouldReturn(true);
$this->delete('filename1')->shouldReturn(false);
}
function it_deletes_dir()
{
$this->delete('dir')->shouldReturn(true);
}
function it_checks_if_given_key_is_directory()
{
$this->isDirectory('dir')->shouldReturn(true);
$this->isDirectory('filename')->shouldReturn(false);
}
function it_creates_local_stream()
{
$this->createStream('filename')->shouldReturnAnInstanceOf('Gaufrette\Stream\Local');
}
function it_does_not_allow_to_read_path_above_main_file_directory()
{
$this
->shouldThrow(new \OutOfBoundsException(sprintf('The path "%s" is out of the filesystem.', vfsStream::url('filename'))))
->duringRead('../filename')
;
$this
->shouldThrow(new \OutOfBoundsException(sprintf('The path "%s" is out of the filesystem.', vfsStream::url('filename'))))
->duringExists('../filename')
;
}
function it_fails_when_directory_does_not_exists()
{
$this->beConstructedWith(vfsStream::url('other'));
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringRead('filename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringWrite('filename', 'some content')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringRename('filename', 'otherFilename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringExists('filename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringKeys()
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringMtime('filename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringDelete('filename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringIsDirectory('filename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringCreateStream('filename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringChecksum('filename')
;
$this
->shouldThrow(new \RuntimeException(sprintf('The directory "%s" does not exist.', vfsStream::url('other'))))
->duringMimeType('filename')
;
}
function it_creates_directory_when_does_not_exists()
{
$this->beConstructedWith(vfsStream::url('test/other'), true);
$this->isDirectory('/')->shouldReturn(true);
}
}

View File

@@ -0,0 +1 @@
content

View File

@@ -0,0 +1,157 @@
<?php
namespace spec\Gaufrette\Adapter;
if (!defined('NET_SFTP_TYPE_REGULAR')) {
define('NET_SFTP_TYPE_REGULAR', 1);
}
if (!defined('NET_SFTP_TYPE_DIRECTORY')) {
define('NET_SFTP_TYPE_DIRECTORY', 2);
}
use Gaufrette\Filesystem;
use phpseclib\Net\SFTP as Base;
use PhpSpec\ObjectBehavior;
class SFTP extends Base
{
public function __construct()
{
}
}
class PhpseclibSftpSpec extends ObjectBehavior
{
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
*/
function let(SFTP $sftp)
{
$this->beConstructedWith($sftp, '/home/l3l0', false, 'l3lo', 'password');
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
function it_is_file_factory()
{
$this->shouldHaveType('Gaufrette\Adapter\FileFactory');
}
function it_supports_native_list_keys()
{
$this->shouldHaveType('Gaufrette\Adapter\ListKeysAware');
}
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
*/
function it_fetches_keys(SFTP $sftp)
{
$sftp
->file_exists('/home/l3l0/')
->willReturn(true);
$sftp
->rawlist('/home/l3l0/')
->willReturn([
'filename' => ['type' => NET_SFTP_TYPE_REGULAR],
'filename1' => ['type' => NET_SFTP_TYPE_REGULAR],
'aaa' => ['type' => NET_SFTP_TYPE_DIRECTORY],
]);
$sftp
->file_exists('/home/l3l0/aaa')
->willReturn(true);
$sftp
->rawlist('/home/l3l0/aaa')
->willReturn([
'filename' => ['type' => NET_SFTP_TYPE_REGULAR],
]);
$this->keys()->shouldReturn(['filename', 'filename1', 'aaa', 'aaa/filename']);
}
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
*/
function it_reads_file(SFTP $sftp)
{
$sftp->get('/home/l3l0/filename')->willReturn('some content');
$this->read('filename')->shouldReturn('some content');
}
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
*/
function it_creates_and_writes_file(SFTP $sftp)
{
$sftp->pwd()->willReturn('/home/l3l0');
$sftp->chdir('/home/l3l0')->willReturn(true);
$sftp->put('/home/l3l0/filename', 'some content')->willReturn(true);
$sftp->size('/home/l3l0/filename')->willReturn(12);
$this->write('filename', 'some content')->shouldReturn(12);
}
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
*/
function it_renames_file(SFTP $sftp)
{
$sftp->pwd()->willReturn('/home/l3l0');
$sftp->chdir('/home/l3l0')->willReturn(true);
$sftp
->rename('/home/l3l0/filename', '/home/l3l0/filename1')
->willReturn(true)
;
$this->rename('filename', 'filename1')->shouldReturn(true);
}
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
*/
function it_should_check_if_file_exists(SFTP $sftp)
{
$sftp->pwd()->willReturn('/home/l3l0');
$sftp->chdir('/home/l3l0')->willReturn(true);
$sftp->stat('/home/l3l0/filename')->willReturn([
'name' => '/home/l3l0/filename',
]);
$sftp->stat('/home/l3l0/filename1')->willReturn(false);
$this->exists('filename')->shouldReturn(true);
$this->exists('filename1')->shouldReturn(false);
}
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
*/
function it_should_check_is_directory(SFTP $sftp)
{
$sftp->pwd()->willReturn('/home/l3l0');
$sftp->chdir('/home/l3l0')->willReturn(true);
$sftp->chdir('/home/l3l0/aaa')->willReturn(true);
$sftp->chdir('/home/l3l0/filename')->willReturn(false);
$this->isDirectory('aaa')->shouldReturn(true);
$this->isDirectory('filename')->shouldReturn(false);
}
/**
* @param \spec\Gaufrette\Adapter\SFTP $sftp
* @param \Gaufrette\Filesystem $filesystem
*/
function it_should_create_file(SFTP $sftp, Filesystem $filesystem)
{
$sftp->stat('/home/l3l0/filename')->willReturn([
'name' => '/home/l3l0/filename',
'size' => '30',
]);
$this->createFile('filename', $filesystem)->beAnInstanceOf('Gaufrette\File');
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace spec\Gaufrette\Adapter;
use org\bovigo\vfs\vfsStream;
use PhpSpec\ObjectBehavior;
class SafeLocalSpec extends ObjectBehavior
{
function let()
{
vfsStream::setup('test');
vfsStream::copyFromFileSystem(__DIR__ . '/MockFilesystem');
$this->beConstructedWith(vfsStream::url('test'));
}
function it_is_local_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter\Local');
}
function it_computes_path_using_base64()
{
rename(vfsStream::url('test/filename'), vfsStream::url('test/' . base64_encode('filename')));
$this->read('filename')->shouldReturn("content\n");
}
function it_computes_key_back_using_base64()
{
$this->keys()->shouldReturn([base64_decode('dir'), base64_decode('dir/file'), base64_decode('filename')]);
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace spec\Gaufrette\Adapter;
use PhpSpec\ObjectBehavior;
class ZipSpec extends ObjectBehavior
{
function let()
{
$this->beConstructedWith('somefile');
}
function it_is_adapter()
{
$this->shouldHaveType('Gaufrette\Adapter');
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Gaufrette\Adapter;
global $createdDirectory;
function time()
{
return \strtotime('2012-10-10 23:10:10');
}
function file_exists($path)
{
//fake it for ssh+ssl: protocol for SFTP testing, otherwise delegate to global
if (strpos($path, 'ssh+ssl:') === 0) {
return in_array($path, ['/home/l3l0/filename', '/home/somedir/filename', 'ssh+ssl://localhost/home/l3l0/filename']) ? true : false;
}
return \file_exists($path);
}
function extension_loaded($name)
{
global $extensionLoaded;
if (is_null($extensionLoaded)) {
return true;
}
return $extensionLoaded;
}
function opendir($url)
{
return true;
}
function apc_fetch($path)
{
return sprintf('%s content', $path);
}
function apc_store($path, $content, $ttl)
{
if ('prefix-apc-test/invalid' === $path) {
return false;
}
return sprintf('%s content', $path);
}
function apc_delete($path)
{
if ('prefix-apc-test/invalid' === $path) {
return false;
}
return true;
}
function apc_exists($path)
{
if ('prefix-apc-test/invalid' === $path) {
return false;
}
return true;
}

View File

@@ -0,0 +1,191 @@
<?php
namespace spec\Gaufrette;
use Gaufrette\Filesystem;
use PhpSpec\ObjectBehavior;
interface MetadataAdapter extends \Gaufrette\Adapter,
\Gaufrette\Adapter\MetadataSupporter
{
}
class FileSpec extends ObjectBehavior
{
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function let(Filesystem $filesystem)
{
$this->beConstructedWith('filename', $filesystem);
}
function it_is_initializable()
{
$this->shouldHaveType('Gaufrette\File');
}
function it_gives_access_to_key()
{
$this->getKey()->shouldReturn('filename');
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_gets_content(Filesystem $filesystem)
{
$filesystem->read('filename')->shouldBeCalled()->willReturn('Some content');
$this->getContent()->shouldReturn('Some content');
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_gets_mtime(Filesystem $filesystem)
{
$filesystem->mtime('filename')->shouldBeCalled()->willReturn(1358797854);
$this->getMtime()->shouldReturn(1358797854);
}
/**
* @param \Gaufrette\Filesystem $filesystem
* @param \spec\Gaufrette\MetadataAdapter $adapter
*/
function it_pass_metadata_when_write_content(Filesystem $filesystem, MetadataAdapter $adapter)
{
$metadata = ['id' => '123'];
$adapter->setMetadata('filename', $metadata)->shouldBeCalled();
$filesystem->write('filename', 'some content', true)->willReturn(12);
$filesystem->getAdapter()->willReturn($adapter);
$this->setContent('some content', $metadata);
}
/**
* @param \Gaufrette\Filesystem $filesystem
* @param \spec\Gaufrette\MetadataAdapter $adapter
*/
function it_pass_metadata_when_read_content(Filesystem $filesystem, MetadataAdapter $adapter)
{
$metadata = ['id' => '123'];
$adapter->setMetadata('filename', $metadata)->shouldBeCalled();
$filesystem->read('filename')->willReturn('some content');
$filesystem->getAdapter()->willReturn($adapter);
$this->getContent($metadata);
}
/**
* @param \Gaufrette\Filesystem $filesystem
* @param \spec\Gaufrette\MetadataAdapter $adapter
*/
function it_pass_metadata_when_delete_content(Filesystem $filesystem, MetadataAdapter $adapter)
{
$metadata = ['id' => '123'];
$adapter->setMetadata('filename', $metadata)->shouldBeCalled();
$filesystem->delete('filename')->willReturn(true);
$filesystem->getAdapter()->willReturn($adapter);
$this->delete($metadata);
}
/**
* @param \Gaufrette\Filesystem $filesystem
* @param \spec\Gaufrette\MetadataAdapter $adapter
*/
function it_sets_content_of_file(Filesystem $filesystem, MetadataAdapter $adapter)
{
$adapter->setMetadata('filename', [])->shouldNotBeCalled();
$filesystem->getAdapter()->willReturn($adapter);
$filesystem->write('filename', 'some content', true)->shouldBeCalled()->willReturn(21);
$this->setContent('some content')->shouldReturn(21);
$this->getContent('filename')->shouldReturn('some content');
}
function it_sets_key_as_name_by_default()
{
$this->getName()->shouldReturn('filename');
}
function it_sets_name()
{
$this->setName('name');
$this->getName()->shouldReturn('name');
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_sets_size_for_new_file(Filesystem $filesystem)
{
$filesystem->write('filename', 'some content', true)->shouldBeCalled()->willReturn(21);
$this->setContent('some content');
$this->getSize()->shouldReturn(21);
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_calculates_size_from_filesystem(Filesystem $filesystem)
{
$filesystem->size('filename')->shouldBeCalled()->willReturn(12);
$this->getSize()->shouldReturn(12);
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_allows_to_set_size(Filesystem $filesystem)
{
$filesystem->read('filename')->shouldNotBeCalled();
$this->setSize(21);
$this->getSize()->shouldReturn(21);
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_gets_zero_size_when_file_not_found(Filesystem $filesystem)
{
$filesystem->size('filename')->willThrow(new \Gaufrette\Exception\FileNotFound('filename'));
$this->getSize()->shouldReturn(0);
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_check_if_file_with_key_exists_in_filesystem(Filesystem $filesystem)
{
$filesystem->has('filename')->willReturn(true);
$this->exists()->shouldReturn(true);
$filesystem->has('filename')->willReturn(false);
$this->exists()->shouldReturn(false);
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_deletes_file_from_filesystem(Filesystem $filesystem)
{
$filesystem->delete('filename')->shouldBeCalled()->willReturn(true);
$this->delete()->shouldReturn(true);
}
/**
* @param \Gaufrette\Filesystem $filesystem
*/
function it_renames_file_from_filesystem(Filesystem $filesystem)
{
$filesystem->rename('filename', 'newname')->shouldBeCalled();
$this->rename('newname');
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace spec\Gaufrette;
use Gaufrette\Filesystem;
use PhpSpec\ObjectBehavior;
class FilesystemMapSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->shouldHaveType('Gaufrette\FilesystemMap');
}
/**
* @param Gaufrette\Filesystem $filesystem
*/
function it_checks_if_has_mapped_filesystem(Filesystem $filesystem)
{
$this->set('some', $filesystem);
$this->has('some')->shouldReturn(true);
$this->has('other')->shouldReturn(false);
}
/**
* @param Gaufrette\Filesystem $filesystem
*/
function it_sets_mapped_filesystem(Filesystem $filesystem)
{
$this->set('some', $filesystem);
$this->get('some')->shouldReturn($filesystem);
}
function it_fails_when_get_filesystem_which_was_not_mapped()
{
$this
->shouldThrow(new \InvalidArgumentException('There is no filesystem defined having "some" name.'))
->duringGet('some')
;
}
/**
* @param Gaufrette\Filesystem $filesystem
*/
function it_removes_mapped_filesystem(Filesystem $filesystem)
{
$this->set('some', $filesystem);
$this->remove('some');
$this->has('some')->shouldReturn(false);
}
function it_fails_when_try_to_remove_filesystem_which_was_not_mapped()
{
$this
->shouldThrow(new \InvalidArgumentException('Cannot remove the "some" filesystem as it is not defined.'))
->duringRemove('some')
;
}
/**
* @param Gaufrette\Filesystem $filesystem
*/
function it_removes_all_filesystems(Filesystem $filesystem)
{
$this->set('some', $filesystem);
$this->set('other', $filesystem);
$this->clear();
$this->has('some')->shouldReturn(false);
$this->has('other')->shouldReturn(false);
$this->all()->shouldReturn([]);
}
}

View File

@@ -0,0 +1,435 @@
<?php
namespace spec\Gaufrette;
use Gaufrette\Adapter;
use Gaufrette\File;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
interface ExtendedAdapter extends \Gaufrette\Adapter,
\Gaufrette\Adapter\FileFactory,
\Gaufrette\Adapter\StreamFactory,
\Gaufrette\Adapter\ChecksumCalculator,
\Gaufrette\Adapter\MetadataSupporter,
\Gaufrette\Adapter\MimeTypeProvider
{
}
class FilesystemSpec extends ObjectBehavior
{
/**
* @param \Gaufrette\Adapter $adapter
*/
function let(Adapter $adapter)
{
$this->beConstructedWith($adapter);
}
function it_is_initializable()
{
$this->shouldBeAnInstanceOf('Gaufrette\Filesystem');
$this->shouldBeAnInstanceOf('Gaufrette\FilesystemInterface');
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_gives_access_to_adapter(Adapter $adapter)
{
$this->getAdapter()->shouldBe($adapter);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_check_if_file_exists_using_adapter(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$adapter->exists('otherFilename')->willReturn(false);
$this->has('filename')->shouldReturn(true);
$this->has('otherFilename')->shouldReturn(false);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_renames_file(Adapter $adapter)
{
$adapter->exists('filename')->shouldBeCalled()->willReturn(true);
$adapter->exists('otherFilename')->shouldBeCalled()->willReturn(false);
$adapter->rename('filename', 'otherFilename')->shouldBeCalled()->willReturn(true);
$this->rename('filename', 'otherFilename')->shouldReturn(true);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_fails_when_renamed_source_file_does_not_exist(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(false);
$this
->shouldThrow(new \Gaufrette\Exception\FileNotFound('filename'))
->duringRename('filename', 'otherFilename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_fails_when_renamed_target_file_exists(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$adapter->exists('otherFilename')->willReturn(true);
$this
->shouldThrow(new \Gaufrette\Exception\UnexpectedFile('otherFilename'))
->duringRename('filename', 'otherFilename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_fails_when_rename_is_not_successful(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$adapter->exists('otherFilename')->willReturn(false);
$adapter->rename('filename', 'otherFilename')->willReturn(false);
$this
->shouldThrow(new \RuntimeException('Could not rename the "filename" key to "otherFilename".'))
->duringRename('filename', 'otherFilename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_creates_file_object_for_key(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$this->get('filename')->shouldBeAnInstanceOf('Gaufrette\File');
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_does_not_get_file_object_when_file_with_key_does_not_exist(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(false);
$this
->shouldThrow(new \Gaufrette\Exception\FileNotFound('filename'))
->duringGet('filename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_gets_file_object_when_file_does_not_exist_but_can_be_created(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(false);
$this->get('filename', true)->shouldBeAnInstanceOf('Gaufrette\File');
}
/**
* @param \spec\Gaufrette\ExtendedAdapter $extendedAdapter
* @param \Gaufrette\File $file
*/
function it_delegates_file_creation_to_adapter_when_adapter_is_file_factory(ExtendedAdapter $extendedAdapter, File $file)
{
$this->beConstructedWith($extendedAdapter);
$extendedAdapter->exists('filename')->willReturn(true);
$extendedAdapter->createFile('filename', $this)->willReturn($file);
$this->get('filename')->shouldBe($file);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_writes_content_to_new_file(Adapter $adapter)
{
$adapter->exists('filename')->shouldBeCalled()->willReturn(false);
$adapter->write('filename', 'some content to write')->shouldBeCalled()->willReturn(21);
$this->write('filename', 'some content to write')->shouldReturn(21);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_updates_content_of_file(Adapter $adapter)
{
$adapter->write('filename', 'some content to write')->shouldBeCalled()->willReturn(21);
$this->write('filename', 'some content to write', true)->shouldReturn(21);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_does_not_update_content_of_file_when_file_cannot_be_overwriten(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$adapter->write('filename', 'some content to write')->shouldNotBeCalled();
$this
->shouldThrow(new \Gaufrette\Exception\FileAlreadyExists('filename'))
->duringWrite('filename', 'some content to write')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_fails_when_write_is_not_successful(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(false);
$adapter->write('filename', 'some content to write')->shouldBeCalled()->willReturn(false);
$this
->shouldThrow(new \RuntimeException('Could not write the "filename" key content.'))
->duringWrite('filename', 'some content to write')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_read_file(Adapter $adapter)
{
$adapter->exists('filename')->shouldBeCalled()->willReturn(true);
$adapter->read('filename')->shouldBeCalled()->willReturn('Some content');
$this->read('filename')->shouldReturn('Some content');
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_does_not_read_file_which_does_not_exist(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(false);
$this
->shouldThrow(new \Gaufrette\Exception\FileNotFound('filename'))
->duringRead('filename');
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_fails_when_read_is_not_successful(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$adapter->read('filename')->willReturn(false);
$this
->shouldThrow(new \RuntimeException('Could not read the "filename" key content.'))
->duringRead('filename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_deletes_file(Adapter $adapter)
{
$adapter->exists('filename')->shouldBeCalled()->willReturn(true);
$adapter->delete('filename')->shouldBeCalled()->willReturn(true);
$this->delete('filename')->shouldReturn(true);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_does_not_delete_file_which_does_not_exist(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(false);
$this
->shouldThrow(new \Gaufrette\Exception\FileNotFound('filename'))
->duringDelete('filename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_fails_when_delete_is_not_successful(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$adapter->delete('filename')->willReturn(false);
$this
->shouldThrow(new \RuntimeException('Could not remove the "filename" key.'))
->duringDelete('filename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_should_get_all_keys(Adapter $adapter)
{
$keys = ['filename', 'filename1', 'filename2'];
$adapter->keys()->willReturn($keys);
$this->keys()->shouldReturn($keys);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_match_listed_keys_using_specified_pattern(Adapter $adapter)
{
$keys = ['filename', 'filename1', 'filename2', 'testKey', 'KeyTest', 'testkey'];
$adapter->keys()->willReturn($keys);
$adapter->isDirectory(Argument::any())->willReturn(false);
$this->listKeys()->shouldReturn(
[
'keys' => ['filename', 'filename1', 'filename2', 'testKey', 'KeyTest', 'testkey'],
'dirs' => [],
]
);
$this->listKeys('filename')->shouldReturn(
[
'keys' => ['filename', 'filename1', 'filename2'],
'dirs' => [],
]
);
$this->listKeys('Key')->shouldReturn(
[
'keys' => ['KeyTest'],
'dirs' => [],
]
);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_listing_directories_using_adapter_is_directory_method(Adapter $adapter)
{
$keys = ['filename', 'filename1', 'filename2', 'testKey', 'KeyTest', 'testkey'];
$adapter->keys()->willReturn($keys);
$adapter->isDirectory('filename')->willReturn(false);
$adapter->isDirectory('filename2')->willReturn(false);
$adapter->isDirectory('KeyTest')->willReturn(false);
$adapter->isDirectory('testkey')->willReturn(false);
$adapter->isDirectory('filename1')->willReturn(true);
$adapter->isDirectory('testKey')->willReturn(true);
$this->listKeys()->shouldReturn(
[
'keys' => ['filename', 'filename2', 'KeyTest', 'testkey'],
'dirs' => ['filename1', 'testKey'],
]
);
$this->listKeys('filename')->shouldReturn(
[
'keys' => ['filename', 'filename2'],
'dirs' => ['filename1'],
]
);
$this->listKeys('Key')->shouldReturn(
[
'keys' => ['KeyTest'],
'dirs' => [],
]
);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_gets_mtime_of_file_using_adapter(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$adapter->mtime('filename')->willReturn(1234567);
$this->mtime('filename')->shouldReturn(1234567);
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_does_not_get_mtime_of_file_which_does_not_exist(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(false);
$this
->shouldThrow(new \Gaufrette\Exception\FileNotFound('filename'))
->duringMtime('filename')
;
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_calculates_file_checksum(Adapter $adapter)
{
$adapter->exists('filename')->shouldBeCalled()->willReturn(true);
$adapter->read('filename')->willReturn('some content');
$this->checksum('filename')->shouldReturn(md5('some content'));
}
/**
* @param \Gaufrette\Adapter $adapter
*/
function it_does_not_calculate_checksum_of_file_which_does_not_exist(Adapter $adapter)
{
$adapter->exists('filename')->shouldBeCalled()->willReturn(false);
$this
->shouldThrow(new \Gaufrette\Exception\FileNotFound('filename'))
->duringChecksum('filename');
}
/**
* @param \spec\Gaufrette\ExtendedAdapter $extendedAdapter
*/
function it_delegates_checksum_calculation_to_adapter_when_adapter_is_checksum_calculator(ExtendedAdapter $extendedAdapter)
{
$this->beConstructedWith($extendedAdapter);
$extendedAdapter->exists('filename')->shouldBeCalled()->willReturn(true);
$extendedAdapter->read('filename')->shouldNotBeCalled();
$extendedAdapter->checksum('filename')->shouldBeCalled()->willReturn(12);
$this->checksum('filename')->shouldReturn(12);
}
/**
* @param \spec\Gaufrette\ExtendedAdapter $extendedAdapter
*/
function it_delegates_mime_type_resolution_to_adapter_when_adapter_is_mime_type_provider(ExtendedAdapter $extendedAdapter)
{
$this->beConstructedWith($extendedAdapter);
$extendedAdapter->exists('filename')->willReturn(true);
$extendedAdapter->mimeType('filename')->willReturn('text/plain');
$this->mimeType('filename')->shouldReturn('text/plain');
}
function it_cannot_resolve_mime_type_if_the_adapter_cannot_provide_it(Adapter $adapter)
{
$adapter->exists('filename')->willReturn(true);
$this
->shouldThrow(new \LogicException(sprintf('Adapter "%s" cannot provide MIME type', get_class($adapter->getWrappedObject()))))
->duringMimeType('filename');
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace spec\Gaufrette\Stream;
use PhpSpec\ObjectBehavior;
use Gaufrette\StreamMode;
use org\bovigo\vfs\vfsStream;
class LocalSpec extends ObjectBehavior
{
function it_throws_runtime_exception_when_file_doesnt_exists()
{
$this->beConstructedWith(vfsStream::url('other'));
$this->shouldThrow('\RuntimeException')->duringOpen(new StreamMode('r'));
}
function it_throws_runtime_exception_when_file_doesnt_exists_and_custom_error_handler_specified()
{
$custom_error_handler = function ($errno, $errstr, $errfile, $errline) {
throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
};
set_error_handler($custom_error_handler);
$this->beConstructedWith(vfsStream::url('other'));
$this->shouldThrow('\RuntimeException')->duringOpen(new StreamMode('r'));
restore_error_handler();
}
}

View File

@@ -0,0 +1,142 @@
<?php
namespace spec\Gaufrette;
use PhpSpec\ObjectBehavior;
class StreamModeSpec extends ObjectBehavior
{
function it_is_initializable()
{
$this->beConstructedWith('r');
$this->shouldHaveType('Gaufrette\StreamMode');
}
function it_gives_access_to_mode()
{
$this->beConstructedWith('r+');
$this->getMode()->shouldReturn('r+');
}
function it_allows_write_only()
{
$this->beConstructedWith('w');
$this->allowsWrite()->shouldReturn(true);
$this->allowsRead()->shouldReturn(false);
}
function it_allows_write_and_read()
{
$this->beConstructedWith('w+');
$this->allowsWrite()->shouldReturn(true);
$this->allowsRead()->shouldReturn(true);
}
function it_allows_read_only()
{
$this->beConstructedWith('r');
$this->allowsWrite()->shouldReturn(false);
$this->allowsRead()->shouldReturn(true);
}
function it_allows_to_existing_file_opening()
{
$this->beConstructedWith('r');
$this->allowsExistingFileOpening()->shouldReturn(true);
}
function it_does_not_allow_to_existing_file_opening()
{
$this->beConstructedWith('x');
$this->allowsExistingFileOpening()->shouldReturn(false);
}
function it_allows_new_file_opening()
{
$this->beConstructedWith('w');
$this->allowsNewFileOpening()->shouldReturn(true);
}
function it_does_not_allow_new_file_opening()
{
$this->beConstructedWith('r');
$this->allowsNewFileOpening()->shouldReturn(false);
}
function it_implies_existing_content_deletion()
{
$this->beConstructedWith('w+');
$this->allowsNewFileOpening()->shouldReturn(true);
}
function it_does_not_implies_existing_content_deletion()
{
$this->beConstructedWith('r+');
$this->allowsNewFileOpening()->shouldReturn(false);
}
function it_implies_positioning_cursor_at_the_beginning()
{
$this->beConstructedWith('r+');
$this->impliesPositioningCursorAtTheBeginning()->shouldReturn(true);
}
function it_does_no_implies_positioning_cursor_at_the_beginning()
{
$this->beConstructedWith('a');
$this->impliesPositioningCursorAtTheBeginning()->shouldReturn(false);
}
function it_implies_positioning_cursor_at_the_end()
{
$this->beConstructedWith('a');
$this->impliesPositioningCursorAtTheEnd()->shouldReturn(true);
}
function it_does_no_implies_positioning_cursor_at_the_end()
{
$this->beConstructedWith('w');
$this->impliesPositioningCursorAtTheEnd()->shouldReturn(false);
}
function it_should_be_binary()
{
$this->beConstructedWith('wb+');
$this->isBinary()->shouldReturn(true);
}
function it_should_not_be_binary()
{
$this->beConstructedWith('w+');
$this->isBinary()->shouldReturn(false);
}
function it_should_not_be_text()
{
$this->beConstructedWith('wb+');
$this->isText()->shouldReturn(false);
}
function it_should_be_text()
{
$this->beConstructedWith('w+');
$this->isText()->shouldReturn(true);
}
}

View File

@@ -0,0 +1,299 @@
<?php
namespace spec\Gaufrette;
use Gaufrette\FilesystemMap;
use Gaufrette\Filesystem;
use Gaufrette\Stream;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class StreamWrapperSpec extends ObjectBehavior
{
/**
* @param \Gaufrette\FilesystemMap $map
* @param \Gaufrette\Filesystem $filesystem
* @param \Gaufrette\Stream $stream
*/
function let(FilesystemMap $map, Filesystem $filesystem, Stream $stream)
{
$filesystem->createStream('filename')->willReturn($stream);
$map->get('some')->willReturn($filesystem);
$this->setFilesystemMap($map);
}
function it_is_initializable()
{
$this->shouldHaveType('Gaufrette\StreamWrapper');
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_opens_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$this->stream_open('gaufrette://some/filename', 'r+')->shouldReturn(true);
}
function it_does_not_open_stream_when_key_is_not_defined()
{
$this
->shouldThrow(new \InvalidArgumentException('The specified path (gaufrette://some) is invalid.'))
->duringStream_open('gaufrette://some', 'r+');
}
function it_does_not_open_stream_when_host_is_not_defined()
{
$this
->shouldThrow(new \InvalidArgumentException('The specified path (gaufrette:///somefile) is invalid.'))
->duringStream_open('gaufrette:///somefile', 'r+')
;
}
function it_does_not_read_from_stream_when_is_not_opened()
{
$this->stream_read(10)->shouldReturn(false);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_not_read_from_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->read(4)->willReturn('some');
$this->stream_open('gaufrette://some/filename', 'r+');
$this->stream_read(4)->shouldReturn('some');
}
function it_does_not_write_to_stream_when_is_not_opened()
{
$this->stream_write('some content')->shouldReturn(0);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_writes_to_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->write('some content')->shouldBeCalled()->willReturn(12);
$this->stream_open('gaufrette://some/filename', 'w+');
$this->stream_write('some content')->shouldReturn(12);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_not_close_stream_when_is_not_opened($stream)
{
$stream->close()->shouldNotBeCalled();
$this->stream_close();
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_closes_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->close()->shouldBeCalled();
$this->stream_open('gaufrette://some/filename', 'w+');
$this->stream_close();
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_not_flush_stream_when_is_not_opened(Stream $stream)
{
$stream->flush()->shouldNotBeCalled();
$this->stream_flush();
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_flushes_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->flush()->shouldBeCalled();
$this->stream_open('gaufrette://some/filename', 'w+');
$this->stream_flush();
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_not_seek_in_stream_when_is_not_opened(Stream $stream)
{
$stream->seek(12, SEEK_SET)->shouldNotBeCalled();
$this->stream_seek(12, SEEK_SET);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_seeks_in_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->seek(12, SEEK_SET)->shouldBeCalled()->willReturn(true);
$this->stream_open('gaufrette://some/filename', 'w+');
$this->stream_seek(12, SEEK_SET)->shouldReturn(true);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_not_tell_about_position_in_stream_when_is_not_opened(Stream $stream)
{
$stream->tell()->shouldNotBeCalled();
$this->stream_tell();
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_tell_about_position_in_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->tell()->shouldBeCalled()->willReturn(12);
$this->stream_open('gaufrette://some/filename', 'w+');
$this->stream_tell()->shouldReturn(12);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_not_mark_as_eof_if_stream_is_not_opened(Stream $stream)
{
$stream->eof()->shouldNotBeCalled();
$this->stream_eof();
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_checks_if_eof(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$this->stream_open('gaufrette://some/filename', 'w+');
$stream->eof()->willReturn(false);
$this->stream_eof()->shouldReturn(false);
$stream->eof()->willReturn(true);
$this->stream_eof()->shouldReturn(true);
}
function it_does_not_get_stat_when_is_not_open()
{
$this->stream_stat()->shouldReturn(false);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_stats_file(Stream $stream)
{
$stat = [
'dev' => 1,
'ino' => 12,
'mode' => 0777,
'nlink' => 0,
'uid' => 123,
'gid' => 1,
'rdev' => 0,
'size' => 666,
'atime' => 1348030800,
'mtime' => 1348030800,
'ctime' => 1348030800,
'blksize' => 5,
'blocks' => 1,
];
$stream->open(Argument::any())->willReturn(true);
$stream->stat()->willReturn($stat);
$this->stream_open('gaufrette://some/filename', 'w+');
$this->stream_stat()->shouldReturn($stat);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_should_stat_from_url(Stream $stream)
{
$stat = [
'dev' => 1,
'ino' => 12,
'mode' => 0777,
'nlink' => 0,
'uid' => 123,
'gid' => 1,
'rdev' => 0,
'size' => 666,
'atime' => 1348030800,
'mtime' => 1348030800,
'ctime' => 1348030800,
'blksize' => 5,
'blocks' => 1,
];
$stream->open(Argument::any())->willReturn(true);
$stream->stat()->willReturn($stat);
$this->url_stat('gaufrette://some/filename', STREAM_URL_STAT_LINK)->shouldReturn($stat);
}
/**
* @param \Gaufrette\Filesystem $stream
* @param \Gaufrette\Stream $stream
*/
function it_stats_even_if_it_cannot_be_open(Filesystem $filesystem, Stream $stream)
{
$filesystem->createStream('dir/')->willReturn($stream);
$stream->open(Argument::any())->willThrow(new \RuntimeException);
$stream->stat(Argument::any())->willReturn(['mode' => 16893]);
$this->url_stat('gaufrette://some/dir/', STREAM_URL_STAT_LINK)->shouldReturn(['mode' => 16893]);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_does_not_unlink_when_cannot_open(Stream $stream)
{
$stream->open(Argument::any())->willThrow(new \RuntimeException);
$this->unlink('gaufrette://some/filename')->shouldReturn(false);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_unlinks_file(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->unlink()->willReturn(true);
$this->unlink('gaufrette://some/filename')->shouldReturn(true);
}
function it_does_not_cast_stream_if_is_not_opened()
{
$this->stream_cast(STREAM_CAST_FOR_SELECT)->shouldReturn(false);
}
/**
* @param \Gaufrette\Stream $stream
*/
function it_casts_stream(Stream $stream)
{
$stream->open(Argument::any())->willReturn(true);
$stream->cast(STREAM_CAST_FOR_SELECT)->willReturn('resource');
$this->stream_open('gaufrette://some/filename', 'w+');
$this->stream_cast(STREAM_CAST_FOR_SELECT)->shouldReturn('resource');
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace spec\Gaufrette\Util;
use PhpSpec\ObjectBehavior;
class ChecksumSpec extends ObjectBehavior
{
function let()
{
file_put_contents($this->getTestFilePath(), 'some other content');
}
function letGo()
{
@unlink($this->getTestFilePath());
}
function it_calculates_checksum_from_content()
{
$this->fromContent('some content')
->shouldReturn(md5('some content'))
;
}
function it_calculates_checksum_from_filepath()
{
$this->fromFile($this->getTestFilePath())
->shouldReturn(md5('some other content'))
;
}
private function getTestFilePath(): string
{
return __DIR__ . DIRECTORY_SEPARATOR . 'testFile';
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace spec\Gaufrette\Util;
use PhpSpec\ObjectBehavior;
class PathSpec extends ObjectBehavior
{
function it_checks_if_path_is_absolute()
{
$this->isAbsolute('/home/path')->shouldBe(true);
$this->isAbsolute('home/path')->shouldBe(false);
$this->isAbsolute('../home/path')->shouldBe(false);
$this->isAbsolute('protocol://home/path')->shouldBe(true);
}
function it_normalizes_file_path()
{
$this->normalize('C:\\some\other.txt')->shouldReturn('c:/some/other.txt');
$this->normalize('..\other.txt')->shouldReturn('../other.txt');
$this->normalize('..\other.txt')->shouldReturn('../other.txt');
$this->normalize('/home/other/../new')->shouldReturn('/home/new');
$this->normalize('/home/other/./new')->shouldReturn('/home/other/new');
$this->normalize('protocol://home/other.txt')->shouldReturn('protocol://home/other.txt');
}
function it_returns_unix_style_dirname()
{
$this->dirname('a/test/path')->shouldReturn('a/test');
}
}

View File

@@ -0,0 +1,15 @@
<?php
namespace spec\Gaufrette\Util;
use PhpSpec\ObjectBehavior;
class SizeSpec extends ObjectBehavior
{
function it_calculates_size_of_content()
{
$this->fromContent('some content')->shouldReturn(12);
$this->fromContent('some other content')->shouldReturn(18);
$this->fromContent('some')->shouldReturn(4);
}
}

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;
}
}

View File

@@ -0,0 +1,152 @@
<?php
namespace Gaufrette\Functional\Adapter;
use AsyncAws\SimpleS3\SimpleS3Client;
use Gaufrette\Adapter\AsyncAwsS3;
use Gaufrette\Adapter\AwsS3;
use Gaufrette\Filesystem;
class AsyncAwsS3Test extends FunctionalTestCase
{
/** @var string */
private $bucket;
/** @var SimpleS3Client */
private $client;
protected function setUp(): void
{
if (!class_exists(SimpleS3Client::class)) {
$this->markTestSkipped('You need to install async-aws/simple-s3 to run this test.');
}
$key = getenv('AWS_KEY');
$secret = getenv('AWS_SECRET');
if (empty($key) || empty($secret)) {
$this->markTestSkipped('Either AWS_KEY and/or AWS_SECRET env variables are not defined.');
}
$this->bucket = uniqid(getenv('AWS_BUCKET'));
$this->client = new SimpleS3Client([
'region' => 'eu-west-1',
'accessKeyId' => $key,
'accessKeySecret' => $secret,
]);
$this->createFilesystem(['create' => true]);
}
protected function tearDown(): void
{
if ($this->client === null) {
return;
}
try {
$files = $this->filesystem->listKeys();
foreach ($files as $file) {
$this->filesystem->delete($file);
}
$this->client->deleteBucket(['Bucket' => $this->bucket]);
} catch (\Throwable $e) {
}
}
private function createFilesystem(array $adapterOptions = [])
{
$this->filesystem = new Filesystem(new AsyncAwsS3($this->client, $this->bucket, $adapterOptions));
}
/**
* @test
*/
public function shouldThrowExceptionIfBucketMissingAndNotCreating(): void
{
$this->expectException(\RuntimeException::class);
$this->createFilesystem();
$this->filesystem->read('foo');
}
/**
* @test
*/
public function shouldWriteObjects(): void
{
$this->assertEquals(7, $this->filesystem->write('foo', 'testing'));
}
/**
* @test
*/
public function shouldCheckForObjectExistence(): void
{
$this->filesystem->write('foo', '');
$this->assertTrue($this->filesystem->has('foo'));
}
/**
* @test
*/
public function shouldCheckForObjectExistenceWithDirectory(): void
{
$this->createFilesystem(['directory' => 'bar', 'create' => true]);
$this->filesystem->write('foo', '');
$this->assertTrue($this->filesystem->has('foo'));
}
/**
* @test
*/
public function shouldListKeysWithoutDirectory(): void
{
$this->assertEquals([], $this->filesystem->listKeys());
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->listKeys());
}
/**
* @test
*/
public function shouldListKeysWithDirectory(): void
{
$this->createFilesystem(['create' => true, 'directory' => 'root/']);
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->listKeys());
$this->assertTrue($this->filesystem->has('test.txt'));
}
/**
* @test
*/
public function shouldGetKeysWithoutDirectory(): void
{
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->keys());
}
/**
* @test
*/
public function shouldGetKeysWithDirectory(): void
{
$this->createFilesystem(['create' => true, 'directory' => 'root/']);
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->keys());
}
/**
* @test
*/
public function shouldUploadWithGivenContentType(): void
{
/** @var AwsS3 $adapter */
$adapter = $this->filesystem->getAdapter();
$adapter->setMetadata('foo', ['ContentType' => 'text/html']);
$this->filesystem->write('foo', '<html></html>');
$this->assertEquals('text/html', $this->filesystem->mimeType('foo'));
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Aws\S3\S3Client;
use Gaufrette\Adapter\AwsS3;
use Gaufrette\Filesystem;
class AwsS3Test extends FunctionalTestCase
{
/** @var int */
private static $SDK_VERSION;
/** @var string */
private $bucket;
/** @var S3Client */
private $client;
protected function setUp(): void
{
$key = getenv('AWS_KEY');
$secret = getenv('AWS_SECRET');
if (empty($key) || empty($secret)) {
$this->markTestSkipped('Either AWS_KEY and/or AWS_SECRET env variables are not defined.');
}
if (self::$SDK_VERSION === null) {
self::$SDK_VERSION = method_exists(S3Client::class, 'getArguments') ? 3 : 2;
}
$this->bucket = uniqid(getenv('AWS_BUCKET'));
if (self::$SDK_VERSION === 3) {
// New way of instantiating S3Client for aws-sdk-php v3
$this->client = new S3Client([
'region' => 'eu-west-1',
'version' => 'latest',
'credentials' => [
'key' => $key,
'secret' => $secret,
],
]);
} else {
$this->client = S3Client::factory([
'region' => 'eu-west-1',
'version' => '2006-03-01',
'key' => $key,
'secret' => $secret,
]);
}
$this->createFilesystem(['create' => true]);
}
protected function tearDown(): void
{
if ($this->client === null || !$this->client->doesBucketExist($this->bucket)) {
return;
}
$result = $this->client->listObjects(['Bucket' => $this->bucket]);
if (!$result->hasKey('Contents')) {
$this->client->deleteBucket(['Bucket' => $this->bucket]);
return;
}
foreach ($result->get('Contents') as $staleObject) {
$this->client->deleteObject(['Bucket' => $this->bucket, 'Key' => $staleObject['Key']]);
}
$this->client->deleteBucket(['Bucket' => $this->bucket]);
}
private function createFilesystem(array $adapterOptions = [])
{
$this->filesystem = new Filesystem(new AwsS3($this->client, $this->bucket, $adapterOptions));
}
/**
* @test
*/
public function shouldThrowExceptionIfBucketMissingAndNotCreating(): void
{
$this->expectException(\RuntimeException::class);
$this->createFilesystem();
$this->filesystem->read('foo');
}
/**
* @test
*/
public function shouldWriteObjects(): void
{
$this->assertEquals(7, $this->filesystem->write('foo', 'testing'));
}
/**
* @test
*/
public function shouldCheckForObjectExistence(): void
{
$this->filesystem->write('foo', '');
$this->assertTrue($this->filesystem->has('foo'));
}
/**
* @test
*/
public function shouldCheckForObjectExistenceWithDirectory(): void
{
$this->createFilesystem(['directory' => 'bar', 'create' => true]);
$this->filesystem->write('foo', '');
$this->assertTrue($this->filesystem->has('foo'));
}
/**
* @test
*/
public function shouldListKeysWithoutDirectory(): void
{
$this->assertEquals([], $this->filesystem->listKeys());
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->listKeys());
}
/**
* @test
*/
public function shouldListKeysWithDirectory(): void
{
$this->createFilesystem(['create' => true, 'directory' => 'root/']);
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->listKeys());
$this->assertTrue($this->filesystem->has('test.txt'));
}
/**
* @test
*/
public function shouldGetKeysWithoutDirectory(): void
{
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->keys());
}
/**
* @test
*/
public function shouldGetKeysWithDirectory(): void
{
$this->createFilesystem(['create' => true, 'directory' => 'root/']);
$this->filesystem->write('test.txt', 'some content');
$this->assertEquals(['test.txt'], $this->filesystem->keys());
}
/**
* @test
*/
public function shouldUploadWithGivenContentType(): void
{
/** @var AwsS3 $adapter */
$adapter = $this->filesystem->getAdapter();
$adapter->setMetadata('foo', ['ContentType' => 'text/html']);
$this->filesystem->write('foo', '<html></html>');
$this->assertEquals('text/html', $this->filesystem->mimeType('foo'));
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Adapter\AzureBlobStorage;
use Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactory;
use Gaufrette\Filesystem;
/**
* Class AzureBlobStorageTest
* @group AzureBlobStorage
*/
class AzureBlobStorageTest extends FunctionalTestCase
{
/** @var string Name of the Azure container used */
private $container;
/** @var AzureBlobStorage */
private $adapter;
protected function setUp(): void
{
$account = getenv('AZURE_ACCOUNT');
$key = getenv('AZURE_KEY');
$containerName = getenv('AZURE_CONTAINER');
if (empty($account) || empty($key) || empty($containerName)) {
$this->markTestSkipped('Either AZURE_ACCOUNT, AZURE_KEY and/or AZURE_CONTAINER env variables are not defined.');
}
$connection = sprintf('DefaultEndpointsProtocol=https;AccountName=%s;AccountKey=%s;EndpointSuffix=core.windows.net', $account, $key);
$this->container = uniqid($containerName);
$this->adapter = new AzureBlobStorage(new BlobProxyFactory($connection), $this->container, true);
$this->filesystem = new Filesystem($this->adapter);
}
/**
* @test
* @group functional
*/
public function shouldGetContentType(): void
{
$path = '/somefile';
$content = 'Some content';
$this->filesystem->write($path, $content);
$this->assertEquals('text/plain', $this->filesystem->mimeType($path));
}
protected function tearDown(): void
{
if ($this->adapter === null) {
return;
}
$this->adapter->deleteContainer($this->container);
}
}

View File

@@ -0,0 +1,270 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Adapter\AzureBlobStorage;
use Gaufrette\Adapter\AzureBlobStorage\BlobProxyFactory;
use Gaufrette\Filesystem;
/**
* Class AzureMultiContainerBlobStorageTest
* @group AzureBlobStorage
* @group AzureMultiContainerBlobStorage
*/
class AzureMultiContainerBlobStorageTest extends FunctionalTestCase
{
private $adapter;
private $containers = [];
protected function setUp(): void
{
$this->markTestSkipped(__CLASS__ . ' is flaky.');
$account = getenv('AZURE_ACCOUNT');
$key = getenv('AZURE_KEY');
if (empty($account) || empty($key)) {
$this->markTestSkipped('Either AZURE_ACCOUNT and/or AZURE_KEY env variables are not defined.');
}
$connection = sprintf('BlobEndpoint=https://%1$s.blob.core.windows.net/;AccountName=%1$s;AccountKey=%2$s', $account, $key);
$this->adapter = new AzureBlobStorage(new BlobProxyFactory($connection));
$this->filesystem = new Filesystem($this->adapter);
}
/**
* @test
* @group functional
*/
public function shouldWriteAndRead(): void
{
$path1 = $this->createUniqueContainerName('container') . '/foo';
$path2 = $this->createUniqueContainerName('test') . '/subdir/foo';
$this->assertEquals(12, $this->filesystem->write($path1, 'Some content'));
$this->assertEquals(13, $this->filesystem->write($path2, 'Some content1', true));
$this->assertEquals('Some content', $this->filesystem->read($path1));
$this->assertEquals('Some content1', $this->filesystem->read($path2));
}
/**
* @test
* @group functional
*/
public function shouldUpdateFileContent(): void
{
$path = $this->createUniqueContainerName('container') . '/foo';
$this->filesystem->write($path, 'Some content');
$this->filesystem->write($path, 'Some content updated', true);
$this->assertEquals('Some content updated', $this->filesystem->read($path));
}
/**
* @test
* @group functional
*/
public function shouldCheckIfFileExists(): void
{
$path1 = $this->createUniqueContainerName('container') . '/foo';
$path2 = $this->createUniqueContainerName('test') . '/somefile';
$this->assertFalse($this->filesystem->has($path1));
$this->filesystem->write($path1, 'Some content');
$this->assertTrue($this->filesystem->has($path1));
// @TODO: why is it done two times?
$this->assertFalse($this->filesystem->has($path2));
$this->assertFalse($this->filesystem->has($path2));
}
/**
* @test
* @group functional
*/
public function shouldGetMtime(): void
{
$path = $this->createUniqueContainerName('container') . '/foo';
$this->filesystem->write($path, 'Some content');
$this->assertGreaterThan(0, $this->filesystem->mtime($path));
}
/**
* @test
* @group functional
*/
public function shouldGetSize(): void
{
$path = $this->createUniqueContainerName('container') . '/foo';
$contentSize = $this->filesystem->write($path, 'Some content');
$this->assertEquals($contentSize, $this->filesystem->size($path));
}
/**
* @test
* @group functional
*/
public function shouldGetMd5Hash(): void
{
$path = $this->createUniqueContainerName('container') . '/foo';
$content = 'Some content';
$this->filesystem->write($path, $content);
$this->assertEquals(\md5($content), $this->filesystem->checksum($path));
}
/**
* @test
* @group functional
*/
public function shouldGetContentType(): void
{
$path = $this->createUniqueContainerName('container') . '/foo';
$content = 'Some content';
$this->filesystem->write($path, $content);
$this->assertEquals('text/plain', $this->filesystem->mimeType($path));
}
/**
* @test
* @group functional
* @expectedException \RuntimeException
* @expectedMessage Could not get mtime for the "foo" key
*/
public function shouldFailWhenTryMtimeForKeyWhichDoesNotExist(): void
{
$this->assertFalse($this->filesystem->mtime('container5/foo'));
}
/**
* @test
* @group functional
*/
public function shouldRenameFile(): void
{
$somedir = $this->createUniqueContainerName('somedir');
$path1 = $this->createUniqueContainerName('container') . '/foo';
$path2 = $this->createUniqueContainerName('container-new') . '/boo';
$path3 = $somedir . '/sub/boo';
$this->filesystem->write($path1, 'Some content');
$this->filesystem->rename($path1, $path2);
$this->assertFalse($this->filesystem->has($path1));
$this->assertEquals('Some content', $this->filesystem->read($path2));
$this->filesystem->write($path1, 'Some content');
$this->filesystem->rename($path1, $path3);
$this->assertFalse($this->filesystem->has($somedir . '/sub/foo'));
$this->assertEquals('Some content', $this->filesystem->read($path3));
}
/**
* @test
* @group functional
*/
public function shouldDeleteFile(): void
{
$path = $this->createUniqueContainerName('container') . '/foo';
$this->filesystem->write($path, 'Some content');
$this->assertTrue($this->filesystem->has($path));
$this->filesystem->delete($path);
$this->assertFalse($this->filesystem->has($path));
}
/**
* @test
* @group functional
*/
public function shouldFetchKeys(): void
{
$path1 = $this->createUniqueContainerName('container-1') . '/foo';
$path2 = $this->createUniqueContainerName('container-2') . '/bar';
$path3 = $this->createUniqueContainerName('container-3') . '/baz';
$this->filesystem->write($path1, 'Some content');
$this->filesystem->write($path2, 'Some content');
$this->filesystem->write($path3, 'Some content');
$actualKeys = $this->filesystem->keys();
foreach ([$path1, $path2, $path3] as $key) {
$this->assertContains($key, $actualKeys);
}
}
/**
* @test
* @group functional
*/
public function shouldWorkWithHiddenFiles(): void
{
$path = $this->createUniqueContainerName('container') . '/.foo';
$this->filesystem->write($path, 'hidden');
$this->assertTrue($this->filesystem->has($path));
$this->assertContains($path, $this->filesystem->keys());
$this->filesystem->delete($path);
$this->assertFalse($this->filesystem->has($path));
}
/**
* @test
* @group functional
*/
public function shouldKeepFileObjectInRegister(): void
{
$path = $this->createUniqueContainerName('container') . '/somefile';
$FileObjectA = $this->filesystem->createFile($path);
$FileObjectB = $this->filesystem->createFile($path);
$this->assertSame($FileObjectB, $FileObjectA);
}
/**
* @test
* @group functional
*/
public function shouldWriteToSameFile(): void
{
$path = $this->createUniqueContainerName('container') . '/somefile';
$FileObjectA = $this->filesystem->createFile($path);
$FileObjectA->setContent('ABC');
$FileObjectB = $this->filesystem->createFile($path);
$FileObjectB->setContent('DEF');
$this->assertEquals('DEF', $FileObjectA->getContent());
}
private function createUniqueContainerName($prefix)
{
$this->containers[] = $container = uniqid($prefix);
return $container;
}
protected function tearDown(): void
{
foreach ($this->containers as $container) {
$this->adapter->deleteContainer($container);
}
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Doctrine\DBAL\DriverManager;
use Gaufrette\Adapter\DoctrineDbal;
use Gaufrette\Filesystem;
class DoctrineDbalTest extends FunctionalTestCase
{
/** @var \Doctrine\DBAL\Connection */
private $connection;
public static function setUpBeforeClass(): void
{
if (!class_exists(DriverManager::class)) {
self::markTestSkipped('Package doctrine/dbal is not installed');
}
parent::setUpBeforeClass();
}
protected function setUp(): void
{
$this->connection = DriverManager::getConnection([
'driver' => 'pdo_sqlite',
'memory' => true,
]);
$schema = $this->connection->getSchemaManager()->createSchema();
$table = $schema->createTable('gaufrette');
$column = $table->addColumn('key', 'string');
if (method_exists($column, 'setPlatformOption')) {
// dbal 3.4+
$column->setPlatformOption('unique', true);
} else {
// BC layer dbal 2.x
$column->setUnique(true);
}
$table->addColumn('content', 'blob');
$table->addColumn('mtime', 'integer');
$table->addColumn('checksum', 'string', ['length' => 32]);
// Generates the SQL from the defined schema and execute each line
array_map([$this->connection, 'exec'], $schema->toSql($this->connection->getDatabasePlatform()));
$this->filesystem = new Filesystem(new DoctrineDbal($this->connection, 'gaufrette'));
}
protected function tearDown(): void
{
$schemaManager = $this->connection->getSchemaManager();
if (in_array('gaufrette', $schemaManager->listTableNames())) {
$schemaManager->dropTable('gaufrette');
}
}
/**
* @test
*/
public function shouldListKeys(): void
{
$this->filesystem->write('foo/foobar/bar.txt', 'data');
$this->filesystem->write('foo/bar/buzz.txt', 'data');
$this->filesystem->write('foobarbuz.txt', 'data');
$this->filesystem->write('foo', 'data');
$allKeys = $this->filesystem->listKeys(' ');
//empty pattern results in ->keys call
$this->assertEquals(
$this->filesystem->keys(),
$allKeys['keys']
);
//these values are canonicalized to avoid wrong order or keys issue
$keys = $this->filesystem->listKeys('foo');
$this->assertEqualsCanonicalizing(
$this->filesystem->keys(),
$keys['keys']
);
$keys = $this->filesystem->listKeys('foo/foob');
$this->assertEqualsCanonicalizing(
['foo/foobar/bar.txt'],
$keys['keys']
);
$keys = $this->filesystem->listKeys('foo/');
$this->assertEqualsCanonicalizing(
['foo/foobar/bar.txt', 'foo/bar/buzz.txt'],
$keys['keys']
);
$keys = $this->filesystem->listKeys('foo');
$this->assertEqualsCanonicalizing(
['foo/foobar/bar.txt', 'foo/bar/buzz.txt', 'foobarbuz.txt', 'foo'],
$keys['keys']
);
$keys = $this->filesystem->listKeys('fooz');
$this->assertEqualsCanonicalizing(
[],
$keys['keys']
);
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Adapter\Ftp;
use Gaufrette\Filesystem;
class FtpTest extends FunctionalTestCase
{
protected function setUp(): void
{
$host = getenv('FTP_HOST');
$port = getenv('FTP_PORT');
$user = getenv('FTP_USER');
$password = getenv('FTP_PASSWORD');
$baseDir = getenv('FTP_BASE_DIR');
if ($user === false || $password === false || $host === false || $baseDir === false) {
$this->markTestSkipped('Either FTP_HOST, FTP_USER, FTP_PASSWORD and/or FTP_BASE_DIR env variables are not defined.');
}
$adapter = new Ftp($baseDir, $host, ['port' => $port, 'username' => $user, 'password' => $password, 'passive' => true, 'create' => true]);
$this->filesystem = new Filesystem($adapter);
}
protected function tearDown(): void
{
if (null === $this->filesystem) {
return;
}
$adapter = $this->filesystem->getAdapter();
foreach ($adapter->keys() as $key) {
if (!$adapter->isDirectory($key)) {
$adapter->delete($key);
}
}
$keys = $adapter->keys();
rsort($keys);
foreach ($keys as $key) {
$adapter->delete($key);
}
$adapter->close();
}
}

View File

@@ -0,0 +1,218 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Exception\FileNotFound;
use Gaufrette\Filesystem;
use PHPUnit\Framework\TestCase;
abstract class FunctionalTestCase extends TestCase
{
/**
* @var Filesystem
*/
protected $filesystem;
public function getAdapterName()
{
if (!preg_match('/\\\\(\w+)Test$/', get_class($this), $matches)) {
throw new \RuntimeException(sprintf(
'Unable to guess filesystem name from class "%s", ' .
'please override the ->getAdapterName() method.',
get_class($this)
));
}
return $matches[1];
}
protected function setUp(): void
{
$basename = $this->getAdapterName();
$filename = sprintf(
'%s/adapters/%s.php',
dirname(__DIR__),
$basename
);
if (!file_exists($filename)) {
$this->markTestSkipped(
<<<EOF
To run the {$basename} filesystem tests, you must:
1. Copy the file "{$filename}.dist" as "{$filename}"
2. Modify the copied file to fit your environment
EOF
);
}
$adapter = include $filename;
$this->filesystem = new Filesystem($adapter);
}
protected function tearDown(): void
{
if (null === $this->filesystem) {
return;
}
$this->filesystem = null;
}
/**
* @test
* @group functional
*/
public function shouldWriteAndRead(): void
{
$this->assertEquals(12, $this->filesystem->write('foo', 'Some content'));
$this->assertEquals(13, $this->filesystem->write('test/subdir/foo', 'Some content1', true));
$this->assertEquals('Some content', $this->filesystem->read('foo'));
$this->assertEquals('Some content1', $this->filesystem->read('test/subdir/foo'));
}
/**
* @test
* @group functional
*/
public function shouldUpdateFileContent(): void
{
$this->filesystem->write('foo', 'Some content');
$this->filesystem->write('foo', 'Some content updated', true);
$this->assertEquals('Some content updated', $this->filesystem->read('foo'));
}
/**
* @test
* @group functional
*/
public function shouldCheckIfFileExists(): void
{
$this->assertFalse($this->filesystem->has('foo'));
$this->filesystem->write('foo', 'Some content');
$this->assertTrue($this->filesystem->has('foo'));
$this->assertFalse($this->filesystem->has('test/somefile'));
$this->assertFalse($this->filesystem->has('test/somefile'));
}
/**
* @test
* @group functional
*/
public function shouldGetMtime(): void
{
$this->filesystem->write('foo', 'Some content');
$this->assertGreaterThan(0, $this->filesystem->mtime('foo'));
}
/**
* @test
* @group functional
*/
public function shouldFailWhenTryMtimeForKeyWhichDoesNotExist(): void
{
$this->expectException(FileNotFound::class);
$this->expectExceptionMessage('The file "foo" was not found.');
$this->filesystem->mtime('foo');
}
/**
* @test
* @group functional
*/
public function shouldRenameFile(): void
{
$this->filesystem->write('foo', 'Some content');
$this->filesystem->rename('foo', 'boo');
$this->assertFalse($this->filesystem->has('foo'));
$this->assertEquals('Some content', $this->filesystem->read('boo'));
$this->filesystem->delete('boo');
$this->filesystem->write('foo', 'Some content');
$this->filesystem->rename('foo', 'somedir/sub/boo');
$this->assertFalse($this->filesystem->has('somedir/sub/foo'));
$this->assertEquals('Some content', $this->filesystem->read('somedir/sub/boo'));
}
/**
* @test
* @group functional
*/
public function shouldDeleteFile(): void
{
$this->filesystem->write('foo', 'Some content');
$this->assertTrue($this->filesystem->has('foo'));
$this->filesystem->delete('foo');
$this->assertFalse($this->filesystem->has('foo'));
}
/**
* @test
* @group functional
*/
public function shouldFetchKeys(): void
{
$this->assertEquals([], $this->filesystem->keys());
$this->filesystem->write('foo', 'Some content');
$this->filesystem->write('bar', 'Some content');
$this->filesystem->write('baz', 'Some content');
$actualKeys = $this->filesystem->keys();
$this->assertCount(3, $actualKeys);
foreach (['foo', 'bar', 'baz'] as $key) {
$this->assertContains($key, $actualKeys);
}
}
/**
* @test
* @group functional
*/
public function shouldWorkWithHiddenFiles(): void
{
$this->filesystem->write('.foo', 'hidden');
$this->assertTrue($this->filesystem->has('.foo'));
$this->assertContains('.foo', $this->filesystem->keys());
$this->filesystem->delete('.foo');
$this->assertFalse($this->filesystem->has('.foo'));
}
/**
* @test
* @group functional
*/
public function shouldKeepFileObjectInRegister(): void
{
$FileObjectA = $this->filesystem->createFile('somefile');
$FileObjectB = $this->filesystem->createFile('somefile');
$this->assertSame($FileObjectA, $FileObjectB);
}
/**
* @test
* @group functional
*/
public function shouldWriteToSameFile(): void
{
$FileObjectA = $this->filesystem->createFile('somefile');
$FileObjectA->setContent('ABC');
$FileObjectB = $this->filesystem->createFile('somefile');
$FileObjectB->setContent('DEF');
$this->assertEquals('DEF', $FileObjectA->getContent());
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Adapter\GoogleCloudStorage;
/**
* Functional tests for the GoogleCloudStorage adapter.
*
* Copy the ../adapters/GoogleCloudStorage.php.dist to GoogleCloudStorage.php and
* adapt to your needs.
*
* @author Patrik Karisch <patrik@karisch.guru>
*/
class GoogleCloudStorageTest extends FunctionalTestCase
{
/**
* @test
* @group functional
*/
public function shouldThrowExceptionIfBucketMissing()
{
$this->expectException(\RuntimeException::class);
/** @var \Gaufrette\Adapter\GoogleCloudStorage $adapter */
$adapter = $this->filesystem->getAdapter();
$adapter->setOptions([GoogleCloudStorage::OPTION_CREATE_BUCKET_IF_NOT_EXISTS => false]);
$adapter->setBucket('Gaufrette-' . mt_rand());
$adapter->read('foo');
}
/**
* @test
* @group functional
*/
public function shouldWriteAndReadWithDirectory()
{
/** @var \Gaufrette\Adapter\GoogleCloudStorage $adapter */
$adapter = $this->filesystem->getAdapter();
$oldOptions = $adapter->getOptions();
$adapter->setOptions(['directory' => 'Gaufrette']);
$this->assertEquals(12, $this->filesystem->write('foo', 'Some content'));
$this->assertEquals(13, $this->filesystem->write('test/subdir/foo', 'Some content1', true));
$this->assertEquals('Some content', $this->filesystem->read('foo'));
$this->assertEquals('Some content1', $this->filesystem->read('test/subdir/foo'));
$this->filesystem->delete('foo');
$this->filesystem->delete('test/subdir/foo');
$adapter->setOptions($oldOptions);
}
/**
* @test
* @group functional
*/
public function shouldSetMetadataCorrectly()
{
/** @var \Gaufrette\Adapter\GoogleCloudStorage $adapter */
$adapter = $this->filesystem->getAdapter();
$adapter->setMetadata('metadata.txt', [
'CacheControl' => 'public, maxage=7200',
'ContentDisposition' => 'attachment; filename="test.txt"',
'ContentEncoding' => 'identity',
'ContentLanguage' => 'en',
'Colour' => 'Yellow',
]);
$this->assertEquals(12, $this->filesystem->write('metadata.txt', 'Some content', true));
$reflectionObject = new \ReflectionObject($adapter);
$reflectionMethod = $reflectionObject->getMethod('getObjectData');
$reflectionMethod->setAccessible(true);
$metadata = $reflectionMethod->invoke($adapter, ['metadata.txt']);
$this->assertEquals('public, maxage=7200', $metadata->cacheControl);
$this->assertEquals('attachment; filename="test.txt"', $metadata->contentDisposition);
$this->assertEquals('identity', $metadata->contentEncoding);
$this->assertEquals('en', $metadata->contentLanguage);
$this->assertEquals([
'Colour' => 'Yellow',
], $metadata->metadata);
$this->filesystem->delete('metadata.txt');
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Adapter\GridFS;
use Gaufrette\Filesystem;
use MongoDB\Client;
class GridFSTest extends FunctionalTestCase
{
protected function setUp(): void
{
$uri = getenv('MONGO_URI');
$dbname = getenv('MONGO_DBNAME');
if ($uri === false || $dbname === false) {
$this->markTestSkipped('Either MONGO_URI or MONGO_DBNAME env variables are not defined.');
}
$client = new Client($uri);
$db = $client->selectDatabase($dbname);
$bucket = $db->selectGridFSBucket();
$bucket->drop();
$this->filesystem = new Filesystem(new GridFS($bucket));
}
/**
* @test
*/
public function shouldListKeys(): void
{
$this->filesystem->write('foo/foobar/bar.txt', 'data');
$this->filesystem->write('foo/bar/buzz.txt', 'data');
$this->filesystem->write('foobarbuz.txt', 'data');
$this->filesystem->write('foo', 'data');
$allKeys = $this->filesystem->listKeys(' ');
//empty pattern results in ->keys call
$this->assertEquals(
$this->filesystem->keys(),
$allKeys['keys']
);
//these values are canonicalized to avoid wrong order or keys issue
$keys = $this->filesystem->listKeys('foo');
$this->assertEqualsCanonicalizing(
$this->filesystem->keys(),
$keys['keys']
);
$keys = $this->filesystem->listKeys('foo/foob');
$this->assertEqualsCanonicalizing(
['foo/foobar/bar.txt'],
$keys['keys']
);
$keys = $this->filesystem->listKeys('foo/');
$this->assertEqualsCanonicalizing(
['foo/foobar/bar.txt', 'foo/bar/buzz.txt'],
$keys['keys']
);
$keys = $this->filesystem->listKeys('foo');
$this->assertEqualsCanonicalizing(
['foo/foobar/bar.txt', 'foo/bar/buzz.txt', 'foobarbuz.txt', 'foo'],
$keys['keys']
);
$keys = $this->filesystem->listKeys('fooz');
$this->assertEqualsCanonicalizing(
[],
$keys['keys']
);
}
/**
* @test
* Tests metadata written to GridFS can be retrieved after writing
*/
public function shouldRetrieveMetadataAfterWrite(): void
{
//Create local copy of fileadapter
$fileadpt = clone $this->filesystem->getAdapter();
$this->filesystem->getAdapter()->setMetadata('metadatatest', ['testing' => true]);
$this->filesystem->write('metadatatest', 'test');
$this->assertEquals($this->filesystem->getAdapter()->getMetadata('metadatatest'), $fileadpt->getMetadata('metadatatest'));
}
/**
* @test
* Test to see if filesize works
*/
public function shouldGetSize(): void
{
$this->filesystem->write('sizetest.txt', 'data');
$this->assertEquals(4, $this->filesystem->size('sizetest.txt'));
}
/**
* @test
* Should retrieve empty metadata w/o errors
*/
public function shouldRetrieveEmptyMetadata(): void
{
$this->filesystem->write('no-metadata.txt', 'content');
$this->assertEquals([], $this->filesystem->getAdapter()->getMetadata('no-metadata.txt'));
}
/**
* @test
* @group functional
*/
public function shouldGetMtime(): void
{
if (strtolower(substr(PHP_OS, 0, 3)) === 'win') {
$this->markTestSkipped('Not working on Windows.');
} else {
parent::shouldGetMtime();
}
}
}

View File

@@ -0,0 +1,164 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Filesystem;
use Gaufrette\Adapter\Local;
class LocalTest extends FunctionalTestCase
{
private $directory;
protected function setUp(): void
{
$this->directory = sprintf('%s/filesystem', str_replace('\\', '/', __DIR__));
if (!file_exists($this->directory)) {
mkdir($this->directory);
}
$this->filesystem = new Filesystem(new Local($this->directory));
}
protected function tearDown(): void
{
$adapter = $this->filesystem->getAdapter();
foreach ($this->filesystem->keys() as $key) {
$adapter->delete($key);
}
$this->filesystem = null;
rmdir($this->directory);
}
/**
* @test
* @group functional
*/
public function shouldWorkWithSyslink(): void
{
if (strtolower(substr(PHP_OS, 0, 3)) === 'win') {
$this->markTestSkipped('Symlinks are not supported on Windows.');
}
$dirname = sprintf(
'%s/adapters/aaa',
dirname(__DIR__)
);
$linkname = sprintf(
'%s/adapters/../../../../link',
dirname(__DIR__)
);
@mkdir($dirname);
@unlink($linkname);
symlink($dirname, $linkname);
$fs = new Filesystem(new Local($linkname));
$fs->write('test.txt', 'abc 123');
$this->assertSame('abc 123', $fs->read('test.txt'));
$fs->delete('test.txt');
@unlink($linkname);
@rmdir($dirname);
}
/**
* @test
* @covers Gaufrette\Adapter\Local
* @group functional
*/
public function shouldListingOnlyGivenDirectory(): void
{
$this->filesystem->write('aaa.txt', 'some content');
$this->filesystem->write('localDir/test.txt', 'some content');
$dirs = $this->filesystem->listKeys('localDir/test');
$this->assertEmpty($dirs['dirs']);
$this->assertCount(1, $dirs['keys']);
$this->assertEquals('localDir/test.txt', $dirs['keys'][0]);
$dirs = $this->filesystem->listKeys();
$this->assertCount(1, $dirs['dirs']);
$this->assertEquals('localDir', $dirs['dirs'][0]);
$this->assertCount(2, $dirs['keys']);
$this->assertEquals('aaa.txt', $dirs['keys'][0]);
$this->assertEquals('localDir/test.txt', $dirs['keys'][1]);
}
/**
* @test
* @covers Gaufrette\Adapter\Local
* @group functional
*/
public function shouldListingAllKeys(): void
{
$this->filesystem->write('aaa.txt', 'some content');
$this->filesystem->write('localDir/dir1/dir2/dir3/test.txt', 'some content');
$keys = $this->filesystem->keys();
$dirs = $this->filesystem->listKeys();
$this->assertCount(6, $keys);
$this->assertCount(4, $dirs['dirs']);
$this->assertEquals('localDir/dir1/dir2/dir3/test.txt', $dirs['keys'][1]);
}
/**
* @test
* @group functional
*/
public function shouldBeAbleToClearCache(): void
{
$this->filesystem->get('test.txt', true);
$this->filesystem->write('test.txt', '123', true);
$this->filesystem->get('test2.txt', true);
$this->filesystem->write('test2.txt', '123', true);
$fsReflection = new \ReflectionClass($this->filesystem);
$fsIsFileInRegister = $fsReflection->getMethod('isFileInRegister');
$fsIsFileInRegister->setAccessible(true);
$this->assertTrue($fsIsFileInRegister->invoke($this->filesystem, 'test.txt'));
$this->filesystem->removeFromRegister('test.txt');
$this->assertFalse($fsIsFileInRegister->invoke($this->filesystem, 'test.txt'));
$this->filesystem->clearFileRegister();
$fsRegister = $fsReflection->getProperty('fileRegister');
$fsRegister->setAccessible(true);
$this->assertCount(0, $fsRegister->getValue($this->filesystem));
}
/**
* @test
* @group functional
*/
public function shouldDeleteDirectory(): void
{
$path = $this->directory . DIRECTORY_SEPARATOR . 'delete-me.d';
mkdir($path);
$this->assertTrue(is_dir($path));
$this->assertTrue($this->filesystem->getAdapter()->delete('delete-me.d'));
$this->assertFalse(is_dir($path));
}
/**
* @test
* @group functional
*/
public function shouldNotDeleteTheAdapterRootDirectory(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Impossible to delete the root directory of this Local adapter');
$this->filesystem->getAdapter()->delete('/');
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Adapter\PhpseclibSftp;
use Gaufrette\Filesystem;
use phpseclib\Net\SFTP;
class PhpseclibSftpTest extends FunctionalTestCase
{
/** @var SFTP */
private $sftp;
/** @var string */
private $baseDir;
protected function setUp(): void
{
$host = getenv('SFTP_HOST');
$port = getenv('SFTP_PORT') ?: 22;
$user = getenv('SFTP_USER');
$password = getenv('SFTP_PASSWORD');
$baseDir = getenv('SFTP_BASE_DIR');
if ($host === false || $user === false || $password === false || $baseDir === false) {
$this->markTestSkipped('Either SFTP_HOST, SFTP_USER, SFTP_PASSWORD and/or SFTP_BASE_DIR env variables are not defined.');
}
$this->baseDir = rtrim($baseDir, '/') . '/' . uniqid();
$this->sftp = new SFTP($host, $port);
$this->sftp->login($user, $password);
$this->filesystem = new Filesystem(new PhpseclibSftp($this->sftp, $this->baseDir, true));
}
protected function tearDown(): void
{
if (!isset($this->sftp)) {
return;
}
$this->sftp->rmdir($this->baseDir);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Filesystem;
use Gaufrette\Adapter\SafeLocal;
class SafeLocalTest extends FunctionalTestCase
{
protected function setUp(): void
{
if (!file_exists($this->getDirectory())) {
mkdir($this->getDirectory());
}
$this->filesystem = new Filesystem(new SafeLocal($this->getDirectory()));
}
protected function tearDown(): void
{
foreach ($this->filesystem->keys() as $key) {
$this->filesystem->delete($key);
}
$this->filesystem = null;
rmdir($this->getDirectory());
}
private function getDirectory(): string
{
return sprintf('%s/filesystem', __DIR__);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Gaufrette\Functional\Adapter;
use Gaufrette\Adapter\Zip;
use Gaufrette\Filesystem;
class ZipTest extends FunctionalTestCase
{
protected function setUp(): void
{
if (!extension_loaded('zip')) {
$this->markTestSkipped('The zip extension is not available.');
} elseif (strtolower(substr(PHP_OS, 0, 3)) === 'win') {
$this->markTestSkipped('Zip adapter is not supported on Windows.');
}
$this->filesystem = new Filesystem(new Zip(__DIR__ . '/test.zip'));
}
protected function tearDown(): void
{
parent::tearDown();
@unlink(__DIR__ . '/test.zip');
}
/**
* @test
* @group functional
*/
public function shouldNotAcceptInvalidZipArchive(): void
{
$this->expectException(\RuntimeException::class);
new Zip(__FILE__);
}
}

View File

@@ -0,0 +1,226 @@
<?php
namespace Gaufrette\Functional\FileStream;
use Gaufrette\StreamWrapper;
use PHPUnit\Framework\TestCase;
abstract class FunctionalTestCase extends TestCase
{
protected $filesystem;
/**
* @test
*/
public function shouldCheckIsFile()
{
$this->filesystem->write('test.txt', 'some content');
$this->assertTrue(is_file('gaufrette://filestream/test.txt'));
$this->filesystem->delete('test.txt');
$this->assertFalse(is_file('gaufrette://filestream/test.txt'));
}
/**
* @test
*/
public function shouldCheckFileExists()
{
$this->filesystem->write('test.txt', 'some content');
$this->assertFileExists('gaufrette://filestream/test.txt');
$this->filesystem->delete('test.txt');
$this->assertFileNotExists('gaufrette://filestream/test.txt');
}
/**
* @test
*/
public function shouldWriteAndReadFile()
{
file_put_contents('gaufrette://filestream/test.txt', 'test content');
$this->assertEquals('test content', file_get_contents('gaufrette://filestream/test.txt'));
$this->filesystem->delete('test.txt');
}
/**
* @test
*/
public function shouldNotReadWhenOpenInWriteMode()
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('The stream does not allow read.');
$this->filesystem->write('test.txt', 'test content');
$fileHandler = fopen('gaufrette://filestream/test.txt', 'w');
fread($fileHandler, 10);
fclose($fileHandler);
$this->filesystem->delete('test.txt');
}
/**
* @test
*/
public function shouldNotWriteWhenOpenInReadMode()
{
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('The stream does not allow write.');
$this->filesystem->write('test.txt', 'test content');
$fileHandler = fopen('gaufrette://filestream/test.txt', 'r');
fwrite($fileHandler, 'test content2');
fclose($fileHandler);
$this->filesystem->delete('test.txt');
}
/**
* @test
*/
public function shouldWriteFromSettedPosition()
{
$fileHandler = fopen('gaufrette://filestream/test.txt', 'w');
fseek($fileHandler, 1, SEEK_SET);
fwrite($fileHandler, 'est');
fseek($fileHandler, 0, SEEK_SET);
fwrite($fileHandler, 't');
fclose($fileHandler);
$this->assertEquals('test', $this->filesystem->read('test.txt'));
$fileHandler = fopen('gaufrette://filestream/test.txt', 'w');
fseek($fileHandler, 0, SEEK_SET);
fwrite($fileHandler, 't');
fseek($fileHandler, 1, SEEK_SET);
fwrite($fileHandler, 'est');
fseek($fileHandler, 0, SEEK_SET);
fwrite($fileHandler, 'f');
fclose($fileHandler);
$this->assertEquals('fest', $this->filesystem->read('test.txt'));
}
/**
* @test
*/
public function shouldWriteEmptyContent()
{
$bytes = file_put_contents('gaufrette://filestream/test.txt', '');
$this->assertEquals('', file_get_contents('gaufrette://filestream/test.txt'));
$this->filesystem->delete('test.txt');
$this->assertSame(0, $bytes);
}
/**
* @test
*/
public function shouldSetAndGetPosition()
{
file_put_contents('gaufrette://filestream/test.txt', 'test content');
$fileHandler = fopen('gaufrette://filestream/test.txt', 'r+');
fseek($fileHandler, 1, SEEK_SET);
$this->assertEquals(1, ftell($fileHandler));
fseek($fileHandler, 1, SEEK_CUR);
$this->assertEquals(2, ftell($fileHandler));
fclose($fileHandler);
$fileHandler = fopen('gaufrette://filestream/test.txt', 'r+');
fseek($fileHandler, 1, SEEK_CUR);
$this->assertEquals(1, ftell($fileHandler));
fclose($fileHandler);
$fileHandler = fopen('gaufrette://filestream/test.txt', 'r+');
fseek($fileHandler, -2, SEEK_END);
$this->assertEquals(10, ftell($fileHandler));
fclose($fileHandler);
}
/**
* @test
*/
public function shouldNotSeekWhenWhenceParameterIsInvalid()
{
file_put_contents('gaufrette://filestream/test.txt', 'test content');
$fileHandler = fopen('gaufrette://filestream/test.txt', 'r+');
$this->assertEquals(-1, fseek($fileHandler, 1, 666));
}
/**
* @test
*/
public function shouldHandlesSubDir()
{
file_put_contents('gaufrette://filestream/subdir/test.txt', 'test content');
$this->assertTrue(is_file('gaufrette://filestream/subdir/test.txt'));
$this->filesystem->delete('subdir/test.txt');
$this->assertFalse(is_file('gaufrette://filestream/subdir/test.txt'));
}
/**
* @test
*/
public function shouldUnlinkFile()
{
if (strtolower(substr(PHP_OS, 0, 3)) === 'win') {
$this->markTestSkipped('Flaky test on windows.');
}
$this->filesystem->write('test.txt', 'some content');
unlink('gaufrette://filestream/test.txt');
$this->assertFalse($this->filesystem->has('test.txt'));
}
/**
* @test
*/
public function shouldCopyFile()
{
file_put_contents('gaufrette://filestream/copy1.txt', 'test content');
$this->assertTrue(is_file('gaufrette://filestream/copy1.txt'));
$this->assertFalse(is_file('gaufrette://filestream/copy2.txt'));
copy('gaufrette://filestream/copy1.txt', 'gaufrette://filestream/copy2.txt');
$this->assertTrue(is_file('gaufrette://filestream/copy1.txt'));
$this->assertTrue(is_file('gaufrette://filestream/copy2.txt'));
}
/**
* @test
* @dataProvider modesProvider
*/
public function shouldCreateNewFile($mode)
{
$fileHandler = fopen('gaufrette://filestream/test.txt', $mode);
$this->assertFileExists('gaufrette://filestream/test.txt');
}
public static function modesProvider()
{
return [
['w'],
['a+'],
['w+'],
['ab+'],
['wb'],
['wb+'],
];
}
protected function registerLocalFilesystemInStream()
{
$filesystemMap = StreamWrapper::getFilesystemMap();
$filesystemMap->set('filestream', $this->filesystem);
StreamWrapper::register();
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Gaufrette\Functional\FileStream;
use Gaufrette\Filesystem;
use Gaufrette\Adapter\InMemory as InMemoryAdapter;
class InMemoryBufferTest extends FunctionalTestCase
{
protected function setUp(): void
{
$this->filesystem = new Filesystem(new InMemoryAdapter([]));
$this->registerLocalFilesystemInStream();
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Gaufrette\Functional\FileStream;
use Gaufrette\Filesystem;
use Gaufrette\Adapter\Local as LocalAdapter;
class LocalTest extends FunctionalTestCase
{
protected $directory;
protected function setUp(): void
{
$this->directory = __DIR__ . DIRECTORY_SEPARATOR . 'filesystem';
@mkdir($this->directory . DIRECTORY_SEPARATOR . 'subdir', 0777, true);
umask(0002);
$this->filesystem = new Filesystem(new LocalAdapter($this->directory, true, 0770));
$this->registerLocalFilesystemInStream();
}
/**
* @test
*/
public function shouldChmodDirectory(): void
{
if (strtolower(substr(PHP_OS, 0, 3)) === 'win') {
$this->markTestSkipped('Chmod and umask are not available on Windows.');
}
$r = fopen('gaufrette://filestream/foo/bar', 'a+');
fclose($r);
$perms = fileperms($this->directory . '/foo/');
$this->assertEquals('0770', substr(sprintf('%o', $perms), -4));
}
protected function tearDown(): void
{
$adapter = $this->filesystem->getAdapter();
foreach ($this->filesystem->keys() as $key) {
$adapter->delete($key);
}
$this->filesystem = null;
rmdir($this->directory);
}
/**
* @test
*/
public function shouldSupportsDirectory(): void
{
$this->assertFileExists('gaufrette://filestream/subdir');
$this->assertDirectoryExists('gaufrette://filestream/subdir');
}
}

View File

@@ -0,0 +1,28 @@
<?php
use Gaufrette\Adapter\GoogleCloudStorage;
$keyFileLocation = '/home/me/path/to/service-auth-key.json';
$bucketName = 'gaufrette-bucket-test-' . uniqid();
$projectId = 'your-project-id-000';
$bucketLocation = 'EUROPE-WEST9';
putenv('GOOGLE_APPLICATION_CREDENTIALS=' . $keyFileLocation);
$client = new \Google\Client();
$client->setApplicationName('Gaufrette');
$client->addScope(Google\Service\Storage::DEVSTORAGE_FULL_CONTROL);
$client->useApplicationDefaultCredentials();
$service = new \Google\Service\Storage($client);
return new GoogleCloudStorage(
$service,
$bucketName,
[
GoogleCloudStorage::OPTION_CREATE_BUCKET_IF_NOT_EXISTS => true,
GoogleCloudStorage::OPTION_PROJECT_ID => $projectId,
GoogleCloudStorage::OPTION_LOCATION => $bucketLocation,
],
true
);