Add source

This commit is contained in:
adlawson 2014-07-06 17:31:39 +01:00
parent 4f985e21f9
commit 404b908bbd
28 changed files with 2314 additions and 0 deletions

View File

@ -0,0 +1,14 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Exception;
interface ExceptionInterface
{
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Exception;
use Exception;
use OutOfBoundsException;
use Vfs\Node\NodeContainerInterface;
class ExistingNodeException extends OutOfBoundsException implements ExceptionInterface
{
protected $container;
protected $name;
/**
* @param string $name
* @param NodeContainerInterface $container
* @param integer $code
* @param Exception $previous
*/
public function __construct($name, NodeContainerInterface $container, $code = 0, Exception $previous = null)
{
$this->container = $container;
$this->name = $name;
$message = sprintf('Node with name "%s" already exists in container.', $name);
parent::__construct($message, $code, $previous);
}
/**
* @return NodeContainerInterface
*/
public function getContainer()
{
return $this->container;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Exception;
use Exception;
use OutOfRangeException;
use Vfs\Node\NodeContainerInterface;
class MissingNodeException extends OutOfRangeException implements ExceptionInterface
{
protected $container;
protected $name;
/**
* @param string $name
* @param NodeContainerInterface $container
* @param integer $code
* @param Exception $previous
*/
public function __construct($name, NodeContainerInterface $container, $code = 0, Exception $previous = null)
{
$this->container = $container;
$this->name = $name;
$message = sprintf('Node with name "%s" doesn\'t exist in container.', $name);
parent::__construct($message, $code, $previous);
}
/**
* @return NodeContainerInterface
*/
public function getContainer()
{
return $this->container;
}
/**
* @return string
*/
public function getName()
{
return $this->name;
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Exception;
use Exception;
use OutOfBoundsException;
class RegisteredSchemeException extends OutOfBoundsException implements ExceptionInterface
{
protected $scheme;
/**
* @param string $scheme
* @param integer $code
* @param Exception $previous
*/
public function __construct($scheme, $code = 0, Exception $previous = null)
{
$this->scheme = $scheme;
$message = sprintf('File system with scheme "%s" has already been registered.', $scheme);
parent::__construct($message, $code, $previous);
}
/**
* @return string
*/
public function getScheme()
{
return $this->scheme;
}
}

View File

@ -0,0 +1,52 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Exception;
use Exception;
use RuntimeException;
use Vfs\Stream\HandleInterface;
class UnopenedHandleException extends RuntimeException implements ExceptionInterface
{
protected $handle;
protected $url;
/**
* @param HandleInterface $handle
* @param string $url
* @param integer $code
* @param Exception $previous
*/
public function __construct(HandleInterface $handle, $url, $code = 0, Exception $previous = null)
{
$this->handle = $handle;
$this->url = $url;
$message = sprintf('Handle at url "%s" hasn\'t been opened.', $url);
parent::__construct($message, $code, $previous);
}
/**
* @return HandleInterface
*/
public function getHandle()
{
return $this->handle;
}
/**
* @return string
*/
public function getUrl()
{
return $this->url;
}
}

View File

@ -0,0 +1,40 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Exception;
use Exception;
use OutOfRangeException;
class UnregisteredSchemeException extends OutOfRangeException implements ExceptionInterface
{
protected $scheme;
/**
* @param string $scheme
* @param integer $code
* @param Exception $previous
*/
public function __construct($scheme, $code = 0, Exception $previous = null)
{
$this->scheme = $scheme;
$message = sprintf('File system with scheme "%s" has not been registered.', $scheme);
parent::__construct($message, $code, $previous);
}
/**
* @return string
*/
public function getScheme()
{
return $this->scheme;
}
}

149
src/FileSystem.php Normal file
View File

@ -0,0 +1,149 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs;
use Psr\Log\LoggerInterface;
use Vfs\Exception\RegisteredSchemeException;
use Vfs\Exception\UnregisteredSchemeException;
use Vfs\Node\Factory\NodeFactoryInterface;
use Vfs\Node\Walker\NodeWalkerInterface;
class FileSystem implements FileSystemInterface
{
protected $factory;
protected $registry;
protected $logger;
protected $scheme;
protected $walker;
protected $wrapperClass;
/**
* @param string $scheme
* @param string $wrapperClass
* @param NodeFactoryInterface $factory
* @param NodeWalkerInterface $walker
* @param RegistryInterface $registry
* @param LoggerInterface $logger
*/
public function __construct(
$scheme,
$wrapperClass,
NodeFactoryInterface $factory,
NodeWalkerInterface $walker,
RegistryInterface $registry,
LoggerInterface $logger
) {
$this->wrapperClass = $wrapperClass;
$this->scheme = $this->formatScheme($scheme);
$this->walker = $walker;
$this->logger = $logger;
$this->factory = $factory;
$this->registry = $registry;
$this->root = $factory->buildDirectory();
}
/**
* @param string $scheme
* @return FileSystem
*/
public static function factory($scheme = self::SCHEME)
{
$builder = new FileSystemBuilder($scheme);
$fs = $builder->build();
$fs->mount();
return $fs;
}
/**
* {@inheritdoc}
*/
public function get($path)
{
return $this->walker->findNode($this->root, $path);
}
/**
* {@inheritdoc}
*/
public function getLogger()
{
return $this->logger;
}
/**
* {@inheritdoc}
*/
public function getNodeFactory()
{
return $this->factory;
}
/**
* {@inheritdoc}
*/
public function getNodeWalker()
{
return $this->walker;
}
/**
* @return string
*/
public function getScheme()
{
return $this->scheme;
}
/**
* {@inheritdoc}
*/
public function mount()
{
if ($this->registry->has($this->scheme) || in_array($this->scheme, stream_get_wrappers())) {
throw new RegisteredSchemeException($this->scheme);
}
if (stream_wrapper_register($this->scheme, $this->wrapperClass)) {
$this->registry->add($this->scheme, $this);
return true;
}
return false;
}
/**
* {@inheritdoc}
*/
public function unmount()
{
if (!$this->registry->has($this->scheme) && !in_array($this->scheme, stream_get_wrappers())) {
throw new UnregisteredSchemeException($this->scheme);
}
if (stream_wrapper_unregister($this->scheme)) {
$this->registry->remove($this->scheme, $this);
return true;
}
return false;
}
/**
* @param string $scheme
* @return string
*/
protected function formatScheme($scheme)
{
return rtrim($scheme, ':/\\');
}
}

232
src/FileSystemBuilder.php Normal file
View File

@ -0,0 +1,232 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs;
use Psr\Log\LoggerInterface;
use Vfs\Logger\PhpErrorLogger;
use Vfs\Node\Factory\NodeFactory;
use Vfs\Node\Factory\NodeFactoryInterface;
use Vfs\Node\Walker\NodeWalker;
use Vfs\Node\Walker\NodeWalkerInterface;
class FileSystemBuilder
{
protected $factory;
protected $registry;
protected $logger;
protected $scheme;
protected $walker;
protected $wrapperClass;
protected $tree = [];
/**
* @param string $scheme
*/
public function __construct($scheme = FileSystemInterface::SCHEME)
{
$this->setScheme($scheme);
}
/**
* @return FileSystemInterface
*/
public function build()
{
$fs = new FileSystem(
$this->getScheme(),
$this->getStreamWrapper() ?: $this->buildDefaultStreamWrapper(),
$this->getNodeFactory() ?: $this->buildDefaultNodeFactory(),
$this->getNodeWalker() ?: $this->buildDefaultNodeWalker(),
$this->getRegistry() ?: $this->buildDefaultRegistry(),
$this->getLogger() ?: $this->buildDefaultLogger()
);
$root = $fs->get('/');
$factory = $fs->getNodeFactory();
foreach ($factory->buildTree($this->getTree()) as $name => $node) {
$root->set($name, $node);
}
return $fs;
}
/**
* @return LoggerInterface
*/
public function getLogger()
{
return $this->logger;
}
/**
* @param LoggerInterface $logger
* @return FileSystemBuilder
*/
public function setLogger(LoggerInterface $logger)
{
$this->logger = $logger;
return $this;
}
/**
* @return NodeFactoryInterface
*/
public function getNodeFactory()
{
return $this->factory;
}
/**
* @param NodeFactoryInterface $factory
* @return FileSystemBuilder
*/
public function setNodeFactory(NodeFactoryInterface $factory)
{
$this->factory = $factory;
return $this;
}
/**
* @return NodeWalkerInterface
*/
public function getNodeWalker()
{
return $this->walker;
}
/**
* @param NodeWalkerInterface $walker
* @return FileSystemBuilder
*/
public function setNodeWalker(NodeWalkerInterface $walker)
{
$this->walker = $walker;
return $this;
}
/**
* @return RegistryInterface
*/
public function getRegistry()
{
return $this->registry;
}
/**
* @param RegistryInterface $registry
* @return FileSystemBuilder
*/
public function setRegistry(RegistryInterface $registry)
{
$this->registry = $registry;
return $this;
}
/**
* @return string
*/
public function getScheme()
{
return $this->scheme;
}
/**
* @param string $scheme
* @return FileSystemBuilder
*/
public function setScheme($scheme)
{
$this->scheme = $scheme;
return $this;
}
/**
* @return string
*/
public function getStreamWrapper()
{
return $this->wrapperClass;
}
/**
* @param string $class
* @return FileSystemBuilder
*/
public function setStreamWrapper($class)
{
$this->wrapperClass = $class;
return $this;
}
/**
* @return array
*/
public function getTree()
{
return $this->tree;
}
/**
* @param array $tree
* @return FileSystemBuilder
*/
public function setTree($tree)
{
$this->tree = $tree;
return $this;
}
/**
* @return LoggerInterface
*/
protected function buildDefaultLogger()
{
return new PhpErrorLogger();
}
/**
* @return NodeFactoryInterface
*/
protected function buildDefaultNodeFactory()
{
return new NodeFactory();
}
/**
* @return NodeWalkerInterface
*/
protected function buildDefaultNodeWalker()
{
return new NodeWalker();
}
/**
* @return RegistryInterface
*/
protected function buildDefaultRegistry()
{
return FileSystemRegistry::getInstance();
}
/**
* @return string
*/
protected function buildDefaultStreamWrapper()
{
return 'Vfs\\Stream\\StreamWrapper';
}
}

View File

@ -0,0 +1,60 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs;
use Psr\Log\LoggerInterface;
use Vfs\Exception\RegisteredSchemeException;
use Vfs\Exception\UnregisteredSchemeException;
use Vfs\Node\Factory\NodeFactoryInterface;
use Vfs\Node\Walker\NodeWalkerInterface;
use Vfs\Node\NodeInterface;
interface FileSystemInterface
{
const SCHEME = 'vfs';
/**
* @param $path
* @return NodeInterface
*/
public function get($path);
/**
* @return LoggerInterface
*/
public function getLogger();
/**
* @return NodeFactoryInterface
*/
public function getNodeFactory();
/**
* @return NodeWalkerInterface
*/
public function getNodeWalker();
/**
* @return string
*/
public function getScheme();
/**
* @return boolean
* @throws RegisteredSchemeException If a mounted file system exists at scheme
*/
public function mount();
/**
* @return boolean
* @throws UnregisteredSchemeException If a mounted file system doesn't exist at scheme
*/
public function unmount();
}

View File

@ -0,0 +1,85 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs;
use Vfs\Exception\RegisteredSchemeException;
use Vfs\Exception\UnregisteredSchemeException;
class FileSystemRegistry implements RegistryInterface
{
protected static $instance;
protected $registered = [];
/**
* @param FileSystemRegistry[] $fss
*/
public function __construct(array $fss = [])
{
foreach ($fss as $name => $fs) {
$this->add($name, $fs);
}
}
/**
* @return FileSystemRegistry
*/
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
/**
* {@inheritdoc}
*/
public function add($scheme, FileSystemInterface $fs)
{
if ($this->has($scheme)) {
throw new RegisteredSchemeException($scheme);
}
$this->registered[$scheme] = $fs;
}
/**
* {@inheritdoc}
*/
public function get($scheme)
{
if (!$this->has($scheme)) {
throw new UnregisteredSchemeException($scheme);
}
return $this->registered[$scheme];
}
/**
* {@inheritdoc}
*/
public function has($scheme)
{
return isset($this->registered[$scheme]);
}
/**
* {@inheritdoc}
*/
public function remove($scheme)
{
if (!$this->has($scheme)) {
throw new UnregisteredSchemeException($scheme);
}
unset($this->registered[$scheme]);
}
}

View File

@ -0,0 +1,70 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Logger;
use Psr\Log\AbstractLogger;
use Psr\Log\LogLevel;
class PhpErrorLogger extends AbstractLogger
{
/**
* @param mixed $level
* @param string $message
* @param array $context
*/
public function log($level, $message, array $context = [])
{
switch ($level) {
case LogLevel::EMERGENCY:
case LogLevel::ALERT:
case LogLevel::CRITICAL:
case LogLevel::ERROR:
trigger_error($this->format($message, $context), E_USER_ERROR);
break;
case LogLevel::WARNING:
trigger_error($this->format($message, $context), E_USER_WARNING);
break;
case LogLevel::NOTICE:
case LogLevel::INFO:
case LogLevel::DEBUG:
trigger_error($this->format($message, $context), E_USER_NOTICE);
break;
}
}
/**
* @param string $message
* @param array $context
* @return string
*/
protected function format($message, array $context)
{
foreach ($context as $key => $value) {
$message = str_replace(sprintf('{%s}', $key), $value, $message);
}
return $message . $this->formatTrace(debug_backtrace(false));
}
/**
* @param array $backtrace
* @return string
*/
protected function formatTrace(array $backtrace)
{
$index = min((count($backtrace) + 1), 6);
$origin = $backtrace[$index];
$file = isset($origin['file']) ? $origin['file'] : 'unknown';
$line = isset($origin['line']) ? $origin['line'] : 0;
return sprintf(' in %s on line %d; triggered', $file, $line);
}
}

158
src/Node/Directory.php Normal file
View File

@ -0,0 +1,158 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node;
use ArrayIterator;
use DateTime;
use Vfs\Exception\ExistingNodeException;
use Vfs\Exception\MissingNodeException;
class Directory implements NodeContainerInterface
{
const DOT_SELF = '.';
const DOT_UP = '..';
protected $dateAccessed;
protected $dateCreated;
protected $dateModified;
protected $mode;
protected $nodes = [];
/**
* @param NodeInterface[] $nodes
*/
public function __construct(array $nodes = [])
{
$this->mode = self::TYPE_BLOCK | self::TYPE_DIR;
$this->dateAccessed = new DateTime();
$this->dateCreated = new DateTime();
$this->dateModified = new DateTime();
foreach ($nodes as $name => $node) {
$this->add($name, $node);
}
$this->set(self::DOT_SELF, $this);
}
/**
* {@inheritdoc}
*/
public function add($name, NodeInterface $node)
{
if ($this->has($name)) {
throw new ExistingNodeException($name, $this);
}
$this->set($name, $node);
}
/**
* {@inheritdoc}
*/
public function get($name)
{
if (!$this->has($name)) {
throw new MissingNodeException($name, $this);
}
return $this->nodes[$name];
}
/**
* {@inheritdoc}
*/
public function has($name)
{
return isset($this->nodes[$name]);
}
/**
* {@inheritdoc}
*/
public function remove($name)
{
if (!$this->has($name)) {
throw new MissingNodeException($name, $this);
}
unset($this->nodes[$name]);
}
/**
* @param string $name
* @param NodeInterface $node
*/
public function set($name, NodeInterface $node)
{
$this->nodes[$name] = $node;
if (self::DOT_UP !== $name && $node instanceof NodeContainerInterface) {
$node->set(self::DOT_UP, $this);
}
}
/**
* {@inheritdoc}
*/
public function getDateAccessed()
{
return $this->dateAccessed;
}
/**
* {@inheritdoc}
*/
public function getDateCreated()
{
return $this->dateCreated;
}
/**
* {@inheritdoc}
*/
public function getDateModified()
{
return $this->dateModified;
}
/**
* {@inheritdoc}
*/
public function getIterator()
{
return new ArrayIterator($this->nodes);
}
/**
* {@inheritdoc}
*/
public function getMode()
{
return $this->mode;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
$size = 0;
foreach ($this->nodes as $name => $node) {
if (!in_array($name, [self::DOT_SELF, self::DOT_UP])) {
$size += $node->getSize();
}
}
return $size;
}
}

View File

@ -0,0 +1,65 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node\Factory;
use LogicException;
use Vfs\Node\Directory;
use Vfs\Node\File;
use Vfs\Node\NodeContainerInterface;
use Vfs\Node\NodeInterface;
class NodeFactory implements NodeFactoryInterface
{
/**
* @param NodeInterface[] $children
* @return NodeContainerInterface
*/
public function buildDirectory(array $children = [])
{
return new Directory($children);
}
/**
* @param string $content
* @return NodeInterface
*/
public function buildFile($content = '')
{
return new File($content);
}
/**
* @param string $content
* @return NodeInterface
*/
public function buildLink($content = '')
{
throw new LogicException('Symlinks aren\'t supported yet.');
}
/**
* @param array $tree
* @return NodeContainerInterface
*/
public function buildTree(array $tree)
{
$nodes = [];
foreach ($tree as $name => $content) {
if (is_array($content)) {
$nodes[$name] = $this->buildTree($content);
} else {
$nodes[$name] = $this->buildFile($content);
}
}
return $this->buildDirectory($nodes);
}
}

View File

@ -0,0 +1,34 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node\Factory;
use Vfs\Node\NodeContainerInterface;
use Vfs\Node\NodeInterface;
interface NodeFactoryInterface
{
/**
* @param NodeInterface[] $children
* @return NodeContainerInterface
*/
public function buildDirectory(array $children = []);
/**
* @param string $content
* @return NodeInterface
*/
public function buildFile($content = '');
/**
* @param string $content
* @return NodeInterface
*/
public function buildLink($content = '');
}

98
src/Node/File.php Normal file
View File

@ -0,0 +1,98 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node;
use DateTime;
class File implements FileInterface
{
protected $dateAccessed;
protected $dateCreated;
protected $dateModified;
protected $content;
protected $mode;
/**
* @param string $content
*/
public function __construct($content = '')
{
$this->content = (string) $content;
$this->mode = self::TYPE_BLOCK | self::TYPE_FILE;
$this->dateAccessed = new DateTime();
$this->dateCreated = new DateTime();
$this->dateModified = new DateTime();
}
/**
* {@inheritdoc}
*/
public function getContent()
{
return $this->content;
}
/**
* {@inheritdoc}
*/
public function setContent($content)
{
$this->content = (string) $content;
}
/**
* {@inheritdoc}
*/
public function getDateAccessed()
{
return $this->dateAccessed;
}
/**
* {@inheritdoc}
*/
public function getDateCreated()
{
return $this->dateCreated;
}
/**
* {@inheritdoc}
*/
public function getDateModified()
{
return $this->dateModified;
}
/**
* {@inheritdoc}
*/
public function getMode()
{
return $this->mode;
}
/**
* {@inheritdoc}
*/
public function getSize()
{
return strlen($this->content);
}
/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->content;
}
}

View File

@ -0,0 +1,28 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node;
interface FileInterface extends NodeInterface
{
/**
* @return string
*/
public function getContent();
/**
* @param string $content
*/
public function setContent($content);
/**
* @return string The file content
*/
public function __toString();
}

View File

@ -0,0 +1,18 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node;
interface LinkInterface extends NodeInterface
{
/**
* @return NodeInterface
*/
public function getTarget();
}

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node;
use DateTime;
use IteratorAggregate;
use Vfs\Exception\ExistingNodeException;
use Vfs\Exception\MissingNodeException;
interface NodeContainerInterface extends NodeInterface, IteratorAggregate
{
/**
* @param string $name
* @param NodeInterface $node
* @throws ExistingNodeException If a node exists in container with name
*/
public function add($name, NodeInterface $node);
/**
* @param string $name
* @throws MissingNodeException If a node doesn't exist in container with name
*/
public function get($name);
/**
* @param string $name
* @return boolean
*/
public function has($name);
/**
* @param string $name
* @throws MissingNodeException If a node doesn't exist in container with name
*/
public function remove($name);
/**
* @param string $name
* @param NodeInterface $node
*/
public function set($name, NodeInterface $node);
}

View File

@ -0,0 +1,35 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node;
use DateTime;
interface NodeInterface extends StatInterface
{
/**
* @return DateTime
*/
public function getDateAccessed();
/**
* @return DateTime
*/
public function getDateCreated();
/**
* @return DateTime
*/
public function getDateModified();
/**
* @return integer
*/
public function getSize();
}

View File

@ -0,0 +1,49 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node;
/**
* @link http://www.gnu.org/software/libc/manual/html_node/Permission-Bits.html
*/
interface StatInterface
{
const S_IFMT = 0170000;
const S_IFLNK = 0120000;
const S_IFREG = 0100000;
const S_IFDIR = 0040000;
const S_IRUSR = 0000400;
const S_IWUSR = 0000200;
const S_IXUSR = 0000100;
const S_IRGRP = 0000040;
const S_IWGRP = 0000020;
const S_IXGRP = 0000010;
const S_IROTH = 0000004;
const S_IWOTH = 0000002;
const S_IXOTH = 0000001;
const GROUP_EXEC = self::S_IXGRP;
const GROUP_WRITE = self::S_IWGRP;
const GROUP_READ = self::S_IRGRP;
const OTHER_EXEC = self::S_IXOTH;
const OTHER_WRITE = self::S_IWOTH;
const OTHER_READ = self::S_IROTH;
const USER_EXEC = self::S_IXUSR;
const USER_READ = self::S_IRUSR;
const USER_WRITE = self::S_IWUSR;
const TYPE_BLOCK = self::S_IFMT;
const TYPE_FILE = self::S_IFREG;
const TYPE_DIR = self::S_IFDIR;
const TYPE_LINK = self::S_IFLNK;
/**
* @return integer
*/
public function getMode();
}

View File

@ -0,0 +1,75 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node\Walker;
use Vfs\Exception\InvalidNodeTypeException;
use Vfs\Node\NodeContainerInterface;
use Vfs\Node\NodeInterface;
class NodeWalker implements NodeWalkerInterface
{
protected $separator;
/**
* @param string $separator
*/
public function __construct($separator = DIRECTORY_SEPARATOR)
{
$this->separator = $separator;
}
/**
* @param NodeInterface $root
* @param string $path
* @return NodeInterface
*/
public function findNode(NodeInterface $root, $path)
{
$parts = $this->splitPath($path);
$node = $root;
return $this->walkPath($root, $path, function (NodeInterface $node, $name) {
if (!$node instanceof NodeContainerInterface || !$node->has($name)) {
return null;
}
return $node->get($name);
});
}
/**
* @param NodeInterface $root
* @param string $path
* @param callable $fn
* @return NodeInterface
*/
public function walkPath(NodeInterface $root, $path, callable $fn)
{
$parts = $this->splitPath($path);
$name = current($parts);
$node = $root;
while ($node && $name) {
$node = $fn($node, $name);
$name = next($parts);
}
return $node;
}
/**
* @param string $path
* @return string[]
*/
protected function splitPath($path)
{
return array_filter(explode($this->separator, $path));
}
}

View File

@ -0,0 +1,22 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Node\Walker;
use Vfs\Node\NodeInterface;
interface NodeWalkerInterface
{
/**
* @param NodeInterface $root
* @param string $path
* @return NodeInterface
*/
public function findNode(NodeInterface $root, $path);
}

43
src/RegistryInterface.php Normal file
View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs;
use Vfs\Exception\RegisteredSchemeException;
use Vfs\Exception\UnregisteredSchemeException;
interface RegistryInterface
{
/**
* @param string $scheme
* @param FileSystemInterface $fs
* @throws RegisteredSchemeException If a mounted file system exists at scheme
*/
public function add($scheme, FileSystemInterface $fs);
/**
* @param string $scheme
* @return FileSystemInterface
* @throws UnregisteredSchemeException If a mounted file system doesn't exist at scheme
*/
public function get($scheme);
/**
* @param string $scheme
* @return boolean
*/
public function has($scheme);
/**
* @param string $scheme
* @return FileSystemInterface
* @throws UnregisteredSchemeException If a mounted file system doesn't exist at scheme
*/
public function remove($scheme);
}

View File

@ -0,0 +1,117 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Stream;
use Vfs\FileSystemInterface;
use Vfs\Node\NodeInterface;
abstract class AbstractHandle implements HandleInterface
{
protected $fs;
protected $node;
protected $mode;
protected $modifier;
protected $path;
protected $scheme;
protected $url;
/**
* @param FileSystemInterface $fs
* @param string $url
* @param string $mode
*/
public function __construct(FileSystemInterface $fs, $url, $mode = null)
{
$this->fs = $fs;
$this->url = $url;
list($this->mode, $this->modifier) = $this->parseMode($mode);
list($this->scheme, $this->path) = $this->parseUrl($url);
}
/**
* @return NodeInterface
*/
public function getNode()
{
return $this->node;
}
/**
* @param string $origin
* @param string $target
* @return NodeInterface
*/
public function rename($target)
{
$this->node = $this->findNode($this->path);
$parent = $this->fs->get(dirname($this->path));
list($_, $targetPath) = $this->parseUrl($target);
$targetParent = $this->fs->get(dirname($targetPath));
if (!$this->node || !$targetPath) {
$this->node = null;
$this->warn('rename({origin},{target}): No such file or directory', [
'origin' => $this->url,
'target' => $target
]);
} else {
$parent->remove(basename($this->path));
$targetParent->add(basename($targetPath), $this->node);
}
return $this->node;
}
/**
* @return NodeInterface
*/
protected function findNode()
{
return $this->fs->get($this->path);
}
/**
* @param string $mode
* @return string[]
*/
protected function parseMode($mode)
{
return [substr($mode, 0, 1), substr($mode, 1, 2)];
}
/**
* @param string $url
* @return string[]
*/
protected function parseUrl($url)
{
$parts = parse_url($url);
$path = null;
$scheme = null;
if (isset($parts['scheme'])) {
$path = substr($url, strlen($parts['scheme'] . ':/'));
$scheme = $parts['scheme'];
}
return [$scheme, $path];
}
/**
* @param string $message
* @param array $context
*/
protected function warn($message, array $context = [])
{
$this->fs->getLogger()->warning($message, $context);
}
}

View File

@ -0,0 +1,133 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Stream;
use Vfs\Exception\UnopenedHandleException;
class DirectoryHandle extends AbstractHandle
{
/**
* @return boolean
*/
public function canRead()
{
return true;
}
/**
* @param integer $perms
* @param boolean $recursive
* @return NodeInterface
*/
public function create($perms, $recursive = false)
{
$this->node = $this->findNode();
if (!$this->node) {
$parentPath = dirname($this->path);
$parent = $this->fs->get($parentPath);
if (!$parent && $this->checkBit($options, STREAM_MKDIR_RECURSIVE)) {
$parent = $this->buildNodesRecursive($this->fs->get('/'), $this->path);
}
if ($parent) {
$this->node = $this->fs->getNodeFactory()->buildDirectory();
$parent->add(basename($this->path), $this->node);
} else {
$this->warn('mkdir({url}): No such file or directory', [
'url' => $this->url
]);
}
} else {
$this->warn('mkdir({url}): File exists', ['url' => $this->url]);
$this->node = null;
}
return $this->node;
}
/**
* @return boolean
*/
public function destroy()
{
$this->node = $this->findNode();
if (!$this->node) {
return (boolean) $this->warn('rmdir({url}): No such file or directory', [
'url' => $this->url
]);
} elseif (!$this->node instanceof NodeContainerInterface) {
return (boolean) $this->warn('rmdir({url}): Not a directory', [
'url' => $this->url
]);
}
$parent = $fs->get(dirname($this->path));
$parent->remove(basename($this->path));
return true;
}
/**
* @return NodeInterface
*/
public function open()
{
return $this->node = $this->findNode();
}
/**
* @param integer $offset
* @return string
*/
public function read($offset = 0)
{
if (!$this->node) {
throw new UnopenedHandleException($this, $this->url);
}
$i = 0;
foreach ($this->node as $name => $node) {
if ($i++ === $offset) {
return $name;
}
}
}
/**
* @param string $content
* @return boolean
*/
public function write($content)
{
return false;
}
/**
* @param NodeContainerInterface $root
* @param string $path
* @return NodeContainerInterface
*/
protected function buildNodesRecursive(NodeContainerInterface $root, $path)
{
$factory = $this->fs->getNodeFactory();
$walker = $this->fs->getNodeWalker();
return $walker->walkPath($root, $this->path, function ($node, $name) use ($factory) {
if (!$node->has($name)) {
$node->add($name, $factory->buildDirectory());
}
return $node->get($name);
});
}
}

130
src/Stream/FileHandle.php Normal file
View File

@ -0,0 +1,130 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Stream;
use Vfs\Exception\UnopenedHandleException;
use Vfs\Node\FileInterface;
use Vfs\Node\NodeContainerInterface;
class FileHandle extends AbstractHandle
{
/**
* @return boolean
*/
public function canRead()
{
return self::MODE_READ === $this->mode || self::MOD_EXTENDED === $this->modifier;
}
/**
* @param integer $perms
* @return NodeInterface
*/
public function create($perms)
{
return $this->node = $this->findOrBuildNode();
}
/**
* @return boolean
*/
public function destroy()
{
$this->node = $this->findNode();
if (!$this->node) {
return (boolean) $this->warn('unlink({url}): No such file or directory', [
'url' => $this->url
]);
} elseif ($this->node instanceof NodeContainerInterface) {
return (boolean) $this->warn('unlink({url}): Is a directory', [
'url' => $this->url
]);
}
$parent = $this->fs->get(dirname($this->path));
$parent->remove(basename($this->path));
return true;
}
/**
* @return NodeInterface
*/
public function open()
{
$this->node = $this->findOrBuildNode();
if ($this->node instanceof FileInterface && self::MODE_TRUNCATE === $this->mode) {
$this->node->setContent('');
}
return $this->node;
}
/**
* @param integer $offset
* @param integer $length
* @return string
*/
public function read($offset = 0, $length = null)
{
if (!$this->node) {
throw new UnopenedHandleException($this, $this->url);
}
if (null !== $length) {
return substr($this->node->getContent(), $offset, $length);
}
return substr($this->node->getContent(), $offset);
}
/**
* @param string $content
* @return boolean
*/
public function write($content)
{
if (!$this->node) {
throw new UnopenedHandleException($this, $this->url);
}
$this->node->setContent($content);
return true;
}
/**
* @return NodeInterface
*/
protected function findOrBuildNode()
{
$this->node = $this->fs->get($this->path);
if ($this->node && self::MODE_WRITE === $this->mode) {
$this->node = null;
} elseif (!$this->node && in_array($this->mode, [
self::MODE_APPEND,
self::MODE_TRUNCATE,
self::MODE_WRITE,
self::MODE_WRITE_NEW
])) {
$dir = $this->fs->get(dirname($this->path));
if ($dir && $dir instanceof NodeContainerInterface) {
$this->node = $this->fs->getNodeFactory()->buildFile();
$dir->set(basename($this->path), $this->node);
}
}
return $this->node;
}
}

View File

@ -0,0 +1,69 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Stream;
use Vfs\Node\NodeInterface;
interface HandleInterface
{
const MODE_APPEND = 'a';
const MODE_READ = 'r';
const MODE_TRUNCATE = 'w';
const MODE_WRITE = 'x';
const MODE_WRITE_NEW = 'c';
const MOD_BINARY = 'b';
const MOD_EXTENDED = '+';
const MOD_TEXT = 't';
/**
* @return boolean
*/
public function canRead();
/**
* @param integer $perms
* @return NodeInterface
*/
public function create($perms);
/**
* @return boolean
*/
public function destroy();
/**
* @return NodeInterface
*/
public function getNode();
/**
* @return NodeInterface
*/
public function open();
/**
* @param string $origin
* @param string $target
* @return NodeInterface
*/
public function rename($target);
/**
* @param integer $offset
* @return string
*/
public function read($offset = 0);
/**
* @param string $content
* @return boolean
*/
public function write($content);
}

View File

@ -0,0 +1,345 @@
<?php
/*
* This file is part of VFS
*
* Copyright (c) 2014 Andrew Lawson <http://adlawson.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Vfs\Stream;
use Vfs\Node\LinkInterface;
use Vfs\FileSystemRegistry;
class StreamWrapper
{
protected $cursor = 0;
protected $handle;
protected $mode;
protected $url;
/**
* @return boolean
*/
public function dir_closedir()
{
$this->stream_close();
return true;
}
/**
* @param string $url
* @param integer $options
* @return boolean
*/
public function dir_opendir($url, $options)
{
$this->handle = $this->buildDirectoryHandle($url);
return (boolean) $this->handle->open();
}
/**
* @return string
*/
public function dir_readdir()
{
return $this->handle->read($this->cursor++);
}
/**
* @return boolean
*/
public function dir_rewinddir()
{
$this->cursor = 0;
return true;
}
/**
* @param string $url
* @param integer $perms
* @param integer $flags
* @return boolean
*/
public function mkdir($url, $perms, $flags)
{
$this->handle = $this->buildDirectoryHandle($url);
$recursive = $this->checkBit($flags, STREAM_MKDIR_RECURSIVE);
return (boolean) $this->handle->create($perms, $recursive);
}
/**
* @param string $origin
* @param string $target
* @return boolean
*/
public function rename($origin, $target)
{
$this->handle = $this->buildFileHandle($origin);
return (boolean) $this->handle->rename($target);
}
/**
* @param string $url
* @param integer $options
* @return boolean
*/
public function rmdir($url, $options)
{
$this->handle = $this->buildDirectoryHandle($url);
return (boolean) $this->handle->destroy();
}
/**
* @param integer $cast
* @return resource|boolean
*/
public function stream_cast($cast)
{
return false; // No underlying resource
}
/**
*/
public function stream_close()
{
$this->cursor = 0;
$this->handle = null;
}
/**
* @return boolean
*/
public function stream_eof()
{
$node = $this->handle->getNode();
return !$node || $node->getSize() <= $this->cursor;
}
/**
* @return boolean
*/
public function stream_flush()
{
return true; // Non-buffered writing
}
/**
* @param string $url
* @param string $mode
* @param integer $options
* @param string $openedPath
* @return boolean
*/
public function stream_open($url, $mode, $options, &$openedPath)
{
$this->cursor = 0;
$this->handle = $this->buildFileHandle($url, $mode);
$node = $this->handle->open();
if ($node && $this->checkBit($options, STREAM_USE_PATH)) {
$openedPath = $url;
}
if (isset($mode[0]) && $node && HandleInterface::MODE_APPEND === $mode[0]) {
$this->cursor = $node->getSize();
}
return (boolean) $node;
}
/**
* @param integer $length
* @return string|boolean
*/
public function stream_read($length)
{
if ($this->handle->canRead()) {
$out = $this->handle->read($this->cursor, $length);
$this->cursor += strlen($out);
return $out;
}
return false;
}
/**
* @param integer $offset
* @param integer $whence
* @return boolean
*/
public function stream_seek($offset, $whence = SEEK_SET)
{
switch ($whence) {
case SEEK_SET:
$this->cursor = (integer) $offset;
break;
case SEEK_CUR:
$this->cursor += (integer) $offset;
break;
case SEEK_END:
$length = strlen($this->wrapper->read());
$this->cursor = $length + (integer) $offset;
break;
default:
return false;
}
return true;
}
/**
* @param integer $option
* @param integer $arg1
* @param integer $arg2
* @return boolean
*/
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
/**
* @param boolean $followLink
* @return array|boolean
*/
public function stream_stat($followLink = false)
{
$node = $this->handle->getNode();
if (!$node) {
return false;
} elseif ($followLink && $node instanceof LinkInterface) {
$node = $node->getTarget();
}
$stat = [
'dev' => 0,
'ino' => 0,
'mode' => $node->getMode(),
'nlink' => 0,
'uid' => 0,
'gid' => 0,
'rdev' => 0,
'size' => $node->getSize(),
'atime' => $node->getDateAccessed()->getTimestamp(),
'mtime' => $node->getDateModified()->getTimestamp(),
'ctime' => $node->getDateCreated()->getTimestamp(),
'blksize' => -1,
'blocks' => -1
];
return array_values($stat) + $stat;
}
/**
* @return integer
*/
public function stream_tell()
{
return $this->cursor;
}
/**
* @param integer $size
* @return boolean
*/
public function stream_truncate($size)
{
if ($size > $current) {
$this->handle->write($this->handle->read() . str_repeat("\0", $size - $current));
} else {
$this->handle->write($this->handle->read(0, $size));
}
return true;
}
/**
* @param string $data
* @return integer
*/
public function stream_write($data)
{
$content = substr($this->handle->read(0, $this->cursor), 0, $this->cursor) . $data;
$written = $this->handle->write($content);
$this->cursor = strlen($content);
return $written ? strlen($data) : 0;
}
/**
* @param string $url
* @return boolean
*/
public function unlink($url)
{
$this->handle = $this->buildFileHandle($url);
return (boolean) $this->handle->destroy();
}
/**
* @param string $url
* @param integer $flags
* @return array
*/
public function url_stat($url, $flags)
{
$this->handle = $this->buildFileHandle($url);
$this->handle->open();
return $this->stream_stat(!$this->checkBit($flags, STREAM_URL_STAT_LINK));
}
/**
* @param string $url
* @return DirectoryHandle
*/
protected function buildDirectoryHandle($url)
{
return new DirectoryHandle($this->getFileSystemForUrl($url), $url);
}
/**
* @param string $url
* @param string $mode
* @return FileHandle
*/
protected function buildFileHandle($url, $mode = null)
{
return new FileHandle($this->getFileSystemForUrl($url), $url, $mode);
}
/**
* @param integer $mask
* @param integer $bit
* @return boolean
*/
protected function checkBit($mask, $bit)
{
return ($mask & $bit) === $bit;
}
/**
* @param string $url
* @return FileSystemInterface
*/
protected function getFileSystemForUrl($url)
{
$parts = parse_url($url);
$scheme = isset($parts['scheme']) ? $parts['scheme'] : null;
return FileSystemRegistry::getInstance()->get($scheme);
}
}