├── .gitignore ├── .sami.php ├── .travis.yml ├── CONTRIBUTORS.md ├── LICENSE ├── Makefile ├── README.md ├── build └── pear │ ├── package.php │ └── package.tpl ├── composer.json ├── composer.lock ├── docs └── examples │ └── permissions │ ├── Checker.php │ ├── CheckerTest.php │ └── Requirements.php ├── phpmd.xml ├── phpunit.xml ├── src └── VirtualFileSystem │ ├── Container.php │ ├── Exception │ ├── FileExistsException.php │ ├── NotDirectoryException.php │ ├── NotFileException.php │ └── NotFoundException.php │ ├── Factory.php │ ├── FileSystem.php │ ├── Loader.php │ ├── Structure │ ├── Directory.php │ ├── File.php │ ├── Link.php │ ├── Node.php │ └── Root.php │ ├── Wrapper.php │ └── Wrapper │ ├── DirectoryHandler.php │ ├── FileHandler.php │ └── PermissionHelper.php └── tests └── VirtualFileSystem ├── ContainerTest.php ├── Structure ├── DirectoryTest.php ├── LinkTest.php ├── NodeTest.php └── RootTest.php ├── VirtualFilesystemTest.php ├── Wrapper ├── FileHandlerTest.php └── PermissionHelperTest.php └── WrapperTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | build/log 4 | build/docs 5 | build/dist 6 | tmp 7 | package.xml 8 | -------------------------------------------------------------------------------- /.sami.php: -------------------------------------------------------------------------------- 1 | files() 9 | ->name('*.php') 10 | ->in($dir = __DIR__.'/src') 11 | ; 12 | 13 | $versions = GitVersionCollection::create($dir) 14 | ->addFromTags('*') 15 | ; 16 | 17 | return new Sami($iterator, array( 18 | 'versions' => $versions, 19 | 'title' => 'php-vfs API', 20 | 'build_dir' => __DIR__.'/build/docs/api/%version%', 21 | 'cache_dir' => __DIR__.'/build/docs/api/%version%', 22 | 'default_opened_level' => 2, 23 | )); 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: trusty 2 | 3 | language: php 4 | 5 | sudo: false 6 | 7 | php: 8 | - 7.0 9 | - 7.1 10 | - 7.2 11 | - 7.3 12 | - 7.4 13 | 14 | cache: 15 | directories: 16 | - $HOME/.composer/cache 17 | 18 | before_script: 19 | - composer self-update 20 | - composer install --no-interaction --prefer-dist 21 | 22 | script: 23 | - mkdir -p build/logs 24 | - vendor/bin/phpunit 25 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michael-donat/php-vfs/e56b0bd9a118941ea5681e1673ed602d47887be8/CONTRIBUTORS.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Michael Donat 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: cs md cov pear docs check 2 | 3 | PHP_VFS_BUILD_BRANCH=$(shell git rev-parse --abbrev-ref HEAD) 4 | PHP_VFS_BUILD_REV=$(shell git rev-parse HEAD) 5 | 6 | cs: 7 | phpcs --standard=PSR2 --ignore=Wrapper.php src/ 8 | 9 | md: 10 | phpmd src/ 11 | 12 | test: 13 | vendor/bin/phpunit 14 | 15 | cov: 16 | vendor/bin/phpunit --coverage-html=build/log/coverage 17 | open build/log/coverage/index.html 18 | 19 | scru: 20 | scrutinizer run -f json --output-file=build/log/scrutinizer.json ./ 21 | 22 | ifndef version 23 | check: 24 | $(error Usage: make tag version=...) 25 | else 26 | check: 27 | @git diff --quiet HEAD || (echo WORKING DIRECTORY DIRTY && false) 28 | endif 29 | 30 | setup-pear: 31 | git remote add pear git@github.com:michael-donat/pear.git || true 32 | git fetch pear 33 | git checkout pear || git checkout -b pear pear/gh-pages 34 | git checkout - 35 | sudo pear channel-discover pear.michaeldonat.net || true 36 | mkdir -p build/dist 37 | 38 | pear: check setup-pear 39 | ./build/pear/package.php --source=src/ --version=$(version) > package.xml 40 | pear package 41 | mv VirtualFileSystem-$(version).tgz build/dist/ 42 | git checkout pear 43 | pirum add . build/dist/VirtualFileSystem-$(version).tgz 44 | git add . && git commit -a -m'adding VirtualFileSystem-$(version).tgz' && git push pear pear:gh-pages 45 | git checkout -f - 46 | 47 | docs: 48 | @git diff --quiet HEAD || (echo WORKING DIRECTORY DIRTY && false) 49 | ./vendor/bin/sami.php update .sami.php 50 | vendor/bin/phpunit --coverage-html=build/log/coverage 51 | git checkout gh-pages 52 | git pull 53 | mkdir -p api 54 | cp -rf build/docs/api/* api/ 55 | git add api 56 | mkdir -p coverage 57 | cp -rf build/log/coverage/* coverage/ 58 | git add coverage 59 | git commit -m"auto-generated coverage & API docs for $(PHP_VFS_BUILD_BRANCH):$(PHP_VFS_BUILD_REV)" || true 60 | git push 61 | git checkout $(PHP_VFS_BUILD_BRANCH) 62 | 63 | tag: check 64 | git tag v$(version) 65 | git push --tags 66 | 67 | deploy: tag pear docs -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Version][icon-version]][url-version] 2 | [![Build status][icon-build]][url-build] 3 | [![Licence][icon-licence]][url-licence] 4 | [![Coverage][icon-coverage]][url-coverage] 5 | [![Quality][icon-quality]][url-quality] 6 | [![Documentation][icon-docs]][url-docs] 7 | 8 | 9 | php-vfs 10 | ======== 11 | 12 | Very simple filesystem emulating PHP stream wrapper for use in unit testing 13 | with PHPUnit, PHPSpec or any other testing framework. It offers means to test methods interacting 14 | with real filesystem without creating temportary directories or file fixtures. 15 | 16 | Released under a MIT licence. 17 | 18 | For latest release please use tag indicated above. 19 | 20 | Prerequisites 21 | ------------- 22 | 23 | PHP >= 7.0 24 | 25 | Compatibility 26 | ------------- 27 | 28 | Tested and working on (Linux/MacOs): 29 | 30 | - php 7.0 31 | - php 7.1 32 | - php 7.2 33 | 34 | There is no reason for php-vfs to not work on php5 and before 1.4.0 those versions where tested with travis. YMMV tho. 35 | 36 | Windows compatibility is unknown (should work!) 37 | 38 | 39 | Installation 40 | ------------ 41 | 42 | **Composer** 43 | 44 | Add a dev dependency on php-vfs/php-vfs to your project's composer.json 45 | 46 | { 47 | "require-dev": { 48 | "php-vfs/php-vfs": "*@stable" 49 | } 50 | } 51 | 52 | Or via command line: 53 | 54 | composer require --dev php-vfs/php-vfs=*@stable 55 | 56 | This will install latest stable version of php-vfs as a development dependency, for the latest development version replace @stable with @dev. 57 | 58 | ~~**PEAR**~~ 59 | 60 | ~~php-vfs is hosted on pear.michaeldonat.net and can be installed using following commands:~~ 61 | 62 | sudo pear channel-discover pear.michaeldonat.net 63 | sudo pear install pear.michaeldonat.net/VirtualFileSystem 64 | 65 | ~~To use PEAR installation with your testing framework you need to initialize the autoloader in your bootsrtap files.~~ 66 | 67 | ```PHP 68 | require_once 'VirtualFileSystem/Loader.php'; 69 | $l = new \VirtualFileSystem\Loader(); 70 | $l->register(); 71 | ``` 72 | 73 | Version 1.2.3 of php-vfs was the last one to be released via PEAR, future builds will not be provided through this installation channel. 74 | 75 | Usage 76 | -------------- 77 | 78 | Let's assume we need to test a class that reads CSV file and provides SUM() of columns. The unit test class would normally look something similar to following: 79 | 80 | ```PHP 81 | class CSVTest extends \PHPUnit_Framework_TestCase { 82 | 83 | public function test_sumIsCorrectlyCalculated() 84 | { 85 | $csv = new CSV('fixtures/sum.csv'); 86 | 87 | $this->assertEquals(10, $csv->getColumnSum(1), 'Sum of first column is 10'); 88 | $this->assertEquals(15, $csv->getColumnSum(2), 'Sum of first column is 15'); 89 | } 90 | } 91 | ``` 92 | 93 | And the CSV file would look something like: 94 | 95 | ``` 96 | "Column 1","Column 2" 97 | 5,5 98 | 4,7 99 | 1,3 100 | ``` 101 | 102 | And our CSV class: 103 | 104 | ```PHP 105 | class CSV { 106 | 107 | protected $data = array(); 108 | 109 | public function __construct($file) 110 | { 111 | if (false === ($handle = fopen($file, "r"))) { 112 | throw new \RuntimeException('Could not read input file: ' . $file); 113 | } 114 | 115 | while (false !== ($data = fgetcsv($handle, 1024))) { 116 | $this->data[] = $data; 117 | } 118 | 119 | fclose($handle); 120 | } 121 | 122 | public function getColumnSum($column) 123 | { 124 | $toSum = array(); 125 | foreach ($this->data as $line) { 126 | $toSum[] = $line[$column]; 127 | } 128 | 129 | return array_sum($toSum); 130 | } 131 | 132 | } 133 | ``` 134 | 135 | While above works, providing fixture file to be able to test is somewhat not in line with unit testing principles; it creates dependency on filesystem. 136 | We could possibly create the file in our setUp method and then clear it in tearDown, but if the test fails and tearDown is never executed we will leave 137 | garbage behind, and we didn't even consider a situation when we don't have permissions to the file system and so on. The other option is to create fixture and keep 138 | it together with source files. While this approach is widely used it does mean that if someone changes that fixture your test will fail. 139 | 140 | The solution is to create our file in memory - this is when php-vfs comes into play. 141 | 142 | Let's consider slightly reworked unit test: 143 | 144 | ```PHP 145 | use VirtualFileSystem\FileSystem; 146 | 147 | class CSVTest extends \PHPUnit_Framework_TestCase { 148 | 149 | protected $csvData = array( 150 | '"Column 1";"Column 2"', 151 | '5,5', 152 | '4,7', 153 | '1,3' 154 | ); 155 | 156 | public function test_sumIsCorrectlyCalculated() 157 | { 158 | $fs = new FileSystem(); 159 | 160 | file_put_contents($fs->path('/sum.csv'), join(PHP_EOL, $this->csvData)); 161 | 162 | $csv = new CSV($fs->path('/sum.csv')); 163 | 164 | $this->assertEquals(10, $csv->getColumnSum(1), 'Sum of first column is 10'); 165 | $this->assertEquals(15, $csv->getColumnSum(2), 'Sum of first column is 15'); 166 | } 167 | } 168 | ``` 169 | 170 | As you can see there is no fixture or file created by our test that could be otherwise left behind. We can control the file contents and existence within the 171 | scope of our unit test, thus keeping our test background/environment isolated from external changes. 172 | 173 | API 174 | -------------- 175 | 176 | While using low level API for interaction with php-vfs is at its core, a much easier approach is to mock filesystem using provided interface. 177 | 178 | There are generally 4 methods you should always use when mocking up the state: 179 | 180 | - ```\VirtualFileSystem\FileSystem::createDirectory($path, $recursive, $mode)``` used to mock directory; 181 | - ```\VirtualFileSystem\FileSystem::createFile($path, $data)``` used to mock file and its contents. 182 | - ```\VirtualFileSystem\FileSystem::createLink($linkPath, $targetPath)``` used to mock symlink. 183 | - ```\VirtualFileSystem\FileSystem::createStructure(array $structure)``` used to mock filesystem from array. 184 | 185 | Combining above should allow you to recreate any directory/file structure. 186 | 187 | Full API documentation is available [here](http://michael-donat.github.io/php-vfs/api/master). 188 | 189 | Behaviour 190 | ------------- 191 | 192 | php-vfs tries to mimic unix filesystem as much as possible. The same conditions must be matched and the same errors will be triggered as if we were interacting via php with real underlying filesystem. 193 | 194 | Most of [PHP filesystem functions](http://www.php.net/manual/en/ref.filesystem.php) are happily supported by php-vfs as long as the full file URL is passed as argument (using ```$fs->path()```). If you find something not working and not listed below please rise an issue on [github issues page](https://github.com/michael-donat/php-vfs/issues). 195 | 196 | **Known pitfalls** 197 | 198 | streamWrapper implementations like php-vfs will not work with glob methods for directory iteration - you need to use ```DirectoryIterator``` or ```readdir()``` instead. 199 | 200 | Contributing 201 | ---------------- 202 | 203 | Any contributions are more than welcome. Please make sure that you keep to [PSR-2](http://www.php-fig.org/psr/psr-2/) standards and provide tested code. 204 | 205 | You are more than welcome to add yourself to CONTRIBUTORS.md. 206 | 207 | Changes in 1.1.x 208 | ---------------- 209 | 210 | For full diff of changes please go to https://github.com/michael-donat/php-vfs/compare/v1.0.0...v1.1.0 211 | 212 | - added support for symlinks 213 | - fixed Windows compatibility issues 214 | - provided method to recreate dir/file structure from array [createStructure method] 215 | - added permission support where it was previously not available (touch etc) 216 | 217 | Changes in 1.2.x 218 | ---------------- 219 | 220 | For full diff of changes please go to https://github.com/michael-donat/php-vfs/compare/v1.1.0...1.2.x 221 | 222 | - added support for locking (thanks to [@mathroc](https://github.com/mathroc)) (1.2.1) 223 | - code improvements as suggested by https://scrutinizer-ci.com/g/michael-donat/php-vfs/ (1.2.1) 224 | - fixed [30](https://github.com/michael-donat/php-vfs/issues/30) allowing owner and root to change metadata ([@milesj](https://github.com/milesj)) (1.2.2) 225 | 226 | Changes in 1.3.x 227 | ---------------- 228 | 229 | For full diff of changes please go to https://github.com/michael-donat/php-vfs/compare/v1.2.3...1.3.x 230 | 231 | - ensured compatibility with php7.0 232 | - ~~ensured compatibility with hhvm~~ 233 | 234 | Note on HHVM: 235 | 236 | There'a a problem with PHPUnit version on HHVM which means the build started failing - I no longer have the time to spare to chase all the little bits so right now the HHVM support is on YMMV basis. 237 | 238 | Changes in 1.4.x 239 | ---------------- 240 | 241 | - confirmed support for php7.0, php7.1 and php7.2 242 | - earlier versions of php no longer officially supported 243 | - ensured compatibility with [Finfo](http://php.net/finfo) 244 | 245 | 246 | [icon-version]: http://img.shields.io/packagist/v/php-vfs/php-vfs.svg?style=flat 247 | [url-version]: https://packagist.org/packages/php-vfs/php-vfs 248 | [icon-build]: http://img.shields.io/travis/michael-donat/php-vfs/1.4.x.svg?style=flat 249 | [url-build]: https://travis-ci.org/michael-donat/php-vfs 250 | [icon-coverage]: http://img.shields.io/scrutinizer/coverage/g/michael-donat/php-vfs/1.4.x.svg?style=flat 251 | [url-coverage]: http://michael-donat.github.io/php-vfs/coverage/ 252 | [icon-quality]: http://img.shields.io/scrutinizer/g/michael-donat/php-vfs/1.4.x.svg?style=flat 253 | [url-quality]: https://scrutinizer-ci.com/g/michael-donat/php-vfs/ 254 | [icon-licence]: http://img.shields.io/packagist/l/php-vfs/php-vfs.svg?style=flat 255 | [url-licence]: LICENCE 256 | [icon-docs]: http://img.shields.io/badge/docs-API-4C73BA.svg?style=flat 257 | [url-docs]: http://michael-donat.github.io/php-vfs/api/v1.4.0/ 258 | [icon-hhvm]: http://img.shields.io/hhvm/php-vfs/php-vfs/1.4.x-dev.svg?style=flat 259 | [url-hhvm]: http://hhvm.h4cc.de/package/php-vfs/php-vfs 260 | -------------------------------------------------------------------------------- /build/pear/package.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | usage(); 15 | } 16 | 17 | $template = str_replace( 18 | array( 19 | '{{date}}', 20 | '{{version}}' 21 | ), 22 | array( 23 | date('Y-m-d', time()), 24 | $opts['version'] 25 | ), 26 | $template 27 | ); 28 | 29 | $source = $opts['source']; 30 | if (substr($source, 0, 1) != '/') { 31 | $source = getcwd().'/'.$source; 32 | } 33 | 34 | $document = new DOMDocument('1.0'); 35 | $document->preserveWhiteSpace = false; 36 | $document->loadXML($template); 37 | $document->formatOutput = true; 38 | 39 | $root = $document->documentElement; 40 | 41 | $before = $root->getElementsByTagName('dependencies')->item(0); 42 | 43 | $contents = new DOMElement('contents'); 44 | $root->insertBefore($contents, $before); 45 | $contentsChild = new DOMElement('dir'); 46 | $contents->appendChild($contentsChild); 47 | $contentsChild->setAttribute('name', '/'); 48 | $contentsChild->setAttribute('baseinstalldir', '/'); 49 | $contentsChild->appendChild($srcWrap = new DOMElement('dir')); 50 | $srcWrap->setAttribute('name', basename($source)); 51 | $this->getDirectoryListing($source, $srcWrap); 52 | 53 | $root->appendChild($wrap = new DOMElement('phprelease')); 54 | $wrap->appendChild($wrap = new DOMElement('filelist')); 55 | 56 | $this->getInstallListing($source, getcwd(), $wrap); 57 | 58 | $contentsChild->appendChild($doc = new DOMElement('file')); 59 | $doc->setAttribute('name', 'LICENCE'); 60 | $doc->setAttribute('role', 'doc'); 61 | 62 | $contentsChild->appendChild($doc = new DOMElement('file')); 63 | $doc->setAttribute('name', 'README.md'); 64 | $doc->setAttribute('role', 'doc'); 65 | 66 | $contentsChild->appendChild($doc = new DOMElement('file')); 67 | $doc->setAttribute('name', 'CONTRIBUTORS.md'); 68 | $doc->setAttribute('role', 'doc'); 69 | 70 | echo $document->saveXML(); 71 | 72 | } 73 | 74 | /** 75 | * @param string $cut 76 | * @param DOMElement $parentNode 77 | */ 78 | protected function getInstallListing($directory, $cut, $parentNode) 79 | { 80 | $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS)); 81 | foreach ($objects as $name => $object) { 82 | $file = ltrim(str_replace($cut, '', $name), '/'); 83 | $as = ltrim(str_replace($directory, '', $name), '/'); 84 | $parentNode->appendChild($node = new DOMElement('install')); 85 | $node->setAttribute('name', $file); 86 | $node->setAttribute('as', $as); 87 | } 88 | } 89 | 90 | /** 91 | * @param DOMElement $parentNode 92 | */ 93 | protected function getDirectoryListing($directory, $parentNode) 94 | { 95 | $return = $parentNode; 96 | 97 | $iterator = new DirectoryIterator($directory); 98 | foreach ($iterator as $fileInfo) { 99 | if ($fileInfo->isDot()) { 100 | continue; 101 | } 102 | if ($fileInfo->isDir()) { 103 | $node = new DOMElement('dir'); 104 | $return->appendChild($node); 105 | $node->setAttribute('name', $fileInfo->getBasename()); 106 | $this->getDirectoryListing($fileInfo->getPathname(), $node); 107 | } else { 108 | $node = new DOMElement('file'); 109 | $return->appendChild($node); 110 | $node->setAttribute('name', $fileInfo->getBasename()); 111 | $node->setAttribute('role', 'php'); 112 | } 113 | } 114 | } 115 | 116 | public function usage() 117 | { 118 | echo <<run(); 133 | } 134 | -------------------------------------------------------------------------------- /build/pear/package.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | VirtualFileSystem 4 | pear.michaeldonat.net 5 | php-vfs is a simple filesystem mocking library 6 | 7 | php-vfs is a simple filesystem mocking library 8 | 9 | 10 | Michael Donat 11 | thornag 12 | michael.donat@me.com 13 | yes 14 | 15 | {{date}} 16 | 17 | 18 | {{version}} 19 | 1.0.0 20 | 21 | 22 | stable 23 | stable 24 | 25 | MIT 26 | - 27 | 28 | 29 | 30 | 5.4.0 31 | 32 | 33 | 1.4.0 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "php-vfs/php-vfs", 3 | "homepage": "http://michael-donat.github.com/php-vfs", 4 | "keywords": ["php", "virtual", "filesystem", "testing", "unit"], 5 | "license": "MIT", 6 | "description": "Virtual file system implementation for use with PHP unit testing.", 7 | "minimum-stability": "stable", 8 | "authors": [ 9 | { 10 | "name": "Michael Donat", 11 | "email": "michael.donat@me.com", 12 | "role": "Developer" 13 | } 14 | ], 15 | "autoload": { 16 | "psr-0": { 17 | "VirtualFileSystem": "src/" 18 | } 19 | }, 20 | "support": { 21 | "issues": "https://github.com/michael-donat/php-vfs/issues", 22 | "wiki": "https://github.com/michael-donat/php-vfs/wiki" 23 | }, 24 | "archive": { 25 | "exclude": ["docs", "vendor", "tests", ".*", "phpunit.xml", "composer.*"] 26 | }, 27 | "require": { 28 | "php": ">=5.4.0" 29 | }, 30 | "require-dev": { 31 | "php": ">=7.0", 32 | "sami/sami": "*", 33 | "phpunit/phpunit": "^6.5", 34 | "doctrine/instantiator": "~1.0.2" 35 | }, 36 | "type": "library" 37 | } 38 | -------------------------------------------------------------------------------- /docs/examples/permissions/Checker.php: -------------------------------------------------------------------------------- 1 | root = $root; 13 | } 14 | 15 | public function result($result, $header) 16 | { 17 | echo $header; 18 | if ($result) { 19 | echo '√'; 20 | } else { 21 | echo 'x'; 22 | } 23 | echo PHP_EOL; 24 | } 25 | 26 | public function checkCache() 27 | { 28 | return is_dir($this->root.'/cache') && is_writable($this->root.'/cache'); 29 | } 30 | 31 | public function checkLog() 32 | { 33 | return is_dir($this->root.'/logs') && is_writable($this->root.'/logs'); 34 | } 35 | 36 | public function checkLib() 37 | { 38 | return !is_writable($this->root.'/lib') && is_readable($this->root.'/lib'); 39 | } 40 | 41 | public function checkInstaller() 42 | { 43 | return !file_exists($this->root.'/lib'); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /docs/examples/permissions/CheckerTest.php: -------------------------------------------------------------------------------- 1 | path('/')); 11 | 12 | $this->assertFalse($checker->checkCache()); 13 | 14 | $fs->createDirectory('/cache'); 15 | 16 | chmod($fs->path('/cache'), 0000); 17 | $this->assertFalse($checker->checkCache()); 18 | 19 | chmod($fs->path('/cache'), 0700); 20 | 21 | $this->assertTrue($checker->checkCache()); 22 | } 23 | } 24 | 25 | class CheckerTest extends PHPUnit_Framework_TestCase 26 | { 27 | public function testCheckingForCacheReturnsWritableState() 28 | { 29 | mkdir($root = '/tmp/'.uniqid()); 30 | 31 | $checker = new Checker($root); 32 | 33 | $this->assertFalse($checker->checkCache()); 34 | 35 | mkdir($cache = $root.'/cache'); 36 | 37 | chmod($cache, 0000); 38 | $this->assertFalse($checker->checkCache()); 39 | 40 | chmod($cache, 0700); 41 | 42 | $this->assertTrue($checker->checkCache()); 43 | 44 | rmdir($cache); 45 | rmdir($root); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /docs/examples/permissions/Requirements.php: -------------------------------------------------------------------------------- 1 | result($checker->checkCache(), 'Cache: '); 10 | $checker->result($checker->checkLib(), 'Lib: '); 11 | $checker->result($checker->checkLog(), 'Log: '); 12 | $checker->result($checker->checkInstaller(), 'Installer removed: '); 13 | -------------------------------------------------------------------------------- /phpmd.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | ./tests/ 19 | 20 | 21 | 22 | 23 | 24 | ./src/ 25 | 26 | ./src/VirtualFileSystem/Loader.php 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Container.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem; 12 | 13 | use VirtualFileSystem\Exception\NotDirectoryException; 14 | use VirtualFileSystem\Exception\NotFileException; 15 | use VirtualFileSystem\Exception\NotFoundException; 16 | use VirtualFileSystem\Structure\Directory; 17 | use VirtualFileSystem\Structure\File; 18 | use VirtualFileSystem\Structure\Root; 19 | 20 | /** 21 | * Class to hold the filesystem structure as object representation. It also provides access and factory methods for 22 | * file system management. 23 | * 24 | * An instance of Container is registered as a default stream options when FileSystem class is instantiated - it is 25 | * later used by streamWrapper implementation to interact with underlying object representation. 26 | * 27 | * @author Michael Donat 28 | * @package php-vfs 29 | * @api 30 | */ 31 | class Container 32 | { 33 | /** 34 | * @var Root 35 | */ 36 | protected $root; 37 | 38 | /** 39 | * @var Factory 40 | */ 41 | protected $factory; 42 | 43 | /** 44 | * @var Wrapper\PermissionHelper 45 | */ 46 | protected $permissionHelper; 47 | 48 | /** 49 | * Class constructor. Sets factory and root object on init. 50 | * 51 | * @param Factory $factory 52 | */ 53 | public function __construct(Factory $factory) 54 | { 55 | $this->setFactory($factory); 56 | $this->root = $this->factory()->getRoot(); 57 | $this->setPermissionHelper(new Wrapper\PermissionHelper()); 58 | } 59 | 60 | /** 61 | * Sets Factory instance 62 | * 63 | * @param \VirtualFileSystem\Factory $factory 64 | */ 65 | public function setFactory($factory) 66 | { 67 | $this->factory = $factory; 68 | } 69 | 70 | /** 71 | * Returns Factory instance 72 | * 73 | * @return \VirtualFileSystem\Factory 74 | */ 75 | public function factory() 76 | { 77 | return $this->factory; 78 | } 79 | 80 | /** 81 | * Returns Root instance 82 | * 83 | * @return Root 84 | */ 85 | public function root() 86 | { 87 | return $this->root; 88 | } 89 | 90 | /** 91 | * Returns filesystem Node|Directory|File|Root at given path. 92 | * 93 | * @param string $path 94 | * 95 | * @return Structure\Node 96 | * 97 | * @throws NotFoundException 98 | */ 99 | public function nodeAt($path) 100 | { 101 | $pathParts = array_filter(explode('/', str_replace('\\', '/', $path)), 'strlen'); 102 | 103 | $node = $this->root(); 104 | 105 | foreach ($pathParts as $level) { 106 | if ($node instanceof File) { 107 | throw new NotFoundException(); 108 | } 109 | $node = $node->childAt($level); 110 | } 111 | 112 | return $node; 113 | } 114 | 115 | /** 116 | * Checks whether filesystem has Node at given path 117 | * 118 | * @param string $path 119 | * 120 | * @return bool 121 | */ 122 | public function hasNodeAt($path) 123 | { 124 | try { 125 | $this->nodeAt($path); 126 | 127 | return true; 128 | } catch (NotFoundException $e) { 129 | return false; 130 | } 131 | } 132 | 133 | /** 134 | * Returns directory at given path 135 | * 136 | * @param string $path 137 | * 138 | * @return Structure\Directory 139 | * 140 | * @throws NotDirectoryException 141 | * @throws NotFoundException 142 | */ 143 | public function directoryAt($path) 144 | { 145 | $file = $this->nodeAt($path); 146 | 147 | if (!$file instanceof Directory) { 148 | throw new NotDirectoryException(); 149 | } 150 | 151 | return $file; 152 | } 153 | 154 | /** 155 | * Returns file at given path 156 | * 157 | * @param string $path 158 | * 159 | * @return Structure\File 160 | * 161 | * @throws NotFileException 162 | * @throws NotFoundException 163 | */ 164 | public function fileAt($path) 165 | { 166 | $file = $this->nodeAt($path); 167 | 168 | if (!$file instanceof File) { 169 | throw new NotFileException(); 170 | } 171 | 172 | return $file; 173 | } 174 | 175 | /** 176 | * Creates Directory at given path. 177 | * 178 | * @param string $path 179 | * @param bool $recursive 180 | * @param null|integer $mode 181 | * 182 | * @return Structure\Directory 183 | * 184 | * @throws NotFoundException 185 | */ 186 | public function createDir($path, $recursive = false, $mode = null) 187 | { 188 | $parentPath = dirname($path); 189 | $name = basename($path); 190 | 191 | try { 192 | $parent = $this->directoryAt($parentPath); 193 | } catch (NotFoundException $e) { 194 | if (!$recursive) { 195 | throw new NotFoundException(sprintf('createDir: %s: No such file or directory', $parentPath)); 196 | } 197 | $parent = $this->createDir($parentPath, $recursive, $mode); 198 | } 199 | 200 | $parent->addDirectory($newDirectory = $this->factory()->getDir($name)); 201 | 202 | if (!is_null($mode)) { 203 | $newDirectory->chmod($mode); 204 | } 205 | 206 | return $newDirectory; 207 | } 208 | 209 | /** 210 | * Creates link at given path 211 | * 212 | * @param string $path 213 | * @param string $destination 214 | * 215 | * @return Structure\Link 216 | * 217 | */ 218 | public function createLink($path, $destination) 219 | { 220 | 221 | $destination = $this->nodeAt($destination); 222 | 223 | if ($this->hasNodeAt($path)) { 224 | throw new \RuntimeException(sprintf('%s already exists', $path)); 225 | } 226 | 227 | $parent = $this->directoryAt(dirname($path)); 228 | 229 | $parent->addLink($newLink = $this->factory()->getLink(basename($path), $destination)); 230 | 231 | return $newLink; 232 | } 233 | 234 | /** 235 | * Creates file at given path 236 | * 237 | * @param string $path 238 | * @param string|null $data 239 | * 240 | * @return Structure\File 241 | * 242 | * @throws \RuntimeException 243 | */ 244 | public function createFile($path, $data = null) 245 | { 246 | if ($this->hasNodeAt($path)) { 247 | throw new \RuntimeException(sprintf('%s already exists', $path)); 248 | } 249 | 250 | $parent = $this->directoryAt(dirname($path)); 251 | 252 | $parent->addFile($newFile = $this->factory()->getFile(basename($path))); 253 | 254 | $newFile->setData($data); 255 | 256 | return $newFile; 257 | 258 | } 259 | 260 | /** 261 | * Creates struture 262 | * 263 | * @param array $structure 264 | * @param string $parent 265 | * @throws NotFoundException 266 | */ 267 | public function createStructure(array $structure, $parent = '/') 268 | { 269 | foreach ($structure as $key => $value) { 270 | if (is_array($value)) { 271 | $this->createDir($parent.$key); 272 | $this->createStructure($value, $parent.$key.'/'); 273 | } else { 274 | $this->createFile($parent.$key, $value); 275 | } 276 | } 277 | } 278 | 279 | /** 280 | * Moves Node from source to destination 281 | * 282 | * @param string $fromPath 283 | * @param string $toPath 284 | * 285 | * @throws \RuntimeException 286 | */ 287 | public function move($fromPath, $toPath) 288 | { 289 | $fromNode = $this->nodeAt($fromPath); 290 | 291 | try { 292 | $nodeToOverride = $this->nodeAt($toPath); 293 | 294 | if (!is_a($nodeToOverride, get_class($fromNode))) { 295 | //nodes of a different type 296 | throw new \RuntimeException('Can\'t move.'); 297 | } 298 | 299 | if ($nodeToOverride instanceof Directory) { 300 | if ($nodeToOverride->size()) { 301 | //nodes of a different type 302 | throw new \RuntimeException('Can\'t override non empty directory.'); 303 | } 304 | } 305 | 306 | $this->remove($toPath, true); 307 | 308 | } catch (NotFoundException $e) { 309 | //nothing at destination, we're good 310 | } 311 | 312 | $toParent = $this->directoryAt(dirname($toPath)); 313 | 314 | $fromNode->setBasename(basename($toPath)); 315 | 316 | $toParent->addNode($fromNode); 317 | 318 | $this->remove($fromPath, true); 319 | 320 | } 321 | 322 | /** 323 | * Removes node at $path 324 | * 325 | * @param string $path 326 | * @param bool $recursive 327 | * 328 | * @throws \RuntimeException 329 | */ 330 | public function remove($path, $recursive = false) 331 | { 332 | $fileToRemove = $this->nodeAt($path); 333 | 334 | if (!$recursive && $fileToRemove instanceof Directory) { 335 | throw new \RuntimeException('Won\'t non-recursively remove directory'); 336 | } 337 | 338 | $this->directoryAt(dirname($path))->remove(basename($path)); 339 | 340 | clearstatcache(true, $path); 341 | } 342 | 343 | /** 344 | * Returns PermissionHelper with given node in context 345 | * 346 | * @param Structure\Node $node 347 | * 348 | * @return \VirtualFileSystem\Wrapper\PermissionHelper 349 | */ 350 | public function getPermissionHelper(Structure\Node $node) 351 | { 352 | return $this->permissionHelper->setNode($node); 353 | } 354 | 355 | /** 356 | * Sets permission helper instance 357 | * 358 | * @param \VirtualFileSystem\Wrapper\PermissionHelper $permissionHelper 359 | */ 360 | public function setPermissionHelper($permissionHelper) 361 | { 362 | $this->permissionHelper = $permissionHelper; 363 | } 364 | } 365 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Exception/FileExistsException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Exception; 12 | 13 | /** 14 | * Thrown when trying to override Node at address (duplicate prevention). 15 | * 16 | * @author Michael Donat 17 | * @package php-vfs 18 | */ 19 | class FileExistsException extends \Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Exception/NotDirectoryException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Exception; 12 | 13 | /** 14 | * Thrown when non-existing Node is requested. 15 | * 16 | * @author Michael Donat 17 | * @package php-vfs 18 | */ 19 | class NotDirectoryException extends \Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Exception/NotFileException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Exception; 12 | 13 | /** 14 | * Thrown when non-existing Node is requested. 15 | * 16 | * @author Michael Donat 17 | * @package php-vfs 18 | */ 19 | class NotFileException extends \Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Exception/NotFoundException.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Exception; 12 | 13 | /** 14 | * Thrown when non-existing Node is requested. 15 | * 16 | * @author Michael Donat 17 | * @package php-vfs 18 | */ 19 | class NotFoundException extends \Exception 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Factory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem; 12 | 13 | use VirtualFileSystem\Structure\Directory; 14 | use VirtualFileSystem\Structure\File; 15 | use VirtualFileSystem\Structure\Link; 16 | use VirtualFileSystem\Structure\Node; 17 | use VirtualFileSystem\Structure\Root; 18 | 19 | /** 20 | * Factory class to encapsulate object creation. 21 | * 22 | * @author Michael Donat 23 | * @package php-vfs 24 | */ 25 | class Factory 26 | { 27 | protected $userid; 28 | protected $groupid; 29 | 30 | /** 31 | * Class constructor. Sets user/group to current system user/group. 32 | * 33 | * On non POSIX systems both attributes will be set to 0 34 | * 35 | */ 36 | public function __construct() 37 | { 38 | $this->userid = function_exists('posix_getuid') ? posix_getuid() : 0; 39 | $this->groupid = function_exists('posix_getgid') ? posix_getgid() : 0; 40 | } 41 | 42 | /** 43 | * Creates Root object. 44 | * 45 | * @return Root 46 | */ 47 | public function getRoot() 48 | { 49 | return $this->updateMetadata(new Root()); 50 | } 51 | 52 | /** 53 | * Updates time and ownership of a node 54 | * 55 | * @param Node $node 56 | * 57 | * @return Node 58 | */ 59 | public function updateMetadata(Node $node) 60 | { 61 | $this->updateFileTimes($node); 62 | $this->updateOwnership($node); 63 | 64 | return $node; 65 | } 66 | 67 | /** 68 | * Update file a/c/m times 69 | * 70 | * @param Node $node 71 | * @return Node 72 | */ 73 | public function updateFileTimes(Node $node) 74 | { 75 | $time = time(); 76 | $node->setAccessTime($time); 77 | $node->setModificationTime($time); 78 | $node->setChangeTime($time); 79 | 80 | return $node; 81 | } 82 | 83 | /** 84 | * Sets default (current) uid/gui on object. 85 | * 86 | * @param Node $node 87 | * 88 | * @return Node 89 | */ 90 | protected function updateOwnership(Node $node) 91 | { 92 | $node->chown($this->userid); 93 | $node->chgrp($this->groupid); 94 | 95 | return $node; 96 | } 97 | 98 | /** 99 | * Creates Directory object. 100 | * 101 | * @param string $basename 102 | * 103 | * @return Directory 104 | */ 105 | public function getDir($basename) 106 | { 107 | return $this->updateMetadata(new Directory($basename)); 108 | } 109 | 110 | /** 111 | * Creates File object. 112 | * 113 | * @param string $basename 114 | * 115 | * @return File 116 | */ 117 | public function getFile($basename) 118 | { 119 | return $this->updateMetadata(new File($basename)); 120 | } 121 | 122 | /** 123 | * Creates Link object. 124 | * 125 | * @param string $basename 126 | * @param Structure\Node $destination 127 | * 128 | * @return Link 129 | */ 130 | public function getLink($basename, Node $destination) 131 | { 132 | return $this->updateMetadata(new Link($basename, $destination)); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/FileSystem.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem; 12 | 13 | use VirtualFileSystem\Structure\Directory; 14 | use VirtualFileSystem\Structure\File; 15 | use VirtualFileSystem\Structure\Link; 16 | use VirtualFileSystem\Structure\Root; 17 | 18 | /** 19 | * Main 'access' class to vfs implementation. It will register new stream wrapper on instantiation. 20 | * 21 | * This class provides methods to get access to Container as well as file URI helper. 22 | * 23 | * @author Michael Donat 24 | * @package php-vfs 25 | */ 26 | class FileSystem 27 | { 28 | protected $scheme; 29 | 30 | /** 31 | * @var Container 32 | */ 33 | protected $container; 34 | 35 | /** 36 | * Class constructor. Will register both, the stream default options and wrapper handler. 37 | * 38 | * Note: Each FileSystem instance will create NEW stream wrapper/scheme. 39 | */ 40 | public function __construct() 41 | { 42 | $this->scheme = uniqid('phpvfs'); 43 | 44 | /* injecting components */ 45 | $this->container = $container = new Container(new Factory()); 46 | $this->container->root()->setScheme($this->scheme); 47 | 48 | $this->registerContextOptions($container); 49 | 50 | stream_wrapper_register($this->scheme, sprintf('\%s\%s', __NAMESPACE__, 'Wrapper')); 51 | } 52 | 53 | /** 54 | * Returns wrapper scheme. 55 | * 56 | * @return string 57 | */ 58 | public function scheme() 59 | { 60 | return $this->scheme; 61 | } 62 | 63 | /** 64 | * Registers Container object as default context option for scheme associated with FileSystem instance. 65 | * 66 | * @param Container $container 67 | */ 68 | protected function registerContextOptions(Container $container) 69 | { 70 | $defaultOptions = stream_context_get_options(stream_context_get_default()); 71 | stream_context_set_default(array_merge( 72 | $defaultOptions, 73 | array($this->scheme => array('Container' => $container)) 74 | )); 75 | } 76 | 77 | /** 78 | * Remoces wrapper registered for scheme associated with FileSystem instance. 79 | */ 80 | public function __destruct() 81 | { 82 | stream_wrapper_unregister($this->scheme); 83 | } 84 | 85 | /** 86 | * Returns Container instance. 87 | * 88 | * @return Container 89 | */ 90 | public function container() 91 | { 92 | return $this->container; 93 | } 94 | 95 | /** 96 | * Returns Root instance. 97 | * 98 | * @return Root 99 | */ 100 | public function root() 101 | { 102 | return $this->container()->root(); 103 | } 104 | 105 | /** 106 | * Returns absolute path to full URI path (with scheme) 107 | * 108 | * @param string $path - path without scheme 109 | * 110 | * @return string - path with scheme 111 | */ 112 | public function path($path) 113 | { 114 | $path = ltrim($path, '/'); 115 | 116 | return $this->scheme().'://'.$path; 117 | } 118 | 119 | /** 120 | * Creates and returns a directory 121 | * 122 | * @param string $path 123 | * @param bool $recursive 124 | * @param integer|null $mode 125 | * 126 | * @return Directory 127 | */ 128 | public function createDirectory($path, $recursive = false, $mode = null) 129 | { 130 | return $this->container()->createDir($path, $recursive, $mode); 131 | } 132 | 133 | /** 134 | * Creates and returns a file 135 | * 136 | * @param string $path 137 | * @param string|null $data 138 | * 139 | * @return File 140 | */ 141 | public function createFile($path, $data = null) 142 | { 143 | return $this->container()->createFile($path, $data); 144 | } 145 | 146 | /** 147 | * Creates fs structure 148 | * 149 | * @param array $structure 150 | */ 151 | public function createStructure(array $structure) 152 | { 153 | $this->container()->createStructure($structure); 154 | } 155 | 156 | /** 157 | * Creates and returns a link 158 | * 159 | * @param string $path 160 | * @param string $destinationPath 161 | * 162 | * @return Link 163 | */ 164 | public function createLink($path, $destinationPath) 165 | { 166 | return $this->container()->createLink($path, $destinationPath); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Loader.php: -------------------------------------------------------------------------------- 1 | register(); 12 | * 13 | * @author Jonathan H. Wage 14 | * @author Roman S. Borschel 15 | * @author Matthew Weier O'Phinney 16 | * @author Kris Wallsmith 17 | * @author Fabien Potencier 18 | */ 19 | 20 | namespace VirtualFileSystem; 21 | 22 | /** 23 | * Class Loader to use with PEAR installation 24 | * 25 | * @package VirtualFileSystem 26 | */ 27 | class Loader 28 | { 29 | private $fileExtension = '.php'; 30 | private $namespace; 31 | private $includePath; 32 | private $namespaceSeparator = '\\'; 33 | 34 | /** 35 | * Creates a new Loader that loads classes of the 36 | * specified namespace. 37 | * 38 | * @param string $namespace The namespace to use. 39 | * @param null $includePath 40 | */ 41 | public function __construct($namespace = 'VirtualFileSystem', $includePath = null) 42 | { 43 | $this->namespace = $namespace; 44 | $this->includePath = $includePath; 45 | } 46 | 47 | /** 48 | * Sets the namespace separator used by classes in the namespace of this class loader. 49 | * 50 | * @param string $sep The separator to use. 51 | */ 52 | public function setNamespaceSeparator($sep) 53 | { 54 | $this->namespaceSeparator = $sep; 55 | } 56 | 57 | /** 58 | * Gets the namespace seperator used by classes in the namespace of this class loader. 59 | * 60 | * @return string 61 | */ 62 | public function getNamespaceSeparator() 63 | { 64 | return $this->namespaceSeparator; 65 | } 66 | 67 | /** 68 | * Sets the base include path for all class files in the namespace of this class loader. 69 | * 70 | * @param string $includePath 71 | */ 72 | public function setIncludePath($includePath) 73 | { 74 | $this->includePath = $includePath; 75 | } 76 | 77 | /** 78 | * Gets the base include path for all class files in the namespace of this class loader. 79 | * 80 | * @return null|string $includePath 81 | */ 82 | public function getIncludePath() 83 | { 84 | return $this->includePath; 85 | } 86 | 87 | /** 88 | * Sets the file extension of class files in the namespace of this class loader. 89 | * 90 | * @param string $fileExtension 91 | */ 92 | public function setFileExtension($fileExtension) 93 | { 94 | $this->fileExtension = $fileExtension; 95 | } 96 | 97 | /** 98 | * Gets the file extension of class files in the namespace of this class loader. 99 | * 100 | * @return string $fileExtension 101 | */ 102 | public function getFileExtension() 103 | { 104 | return $this->fileExtension; 105 | } 106 | 107 | /** 108 | * Installs this class loader on the SPL autoload stack. 109 | * 110 | * @param bool $prepend If true, prepend autoloader on the autoload stack 111 | */ 112 | public function register($prepend = false) 113 | { 114 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 115 | } 116 | 117 | /** 118 | * Uninstalls this class loader from the SPL autoloader stack. 119 | */ 120 | public function unregister() 121 | { 122 | spl_autoload_unregister(array($this, 'loadClass')); 123 | } 124 | 125 | /** 126 | * Loads the given class or interface. 127 | * 128 | * @param string $className The name of the class to load. 129 | * @return void 130 | */ 131 | public function loadClass($className) 132 | { 133 | if (null === $this->namespace 134 | || $this->namespace.$this->namespaceSeparator === substr( 135 | $className, 136 | 0, 137 | strlen($this->namespace.$this->namespaceSeparator) 138 | )) { 139 | $fileName = ''; 140 | if (false !== ($lastNsPos = strripos($className, $this->namespaceSeparator))) { 141 | $namespace = substr($className, 0, $lastNsPos); 142 | $className = substr($className, $lastNsPos + 1); 143 | $fileName = 144 | str_replace($this->namespaceSeparator, DIRECTORY_SEPARATOR, $namespace) . 145 | DIRECTORY_SEPARATOR; 146 | } 147 | $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . $this->fileExtension; 148 | require $this->getFullPath($fileName); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Structure/Directory.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Structure; 12 | 13 | use VirtualFileSystem\Exception\FileExistsException; 14 | use VirtualFileSystem\Exception\NotFoundException; 15 | 16 | /** 17 | * FileSystem Directory representation. 18 | * 19 | * @author Michael Donat 20 | * @package php-vfs 21 | */ 22 | class Directory extends Node 23 | { 24 | /** 25 | * @see http://man7.org/linux/man-pages/man2/lstat.2.html 26 | */ 27 | const S_IFTYPE = 0040000; 28 | 29 | protected $children = array(); 30 | 31 | /** 32 | * Class constructor. 33 | * 34 | * @param string $basename 35 | * 36 | * @throws \InvalidArgumentException 37 | */ 38 | public function __construct($basename) 39 | { 40 | if ($basename == Root::BASENAME) { 41 | throw new \InvalidArgumentException('Creating directories as root is prohibited'); 42 | } 43 | parent::__construct($basename); 44 | } 45 | 46 | /** 47 | * Adds child Directory. 48 | * 49 | * @param Directory $directory 50 | */ 51 | public function addDirectory(Directory $directory) 52 | { 53 | $this->addNode($directory); 54 | } 55 | 56 | /** 57 | * Adds child File. 58 | * 59 | * @param File $file 60 | */ 61 | public function addFile(File $file) 62 | { 63 | $this->addNode($file); 64 | } 65 | 66 | /** 67 | * Adds child Link. 68 | * 69 | * @param Link $link 70 | */ 71 | public function addLink(Link $link) 72 | { 73 | $this->addNode($link); 74 | } 75 | 76 | /** 77 | * Adds child Node. 78 | * 79 | * @param Node $node 80 | * 81 | * @throws FileExistsException 82 | */ 83 | public function addNode(Node $node) 84 | { 85 | if (array_key_exists($node->basename(), $this->children)) { 86 | throw new FileExistsException(sprintf('%s already exists', $node->basename())); 87 | } 88 | 89 | $this->children[$node->basename()] = $node; 90 | $node->setParent($this); 91 | } 92 | 93 | /** 94 | * Returns size as the number of child elements. 95 | * 96 | * @return int 97 | */ 98 | public function size() 99 | { 100 | return count($this->children); 101 | } 102 | 103 | /** 104 | * Returns child Node existing at path. 105 | * 106 | * @param string $path 107 | * 108 | * @return Node 109 | * 110 | * @throws \VirtualFileSystem\Exception\NotFoundException 111 | */ 112 | public function childAt($path) 113 | { 114 | if (!array_key_exists($path, $this->children)) { 115 | throw new NotFoundException(sprintf('Could not find child %s in %s', $path, $this->path())); 116 | } 117 | 118 | return $this->children[$path]; 119 | } 120 | 121 | /** 122 | * Removes child Node 123 | * 124 | * @param string $basename 125 | */ 126 | public function remove($basename) 127 | { 128 | unset($this->children[$basename]); 129 | } 130 | 131 | /** 132 | * Returns children 133 | * 134 | * @return array 135 | */ 136 | public function children() 137 | { 138 | return $this->children; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Structure/File.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Structure; 12 | 13 | use SplObjectStorage; 14 | 15 | /** 16 | * Object representation of File. 17 | * 18 | * @author Michael Donat 19 | * @package php-vfs 20 | */ 21 | class File extends Node 22 | { 23 | /** 24 | * @see http://man7.org/linux/man-pages/man2/lstat.2.html 25 | */ 26 | const S_IFTYPE = 0100000; 27 | 28 | protected $data; 29 | 30 | /** 31 | * Resource with exclusive lock on this file 32 | * @var resource|null 33 | */ 34 | private $exclusiveLock = null; 35 | 36 | /** 37 | * Resources with a shared lock on this file 38 | * @var SplObjectStorage 39 | */ 40 | private $sharedLock; 41 | 42 | /** 43 | * @inherit 44 | * @param string $basename 45 | */ 46 | public function __construct($basename) 47 | { 48 | parent::__construct($basename); 49 | $this->sharedLock = new SplObjectStorage; 50 | } 51 | 52 | /** 53 | * Returns contents size. 54 | * 55 | * @return int 56 | */ 57 | public function size() 58 | { 59 | return strlen($this->data); 60 | } 61 | 62 | /** 63 | * Returns contents. 64 | * 65 | * @return null|string 66 | */ 67 | public function data() 68 | { 69 | return $this->data; 70 | } 71 | 72 | /** 73 | * Sets contents. 74 | * 75 | * @param $data 76 | * @param null|string $data 77 | */ 78 | public function setData($data) 79 | { 80 | $this->data = $data; 81 | } 82 | 83 | public function lock($resource, $operation) 84 | { 85 | if ($this->exclusiveLock === $resource) { 86 | $this->exclusiveLock = null; 87 | } else { 88 | $this->sharedLock->detach($resource); 89 | } 90 | 91 | if ($operation & LOCK_NB) { 92 | $operation -= LOCK_NB; 93 | } 94 | 95 | $unlock = $operation === LOCK_UN; 96 | $exclusive = $operation === LOCK_EX; 97 | 98 | if ($unlock) { 99 | return true; 100 | } 101 | 102 | if ($this->exclusiveLock !== null) { 103 | return false; 104 | } 105 | 106 | if (!$exclusive) { 107 | $this->sharedLock->attach($resource); 108 | 109 | return true; 110 | } 111 | 112 | if ($this->sharedLock->count()) { 113 | return false; 114 | } 115 | 116 | $this->exclusiveLock = $resource; 117 | 118 | return true; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Structure/Link.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Structure; 12 | 13 | /** 14 | * Object representation of a Link. 15 | * 16 | * @author Michael Donat 17 | * @package php-vfs 18 | */ 19 | class Link extends Node 20 | { 21 | /** 22 | * @see http://man7.org/linux/man-pages/man2/lstat.2.html 23 | */ 24 | const S_IFTYPE = 0120000; 25 | 26 | /** 27 | * @var Node 28 | */ 29 | protected $destination; 30 | 31 | /** 32 | * Class constructor. 33 | * 34 | * @param string $basename 35 | */ 36 | public function __construct($basename, Node $destination) 37 | { 38 | parent::__construct($basename); 39 | $this->destination = $destination; 40 | } 41 | 42 | /** 43 | * Returns Link size. 44 | * 45 | * The size is the length of the destination path 46 | * 47 | * @return mixed 48 | */ 49 | public function size() 50 | { 51 | return $this->destination->size(); 52 | } 53 | 54 | /** 55 | * @return Node 56 | */ 57 | public function getDestination() 58 | { 59 | return $this->destination; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Structure/Node.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Structure; 12 | 13 | /** 14 | * Abstract class to represent filesystem Node. 15 | * 16 | * @author Michael Donat 17 | * @package php-vfs 18 | */ 19 | abstract class Node 20 | { 21 | const S_IFMT = 0160000; 22 | const DEF_MODE = 0755; 23 | 24 | protected $basename; 25 | protected $parent; 26 | protected $userid; 27 | protected $groupid; 28 | 29 | protected $atime; 30 | protected $mtime; 31 | protected $ctime; 32 | 33 | protected $mode; 34 | 35 | /** 36 | * Class constructor. 37 | * 38 | * @param string $basename 39 | */ 40 | public function __construct($basename) 41 | { 42 | $this->basename = $basename; 43 | $this->chmod(self::DEF_MODE); 44 | } 45 | 46 | /** 47 | * Changes access to file. 48 | * 49 | * This will apply the DIR/FILE type mask for use by stat to distinguish between file and directory. 50 | * @see http://man7.org/linux/man-pages/man2/lstat.2.html for explanation. 51 | * 52 | * @param int $mode 53 | */ 54 | public function chmod($mode) 55 | { 56 | $this->mode = $mode | static::S_IFTYPE; 57 | } 58 | 59 | /** 60 | * Returns file mode 61 | * 62 | * @return int 63 | */ 64 | public function mode() 65 | { 66 | return $this->mode; 67 | } 68 | 69 | /** 70 | * Changes ownership. 71 | * 72 | * @param $userid 73 | */ 74 | public function chown($userid) 75 | { 76 | $this->userid = $userid; 77 | } 78 | 79 | /** 80 | * Returns ownership. 81 | * 82 | * @return mixed 83 | */ 84 | public function user() 85 | { 86 | return $this->userid; 87 | } 88 | 89 | /** 90 | * Changes group ownership. 91 | * 92 | * @param $groupid 93 | */ 94 | public function chgrp($groupid) 95 | { 96 | $this->groupid = $groupid; 97 | } 98 | 99 | /** 100 | * Returns group ownership. 101 | * 102 | * @return mixed 103 | */ 104 | public function group() 105 | { 106 | return $this->groupid; 107 | } 108 | 109 | /** 110 | * Returns Node size. 111 | * 112 | * @return mixed 113 | */ 114 | abstract public function size(); 115 | 116 | /** 117 | * Sets parent Node. 118 | * 119 | * @param Directory $parent 120 | */ 121 | protected function setParent(Directory $parent) 122 | { 123 | $this->parent = $parent; 124 | } 125 | 126 | /** 127 | * Returns Node basename. 128 | * 129 | * @return string 130 | */ 131 | public function basename() 132 | { 133 | return $this->basename; 134 | } 135 | 136 | /** 137 | * Sets new basename 138 | * 139 | * @param string $basename 140 | */ 141 | public function setBasename($basename) 142 | { 143 | $this->basename = $basename; 144 | } 145 | 146 | /** 147 | * Returns node path. 148 | * 149 | * @return string 150 | */ 151 | public function path() 152 | { 153 | $dirname = $this->dirname(); 154 | 155 | if ($this->parent instanceof Root) { //at root 156 | 157 | return $dirname.$this->basename(); 158 | } 159 | 160 | return sprintf('%s/%s', $dirname, $this->basename()); 161 | 162 | } 163 | 164 | /** 165 | * Returns node URL. 166 | * 167 | * @return string 168 | */ 169 | public function url() 170 | { 171 | $dirname = $this->parent->url(); 172 | 173 | if ($this->parent instanceof Root) { //at root 174 | 175 | return $dirname.$this->basename(); 176 | } 177 | 178 | return sprintf('%s/%s', $dirname, $this->basename()); 179 | 180 | } 181 | 182 | /** 183 | * Returns node absolute path (without scheme). 184 | * 185 | * @return string 186 | */ 187 | public function __toString() 188 | { 189 | return $this->path(); 190 | } 191 | 192 | /** 193 | * Returns Node parent absolute path. 194 | * 195 | * @return string|null 196 | */ 197 | public function dirname() 198 | { 199 | if ($this->parent) { 200 | return $this->parent->path(); 201 | } 202 | } 203 | 204 | /** 205 | * Sets last access time 206 | * 207 | * @param int $time 208 | */ 209 | public function setAccessTime($time) 210 | { 211 | $this->atime = $time; 212 | } 213 | 214 | /** 215 | * Sets last modification time 216 | * 217 | * @param int $time 218 | */ 219 | public function setModificationTime($time) 220 | { 221 | $this->mtime = $time; 222 | } 223 | 224 | /** 225 | * Sets last inode change time 226 | * 227 | * @param int $time 228 | */ 229 | public function setChangeTime($time) 230 | { 231 | $this->ctime = $time; 232 | } 233 | 234 | /** 235 | * Returns last access time 236 | * 237 | * @return int 238 | */ 239 | public function atime() 240 | { 241 | return $this->atime; 242 | } 243 | 244 | /** 245 | * Returns last modification time 246 | * 247 | * @return int 248 | */ 249 | public function mtime() 250 | { 251 | return $this->mtime; 252 | } 253 | 254 | /** 255 | * Returns last inode change time (chown etc.) 256 | * 257 | * @return int 258 | */ 259 | public function ctime() 260 | { 261 | return $this->ctime; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Structure/Root.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Structure; 12 | 13 | /** 14 | * FileSystem Root representation. 15 | * 16 | * Specialised Directory that does not allow for basename or parent setting. 17 | * 18 | * @author Michael Donat 19 | * @package php-vfs 20 | */ 21 | class Root extends Directory 22 | { 23 | const BASENAME = '/'; 24 | protected $scheme; 25 | 26 | /** 27 | * Class constructor. 28 | */ 29 | public function __construct() 30 | { 31 | $this->basename = self::BASENAME; 32 | $this->chmod(static::DEF_MODE); 33 | } 34 | 35 | /** 36 | * Defined to prevent setting parent on Root. 37 | * 38 | * @param Directory $parent 39 | * 40 | * @throws \LogicException 41 | */ 42 | protected function setParent(Directory $parent) 43 | { 44 | throw new \LogicException('Root cannot have a parent.'); 45 | } 46 | 47 | /** 48 | * Set root scheme for use in path method. 49 | * 50 | * @param string $scheme 51 | */ 52 | public function setScheme($scheme) 53 | { 54 | list($scheme) = explode(':', $scheme); 55 | $this->scheme = $scheme.'://'; 56 | } 57 | 58 | /** 59 | * Returns URL to file. 60 | * 61 | * @return string 62 | */ 63 | public function path() 64 | { 65 | return '/'; 66 | } 67 | 68 | public function url() 69 | { 70 | if (!$this->scheme) { 71 | throw new \RuntimeException('No scheme set'); 72 | } 73 | 74 | return $this->scheme; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Wrapper.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem; 12 | 13 | use VirtualFileSystem\Exception\FileExistsException; 14 | use VirtualFileSystem\Exception\NotDirectoryException; 15 | use VirtualFileSystem\Exception\NotFoundException; 16 | use VirtualFileSystem\Structure\Directory; 17 | use VirtualFileSystem\Structure\File; 18 | use VirtualFileSystem\Structure\Link; 19 | use VirtualFileSystem\Structure\Root; 20 | use VirtualFileSystem\Wrapper\DirectoryHandler; 21 | use VirtualFileSystem\Wrapper\FileHandler; 22 | 23 | /** 24 | * Stream wrapper class. This is the class that PHP uses as the stream operations handler. 25 | * 26 | * @see http://php.net/streamwrapper for informal protocol description 27 | * 28 | * @author Michael Donat 29 | * @package php-vfs 30 | * @api 31 | */ 32 | class Wrapper 33 | { 34 | public $context; 35 | 36 | /** 37 | * @var FileHandler 38 | */ 39 | protected $currentlyOpenedFile; 40 | 41 | /** 42 | * @var DirectoryHandler 43 | */ 44 | protected $currentlyOpenedDir; 45 | 46 | /** 47 | * Returns default expectation for stat() function. 48 | * 49 | * @see http://php.net/stat 50 | * 51 | * @return array<*,integer> 52 | */ 53 | protected function getStatArray() 54 | { 55 | $assoc = array( 56 | 'dev' => 0, 57 | 'ino' => 0, 58 | 'mode' => 0, 59 | 'nlink' => 0, 60 | 'uid' => 0, 61 | 'gid' => 0, 62 | 'rdev' => 0, 63 | 'size' => 123, 64 | 'atime' => 0, 65 | 'mtime' => 0, 66 | 'ctime' => 0, 67 | 'blksize' => -1, 68 | 'blocks' => -1 69 | ); 70 | 71 | return array_merge(array_values($assoc), $assoc); 72 | } 73 | 74 | /** 75 | * Returns path stripped of url scheme (http://, ftp://, test:// etc.) 76 | * 77 | * @param string $path 78 | * 79 | * @return string 80 | */ 81 | public function stripScheme($path) 82 | { 83 | $scheme = preg_split('#://#', $path, 2); 84 | $scheme = end($scheme); 85 | 86 | return '/'.ltrim($scheme, '/'); 87 | } 88 | 89 | /** 90 | * Returns Container object fished form default_context_options by scheme. 91 | * 92 | * @param $path 93 | * 94 | * @return Container 95 | */ 96 | public function getContainerFromContext($path) 97 | { 98 | $scheme = current(preg_split('#://#', $path)); 99 | $options = stream_context_get_options(stream_context_get_default()); 100 | 101 | return $options[$scheme]['Container']; 102 | } 103 | 104 | /** 105 | * @see http://php.net/streamwrapper.stream-tell 106 | * 107 | * @return int 108 | */ 109 | public function stream_tell() 110 | { 111 | return $this->currentlyOpenedFile->position(); 112 | } 113 | 114 | /** 115 | * @see http://php.net/streamwrapper.stream-cast 116 | * 117 | * @return void 118 | */ 119 | public function stream_cast() { 120 | return false; 121 | } 122 | 123 | /** 124 | * @see http://php.net/streamwrapper.stream-close 125 | * 126 | * @return void 127 | */ 128 | public function stream_close() 129 | { 130 | $this->currentlyOpenedFile = null; 131 | } 132 | 133 | /** 134 | * Opens stream in selected mode. 135 | * 136 | * @see http://php.net/streamwrapper.stream-open 137 | * 138 | * @param string $path 139 | * @param int $mode 140 | * @param int $options 141 | * 142 | * @param $openedPath 143 | * @throws NotDirectoryException 144 | * @throws NotFoundException 145 | * @return bool 146 | */ 147 | public function stream_open($path, $mode, $options, &$openedPath) 148 | { 149 | $container = $this->getContainerFromContext($path); 150 | $path = $this->stripScheme($path); 151 | 152 | $mode = str_split(str_replace('b', '', $mode)); 153 | 154 | $accessDeniedError = function () use ($path, $options) { 155 | if ($options & STREAM_REPORT_ERRORS) { 156 | trigger_error(sprintf('fopen(%s): failed to open stream: Permission denied', $path), E_USER_WARNING); 157 | } 158 | 159 | return false; 160 | }; 161 | 162 | $appendMode = in_array('a', $mode); 163 | $readMode = in_array('r', $mode); 164 | $writeMode = in_array('w', $mode); 165 | $extended = in_array('+', $mode); 166 | 167 | if (!$container->hasNodeAt($path)) { 168 | if ($readMode || !$container->hasNodeAt(dirname($path))) { 169 | if ($options & STREAM_REPORT_ERRORS) { 170 | trigger_error(sprintf('%s: failed to open stream.', $path), E_USER_WARNING); 171 | } 172 | 173 | return false; 174 | } 175 | $parent = $container->directoryAt(dirname($path)); 176 | $permissionHelper = $container->getPermissionHelper($parent); 177 | if (!$permissionHelper->isWritable()) { 178 | return $accessDeniedError(); 179 | } 180 | $parent->addFile($container->factory()->getFile(basename($path))); 181 | } 182 | 183 | $file = $container->nodeAt($path); 184 | 185 | if ($file instanceof Link) { 186 | $file = $file->getDestination(); 187 | } 188 | 189 | if (($extended || $writeMode || $appendMode) && $file instanceof Directory) { 190 | if ($options & STREAM_REPORT_ERRORS) { 191 | trigger_error(sprintf('fopen(%s): failed to open stream: Is a directory', $path), E_USER_WARNING); 192 | } 193 | 194 | return false; 195 | } 196 | 197 | if ($file instanceof Directory) { 198 | $dir = $file; 199 | $file = $container->factory()->getFile('tmp'); 200 | $file->chmod($dir->mode()); 201 | $file->chown($dir->user()); 202 | $file->chgrp($dir->group()); 203 | } 204 | 205 | $permissionHelper = $container->getPermissionHelper($file); 206 | 207 | $this->currentlyOpenedFile = new FileHandler(); 208 | $this->currentlyOpenedFile->setFile($file); 209 | 210 | if ($extended) { 211 | if (!$permissionHelper->isReadable() || !$permissionHelper->isWritable()) { 212 | return $accessDeniedError(); 213 | } 214 | $this->currentlyOpenedFile->setReadWriteMode(); 215 | } elseif ($readMode) { 216 | if (!$permissionHelper->isReadable()) { 217 | return $accessDeniedError(); 218 | } 219 | $this->currentlyOpenedFile->setReadOnlyMode(); 220 | } else { // a or w are for write only 221 | if (!$permissionHelper->isWritable()) { 222 | return $accessDeniedError(); 223 | } 224 | $this->currentlyOpenedFile->setWriteOnlyMode(); 225 | } 226 | 227 | if ($appendMode) { 228 | $this->currentlyOpenedFile->seekToEnd(); 229 | } elseif ($writeMode) { 230 | $this->currentlyOpenedFile->truncate(); 231 | clearstatcache(); 232 | } 233 | 234 | $openedPath = $file->path(); 235 | 236 | return true; 237 | } 238 | 239 | /** 240 | * Writes data to stream. 241 | * 242 | * @see http://php.net/streamwrapper.stream-write 243 | * 244 | * @param $data 245 | * 246 | * @return integer 247 | */ 248 | public function stream_write($data) 249 | { 250 | if (!$this->currentlyOpenedFile->isOpenedForWriting()) { 251 | return 0; 252 | } 253 | //file access time changes so stat cache needs to be cleared 254 | $written = $this->currentlyOpenedFile->write($data); 255 | clearstatcache(); 256 | 257 | return $written; 258 | } 259 | 260 | /** 261 | * Returns stat data for file inclusion. 262 | * 263 | * @see http://php.net/streamwrapper.stream-stat 264 | * 265 | * @return bool 266 | */ 267 | public function stream_stat() 268 | { 269 | 270 | try { 271 | $file = $this->currentlyOpenedFile->getFile(); 272 | 273 | return array_merge($this->getStatArray(), array( 274 | 'mode' => $file->mode(), 275 | 'uid' => $file->user(), 276 | 'gid' => $file->group(), 277 | 'atime' => $file->atime(), 278 | 'mtime' => $file->mtime(), 279 | 'ctime' => $file->ctime(), 280 | 'size' => $file->size() 281 | )); 282 | } catch (NotFoundException $e) { 283 | return false; 284 | } 285 | } 286 | 287 | /** 288 | * Returns file stat information 289 | * 290 | * @see http://php.net/stat 291 | * 292 | * @param string $path 293 | * 294 | * @return array|false 295 | */ 296 | public function url_stat($path) 297 | { 298 | try { 299 | $file = $this->getContainerFromContext($path)->nodeAt($this->stripScheme($path)); 300 | 301 | return array_merge($this->getStatArray(), array( 302 | 'mode' => $file->mode(), 303 | 'uid' => $file->user(), 304 | 'gid' => $file->group(), 305 | 'atime' => $file->atime(), 306 | 'mtime' => $file->mtime(), 307 | 'ctime' => $file->ctime(), 308 | 'size' => $file->size() 309 | )); 310 | } catch (NotFoundException $e) { 311 | return false; 312 | } 313 | } 314 | 315 | /** 316 | * Reads and returns $bytes amount of bytes from stream. 317 | * 318 | * @see http://php.net/streamwrapper.stream-read 319 | * 320 | * @param int $bytes 321 | * 322 | * @return string|null 323 | */ 324 | public function stream_read($bytes) 325 | { 326 | if (!$this->currentlyOpenedFile->isOpenedForReading()) { 327 | return null; 328 | } 329 | $data = $this->currentlyOpenedFile->read($bytes); 330 | //file access time changes so stat cache needs to be cleared 331 | clearstatcache(); 332 | 333 | return $data; 334 | } 335 | 336 | /** 337 | * Checks whether pointer has reached EOF. 338 | * 339 | * @see http://php.net/streamwrapper.stream-eof 340 | * 341 | * @return bool 342 | */ 343 | public function stream_eof() 344 | { 345 | return $this->currentlyOpenedFile->atEof(); 346 | } 347 | 348 | /** 349 | * Flushes the output. This always returns TRUE, since no buffering takes place in FileHandler. 350 | * 351 | * @see http://php.net/streamwrapper.stream-flush 352 | * 353 | * @return bool 354 | */ 355 | public function stream_flush() 356 | { 357 | return true; 358 | } 359 | 360 | /** 361 | * Called in response to mkdir to create directory. 362 | * 363 | * @see http://php.net/streamwrapper.mkdir 364 | * 365 | * @param string $path 366 | * @param int $mode 367 | * @param int $options 368 | * 369 | * @return bool 370 | */ 371 | public function mkdir($path, $mode, $options) 372 | { 373 | $container = $this->getContainerFromContext($path); 374 | $path = $this->stripScheme($path); 375 | $recursive = (bool) ($options & STREAM_MKDIR_RECURSIVE); 376 | 377 | try { 378 | //need to check all parents for permissions 379 | $parentPath = $path; 380 | while ($parentPath = dirname($parentPath)) { 381 | try { 382 | $parent = $container->nodeAt($parentPath); 383 | $permissionHelper = $container->getPermissionHelper($parent); 384 | if (!$permissionHelper->isWritable()) { 385 | trigger_error(sprintf('mkdir: %s: Permission denied', dirname($path)), E_USER_WARNING); 386 | 387 | return false; 388 | } 389 | if ($parent instanceof Root) { 390 | break; 391 | } 392 | } catch (NotFoundException $e) { 393 | break; //will sort missing parent below 394 | } 395 | } 396 | $container->createDir($path, $recursive, $mode); 397 | } catch (FileExistsException $e) { 398 | trigger_error($e->getMessage(), E_USER_WARNING); 399 | 400 | return false; 401 | } catch (NotFoundException $e) { 402 | trigger_error(sprintf('mkdir: %s: No such file or directory', dirname($path)), E_USER_WARNING); 403 | 404 | return false; 405 | } 406 | 407 | return true; 408 | } 409 | 410 | /** 411 | * Called in response to chown/chgrp/touch/chmod etc. 412 | * 413 | * @see http://php.net/streamwrapper.stream-metadata 414 | * 415 | * @param string $path 416 | * @param int $option 417 | * @param integer $value 418 | * 419 | * @return bool 420 | */ 421 | public function stream_metadata($path, $option, $value) 422 | { 423 | $container = $this->getContainerFromContext($path); 424 | $strippedPath = $this->stripScheme($path); 425 | 426 | try { 427 | 428 | if ($option == STREAM_META_TOUCH) { 429 | if (!$container->hasNodeAt($strippedPath)) { 430 | try { 431 | $container->createFile($strippedPath); 432 | } catch (NotFoundException $e) { 433 | trigger_error( 434 | sprintf('touch: %s: No such file or directory.', $strippedPath), 435 | E_USER_WARNING 436 | ); 437 | 438 | return false; 439 | } 440 | } 441 | $file = $container->nodeAt($strippedPath); 442 | 443 | $permissionHelper = $container->getPermissionHelper($file); 444 | 445 | if (!$permissionHelper->userIsOwner() && !$permissionHelper->isWritable()) { 446 | trigger_error( 447 | sprintf('touch: %s: Permission denied', $strippedPath), 448 | E_USER_WARNING 449 | ); 450 | 451 | return false; 452 | } 453 | 454 | $time = isset($value[0]) ? $value[0] : time(); 455 | $atime = isset($value[1]) ? $value[1] : $time; 456 | 457 | $file->setChangeTime($time); 458 | $file->setModificationTime($time); 459 | $file->setAccessTime($atime); 460 | 461 | clearstatcache(true, $path); 462 | 463 | return true; 464 | 465 | } 466 | 467 | $node = $container->nodeAt($strippedPath); 468 | $permissionHelper = $container->getPermissionHelper($node); 469 | 470 | switch ($option) { 471 | case STREAM_META_ACCESS: 472 | 473 | if ($node instanceof Link) { 474 | $node = $node->getDestination(); 475 | $permissionHelper = $container->getPermissionHelper($node); 476 | } 477 | 478 | if (!$permissionHelper->userIsOwner()) { 479 | trigger_error( 480 | sprintf('chmod: %s: Permission denied', $strippedPath), 481 | E_USER_WARNING 482 | ); 483 | 484 | return false; 485 | } 486 | $node->chmod($value); 487 | $node->setChangeTime(time()); 488 | break; 489 | 490 | case STREAM_META_OWNER_NAME: 491 | if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { 492 | trigger_error( 493 | sprintf('chown: %s: Permission denied', $strippedPath), 494 | E_USER_WARNING 495 | ); 496 | 497 | return false; 498 | } 499 | 500 | $uid = 0; 501 | 502 | if (function_exists('posix_getpwnam')) { 503 | $user = posix_getpwnam($value); 504 | 505 | if ($user !== false) { 506 | $uid = $user['uid']; 507 | } 508 | } 509 | 510 | $node->chown($uid); 511 | $node->setChangeTime(time()); 512 | break; 513 | 514 | case STREAM_META_OWNER: 515 | if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { 516 | trigger_error( 517 | sprintf('chown: %s: Permission denied', $strippedPath), 518 | E_USER_WARNING 519 | ); 520 | 521 | return false; 522 | } 523 | $node->chown($value); 524 | $node->setChangeTime(time()); 525 | break; 526 | 527 | case STREAM_META_GROUP_NAME: 528 | if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { 529 | trigger_error( 530 | sprintf('chgrp: %s: Permission denied', $strippedPath), 531 | E_USER_WARNING 532 | ); 533 | 534 | return false; 535 | } 536 | 537 | $gid = 0; 538 | 539 | if (function_exists('posix_getgrnam')) { 540 | $group = posix_getgrnam($value); 541 | 542 | if ($group !== false) { 543 | $gid = $group['gid']; 544 | } 545 | } 546 | 547 | $node->chgrp($gid); 548 | $node->setChangeTime(time()); 549 | break; 550 | 551 | case STREAM_META_GROUP: 552 | if (!$permissionHelper->userIsRoot() && !$permissionHelper->userIsOwner()) { 553 | trigger_error( 554 | sprintf('chgrp: %s: Permission denied', $strippedPath), 555 | E_USER_WARNING 556 | ); 557 | 558 | return false; 559 | } 560 | $node->chgrp($value); 561 | $node->setChangeTime(time()); 562 | break; 563 | } 564 | } catch (NotFoundException $e) { 565 | return false; 566 | } 567 | 568 | clearstatcache(true, $path); 569 | 570 | return true; 571 | } 572 | 573 | /** 574 | * Sets file pointer to specified position 575 | * 576 | * @param int $offset 577 | * @param int $whence 578 | * 579 | * @return bool 580 | */ 581 | public function stream_seek($offset, $whence = SEEK_SET) 582 | { 583 | switch ($whence) { 584 | case SEEK_SET: 585 | $this->currentlyOpenedFile->position($offset); 586 | break; 587 | case SEEK_CUR: 588 | $this->currentlyOpenedFile->offsetPosition($offset); 589 | break; 590 | case SEEK_END: 591 | $this->currentlyOpenedFile->seekToEnd(); 592 | $this->currentlyOpenedFile->offsetPosition($offset); 593 | } 594 | 595 | return true; 596 | } 597 | 598 | /** 599 | * Truncates file to given size 600 | * 601 | * @param int $newSize 602 | * 603 | * @return bool 604 | */ 605 | public function stream_truncate($newSize) 606 | { 607 | $this->currentlyOpenedFile->truncate($newSize); 608 | clearstatcache(); 609 | 610 | return true; 611 | } 612 | 613 | /** 614 | * Renames/Moves file 615 | * 616 | * @param string $oldname 617 | * @param string $newname 618 | * 619 | * @return bool 620 | */ 621 | public function rename($oldname, $newname) 622 | { 623 | $container = $this->getContainerFromContext($newname); 624 | $oldname = $this->stripScheme($oldname); 625 | $newname = $this->stripScheme($newname); 626 | 627 | try { 628 | $container->move($oldname, $newname); 629 | } catch (NotFoundException $e) { 630 | trigger_error( 631 | sprintf('mv: rename %s to %s: No such file or directory', $oldname, $newname), 632 | E_USER_WARNING 633 | ); 634 | 635 | return false; 636 | } catch (\RuntimeException $e) { 637 | trigger_error( 638 | sprintf('mv: rename %s to %s: Not a directory', $oldname, $newname), 639 | E_USER_WARNING 640 | ); 641 | 642 | return false; 643 | } 644 | 645 | return true; 646 | } 647 | 648 | /** 649 | * Deletes file at given path 650 | * 651 | * @param int $path 652 | * 653 | * @return bool 654 | */ 655 | public function unlink($path) 656 | { 657 | $container = $this->getContainerFromContext($path); 658 | 659 | try { 660 | 661 | $path = $this->stripScheme($path); 662 | 663 | $parent = $container->nodeAt(dirname($path)); 664 | 665 | $permissionHelper = $container->getPermissionHelper($parent); 666 | if (!$permissionHelper->isWritable()) { 667 | trigger_error( 668 | sprintf('rm: %s: Permission denied', $path), 669 | E_USER_WARNING 670 | ); 671 | 672 | return false; 673 | } 674 | 675 | $container->remove($path = $this->stripScheme($path)); 676 | } catch (NotFoundException $e) { 677 | trigger_error( 678 | sprintf('rm: %s: No such file or directory', $path), 679 | E_USER_WARNING 680 | ); 681 | 682 | return false; 683 | } catch (\RuntimeException $e) { 684 | trigger_error( 685 | sprintf('rm: %s: is a directory', $path), 686 | E_USER_WARNING 687 | ); 688 | 689 | return false; 690 | } 691 | 692 | return true; 693 | } 694 | 695 | /** 696 | * Removes directory 697 | * 698 | * @param string $path 699 | * 700 | * @return bool 701 | */ 702 | public function rmdir($path) 703 | { 704 | $container = $this->getContainerFromContext($path); 705 | $path = $this->stripScheme($path); 706 | 707 | try { 708 | $directory = $container->nodeAt($path); 709 | 710 | if ($directory instanceof File) { 711 | trigger_error( 712 | sprintf('Warning: rmdir(%s): Not a directory', $path), 713 | E_USER_WARNING 714 | ); 715 | 716 | return false; 717 | } 718 | 719 | $permissionHelper = $container->getPermissionHelper($directory); 720 | if (!$permissionHelper->isReadable()) { 721 | trigger_error( 722 | sprintf('rmdir: %s: Permission denied', $path), 723 | E_USER_WARNING 724 | ); 725 | 726 | return false; 727 | } 728 | 729 | } catch (NotFoundException $e) { 730 | trigger_error( 731 | sprintf('Warning: rmdir(%s): No such file or directory', $path), 732 | E_USER_WARNING 733 | ); 734 | 735 | return false; 736 | } 737 | 738 | if ($directory->size()) { 739 | trigger_error( 740 | sprintf('Warning: rmdir(%s): Directory not empty', $path), 741 | E_USER_WARNING 742 | ); 743 | 744 | return false; 745 | } 746 | 747 | $container->remove($path, true); 748 | 749 | return true; 750 | } 751 | 752 | /** 753 | * Opens directory for iteration 754 | * 755 | * @param string $path 756 | * 757 | * @return bool 758 | */ 759 | public function dir_opendir($path) 760 | { 761 | $container = $this->getContainerFromContext($path); 762 | 763 | $path = $this->stripScheme($path); 764 | 765 | if (!$container->hasNodeAt($path)) { 766 | trigger_error(sprintf('opendir(%s): failed to open dir: No such file or directory', $path), E_USER_WARNING); 767 | 768 | return false; 769 | } 770 | 771 | try { 772 | 773 | $dir = $container->directoryAt($path); 774 | 775 | } catch (NotDirectoryException $e) { 776 | trigger_error(sprintf('opendir(%s): failed to open dir: Not a directory', $path), E_USER_WARNING); 777 | 778 | return false; 779 | } 780 | 781 | $permissionHelper = $container->getPermissionHelper($dir); 782 | 783 | if (!$permissionHelper->isReadable()) { 784 | trigger_error(sprintf('opendir(%s): failed to open dir: Permission denied', $path), E_USER_WARNING); 785 | 786 | return false; 787 | } 788 | 789 | $this->currentlyOpenedDir = new DirectoryHandler(); 790 | $this->currentlyOpenedDir->setDirectory($dir); 791 | 792 | return true; 793 | } 794 | 795 | /** 796 | * Closes opened dir 797 | * 798 | * @return bool 799 | */ 800 | public function dir_closedir() 801 | { 802 | if ($this->currentlyOpenedDir) { 803 | $this->currentlyOpenedDir = null; 804 | 805 | return true; 806 | } 807 | 808 | return false; 809 | } 810 | 811 | /** 812 | * Returns next file url in directory 813 | * 814 | * @return null 815 | */ 816 | public function dir_readdir() 817 | { 818 | $node = $this->currentlyOpenedDir->iterator()->current(); 819 | if (!$node) { 820 | return false; 821 | } 822 | $this->currentlyOpenedDir->iterator()->next(); 823 | 824 | return $node->basename(); 825 | } 826 | 827 | /** 828 | * Resets directory iterator 829 | */ 830 | public function dir_rewinddir() 831 | { 832 | $this->currentlyOpenedDir->iterator()->rewind(); 833 | } 834 | 835 | public function stream_lock($operation) 836 | { 837 | return $this->currentlyOpenedFile->lock($this, $operation); 838 | } 839 | 840 | public function stream_set_option($option, $arg1, $arg2) 841 | { 842 | return false; 843 | } 844 | } 845 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Wrapper/DirectoryHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Wrapper; 12 | 13 | use VirtualFileSystem\Structure\Directory; 14 | 15 | /** 16 | * User as directory handle by streamWrapper implementation. 17 | * 18 | * This class is responsible mainly iterating over directory. 19 | * 20 | * @author Michael Donat 21 | * @package php-vfs 22 | */ 23 | class DirectoryHandler 24 | { 25 | /** 26 | * @var Directory 27 | */ 28 | protected $directory; 29 | 30 | /** 31 | * @var \ArrayIterator 32 | */ 33 | protected $iterator; 34 | 35 | /** 36 | * Sets directory in context. 37 | * 38 | * @param Directory $directory 39 | */ 40 | public function setDirectory(Directory $directory) 41 | { 42 | $this->directory = $directory; 43 | $this->iterator = new \ArrayIterator($directory->children()); 44 | } 45 | 46 | /** 47 | * Returns children iterator 48 | * 49 | * @return ArrayIterator 50 | */ 51 | public function iterator() 52 | { 53 | return $this->iterator; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Wrapper/FileHandler.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Wrapper; 12 | 13 | use VirtualFileSystem\Structure\File; 14 | 15 | /** 16 | * User as file handle by streamWrapper implementation. 17 | * 18 | * This class is responsible mainly for managing the pointer position during reading and writing. 19 | * 20 | * @author Michael Donat 21 | * @package php-vfs 22 | */ 23 | class FileHandler 24 | { 25 | const READ_MODE = 1; 26 | const WRITE_MODE = 2; 27 | 28 | protected $position = 0; 29 | 30 | protected $mode = 0; 31 | 32 | /** 33 | * @var File 34 | */ 35 | protected $file; 36 | 37 | /** 38 | * Sets file in context. 39 | * 40 | * @param File $file 41 | */ 42 | public function setFile(File $file) 43 | { 44 | $this->file = $file; 45 | } 46 | 47 | /** 48 | * Returns file in context. 49 | * 50 | * @return File 51 | */ 52 | public function getFile() { 53 | return $this->file; 54 | } 55 | 56 | /** 57 | * Writes data to file. Will return the number of bytes written. Will advance pointer by number of bytes written. 58 | * 59 | * @param string $data 60 | * 61 | * @return int 62 | */ 63 | public function write($data) 64 | { 65 | $content = $this->file->data(); 66 | $content = substr($content, 0, $this->position()); 67 | $content .= $data; 68 | $this->file->setData($content); 69 | $written = strlen($data); 70 | $this->offsetPosition($written); 71 | $this->file->setModificationTime(time()); 72 | $this->file->setChangeTime(time()); 73 | 74 | return $written; 75 | } 76 | 77 | /** 78 | * Will read and return $bytes bytes from file. Will advance pointer by $bytes bytes. 79 | * 80 | * @param int $bytes 81 | * 82 | * @return string 83 | */ 84 | public function read($bytes) 85 | { 86 | $content = $this->file->data(); 87 | 88 | $return = substr($content, $this->position(), $bytes); 89 | 90 | $newPosition = $this->position()+$bytes; 91 | 92 | $newPosition = $newPosition > strlen($content) ? strlen($content) : $newPosition; 93 | 94 | $this->position($newPosition); 95 | $this->file->setAccessTime(time()); 96 | 97 | return $return; 98 | } 99 | 100 | /** 101 | * Returns current pointer position. 102 | * 103 | * @param integer|null $position 104 | * 105 | * @return int 106 | */ 107 | public function position($position = null) 108 | { 109 | return is_null($position) ? $this->position : $this->position = $position; 110 | } 111 | 112 | /** 113 | * Moves pointer to the end of file (for append modes). 114 | * 115 | * @return int 116 | */ 117 | public function seekToEnd() 118 | { 119 | return $this->position(strlen($this->file->data())); 120 | } 121 | 122 | /** 123 | * Offsets position by $offset 124 | * 125 | * @param int $offset 126 | */ 127 | public function offsetPosition($offset) 128 | { 129 | $this->position += $offset; 130 | } 131 | 132 | /** 133 | * Tells whether pointer is at the end of file. 134 | * 135 | * @return bool 136 | */ 137 | public function atEof() 138 | { 139 | return $this->position() >= strlen($this->file->data()); 140 | } 141 | 142 | /** 143 | * Removed all data from file and sets pointer to 0 144 | * 145 | * @param int $newSize 146 | * 147 | * @return void 148 | */ 149 | public function truncate($newSize = 0) 150 | { 151 | $this->position(0); 152 | $newData = substr($this->file->data(), 0, $newSize); 153 | $newData = false === $newData ? '' : $newData; 154 | $this->file->setData($newData); 155 | $this->file->setModificationTime(time()); 156 | $this->file->setChangeTime(time()); 157 | 158 | return; 159 | } 160 | 161 | /** 162 | * Sets handler to read only 163 | */ 164 | public function setReadOnlyMode() 165 | { 166 | $this->mode = self::READ_MODE; 167 | } 168 | 169 | /** 170 | * Sets handler into read/write mode 171 | */ 172 | public function setReadWriteMode() 173 | { 174 | $this->mode = self::READ_MODE | self::WRITE_MODE; 175 | } 176 | 177 | public function setWriteOnlyMode() 178 | { 179 | $this->mode = self::WRITE_MODE; 180 | } 181 | 182 | /** 183 | * Checks if pointer allows writing 184 | * 185 | * @return bool 186 | */ 187 | public function isOpenedForWriting() 188 | { 189 | return (bool) ($this->mode & self::WRITE_MODE); 190 | } 191 | 192 | /** 193 | * Checks if pointer allows reading 194 | * 195 | * @return bool 196 | */ 197 | public function isOpenedForReading() 198 | { 199 | return (bool) ($this->mode & self::READ_MODE); 200 | } 201 | 202 | /** 203 | * @param $resource 204 | */ 205 | public function lock($resource, $operation) 206 | { 207 | return $this->file->lock($resource, $operation); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /src/VirtualFileSystem/Wrapper/PermissionHelper.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | 11 | namespace VirtualFileSystem\Wrapper; 12 | 13 | use VirtualFileSystem\Structure\Node; 14 | 15 | /** 16 | * Class to encapsulate permission checks 17 | * 18 | * @author Michael Donat 19 | * @package php-vfs 20 | * @api 21 | */ 22 | class PermissionHelper 23 | { 24 | const MODE_USER_READ = 0400; 25 | const MODE_USER_WRITE = 0200; 26 | 27 | const MODE_GROUP_READ = 0040; 28 | const MODE_GROUP_WRITE = 0020; 29 | 30 | const MODE_WORLD_READ = 0004; 31 | const MODE_WORLD_WRITE = 0002; 32 | 33 | /** 34 | * @var Node 35 | */ 36 | protected $node; 37 | 38 | protected $userid; 39 | protected $groupid; 40 | 41 | /** 42 | * @param integer|null $uid 43 | * @param integer|null $gid 44 | */ 45 | public function __construct($uid = null, $gid = null) 46 | { 47 | $this->userid = is_null($uid) ? (function_exists('posix_getuid') ? posix_getuid() : 0) : $uid; 48 | $this->groupid = is_null($gid) ? ((function_exists('posix_getgid') ? posix_getgid() : 0)) : $gid; 49 | } 50 | 51 | /** 52 | * Checks whether user_id on file is the same as executing user 53 | * 54 | * @return bool 55 | */ 56 | public function userIsOwner() 57 | { 58 | return (bool) ($this->userid == $this->node->user()); 59 | } 60 | 61 | /** 62 | * Checks whether file is readable for user 63 | * 64 | * @return bool 65 | */ 66 | public function userCanRead() 67 | { 68 | return $this->userIsOwner() && ($this->node->mode() & self::MODE_USER_READ); 69 | } 70 | 71 | /** 72 | * Checks whether file is writable for user 73 | * 74 | * @return bool 75 | */ 76 | public function userCanWrite() 77 | { 78 | return $this->userIsOwner() && ($this->node->mode() & self::MODE_USER_WRITE); 79 | } 80 | 81 | /** 82 | * Checks whether group_id on file is the same as executing user 83 | * 84 | * @return bool 85 | */ 86 | public function groupIsOwner() 87 | { 88 | return (bool) ($this->groupid == $this->node->group()); 89 | } 90 | 91 | /** 92 | * Checks whether file is readable for group 93 | * 94 | * @return bool 95 | */ 96 | public function groupCanRead() 97 | { 98 | return $this->groupIsOwner() && ($this->node->mode() & self::MODE_GROUP_READ); 99 | } 100 | 101 | /** 102 | * Checks whether file is writable for group 103 | * 104 | * @return bool 105 | */ 106 | public function groupCanWrite() 107 | { 108 | return $this->groupIsOwner() && ($this->node->mode() & self::MODE_GROUP_WRITE); 109 | } 110 | 111 | /** 112 | * Checks whether file is readable for world 113 | * 114 | * @return bool 115 | */ 116 | public function worldCanRead() 117 | { 118 | return (bool) ($this->node->mode() & self::MODE_WORLD_READ); 119 | } 120 | 121 | /** 122 | * Checks whether file is writable for world 123 | * 124 | * @return bool 125 | */ 126 | public function worldCanWrite() 127 | { 128 | return (bool) ($this->node->mode() & self::MODE_WORLD_WRITE); 129 | } 130 | 131 | /** 132 | * Checks whether file is readable (by user, group or world) 133 | * 134 | * @return bool 135 | */ 136 | public function isReadable() 137 | { 138 | return $this->userCanRead() || $this->groupCanRead() || $this->worldCanRead(); 139 | } 140 | 141 | /** 142 | * Checks whether file is writable (by user, group or world) 143 | * 144 | * @return bool 145 | */ 146 | public function isWritable() 147 | { 148 | return $this->userCanWrite() || $this->groupCanWrite() || $this->worldCanWrite(); 149 | } 150 | 151 | /** 152 | * Checks whether userid is 0 - root. 153 | * 154 | * @return bool 155 | */ 156 | public function userIsRoot() 157 | { 158 | return $this->userid == 0; 159 | } 160 | 161 | /** 162 | * @param \VirtualFileSystem\Structure\Node $node 163 | * 164 | * @return PermissionHelper 165 | */ 166 | public function setNode($node) 167 | { 168 | $this->node = $node; 169 | 170 | return $this; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/ContainerTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('\VirtualFileSystem\Structure\Root', $container->root()); 16 | $this->assertEquals('/', $container->root()->basename()); 17 | $this->assertEquals('/', $container->root()->path()); 18 | 19 | } 20 | 21 | public function testNodeAtAddressReturned() 22 | { 23 | $container = new Container(new Factory()); 24 | $container->root()->addDirectory(new Directory('dir1.1')); 25 | $container->root()->addDirectory($d12 = new Directory('dir1.2')); 26 | 27 | $d12->addDirectory($d21 = new Directory('dir2.1')); 28 | $d21->addDirectory($d221 = new Directory('dir2.2.1')); 29 | $d221->addFile($file = new File('testFile')); 30 | 31 | $this->assertEquals($d221, $container->nodeAt('/dir1.2/dir2.1/dir2.2.1')); 32 | 33 | } 34 | 35 | public function testHasNodeAtReturnsCorrectly() 36 | { 37 | 38 | $container = new Container(new Factory()); 39 | $container->root()->addDirectory(new Directory('dir1.1')); 40 | 41 | $this->assertTrue($container->hasNodeAt('/dir1.1')); 42 | $this->assertFalse($container->hasNodeAt('/dir')); 43 | 44 | } 45 | 46 | public function testDirectoryCreation() 47 | { 48 | $container = new Container(new Factory()); 49 | $container->createDir('/dir1'); 50 | 51 | $this->assertInstanceOf('\VirtualFileSystem\Structure\Directory', $container->nodeAt('/dir1')); 52 | 53 | //now recursive 54 | $container = new Container(new Factory()); 55 | $container->createDir('/dir1/dir2', true); 56 | 57 | $this->assertInstanceOf('\VirtualFileSystem\Structure\Directory', $container->nodeAt('/dir1/dir2')); 58 | 59 | //and mode 60 | $container = new Container(new Factory()); 61 | $dir = $container->createDir('/dir1/dir2/dir3', true, 0000); 62 | 63 | $this->assertEquals(0000 | Directory::S_IFTYPE, $dir->mode()); 64 | 65 | } 66 | 67 | public function testMkdirThrowsWhenNoParent() 68 | { 69 | $this->expectException('\VirtualFilesystem\Exception\NotFoundException'); 70 | 71 | $container = new Container(new Factory()); 72 | $container->createDir('/dir1/dir2'); 73 | 74 | } 75 | 76 | public function testFileCreation() 77 | { 78 | $container = new Container(new Factory()); 79 | 80 | $container->createFile('/file'); 81 | 82 | $this->assertInstanceOf('\VirtualFileSystem\Structure\File', $container->nodeAt('/file')); 83 | 84 | //with content 85 | 86 | $container->createFile('/file2', 'someData'); 87 | 88 | $this->assertEquals('someData', $container->fileAt('/file2')->data()); 89 | 90 | } 91 | 92 | public function testFileCreationThrowsWhenNoParent() 93 | { 94 | $this->expectException('\VirtualFilesystem\Exception\NotFoundException'); 95 | 96 | $container = new Container(new Factory()); 97 | 98 | $container->createFile('/dir/file'); 99 | 100 | } 101 | 102 | public function testFileCreationThrowsWhenTryingToOverride() 103 | { 104 | $container = new Container(new Factory()); 105 | 106 | $container->createFile('/file'); 107 | 108 | $this->expectException('\RuntimeException'); 109 | 110 | $container->createFile('/file'); 111 | 112 | } 113 | 114 | public function testMovingFilesWithinParent() 115 | { 116 | $container = new Container(new Factory()); 117 | $container->createFile('/file'); 118 | 119 | $container->move('/file', '/file2'); 120 | 121 | $this->assertTrue($container->hasNodeAt('/file2'), 'File exists at new location.'); 122 | $this->assertFalse($container->hasNodeAt('/file'), 'File does not exist at old location.'); 123 | } 124 | 125 | public function testMovingDirectoriesWithinParent() 126 | { 127 | $container = new Container(new Factory()); 128 | $container->root()->addDirectory($dir = new Directory('dir1')); 129 | $container->root()->addDirectory(new Directory('dir2')); 130 | $dir->addDirectory(new Directory('dir11')); 131 | $dir->addDirectory(new Directory('dir12')); 132 | $dir->addFile(new File('file')); 133 | 134 | $container->move('/dir1', '/dirMoved'); 135 | 136 | $this->assertTrue($container->hasNodeAt('/dir2'), 'Other parent directories not moved'); 137 | $this->assertTrue($container->hasNodeAt('/dirMoved'), 'Directory moved to new location'); 138 | $this->assertFalse($container->hasNodeAt('/dir1'), 'Directory does not exist at old location'); 139 | $this->assertTrue($container->hasNodeAt('/dirMoved/dir11'), 'Directory child of type Dir moved'); 140 | $this->assertTrue($container->hasNodeAt('/dirMoved/file'), 'Directory child of type File moved'); 141 | 142 | } 143 | 144 | public function testMovingToDifferentParent() 145 | { 146 | $container = new Container(new Factory()); 147 | $container->root()->addDirectory($dir = new Directory('dir1')); 148 | $container->root()->addDirectory(new Directory('dir2')); 149 | $dir->addDirectory(new Directory('dir11')); 150 | $dir->addDirectory(new Directory('dir12')); 151 | $dir->addFile(new File('file')); 152 | 153 | $container->move('/dir1', '/dir2/dirMoved'); 154 | 155 | $this->assertTrue($container->hasNodeAt('/dir2'), 'Other parent directories not moved'); 156 | $this->assertTrue($container->hasNodeAt('/dir2/dirMoved'), 'Directory moved to new location'); 157 | $this->assertFalse($container->hasNodeAt('/dir1'), 'Directory does not exist at old location'); 158 | $this->assertTrue($container->hasNodeAt('/dir2/dirMoved/dir11'), 'Directory child of type Dir moved'); 159 | $this->assertTrue($container->hasNodeAt('/dir2/dirMoved/file'), 'Directory child of type File moved'); 160 | } 161 | 162 | public function testMovingFileOntoExistingFileOverridesTarget() 163 | { 164 | $container = new Container(new Factory()); 165 | $container->createFile('/file1', 'file1'); 166 | $container->createFile('/file2', 'file2'); 167 | 168 | $container->move('/file1', '/file2'); 169 | 170 | $this->assertTrue($container->hasNodeAt('/file2')); 171 | $this->assertFalse($container->hasNodeAt('/file1')); 172 | $this->assertEquals('file1', $container->fileAt('/file2')->data()); 173 | } 174 | 175 | public function testMovingDirectoryOntoExistingDirectoryOverridesTarget() 176 | { 177 | $container = new Container(new Factory()); 178 | $container->createDir('/dir1'); 179 | $container->createDir('/dir2'); 180 | 181 | $container->move('/dir1', '/dir2'); 182 | 183 | $this->assertTrue($container->hasNodeAt('/dir2')); 184 | $this->assertFalse($container->hasNodeAt('/dir1')); 185 | } 186 | 187 | public function testMovingNonEmptyDirectoryOntoExistingDirectoryFails() 188 | { 189 | $container = new Container(new Factory()); 190 | $container->createDir('/dir1'); 191 | $container->createDir('/dir2'); 192 | $container->createFile('/dir2/file1', 'file'); 193 | 194 | $this->expectException('\RuntimeException', 'Can\'t override non empty directory.'); 195 | 196 | $container->move('/dir1', '/dir2'); 197 | 198 | } 199 | 200 | public function testMovingDirectoryOntoExistingFileThrows() 201 | { 202 | $container = new Container(new Factory()); 203 | $container->createDir('/dir1'); 204 | $container->createFile('/file2', 'file2'); 205 | 206 | $this->expectException('\RuntimeException', 'Can\'t move.'); 207 | 208 | $container->move('/dir1', '/file2'); 209 | 210 | } 211 | 212 | public function testMovingFileOntoExistingDirectoryThrows() 213 | { 214 | $container = new Container(new Factory()); 215 | $container->createDir('/dir1'); 216 | $container->createFile('/file2', 'file2'); 217 | 218 | $this->expectException('\RuntimeException', 'Can\'t move.'); 219 | 220 | $container->move('/file2', '/dir1'); 221 | 222 | } 223 | 224 | public function testMovingFileOntoInvalidPathWithFileParentThrows() 225 | { 226 | $container = new Container(new Factory()); 227 | $container->createFile('/file1'); 228 | $container->createFile('/file2', 'file2'); 229 | 230 | $this->expectException('VirtualFileSystem\Exception\NotDirectoryException'); 231 | 232 | $container->move('/file1', '/file2/file1'); 233 | 234 | } 235 | 236 | public function testRemoveDeletesNodeFromParent() 237 | { 238 | $container = new Container(new Factory()); 239 | $container->createFile('/file'); 240 | 241 | $container->remove('/file'); 242 | 243 | $this->assertFalse($container->hasNodeAt('/file'), 'File was removed'); 244 | 245 | $container->createDir('/dir'); 246 | 247 | $container->remove('/dir', true); 248 | 249 | $this->assertFalse($container->hasNodeAt('/dir'), 'Directory was removed'); 250 | } 251 | 252 | public function testRemoveThrowsWhenDeletingDirectoriesWithRecursiveFlag() 253 | { 254 | $container = new Container(new Factory()); 255 | $container->createDir('/dir'); 256 | 257 | $this->expectException('\RuntimeException', 'Won\'t non-recursively remove directory'); 258 | 259 | $container->remove('/dir'); 260 | } 261 | 262 | public function testLinkCreation() 263 | { 264 | $container = new Container(new Factory()); 265 | $container->createFile('/file'); 266 | $container->createLink('/link', '/file'); 267 | 268 | $this->assertInstanceOf('\VirtualFileSystem\Structure\Link', $container->nodeAt('/link')); 269 | 270 | } 271 | 272 | public function testLinkCreationThrowsWhenTryingToOverride() 273 | { 274 | $container = new Container(new Factory()); 275 | 276 | $container->createFile('/file'); 277 | $container->createLink('/link', '/file'); 278 | 279 | $this->expectException('\RuntimeException'); 280 | 281 | $container->createLink('/link', '/file'); 282 | 283 | } 284 | 285 | public function testCreatingDirectoryOnPathThrowsWhenParentIsAFile() 286 | { 287 | $container = new Container(new Factory()); 288 | $container->createFile('/file'); 289 | 290 | $this->expectException('VirtualFileSystem\Exception\NotDirectoryException'); 291 | 292 | $container->createDir('/file/dir'); 293 | } 294 | 295 | public function testFileAtThrowsWhenFileOnParentPath() 296 | { 297 | $container = new Container(new Factory()); 298 | $container->createFile('/file'); 299 | 300 | $this->expectException('VirtualFileSystem\Exception\NotFoundException'); 301 | 302 | $container->nodeAt('/file/file2'); 303 | } 304 | 305 | public function testCreateFileThrowsNonDirWhenParentNotDirectory() 306 | { 307 | $container = new Container(new Factory()); 308 | $container->createFile('/file'); 309 | 310 | $this->expectException('VirtualFileSystem\Exception\NotDirectoryException'); 311 | 312 | $container->createFile('/file/file2'); 313 | } 314 | 315 | public function testDirectoryAtThrowsNonDirIfReturnedNotDir() 316 | { 317 | $container = new Container(new Factory()); 318 | $container->createFile('/file'); 319 | 320 | $this->expectException('VirtualFileSystem\Exception\NotDirectoryException'); 321 | 322 | $container->directoryAt('/file'); 323 | } 324 | 325 | public function testDirectoryAtBubblesNotFoundOnBadPath() 326 | { 327 | $container = new Container(new Factory()); 328 | 329 | $this->expectException('VirtualFileSystem\Exception\NotFoundException'); 330 | 331 | $container->directoryAt('/dir'); 332 | } 333 | 334 | public function testDirectoryAtReturnsDirectory() 335 | { 336 | $container = new Container(new Factory()); 337 | $container->createDir('/dir'); 338 | 339 | $this->assertInstanceOf('VirtualFileSystem\Structure\Directory', $container->directoryAt('/dir')); 340 | } 341 | 342 | public function testFileAtThrowsNonFileIfReturnedNotFile() 343 | { 344 | $container = new Container(new Factory()); 345 | $container->createDir('/dir'); 346 | 347 | $this->expectException('VirtualFileSystem\Exception\NotFileException'); 348 | 349 | $container->fileAt('/dir'); 350 | } 351 | 352 | public function testFileAtBubblesNotFoundOnBadPath() 353 | { 354 | $container = new Container(new Factory()); 355 | 356 | $this->expectException('VirtualFileSystem\Exception\NotFoundException'); 357 | 358 | $container->fileAt('/file'); 359 | } 360 | 361 | public function testFileAtReturnsFile() 362 | { 363 | $container = new Container(new Factory()); 364 | $container->createFile('/file'); 365 | 366 | $this->assertInstanceOf('VirtualFileSystem\Structure\File', $container->fileAt('/file')); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/Structure/DirectoryTest.php: -------------------------------------------------------------------------------- 1 | expectException('\InvalidArgumentException'); 10 | 11 | new Directory('/'); 12 | } 13 | 14 | public function testBasename() 15 | { 16 | $root = new Root(); 17 | $root->addDirectory($d1 = new Directory('dir1')); 18 | $root->addDirectory($d2 = new Directory('dir2')); 19 | $d2->addDirectory($d3 = new Directory('dir3')); 20 | 21 | $this->assertEquals('dir1', $d1->basename()); 22 | $this->assertEquals('dir2', $d2->basename()); 23 | $this->assertEquals('dir3', $d3->basename()); 24 | } 25 | 26 | public function testDirnameBuilding() 27 | { 28 | $root = new Root(); 29 | $root->addDirectory($d1 = new Directory('dir1')); 30 | $root->addDirectory($d2 = new Directory('dir2')); 31 | $d2->addDirectory($d3 = new Directory('dir3')); 32 | 33 | $this->assertEquals(null, $root->dirname()); 34 | 35 | $this->assertEquals('/', $d1->dirname()); 36 | $this->assertEquals('/', $d2->dirname()); 37 | $this->assertEquals('/dir2', $d3->dirname()); 38 | 39 | } 40 | 41 | public function testPathBuilding() 42 | { 43 | $root = new Root(); 44 | $root->addDirectory($d1 = new Directory('dir1')); 45 | $root->addDirectory($d2 = new Directory('dir2')); 46 | $d2->addDirectory($d3 = new Directory('dir3')); 47 | 48 | $this->assertEquals('/dir1', $d1->path()); 49 | $this->assertEquals('/dir2', $d2->path()); 50 | $this->assertEquals('/dir2/dir3', $d3->path()); 51 | 52 | } 53 | 54 | public function testChildAtReturnsCorrectNode() 55 | { 56 | $root = new Root(); 57 | $root->addDirectory($d1 = new Directory('dir1')); 58 | $root->addDirectory($d2 = new Directory('dir2')); 59 | $root->addFile($f1 = new File('file1')); 60 | 61 | $this->assertEquals($d1, $root->childAt('dir1')); 62 | $this->assertEquals($d2, $root->childAt('dir2')); 63 | $this->assertEquals($f1, $root->childAt('file1')); 64 | } 65 | 66 | public function testChildAtThrowsNotFoundWhenInvalidElementRequested() 67 | { 68 | $root = new Root(); 69 | $root->addDirectory($d1 = new Directory('dir1')); 70 | 71 | $this->expectException('\VirtualFileSystem\Exception\NotFoundException'); 72 | 73 | $root->childAt('dir2'); 74 | } 75 | 76 | public function testSizeIsReturnAsNumberOfChildren() 77 | { 78 | $root = new Root(); 79 | $root->addDirectory($d1 = new Directory('dir1')); 80 | $root->addDirectory($d1 = new Directory('dir2')); 81 | 82 | $this->assertEquals(2, $root->size()); 83 | } 84 | 85 | public function testThrowsWhenFileNameClashes() 86 | { 87 | 88 | $root = new Root(); 89 | $root->addDirectory(new Directory('dir1')); 90 | 91 | $this->expectException('\VirtualFileSystem\Exception\FileExistsException'); 92 | $root->addDirectory(new Directory('dir1')); 93 | 94 | } 95 | 96 | public function testRemove() 97 | { 98 | $root = new Root(); 99 | $root->addDirectory(new Directory('dir1')); 100 | $root->remove('dir1'); 101 | 102 | $this->expectException('\VirtualFileSystem\Exception\NotFoundException'); 103 | 104 | $root->childAt('dir1'); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/Structure/LinkTest.php: -------------------------------------------------------------------------------- 1 | setData('12345'); 11 | 12 | $link = new Link('link', $node); 13 | 14 | $this->assertEquals($node->size(), $link->size()); 15 | 16 | $dir = new Directory('/d'); 17 | 18 | new Link('link', $dir); 19 | 20 | $this->assertEquals($dir->size(), $dir->size()); 21 | 22 | $dir->addFile($node); 23 | 24 | $this->assertEquals($dir->size(), $dir->size()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/Structure/NodeTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(Node::DEF_MODE | File::S_IFTYPE, $file->mode()); 18 | 19 | $file->chmod(0200); 20 | $this->assertEquals(0200 | File::S_IFTYPE, $file->mode()); 21 | 22 | $file->chmod(0777); 23 | $this->assertEquals(0777 | File::S_IFTYPE, $file->mode()); 24 | 25 | } 26 | 27 | public function testToStringReturnsPath() 28 | { 29 | $dir = new Directory('dir'); 30 | $dir->addFile($file = new File('file')); 31 | 32 | $this->assertEquals($file->path(), $file, '__toString() invoked and returned path'); 33 | 34 | } 35 | 36 | public function testSizeIsReturned() 37 | { 38 | $file = new File('file'); 39 | $file->setData('1234567890'); 40 | 41 | $this->assertEquals(10, $file->size()); 42 | } 43 | 44 | public function testURLConstruction() 45 | { 46 | $root = new Root(); 47 | $root->setScheme('s://'); 48 | 49 | $root->addDirectory($dir = new Directory('dir')); 50 | $dir->addDirectory($dir = new Directory('dir')); 51 | $dir->addFile($file = new File('file')); 52 | 53 | $this->assertEquals('s://dir/dir/file', $file->url()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/Structure/RootTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('/', $root->basename()); 11 | } 12 | 13 | public function testPath() 14 | { 15 | $root = new Root(); 16 | $this->assertEquals('/', $root->path()); 17 | } 18 | 19 | public function testDirname() 20 | { 21 | $root = new Root(); 22 | $this->assertEquals('', $root->dirname()); 23 | } 24 | 25 | public function testThrowsWhenTryingToSetParent() 26 | { 27 | $root = new Root(); 28 | $dir = new Directory('a'); 29 | 30 | $this->expectException('\LogicException'); 31 | 32 | $dir->addDirectory($root); 33 | } 34 | 35 | public function testRootPathReturnsWithScheme() 36 | { 37 | $root = new Root(); 38 | 39 | $root->setScheme('scheme://'); 40 | $this->assertEquals('/', $root, 'No scheme when one is set'); 41 | 42 | } 43 | 44 | public function testURLIsReturned() 45 | { 46 | 47 | $root = new Root(); 48 | 49 | $root->setScheme('scheme://'); 50 | $this->assertEquals('scheme://', $root->url()); 51 | 52 | $root->setScheme('scheme'); 53 | $this->assertEquals('scheme://', $root->url(), 'Scheme reformatted'); 54 | 55 | } 56 | 57 | public function testURLThrowsWhenNoScheme() 58 | { 59 | $root = new Root(); 60 | 61 | $this->expectException('RuntimeException'); 62 | 63 | $root->url(); 64 | } 65 | 66 | public function testRootPathReturnsWithoutScheme() 67 | { 68 | $root = new Root(); 69 | 70 | $this->assertEquals('/', $root); 71 | 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/VirtualFilesystemTest.php: -------------------------------------------------------------------------------- 1 | assertInstanceOf('\VirtualFilesystem\Container', $fs->container()); 12 | $this->assertInstanceOf('\VirtualFilesystem\Structure\Root', $fs->root()); 13 | } 14 | 15 | public function testFactoryIsSetDuringConstruction() 16 | { 17 | $fs = new FileSystem(); 18 | 19 | $this->assertInstanceOf('\VirtualFilesystem\Factory', $fs->container()->factory()); 20 | } 21 | 22 | public function testWrapperIsRegisteredDuringObjectLifetime() 23 | { 24 | $fs = new FileSystem(); 25 | $scheme = $fs->scheme(); 26 | 27 | $this->assertTrue(in_array($scheme, stream_get_wrappers()), 'Wrapper registered in __construct()'); 28 | 29 | unset($fs); //provoking __destruct 30 | $this->assertFalse(in_array($scheme, stream_get_wrappers()), 'Wrapper unregistered in __destruct()'); 31 | } 32 | 33 | public function testFilesystemFactoryAddedToDefaultContextDuringObjectLifetime() 34 | { 35 | $fs = new FileSystem(); 36 | $scheme = $fs->scheme(); 37 | 38 | $options = stream_context_get_options(stream_context_get_default()); 39 | 40 | $this->assertArrayHasKey($scheme, $options, 'Wrraper key registered in context'); 41 | $this->assertArrayHasKey('Container', $options[$scheme], 'Container registered in context key'); 42 | 43 | //can't find a way to unset default context yet 44 | //unset($fs); //provoking __destruct 45 | //$options = stream_context_get_options(stream_context_get_default()); 46 | //$this->assertArrayNotHasKey('anotherVFSwrapper', $options, 'Wrraper key not registered in context'); 47 | 48 | } 49 | 50 | public function testDefaultContextOptionsAreExtended() 51 | { 52 | stream_context_set_default(array('someContext' => array('a' => 'b'))); 53 | 54 | $fs = new FileSystem(); 55 | $scheme = $fs->scheme(); 56 | 57 | $options = stream_context_get_options(stream_context_get_default()); 58 | 59 | $this->assertArrayHasKey($scheme, $options, 'FS Context option present'); 60 | $this->assertArrayHasKey('someContext', $options, 'Previously existing context option present'); 61 | 62 | } 63 | 64 | 65 | 66 | public function testCreateDirectoryCreatesDirectories() 67 | { 68 | $fs = new FileSystem(); 69 | 70 | $directory = $fs->createDirectory('/dir/dir', true); 71 | 72 | $this->assertInstanceOf('\VirtualFileSystem\Structure\Directory', $directory); 73 | $this->assertEquals('/dir/dir', $directory->path()); 74 | } 75 | 76 | public function testCreateFileCreatesFile() 77 | { 78 | $fs = new FileSystem(); 79 | 80 | $file = $fs->createFile('/file', 'data'); 81 | 82 | $this->assertInstanceOf('\VirtualFileSystem\Structure\File', $file); 83 | $this->assertEquals('/file', $file->path()); 84 | $this->assertEquals('data', $file->data()); 85 | } 86 | 87 | public function testCreateStuctureMirrorsStructure() 88 | { 89 | $fs = new FileSystem(); 90 | $fs->createStructure(['file' => 'omg', 'file2' => 'gmo']); 91 | 92 | $file = $fs->container()->fileAt('/file'); 93 | $file2 = $fs->container()->fileAt('/file2'); 94 | 95 | $this->assertEquals('omg', $file->data()); 96 | $this->assertEquals('gmo', $file2->data()); 97 | 98 | $fs->createStructure(['dir' => [], 'dir2' => []]); 99 | 100 | $dir = $fs->container()->nodeAt('/dir'); 101 | $dir2 = $fs->container()->nodeAt('/dir2'); 102 | 103 | $this->assertInstanceOf('VirtualFileSystem\Structure\Directory', $dir); 104 | $this->assertInstanceOf('VirtualFileSystem\Structure\Directory', $dir2); 105 | 106 | $fs->createStructure([ 107 | 'dir3' => [ 108 | 'file' => 'nested', 109 | 'dir4' => [ 110 | 'dir5' => [ 111 | 'file5' => 'nestednested' 112 | ] 113 | ] 114 | ] 115 | ]); 116 | 117 | $dir = $fs->container()->directoryAt('/dir3'); 118 | 119 | $this->assertInstanceOf('VirtualFileSystem\Structure\Directory', $dir); 120 | 121 | $file = $fs->container()->fileAt('/dir3/file'); 122 | 123 | $this->assertEquals('nested', $file->data()); 124 | 125 | $dir = $fs->container()->directoryAt('/dir3/dir4/dir5'); 126 | 127 | $this->assertInstanceOf('VirtualFileSystem\Structure\Directory', $dir); 128 | 129 | $file = $fs->container()->fileAt('/dir3/dir4/dir5/file5'); 130 | 131 | $this->assertEquals('nestednested', $file->data()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/Wrapper/FileHandlerTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(0, $pointer->position()); 14 | } 15 | 16 | public function testPointerPositionSetterGetter() 17 | { 18 | $pointer = new FileHandler(); 19 | 20 | $pointer->position(15); 21 | $this->assertEquals(15, $pointer->position()); 22 | } 23 | 24 | public function testPointerFindsEndOfFile() 25 | { 26 | $file = new File('/file'); 27 | $file->setData('1234567'); 28 | 29 | $pointer = new FileHandler(); 30 | $pointer->setFile($file); 31 | 32 | $pointer->seekToEnd(); 33 | 34 | $this->assertEquals(7, $pointer->position()); 35 | } 36 | 37 | public function testDataIsReadInChunks() 38 | { 39 | $file = new File('/file'); 40 | $file->setData('1234567'); 41 | 42 | $pointer = new FileHandler(); 43 | $pointer->setFile($file); 44 | 45 | $this->assertEquals('12', $pointer->read(2)); 46 | $this->assertEquals(2, $pointer->position()); 47 | $this->assertEquals('345', $pointer->read(3)); 48 | $this->assertEquals(5, $pointer->position()); 49 | $this->assertEquals('67', $pointer->read(10)); 50 | $this->assertEquals(7, $pointer->position()); 51 | } 52 | 53 | public function testCheckingEOF() 54 | { 55 | $file = new File('/file'); 56 | 57 | $handler = new FileHandler(); 58 | $handler->setFile($file); 59 | 60 | $this->assertTrue($handler->atEof()); 61 | 62 | $file->setData('1'); 63 | 64 | $this->assertFalse($handler->atEof()); 65 | 66 | $handler->position(1); 67 | $this->assertTrue($handler->atEof()); 68 | 69 | $handler->position(2); 70 | $this->assertTrue($handler->atEof()); 71 | 72 | } 73 | 74 | public function testTruncateRemovesDataAndResetsPointer() 75 | { 76 | 77 | $file = new File('/file'); 78 | $file->setData('data'); 79 | 80 | $handler = new FileHandler(); 81 | $handler->setFile($file); 82 | 83 | $handler->truncate(); 84 | 85 | $this->assertEmpty($file->data()); 86 | $this->assertEquals(0, $handler->position()); 87 | 88 | //truncate to size 89 | $file->setData('data--'); 90 | 91 | $handler->truncate(4); 92 | $this->assertEquals(0, $handler->position()); 93 | $this->assertEquals('data', $file->data()); 94 | 95 | } 96 | 97 | public function testOffsetPositionMovesPointerCorrectly() 98 | { 99 | 100 | $file = new File('/file'); 101 | $file->setData('data'); 102 | 103 | $handler = new FileHandler(); 104 | $handler->setFile($file); 105 | 106 | $handler->offsetPosition(2); 107 | $this->assertEquals(2, $handler->position()); 108 | 109 | $handler->offsetPosition(2); 110 | $this->assertEquals(4, $handler->position()); 111 | 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/Wrapper/PermissionHelperTest.php: -------------------------------------------------------------------------------- 1 | uid = function_exists('posix_getuid') ? posix_getuid() : 0; 15 | $this->gid = function_exists('posix_getgid') ? posix_getgid() : 0; 16 | } 17 | 18 | public function testUserPermissionsAreCalculatedCorrectly() 19 | { 20 | 21 | $file = new File('file'); 22 | $file->chown($this->uid); 23 | 24 | $ph = new PermissionHelper(); 25 | $ph->setNode($file); 26 | 27 | $file->chmod(0000); 28 | $this->assertFalse($ph->userCanRead(), 'User can\'t read with 0000'); 29 | $this->assertFalse($ph->userCanWrite(), 'User can\'t write with 0000'); 30 | 31 | $file->chmod(0100); 32 | $this->assertFalse($ph->userCanRead(), 'User can\'t read with 0100'); 33 | $this->assertFalse($ph->userCanWrite(), 'User can\'t write with 0100'); 34 | 35 | $file->chmod(0200); 36 | $this->assertFalse($ph->userCanRead(), 'User can\'t read with 0200'); 37 | $this->assertTrue($ph->userCanWrite(), 'User can write with 0200'); 38 | 39 | $file->chmod(0400); 40 | $this->assertTrue($ph->userCanRead(), 'User can read with 0400'); 41 | $this->assertFalse($ph->userCanWrite(), 'User can\'t write with 0400'); 42 | 43 | $file->chmod(0500); 44 | $this->assertTrue($ph->userCanRead(), 'User can read with 0500'); 45 | $this->assertFalse($ph->userCanWrite(), 'User can\'t write with 0500'); 46 | 47 | $file->chmod(0600); 48 | $this->assertTrue($ph->userCanRead(), 'User can read with 0600'); 49 | $this->assertTrue($ph->userCanWrite(), 'User can read with 0600'); 50 | 51 | $file->chown(1); 52 | $file->chmod(0666); 53 | 54 | $this->assertFalse($ph->userCanRead(), 'User can\'t read without ownership'); 55 | $this->assertFalse($ph->userCanWrite(), 'User can\'t without ownership'); 56 | } 57 | 58 | public function testGroupPermissionsAreCalculatedCorrectly() 59 | { 60 | $file = new File('file'); 61 | $file->chgrp($this->gid); 62 | 63 | $ph = new PermissionHelper(); 64 | $ph->setNode($file); 65 | 66 | $file->chmod(0000); 67 | $this->assertFalse($ph->groupCanRead(), 'group can\'t read with 0000'); 68 | $this->assertFalse($ph->groupCanWrite(), 'group can\'t write with 0000'); 69 | 70 | $file->chmod(0010); 71 | $this->assertFalse($ph->groupCanRead(), 'group can\'t read with 0010'); 72 | $this->assertFalse($ph->groupCanWrite(), 'group can\'t write with 0010'); 73 | 74 | $file->chmod(0020); 75 | $this->assertFalse($ph->groupCanRead(), 'group can\'t read with 0020'); 76 | $this->assertTrue($ph->groupCanWrite(), 'group can write with 0020'); 77 | 78 | $file->chmod(0040); 79 | $this->assertTrue($ph->groupCanRead(), 'group can read with 0040'); 80 | $this->assertFalse($ph->groupCanWrite(), 'group can\'t write with 0040'); 81 | 82 | $file->chmod(0050); 83 | $this->assertTrue($ph->groupCanRead(), 'group can read with 0050'); 84 | $this->assertFalse($ph->groupCanWrite(), 'group can\'t write with 0050'); 85 | 86 | $file->chmod(0060); 87 | $this->assertTrue($ph->groupCanRead(), 'group can read with 0060'); 88 | $this->assertTrue($ph->groupCanWrite(), 'group can read with 0060'); 89 | 90 | $file->chgrp(0); 91 | $file->chmod(0666); 92 | 93 | $this->assertFalse($ph->groupCanRead(), 'group can\'t read without ownership'); 94 | $this->assertFalse($ph->groupCanWrite(), 'group can\'t without ownership'); 95 | } 96 | 97 | public function testWorldPermissionsAreCalculatedCorrectly() 98 | { 99 | $file = new File('file'); 100 | 101 | $ph = new PermissionHelper(); 102 | $ph->setNode($file); 103 | 104 | $file->chmod(0000); 105 | $this->assertFalse($ph->worldCanRead(), 'world can\'t read with 0000'); 106 | $this->assertFalse($ph->worldCanWrite(), 'world can\'t write with 0000'); 107 | 108 | $file->chmod(0001); 109 | $this->assertFalse($ph->worldCanRead(), 'world can\'t read with 0001'); 110 | $this->assertFalse($ph->worldCanWrite(), 'world can\'t write with 0001'); 111 | 112 | $file->chmod(0002); 113 | $this->assertFalse($ph->worldCanRead(), 'world can\'t read with 0002'); 114 | $this->assertTrue($ph->worldCanWrite(), 'world can write with 0002'); 115 | 116 | $file->chmod(0004); 117 | $this->assertTrue($ph->worldCanRead(), 'world can read with 0004'); 118 | $this->assertFalse($ph->worldCanWrite(), 'world can\'t write with 0004'); 119 | 120 | $file->chmod(0005); 121 | $this->assertTrue($ph->worldCanRead(), 'world can read with 0005'); 122 | $this->assertFalse($ph->worldCanWrite(), 'world can\'t write with 0005'); 123 | 124 | $file->chmod(0006); 125 | $this->assertTrue($ph->worldCanRead(), 'world can read with 0006'); 126 | $this->assertTrue($ph->worldCanWrite(), 'world can read with 0006'); 127 | 128 | } 129 | 130 | public function testIsReadable() 131 | { 132 | $file = new File('file'); 133 | 134 | $ph = new PermissionHelper(); 135 | $ph->setNode($file); 136 | 137 | $file->chmod(0000); 138 | $file->chown(0); 139 | $file->chgrp(0); 140 | $this->assertFalse($ph->isReadable(), 'File is not readable root:root 0000'); 141 | 142 | $file->chmod(0400); 143 | $file->chown(0); 144 | $file->chgrp(0); 145 | $this->assertFalse($ph->isReadable(), 'File is not readable root:root 0400'); 146 | 147 | $file->chmod(0040); 148 | $file->chown(0); 149 | $file->chgrp(0); 150 | $this->assertFalse($ph->isReadable(), 'File is not readable root:root 0040'); 151 | 152 | $file->chmod(0004); 153 | $file->chown(0); 154 | $file->chgrp(0); 155 | $this->assertTrue($ph->isReadable(), 'File is readable root:root 0004'); 156 | 157 | $file->chmod(0000); 158 | $file->chown($this->uid); 159 | $file->chgrp(0); 160 | $this->assertFalse($ph->isReadable(), 'File is not readable user:root 0000'); 161 | 162 | $file->chmod(0400); 163 | $file->chown($this->uid); 164 | $file->chgrp(0); 165 | $this->assertTrue($ph->isReadable(), 'File is readable user:root 0400'); 166 | 167 | $file->chmod(0040); 168 | $file->chown($this->uid); 169 | $file->chgrp(0); 170 | $this->assertFalse($ph->isReadable(), 'File is not readable user:root 0040'); 171 | 172 | $file->chmod(0004); 173 | $file->chown($this->uid); 174 | $file->chgrp(0); 175 | $this->assertTrue($ph->isReadable(), 'File is readable user:root 0004'); 176 | 177 | $file->chmod(0000); 178 | $file->chown(0); 179 | $file->chgrp($this->gid); 180 | $this->assertFalse($ph->isReadable(), 'File is not readable root:user 0000'); 181 | 182 | $file->chmod(0040); 183 | $file->chown(0); 184 | $file->chgrp($this->gid); 185 | $this->assertTrue($ph->isReadable(), 'File is readable root:user 0040'); 186 | 187 | $file->chmod(0400); 188 | $file->chown(0); 189 | $file->chgrp($this->gid); 190 | $this->assertFalse($ph->isReadable(), 'File is not readable root:user 0400'); 191 | 192 | $file->chmod(0004); 193 | $file->chown(0); 194 | $file->chgrp($this->gid); 195 | $this->assertTrue($ph->isReadable(), 'File is readable root:user 0004'); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /tests/VirtualFileSystem/WrapperTest.php: -------------------------------------------------------------------------------- 1 | uid = function_exists('posix_getuid') ? posix_getuid() : 0; 21 | $this->gid = function_exists('posix_getgid') ? posix_getgid() : 0; 22 | 23 | $na = []; 24 | @$na['n/a']; //putting error in known state 25 | } 26 | 27 | public function testSchemeStripping() 28 | { 29 | $c = new Wrapper(); 30 | 31 | $this->assertEquals('/1/2/3/4', $c->stripScheme('test://1/2/3/4')); 32 | $this->assertEquals('/', $c->stripScheme('test://')); 33 | $this->assertEquals('/', $c->stripScheme('test:///')); 34 | $this->assertEquals('/dir', $c->stripScheme('test:///dir')); 35 | 36 | } 37 | 38 | public function testContainerIsReturnedFromContext() 39 | { 40 | stream_context_set_default(array('contextContainerTest' => array('Container' => 'test'))); 41 | 42 | $c = new Wrapper(); 43 | 44 | $this->assertEquals('test', $c->getContainerFromContext('contextContainerTest://file')); 45 | $this->assertEquals('test', $c->getContainerFromContext('contextContainerTest://')); 46 | $this->assertEquals('test', $c->getContainerFromContext('contextContainerTest:///file')); 47 | 48 | } 49 | 50 | public function testFileExists() 51 | { 52 | $fs = new FileSystem(); 53 | $fs->root()->addDirectory($d = new Directory('dir')); 54 | $d->addFile(new File('file')); 55 | $d->addDirectory(new Directory('dir')); 56 | 57 | $this->assertTrue(file_exists($fs->path('/dir/file'))); 58 | $this->assertTrue(file_exists($fs->path('/dir'))); 59 | $this->assertFalse(file_exists($fs->path('/dir/fileNotExist'))); 60 | 61 | } 62 | 63 | public function testIsDir() 64 | { 65 | $fs = new FileSystem(); 66 | $fs->root()->addDirectory($d = new Directory('dir')); 67 | $d->addFile(new File('file')); 68 | $d->addDirectory(new Directory('dir')); 69 | 70 | $this->assertFalse(is_dir($fs->path('/dir/file'))); 71 | $this->assertTrue(is_dir($fs->path('/dir'))); 72 | $this->assertTrue(is_dir($fs->path('/dir/dir'))); 73 | $this->assertTrue(is_dir($fs->path('/'))); 74 | 75 | } 76 | 77 | public function testIsLink() 78 | { 79 | $fs = new FileSystem(); 80 | $fs->root()->addDirectory($d = new Directory('dir')); 81 | $d->addLink(new Link('link', $d)); 82 | 83 | $this->assertTrue(is_link($fs->path('/dir/link'))); 84 | } 85 | 86 | public function testIsFile() 87 | { 88 | $fs = new FileSystem(); 89 | $fs->root()->addDirectory($d = new Directory('dir')); 90 | $d->addFile(new File('file')); 91 | $fs->root()->addFile(new File('file2')); 92 | $d->addDirectory(new Directory('dir')); 93 | 94 | $this->assertTrue(is_file($fs->path('/dir/file'))); 95 | $this->assertFalse(is_file($fs->path('/dir'))); 96 | $this->assertFalse(is_file($fs->path('/dir/dir'))); 97 | $this->assertFalse(is_file($fs->path('/'))); 98 | $this->assertTrue(is_file($fs->path('/file2'))); 99 | 100 | } 101 | 102 | public function testChmod() 103 | { 104 | $fs = new FileSystem(); 105 | $path = $fs->path('/'); 106 | 107 | chmod($path, 0777); 108 | $this->assertEquals(0777 | Directory::S_IFTYPE, $fs->root()->mode()); 109 | 110 | $fs->root()->chmod(0755); 111 | $this->assertEquals(0755 | Directory::S_IFTYPE, fileperms($path)); 112 | 113 | //accessing non existent file should return false 114 | $this->assertFalse(chmod($fs->path('/nonExistingFile'), 0777)); 115 | 116 | } 117 | 118 | public function testChownByName() 119 | { 120 | if ($this->uid == 0) { 121 | $this->markTestSkipped( 122 | 'No point testing if user is already root. '. 123 | 'Php unit shouldn\'t be run as root user. (Unless you are a windows user!)' 124 | ); 125 | } 126 | 127 | $fs = new FileSystem(); 128 | $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root 129 | 130 | chown($fs->path('/'), 'root'); 131 | $this->assertEquals('root', posix_getpwuid(fileowner($fs->path('/')))['name']); 132 | } 133 | 134 | public function testChownById() 135 | { 136 | if ($this->uid == 0) { 137 | $this->markTestSkipped( 138 | 'No point testing if user is already root. Php unit shouldn\'t be run as root user.' 139 | ); 140 | } 141 | 142 | $fs = new FileSystem(); 143 | $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root 144 | 145 | chown($fs->path('/'), 0); 146 | 147 | $this->assertEquals(0, fileowner($fs->path('/'))); 148 | 149 | } 150 | 151 | public function testChgrpByName() 152 | { 153 | if ($this->uid == 0) { 154 | $this->markTestSkipped( 155 | 'No point testing if group is already root. '. 156 | 'Php unit shouldn\'t be run as root group. (Unless you are on Windows - then we skip)' 157 | ); 158 | } 159 | 160 | $fs = new FileSystem(); 161 | $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root 162 | 163 | //lets workout available group 164 | //this is needed to find string name of group root belongs to 165 | $group = posix_getgrgid(posix_getpwuid(0)['gid'])['name']; 166 | 167 | chgrp($fs->path('/'), $group); 168 | 169 | $this->assertEquals($group, posix_getgrgid(filegroup($fs->path('/')))['name']); 170 | } 171 | 172 | public function testChgrpById() 173 | { 174 | if ($this->gid == 0) { 175 | $this->markTestSkipped( 176 | 'No point testing if group is already root. '. 177 | 'Php unit shouldn\'t be run as root group. (Unless you are on Windows - then we skip)' 178 | ); 179 | } 180 | 181 | $fs = new FileSystem(); 182 | $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root 183 | 184 | //lets workout available group 185 | $group = posix_getpwuid(0)['gid']; 186 | 187 | chgrp($fs->path('/'), $group); 188 | 189 | $this->assertEquals($group, filegroup($fs->path('/'))); 190 | } 191 | 192 | public function testMkdir() 193 | { 194 | $fs = new FileSystem(); 195 | 196 | mkdir($fs->path('/dir')); 197 | 198 | $this->assertTrue(file_exists($fs->path('/dir'))); 199 | $this->assertTrue(is_dir($fs->path('/dir'))); 200 | 201 | mkdir($fs->path('/dir2'), 0000, false); 202 | 203 | $dir = $fs->container()->nodeAt('/dir2'); 204 | 205 | $this->assertEquals(0000 | Directory::S_IFTYPE, $dir->mode()); 206 | 207 | } 208 | 209 | public function testMkdirCatchesClashes() 210 | { 211 | $fs = new FileSystem(); 212 | 213 | mkdir($fs->path('/dir')); 214 | @mkdir($fs->path('/dir')); 215 | 216 | $error = error_get_last(); 217 | 218 | $this->assertEquals($error['message'], 'dir already exists'); 219 | } 220 | 221 | public function testMkdirRecursive() 222 | { 223 | $fs = new FileSystem(); 224 | 225 | mkdir($fs->path('/dir/dir2'), 0777, true); 226 | 227 | $this->assertTrue(file_exists($fs->path('/dir/dir2'))); 228 | $this->assertTrue(is_dir($fs->path('/dir/dir2'))); 229 | 230 | @mkdir($fs->path('/dir/a/b'), 0777, false); 231 | 232 | $error = error_get_last(); 233 | 234 | $this->assertStringMatchesFormat('mkdir: %s: No such file or directory', $error['message']); 235 | 236 | } 237 | 238 | public function testStreamWriting() 239 | { 240 | $fs = new FileSystem(); 241 | 242 | file_put_contents($fs->path('/file'), 'data'); 243 | 244 | $this->assertEquals('data', $fs->container()->fileAt('/file')->data()); 245 | 246 | //long strings 247 | file_put_contents($fs->path('/file2'), str_repeat('data ', 5000)); 248 | 249 | $this->assertEquals(str_repeat('data ', 5000), $fs->container()->fileAt('/file2')->data()); 250 | 251 | //truncating 252 | file_put_contents($fs->path('/file'), 'data2'); 253 | 254 | $this->assertEquals('data2', $fs->container()->fileAt('/file')->data()); 255 | 256 | //appending 257 | file_put_contents($fs->path('/file'), 'data3', FILE_APPEND); 258 | 259 | $this->assertEquals('data2data3', $fs->container()->fileAt('/file')->data()); 260 | 261 | $handle = fopen($fs->path('/file2'), 'w'); 262 | 263 | fwrite($handle, 'data'); 264 | $this->assertEquals('data', $fs->container()->fileAt('/file2')->data()); 265 | 266 | fwrite($handle, '2'); 267 | $this->assertEquals('data2', $fs->container()->fileAt('/file2')->data(), 'Pointer advanced'); 268 | 269 | fwrite($handle, 'data', 1); 270 | $this->assertEquals('data2d', $fs->container()->fileAt('/file2')->data(), 'Written with limited length'); 271 | 272 | } 273 | 274 | public function testStreamReading() 275 | { 276 | $fs = new FileSystem(); 277 | $fs->container()->createFile('/file', 'test data'); 278 | 279 | $this->assertEquals('test data', file_get_contents($fs->path('/file'))); 280 | 281 | //long string 282 | $fs->container()->createFile('/file2', str_repeat('test data', 5000)); 283 | $this->assertEquals(str_repeat('test data', 5000), file_get_contents($fs->path('/file2'))); 284 | 285 | $fs->container()->createDir('/dir'); 286 | 287 | $this->assertEmpty(file_get_contents($fs->path('/dir'))); 288 | 289 | } 290 | 291 | public function testStreamFlushing() 292 | { 293 | $fs = new FileSystem(); 294 | 295 | $handle = fopen($fs->path('/file'), 'w'); 296 | 297 | $this->assertTrue(fflush($handle)); 298 | } 299 | 300 | public function testOpeningForReadingOnNonExistingFails() 301 | { 302 | $fs = new FileSystem(); 303 | 304 | $this->assertFalse(@fopen($fs->path('/nonExistingFile'), 'r')); 305 | 306 | if(is_hhvm()) return; 307 | 308 | $error = error_get_last(); 309 | 310 | $this->assertStringMatchesFormat('fopen(%s://nonExistingFile): failed to open stream: %s', $error['message']); 311 | } 312 | 313 | public function testOpeningForWritingCorrectlyOpensAndTruncatesFile() 314 | { 315 | $fs = new FileSystem(); 316 | 317 | $handle = fopen($fs->path('/nonExistingFile'), 'w'); 318 | 319 | $this->assertTrue(is_resource($handle)); 320 | 321 | $file = $fs->container()->createFile('/file', 'data'); 322 | 323 | $handle = fopen($fs->path('/file'), 'w'); 324 | 325 | $this->assertTrue(is_resource($handle)); 326 | $this->assertEmpty($file->data()); 327 | } 328 | 329 | public function testOpeningForAppendingDoesNotTruncateFile() 330 | { 331 | 332 | $fs = new FileSystem(); 333 | $file = $fs->container()->createFile('/file', 'data'); 334 | 335 | $handle = fopen($fs->path('/file'), 'a'); 336 | 337 | $this->assertTrue(is_resource($handle)); 338 | $this->assertEquals('data', $file->data()); 339 | 340 | } 341 | 342 | public function testCreatingFileWhileOpeningFailsCorrectly() 343 | { 344 | 345 | $fs = new FileSystem(); 346 | 347 | $this->assertFalse(@fopen($fs->path('/dir/file'), 'w')); 348 | 349 | if(is_hhvm()) return; 350 | 351 | $error = error_get_last(); 352 | 353 | $this->assertStringMatchesFormat('fopen(%s://dir/file): failed to open stream: %s', $error['message']); 354 | 355 | } 356 | 357 | public function testFileGetContentsOffsetsAndLimitsCorrectly() 358 | { 359 | 360 | $fs = new FileSystem(); 361 | $fs->container()->createFile('/file', '--data--'); 362 | 363 | $this->assertEquals('data', file_get_contents($fs->path('/file'), false, null, 2, 4)); 364 | 365 | } 366 | 367 | public function testFileSeeking() 368 | { 369 | $fs = new FileSystem(); 370 | $fs->container()->createFile('/file', 'data'); 371 | 372 | $handle = fopen($fs->path('/file'), 'r'); 373 | 374 | fseek($handle, 2); 375 | $this->assertEquals(2, ftell($handle)); 376 | 377 | fseek($handle, 1, SEEK_CUR); 378 | $this->assertEquals(3, ftell($handle)); 379 | 380 | fseek($handle, 6, SEEK_END); 381 | $this->assertEquals(10, ftell($handle), 'End of file + 6 is 10'); 382 | } 383 | 384 | public function testFileTruncating() 385 | { 386 | $fs = new FileSystem(); 387 | $file = $fs->container()->createFile('/file', 'data--'); 388 | 389 | //has to opened for append otherwise file is automatically truncated by 'w' opening mode 390 | $handle = fopen($fs->path('/file'), 'a'); 391 | 392 | ftruncate($handle, 4); 393 | 394 | $this->assertEquals('data', $file->data()); 395 | 396 | } 397 | 398 | public function testOpeningModesAreHandledCorrectly() 399 | { 400 | 401 | $fs = new FileSystem(); 402 | $file = $fs->container()->createFile('/file', 'data'); 403 | 404 | $handle = fopen($fs->path('/file'), 'r'); 405 | $this->assertEquals('data', fread($handle, 4), 'Contents can be read in read mode'); 406 | $this->assertEquals(0, fwrite($handle, '--'), '0 bytes should be written in readonly mode'); 407 | 408 | $handle = fopen($fs->path('/file'), 'r+'); 409 | $this->assertEquals('data', fread($handle, 4), 'Contents can be read in extended read mode'); 410 | $this->assertEquals(2, fwrite($handle, '--'), '2 bytes should be written in extended readonly mode'); 411 | 412 | $handle = fopen($fs->path('/file'), 'w'); 413 | $this->assertEquals(4, fwrite($handle, 'data'), '4 bytes written in writeonly mode'); 414 | fseek($handle, 0); 415 | $this->assertEmpty(fread($handle, 4), 'No bytes read in write only mode'); 416 | 417 | $handle = fopen($fs->path('/file'), 'w+'); 418 | $this->assertEquals(4, fwrite($handle, 'data'), '4 bytes written in extended writeonly mode'); 419 | fseek($handle, 0); 420 | $this->assertEquals('data', fread($handle, 4), 'Bytes read in extended write only mode'); 421 | 422 | $file->setData('data'); 423 | 424 | $handle = fopen($fs->path('/file'), 'a'); 425 | $this->assertEquals(4, fwrite($handle, 'data'), '4 bytes written in append mode'); 426 | fseek($handle, 0); 427 | $this->assertEmpty(fread($handle, 4), 'No bytes read in append mode'); 428 | 429 | $handle = fopen($fs->path('/file'), 'a+'); 430 | $this->assertEquals(4, fwrite($handle, 'data'), '4 bytes written in extended append mode'); 431 | fseek($handle, 0); 432 | $this->assertEquals('datadata', fread($handle, 8), 'Bytes read in extended append mode'); 433 | 434 | } 435 | 436 | public function testFileTimesAreModifiedCorerectly() 437 | { 438 | 439 | $fs = new FileSystem(); 440 | $file = $fs->container()->createFile('/file', 'data'); 441 | 442 | $stat = stat($fs->path('/file')); 443 | 444 | $this->assertNotEquals(0, $stat['atime']); 445 | $this->assertNotEquals(0, $stat['mtime']); 446 | $this->assertNotEquals(0, $stat['ctime']); 447 | 448 | $file->setAccessTime(10); 449 | $file->setModificationTime(10); 450 | $file->setChangeTime(10); 451 | 452 | file_get_contents($fs->path('/file')); 453 | $stat = stat($fs->path('/file')); 454 | 455 | $this->assertNotEquals(10, $stat['atime'], 'Access time has changed after read'); 456 | $this->assertEquals(10, $stat['mtime'], 'Modification time has not changed after read'); 457 | $this->assertEquals(10, $stat['ctime'], 'inode change time has not changed after read'); 458 | 459 | $file->setAccessTime(10); 460 | $file->setModificationTime(10); 461 | $file->setChangeTime(10); 462 | 463 | file_put_contents($fs->path('/file'), 'data'); 464 | $stat = stat($fs->path('/file')); 465 | 466 | $this->assertEquals(10, $stat['atime'], 'Access time has not changed after write'); 467 | $this->assertNotEquals(10, $stat['mtime'], 'Modification time has changed after write'); 468 | $this->assertNotEquals(10, $stat['ctime'], 'inode change time has changed after write'); 469 | 470 | $file->setAccessTime(10); 471 | $file->setModificationTime(10); 472 | $file->setChangeTime(10); 473 | 474 | chmod($fs->path('/file'), 0777); 475 | $stat = stat($fs->path('/file')); 476 | 477 | $this->assertEquals(10, $stat['atime'], 'Access time has not changed after inode change'); 478 | $this->assertEquals(10, $stat['mtime'], 'Modification time has not changed after inode change'); 479 | $this->assertNotEquals(10, $stat['ctime'], 'inode change time has changed after inode change'); 480 | 481 | $file->setAccessTime(10); 482 | $file->setModificationTime(10); 483 | $file->setChangeTime(10); 484 | 485 | clearstatcache(); 486 | 487 | fopen($fs->path('/file'), 'r'); 488 | $stat = stat($fs->path('/file')); 489 | 490 | $this->assertEquals(10, $stat['atime'], 'Access time has not changed after opening for reading'); 491 | $this->assertEquals(10, $stat['mtime'], 'Modification time has not changed after opening for reading'); 492 | $this->assertEquals(10, $stat['ctime'], 'inode change time has not changed after opening for reading'); 493 | 494 | $file->setAccessTime(20); 495 | $file->setModificationTime(20); 496 | $file->setChangeTime(20); 497 | 498 | fopen($fs->path('/file'), 'w'); 499 | $stat = stat($fs->path('/file')); 500 | 501 | $this->assertEquals(20, $stat['atime'], 'Access time has not changed after opening for writing'); 502 | $this->assertNotEquals(20, $stat['mtime'], 'Modification time has changed after opnening for writing'); 503 | $this->assertNotEquals(20, $stat['ctime'], 'inode change time has changed after opnening for writing'); 504 | 505 | } 506 | 507 | public function testTouchFileCreation() 508 | { 509 | $fs = new FileSystem(); 510 | 511 | touch($fs->path('/file2')); 512 | 513 | $this->assertTrue(file_exists($fs->path('/file2'))); 514 | 515 | @touch($fs->path('/dir/file')); 516 | 517 | $error = error_get_last(); 518 | 519 | $this->assertStringMatchesFormat( 520 | 'touch: %s: No such file or directory.', 521 | $error['message'], 522 | 'Fails when no parent' 523 | ); 524 | 525 | $file = $fs->container()->nodeAt('/file2'); 526 | 527 | $file->setAccessTime(20); 528 | $file->setModificationTime(20); 529 | $file->setChangeTime(20); 530 | 531 | touch($fs->path('/file2')); 532 | $stat = stat($fs->path('/file2')); 533 | 534 | $this->assertNotEquals(20, $stat['atime'], 'Access time has changed after touch'); 535 | $this->assertNotEquals(20, $stat['mtime'], 'Modification time has changed after touch'); 536 | $this->assertNotEquals(20, $stat['ctime'], 'inode change time has changed after touch'); 537 | 538 | } 539 | 540 | public function testTouchUpdatesTimes() 541 | { 542 | $fs = new FileSystem(); 543 | $path = $fs->path('/file'); 544 | 545 | $time = 1500020720; 546 | $atime = 1500204791; 547 | 548 | touch($path, $time, $atime); 549 | 550 | $this->assertEquals($time, filectime($path)); 551 | $this->assertEquals($time, filemtime($path)); 552 | $this->assertEquals($atime, fileatime($path)); 553 | } 554 | 555 | public function testRenamesMovesFileCorrectly() 556 | { 557 | $fs = new FileSystem(); 558 | $fs->container()->createFile('/file', 'data'); 559 | 560 | rename($fs->path('/file'), $fs->path('/file2')); 561 | 562 | $this->assertTrue($fs->container()->hasNodeAt('/file2')); 563 | $this->assertFalse($fs->container()->hasNodeAt('/file')); 564 | $this->assertEquals('data', $fs->container()->fileAt('/file2')->data()); 565 | } 566 | 567 | public function testRenameReturnsCorrectWarnings() 568 | { 569 | if(is_hhvm()) $this->markTestSkipped('Skip on HHVM'); 570 | 571 | $fs = new FileSystem(); 572 | 573 | @rename($fs->path('/file'), $fs->path('/dir/file2')); 574 | 575 | $error = error_get_last(); 576 | 577 | $this->assertStringMatchesFormat( 578 | 'mv: rename %s to %s: No such file or directory', 579 | $error['message'], 580 | 'Triggers when moving non existing file' 581 | ); 582 | 583 | $fs->container()->createFile('/file'); 584 | 585 | @rename($fs->path('/file'), $fs->path('/dir/file2')); 586 | 587 | $error = error_get_last(); 588 | 589 | $this->assertStringMatchesFormat( 590 | 'mv: rename %s to %s: No such file or directory', 591 | $error['message'], 592 | 'Triggers when moving to non existing directory' 593 | ); 594 | 595 | $fs->container()->createDir('/dir'); 596 | 597 | @rename($fs->path('/dir'), $fs->path('/file')); 598 | 599 | $error = error_get_last(); 600 | 601 | $this->assertStringMatchesFormat( 602 | 'mv: rename %s to %s: Not a directory', 603 | $error['message'], 604 | 'Triggers when moving to non existing directory' 605 | ); 606 | 607 | } 608 | 609 | public function testRenameFailsCorrectly() 610 | { 611 | if(!is_hhvm()) $this->markTestSkipped('Skip on PHP'); 612 | 613 | $fs = new FileSystem(); 614 | 615 | $this->assertFalse(@rename($fs->path('/file'), $fs->path('/dir/file2'))); 616 | 617 | $fs->container()->createFile('/file'); 618 | 619 | $this->assertFalse(@rename($fs->path('/file'), $fs->path('/dir/file2'))); 620 | 621 | $fs->container()->createDir('/dir'); 622 | 623 | $this->assertFalse(@rename($fs->path('/dir'), $fs->path('/file'))); 624 | } 625 | 626 | public function testUnlinkRemovesFile() 627 | { 628 | $fs = new FileSystem(); 629 | $fs->container()->createFile('/file'); 630 | 631 | unlink($fs->path('/file')); 632 | 633 | $this->assertFalse($fs->container()->hasNodeAt('/file')); 634 | } 635 | 636 | public function testUnlinkThrowsWarnings() 637 | { 638 | $fs = new FileSystem(); 639 | 640 | @unlink($fs->path('/file')); 641 | 642 | $error = error_get_last(); 643 | 644 | $this->assertStringMatchesFormat( 645 | 'rm: %s: No such file or directory', 646 | $error['message'], 647 | 'Warning when file does not exist' 648 | ); 649 | 650 | $fs->container()->createDir('/dir'); 651 | 652 | @unlink($fs->path('/dir')); 653 | 654 | $error = error_get_last(); 655 | 656 | $this->assertStringMatchesFormat( 657 | 'rm: %s: is a directory', 658 | $error['message'], 659 | 'Warning when trying to remove directory' 660 | ); 661 | 662 | } 663 | 664 | public function testRmdirRemovesDirectories() 665 | { 666 | $fs = new FileSystem(); 667 | $fs->container()->createDir('/dir'); 668 | 669 | rmdir($fs->path('/dir')); 670 | 671 | $this->assertFalse($fs->container()->hasNodeAt('/dir'), 'Directory has been removed'); 672 | } 673 | 674 | public function testRmdirErrorsWithNonEmptyDirectories() 675 | { 676 | $fs = new FileSystem(); 677 | $fs->container()->createDir('/dir/dir', true); 678 | 679 | @rmdir($fs->path('/dir')); 680 | 681 | $error = error_get_last(); 682 | 683 | $this->assertStringMatchesFormat( 684 | 'Warning: rmdir(%s): Directory not empty', 685 | $error['message'], 686 | 'Warning triggered when removing non empty directory' 687 | ); 688 | } 689 | 690 | public function testRmdirErrorsWhenRemovingNonExistingDirectory() 691 | { 692 | $fs = new FileSystem(); 693 | 694 | @rmdir($fs->path('/dir')); 695 | 696 | $error = error_get_last(); 697 | 698 | $this->assertStringMatchesFormat( 699 | 'Warning: rmdir(%s): No such file or directory', 700 | $error['message'], 701 | 'Warning triggered when removing non existing directory' 702 | ); 703 | } 704 | 705 | public function testRmdirErrorsWhenRemovingFile() 706 | { 707 | $fs = new FileSystem(); 708 | $fs->container()->createFile('/file'); 709 | 710 | @rmdir($fs->path('/file')); 711 | 712 | $error = error_get_last(); 713 | 714 | $this->assertStringMatchesFormat( 715 | 'Warning: rmdir(%s): Not a directory', 716 | $error['message'], 717 | 'Warning triggered when trying to remove a file' 718 | ); 719 | } 720 | 721 | public function testStreamOpenWarnsWhenFlagPassed() 722 | { 723 | $fs = new FileSystem(); 724 | $opened_path = null; 725 | 726 | $wrapper = new Wrapper(); 727 | 728 | $this->assertFalse($wrapper->stream_open($fs->path('/file'), 'r', 0, $opened_path), 'No warning when no flag'); 729 | 730 | @$wrapper->stream_open($fs->path('/file'), 'r', STREAM_REPORT_ERRORS, $opened_path); 731 | 732 | $error = error_get_last(); 733 | 734 | $this->assertStringMatchesFormat( 735 | '%s: failed to open stream.', 736 | $error['message'], 737 | 'Stream open errors when flag passed' 738 | ); 739 | 740 | } 741 | 742 | public function testDirectoryOpensForReading() 743 | { 744 | $fs = new FileSystem(); 745 | $fs->container()->createDir('/dir'); 746 | 747 | $wrapper = new Wrapper(); 748 | 749 | $handle = $wrapper->dir_opendir($fs->path('/dir')); 750 | 751 | $this->assertTrue($handle, 'Directory opened for reading'); 752 | 753 | $handle = @$wrapper->dir_opendir($fs->path('/nonExistingDir')); 754 | 755 | $this->assertFalse($handle, 'Non existing directory not opened for reading'); 756 | 757 | $error = error_get_last(); 758 | 759 | $this->assertStringMatchesFormat( 760 | 'opendir(%s): failed to open dir: No such file or directory', 761 | $error['message'], 762 | 'Opening non existing directory triggers warning' 763 | ); 764 | 765 | $handle = opendir($fs->path('/dir')); 766 | 767 | $this->assertTrue(is_resource($handle), 'opendir uses dir_opendir'); 768 | } 769 | 770 | public function testDirectoryOpenDoesNotOpenFiles() 771 | { 772 | $fs = new FileSystem(); 773 | $fs->container()->createFile('/file'); 774 | 775 | $wrapper = new Wrapper(); 776 | 777 | $handle = @$wrapper->dir_opendir($fs->path('/file')); 778 | 779 | $this->assertFalse($handle, 'Opening fiels with opendir fails'); 780 | 781 | $error = error_get_last(); 782 | 783 | $this->assertStringMatchesFormat( 784 | 'opendir(%s): failed to open dir: Not a directory', 785 | $error['message'], 786 | 'Opening fiels with opendir triggers warning' 787 | ); 788 | } 789 | 790 | public function testDirectoryCloses() 791 | { 792 | $fs = new FileSystem(); 793 | $fs->container()->createDir('/dir'); 794 | 795 | $wrapper = new Wrapper(); 796 | 797 | $this->assertFalse($wrapper->dir_closedir(), 'Returns false when no dir opened'); 798 | 799 | $wrapper->dir_opendir($fs->path('/dir')); 800 | 801 | $this->assertTrue($wrapper->dir_closedir()); 802 | } 803 | 804 | public function testDirectoryReading() 805 | { 806 | $fs = new FileSystem(); 807 | $fs->container()->createDir('/dir1'); 808 | $fs->container()->createDir('/dir2'); 809 | $fs->container()->createDir('/dir3'); 810 | 811 | $wr = new Wrapper(); 812 | $wr->dir_opendir($fs->path('/')); 813 | 814 | $this->assertEquals('dir1', $wr->dir_readdir()); 815 | $this->assertEquals('dir2', $wr->dir_readdir()); 816 | $this->assertEquals('dir3', $wr->dir_readdir()); 817 | $this->assertFalse($wr->dir_readdir()); 818 | 819 | $wr->dir_rewinddir(); 820 | $this->assertEquals('dir1', $wr->dir_readdir(), 'Directory rewound'); 821 | 822 | } 823 | 824 | public function testDirectoryIterationWithDirectoryIterator() 825 | { 826 | $fs = new FileSystem(); 827 | $fs->container()->createDir('/dir1'); 828 | $fs->container()->createDir('/dir2'); 829 | $fs->container()->createDir('/dir3'); 830 | 831 | $result = array(); 832 | 833 | foreach (new \DirectoryIterator($fs->path('/')) as $fileInfo) { 834 | $result[] = $fileInfo->getBasename(); 835 | } 836 | 837 | $this->assertEquals(array('dir1', 'dir2', 'dir3'), $result, 'All directories found'); 838 | 839 | } 840 | 841 | public function testStreamOpenDoesNotOpenDirectoriesForWriting() 842 | { 843 | $fs = new FileSystem(); 844 | $fs->container()->createDir('/dir'); 845 | 846 | $this->assertFalse(@fopen($fs->path('/dir'), 'w')); 847 | $this->assertFalse(@fopen($fs->path('/dir'), 'r+')); 848 | $this->assertFalse(@fopen($fs->path('/dir'), 'w+')); 849 | $this->assertFalse(@fopen($fs->path('/dir'), 'a')); 850 | $this->assertFalse(@fopen($fs->path('/dir'), 'a+')); 851 | 852 | $opened_path = null; 853 | 854 | $wr = new Wrapper(); 855 | @$wr->stream_open($fs->path('/dir'), 'w', STREAM_REPORT_ERRORS, $opened_path); 856 | 857 | $error = error_get_last(); 858 | 859 | $this->assertStringMatchesFormat( 860 | 'fopen(%s): failed to open stream: Is a directory', 861 | $error['message'], 862 | 'Stream does not open directories' 863 | ); 864 | } 865 | 866 | public function testStreamOpenAllowsForDirectoryOpeningForReadingAndReturnsEmptyStrings() 867 | { 868 | $fs = new FileSystem(); 869 | $fs->container()->createDir('/dir'); 870 | 871 | $handle = fopen($fs->path('/dir'), 'r'); 872 | 873 | $this->assertTrue(is_resource($handle)); 874 | 875 | $this->assertEmpty(fread($handle, 1)); 876 | } 877 | 878 | public function testPermissionsAreCheckedWhenOpeningFiles() 879 | { 880 | $fs = new FileSystem(); 881 | $file = $fs->container()->createFile('/file'); 882 | $openedPath = null; 883 | 884 | $wr = new Wrapper(); 885 | 886 | $file->chmod(0000); 887 | $file->chown(0); 888 | $file->chgrp(0); 889 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'r', 0, $openedPath)); 890 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'r+', 0, $openedPath)); 891 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'w', 0, $openedPath)); 892 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'w+', 0, $openedPath)); 893 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'a', 0, $openedPath)); 894 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'a+', 0, $openedPath)); 895 | 896 | $file->chmod(0400); 897 | $file->chown($this->uid); 898 | $file->chgrp(0); 899 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'r', 0, $openedPath)); 900 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'r+', 0, $openedPath)); 901 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'w', 0, $openedPath)); 902 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'w+', 0, $openedPath)); 903 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'a', 0, $openedPath)); 904 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'a+', 0, $openedPath)); 905 | 906 | $file->chmod(0200); 907 | $file->chown($this->uid); 908 | $file->chgrp(0); 909 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'r', 0, $openedPath)); 910 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'r+', 0, $openedPath)); 911 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'w', 0, $openedPath)); 912 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'w+', 0, $openedPath)); 913 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'a', 0, $openedPath)); 914 | $this->assertFalse($wr->stream_open($fs->path('/file'), 'a+', 0, $openedPath)); 915 | 916 | $file->chmod(0600); 917 | $file->chown($this->uid); 918 | $file->chgrp(0); 919 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'r', 0, $openedPath)); 920 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'r+', 0, $openedPath)); 921 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'w', 0, $openedPath)); 922 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'w+', 0, $openedPath)); 923 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'a', 0, $openedPath)); 924 | $this->assertTrue($wr->stream_open($fs->path('/file'), 'a+', 0, $openedPath)); 925 | 926 | } 927 | 928 | public function testTemporaryFileCreatedToReadDirectoriesWithStreamOpenInheritsPermissions() 929 | { 930 | $fs = new FileSystem(); 931 | $file = $fs->container()->createDir('/dir'); 932 | $openedPath = null; 933 | 934 | $wr = new Wrapper(); 935 | 936 | $file->chmod(0000); 937 | $file->chown(0); 938 | $file->chgrp(0); 939 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'r', 0, $openedPath)); 940 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'r+', 0, $openedPath)); 941 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w', 0, $openedPath)); 942 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w+', 0, $openedPath)); 943 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a', 0, $openedPath)); 944 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a+', 0, $openedPath)); 945 | 946 | $file->chmod(0400); 947 | $file->chown($this->uid); 948 | $file->chgrp(0); 949 | $this->assertTrue($wr->stream_open($fs->path('/dir'), 'r', 0, $openedPath)); 950 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'r+', 0, $openedPath)); 951 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w', 0, $openedPath)); 952 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w+', 0, $openedPath)); 953 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a', 0, $openedPath)); 954 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a+', 0, $openedPath)); 955 | 956 | $file->chmod(0200); 957 | $file->chown($this->uid); 958 | $file->chgrp(0); 959 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'r', 0, $openedPath)); 960 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'r+', 0, $openedPath)); 961 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w', 0, $openedPath)); 962 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w+', 0, $openedPath)); 963 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a', 0, $openedPath)); 964 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a+', 0, $openedPath)); 965 | 966 | $file->chmod(0600); 967 | $file->chown($this->uid); 968 | $file->chgrp(0); 969 | $this->assertTrue($wr->stream_open($fs->path('/dir'), 'r', 0, $openedPath)); 970 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'r+', 0, $openedPath)); 971 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w', 0, $openedPath)); 972 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'w+', 0, $openedPath)); 973 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a', 0, $openedPath)); 974 | $this->assertFalse($wr->stream_open($fs->path('/dir'), 'a+', 0, $openedPath)); 975 | } 976 | 977 | public function testPermissionsAreCheckedWhenOpeningDirectories() 978 | { 979 | $fs = new FileSystem(); 980 | $file = $fs->container()->createDir('/dir'); 981 | $openedPath = null; 982 | 983 | $wr = new Wrapper(); 984 | 985 | $file->chmod(0000); 986 | $file->chown(0); 987 | $file->chgrp(0); 988 | $this->assertFalse(@$wr->dir_opendir($fs->path('/dir'), 0, $openedPath)); 989 | 990 | $file->chmod(0200); 991 | $file->chown($this->uid); 992 | $file->chgrp(0); 993 | $this->assertFalse(@$wr->dir_opendir($fs->path('/dir'), 0, $openedPath)); 994 | 995 | $file->chmod(0400); 996 | $file->chown($this->uid); 997 | $file->chgrp(0); 998 | $this->assertTrue(@$wr->stream_open($fs->path('/dir'), 'r', 0, $openedPath)); 999 | 1000 | $file->chmod(0040); 1001 | $file->chown(0); 1002 | $file->chgrp($this->gid); 1003 | $this->assertTrue(@$wr->stream_open($fs->path('/dir'), 'r', 0, $openedPath)); 1004 | } 1005 | 1006 | public function testPermissionsAreCheckedWhenCreatingFilesWithinDirectories() 1007 | { 1008 | $fs = new FileSystem(); 1009 | $dir = $fs->createDirectory('/dir'); 1010 | 1011 | $dir->chmod(0000); 1012 | $this->assertFalse(@file_put_contents($fs->path('/dir/file'), 'data')); 1013 | 1014 | $dir->chmod(0400); 1015 | $this->assertFalse(@file_put_contents($fs->path('/dir/file'), 'data')); 1016 | 1017 | $dir->chmod(0200); 1018 | $this->assertGreaterThan(0, @file_put_contents($fs->path('/dir/file'), 'data')); 1019 | } 1020 | 1021 | public function testStreamOpenReportsErrorsOnPermissionDenied() 1022 | { 1023 | $fs = new FileSystem(); 1024 | $dir = $fs->createDirectory('/dir'); 1025 | $file = $fs->createFile('/file'); 1026 | $dir->chmod(0000); 1027 | $na = []; 1028 | $openedPath = null; 1029 | 1030 | $wr = new Wrapper(); 1031 | 1032 | @$wr->stream_open($fs->path('/dir/file'), 'w', STREAM_REPORT_ERRORS, $openedPath); 1033 | 1034 | $error = error_get_last(); 1035 | 1036 | $this->assertStringMatchesFormat( 1037 | '%s: failed to open stream: Permission denied', 1038 | $error['message'] 1039 | ); 1040 | 1041 | @$na['n/a']; //putting error in known state 1042 | $file->chmod(0000); 1043 | @$wr->stream_open($fs->path('/file'), 'r', STREAM_REPORT_ERRORS, $openedPath); 1044 | 1045 | $error = error_get_last(); 1046 | 1047 | $this->assertStringMatchesFormat( 1048 | '%s: failed to open stream: Permission denied', 1049 | $error['message'] 1050 | ); 1051 | 1052 | @$na['n/a']; //putting error in known state 1053 | $file->chmod(0000); 1054 | @$wr->stream_open($fs->path('/file'), 'w', STREAM_REPORT_ERRORS, $openedPath); 1055 | 1056 | $error = error_get_last(); 1057 | 1058 | $this->assertStringMatchesFormat( 1059 | '%s: failed to open stream: Permission denied', 1060 | $error['message'] 1061 | ); 1062 | 1063 | @$na['n/a']; //putting error in known state 1064 | $file->chmod(0000); 1065 | @$wr->stream_open($fs->path('/file'), 'a', STREAM_REPORT_ERRORS, $openedPath); 1066 | 1067 | $error = error_get_last(); 1068 | 1069 | $this->assertStringMatchesFormat( 1070 | '%s: failed to open stream: Permission denied', 1071 | $error['message'] 1072 | ); 1073 | 1074 | @$na['n/a']; //putting error in known state 1075 | $file->chmod(0000); 1076 | @$wr->stream_open($fs->path('/file'), 'w+', STREAM_REPORT_ERRORS, $openedPath); 1077 | 1078 | $error = error_get_last(); 1079 | 1080 | $this->assertStringMatchesFormat( 1081 | '%s: failed to open stream: Permission denied', 1082 | $error['message'] 1083 | ); 1084 | 1085 | } 1086 | 1087 | public function testPermissionsAreCheckedWhenCreatingDirectories() 1088 | { 1089 | $fs = new FileSystem(); 1090 | $fs->createDirectory('/test', false, 0000); 1091 | 1092 | $wr = new Wrapper(); 1093 | 1094 | $this->assertFalse(@$wr->mkdir($fs->path('/test/dir'), 0777, 0)); 1095 | 1096 | $error = error_get_last(); 1097 | 1098 | $this->assertStringMatchesFormat( 1099 | 'mkdir: %s: Permission denied', 1100 | $error['message'] 1101 | ); 1102 | } 1103 | 1104 | public function testPermissionsAreCheckedWhenRemovingFiles() 1105 | { 1106 | $fs = new FileSystem(); 1107 | $file = $fs->createFile('/file'); 1108 | $file->chmod(0000); 1109 | 1110 | $wr = new Wrapper(); 1111 | $this->assertTrue($wr->unlink($fs->path('/file')), 'Allows removals with writable parent'); 1112 | 1113 | $fs->root()->chmod(0500); 1114 | 1115 | $this->assertFalse(@$wr->unlink($fs->path('/file')), 'Does not allow removals with non-writable parent'); 1116 | 1117 | $error = error_get_last(); 1118 | 1119 | $this->assertStringMatchesFormat( 1120 | 'rm: %s: Permission denied', 1121 | $error['message'] 1122 | ); 1123 | } 1124 | 1125 | public function testRmDirNotAllowedWhenDirectoryNotWritable() 1126 | { 1127 | $fs = new FileSystem(); 1128 | $dir = $fs->createDirectory('/dir'); 1129 | 1130 | $wr = new Wrapper(); 1131 | 1132 | $dir->chmod(0000); 1133 | $this->assertFalse(@$wr->rmdir($fs->path('/dir')), 'Directory not removed with no permissions'); 1134 | 1135 | $dir->chmod(0100); 1136 | $this->assertFalse(@$wr->rmdir($fs->path('/dir')), 'Directory not removed with exec only'); 1137 | 1138 | $dir->chmod(0200); 1139 | $this->assertFalse(@$wr->rmdir($fs->path('/dir')), 'Directory not removed with write'); 1140 | 1141 | $error = error_get_last(); 1142 | 1143 | $this->assertStringMatchesFormat( 1144 | 'rmdir: %s: Permission denied', 1145 | $error['message'] 1146 | ); 1147 | 1148 | $dir->chmod(0400); 1149 | $this->assertTrue( 1150 | $wr->rmdir($fs->path('/dir')), 1151 | 'Directory removed with read permission, yes that is how it normally behaves ;)' 1152 | ); 1153 | } 1154 | 1155 | public function testChmodNotAllowedIfNotOwner() 1156 | { 1157 | $fs = new FileSystem(); 1158 | $file = $fs->createFile('/file'); 1159 | $file->chown($this->uid + 1); //set to non current 1160 | 1161 | $wr = new Wrapper(); 1162 | 1163 | $this->assertFalse( 1164 | @$wr->stream_metadata($fs->path('/file'), STREAM_META_ACCESS, 0000), 1165 | 'Not allowed to chmod if not owner' 1166 | ); 1167 | 1168 | $error = error_get_last(); 1169 | 1170 | $this->assertStringMatchesFormat( 1171 | 'chmod: %s: Permission denied', 1172 | $error['message'] 1173 | ); 1174 | } 1175 | 1176 | public function testChownAndChgrpAllowedIfOwner() 1177 | { 1178 | $fs = new FileSystem(); 1179 | $file = $fs->createFile($fileName = uniqid('/')); 1180 | $file->chown($this->uid); //set to current 1181 | 1182 | $uid = $this->uid + 1; 1183 | 1184 | $wr = new Wrapper(); 1185 | 1186 | $this->assertTrue( 1187 | $wr->stream_metadata($fs->path($fileName), STREAM_META_OWNER, $uid) 1188 | ); 1189 | 1190 | $file = $fs->createFile($fileName = uniqid('/')); 1191 | $file->chown($this->uid); //set to current 1192 | 1193 | $this->assertTrue( 1194 | $wr->stream_metadata($fs->path($fileName), STREAM_META_OWNER_NAME, 'user') 1195 | ); 1196 | 1197 | $file = $fs->createFile($fileName = uniqid('/')); 1198 | $file->chown($this->uid); //set to current 1199 | 1200 | $this->assertTrue( 1201 | $wr->stream_metadata($fs->path($fileName), STREAM_META_GROUP, $uid) 1202 | ); 1203 | 1204 | $file = $fs->createFile($fileName = uniqid('/')); 1205 | $file->chown($this->uid); //set to current 1206 | 1207 | $this->assertTrue( 1208 | $wr->stream_metadata($fs->path($fileName), STREAM_META_GROUP_NAME, 'userGroup') 1209 | ); 1210 | 1211 | } 1212 | 1213 | public function testChownAndChgrpNotAllowedIfNotRoot() 1214 | { 1215 | $fs = new FileSystem(); 1216 | $file = $fs->createFile('/file'); 1217 | $file->chown($this->uid + 1); //set to non current 1218 | 1219 | $wr = new Wrapper(); 1220 | 1221 | $this->assertFalse( 1222 | @$wr->stream_metadata($fs->path('/file'), STREAM_META_OWNER, 1), 1223 | 'Not allowed to chown if not root' 1224 | ); 1225 | 1226 | $error = error_get_last(); 1227 | 1228 | $this->assertStringMatchesFormat( 1229 | 'chown: %s: Permission denied', 1230 | $error['message'] 1231 | ); 1232 | 1233 | $na = []; 1234 | @$na['n/a']; //putting error in known state 1235 | 1236 | $this->assertFalse( 1237 | @$wr->stream_metadata($fs->path('/file'), STREAM_META_OWNER_NAME, 'user'), 1238 | 'Not allowed to chown by name if not root' 1239 | ); 1240 | 1241 | $error = error_get_last(); 1242 | 1243 | $this->assertStringMatchesFormat( 1244 | 'chown: %s: Permission denied', 1245 | $error['message'] 1246 | ); 1247 | 1248 | @$na['n/a']; //putting error in known state 1249 | 1250 | $this->assertFalse( 1251 | @$wr->stream_metadata($fs->path('/file'), STREAM_META_GROUP, 1), 1252 | 'Not allowed to chgrp if not root' 1253 | ); 1254 | 1255 | $error = error_get_last(); 1256 | 1257 | $this->assertStringMatchesFormat( 1258 | 'chgrp: %s: Permission denied', 1259 | $error['message'] 1260 | ); 1261 | 1262 | @$na['n/a']; //putting error in known state 1263 | 1264 | $this->assertFalse( 1265 | @$wr->stream_metadata($fs->path('/file'), STREAM_META_GROUP_NAME, 'group'), 1266 | 'Not allowed to chgrp by name if not root' 1267 | ); 1268 | 1269 | $error = error_get_last(); 1270 | 1271 | $this->assertStringMatchesFormat( 1272 | 'chgrp: %s: Permission denied', 1273 | $error['message'] 1274 | ); 1275 | } 1276 | 1277 | public function testTouchNotAllowedIfNotOwnerOrNotWritable() 1278 | { 1279 | $fs = new FileSystem(); 1280 | $file = $fs->createFile('/file'); 1281 | $file->chown($this->uid + 1); //set to non current 1282 | $file->chmod(0000); 1283 | 1284 | $wr = new Wrapper(); 1285 | 1286 | $this->assertFalse( 1287 | @$wr->stream_metadata($fs->path('/file'), STREAM_META_TOUCH, 0), 1288 | 'Not allowed to touch if not owner and no permission' 1289 | ); 1290 | 1291 | $error = error_get_last(); 1292 | 1293 | $this->assertStringMatchesFormat( 1294 | 'touch: %s: Permission denied', 1295 | $error['message'] 1296 | ); 1297 | 1298 | $file->chown($this->uid); 1299 | 1300 | $this->assertTrue( 1301 | $wr->stream_metadata($fs->path('/file'), STREAM_META_TOUCH, 0), 1302 | 'Allowed to touch if owner and no permission' 1303 | ); 1304 | 1305 | $file->chown($this->uid + 1); //set to non current 1306 | $file->chmod(0002); 1307 | 1308 | $this->assertTrue( 1309 | $wr->stream_metadata($fs->path('/file'), STREAM_META_TOUCH, 0), 1310 | 'Allowed to touch if not owner but with write permission' 1311 | ); 1312 | } 1313 | 1314 | public function testLchown() 1315 | { 1316 | if ($this->uid == 0) { 1317 | $this->markTestSkipped( 1318 | 'No point testing if user is already root. '. 1319 | 'Php unit shouldn\'t be run as root user. (Unless you are a windows user!)' 1320 | ); 1321 | } 1322 | 1323 | $fs = new FileSystem(); 1324 | $directory = $fs->createDirectory('/dir'); 1325 | $link = new Link('link', $directory); 1326 | $directory->addLink($link); 1327 | 1328 | $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root 1329 | 1330 | lchown($fs->path('/dir/link'), 'root'); 1331 | $this->assertEquals('root', posix_getpwuid(fileowner($fs->path('/dir/link')))['name']); 1332 | 1333 | } 1334 | 1335 | public function testLchgrp() 1336 | { 1337 | if ($this->uid == 0) { 1338 | $this->markTestSkipped( 1339 | 'No point testing if group is already root. '. 1340 | 'Php unit shouldn\'t be run as root group. (Unless you are on Windows - then we skip)' 1341 | ); 1342 | } 1343 | 1344 | $fs = new FileSystem(); 1345 | $directory = $fs->createDirectory('/dir'); 1346 | $link = new Link('link', $directory); 1347 | $directory->addLink($link); 1348 | 1349 | $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root 1350 | 1351 | //lets workout available group 1352 | //this is needed to find string name of group root belongs to 1353 | $group = posix_getgrgid(posix_getpwuid(0)['gid'])['name']; 1354 | 1355 | chgrp($fs->path('/dir/link'), $group); 1356 | 1357 | $this->assertEquals($group, posix_getgrgid(filegroup($fs->path('/dir/link')))['name']); 1358 | } 1359 | 1360 | public function testFileCopy() 1361 | { 1362 | $fs = new FileSystem(); 1363 | $fs->createFile('/file', 'data'); 1364 | 1365 | copy($fs->path('/file'), $fs->path('/file2')); 1366 | 1367 | $this->assertTrue(file_exists($fs->path('/file2'))); 1368 | 1369 | $this->assertEquals('data', $fs->container()->fileAt('/file2')->data()); 1370 | 1371 | } 1372 | 1373 | public function testLinkCopyCreatesHardCopyOfFile() 1374 | { 1375 | 1376 | $fs = new FileSystem(); 1377 | $fs->createFile('/file', 'data'); 1378 | $fs->createLink('/link', '/file'); 1379 | 1380 | copy($fs->path('/link'), $fs->path('/file2')); 1381 | 1382 | $this->assertTrue(file_exists($fs->path('/file2'))); 1383 | $this->assertEquals('data', $fs->container()->fileAt('/file2')->data()); 1384 | 1385 | } 1386 | 1387 | public function testLinkReading() 1388 | { 1389 | 1390 | $fs = new FileSystem(); 1391 | $fs->createFile('/file', 'data'); 1392 | $fs->createLink('/link', '/file'); 1393 | 1394 | $this->assertEquals('data', file_get_contents($fs->path('/link'))); 1395 | } 1396 | 1397 | public function testLinkWriting() 1398 | { 1399 | 1400 | $fs = new FileSystem(); 1401 | $fs->createFile('/file', 'ubots!'); 1402 | $fs->createLink('/link', '/file'); 1403 | 1404 | file_put_contents($fs->path('/link'), 'data'); 1405 | 1406 | $this->assertEquals('data', file_get_contents($fs->path('/link'))); 1407 | 1408 | } 1409 | 1410 | public function testChmodViaLink() 1411 | { 1412 | $fs = new FileSystem(); 1413 | $name = $fs->path($fs->createFile('/file')->path()); 1414 | $link = $fs->path($fs->createLink('/link', '/file')->path()); 1415 | 1416 | chmod($link, 0000); 1417 | 1418 | $this->assertFalse(is_readable($name)); 1419 | $this->assertFalse(is_writable($name)); 1420 | $this->assertFalse(is_executable($name)); 1421 | 1422 | chmod($link, 0700); 1423 | 1424 | $this->assertTrue(is_readable($name)); 1425 | $this->assertTrue(is_writable($name)); 1426 | $this->assertTrue(is_executable($name)); 1427 | 1428 | } 1429 | 1430 | public function testIsExecutableReturnsCorrectly() 1431 | { 1432 | $fs = new FileSystem(); 1433 | $fs->createFile('/file'); 1434 | 1435 | chmod($fs->path('/file'), 0000); 1436 | 1437 | $this->assertFalse(is_executable($fs->path('/file'))); 1438 | 1439 | chmod($fs->path('/file'), 0777); 1440 | 1441 | $this->assertTrue(is_executable($fs->path('/file'))); 1442 | } 1443 | 1444 | public function testExclusiveLock() 1445 | { 1446 | $fs = new FileSystem(); 1447 | $file = $fs->path($fs->createFile('/file')->path()); 1448 | 1449 | $fh1 = fopen($file, 'c'); 1450 | $fh2 = fopen($file, 'c'); 1451 | 1452 | $this->assertTrue(flock($fh1, LOCK_EX|LOCK_NB)); 1453 | $this->assertFalse(flock($fh2, LOCK_EX|LOCK_NB)); 1454 | } 1455 | 1456 | public function testSharedLock() 1457 | { 1458 | $fs = new FileSystem(); 1459 | $file = $fs->path($fs->createFile('/file')->path()); 1460 | 1461 | $fh1 = fopen($file, 'c'); 1462 | $fh2 = fopen($file, 'c'); 1463 | $fh3 = fopen($file, 'c'); 1464 | 1465 | $this->assertTrue(flock($fh1, LOCK_SH|LOCK_NB)); 1466 | $this->assertTrue(flock($fh2, LOCK_SH|LOCK_NB)); 1467 | $this->assertFalse(flock($fh3, LOCK_EX|LOCK_NB)); 1468 | } 1469 | 1470 | public function testUnlockSharedLock() 1471 | { 1472 | $fs = new FileSystem(); 1473 | $file = $fs->path($fs->createFile('/file')->path()); 1474 | 1475 | $fh1 = fopen($file, 'c'); 1476 | $fh2 = fopen($file, 'c'); 1477 | 1478 | $this->assertTrue(flock($fh1, LOCK_SH|LOCK_NB)); 1479 | $this->assertTrue(flock($fh1, LOCK_UN|LOCK_NB)); 1480 | $this->assertTrue(flock($fh2, LOCK_EX|LOCK_NB)); 1481 | } 1482 | 1483 | public function testUnlockExclusiveLock() 1484 | { 1485 | $fs = new FileSystem(); 1486 | $file = $fs->path($fs->createFile('/file')->path()); 1487 | 1488 | $fh1 = fopen($file, 'c'); 1489 | $fh2 = fopen($file, 'c'); 1490 | 1491 | $this->assertTrue(flock($fh1, LOCK_EX|LOCK_NB)); 1492 | $this->assertTrue(flock($fh1, LOCK_UN|LOCK_NB)); 1493 | $this->assertTrue(flock($fh2, LOCK_EX|LOCK_NB)); 1494 | } 1495 | 1496 | public function testDowngradeExclusiveLock() 1497 | { 1498 | $fs = new FileSystem(); 1499 | $file = $fs->path($fs->createFile('/file')->path()); 1500 | 1501 | $fh1 = fopen($file, 'c'); 1502 | $fh2 = fopen($file, 'c'); 1503 | 1504 | $this->assertTrue(flock($fh1, LOCK_EX|LOCK_NB)); 1505 | $this->assertTrue(flock($fh1, LOCK_SH|LOCK_NB)); 1506 | $this->assertTrue(flock($fh2, LOCK_SH|LOCK_NB)); 1507 | } 1508 | 1509 | public function testUpgradeSharedLock() 1510 | { 1511 | $fs = new FileSystem(); 1512 | $file = $fs->path($fs->createFile('/file')->path()); 1513 | 1514 | $fh1 = fopen($file, 'c'); 1515 | $fh2 = fopen($file, 'c'); 1516 | 1517 | $this->assertTrue(flock($fh1, LOCK_SH|LOCK_NB)); 1518 | $this->assertTrue(flock($fh1, LOCK_EX|LOCK_NB)); 1519 | $this->assertFalse(flock($fh2, LOCK_SH|LOCK_NB)); 1520 | } 1521 | 1522 | public function testUpgradeSharedLockImpossible() 1523 | { 1524 | $fs = new FileSystem(); 1525 | $file = $fs->path($fs->createFile('/file')->path()); 1526 | 1527 | $fh1 = fopen($file, 'c'); 1528 | $fh2 = fopen($file, 'c'); 1529 | 1530 | $this->assertTrue(flock($fh1, LOCK_SH|LOCK_NB)); 1531 | $this->assertTrue(flock($fh2, LOCK_SH|LOCK_NB)); 1532 | $this->assertFalse(flock($fh1, LOCK_EX|LOCK_NB)); 1533 | } 1534 | 1535 | public function testFileSize() 1536 | { 1537 | $fs = new FileSystem(); 1538 | $file = $fs->path($fs->createFile('/file', '12345')->path()); 1539 | 1540 | $this->assertEquals(5, filesize($file)); 1541 | } 1542 | 1543 | public function testRmdirAfterUrlStatCall() 1544 | { 1545 | $fs = new FileSystem(); 1546 | 1547 | $path = $fs->path('dir'); 1548 | 1549 | mkdir($path); 1550 | 1551 | $this->assertFileExists($path); 1552 | 1553 | rmdir($path); 1554 | 1555 | $this->assertFileNotExists($path); 1556 | } 1557 | 1558 | public function testUnlinkAfterUrlStatCall() 1559 | { 1560 | $fs = new FileSystem(); 1561 | 1562 | $path = $fs->path('file'); 1563 | 1564 | touch($path); 1565 | 1566 | $this->assertFileExists($path); 1567 | 1568 | unlink($path); 1569 | 1570 | $this->assertFileNotExists($path); 1571 | } 1572 | 1573 | public function testFinfoSupport() 1574 | { 1575 | $fs = new FileSystem(); 1576 | 1577 | $fs->createFile('/file.gif', base64_decode("R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")); 1578 | 1579 | $finfo = new \Finfo(FILEINFO_MIME_TYPE); 1580 | 1581 | $this->assertEquals("image/gif", $finfo->file($fs->path('/file.gif'))); 1582 | 1583 | } 1584 | 1585 | public function testRequire() 1586 | { 1587 | $fs = new FileSystem(); 1588 | $fs->createFile('/file.php', <<<'PHP' 1589 | assertSame(1, require $fs->path('/file.php')); 1594 | } 1595 | } 1596 | --------------------------------------------------------------------------------