274 lines
6.4 KiB
PHP
274 lines
6.4 KiB
PHP
<?php
|
|
|
|
|
|
namespace Drupal\xautoload\Tests\Filesystem;
|
|
|
|
|
|
use Drupal\xautoload\Util;
|
|
|
|
class VirtualFilesystem {
|
|
|
|
/**
|
|
* @var VirtualFilesystem[]
|
|
*/
|
|
protected static $instances = array();
|
|
|
|
/**
|
|
* Called from generated PHP included over a stream wrapper.
|
|
*
|
|
* @param string $instance_key
|
|
* @param string $file
|
|
*/
|
|
static function reportFileIncluded($instance_key, $file) {
|
|
self::$instances[$instance_key]->reportedOperations[] = $file . ' - include';
|
|
}
|
|
|
|
/**
|
|
* @var string
|
|
*/
|
|
protected $instanceKey;
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
protected $knownPaths = array();
|
|
|
|
/**
|
|
* @var string[]
|
|
*/
|
|
protected $reportedOperations = array();
|
|
|
|
const NOTHING = FALSE;
|
|
const DIR = '(dir)';
|
|
const FILE = '(file)';
|
|
|
|
function __construct() {
|
|
$this->instanceKey = Util::randomString();
|
|
self::$instances[$this->instanceKey] = $this;
|
|
}
|
|
|
|
/**
|
|
* @return array[]
|
|
*/
|
|
function getReportedOperations() {
|
|
return $this->reportedOperations;
|
|
}
|
|
|
|
/**
|
|
* Delete all reported operations and start fresh.
|
|
*/
|
|
function resetReportedOperations() {
|
|
$this->reportedOperations = array();
|
|
}
|
|
|
|
/**
|
|
* @param string $file
|
|
* @param string $class
|
|
* @throws \Exception
|
|
*/
|
|
function addClass($file, $class) {
|
|
$this->addKnownFile($file);
|
|
if (self::FILE !== ($existing = $this->knownPaths[$file])) {
|
|
throw new \Exception("A non-empty file already exists at '$file'. Cannot overwrite with class '$class'.");
|
|
}
|
|
$this->knownPaths[$file] = $class;
|
|
}
|
|
|
|
/**
|
|
* @param string $file
|
|
* @param string $php
|
|
* File contents starting with '<?php'.
|
|
* @param bool $overwrite
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
function addPhpFile($file, $php, $overwrite = FALSE) {
|
|
$this->addKnownFile($file);
|
|
if (!$overwrite && self::FILE !== ($existing = $this->knownPaths[$file])) {
|
|
throw new \Exception("A non-empty file already exists at '$file'. Cannot overwrite with PHP code.");
|
|
}
|
|
if (0 !== strpos($php, '<?php')) {
|
|
throw new \Exception("PHP files must begin with '<?php'.");
|
|
}
|
|
$this->knownPaths[$file] = $php;
|
|
}
|
|
|
|
/**
|
|
* @param string[] $files
|
|
*/
|
|
function addKnownFiles(array $files) {
|
|
foreach ($files as $file) {
|
|
$this->addKnownFile($file);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $file
|
|
*
|
|
* @throws \Exception
|
|
*/
|
|
function addKnownFile($file) {
|
|
if (!isset($this->knownPaths[$file])) {
|
|
$this->knownPaths[$file] = self::FILE;
|
|
$this->addKnownDir(dirname($file));
|
|
}
|
|
elseif (self::DIR === $this->knownPaths[$file]) {
|
|
throw new \Exception("A directory already exists at '$file', cannot overwrite with a file.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $dir
|
|
*/
|
|
function addKnownDir($dir) {
|
|
if (FALSE === strpos($dir, '://')) {
|
|
return;
|
|
}
|
|
if (!isset($this->knownPaths[$dir])) {
|
|
// Need to set parents first.
|
|
$this->addKnownDir(dirname($dir));
|
|
}
|
|
$this->knownPaths[$dir] = self::DIR;
|
|
}
|
|
|
|
/**
|
|
* @param string $path
|
|
* @return string|bool
|
|
* One of self::NOTHING, self::DIR, self::FILE, or a class name for a class
|
|
* that is supposed to be defined in the file.
|
|
*/
|
|
function resolvePath($path) {
|
|
if (isset($this->knownPaths[$path])) {
|
|
return $this->knownPaths[$path];
|
|
}
|
|
else {
|
|
return self::NOTHING;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param string $dir
|
|
* @return array|bool
|
|
*/
|
|
function getDirContents($dir) {
|
|
if (empty($this->knownPaths[$dir]) || self::DIR !== $this->knownPaths[$dir]) {
|
|
return FALSE;
|
|
}
|
|
$pos = strlen($dir . '/');
|
|
$contents = array('.', '..');
|
|
foreach ($this->knownPaths as $path => $type) {
|
|
if ($dir . '/' !== substr($path, 0, $pos)) {
|
|
continue;
|
|
}
|
|
$name = substr($path, $pos);
|
|
if (FALSE !== strpos($name, '/')) {
|
|
// This is a deeper subdirectory.
|
|
continue;
|
|
}
|
|
if ('' === $name) {
|
|
continue;
|
|
}
|
|
$contents[] = $name;
|
|
}
|
|
return $contents;
|
|
}
|
|
|
|
/**
|
|
* @param string $path
|
|
* @param bool $report
|
|
*
|
|
* @return array
|
|
*/
|
|
function getStat($path, $report = TRUE) {
|
|
if ($report) {
|
|
$this->reportedOperations[] = $path . ' - stat';
|
|
}
|
|
if (!isset($this->knownPaths[$path])) {
|
|
// File does not exist.
|
|
return FALSE;
|
|
}
|
|
elseif (self::DIR === $this->knownPaths[$path]) {
|
|
return stat(__DIR__);
|
|
}
|
|
else {
|
|
// Create a tmp file with the contents and get its stats.
|
|
$contents = $this->getFileContents($path);
|
|
$resource = tmpfile();
|
|
fwrite($resource, $contents);
|
|
$stat = fstat($resource);
|
|
fclose($resource);
|
|
return $stat;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param $path
|
|
* The file path.
|
|
*
|
|
* @return string
|
|
* The file contents.
|
|
*
|
|
* @throws \Exception
|
|
* Exception thrown if there is no file at $path.
|
|
*/
|
|
function getFileContents($path) {
|
|
if (!isset($this->knownPaths[$path])) {
|
|
// File does not exist.
|
|
throw new \Exception("Assumed file '$path' does not exist.");
|
|
}
|
|
elseif (self::DIR === $this->knownPaths[$path]) {
|
|
throw new \Exception("Assumed file '$path' is a directory.");
|
|
}
|
|
|
|
$instance_key_export = var_export($this->instanceKey, TRUE);
|
|
$path_export = var_export($path, TRUE);
|
|
if (self::FILE === $this->knownPaths[$path]) {
|
|
// Empty PHP file..
|
|
return <<<EOT
|
|
<?php
|
|
Drupal\\xautoload\\Tests\\Filesystem\\VirtualFilesystem::reportFileIncluded($instance_key_export, $path_export);
|
|
|
|
EOT;
|
|
}
|
|
|
|
if (0 === strpos($this->knownPaths[$path], '<?php')) {
|
|
// PHP file.
|
|
$php = substr($this->knownPaths[$path], 5);
|
|
return <<<EOT
|
|
<?php
|
|
Drupal\\xautoload\\Tests\\Filesystem\\VirtualFilesystem::reportFileIncluded($instance_key_export, $path_export);
|
|
$php
|
|
EOT;
|
|
}
|
|
|
|
if (preg_match('#\s#', $this->knownPaths[$path])) {
|
|
// File with arbitrary contents.
|
|
return $this->knownPaths[$path];
|
|
}
|
|
|
|
// PHP file with class definition.
|
|
$class = $this->knownPaths[$path];
|
|
|
|
if (FALSE === ($pos = strrpos($class, '\\'))) {
|
|
// Class without namespace.
|
|
return <<<EOT
|
|
<?php
|
|
Drupal\\xautoload\\Tests\\Filesystem\\VirtualFilesystem::reportFileIncluded($instance_key_export, $path_export);
|
|
class $class {}
|
|
|
|
EOT;
|
|
}
|
|
|
|
// Class without namespace.
|
|
$namespace = substr($class, 0, $pos);
|
|
$classname = substr($class, $pos + 1);
|
|
return <<<EOT
|
|
<?php
|
|
namespace $namespace;
|
|
\\Drupal\\xautoload\\Tests\\Filesystem\\VirtualFilesystem::reportFileIncluded($instance_key_export, $path_export);
|
|
class $classname {}
|
|
|
|
EOT;
|
|
}
|
|
}
|