Start the actual Slim application with some Database connections.

This commit is contained in:
Dave Smith-Hayes 2024-05-13 22:06:01 -04:00
parent 03a4040075
commit f5d5afa3c2
2500 changed files with 228308 additions and 0 deletions

28
code/composer.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "dsmithhayes/slovocast",
"description": "The Slovocast web application",
"type": "project",
"require": {
"slim/slim": "^4.13",
"slim/twig-view": "^3.4",
"slim/psr7": "^1.6",
"php-di/php-di": "^7.0",
"league/flysystem": "^3.27",
"league/config": "^1.2",
"monolog/monolog": "^3.6"
},
"require-dev": {
"phpunit/phpunit": "^11.1"
},
"authors": [
{
"name": "Dave Smith-Hayes",
"email": "me@davesmithhayes.com"
}
],
"autoload": {
"psr-4": {
"Slovocast\\": "src/"
}
}
}

3647
code/composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

6
code/public/index.php Normal file
View File

@ -0,0 +1,6 @@
<?php
require_once '../vendor/autoload.php';
$app = \Slovocast\Bootstrap::init();
$app->run();

85
code/src/Bootstrap.php Normal file
View File

@ -0,0 +1,85 @@
<?php
namespace Slovocast;
use Slim\App;
use Slim\Factory\AppFactory;
use League\Config\Configuration;
use DI\Container;
use DI\ContainerBuilder;
class Bootstrap
{
/**
* Pulls out all the configuration schemas and configuration values.
*
* @return Configuration
*/
protected static function initConfig(): Configuration
{
$config = new Configuration();
// set all configuration details
return $config;
}
/**
* Set up the Container with all of its definitions, as well as
* initialization of configuration.
*
* @return Container
*/
protected static function initContainer(): Container
{
$containerBuilder = new ContainerBuilder();
$config = self::initConfig();
$containerBuilder->addDefinitions([
'conifg' => $config,
// more DI stuff
]);
return $containerBuilder->build();
}
/**
* Tasking the instaniated Application, sets up all the routes for the
* application.
*
* @param App $app
* @return void
*/
protected static function establishRoutes(App $app): void
{
$app->get('/', function ($req, $res) {
$res->getBody()->write(json_encode([ 'message' => "hello!" ]));
return $res->withHeader("Content-Type", "application/json");
});
}
/**
* Taking an instantiated application, sets up all the middleware required
* for the application to run.
*
* @param App $app
*/
protected static function establishMiddleware(App $app): void
{
$app->addErrorMiddleware(true, true, true);
}
/**
* Instantiates the application.
*
* @return App
*/
public static function init(): App
{
$container = self::initContainer();
$app = AppFactory::createFromContainer($container);
self::establishMiddleware($app);
self::establishRoutes($app);
return $app;
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace Slovocast\Domain\Model;
class Channel
{
public function __construct(
private string $name,
) {}
}

View File

@ -0,0 +1,72 @@
<?php
namespace Slovocast\Domain\Model;
class User
{
public function __construct(
private readonly ?int $id,
private string $email,
private string $password,
private string $name
) {
$this->email = $email;
$this->password = $password;
$this->name;
}
public function getEmail(): string
{
return $this->email;
}
public function getPassword(): string
{
return $this->password;
}
public function getName(): string
{
return $this->name;
}
/**
* If the `id` property exists, we can assume this entity already exists.
*
* @return bool
*/
public function isNew(): bool
{
return (bool) $this->id;
}
/**
* @param string[] $props Properties of the User model
* @return User
*/
public static function fromArray(array $props): User
{
return new self(
$props['id'] ?? null,
$props['email'],
$props['password'],
$props['name']
);
}
/**
* Generates a new instance of a User model
*
* @param string[] $props Properties for the new User model
* @return User
*/
public static function newInstance(array $props): User
{
return new self(
null,
$props['email'],
$props['password'],
$props['name']
);
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace Slovocast\Infrastructure;
use League\Config\Configuration;
use Nette\Schema\Expect;
use PDO;
/**
* Represents an active connection to a database
*/
class DatabaseConnection
{
private PDO $pdo;
public function __construct(Configuration $config)
{
$this->pdo = new PDO();
}
public function getConnection(): PDO
{
return $this->pdo;
}
public static function getConfigSchema(): Configuration
{
return new Configuration([
'database' => Expect::structure([
'driver' => Expect::anyOf('mysql', 'sqlite')->required(),
'host' => Expect::string()->default('localhost'),
'post' => Expect::int()->min(1)->max(65535),
'database' => Expect::string()->required(),
'username' => Expect::string()->required(),
'password' => Expect::string()->nullable()
])
]);
}
}

25
code/vendor/autoload.php vendored Normal file
View File

@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit0187bc48337726eafc963b41d1b65200::getLoader();

119
code/vendor/bin/php-parse vendored Executable file
View File

@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
}
}
return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';

122
code/vendor/bin/phpunit vendored Executable file
View File

@ -0,0 +1,122 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = 'phpvfscomposer://'.$this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
}
}
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';

579
code/vendor/composer/ClassLoader.php vendored Normal file
View File

@ -0,0 +1,579 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer\Autoload;
/**
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
*
* $loader = new \Composer\Autoload\ClassLoader();
*
* // register classes with namespaces
* $loader->add('Symfony\Component', __DIR__.'/component');
* $loader->add('Symfony', __DIR__.'/framework');
*
* // activate the autoloader
* $loader->register();
*
* // to enable searching the include path (eg. for PEAR packages)
* $loader->setUseIncludePath(true);
*
* In this example, if you try to use a class in the Symfony\Component
* namespace or one of its children (Symfony\Component\Console for instance),
* the autoloader will first look for the class under the component/
* directory, and it will then fallback to the framework/ directory if not
* found before giving up.
*
* This class is loosely based on the Symfony UniversalClassLoader.
*
* @author Fabien Potencier <fabien@symfony.com>
* @author Jordi Boggiano <j.boggiano@seld.be>
* @see https://www.php-fig.org/psr/psr-0/
* @see https://www.php-fig.org/psr/psr-4/
*/
class ClassLoader
{
/** @var \Closure(string):void */
private static $includeFile;
/** @var string|null */
private $vendorDir;
// PSR-4
/**
* @var array<string, array<string, int>>
*/
private $prefixLengthsPsr4 = array();
/**
* @var array<string, list<string>>
*/
private $prefixDirsPsr4 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr4 = array();
// PSR-0
/**
* List of PSR-0 prefixes
*
* Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
*
* @var array<string, array<string, list<string>>>
*/
private $prefixesPsr0 = array();
/**
* @var list<string>
*/
private $fallbackDirsPsr0 = array();
/** @var bool */
private $useIncludePath = false;
/**
* @var array<string, string>
*/
private $classMap = array();
/** @var bool */
private $classMapAuthoritative = false;
/**
* @var array<string, bool>
*/
private $missingClasses = array();
/** @var string|null */
private $apcuPrefix;
/**
* @var array<string, self>
*/
private static $registeredLoaders = array();
/**
* @param string|null $vendorDir
*/
public function __construct($vendorDir = null)
{
$this->vendorDir = $vendorDir;
self::initializeIncludeClosure();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixes()
{
if (!empty($this->prefixesPsr0)) {
return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
}
return array();
}
/**
* @return array<string, list<string>>
*/
public function getPrefixesPsr4()
{
return $this->prefixDirsPsr4;
}
/**
* @return list<string>
*/
public function getFallbackDirs()
{
return $this->fallbackDirsPsr0;
}
/**
* @return list<string>
*/
public function getFallbackDirsPsr4()
{
return $this->fallbackDirsPsr4;
}
/**
* @return array<string, string> Array of classname => path
*/
public function getClassMap()
{
return $this->classMap;
}
/**
* @param array<string, string> $classMap Class to filename map
*
* @return void
*/
public function addClassMap(array $classMap)
{
if ($this->classMap) {
$this->classMap = array_merge($this->classMap, $classMap);
} else {
$this->classMap = $classMap;
}
}
/**
* Registers a set of PSR-0 directories for a given prefix, either
* appending or prepending to the ones previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 root directories
* @param bool $prepend Whether to prepend the directories
*
* @return void
*/
public function add($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
if ($prepend) {
$this->fallbackDirsPsr0 = array_merge(
$paths,
$this->fallbackDirsPsr0
);
} else {
$this->fallbackDirsPsr0 = array_merge(
$this->fallbackDirsPsr0,
$paths
);
}
return;
}
$first = $prefix[0];
if (!isset($this->prefixesPsr0[$first][$prefix])) {
$this->prefixesPsr0[$first][$prefix] = $paths;
return;
}
if ($prepend) {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$paths,
$this->prefixesPsr0[$first][$prefix]
);
} else {
$this->prefixesPsr0[$first][$prefix] = array_merge(
$this->prefixesPsr0[$first][$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-4 directories for a given namespace, either
* appending or prepending to the ones previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
* @param bool $prepend Whether to prepend the directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function addPsr4($prefix, $paths, $prepend = false)
{
$paths = (array) $paths;
if (!$prefix) {
// Register directories for the root namespace.
if ($prepend) {
$this->fallbackDirsPsr4 = array_merge(
$paths,
$this->fallbackDirsPsr4
);
} else {
$this->fallbackDirsPsr4 = array_merge(
$this->fallbackDirsPsr4,
$paths
);
}
} elseif (!isset($this->prefixDirsPsr4[$prefix])) {
// Register directories for a new namespace.
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = $paths;
} elseif ($prepend) {
// Prepend directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$paths,
$this->prefixDirsPsr4[$prefix]
);
} else {
// Append directories for an already registered namespace.
$this->prefixDirsPsr4[$prefix] = array_merge(
$this->prefixDirsPsr4[$prefix],
$paths
);
}
}
/**
* Registers a set of PSR-0 directories for a given prefix,
* replacing any others previously set for this prefix.
*
* @param string $prefix The prefix
* @param list<string>|string $paths The PSR-0 base directories
*
* @return void
*/
public function set($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr0 = (array) $paths;
} else {
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
}
}
/**
* Registers a set of PSR-4 directories for a given namespace,
* replacing any others previously set for this namespace.
*
* @param string $prefix The prefix/namespace, with trailing '\\'
* @param list<string>|string $paths The PSR-4 base directories
*
* @throws \InvalidArgumentException
*
* @return void
*/
public function setPsr4($prefix, $paths)
{
if (!$prefix) {
$this->fallbackDirsPsr4 = (array) $paths;
} else {
$length = strlen($prefix);
if ('\\' !== $prefix[$length - 1]) {
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
}
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
$this->prefixDirsPsr4[$prefix] = (array) $paths;
}
}
/**
* Turns on searching the include path for class files.
*
* @param bool $useIncludePath
*
* @return void
*/
public function setUseIncludePath($useIncludePath)
{
$this->useIncludePath = $useIncludePath;
}
/**
* Can be used to check if the autoloader uses the include path to check
* for classes.
*
* @return bool
*/
public function getUseIncludePath()
{
return $this->useIncludePath;
}
/**
* Turns off searching the prefix and fallback directories for classes
* that have not been registered with the class map.
*
* @param bool $classMapAuthoritative
*
* @return void
*/
public function setClassMapAuthoritative($classMapAuthoritative)
{
$this->classMapAuthoritative = $classMapAuthoritative;
}
/**
* Should class lookup fail if not found in the current class map?
*
* @return bool
*/
public function isClassMapAuthoritative()
{
return $this->classMapAuthoritative;
}
/**
* APCu prefix to use to cache found/not-found classes, if the extension is enabled.
*
* @param string|null $apcuPrefix
*
* @return void
*/
public function setApcuPrefix($apcuPrefix)
{
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
}
/**
* The APCu prefix in use, or null if APCu caching is not enabled.
*
* @return string|null
*/
public function getApcuPrefix()
{
return $this->apcuPrefix;
}
/**
* Registers this instance as an autoloader.
*
* @param bool $prepend Whether to prepend the autoloader or not
*
* @return void
*/
public function register($prepend = false)
{
spl_autoload_register(array($this, 'loadClass'), true, $prepend);
if (null === $this->vendorDir) {
return;
}
if ($prepend) {
self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
} else {
unset(self::$registeredLoaders[$this->vendorDir]);
self::$registeredLoaders[$this->vendorDir] = $this;
}
}
/**
* Unregisters this instance as an autoloader.
*
* @return void
*/
public function unregister()
{
spl_autoload_unregister(array($this, 'loadClass'));
if (null !== $this->vendorDir) {
unset(self::$registeredLoaders[$this->vendorDir]);
}
}
/**
* Loads the given class or interface.
*
* @param string $class The name of the class
* @return true|null True if loaded, null otherwise
*/
public function loadClass($class)
{
if ($file = $this->findFile($class)) {
$includeFile = self::$includeFile;
$includeFile($file);
return true;
}
return null;
}
/**
* Finds the path to the file where the class is defined.
*
* @param string $class The name of the class
*
* @return string|false The path if found, false otherwise
*/
public function findFile($class)
{
// class map lookup
if (isset($this->classMap[$class])) {
return $this->classMap[$class];
}
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
return false;
}
if (null !== $this->apcuPrefix) {
$file = apcu_fetch($this->apcuPrefix.$class, $hit);
if ($hit) {
return $file;
}
}
$file = $this->findFileWithExtension($class, '.php');
// Search for Hack files if we are running on HHVM
if (false === $file && defined('HHVM_VERSION')) {
$file = $this->findFileWithExtension($class, '.hh');
}
if (null !== $this->apcuPrefix) {
apcu_add($this->apcuPrefix.$class, $file);
}
if (false === $file) {
// Remember that this class does not exist.
$this->missingClasses[$class] = true;
}
return $file;
}
/**
* Returns the currently registered loaders keyed by their corresponding vendor directories.
*
* @return array<string, self>
*/
public static function getRegisteredLoaders()
{
return self::$registeredLoaders;
}
/**
* @param string $class
* @param string $ext
* @return string|false
*/
private function findFileWithExtension($class, $ext)
{
// PSR-4 lookup
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
$first = $class[0];
if (isset($this->prefixLengthsPsr4[$first])) {
$subPath = $class;
while (false !== $lastPos = strrpos($subPath, '\\')) {
$subPath = substr($subPath, 0, $lastPos);
$search = $subPath . '\\';
if (isset($this->prefixDirsPsr4[$search])) {
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
foreach ($this->prefixDirsPsr4[$search] as $dir) {
if (file_exists($file = $dir . $pathEnd)) {
return $file;
}
}
}
}
}
// PSR-4 fallback dirs
foreach ($this->fallbackDirsPsr4 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
return $file;
}
}
// PSR-0 lookup
if (false !== $pos = strrpos($class, '\\')) {
// namespaced class name
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
} else {
// PEAR-like class name
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
}
if (isset($this->prefixesPsr0[$first])) {
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
if (0 === strpos($class, $prefix)) {
foreach ($dirs as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
}
}
}
// PSR-0 fallback dirs
foreach ($this->fallbackDirsPsr0 as $dir) {
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
return $file;
}
}
// PSR-0 include paths.
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
return $file;
}
return false;
}
/**
* @return void
*/
private static function initializeIncludeClosure()
{
if (self::$includeFile !== null) {
return;
}
/**
* Scope isolated include.
*
* Prevents access to $this/self from included files.
*
* @param string $file
* @return void
*/
self::$includeFile = \Closure::bind(static function($file) {
include $file;
}, null, null);
}
}

View File

@ -0,0 +1,359 @@
<?php
/*
* This file is part of Composer.
*
* (c) Nils Adermann <naderman@naderman.de>
* Jordi Boggiano <j.boggiano@seld.be>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Composer;
use Composer\Autoload\ClassLoader;
use Composer\Semver\VersionParser;
/**
* This class is copied in every Composer installed project and available to all
*
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
*
* To require its presence, you can require `composer-runtime-api ^2.0`
*
* @final
*/
class InstalledVersions
{
/**
* @var mixed[]|null
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
*/
private static $installed;
/**
* @var bool|null
*/
private static $canGetVendors;
/**
* @var array[]
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static $installedByVendor = array();
/**
* Returns a list of all package names which are present, either by being installed, replaced or provided
*
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackages()
{
$packages = array();
foreach (self::getInstalled() as $installed) {
$packages[] = array_keys($installed['versions']);
}
if (1 === \count($packages)) {
return $packages[0];
}
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
}
/**
* Returns a list of all package names with a specific type e.g. 'library'
*
* @param string $type
* @return string[]
* @psalm-return list<string>
*/
public static function getInstalledPackagesByType($type)
{
$packagesByType = array();
foreach (self::getInstalled() as $installed) {
foreach ($installed['versions'] as $name => $package) {
if (isset($package['type']) && $package['type'] === $type) {
$packagesByType[] = $name;
}
}
}
return $packagesByType;
}
/**
* Checks whether the given package is installed
*
* This also returns true if the package name is provided or replaced by another package
*
* @param string $packageName
* @param bool $includeDevRequirements
* @return bool
*/
public static function isInstalled($packageName, $includeDevRequirements = true)
{
foreach (self::getInstalled() as $installed) {
if (isset($installed['versions'][$packageName])) {
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
}
}
return false;
}
/**
* Checks whether the given package satisfies a version constraint
*
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
*
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
*
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
* @param string $packageName
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
* @return bool
*/
public static function satisfies(VersionParser $parser, $packageName, $constraint)
{
$constraint = $parser->parseConstraints((string) $constraint);
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
return $provided->matches($constraint);
}
/**
* Returns a version constraint representing all the range(s) which are installed for a given package
*
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
* whether a given version of a package is installed, and not just whether it exists
*
* @param string $packageName
* @return string Version constraint usable with composer/semver
*/
public static function getVersionRanges($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
$ranges = array();
if (isset($installed['versions'][$packageName]['pretty_version'])) {
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
}
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
}
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
}
if (array_key_exists('provided', $installed['versions'][$packageName])) {
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
}
return implode(' || ', $ranges);
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['version'])) {
return null;
}
return $installed['versions'][$packageName]['version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
*/
public static function getPrettyVersion($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
return null;
}
return $installed['versions'][$packageName]['pretty_version'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
*/
public static function getReference($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
if (!isset($installed['versions'][$packageName]['reference'])) {
return null;
}
return $installed['versions'][$packageName]['reference'];
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @param string $packageName
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
*/
public static function getInstallPath($packageName)
{
foreach (self::getInstalled() as $installed) {
if (!isset($installed['versions'][$packageName])) {
continue;
}
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
}
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
}
/**
* @return array
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
*/
public static function getRootPackage()
{
$installed = self::getInstalled();
return $installed[0]['root'];
}
/**
* Returns the raw installed.php data for custom implementations
*
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
* @return array[]
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
*/
public static function getRawData()
{
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
self::$installed = include __DIR__ . '/installed.php';
} else {
self::$installed = array();
}
}
return self::$installed;
}
/**
* Returns the raw data of all installed.php which are currently loaded for custom implementations
*
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
public static function getAllRawData()
{
return self::getInstalled();
}
/**
* Lets you reload the static array from another file
*
* This is only useful for complex integrations in which a project needs to use
* this class but then also needs to execute another project's autoloader in process,
* and wants to ensure both projects have access to their version of installed.php.
*
* A typical case would be PHPUnit, where it would need to make sure it reads all
* the data it needs from this class, then call reload() with
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
* the project in which it runs can then also use this class safely, without
* interference between PHPUnit's dependencies and the project's dependencies.
*
* @param array[] $data A vendor/composer/installed.php data set
* @return void
*
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
*/
public static function reload($data)
{
self::$installed = $data;
self::$installedByVendor = array();
}
/**
* @return array[]
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
*/
private static function getInstalled()
{
if (null === self::$canGetVendors) {
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
}
$installed = array();
if (self::$canGetVendors) {
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
if (isset(self::$installedByVendor[$vendorDir])) {
$installed[] = self::$installedByVendor[$vendorDir];
} elseif (is_file($vendorDir.'/composer/installed.php')) {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require $vendorDir.'/composer/installed.php';
$installed[] = self::$installedByVendor[$vendorDir] = $required;
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
self::$installed = $installed[count($installed) - 1];
}
}
}
}
if (null === self::$installed) {
// only require the installed.php file if this file is loaded from its dumped location,
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
if (substr(__DIR__, -8, 1) !== 'C') {
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
$required = require __DIR__ . '/installed.php';
self::$installed = $required;
} else {
self::$installed = array();
}
}
if (self::$installed !== array()) {
$installed[] = self::$installed;
}
return $installed;
}
}

21
code/vendor/composer/LICENSE vendored Normal file
View File

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

1254
code/vendor/composer/autoload_classmap.php vendored Normal file

File diff suppressed because it is too large Load Diff

23
code/vendor/composer/autoload_files.php vendored Normal file
View File

@ -0,0 +1,23 @@
<?php
// autoload_files.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'6124b4c8570aa390c21fafd04a26c69f' => $vendorDir . '/myclabs/deep-copy/src/DeepCopy/deep_copy.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'23c18046f52bef3eea034657bafda50f' => $vendorDir . '/symfony/polyfill-php81/bootstrap.php',
'89efb1254ef2d1c5d80096acd12c4098' => $vendorDir . '/twig/twig/src/Resources/core.php',
'ffecb95d45175fd40f75be8a23b34f90' => $vendorDir . '/twig/twig/src/Resources/debug.php',
'c7baa00073ee9c61edf148c51917cfb4' => $vendorDir . '/twig/twig/src/Resources/escaper.php',
'f844ccf1d25df8663951193c3fc307c8' => $vendorDir . '/twig/twig/src/Resources/string_loader.php',
'b33e3d135e5d9e47d845c576147bda89' => $vendorDir . '/php-di/php-di/src/functions.php',
'ec07570ca5a812141189b1fa81503674' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert/Functions.php',
);

View File

@ -0,0 +1,9 @@
<?php
// autoload_namespaces.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
);

35
code/vendor/composer/autoload_psr4.php vendored Normal file
View File

@ -0,0 +1,35 @@
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'Twig\\' => array($vendorDir . '/twig/twig/src'),
'Symfony\\Polyfill\\Php81\\' => array($vendorDir . '/symfony/polyfill-php81'),
'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'),
'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'),
'Symfony\\Polyfill\\Ctype\\' => array($vendorDir . '/symfony/polyfill-ctype'),
'Slovocast\\' => array($baseDir . '/src'),
'Slim\\Views\\' => array($vendorDir . '/slim/twig-view/src'),
'Slim\\Psr7\\' => array($vendorDir . '/slim/psr7/src'),
'Slim\\' => array($vendorDir . '/slim/slim/Slim'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
'Psr\\Http\\Server\\' => array($vendorDir . '/psr/http-server-handler/src', $vendorDir . '/psr/http-server-middleware/src'),
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'),
'League\\Flysystem\\Local\\' => array($vendorDir . '/league/flysystem-local'),
'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'),
'League\\Config\\' => array($vendorDir . '/league/config/src'),
'Laravel\\SerializableClosure\\' => array($vendorDir . '/laravel/serializable-closure/src'),
'Invoker\\' => array($vendorDir . '/php-di/invoker/src'),
'Fig\\Http\\Message\\' => array($vendorDir . '/fig/http-message-util/src'),
'FastRoute\\' => array($vendorDir . '/nikic/fast-route/src'),
'Dflydev\\DotAccessData\\' => array($vendorDir . '/dflydev/dot-access-data/src'),
'DeepCopy\\' => array($vendorDir . '/myclabs/deep-copy/src/DeepCopy'),
'DI\\' => array($vendorDir . '/php-di/php-di/src'),
);

50
code/vendor/composer/autoload_real.php vendored Normal file
View File

@ -0,0 +1,50 @@
<?php
// autoload_real.php @generated by Composer
class ComposerAutoloaderInit0187bc48337726eafc963b41d1b65200
{
private static $loader;
public static function loadClassLoader($class)
{
if ('Composer\Autoload\ClassLoader' === $class) {
require __DIR__ . '/ClassLoader.php';
}
}
/**
* @return \Composer\Autoload\ClassLoader
*/
public static function getLoader()
{
if (null !== self::$loader) {
return self::$loader;
}
require __DIR__ . '/platform_check.php';
spl_autoload_register(array('ComposerAutoloaderInit0187bc48337726eafc963b41d1b65200', 'loadClassLoader'), true, true);
self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
spl_autoload_unregister(array('ComposerAutoloaderInit0187bc48337726eafc963b41d1b65200', 'loadClassLoader'));
require __DIR__ . '/autoload_static.php';
call_user_func(\Composer\Autoload\ComposerStaticInit0187bc48337726eafc963b41d1b65200::getInitializer($loader));
$loader->register(true);
$filesToLoad = \Composer\Autoload\ComposerStaticInit0187bc48337726eafc963b41d1b65200::$files;
$requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
require $file;
}
}, null, null);
foreach ($filesToLoad as $fileIdentifier => $file) {
$requireFile($fileIdentifier, $file);
}
return $loader;
}
}

1445
code/vendor/composer/autoload_static.php vendored Normal file

File diff suppressed because it is too large Load Diff

3825
code/vendor/composer/installed.json vendored Normal file

File diff suppressed because it is too large Load Diff

542
code/vendor/composer/installed.php vendored Normal file
View File

@ -0,0 +1,542 @@
<?php return array(
'root' => array(
'name' => 'dsmithhayes/slovocast',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '03a404007550f9df9caaeb02c768eae8ae485222',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev' => true,
),
'versions' => array(
'dflydev/dot-access-data' => array(
'pretty_version' => 'v3.0.2',
'version' => '3.0.2.0',
'reference' => 'f41715465d65213d644d3141a6a93081be5d3549',
'type' => 'library',
'install_path' => __DIR__ . '/../dflydev/dot-access-data',
'aliases' => array(),
'dev_requirement' => false,
),
'dsmithhayes/slovocast' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '03a404007550f9df9caaeb02c768eae8ae485222',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
'dev_requirement' => false,
),
'fig/http-message-util' => array(
'pretty_version' => '1.1.5',
'version' => '1.1.5.0',
'reference' => '9d94dc0154230ac39e5bf89398b324a86f63f765',
'type' => 'library',
'install_path' => __DIR__ . '/../fig/http-message-util',
'aliases' => array(),
'dev_requirement' => false,
),
'laravel/serializable-closure' => array(
'pretty_version' => 'v1.3.3',
'version' => '1.3.3.0',
'reference' => '3dbf8a8e914634c48d389c1234552666b3d43754',
'type' => 'library',
'install_path' => __DIR__ . '/../laravel/serializable-closure',
'aliases' => array(),
'dev_requirement' => false,
),
'league/config' => array(
'pretty_version' => 'v1.2.0',
'version' => '1.2.0.0',
'reference' => '754b3604fb2984c71f4af4a9cbe7b57f346ec1f3',
'type' => 'library',
'install_path' => __DIR__ . '/../league/config',
'aliases' => array(),
'dev_requirement' => false,
),
'league/flysystem' => array(
'pretty_version' => '3.27.0',
'version' => '3.27.0.0',
'reference' => '4729745b1ab737908c7d055148c9a6b3e959832f',
'type' => 'library',
'install_path' => __DIR__ . '/../league/flysystem',
'aliases' => array(),
'dev_requirement' => false,
),
'league/flysystem-local' => array(
'pretty_version' => '3.25.1',
'version' => '3.25.1.0',
'reference' => '61a6a90d6e999e4ddd9ce5adb356de0939060b92',
'type' => 'library',
'install_path' => __DIR__ . '/../league/flysystem-local',
'aliases' => array(),
'dev_requirement' => false,
),
'league/mime-type-detection' => array(
'pretty_version' => '1.15.0',
'version' => '1.15.0.0',
'reference' => 'ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301',
'type' => 'library',
'install_path' => __DIR__ . '/../league/mime-type-detection',
'aliases' => array(),
'dev_requirement' => false,
),
'monolog/monolog' => array(
'pretty_version' => '3.6.0',
'version' => '3.6.0.0',
'reference' => '4b18b21a5527a3d5ffdac2fd35d3ab25a9597654',
'type' => 'library',
'install_path' => __DIR__ . '/../monolog/monolog',
'aliases' => array(),
'dev_requirement' => false,
),
'myclabs/deep-copy' => array(
'pretty_version' => '1.11.1',
'version' => '1.11.1.0',
'reference' => '7284c22080590fb39f2ffa3e9057f10a4ddd0e0c',
'type' => 'library',
'install_path' => __DIR__ . '/../myclabs/deep-copy',
'aliases' => array(),
'dev_requirement' => true,
),
'nette/schema' => array(
'pretty_version' => 'v1.3.0',
'version' => '1.3.0.0',
'reference' => 'a6d3a6d1f545f01ef38e60f375d1cf1f4de98188',
'type' => 'library',
'install_path' => __DIR__ . '/../nette/schema',
'aliases' => array(),
'dev_requirement' => false,
),
'nette/utils' => array(
'pretty_version' => 'v4.0.4',
'version' => '4.0.4.0',
'reference' => 'd3ad0aa3b9f934602cb3e3902ebccf10be34d218',
'type' => 'library',
'install_path' => __DIR__ . '/../nette/utils',
'aliases' => array(),
'dev_requirement' => false,
),
'nikic/fast-route' => array(
'pretty_version' => 'v1.3.0',
'version' => '1.3.0.0',
'reference' => '181d480e08d9476e61381e04a71b34dc0432e812',
'type' => 'library',
'install_path' => __DIR__ . '/../nikic/fast-route',
'aliases' => array(),
'dev_requirement' => false,
),
'nikic/php-parser' => array(
'pretty_version' => 'v5.0.2',
'version' => '5.0.2.0',
'reference' => '139676794dc1e9231bf7bcd123cfc0c99182cb13',
'type' => 'library',
'install_path' => __DIR__ . '/../nikic/php-parser',
'aliases' => array(),
'dev_requirement' => true,
),
'phar-io/manifest' => array(
'pretty_version' => '2.0.4',
'version' => '2.0.4.0',
'reference' => '54750ef60c58e43759730615a392c31c80e23176',
'type' => 'library',
'install_path' => __DIR__ . '/../phar-io/manifest',
'aliases' => array(),
'dev_requirement' => true,
),
'phar-io/version' => array(
'pretty_version' => '3.2.1',
'version' => '3.2.1.0',
'reference' => '4f7fd7836c6f332bb2933569e566a0d6c4cbed74',
'type' => 'library',
'install_path' => __DIR__ . '/../phar-io/version',
'aliases' => array(),
'dev_requirement' => true,
),
'php-di/invoker' => array(
'pretty_version' => '2.3.4',
'version' => '2.3.4.0',
'reference' => '33234b32dafa8eb69202f950a1fc92055ed76a86',
'type' => 'library',
'install_path' => __DIR__ . '/../php-di/invoker',
'aliases' => array(),
'dev_requirement' => false,
),
'php-di/php-di' => array(
'pretty_version' => '7.0.6',
'version' => '7.0.6.0',
'reference' => '8097948a89f6ec782839b3e958432f427cac37fd',
'type' => 'library',
'install_path' => __DIR__ . '/../php-di/php-di',
'aliases' => array(),
'dev_requirement' => false,
),
'phpunit/php-code-coverage' => array(
'pretty_version' => '11.0.3',
'version' => '11.0.3.0',
'reference' => '7e35a2cbcabac0e6865fd373742ea432a3c34f92',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-code-coverage',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-file-iterator' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => '99e95c94ad9500daca992354fa09d7b99abe2210',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-file-iterator',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-invoker' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => '5d8d9355a16d8cc5a1305b0a85342cfa420612be',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-invoker',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-text-template' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => 'd38f6cbff1cdb6f40b03c9811421561668cc133e',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-text-template',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/php-timer' => array(
'pretty_version' => '7.0.0',
'version' => '7.0.0.0',
'reference' => '8a59d9e25720482ee7fcdf296595e08795b84dc5',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/php-timer',
'aliases' => array(),
'dev_requirement' => true,
),
'phpunit/phpunit' => array(
'pretty_version' => '11.1.3',
'version' => '11.1.3.0',
'reference' => 'd475be032238173ca3b0a516f5cc291d174708ae',
'type' => 'library',
'install_path' => __DIR__ . '/../phpunit/phpunit',
'aliases' => array(),
'dev_requirement' => true,
),
'psr/container' => array(
'pretty_version' => '2.0.2',
'version' => '2.0.2.0',
'reference' => 'c71ecc56dfe541dbd90c5360474fbc405f8d5963',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/container',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/container-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '^1.0',
),
),
'psr/http-factory' => array(
'pretty_version' => '1.1.0',
'version' => '1.1.0.0',
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-factory',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-factory-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-message' => array(
'pretty_version' => '1.1',
'version' => '1.1.0.0',
'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-message',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-message-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '1.0',
),
),
'psr/http-server-handler' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => '84c4fb66179be4caaf8e97bd239203245302e7d4',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-server-handler',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/http-server-middleware' => array(
'pretty_version' => '1.0.2',
'version' => '1.0.2.0',
'reference' => 'c1481f747daaa6a0782775cd6a8c26a1bf4a3829',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/http-server-middleware',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/log' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'reference' => 'fe5ea303b0887d5caefd3d431c3e61ad47037001',
'type' => 'library',
'install_path' => __DIR__ . '/../psr/log',
'aliases' => array(),
'dev_requirement' => false,
),
'psr/log-implementation' => array(
'dev_requirement' => false,
'provided' => array(
0 => '3.0.0',
),
),
'ralouphie/getallheaders' => array(
'pretty_version' => '3.0.3',
'version' => '3.0.3.0',
'reference' => '120b605dfeb996808c31b6477290a714d356e822',
'type' => 'library',
'install_path' => __DIR__ . '/../ralouphie/getallheaders',
'aliases' => array(),
'dev_requirement' => false,
),
'sebastian/cli-parser' => array(
'pretty_version' => '3.0.1',
'version' => '3.0.1.0',
'reference' => '00a74d5568694711f0222e54fb281e1d15fdf04a',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/cli-parser',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/code-unit' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'reference' => '6634549cb8d702282a04a774e36a7477d2bd9015',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/code-unit',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/code-unit-reverse-lookup' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => 'df80c875d3e459b45c6039e4d9b71d4fbccae25d',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/code-unit-reverse-lookup',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/comparator' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => 'bd0f2fa5b9257c69903537b266ccb80fcf940db8',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/comparator',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/complexity' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => '88a434ad86150e11a606ac4866b09130712671f0',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/complexity',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/diff' => array(
'pretty_version' => '6.0.1',
'version' => '6.0.1.0',
'reference' => 'ab83243ecc233de5655b76f577711de9f842e712',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/diff',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/environment' => array(
'pretty_version' => '7.1.0',
'version' => '7.1.0.0',
'reference' => '4eb3a442574d0e9d141aab209cd4aaf25701b09a',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/environment',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/exporter' => array(
'pretty_version' => '6.0.1',
'version' => '6.0.1.0',
'reference' => 'f291e5a317c321c0381fa9ecc796fa2d21b186da',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/exporter',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/global-state' => array(
'pretty_version' => '7.0.1',
'version' => '7.0.1.0',
'reference' => 'c3a307e832f2e69c7ef869e31fc644fde0e7cb3e',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/global-state',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/lines-of-code' => array(
'pretty_version' => '3.0.0',
'version' => '3.0.0.0',
'reference' => '376c5b3f6b43c78fdc049740bca76a7c846706c0',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/lines-of-code',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/object-enumerator' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => 'f75f6c460da0bbd9668f43a3dde0ec0ba7faa678',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-enumerator',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/object-reflector' => array(
'pretty_version' => '4.0.0',
'version' => '4.0.0.0',
'reference' => 'bb2a6255d30853425fd38f032eb64ced9f7f132d',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/object-reflector',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/recursion-context' => array(
'pretty_version' => '6.0.0',
'version' => '6.0.0.0',
'reference' => 'b75224967b5a466925c6d54e68edd0edf8dd4ed4',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/recursion-context',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/type' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => 'b8502785eb3523ca0dd4afe9ca62235590020f3f',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/type',
'aliases' => array(),
'dev_requirement' => true,
),
'sebastian/version' => array(
'pretty_version' => '5.0.0',
'version' => '5.0.0.0',
'reference' => '13999475d2cb1ab33cb73403ba356a814fdbb001',
'type' => 'library',
'install_path' => __DIR__ . '/../sebastian/version',
'aliases' => array(),
'dev_requirement' => true,
),
'slim/psr7' => array(
'pretty_version' => '1.6.1',
'version' => '1.6.1.0',
'reference' => '72d2b2bac94ab4575d369f605dbfafbe168d3163',
'type' => 'library',
'install_path' => __DIR__ . '/../slim/psr7',
'aliases' => array(),
'dev_requirement' => false,
),
'slim/slim' => array(
'pretty_version' => '4.13.0',
'version' => '4.13.0.0',
'reference' => '038fd5713d5a41636fdff0e8dcceedecdd17fc17',
'type' => 'library',
'install_path' => __DIR__ . '/../slim/slim',
'aliases' => array(),
'dev_requirement' => false,
),
'slim/twig-view' => array(
'pretty_version' => '3.4.0',
'version' => '3.4.0.0',
'reference' => '1b351536b9a07ed90a3563ee9d71a987c5d74610',
'type' => 'library',
'install_path' => __DIR__ . '/../slim/twig-view',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/deprecation-contracts' => array(
'pretty_version' => 'v3.5.0',
'version' => '3.5.0.0',
'reference' => '0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-ctype' => array(
'pretty_version' => 'v1.29.0',
'version' => '1.29.0.0',
'reference' => 'ef4d7e442ca910c4764bce785146269b30cb5fc4',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-ctype',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-mbstring' => array(
'pretty_version' => 'v1.29.0',
'version' => '1.29.0.0',
'reference' => '9773676c8a1bb1f8d4340a62efe641cf76eda7ec',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php80' => array(
'pretty_version' => 'v1.29.0',
'version' => '1.29.0.0',
'reference' => '87b68208d5c1188808dd7839ee1e6c8ec3b02f1b',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
'aliases' => array(),
'dev_requirement' => false,
),
'symfony/polyfill-php81' => array(
'pretty_version' => 'v1.29.0',
'version' => '1.29.0.0',
'reference' => 'c565ad1e63f30e7477fc40738343c62b40bc672d',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/polyfill-php81',
'aliases' => array(),
'dev_requirement' => false,
),
'theseer/tokenizer' => array(
'pretty_version' => '1.2.3',
'version' => '1.2.3.0',
'reference' => '737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2',
'type' => 'library',
'install_path' => __DIR__ . '/../theseer/tokenizer',
'aliases' => array(),
'dev_requirement' => true,
),
'twig/twig' => array(
'pretty_version' => 'v3.10.1',
'version' => '3.10.1.0',
'reference' => '3af5ab2e52279e5e23dc192b1a26db3b8cffa4e7',
'type' => 'library',
'install_path' => __DIR__ . '/../twig/twig',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

26
code/vendor/composer/platform_check.php vendored Normal file
View File

@ -0,0 +1,26 @@
<?php
// platform_check.php @generated by Composer
$issues = array();
if (!(PHP_VERSION_ID >= 80100)) {
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.1.0". You are running ' . PHP_VERSION . '.';
}
if ($issues) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
} elseif (!headers_sent()) {
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
}
}
trigger_error(
'Composer detected issues in your platform: ' . implode(' ', $issues),
E_USER_ERROR
);
}

View File

@ -0,0 +1,67 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
## [3.0.2] - 2022-10-27
### Fixed
- Added missing return types to docblocks (#44, #45)
## [3.0.1] - 2021-08-13
### Added
- Adds ReturnTypeWillChange to suppress PHP 8.1 warnings (#40)
## [3.0.0] - 2021-01-01
### Added
- Added support for both `.` and `/`-delimited key paths (#24)
- Added parameter and return types to everything; enabled strict type checks (#18)
- Added new exception classes to better identify certain types of errors (#20)
- `Data` now implements `ArrayAccess` (#17)
- Added ability to merge non-associative array values (#31, #32)
### Changed
- All thrown exceptions are now instances or subclasses of `DataException` (#20)
- Calling `get()` on a missing key path without providing a default will throw a `MissingPathException` instead of returning `null` (#29)
- Bumped supported PHP versions to 7.1 - 8.x (#18)
### Fixed
- Fixed incorrect merging of array values into string values (#32)
- Fixed `get()` method behaving as if keys with `null` values didn't exist
## [2.0.0] - 2017-12-21
### Changed
- Bumped supported PHP versions to 7.0 - 7.4 (#12)
- Switched to PSR-4 autoloading
## [1.1.0] - 2017-01-20
### Added
- Added new `has()` method to check for the existence of the given key (#4, #7)
## [1.0.1] - 2015-08-12
### Added
- Added new optional `$default` parameter to the `get()` method (#2)
## [1.0.0] - 2012-07-17
**Initial release!**
[Unreleased]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.2...main
[3.0.2]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.1...v3.0.2
[3.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v3.0.0...v3.0.1
[3.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v2.0.0...v3.0.0
[2.0.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.1.0...v2.0.0
[1.1.0]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/dflydev/dflydev-dot-access-data/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/dflydev/dflydev-dot-access-data/releases/tag/v1.0.0

View File

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

View File

@ -0,0 +1,158 @@
Dot Access Data
===============
[![Latest Version](https://img.shields.io/packagist/v/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data)
[![Total Downloads](https://img.shields.io/packagist/dt/dflydev/dot-access-data.svg?style=flat-square)](https://packagist.org/packages/dflydev/dot-access-data)
[![Software License](https://img.shields.io/badge/License-MIT-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/github/workflow/status/dflydev/dflydev-dot-access-data/Tests/main.svg?style=flat-square)](https://github.com/dflydev/dflydev-dot-access-data/actions?query=workflow%3ATests+branch%3Amain)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data/code-structure/)
[![Quality Score](https://img.shields.io/scrutinizer/g/dflydev/dflydev-dot-access-data.svg?style=flat-square)](https://scrutinizer-ci.com/g/dflydev/dflydev-dot-access-data)
Given a deep data structure, access data by dot notation.
Requirements
------------
* PHP (7.1+)
> For PHP (5.3+) please refer to version `1.0`.
Usage
-----
Abstract example:
```php
use Dflydev\DotAccessData\Data;
$data = new Data;
$data->set('a.b.c', 'C');
$data->set('a.b.d', 'D1');
$data->append('a.b.d', 'D2');
$data->set('a.b.e', ['E0', 'E1', 'E2']);
// C
$data->get('a.b.c');
// ['D1', 'D2']
$data->get('a.b.d');
// ['E0', 'E1', 'E2']
$data->get('a.b.e');
// true
$data->has('a.b.c');
// false
$data->has('a.b.d.j');
// 'some-default-value'
$data->get('some.path.that.does.not.exist', 'some-default-value');
// throws a MissingPathException because no default was given
$data->get('some.path.that.does.not.exist');
```
A more concrete example:
```php
use Dflydev\DotAccessData\Data;
$data = new Data([
'hosts' => [
'hewey' => [
'username' => 'hman',
'password' => 'HPASS',
'roles' => ['web'],
],
'dewey' => [
'username' => 'dman',
'password' => 'D---S',
'roles' => ['web', 'db'],
'nick' => 'dewey dman',
],
'lewey' => [
'username' => 'lman',
'password' => 'LP@$$',
'roles' => ['db'],
],
],
]);
// hman
$username = $data->get('hosts.hewey.username');
// HPASS
$password = $data->get('hosts.hewey.password');
// ['web']
$roles = $data->get('hosts.hewey.roles');
// dewey dman
$nick = $data->get('hosts.dewey.nick');
// Unknown
$nick = $data->get('hosts.lewey.nick', 'Unknown');
// DataInterface instance
$dewey = $data->getData('hosts.dewey');
// dman
$username = $dewey->get('username');
// D---S
$password = $dewey->get('password');
// ['web', 'db']
$roles = $dewey->get('roles');
// No more lewey
$data->remove('hosts.lewey');
// Add DB to hewey's roles
$data->append('hosts.hewey.roles', 'db');
$data->set('hosts.april', [
'username' => 'aman',
'password' => '@---S',
'roles' => ['web'],
]);
// Check if a key exists (true to this case)
$hasKey = $data->has('hosts.dewey.username');
```
`Data` may be used as an array, since it implements `ArrayAccess` interface:
```php
// Get
$data->get('name') === $data['name']; // true
$data['name'] = 'Dewey';
// is equivalent to
$data->set($name, 'Dewey');
isset($data['name']) === $data->has('name');
// Remove key
unset($data['name']);
```
`/` can also be used as a path delimiter:
```php
$data->set('a/b/c', 'd');
echo $data->get('a/b/c'); // "d"
$data->get('a/b/c') === $data->get('a.b.c'); // true
```
License
-------
This library is licensed under the MIT License - see the LICENSE file
for details.
Community
---------
If you have questions or want to help out, join us in the
[#dflydev](irc://irc.freenode.net/#dflydev) channel on irc.freenode.net.

View File

@ -0,0 +1,67 @@
{
"name": "dflydev/dot-access-data",
"type": "library",
"description": "Given a deep data structure, access data by dot notation.",
"homepage": "https://github.com/dflydev/dflydev-dot-access-data",
"keywords": ["dot", "access", "data", "notation"],
"license": "MIT",
"authors": [
{
"name": "Dragonfly Development Inc.",
"email": "info@dflydev.com",
"homepage": "http://dflydev.com"
},
{
"name": "Beau Simensen",
"email": "beau@dflydev.com",
"homepage": "http://beausimensen.com"
},
{
"name": "Carlos Frutos",
"email": "carlos@kiwing.it",
"homepage": "https://github.com/cfrutos"
},
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com"
}
],
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"phpstan/phpstan": "^0.12.42",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
"scrutinizer/ocular": "1.6.0",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.0.0"
},
"autoload": {
"psr-4": {
"Dflydev\\DotAccessData\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Dflydev\\DotAccessData\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-main": "3.x-dev"
}
},
"scripts": {
"phpcs": "phpcs",
"phpstan": "phpstan analyse",
"phpunit": "phpunit --no-coverage",
"psalm": "psalm",
"test": [
"@phpcs",
"@phpstan",
"@psalm",
"@phpunit"
]
}
}

View File

@ -0,0 +1,286 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
use ArrayAccess;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
use Dflydev\DotAccessData\Exception\MissingPathException;
/**
* @implements ArrayAccess<string, mixed>
*/
class Data implements DataInterface, ArrayAccess
{
private const DELIMITERS = ['.', '/'];
/**
* Internal representation of data data
*
* @var array<string, mixed>
*/
protected $data;
/**
* Constructor
*
* @param array<string, mixed> $data
*/
public function __construct(array $data = [])
{
$this->data = $data;
}
/**
* {@inheritdoc}
*/
public function append(string $key, $value = null): void
{
$currentValue =& $this->data;
$keyPath = self::keyToPathArray($key);
$endKey = array_pop($keyPath);
foreach ($keyPath as $currentKey) {
if (! isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
}
$currentValue =& $currentValue[$currentKey];
}
if (!isset($currentValue[$endKey])) {
$currentValue[$endKey] = [];
}
if (!is_array($currentValue[$endKey])) {
// Promote this key to an array.
// TODO: Is this really what we want to do?
$currentValue[$endKey] = [$currentValue[$endKey]];
}
$currentValue[$endKey][] = $value;
}
/**
* {@inheritdoc}
*/
public function set(string $key, $value = null): void
{
$currentValue =& $this->data;
$keyPath = self::keyToPathArray($key);
$endKey = array_pop($keyPath);
foreach ($keyPath as $currentKey) {
if (!isset($currentValue[$currentKey])) {
$currentValue[$currentKey] = [];
}
if (!is_array($currentValue[$currentKey])) {
throw new DataException(sprintf('Key path "%s" within "%s" cannot be indexed into (is not an array)', $currentKey, self::formatPath($key)));
}
$currentValue =& $currentValue[$currentKey];
}
$currentValue[$endKey] = $value;
}
/**
* {@inheritdoc}
*/
public function remove(string $key): void
{
$currentValue =& $this->data;
$keyPath = self::keyToPathArray($key);
$endKey = array_pop($keyPath);
foreach ($keyPath as $currentKey) {
if (!isset($currentValue[$currentKey])) {
return;
}
$currentValue =& $currentValue[$currentKey];
}
unset($currentValue[$endKey]);
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function get(string $key, $default = null)
{
/** @psalm-suppress ImpureFunctionCall */
$hasDefault = \func_num_args() > 1;
$currentValue = $this->data;
$keyPath = self::keyToPathArray($key);
foreach ($keyPath as $currentKey) {
if (!is_array($currentValue) || !array_key_exists($currentKey, $currentValue)) {
if ($hasDefault) {
return $default;
}
throw new MissingPathException($key, sprintf('No data exists at the given path: "%s"', self::formatPath($keyPath)));
}
$currentValue = $currentValue[$currentKey];
}
return $currentValue === null ? $default : $currentValue;
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function has(string $key): bool
{
$currentValue = $this->data;
foreach (self::keyToPathArray($key) as $currentKey) {
if (
!is_array($currentValue) ||
!array_key_exists($currentKey, $currentValue)
) {
return false;
}
$currentValue = $currentValue[$currentKey];
}
return true;
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function getData(string $key): DataInterface
{
$value = $this->get($key);
if (is_array($value) && Util::isAssoc($value)) {
return new Data($value);
}
throw new DataException(sprintf('Value at "%s" could not be represented as a DataInterface', self::formatPath($key)));
}
/**
* {@inheritdoc}
*/
public function import(array $data, int $mode = self::REPLACE): void
{
$this->data = Util::mergeAssocArray($this->data, $data, $mode);
}
/**
* {@inheritdoc}
*/
public function importData(DataInterface $data, int $mode = self::REPLACE): void
{
$this->import($data->export(), $mode);
}
/**
* {@inheritdoc}
*
* @psalm-mutation-free
*/
public function export(): array
{
return $this->data;
}
/**
* {@inheritdoc}
*
* @return bool
*/
#[\ReturnTypeWillChange]
public function offsetExists($key)
{
return $this->has($key);
}
/**
* {@inheritdoc}
*
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($key)
{
return $this->get($key, null);
}
/**
* {@inheritdoc}
*
* @param string $key
* @param mixed $value
*
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetSet($key, $value)
{
$this->set($key, $value);
}
/**
* {@inheritdoc}
*
* @return void
*/
#[\ReturnTypeWillChange]
public function offsetUnset($key)
{
$this->remove($key);
}
/**
* @param string $path
*
* @return string[]
*
* @psalm-return non-empty-list<string>
*
* @psalm-pure
*/
protected static function keyToPathArray(string $path): array
{
if (\strlen($path) === 0) {
throw new InvalidPathException('Path cannot be an empty string');
}
$path = \str_replace(self::DELIMITERS, '.', $path);
return \explode('.', $path);
}
/**
* @param string|string[] $path
*
* @return string
*
* @psalm-pure
*/
protected static function formatPath($path): string
{
if (is_string($path)) {
$path = self::keyToPathArray($path);
}
return implode(' » ', $path);
}
}

View File

@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
interface DataInterface
{
public const PRESERVE = 0;
public const REPLACE = 1;
public const MERGE = 2;
/**
* Append a value to a key (assumes key refers to an array value)
*
* If the key does not yet exist it will be created.
* If the key references a non-array it's existing contents will be added into a new array before appending the new value.
*
* @param string $key
* @param mixed $value
*
* @throws InvalidPathException if the given key is empty
*/
public function append(string $key, $value = null): void;
/**
* Set a value for a key
*
* If the key does not yet exist it will be created.
*
* @param string $key
* @param mixed $value
*
* @throws InvalidPathException if the given key is empty
* @throws DataException if the given key does not target an array
*/
public function set(string $key, $value = null): void;
/**
* Remove a key
*
* No exception will be thrown if the key does not exist
*
* @param string $key
*
* @throws InvalidPathException if the given key is empty
*/
public function remove(string $key): void;
/**
* Get the raw value for a key
*
* If the key does not exist, an optional default value can be returned instead.
* If no default is provided then an exception will be thrown instead.
*
* @param string $key
* @param mixed $default
*
* @return mixed
*
* @throws InvalidPathException if the given key is empty
* @throws InvalidPathException if the given key does not exist and no default value was given
*
* @psalm-mutation-free
*/
public function get(string $key, $default = null);
/**
* Check if the key exists
*
* @param string $key
*
* @return bool
*
* @throws InvalidPathException if the given key is empty
*
* @psalm-mutation-free
*/
public function has(string $key): bool;
/**
* Get a data instance for a key
*
* @param string $key
*
* @return DataInterface
*
* @throws InvalidPathException if the given key is empty
* @throws DataException if the given key does not reference an array
*
* @psalm-mutation-free
*/
public function getData(string $key): DataInterface;
/**
* Import data into existing data
*
* @param array<string, mixed> $data
* @param self::PRESERVE|self::REPLACE|self::MERGE $mode
*/
public function import(array $data, int $mode = self::REPLACE): void;
/**
* Import data from an external data into existing data
*
* @param DataInterface $data
* @param self::PRESERVE|self::REPLACE|self::MERGE $mode
*/
public function importData(DataInterface $data, int $mode = self::REPLACE): void;
/**
* Export data as raw data
*
* @return array<string, mixed>
*
* @psalm-mutation-free
*/
public function export(): array;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData\Exception;
/**
* Base runtime exception type thrown by this library
*/
class DataException extends \RuntimeException
{
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData\Exception;
/**
* Thrown when trying to access an invalid path in the data array
*/
class InvalidPathException extends DataException
{
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData\Exception;
use Throwable;
/**
* Thrown when trying to access a path that does not exist
*/
class MissingPathException extends DataException
{
/** @var string */
protected $path;
public function __construct(string $path, string $message = '', int $code = 0, Throwable $previous = null)
{
$this->path = $path;
parent::__construct($message, $code, $previous);
}
public function getPath(): string
{
return $this->path;
}
}

View File

@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
/*
* This file is a part of dflydev/dot-access-data.
*
* (c) Dragonfly Development Inc.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Dflydev\DotAccessData;
class Util
{
/**
* Test if array is an associative array
*
* Note that this function will return true if an array is empty. Meaning
* empty arrays will be treated as if they are associative arrays.
*
* @param array<mixed> $arr
*
* @return bool
*
* @psalm-pure
*/
public static function isAssoc(array $arr): bool
{
return !count($arr) || count(array_filter(array_keys($arr), 'is_string')) == count($arr);
}
/**
* Merge contents from one associtative array to another
*
* @param mixed $to
* @param mixed $from
* @param DataInterface::PRESERVE|DataInterface::REPLACE|DataInterface::MERGE $mode
*
* @return mixed
*
* @psalm-pure
*/
public static function mergeAssocArray($to, $from, int $mode = DataInterface::REPLACE)
{
if ($mode === DataInterface::MERGE && self::isList($to) && self::isList($from)) {
return array_merge($to, $from);
}
if (is_array($from) && is_array($to)) {
foreach ($from as $k => $v) {
if (!isset($to[$k])) {
$to[$k] = $v;
} else {
$to[$k] = self::mergeAssocArray($to[$k], $v, $mode);
}
}
return $to;
}
return $mode === DataInterface::PRESERVE ? $to : $from;
}
/**
* @param mixed $value
*
* @return bool
*
* @psalm-pure
*/
private static function isList($value): bool
{
return is_array($value) && array_values($value) === $value;
}
}

View File

@ -0,0 +1 @@
vendor/

View File

@ -0,0 +1,147 @@
# Changelog
All notable changes to this project will be documented in this file, in reverse chronological order by release.
## 1.1.5 - 2020-11-24
### Added
- [#19](https://github.com/php-fig/http-message-util/pull/19) adds support for PHP 8.
### Changed
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.1.4 - 2020-02-05
### Added
- Nothing.
### Changed
- Nothing.
### Deprecated
- Nothing.
### Removed
- [#15](https://github.com/php-fig/http-message-util/pull/15) removes the dependency on psr/http-message, as it is not technically necessary for usage of this package.
### Fixed
- Nothing.
## 1.1.3 - 2018-11-19
### Added
- [#10](https://github.com/php-fig/http-message-util/pull/10) adds the constants `StatusCodeInterface::STATUS_EARLY_HINTS` (103) and
`StatusCodeInterface::STATUS_TOO_EARLY` (425).
### Changed
- Nothing.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.1.2 - 2017-02-09
### Added
- [#4](https://github.com/php-fig/http-message-util/pull/4) adds the constant
`StatusCodeInterface::STATUS_MISDIRECTED_REQUEST` (421).
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.1.1 - 2017-02-06
### Added
- [#3](https://github.com/php-fig/http-message-util/pull/3) adds the constant
`StatusCodeInterface::STATUS_IM_A_TEAPOT` (418).
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.1.0 - 2016-09-19
### Added
- [#1](https://github.com/php-fig/http-message-util/pull/1) adds
`Fig\Http\Message\StatusCodeInterface`, with constants named after common
status reason phrases, with values indicating the status codes themselves.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.
## 1.0.0 - 2017-08-05
### Added
- Adds `Fig\Http\Message\RequestMethodInterface`, with constants covering the
most common HTTP request methods as specified by the IETF.
### Deprecated
- Nothing.
### Removed
- Nothing.
### Fixed
- Nothing.

View File

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

View File

@ -0,0 +1,17 @@
# PSR Http Message Util
This repository holds utility classes and constants to facilitate common
operations of [PSR-7](https://www.php-fig.org/psr/psr-7/); the primary purpose is
to provide constants for referring to request methods, response status codes and
messages, and potentially common headers.
Implementation of PSR-7 interfaces is **not** within the scope of this package.
## Installation
Install by adding the package as a [Composer](https://getcomposer.org)
requirement:
```bash
$ composer require fig/http-message-util
```

View File

@ -0,0 +1,28 @@
{
"name": "fig/http-message-util",
"description": "Utility classes and constants for use with PSR-7 (psr/http-message)",
"keywords": ["psr", "psr-7", "http", "http-message", "request", "response"],
"license": "MIT",
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"require": {
"php": "^5.3 || ^7.0 || ^8.0"
},
"suggest": {
"psr/http-message": "The package containing the PSR-7 interfaces"
},
"autoload": {
"psr-4": {
"Fig\\Http\\Message\\": "src/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.1.x-dev"
}
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace Fig\Http\Message;
/**
* Defines constants for common HTTP request methods.
*
* Usage:
*
* <code>
* class RequestFactory implements RequestMethodInterface
* {
* public static function factory(
* $uri = '/',
* $method = self::METHOD_GET,
* $data = []
* ) {
* }
* }
* </code>
*/
interface RequestMethodInterface
{
const METHOD_HEAD = 'HEAD';
const METHOD_GET = 'GET';
const METHOD_POST = 'POST';
const METHOD_PUT = 'PUT';
const METHOD_PATCH = 'PATCH';
const METHOD_DELETE = 'DELETE';
const METHOD_PURGE = 'PURGE';
const METHOD_OPTIONS = 'OPTIONS';
const METHOD_TRACE = 'TRACE';
const METHOD_CONNECT = 'CONNECT';
}

View File

@ -0,0 +1,107 @@
<?php
namespace Fig\Http\Message;
/**
* Defines constants for common HTTP status code.
*
* @see https://tools.ietf.org/html/rfc2295#section-8.1
* @see https://tools.ietf.org/html/rfc2324#section-2.3
* @see https://tools.ietf.org/html/rfc2518#section-9.7
* @see https://tools.ietf.org/html/rfc2774#section-7
* @see https://tools.ietf.org/html/rfc3229#section-10.4
* @see https://tools.ietf.org/html/rfc4918#section-11
* @see https://tools.ietf.org/html/rfc5842#section-7.1
* @see https://tools.ietf.org/html/rfc5842#section-7.2
* @see https://tools.ietf.org/html/rfc6585#section-3
* @see https://tools.ietf.org/html/rfc6585#section-4
* @see https://tools.ietf.org/html/rfc6585#section-5
* @see https://tools.ietf.org/html/rfc6585#section-6
* @see https://tools.ietf.org/html/rfc7231#section-6
* @see https://tools.ietf.org/html/rfc7238#section-3
* @see https://tools.ietf.org/html/rfc7725#section-3
* @see https://tools.ietf.org/html/rfc7540#section-9.1.2
* @see https://tools.ietf.org/html/rfc8297#section-2
* @see https://tools.ietf.org/html/rfc8470#section-7
* Usage:
*
* <code>
* class ResponseFactory implements StatusCodeInterface
* {
* public function createResponse($code = self::STATUS_OK)
* {
* }
* }
* </code>
*/
interface StatusCodeInterface
{
// Informational 1xx
const STATUS_CONTINUE = 100;
const STATUS_SWITCHING_PROTOCOLS = 101;
const STATUS_PROCESSING = 102;
const STATUS_EARLY_HINTS = 103;
// Successful 2xx
const STATUS_OK = 200;
const STATUS_CREATED = 201;
const STATUS_ACCEPTED = 202;
const STATUS_NON_AUTHORITATIVE_INFORMATION = 203;
const STATUS_NO_CONTENT = 204;
const STATUS_RESET_CONTENT = 205;
const STATUS_PARTIAL_CONTENT = 206;
const STATUS_MULTI_STATUS = 207;
const STATUS_ALREADY_REPORTED = 208;
const STATUS_IM_USED = 226;
// Redirection 3xx
const STATUS_MULTIPLE_CHOICES = 300;
const STATUS_MOVED_PERMANENTLY = 301;
const STATUS_FOUND = 302;
const STATUS_SEE_OTHER = 303;
const STATUS_NOT_MODIFIED = 304;
const STATUS_USE_PROXY = 305;
const STATUS_RESERVED = 306;
const STATUS_TEMPORARY_REDIRECT = 307;
const STATUS_PERMANENT_REDIRECT = 308;
// Client Errors 4xx
const STATUS_BAD_REQUEST = 400;
const STATUS_UNAUTHORIZED = 401;
const STATUS_PAYMENT_REQUIRED = 402;
const STATUS_FORBIDDEN = 403;
const STATUS_NOT_FOUND = 404;
const STATUS_METHOD_NOT_ALLOWED = 405;
const STATUS_NOT_ACCEPTABLE = 406;
const STATUS_PROXY_AUTHENTICATION_REQUIRED = 407;
const STATUS_REQUEST_TIMEOUT = 408;
const STATUS_CONFLICT = 409;
const STATUS_GONE = 410;
const STATUS_LENGTH_REQUIRED = 411;
const STATUS_PRECONDITION_FAILED = 412;
const STATUS_PAYLOAD_TOO_LARGE = 413;
const STATUS_URI_TOO_LONG = 414;
const STATUS_UNSUPPORTED_MEDIA_TYPE = 415;
const STATUS_RANGE_NOT_SATISFIABLE = 416;
const STATUS_EXPECTATION_FAILED = 417;
const STATUS_IM_A_TEAPOT = 418;
const STATUS_MISDIRECTED_REQUEST = 421;
const STATUS_UNPROCESSABLE_ENTITY = 422;
const STATUS_LOCKED = 423;
const STATUS_FAILED_DEPENDENCY = 424;
const STATUS_TOO_EARLY = 425;
const STATUS_UPGRADE_REQUIRED = 426;
const STATUS_PRECONDITION_REQUIRED = 428;
const STATUS_TOO_MANY_REQUESTS = 429;
const STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
const STATUS_UNAVAILABLE_FOR_LEGAL_REASONS = 451;
// Server Errors 5xx
const STATUS_INTERNAL_SERVER_ERROR = 500;
const STATUS_NOT_IMPLEMENTED = 501;
const STATUS_BAD_GATEWAY = 502;
const STATUS_SERVICE_UNAVAILABLE = 503;
const STATUS_GATEWAY_TIMEOUT = 504;
const STATUS_VERSION_NOT_SUPPORTED = 505;
const STATUS_VARIANT_ALSO_NEGOTIATES = 506;
const STATUS_INSUFFICIENT_STORAGE = 507;
const STATUS_LOOP_DETECTED = 508;
const STATUS_NOT_EXTENDED = 510;
const STATUS_NETWORK_AUTHENTICATION_REQUIRED = 511;
}

View File

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

View File

@ -0,0 +1,73 @@
# Serializable Closure
<a href="https://github.com/laravel/serializable-closure/actions">
<img src="https://github.com/laravel/serializable-closure/workflows/tests/badge.svg" alt="Build Status">
</a>
<a href="https://packagist.org/packages/laravel/serializable-closure">
<img src="https://img.shields.io/packagist/dt/laravel/serializable-closure" alt="Total Downloads">
</a>
<a href="https://packagist.org/packages/laravel/serializable-closure">
<img src="https://img.shields.io/packagist/v/laravel/serializable-closure" alt="Latest Stable Version">
</a>
<a href="https://packagist.org/packages/laravel/serializable-closure">
<img src="https://img.shields.io/packagist/l/laravel/serializable-closure" alt="License">
</a>
## Introduction
> This project is a fork of the excellent [opis/closure: 3.x](https://github.com/opis/closure) package. At Laravel, we decided to fork this package as the upcoming version [4.x](https://github.com/opis/closure) is a complete rewrite on top of the [FFI extension](https://www.php.net/manual/en/book.ffi.php). As Laravel is a web framework, and FFI is not enabled by default in web requests, this fork allows us to keep using the `3.x` series while adding support for new PHP versions.
Laravel Serializable Closure provides an easy and secure way to **serialize closures in PHP**.
## Official Documentation
### Installation
> **Requires [PHP 7.4+](https://php.net/releases/)**
First, install Laravel Serializable Closure via the [Composer](https://getcomposer.org/) package manager:
```bash
composer require laravel/serializable-closure
```
### Usage
You may serialize a closure this way:
```php
use Laravel\SerializableClosure\SerializableClosure;
$closure = fn () => 'james';
// Recommended
SerializableClosure::setSecretKey('secret');
$serialized = serialize(new SerializableClosure($closure));
$closure = unserialize($serialized)->getClosure();
echo $closure(); // james;
```
### Caveats
* Anonymous classes cannot be created within closures.
* Attributes cannot be used within closures.
* Serializing closures on REPL environments like Laravel Tinker is not supported.
* Serializing closures that reference objects with readonly properties is not supported.
## Contributing
Thank you for considering contributing to Serializable Closure! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions).
## Code of Conduct
In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct).
## Security Vulnerabilities
Please review [our security policy](https://github.com/laravel/serializable-closure/security/policy) on how to report security vulnerabilities.
## License
Serializable Closure is open-sourced software licensed under the [MIT license](LICENSE.md).

View File

@ -0,0 +1,52 @@
{
"name": "laravel/serializable-closure",
"description": "Laravel Serializable Closure provides an easy and secure way to serialize closures in PHP.",
"keywords": ["laravel", "Serializable", "closure"],
"license": "MIT",
"support": {
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Nuno Maduro",
"email": "nuno@laravel.com"
}
],
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
"nesbot/carbon": "^2.61",
"pestphp/pest": "^1.21.3",
"phpstan/phpstan": "^1.8.2",
"symfony/var-dumper": "^5.4.11"
},
"autoload": {
"psr-4": {
"Laravel\\SerializableClosure\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"extra": {
"branch-alias": {
"dev-master": "1.x-dev"
}
},
"config": {
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -0,0 +1,20 @@
<?php
namespace Laravel\SerializableClosure\Contracts;
interface Serializable
{
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke();
/**
* Gets the closure that got serialized/unserialized.
*
* @return \Closure
*/
public function getClosure();
}

View File

@ -0,0 +1,22 @@
<?php
namespace Laravel\SerializableClosure\Contracts;
interface Signer
{
/**
* Sign the given serializable.
*
* @param string $serializable
* @return array
*/
public function sign($serializable);
/**
* Verify the given signature.
*
* @param array $signature
* @return bool
*/
public function verify($signature);
}

View File

@ -0,0 +1,19 @@
<?php
namespace Laravel\SerializableClosure\Exceptions;
use Exception;
class InvalidSignatureException extends Exception
{
/**
* Create a new exception instance.
*
* @param string $message
* @return void
*/
public function __construct($message = 'Your serialized closure might have been modified or it\'s unsafe to be unserialized.')
{
parent::__construct($message);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Laravel\SerializableClosure\Exceptions;
use Exception;
class MissingSecretKeyException extends Exception
{
/**
* Create a new exception instance.
*
* @param string $message
* @return void
*/
public function __construct($message = 'No serializable closure secret key has been specified.')
{
parent::__construct($message);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace Laravel\SerializableClosure\Exceptions;
use Exception;
class PhpVersionNotSupportedException extends Exception
{
/**
* Create a new exception instance.
*
* @param string $message
* @return void
*/
public function __construct($message = 'PHP 7.3 is not supported.')
{
parent::__construct($message);
}
}

View File

@ -0,0 +1,139 @@
<?php
namespace Laravel\SerializableClosure;
use Closure;
use Laravel\SerializableClosure\Exceptions\InvalidSignatureException;
use Laravel\SerializableClosure\Exceptions\PhpVersionNotSupportedException;
use Laravel\SerializableClosure\Serializers\Signed;
use Laravel\SerializableClosure\Signers\Hmac;
class SerializableClosure
{
/**
* The closure's serializable.
*
* @var \Laravel\SerializableClosure\Contracts\Serializable
*/
protected $serializable;
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct(Closure $closure)
{
if (\PHP_VERSION_ID < 70400) {
throw new PhpVersionNotSupportedException();
}
$this->serializable = Serializers\Signed::$signer
? new Serializers\Signed($closure)
: new Serializers\Native($closure);
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
if (\PHP_VERSION_ID < 70400) {
throw new PhpVersionNotSupportedException();
}
return call_user_func_array($this->serializable, func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
if (\PHP_VERSION_ID < 70400) {
throw new PhpVersionNotSupportedException();
}
return $this->serializable->getClosure();
}
/**
* Create a new unsigned serializable closure instance.
*
* @param Closure $closure
* @return \Laravel\SerializableClosure\UnsignedSerializableClosure
*/
public static function unsigned(Closure $closure)
{
return new UnsignedSerializableClosure($closure);
}
/**
* Sets the serializable closure secret key.
*
* @param string|null $secret
* @return void
*/
public static function setSecretKey($secret)
{
Serializers\Signed::$signer = $secret
? new Hmac($secret)
: null;
}
/**
* Sets the serializable closure secret key.
*
* @param \Closure|null $transformer
* @return void
*/
public static function transformUseVariablesUsing($transformer)
{
Serializers\Native::$transformUseVariables = $transformer;
}
/**
* Sets the serializable closure secret key.
*
* @param \Closure|null $resolver
* @return void
*/
public static function resolveUseVariablesUsing($resolver)
{
Serializers\Native::$resolveUseVariables = $resolver;
}
/**
* Get the serializable representation of the closure.
*
* @return array
*/
public function __serialize()
{
return [
'serializable' => $this->serializable,
];
}
/**
* Restore the closure after serialization.
*
* @param array $data
* @return void
*
* @throws \Laravel\SerializableClosure\Exceptions\InvalidSignatureException
*/
public function __unserialize($data)
{
if (Signed::$signer && ! $data['serializable'] instanceof Signed) {
throw new InvalidSignatureException();
}
$this->serializable = $data['serializable'];
}
}

View File

@ -0,0 +1,514 @@
<?php
namespace Laravel\SerializableClosure\Serializers;
use Closure;
use DateTimeInterface;
use Laravel\SerializableClosure\Contracts\Serializable;
use Laravel\SerializableClosure\SerializableClosure;
use Laravel\SerializableClosure\Support\ClosureScope;
use Laravel\SerializableClosure\Support\ClosureStream;
use Laravel\SerializableClosure\Support\ReflectionClosure;
use Laravel\SerializableClosure\Support\SelfReference;
use Laravel\SerializableClosure\UnsignedSerializableClosure;
use ReflectionObject;
use UnitEnum;
class Native implements Serializable
{
/**
* Transform the use variables before serialization.
*
* @var \Closure|null
*/
public static $transformUseVariables;
/**
* Resolve the use variables after unserialization.
*
* @var \Closure|null
*/
public static $resolveUseVariables;
/**
* The closure to be serialized/unserialized.
*
* @var \Closure
*/
protected $closure;
/**
* The closure's reflection.
*
* @var \Laravel\SerializableClosure\Support\ReflectionClosure|null
*/
protected $reflector;
/**
* The closure's code.
*
* @var array|null
*/
protected $code;
/**
* The closure's reference.
*
* @var string
*/
protected $reference;
/**
* The closure's scope.
*
* @var \Laravel\SerializableClosure\Support\ClosureScope|null
*/
protected $scope;
/**
* The "key" that marks an array as recursive.
*/
const ARRAY_RECURSIVE_KEY = 'LARAVEL_SERIALIZABLE_RECURSIVE_KEY';
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct(Closure $closure)
{
$this->closure = $closure;
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
return call_user_func_array($this->closure, func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
return $this->closure;
}
/**
* Get the serializable representation of the closure.
*
* @return array
*/
public function __serialize()
{
if ($this->scope === null) {
$this->scope = new ClosureScope();
$this->scope->toSerialize++;
}
$this->scope->serializations++;
$scope = $object = null;
$reflector = $this->getReflector();
if ($reflector->isBindingRequired()) {
$object = $reflector->getClosureThis();
static::wrapClosures($object, $this->scope);
}
if ($scope = $reflector->getClosureScopeClass()) {
$scope = $scope->name;
}
$this->reference = spl_object_hash($this->closure);
$this->scope[$this->closure] = $this;
$use = $reflector->getUseVariables();
if (static::$transformUseVariables) {
$use = call_user_func(static::$transformUseVariables, $reflector->getUseVariables());
}
$code = $reflector->getCode();
$this->mapByReference($use);
$data = [
'use' => $use,
'function' => $code,
'scope' => $scope,
'this' => $object,
'self' => $this->reference,
];
if (! --$this->scope->serializations && ! --$this->scope->toSerialize) {
$this->scope = null;
}
return $data;
}
/**
* Restore the closure after serialization.
*
* @param array $data
* @return void
*/
public function __unserialize($data)
{
ClosureStream::register();
$this->code = $data;
unset($data);
$this->code['objects'] = [];
if ($this->code['use']) {
$this->scope = new ClosureScope();
if (static::$resolveUseVariables) {
$this->code['use'] = call_user_func(static::$resolveUseVariables, $this->code['use']);
}
$this->mapPointers($this->code['use']);
extract($this->code['use'], EXTR_OVERWRITE | EXTR_REFS);
$this->scope = null;
}
$this->closure = include ClosureStream::STREAM_PROTO.'://'.$this->code['function'];
if ($this->code['this'] === $this) {
$this->code['this'] = null;
}
$this->closure = $this->closure->bindTo($this->code['this'], $this->code['scope']);
if (! empty($this->code['objects'])) {
foreach ($this->code['objects'] as $item) {
$item['property']->setValue($item['instance'], $item['object']->getClosure());
}
}
$this->code = $this->code['function'];
}
/**
* Ensures the given closures are serializable.
*
* @param mixed $data
* @param \Laravel\SerializableClosure\Support\ClosureScope $storage
* @return void
*/
public static function wrapClosures(&$data, $storage)
{
if ($data instanceof Closure) {
$data = new static($data);
} elseif (is_array($data)) {
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}
$data[self::ARRAY_RECURSIVE_KEY] = true;
foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
}
static::wrapClosures($value, $storage);
}
unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
if (isset($storage[$data])) {
$data = $storage[$data];
return;
}
$data = $storage[$data] = clone $data;
foreach ($data as &$value) {
static::wrapClosures($value, $storage);
}
unset($value);
} elseif (is_object($data) && ! $data instanceof static && ! $data instanceof UnitEnum) {
if (isset($storage[$data])) {
$data = $storage[$data];
return;
}
$instance = $data;
$reflection = new ReflectionObject($instance);
if (! $reflection->isUserDefined()) {
$storage[$instance] = $data;
return;
}
$storage[$instance] = $data = $reflection->newInstanceWithoutConstructor();
do {
if (! $reflection->isUserDefined()) {
break;
}
foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
continue;
}
$property->setAccessible(true);
if (PHP_VERSION >= 7.4 && ! $property->isInitialized($instance)) {
continue;
}
$value = $property->getValue($instance);
if (is_array($value) || is_object($value)) {
static::wrapClosures($value, $storage);
}
$property->setValue($data, $value);
}
} while ($reflection = $reflection->getParentClass());
}
}
/**
* Gets the closure's reflector.
*
* @return \Laravel\SerializableClosure\Support\ReflectionClosure
*/
public function getReflector()
{
if ($this->reflector === null) {
$this->code = null;
$this->reflector = new ReflectionClosure($this->closure);
}
return $this->reflector;
}
/**
* Internal method used to map closure pointers.
*
* @param mixed $data
* @return void
*/
protected function mapPointers(&$data)
{
$scope = $this->scope;
if ($data instanceof static) {
$data = &$data->closure;
} elseif (is_array($data)) {
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}
$data[self::ARRAY_RECURSIVE_KEY] = true;
foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
} elseif ($value instanceof static) {
$data[$key] = &$value->closure;
} elseif ($value instanceof SelfReference && $value->hash === $this->code['self']) {
$data[$key] = &$this->closure;
} else {
$this->mapPointers($value);
}
}
unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
if (isset($scope[$data])) {
return;
}
$scope[$data] = true;
foreach ($data as $key => &$value) {
if ($value instanceof SelfReference && $value->hash === $this->code['self']) {
$data->{$key} = &$this->closure;
} elseif (is_array($value) || is_object($value)) {
$this->mapPointers($value);
}
}
unset($value);
} elseif (is_object($data) && ! ($data instanceof Closure)) {
if (isset($scope[$data])) {
return;
}
$scope[$data] = true;
$reflection = new ReflectionObject($data);
do {
if (! $reflection->isUserDefined()) {
break;
}
foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
continue;
}
$property->setAccessible(true);
if (PHP_VERSION >= 7.4 && ! $property->isInitialized($data)) {
continue;
}
$item = $property->getValue($data);
if ($item instanceof SerializableClosure || $item instanceof UnsignedSerializableClosure || ($item instanceof SelfReference && $item->hash === $this->code['self'])) {
$this->code['objects'][] = [
'instance' => $data,
'property' => $property,
'object' => $item instanceof SelfReference ? $this : $item,
];
} elseif (is_array($item) || is_object($item)) {
$this->mapPointers($item);
$property->setValue($data, $item);
}
}
} while ($reflection = $reflection->getParentClass());
}
}
/**
* Internal method used to map closures by reference.
*
* @param mixed $data
* @return void
*/
protected function mapByReference(&$data)
{
if ($data instanceof Closure) {
if ($data === $this->closure) {
$data = new SelfReference($this->reference);
return;
}
if (isset($this->scope[$data])) {
$data = $this->scope[$data];
return;
}
$instance = new static($data);
$instance->scope = $this->scope;
$data = $this->scope[$data] = $instance;
} elseif (is_array($data)) {
if (isset($data[self::ARRAY_RECURSIVE_KEY])) {
return;
}
$data[self::ARRAY_RECURSIVE_KEY] = true;
foreach ($data as $key => &$value) {
if ($key === self::ARRAY_RECURSIVE_KEY) {
continue;
}
$this->mapByReference($value);
}
unset($value);
unset($data[self::ARRAY_RECURSIVE_KEY]);
} elseif ($data instanceof \stdClass) {
if (isset($this->scope[$data])) {
$data = $this->scope[$data];
return;
}
$instance = $data;
$this->scope[$instance] = $data = clone $data;
foreach ($data as &$value) {
$this->mapByReference($value);
}
unset($value);
} elseif (is_object($data) && ! $data instanceof SerializableClosure && ! $data instanceof UnsignedSerializableClosure) {
if (isset($this->scope[$data])) {
$data = $this->scope[$data];
return;
}
$instance = $data;
if ($data instanceof DateTimeInterface) {
$this->scope[$instance] = $data;
return;
}
if ($data instanceof UnitEnum) {
$this->scope[$instance] = $data;
return;
}
$reflection = new ReflectionObject($data);
if (! $reflection->isUserDefined()) {
$this->scope[$instance] = $data;
return;
}
$this->scope[$instance] = $data = $reflection->newInstanceWithoutConstructor();
do {
if (! $reflection->isUserDefined()) {
break;
}
foreach ($reflection->getProperties() as $property) {
if ($property->isStatic() || ! $property->getDeclaringClass()->isUserDefined()) {
continue;
}
$property->setAccessible(true);
if (PHP_VERSION >= 7.4 && ! $property->isInitialized($instance)) {
continue;
}
$value = $property->getValue($instance);
if (is_array($value) || is_object($value)) {
$this->mapByReference($value);
}
$property->setValue($data, $value);
}
} while ($reflection = $reflection->getParentClass());
}
}
}

View File

@ -0,0 +1,91 @@
<?php
namespace Laravel\SerializableClosure\Serializers;
use Laravel\SerializableClosure\Contracts\Serializable;
use Laravel\SerializableClosure\Exceptions\InvalidSignatureException;
use Laravel\SerializableClosure\Exceptions\MissingSecretKeyException;
class Signed implements Serializable
{
/**
* The signer that will sign and verify the closure's signature.
*
* @var \Laravel\SerializableClosure\Contracts\Signer|null
*/
public static $signer;
/**
* The closure to be serialized/unserialized.
*
* @var \Closure
*/
protected $closure;
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct($closure)
{
$this->closure = $closure;
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
return call_user_func_array($this->closure, func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
return $this->closure;
}
/**
* Get the serializable representation of the closure.
*
* @return array
*/
public function __serialize()
{
if (! static::$signer) {
throw new MissingSecretKeyException();
}
return static::$signer->sign(
serialize(new Native($this->closure))
);
}
/**
* Restore the closure after serialization.
*
* @param array $signature
* @return void
*
* @throws \Laravel\SerializableClosure\Exceptions\InvalidSignatureException
*/
public function __unserialize($signature)
{
if (static::$signer && ! static::$signer->verify($signature)) {
throw new InvalidSignatureException();
}
/** @var \Laravel\SerializableClosure\Contracts\Serializable $serializable */
$serializable = unserialize($signature['serializable']);
$this->closure = $serializable->getClosure();
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace Laravel\SerializableClosure\Signers;
use Laravel\SerializableClosure\Contracts\Signer;
class Hmac implements Signer
{
/**
* The secret key.
*
* @var string
*/
protected $secret;
/**
* Creates a new signer instance.
*
* @param string $secret
* @return void
*/
public function __construct($secret)
{
$this->secret = $secret;
}
/**
* Sign the given serializable.
*
* @param string $serialized
* @return array
*/
public function sign($serialized)
{
return [
'serializable' => $serialized,
'hash' => base64_encode(hash_hmac('sha256', $serialized, $this->secret, true)),
];
}
/**
* Verify the given signature.
*
* @param array $signature
* @return bool
*/
public function verify($signature)
{
return hash_equals(base64_encode(
hash_hmac('sha256', $signature['serializable'], $this->secret, true)
), $signature['hash']);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace Laravel\SerializableClosure\Support;
use SplObjectStorage;
class ClosureScope extends SplObjectStorage
{
/**
* The number of serializations in current scope.
*
* @var int
*/
public $serializations = 0;
/**
* The number of closures that have to be serialized.
*
* @var int
*/
public $toSerialize = 0;
}

View File

@ -0,0 +1,179 @@
<?php
namespace Laravel\SerializableClosure\Support;
#[\AllowDynamicProperties]
class ClosureStream
{
/**
* The stream protocol.
*/
const STREAM_PROTO = 'laravel-serializable-closure';
/**
* Checks if this stream is registered.
*
* @var bool
*/
protected static $isRegistered = false;
/**
* The stream content.
*
* @var string
*/
protected $content;
/**
* The stream content.
*
* @var int
*/
protected $length;
/**
* The stream pointer.
*
* @var int
*/
protected $pointer = 0;
/**
* Opens file or URL.
*
* @param string $path
* @param string $mode
* @param string $options
* @param string|null $opened_path
* @return bool
*/
public function stream_open($path, $mode, $options, &$opened_path)
{
$this->content = "<?php\nreturn ".substr($path, strlen(static::STREAM_PROTO.'://')).';';
$this->length = strlen($this->content);
return true;
}
/**
* Read from stream.
*
* @param int $count
* @return string
*/
public function stream_read($count)
{
$value = substr($this->content, $this->pointer, $count);
$this->pointer += $count;
return $value;
}
/**
* Tests for end-of-file on a file pointer.
*
* @return bool
*/
public function stream_eof()
{
return $this->pointer >= $this->length;
}
/**
* Change stream options.
*
* @param int $option
* @param int $arg1
* @param int $arg2
* @return bool
*/
public function stream_set_option($option, $arg1, $arg2)
{
return false;
}
/**
* Retrieve information about a file resource.
*
* @return array|bool
*/
public function stream_stat()
{
$stat = stat(__FILE__);
// @phpstan-ignore-next-line
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
/**
* Retrieve information about a file.
*
* @param string $path
* @param int $flags
* @return array|bool
*/
public function url_stat($path, $flags)
{
$stat = stat(__FILE__);
// @phpstan-ignore-next-line
$stat[7] = $stat['size'] = $this->length;
return $stat;
}
/**
* Seeks to specific location in a stream.
*
* @param int $offset
* @param int $whence
* @return bool
*/
public function stream_seek($offset, $whence = SEEK_SET)
{
$crt = $this->pointer;
switch ($whence) {
case SEEK_SET:
$this->pointer = $offset;
break;
case SEEK_CUR:
$this->pointer += $offset;
break;
case SEEK_END:
$this->pointer = $this->length + $offset;
break;
}
if ($this->pointer < 0 || $this->pointer >= $this->length) {
$this->pointer = $crt;
return false;
}
return true;
}
/**
* Retrieve the current position of a stream.
*
* @return int
*/
public function stream_tell()
{
return $this->pointer;
}
/**
* Registers the stream.
*
* @return void
*/
public static function register()
{
if (! static::$isRegistered) {
static::$isRegistered = stream_wrapper_register(static::STREAM_PROTO, __CLASS__);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
<?php
namespace Laravel\SerializableClosure\Support;
class SelfReference
{
/**
* The unique hash representing the object.
*
* @var string
*/
public $hash;
/**
* Creates a new self reference instance.
*
* @param string $hash
* @return void
*/
public function __construct($hash)
{
$this->hash = $hash;
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Laravel\SerializableClosure;
use Closure;
use Laravel\SerializableClosure\Exceptions\PhpVersionNotSupportedException;
class UnsignedSerializableClosure
{
/**
* The closure's serializable.
*
* @var \Laravel\SerializableClosure\Contracts\Serializable
*/
protected $serializable;
/**
* Creates a new serializable closure instance.
*
* @param \Closure $closure
* @return void
*/
public function __construct(Closure $closure)
{
if (\PHP_VERSION_ID < 70400) {
throw new PhpVersionNotSupportedException();
}
$this->serializable = new Serializers\Native($closure);
}
/**
* Resolve the closure with the given arguments.
*
* @return mixed
*/
public function __invoke()
{
if (\PHP_VERSION_ID < 70400) {
throw new PhpVersionNotSupportedException();
}
return call_user_func_array($this->serializable, func_get_args());
}
/**
* Gets the closure.
*
* @return \Closure
*/
public function getClosure()
{
if (\PHP_VERSION_ID < 70400) {
throw new PhpVersionNotSupportedException();
}
return $this->serializable->getClosure();
}
/**
* Get the serializable representation of the closure.
*
* @return array
*/
public function __serialize()
{
return [
'serializable' => $this->serializable,
];
}
/**
* Restore the closure after serialization.
*
* @param array $data
* @return void
*/
public function __unserialize($data)
{
$this->serializable = $data['serializable'];
}
}

42
code/vendor/league/config/CHANGELOG.md vendored Normal file
View File

@ -0,0 +1,42 @@
# Change Log
All notable changes to this project will be documented in this file.
Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [Unreleased][unreleased]
## [1.2.0] - 2022-12-11
### Changed
- Values can now be set prior to the corresponding schema being registered.
- `exists()` and `get()` now only trigger validation for the relevant schema, not the entire config at once.
## [1.1.1] - 2021-08-14
### Changed
- Bumped the minimum version of dflydev/dot-access-data for PHP 8.1 support
## [1.1.0] - 2021-06-19
### Changed
- Bumped the minimum PHP version to 7.4+
- Bumped the minimum version of nette/schema to 1.2.0
## [1.0.1] - 2021-05-31
### Fixed
- Fixed the `ConfigurationExceptionInterface` marker interface not extending `Throwable` (#2)
## [1.0.0] - 2021-05-31
Initial release! 🎉
[unreleased]: https://github.com/thephpleague/config/compare/v1.2.0...main
[1.2.0]: https://github.com/thephpleague/config/compare/v1.1.1...v.1.2.0
[1.1.1]: https://github.com/thephpleague/config/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/thephpleague/config/compare/v1.0.1...v1.1.0
[1.0.1]: https://github.com/thephpleague/config/compare/v1.0.0...v1.0.1
[1.0.0]: https://github.com/thephpleague/config/releases/tag/v1.0.0

28
code/vendor/league/config/LICENSE.md vendored Normal file
View File

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2022, Colin O'Dell. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

153
code/vendor/league/config/README.md vendored Normal file
View File

@ -0,0 +1,153 @@
# league/config
[![Latest Version](https://img.shields.io/packagist/v/league/config.svg?style=flat-square)](https://packagist.org/packages/league/config)
[![Total Downloads](https://img.shields.io/packagist/dt/league/config.svg?style=flat-square)](https://packagist.org/packages/league/config)
[![Software License](https://img.shields.io/badge/License-BSD--3-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/github/workflow/status/thephpleague/config/Tests/main.svg?style=flat-square)](https://github.com/thephpleague/config/actions?query=workflow%3ATests+branch%3Amain)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/config.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/config/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/config.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/config)
[![Sponsor development of this project](https://img.shields.io/badge/sponsor%20this%20package-%E2%9D%A4-ff69b4.svg?style=flat-square)](https://www.colinodell.com/sponsor)
**league/config** helps you define nested configuration arrays with strict schemas and access configuration values with dot notation. It was created by [Colin O'Dell][@colinodell].
## 📦 Installation
This project requires PHP 7.4 or higher. To install it via [Composer] simply run:
```bash
composer require league/config
```
## 🧰️ Basic Usage
The `Configuration` class provides everything you need to define the configuration structure and fetch values:
```php
use League\Config\Configuration;
use Nette\Schema\Expect;
// Define your configuration schema
$config = new Configuration([
'database' => Expect::structure([
'driver' => Expect::anyOf('mysql', 'postgresql', 'sqlite')->required(),
'host' => Expect::string()->default('localhost'),
'port' => Expect::int()->min(1)->max(65535),
'ssl' => Expect::bool(),
'database' => Expect::string()->required(),
'username' => Expect::string()->required(),
'password' => Expect::string()->nullable(),
]),
'logging' => Expect::structure([
'enabled' => Expect::bool()->default($_ENV['DEBUG'] == true),
'file' => Expect::string()->deprecated("use logging.path instead"),
'path' => Expect::string()->assert(function ($path) { return \is_writeable($path); })->required(),
]),
]);
// Set the values, either all at once with `merge()`:
$config->merge([
'database' => [
'driver' => 'mysql',
'port' => 3306,
'database' => 'mydb',
'username' => 'user',
'password' => 'secret',
],
]);
// Or one-at-a-time with `set()`:
$config->set('logging.path', '/var/log/myapp.log');
// You can now retrieve those values with `get()`.
// Validation and defaults will be applied for you automatically
$config->get('database'); // Fetches the entire "database" section as an array
$config->get('database.driver'); // Fetch a specific nested value with dot notation
$config->get('database/driver'); // Fetch a specific nested value with slash notation
$config->get('database.host'); // Returns the default value "localhost"
$config->get('logging.path'); // Guaranteed to be writeable thanks to the assertion in the schema
// If validation fails an `InvalidConfigurationException` will be thrown:
$config->set('database.driver', 'mongodb');
$config->get('database.driver'); // InvalidConfigurationException
// Attempting to fetch a non-existent key will result in an `InvalidConfigurationException`
$config->get('foo.bar');
// You could avoid this by checking whether that item exists:
$config->exists('foo.bar'); // Returns `false`
```
## 📓 Documentation
Full documentation can be found at [config.thephpleague.com][docs].
## 💭 Philosophy
This library aims to provide a **simple yet opinionated** approach to configuration with the following goals:
- The configuration should operate on **arrays with nested values** which are easily accessible
- The configuration structure should be **defined with strict schemas** defining the overall structure, allowed types, and allowed values
- Schemas should be defined using a **simple, fluent interface**
- You should be able to **add and combine schemas but never modify existing ones**
- Both the configuration values and the schema should be **defined and managed with PHP code**
- Schemas should be **immutable**; they should never change once they are set
- Configuration values should never define or influence the schemas
As a result, this library will likely **never** support features like:
- Loading and/or exporting configuration values or schemas using YAML, XML, or other files
- Parsing configuration values from a command line or other user interface
- Dynamically changing the schema, allowed values, or default values based on other configuration values
If you need that functionality you should check out other libraries like:
- [symfony/config]
- [symfony/options-resolver]
- [hassankhan/config]
- [consolidation/config]
- [laminas/laminas-config]
## 🏷️ Versioning
[SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase.
Any classes or methods marked `@internal` are not intended for use outside this library and are subject to breaking changes at any time, so please avoid using them.
## 🛠️ Maintenance & Support
When a new **minor** version (e.g. `1.0` -> `1.1`) is released, the previous one (`1.0`) will continue to receive security and critical bug fixes for *at least* 3 months.
When a new **major** version is released (e.g. `1.1` -> `2.0`), the previous one (`1.1`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out.
(This policy may change in the future and exceptions may be made on a case-by-case basis.)
## 👷‍️ Contributing
Contributions to this library are **welcome**! We only ask that you adhere to our [contributor guidelines] and avoid making changes that conflict with our Philosophy above.
## 🧪 Testing
```bash
composer test
```
## 📄 License
**league/config** is licensed under the BSD-3 license. See the [`LICENSE.md`][license] file for more details.
## 🗺️ Who Uses It?
This project is used by [league/commonmark][league-commonmark].
[docs]: https://config.thephpleague.com/
[@colinodell]: https://www.twitter.com/colinodell
[Composer]: https://getcomposer.org/
[PHP League]: https://thephpleague.com
[symfony/config]: https://symfony.com/doc/current/components/config.html
[symfony/options-resolver]: https://symfony.com/doc/current/components/options_resolver.html
[hassankhan/config]: https://github.com/hassankhan/config
[consolidation/config]: https://github.com/consolidation/config
[laminas/laminas-config]: https://docs.laminas.dev/laminas-config/
[contributor guidelines]: https://github.com/thephpleague/config/blob/main/.github/CONTRIBUTING.md
[license]: https://github.com/thephpleague/config/blob/main/LICENSE.md
[league-commonmark]: https://commonmark.thephpleague.com

69
code/vendor/league/config/composer.json vendored Normal file
View File

@ -0,0 +1,69 @@
{
"name": "league/config",
"type": "library",
"description": "Define configuration arrays with strict schemas and access values with dot notation",
"keywords": ["configuration","config","schema","array","nested","dot","dot-access"],
"homepage": "https://config.thephpleague.com",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com",
"role": "Lead Developer"
}
],
"support": {
"docs": "https://config.thephpleague.com/",
"issues": "https://github.com/thephpleague/config/issues",
"rss": "https://github.com/thephpleague/config/releases.atom",
"source": "https://github.com/thephpleague/config"
},
"require": {
"php": "^7.4 || ^8.0",
"dflydev/dot-access-data": "^3.0.1",
"nette/schema": "^1.2"
},
"require-dev": {
"phpstan/phpstan": "^1.8.2",
"phpunit/phpunit": "^9.5.5",
"scrutinizer/ocular": "^1.8.1",
"unleashedtech/php-coding-standard": "^3.1",
"vimeo/psalm": "^4.7.3"
},
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"League\\Config\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"League\\Config\\Tests\\": "tests"
}
},
"scripts": {
"phpcs": "phpcs",
"phpstan": "phpstan analyse",
"phpunit": "phpunit --no-coverage",
"psalm": "psalm",
"test": [
"@phpcs",
"@phpstan",
"@psalm",
"@phpunit"
]
},
"extra": {
"branch-alias": {
"dev-main": "1.2-dev"
}
},
"config": {
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
}
}

View File

@ -0,0 +1,255 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use Dflydev\DotAccessData\Data;
use Dflydev\DotAccessData\DataInterface;
use Dflydev\DotAccessData\Exception\DataException;
use Dflydev\DotAccessData\Exception\InvalidPathException;
use Dflydev\DotAccessData\Exception\MissingPathException;
use League\Config\Exception\UnknownOptionException;
use League\Config\Exception\ValidationException;
use Nette\Schema\Expect;
use Nette\Schema\Processor;
use Nette\Schema\Schema;
use Nette\Schema\ValidationException as NetteValidationException;
final class Configuration implements ConfigurationBuilderInterface, ConfigurationInterface
{
/** @psalm-readonly */
private Data $userConfig;
/**
* @var array<string, Schema>
*
* @psalm-allow-private-mutation
*/
private array $configSchemas = [];
/** @psalm-allow-private-mutation */
private Data $finalConfig;
/**
* @var array<string, mixed>
*
* @psalm-allow-private-mutation
*/
private array $cache = [];
/** @psalm-readonly */
private ConfigurationInterface $reader;
/**
* @param array<string, Schema> $baseSchemas
*/
public function __construct(array $baseSchemas = [])
{
$this->configSchemas = $baseSchemas;
$this->userConfig = new Data();
$this->finalConfig = new Data();
$this->reader = new ReadOnlyConfiguration($this);
}
/**
* Registers a new configuration schema at the given top-level key
*
* @psalm-allow-private-mutation
*/
public function addSchema(string $key, Schema $schema): void
{
$this->invalidate();
$this->configSchemas[$key] = $schema;
}
/**
* {@inheritDoc}
*
* @psalm-allow-private-mutation
*/
public function merge(array $config = []): void
{
$this->invalidate();
$this->userConfig->import($config, DataInterface::REPLACE);
}
/**
* {@inheritDoc}
*
* @psalm-allow-private-mutation
*/
public function set(string $key, $value): void
{
$this->invalidate();
try {
$this->userConfig->set($key, $value);
} catch (DataException $ex) {
throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
}
}
/**
* {@inheritDoc}
*
* @psalm-external-mutation-free
*/
public function get(string $key)
{
if (\array_key_exists($key, $this->cache)) {
return $this->cache[$key];
}
try {
$this->build(self::getTopLevelKey($key));
return $this->cache[$key] = $this->finalConfig->get($key);
} catch (InvalidPathException | MissingPathException $ex) {
throw new UnknownOptionException($ex->getMessage(), $key, (int) $ex->getCode(), $ex);
}
}
/**
* {@inheritDoc}
*
* @psalm-external-mutation-free
*/
public function exists(string $key): bool
{
if (\array_key_exists($key, $this->cache)) {
return true;
}
try {
$this->build(self::getTopLevelKey($key));
return $this->finalConfig->has($key);
} catch (InvalidPathException | UnknownOptionException $ex) {
return false;
}
}
/**
* @psalm-mutation-free
*/
public function reader(): ConfigurationInterface
{
return $this->reader;
}
/**
* @psalm-external-mutation-free
*/
private function invalidate(): void
{
$this->cache = [];
$this->finalConfig = new Data();
}
/**
* Applies the schema against the configuration to return the final configuration
*
* @throws ValidationException|UnknownOptionException|InvalidPathException
*
* @psalm-allow-private-mutation
*/
private function build(string $topLevelKey): void
{
if ($this->finalConfig->has($topLevelKey)) {
return;
}
if (! isset($this->configSchemas[$topLevelKey])) {
throw new UnknownOptionException(\sprintf('Missing config schema for "%s"', $topLevelKey), $topLevelKey);
}
try {
$userData = [$topLevelKey => $this->userConfig->get($topLevelKey)];
} catch (DataException $ex) {
$userData = [];
}
try {
$schema = $this->configSchemas[$topLevelKey];
$processor = new Processor();
$processed = $processor->process(Expect::structure([$topLevelKey => $schema]), $userData);
$this->raiseAnyDeprecationNotices($processor->getWarnings());
$this->finalConfig->import((array) self::convertStdClassesToArrays($processed));
} catch (NetteValidationException $ex) {
throw new ValidationException($ex);
}
}
/**
* Recursively converts stdClass instances to arrays
*
* @phpstan-template T
*
* @param T $data
*
* @return mixed
*
* @phpstan-return ($data is \stdClass ? array<string, mixed> : T)
*
* @psalm-pure
*/
private static function convertStdClassesToArrays($data)
{
if ($data instanceof \stdClass) {
$data = (array) $data;
}
if (\is_array($data)) {
foreach ($data as $k => $v) {
$data[$k] = self::convertStdClassesToArrays($v);
}
}
return $data;
}
/**
* @param string[] $warnings
*/
private function raiseAnyDeprecationNotices(array $warnings): void
{
foreach ($warnings as $warning) {
@\trigger_error($warning, \E_USER_DEPRECATED);
}
}
/**
* @throws InvalidPathException
*/
private static function getTopLevelKey(string $path): string
{
if (\strlen($path) === 0) {
throw new InvalidPathException('Path cannot be an empty string');
}
$path = \str_replace(['.', '/'], '.', $path);
$firstDelimiter = \strpos($path, '.');
if ($firstDelimiter === false) {
return $path;
}
return \substr($path, 0, $firstDelimiter);
}
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* Implement this class to facilitate setter injection of the configuration where needed
*/
interface ConfigurationAwareInterface
{
public function setConfiguration(ConfigurationInterface $configuration): void;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* An interface that provides the ability to set both the schema and configuration values
*/
interface ConfigurationBuilderInterface extends MutableConfigurationInterface, SchemaBuilderInterface
{
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use League\Config\Exception\UnknownOptionException;
use League\Config\Exception\ValidationException;
/**
* Interface for reading configuration values
*/
interface ConfigurationInterface
{
/**
* @param string $key Configuration option path/key
*
* @psalm-param non-empty-string $key
*
* @return mixed
*
* @throws ValidationException if the schema failed to validate the given input
* @throws UnknownOptionException if the requested key does not exist or is malformed
*/
public function get(string $key);
/**
* @param string $key Configuration option path/key
*
* @psalm-param non-empty-string $key
*
* @return bool Whether the given option exists
*
* @throws ValidationException if the schema failed to validate the given input
*/
public function exists(string $key): bool;
}

View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* Interface for a service which provides a readable configuration object
*/
interface ConfigurationProviderInterface
{
public function getConfiguration(): ConfigurationInterface;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
/**
* Marker interface for any/all exceptions thrown by this library
*/
interface ConfigurationExceptionInterface extends \Throwable
{
}

View File

@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
class InvalidConfigurationException extends \UnexpectedValueException implements ConfigurationExceptionInterface
{
/**
* @param string $option Name/path of the option
* @param mixed $valueGiven The invalid option that was provided
* @param ?string $description Additional text describing the issue (optional)
*/
public static function forConfigOption(string $option, $valueGiven, ?string $description = null): self
{
$message = \sprintf('Invalid config option for "%s": %s', $option, self::getDebugValue($valueGiven));
if ($description !== null) {
$message .= \sprintf(' (%s)', $description);
}
return new self($message);
}
/**
* @param mixed $value
*
* @psalm-pure
*/
private static function getDebugValue($value): string
{
if (\is_object($value)) {
return \get_class($value);
}
return \print_r($value, true);
}
}

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
use Throwable;
final class UnknownOptionException extends \InvalidArgumentException implements ConfigurationExceptionInterface
{
private string $path;
public function __construct(string $message, string $path, int $code = 0, ?Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
$this->path = $path;
}
public function getPath(): string
{
return $this->path;
}
}

View File

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config\Exception;
use Nette\Schema\ValidationException as NetteException;
final class ValidationException extends InvalidConfigurationException
{
/** @var string[] */
private array $messages;
public function __construct(NetteException $innerException)
{
parent::__construct($innerException->getMessage(), (int) $innerException->getCode(), $innerException);
$this->messages = $innerException->getMessages();
}
/**
* @return string[]
*/
public function getMessages(): array
{
return $this->messages;
}
}

View File

@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use League\Config\Exception\UnknownOptionException;
/**
* Interface for setting/merging user-defined configuration values into the configuration object
*/
interface MutableConfigurationInterface
{
/**
* @param mixed $value
*
* @throws UnknownOptionException if $key contains a nested path which doesn't point to an array value
*/
public function set(string $key, $value): void;
/**
* @param array<string, mixed> $config
*/
public function merge(array $config = []): void;
}

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
/**
* Provides read-only access to a given Configuration object
*/
final class ReadOnlyConfiguration implements ConfigurationInterface
{
private Configuration $config;
public function __construct(Configuration $config)
{
$this->config = $config;
}
/**
* {@inheritDoc}
*/
public function get(string $key)
{
return $this->config->get($key);
}
public function exists(string $key): bool
{
return $this->config->exists($key);
}
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/config package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\Config;
use Nette\Schema\Schema;
/**
* Interface that allows new schemas to be added to a configuration
*/
interface SchemaBuilderInterface
{
/**
* Registers a new configuration schema at the given top-level key
*/
public function addSchema(string $key, Schema $schema): void;
}

View File

@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\Local;
use League\MimeTypeDetection\MimeTypeDetector;
use function in_array;
class FallbackMimeTypeDetector implements MimeTypeDetector
{
private const INCONCLUSIVE_MIME_TYPES = [
'application/x-empty',
'text/plain',
'text/x-asm',
'application/octet-stream',
'inode/x-empty',
];
public function __construct(
private MimeTypeDetector $detector,
private array $inconclusiveMimetypes = self::INCONCLUSIVE_MIME_TYPES,
private bool $useInconclusiveMimeTypeFallback = false,
) {
}
public function detectMimeType(string $path, $contents): ?string
{
return $this->detector->detectMimeType($path, $contents);
}
public function detectMimeTypeFromBuffer(string $contents): ?string
{
return $this->detector->detectMimeTypeFromBuffer($contents);
}
public function detectMimeTypeFromPath(string $path): ?string
{
return $this->detector->detectMimeTypeFromPath($path);
}
public function detectMimeTypeFromFile(string $path): ?string
{
$mimeType = $this->detector->detectMimeTypeFromFile($path);
if ($mimeType !== null && ! in_array($mimeType, $this->inconclusiveMimetypes)) {
return $mimeType;
}
return $this->detector->detectMimeTypeFromPath($path) ?? ($this->useInconclusiveMimeTypeFallback ? $mimeType : null);
}
}

View File

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

View File

@ -0,0 +1,483 @@
<?php
declare(strict_types=1);
namespace League\Flysystem\Local;
use const DIRECTORY_SEPARATOR;
use const LOCK_EX;
use DirectoryIterator;
use FilesystemIterator;
use Generator;
use League\Flysystem\ChecksumProvider;
use League\Flysystem\Config;
use League\Flysystem\DirectoryAttributes;
use League\Flysystem\FileAttributes;
use League\Flysystem\FilesystemAdapter;
use League\Flysystem\PathPrefixer;
use League\Flysystem\SymbolicLinkEncountered;
use League\Flysystem\UnableToCopyFile;
use League\Flysystem\UnableToCreateDirectory;
use League\Flysystem\UnableToDeleteDirectory;
use League\Flysystem\UnableToDeleteFile;
use League\Flysystem\UnableToMoveFile;
use League\Flysystem\UnableToProvideChecksum;
use League\Flysystem\UnableToReadFile;
use League\Flysystem\UnableToRetrieveMetadata;
use League\Flysystem\UnableToSetVisibility;
use League\Flysystem\UnableToWriteFile;
use League\Flysystem\UnixVisibility\PortableVisibilityConverter;
use League\Flysystem\UnixVisibility\VisibilityConverter;
use League\MimeTypeDetection\FinfoMimeTypeDetector;
use League\MimeTypeDetection\MimeTypeDetector;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use SplFileInfo;
use Throwable;
use function chmod;
use function clearstatcache;
use function dirname;
use function error_clear_last;
use function error_get_last;
use function file_exists;
use function file_put_contents;
use function hash_file;
use function is_dir;
use function is_file;
use function mkdir;
use function rename;
class LocalFilesystemAdapter implements FilesystemAdapter, ChecksumProvider
{
/**
* @var int
*/
public const SKIP_LINKS = 0001;
/**
* @var int
*/
public const DISALLOW_LINKS = 0002;
private PathPrefixer $prefixer;
private VisibilityConverter $visibility;
private MimeTypeDetector $mimeTypeDetector;
private string $rootLocation;
/**
* @var bool
*/
private $rootLocationIsSetup = false;
public function __construct(
string $location,
?VisibilityConverter $visibility = null,
private int $writeFlags = LOCK_EX,
private int $linkHandling = self::DISALLOW_LINKS,
?MimeTypeDetector $mimeTypeDetector = null,
bool $lazyRootCreation = false,
bool $useInconclusiveMimeTypeFallback = false,
) {
$this->prefixer = new PathPrefixer($location, DIRECTORY_SEPARATOR);
$visibility ??= new PortableVisibilityConverter();
$this->visibility = $visibility;
$this->rootLocation = $location;
$this->mimeTypeDetector = $mimeTypeDetector ?? new FallbackMimeTypeDetector(
detector: new FinfoMimeTypeDetector(),
useInconclusiveMimeTypeFallback: $useInconclusiveMimeTypeFallback,
);
if ( ! $lazyRootCreation) {
$this->ensureRootDirectoryExists();
}
}
private function ensureRootDirectoryExists(): void
{
if ($this->rootLocationIsSetup) {
return;
}
$this->ensureDirectoryExists($this->rootLocation, $this->visibility->defaultForDirectories());
$this->rootLocationIsSetup = true;
}
public function write(string $path, string $contents, Config $config): void
{
$this->writeToFile($path, $contents, $config);
}
public function writeStream(string $path, $contents, Config $config): void
{
$this->writeToFile($path, $contents, $config);
}
/**
* @param resource|string $contents
*/
private function writeToFile(string $path, $contents, Config $config): void
{
$prefixedLocation = $this->prefixer->prefixPath($path);
$this->ensureRootDirectoryExists();
$this->ensureDirectoryExists(
dirname($prefixedLocation),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
error_clear_last();
if (@file_put_contents($prefixedLocation, $contents, $this->writeFlags) === false) {
throw UnableToWriteFile::atLocation($path, error_get_last()['message'] ?? '');
}
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($path, (string) $visibility);
}
}
public function delete(string $path): void
{
$location = $this->prefixer->prefixPath($path);
if ( ! file_exists($location)) {
return;
}
error_clear_last();
if ( ! @unlink($location)) {
throw UnableToDeleteFile::atLocation($location, error_get_last()['message'] ?? '');
}
}
public function deleteDirectory(string $prefix): void
{
$location = $this->prefixer->prefixPath($prefix);
if ( ! is_dir($location)) {
return;
}
$contents = $this->listDirectoryRecursively($location, RecursiveIteratorIterator::CHILD_FIRST);
/** @var SplFileInfo $file */
foreach ($contents as $file) {
if ( ! $this->deleteFileInfoObject($file)) {
throw UnableToDeleteDirectory::atLocation($prefix, "Unable to delete file at " . $file->getPathname());
}
}
unset($contents);
if ( ! @rmdir($location)) {
throw UnableToDeleteDirectory::atLocation($prefix, error_get_last()['message'] ?? '');
}
}
private function listDirectoryRecursively(
string $path,
int $mode = RecursiveIteratorIterator::SELF_FIRST
): Generator {
if ( ! is_dir($path)) {
return;
}
yield from new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
$mode
);
}
protected function deleteFileInfoObject(SplFileInfo $file): bool
{
switch ($file->getType()) {
case 'dir':
return @rmdir((string) $file->getRealPath());
case 'link':
return @unlink((string) $file->getPathname());
default:
return @unlink((string) $file->getRealPath());
}
}
public function listContents(string $path, bool $deep): iterable
{
$location = $this->prefixer->prefixPath($path);
if ( ! is_dir($location)) {
return;
}
/** @var SplFileInfo[] $iterator */
$iterator = $deep ? $this->listDirectoryRecursively($location) : $this->listDirectory($location);
foreach ($iterator as $fileInfo) {
$pathName = $fileInfo->getPathname();
try {
if ($fileInfo->isLink()) {
if ($this->linkHandling & self::SKIP_LINKS) {
continue;
}
throw SymbolicLinkEncountered::atLocation($pathName);
}
$path = $this->prefixer->stripPrefix($pathName);
$lastModified = $fileInfo->getMTime();
$isDirectory = $fileInfo->isDir();
$permissions = octdec(substr(sprintf('%o', $fileInfo->getPerms()), -4));
$visibility = $isDirectory ? $this->visibility->inverseForDirectory($permissions) : $this->visibility->inverseForFile($permissions);
yield $isDirectory ? new DirectoryAttributes(str_replace('\\', '/', $path), $visibility, $lastModified) : new FileAttributes(
str_replace('\\', '/', $path),
$fileInfo->getSize(),
$visibility,
$lastModified
);
} catch (Throwable $exception) {
if (file_exists($pathName)) {
throw $exception;
}
}
}
}
public function move(string $source, string $destination, Config $config): void
{
$sourcePath = $this->prefixer->prefixPath($source);
$destinationPath = $this->prefixer->prefixPath($destination);
$this->ensureRootDirectoryExists();
$this->ensureDirectoryExists(
dirname($destinationPath),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
if ( ! @rename($sourcePath, $destinationPath)) {
throw UnableToMoveFile::because(error_get_last()['message'] ?? 'unknown reason', $source, $destination);
}
if ($visibility = $config->get(Config::OPTION_VISIBILITY)) {
$this->setVisibility($destination, (string) $visibility);
}
}
public function copy(string $source, string $destination, Config $config): void
{
$sourcePath = $this->prefixer->prefixPath($source);
$destinationPath = $this->prefixer->prefixPath($destination);
$this->ensureRootDirectoryExists();
$this->ensureDirectoryExists(
dirname($destinationPath),
$this->resolveDirectoryVisibility($config->get(Config::OPTION_DIRECTORY_VISIBILITY))
);
if ( ! @copy($sourcePath, $destinationPath)) {
throw UnableToCopyFile::because(error_get_last()['message'] ?? 'unknown', $source, $destination);
}
$visibility = $config->get(
Config::OPTION_VISIBILITY,
$config->get(Config::OPTION_RETAIN_VISIBILITY, true)
? $this->visibility($source)->visibility()
: null,
);
if ($visibility) {
$this->setVisibility($destination, (string) $visibility);
}
}
public function read(string $path): string
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$contents = @file_get_contents($location);
if ($contents === false) {
throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
}
return $contents;
}
public function readStream(string $path)
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$contents = @fopen($location, 'rb');
if ($contents === false) {
throw UnableToReadFile::fromLocation($path, error_get_last()['message'] ?? '');
}
return $contents;
}
protected function ensureDirectoryExists(string $dirname, int $visibility): void
{
if (is_dir($dirname)) {
return;
}
error_clear_last();
if ( ! @mkdir($dirname, $visibility, true)) {
$mkdirError = error_get_last();
}
clearstatcache(true, $dirname);
if ( ! is_dir($dirname)) {
$errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : '';
throw UnableToCreateDirectory::atLocation($dirname, $errorMessage);
}
}
public function fileExists(string $location): bool
{
$location = $this->prefixer->prefixPath($location);
return is_file($location);
}
public function directoryExists(string $location): bool
{
$location = $this->prefixer->prefixPath($location);
return is_dir($location);
}
public function createDirectory(string $path, Config $config): void
{
$this->ensureRootDirectoryExists();
$location = $this->prefixer->prefixPath($path);
$visibility = $config->get(Config::OPTION_VISIBILITY, $config->get(Config::OPTION_DIRECTORY_VISIBILITY));
$permissions = $this->resolveDirectoryVisibility($visibility);
if (is_dir($location)) {
$this->setPermissions($location, $permissions);
return;
}
error_clear_last();
if ( ! @mkdir($location, $permissions, true)) {
throw UnableToCreateDirectory::atLocation($path, error_get_last()['message'] ?? '');
}
}
public function setVisibility(string $path, string $visibility): void
{
$path = $this->prefixer->prefixPath($path);
$visibility = is_dir($path) ? $this->visibility->forDirectory($visibility) : $this->visibility->forFile(
$visibility
);
$this->setPermissions($path, $visibility);
}
public function visibility(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
clearstatcache(false, $location);
error_clear_last();
$fileperms = @fileperms($location);
if ($fileperms === false) {
throw UnableToRetrieveMetadata::visibility($path, error_get_last()['message'] ?? '');
}
$permissions = $fileperms & 0777;
$visibility = $this->visibility->inverseForFile($permissions);
return new FileAttributes($path, null, $visibility);
}
private function resolveDirectoryVisibility(?string $visibility): int
{
return $visibility === null ? $this->visibility->defaultForDirectories() : $this->visibility->forDirectory(
$visibility
);
}
public function mimeType(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
if ( ! is_file($location)) {
throw UnableToRetrieveMetadata::mimeType($location, 'No such file exists.');
}
$mimeType = $this->mimeTypeDetector->detectMimeTypeFromFile($location);
if ($mimeType === null) {
throw UnableToRetrieveMetadata::mimeType($path, error_get_last()['message'] ?? '');
}
return new FileAttributes($path, null, null, null, $mimeType);
}
public function lastModified(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$lastModified = @filemtime($location);
if ($lastModified === false) {
throw UnableToRetrieveMetadata::lastModified($path, error_get_last()['message'] ?? '');
}
return new FileAttributes($path, null, null, $lastModified);
}
public function fileSize(string $path): FileAttributes
{
$location = $this->prefixer->prefixPath($path);
error_clear_last();
if (is_file($location) && ($fileSize = @filesize($location)) !== false) {
return new FileAttributes($path, $fileSize);
}
throw UnableToRetrieveMetadata::fileSize($path, error_get_last()['message'] ?? '');
}
public function checksum(string $path, Config $config): string
{
$algo = $config->get('checksum_algo', 'md5');
$location = $this->prefixer->prefixPath($path);
error_clear_last();
$checksum = @hash_file($algo, $location);
if ($checksum === false) {
throw new UnableToProvideChecksum(error_get_last()['message'] ?? '', $path);
}
return $checksum;
}
private function listDirectory(string $location): Generator
{
$iterator = new DirectoryIterator($location);
foreach ($iterator as $item) {
if ($item->isDot()) {
continue;
}
yield $item;
}
}
private function setPermissions(string $location, int $visibility): void
{
error_clear_last();
if ( ! @chmod($location, $visibility)) {
$extraMessage = error_get_last()['message'] ?? '';
throw UnableToSetVisibility::atLocation($this->prefixer->stripPrefix($location), $extraMessage);
}
}
}

View File

@ -0,0 +1,25 @@
{
"name": "league/flysystem-local",
"description": "Local filesystem adapter for Flysystem.",
"keywords": ["flysystem", "filesystem", "local", "file", "files"],
"type": "library",
"prefer-stable": true,
"autoload": {
"psr-4": {
"League\\Flysystem\\Local\\": ""
}
},
"require": {
"php": "^8.0.2",
"ext-fileinfo": "*",
"league/flysystem": "^3.0.0",
"league/mime-type-detection": "^1.0.0"
},
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
]
}

2
code/vendor/league/flysystem/INFO.md vendored Normal file
View File

@ -0,0 +1,2 @@
View the docs at: https://flysystem.thephpleague.com/docs/
Changelog at: https://github.com/thephpleague/flysystem/blob/3.x/CHANGELOG.md

19
code/vendor/league/flysystem/LICENSE vendored Normal file
View File

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

View File

@ -0,0 +1,69 @@
{
"name": "league/flysystem",
"description": "File storage abstraction for PHP",
"keywords": [
"filesystem", "filesystems", "files", "storage", "aws",
"s3", "ftp", "sftp", "webdav", "file", "cloud"
],
"scripts": {
"phpstan": "vendor/bin/phpstan analyse -l 6 src"
},
"type": "library",
"minimum-stability": "dev",
"prefer-stable": true,
"autoload": {
"psr-4": {
"League\\Flysystem\\": "src"
}
},
"require": {
"php": "^8.0.2",
"league/flysystem-local": "^3.0.0",
"league/mime-type-detection": "^1.0.0"
},
"require-dev": {
"ext-zip": "*",
"ext-fileinfo": "*",
"ext-ftp": "*",
"microsoft/azure-storage-blob": "^1.1",
"phpunit/phpunit": "^9.5.11|^10.0",
"phpstan/phpstan": "^1.10",
"phpseclib/phpseclib": "^3.0.36",
"aws/aws-sdk-php": "^3.295.10",
"composer/semver": "^3.0",
"friendsofphp/php-cs-fixer": "^3.5",
"google/cloud-storage": "^1.23",
"async-aws/s3": "^1.5 || ^2.0",
"async-aws/simple-s3": "^1.1 || ^2.0",
"sabre/dav": "^4.6.0"
},
"conflict": {
"async-aws/core": "<1.19.0",
"async-aws/s3": "<1.14.0",
"symfony/http-client": "<5.2",
"guzzlehttp/ringphp": "<1.1.1",
"guzzlehttp/guzzle": "<7.0",
"aws/aws-sdk-php": "3.209.31 || 3.210.0",
"phpseclib/phpseclib": "3.0.15"
},
"license": "MIT",
"authors": [
{
"name": "Frank de Jonge",
"email": "info@frankdejonge.nl"
}
],
"repositories": [
{
"type": "package",
"package": {
"name": "league/flysystem-local",
"version": "3.0.0",
"dist": {
"type": "path",
"url": "src/Local"
}
}
}
]
}

58
code/vendor/league/flysystem/readme.md vendored Normal file
View File

@ -0,0 +1,58 @@
# League\Flysystem
[![Author](https://img.shields.io/badge/author-@frankdejonge-blue.svg)](https://twitter.com/frankdejonge)
[![Source Code](https://img.shields.io/badge/source-thephpleague/flysystem-blue.svg)](https://github.com/thephpleague/flysystem)
[![Latest Version](https://img.shields.io/github/tag/thephpleague/flysystem.svg)](https://github.com/thephpleague/flysystem/releases)
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://github.com/thephpleague/flysystem/blob/master/LICENSE)
[![Quality Assurance](https://github.com/thephpleague/flysystem/workflows/Quality%20Assurance/badge.svg?branch=2.x)](https://github.com/thephpleague/flysystem/actions?query=workflow%3A%22Quality+Assurance%22)
[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem.svg)](https://packagist.org/packages/league/flysystem)
![php 7.2+](https://img.shields.io/badge/php-min%208.0.2-red.svg)
## About Flysystem
Flysystem is a file storage library for PHP. It provides one interface to
interact with many types of filesystems. When you use Flysystem, you're
not only protected from vendor lock-in, you'll also have a consistent experience
for which ever storage is right for you.
## Getting Started
* **[New in V3](https://flysystem.thephpleague.com/docs/what-is-new/)**: What is new in Flysystem V2/V3?
* **[Architecture](https://flysystem.thephpleague.com/docs/architecture/)**: Flysystem's internal architecture
* **[Flysystem API](https://flysystem.thephpleague.com/docs/usage/filesystem-api/)**: How to interact with your Flysystem instance
* **[Upgrade from 1x](https://flysystem.thephpleague.com/docs/upgrade-from-1.x/)**: How to upgrade from 1.x/2.x
### Officially supported adapters
* **[Local](https://flysystem.thephpleague.com/docs/adapter/local/)**
* **[FTP](https://flysystem.thephpleague.com/docs/adapter/ftp/)**
* **[SFTP](https://flysystem.thephpleague.com/docs/adapter/sftp-v3/)**
* **[Memory](https://flysystem.thephpleague.com/docs/adapter/in-memory/)**
* **[AWS S3](https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/)**
* **[AsyncAws S3](https://flysystem.thephpleague.com/docs/adapter/async-aws-s3/)**
* **[Google Cloud Storage](https://flysystem.thephpleague.com/docs/adapter/google-cloud-storage/)**
* **[Azure Blob Storage](https://flysystem.thephpleague.com/docs/adapter/azure-blob-storage/)**
* **[WebDAV](https://flysystem.thephpleague.com/docs/adapter/webdav/)**
* **[ZipArchive](https://flysystem.thephpleague.com/docs/adapter/zip-archive/)**
### Third party Adapters
* **[Gitlab](https://github.com/RoyVoetman/flysystem-gitlab-storage)**
* **[Google Drive (using regular paths)](https://github.com/masbug/flysystem-google-drive-ext)**
* **[bunny.net / BunnyCDN](https://github.com/PlatformCommunity/flysystem-bunnycdn/tree/v3)**
* **[Sharepoint 365 / One Drive (Using MS Graph)](https://github.com/shitware-ltd/flysystem-msgraph)**
* **[OneDrive](https://github.com/doerffler/flysystem-onedrive)**
* **[Dropbox](https://github.com/spatie/flysystem-dropbox)**
* **[ReplicateAdapter](https://github.com/ajgarlag/flysystem-replicate)**
* **[Uploadcare](https://github.com/vormkracht10/flysystem-uploadcare)**
* **[Useful adapters (FallbackAdapter, LogAdapter, ReadWriteAdapter, RetryAdapter)](https://github.com/ElGigi/FlysystemUsefulAdapters)**
You can always [create an adapter](https://flysystem.thephpleague.com/docs/advanced/creating-an-adapter/) yourself.
## Security
If you discover any security related issues, please email info@frankdejonge.nl instead of using the issue tracker.
## Enjoy
Oh, and if you've come down this far, you might as well follow me on [twitter](https://twitter.com/frankdejonge).

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use function hash_final;
use function hash_init;
use function hash_update_stream;
trait CalculateChecksumFromStream
{
private function calculateChecksumFromStream(string $path, Config $config): string
{
try {
$stream = $this->readStream($path);
$algo = (string) $config->get('checksum_algo', 'md5');
$context = hash_init($algo);
hash_update_stream($context, $stream);
return hash_final($context);
} catch (FilesystemException $exception) {
throw new UnableToProvideChecksum($exception->getMessage(), $path, $exception);
}
}
/**
* @return resource
*/
abstract public function readStream(string $path);
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException;
final class ChecksumAlgoIsNotSupported extends InvalidArgumentException
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace League\Flysystem;
interface ChecksumProvider
{
/**
* @return string MD5 hash of the file contents
*
* @throws UnableToProvideChecksum
* @throws ChecksumAlgoIsNotSupported
*/
public function checksum(string $path, Config $config): string;
}

View File

@ -0,0 +1,57 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use function array_diff_key;
use function array_flip;
use function array_merge;
class Config
{
public const OPTION_COPY_IDENTICAL_PATH = 'copy_destination_same_as_source';
public const OPTION_MOVE_IDENTICAL_PATH = 'move_destination_same_as_source';
public const OPTION_VISIBILITY = 'visibility';
public const OPTION_DIRECTORY_VISIBILITY = 'directory_visibility';
public const OPTION_RETAIN_VISIBILITY = 'retain_visibility';
public function __construct(private array $options = [])
{
}
/**
* @param mixed $default
*
* @return mixed
*/
public function get(string $property, $default = null)
{
return $this->options[$property] ?? $default;
}
public function extend(array $options): Config
{
return new Config(array_merge($this->options, $options));
}
public function withDefaults(array $defaults): Config
{
return new Config($this->options + $defaults);
}
public function toArray(): array
{
return $this->options;
}
public function withSetting(string $property, mixed $setting): Config
{
return $this->extend([$property => $setting]);
}
public function withoutSettings(string ...$settings): Config
{
return new Config(array_diff_key($this->options, array_flip($settings)));
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace League\Flysystem;
use RuntimeException;
final class CorruptedPathDetected extends RuntimeException implements FilesystemException
{
public static function forPath(string $path): CorruptedPathDetected
{
return new CorruptedPathDetected("Corrupted path detected: " . $path);
}
}

View File

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
abstract class DecoratedAdapter implements FilesystemAdapter
{
public function __construct(protected FilesystemAdapter $adapter)
{
}
public function fileExists(string $path): bool
{
return $this->adapter->fileExists($path);
}
public function directoryExists(string $path): bool
{
return $this->adapter->directoryExists($path);
}
public function write(string $path, string $contents, Config $config): void
{
$this->adapter->write($path, $contents, $config);
}
public function writeStream(string $path, $contents, Config $config): void
{
$this->adapter->writeStream($path, $contents, $config);
}
public function read(string $path): string
{
return $this->adapter->read($path);
}
public function readStream(string $path)
{
return $this->adapter->readStream($path);
}
public function delete(string $path): void
{
$this->adapter->delete($path);
}
public function deleteDirectory(string $path): void
{
$this->adapter->deleteDirectory($path);
}
public function createDirectory(string $path, Config $config): void
{
$this->adapter->createDirectory($path, $config);
}
public function setVisibility(string $path, string $visibility): void
{
$this->adapter->setVisibility($path, $visibility);
}
public function visibility(string $path): FileAttributes
{
return $this->adapter->visibility($path);
}
public function mimeType(string $path): FileAttributes
{
return $this->adapter->mimeType($path);
}
public function lastModified(string $path): FileAttributes
{
return $this->adapter->lastModified($path);
}
public function fileSize(string $path): FileAttributes
{
return $this->adapter->fileSize($path);
}
public function listContents(string $path, bool $deep): iterable
{
return $this->adapter->listContents($path, $deep);
}
public function move(string $source, string $destination, Config $config): void
{
$this->adapter->move($source, $destination, $config);
}
public function copy(string $source, string $destination, Config $config): void
{
$this->adapter->copy($source, $destination, $config);
}
}

View File

@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class DirectoryAttributes implements StorageAttributes
{
use ProxyArrayAccessToProperties;
private string $type = StorageAttributes::TYPE_DIRECTORY;
public function __construct(
private string $path,
private ?string $visibility = null,
private ?int $lastModified = null,
private array $extraMetadata = []
) {
$this->path = trim($this->path, '/');
}
public function path(): string
{
return $this->path;
}
public function type(): string
{
return $this->type;
}
public function visibility(): ?string
{
return $this->visibility;
}
public function lastModified(): ?int
{
return $this->lastModified;
}
public function extraMetadata(): array
{
return $this->extraMetadata;
}
public function isFile(): bool
{
return false;
}
public function isDir(): bool
{
return true;
}
public function withPath(string $path): self
{
$clone = clone $this;
$clone->path = $path;
return $clone;
}
public static function fromArray(array $attributes): self
{
return new DirectoryAttributes(
$attributes[StorageAttributes::ATTRIBUTE_PATH],
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
);
}
/**
* @inheritDoc
*/
public function jsonSerialize(): array
{
return [
StorageAttributes::ATTRIBUTE_TYPE => $this->type,
StorageAttributes::ATTRIBUTE_PATH => $this->path,
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
];
}
}

View File

@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use ArrayIterator;
use Generator;
use IteratorAggregate;
use Traversable;
/**
* @template T
*/
class DirectoryListing implements IteratorAggregate
{
/**
* @param iterable<T> $listing
*/
public function __construct(private iterable $listing)
{
}
/**
* @param callable(T): bool $filter
*
* @return DirectoryListing<T>
*/
public function filter(callable $filter): DirectoryListing
{
$generator = (static function (iterable $listing) use ($filter): Generator {
foreach ($listing as $item) {
if ($filter($item)) {
yield $item;
}
}
})($this->listing);
return new DirectoryListing($generator);
}
/**
* @template R
*
* @param callable(T): R $mapper
*
* @return DirectoryListing<R>
*/
public function map(callable $mapper): DirectoryListing
{
$generator = (static function (iterable $listing) use ($mapper): Generator {
foreach ($listing as $item) {
yield $mapper($item);
}
})($this->listing);
return new DirectoryListing($generator);
}
/**
* @return DirectoryListing<T>
*/
public function sortByPath(): DirectoryListing
{
$listing = $this->toArray();
usort($listing, function (StorageAttributes $a, StorageAttributes $b) {
return $a->path() <=> $b->path();
});
return new DirectoryListing($listing);
}
/**
* @return Traversable<T>
*/
public function getIterator(): Traversable
{
return $this->listing instanceof Traversable
? $this->listing
: new ArrayIterator($this->listing);
}
/**
* @return T[]
*/
public function toArray(): array
{
return $this->listing instanceof Traversable
? iterator_to_array($this->listing, false)
: (array) $this->listing;
}
}

View File

@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
class FileAttributes implements StorageAttributes
{
use ProxyArrayAccessToProperties;
private string $type = StorageAttributes::TYPE_FILE;
public function __construct(
private string $path,
private ?int $fileSize = null,
private ?string $visibility = null,
private ?int $lastModified = null,
private ?string $mimeType = null,
private array $extraMetadata = []
) {
$this->path = ltrim($this->path, '/');
}
public function type(): string
{
return $this->type;
}
public function path(): string
{
return $this->path;
}
public function fileSize(): ?int
{
return $this->fileSize;
}
public function visibility(): ?string
{
return $this->visibility;
}
public function lastModified(): ?int
{
return $this->lastModified;
}
public function mimeType(): ?string
{
return $this->mimeType;
}
public function extraMetadata(): array
{
return $this->extraMetadata;
}
public function isFile(): bool
{
return true;
}
public function isDir(): bool
{
return false;
}
public function withPath(string $path): self
{
$clone = clone $this;
$clone->path = $path;
return $clone;
}
public static function fromArray(array $attributes): self
{
return new FileAttributes(
$attributes[StorageAttributes::ATTRIBUTE_PATH],
$attributes[StorageAttributes::ATTRIBUTE_FILE_SIZE] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_VISIBILITY] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_LAST_MODIFIED] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_MIME_TYPE] ?? null,
$attributes[StorageAttributes::ATTRIBUTE_EXTRA_METADATA] ?? []
);
}
public function jsonSerialize(): array
{
return [
StorageAttributes::ATTRIBUTE_TYPE => self::TYPE_FILE,
StorageAttributes::ATTRIBUTE_PATH => $this->path,
StorageAttributes::ATTRIBUTE_FILE_SIZE => $this->fileSize,
StorageAttributes::ATTRIBUTE_VISIBILITY => $this->visibility,
StorageAttributes::ATTRIBUTE_LAST_MODIFIED => $this->lastModified,
StorageAttributes::ATTRIBUTE_MIME_TYPE => $this->mimeType,
StorageAttributes::ATTRIBUTE_EXTRA_METADATA => $this->extraMetadata,
];
}
}

View File

@ -0,0 +1,281 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use DateTimeInterface;
use Generator;
use League\Flysystem\UrlGeneration\PrefixPublicUrlGenerator;
use League\Flysystem\UrlGeneration\PublicUrlGenerator;
use League\Flysystem\UrlGeneration\ShardedPrefixPublicUrlGenerator;
use League\Flysystem\UrlGeneration\TemporaryUrlGenerator;
use Throwable;
use function array_key_exists;
use function is_array;
class Filesystem implements FilesystemOperator
{
use CalculateChecksumFromStream;
private Config $config;
private PathNormalizer $pathNormalizer;
public function __construct(
private FilesystemAdapter $adapter,
array $config = [],
?PathNormalizer $pathNormalizer = null,
private ?PublicUrlGenerator $publicUrlGenerator = null,
private ?TemporaryUrlGenerator $temporaryUrlGenerator = null,
) {
$this->config = new Config($config);
$this->pathNormalizer = $pathNormalizer ?? new WhitespacePathNormalizer();
}
public function fileExists(string $location): bool
{
return $this->adapter->fileExists($this->pathNormalizer->normalizePath($location));
}
public function directoryExists(string $location): bool
{
return $this->adapter->directoryExists($this->pathNormalizer->normalizePath($location));
}
public function has(string $location): bool
{
$path = $this->pathNormalizer->normalizePath($location);
return $this->adapter->fileExists($path) || $this->adapter->directoryExists($path);
}
public function write(string $location, string $contents, array $config = []): void
{
$this->adapter->write(
$this->pathNormalizer->normalizePath($location),
$contents,
$this->config->extend($config)
);
}
public function writeStream(string $location, $contents, array $config = []): void
{
/* @var resource $contents */
$this->assertIsResource($contents);
$this->rewindStream($contents);
$this->adapter->writeStream(
$this->pathNormalizer->normalizePath($location),
$contents,
$this->config->extend($config)
);
}
public function read(string $location): string
{
return $this->adapter->read($this->pathNormalizer->normalizePath($location));
}
public function readStream(string $location)
{
return $this->adapter->readStream($this->pathNormalizer->normalizePath($location));
}
public function delete(string $location): void
{
$this->adapter->delete($this->pathNormalizer->normalizePath($location));
}
public function deleteDirectory(string $location): void
{
$this->adapter->deleteDirectory($this->pathNormalizer->normalizePath($location));
}
public function createDirectory(string $location, array $config = []): void
{
$this->adapter->createDirectory(
$this->pathNormalizer->normalizePath($location),
$this->config->extend($config)
);
}
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
{
$path = $this->pathNormalizer->normalizePath($location);
$listing = $this->adapter->listContents($path, $deep);
return new DirectoryListing($this->pipeListing($location, $deep, $listing));
}
private function pipeListing(string $location, bool $deep, iterable $listing): Generator
{
try {
foreach ($listing as $item) {
yield $item;
}
} catch (Throwable $exception) {
throw UnableToListContents::atLocation($location, $deep, $exception);
}
}
public function move(string $source, string $destination, array $config = []): void
{
$config = $this->resolveConfigForMoveAndCopy($config);
$from = $this->pathNormalizer->normalizePath($source);
$to = $this->pathNormalizer->normalizePath($destination);
if ($from === $to) {
$resolutionStrategy = $config->get(Config::OPTION_MOVE_IDENTICAL_PATH, ResolveIdenticalPathConflict::TRY);
if ($resolutionStrategy === ResolveIdenticalPathConflict::FAIL) {
throw UnableToMoveFile::sourceAndDestinationAreTheSame($source, $destination);
} elseif ($resolutionStrategy === ResolveIdenticalPathConflict::IGNORE) {
return;
}
}
$this->adapter->move($from, $to, $config);
}
public function copy(string $source, string $destination, array $config = []): void
{
$config = $this->resolveConfigForMoveAndCopy($config);
$from = $this->pathNormalizer->normalizePath($source);
$to = $this->pathNormalizer->normalizePath($destination);
if ($from === $to) {
$resolutionStrategy = $config->get(Config::OPTION_COPY_IDENTICAL_PATH, ResolveIdenticalPathConflict::TRY);
if ($resolutionStrategy === ResolveIdenticalPathConflict::FAIL) {
throw UnableToCopyFile::sourceAndDestinationAreTheSame($source, $destination);
} elseif ($resolutionStrategy === ResolveIdenticalPathConflict::IGNORE) {
return;
}
}
$this->adapter->copy($from, $to, $config);
}
public function lastModified(string $path): int
{
return $this->adapter->lastModified($this->pathNormalizer->normalizePath($path))->lastModified();
}
public function fileSize(string $path): int
{
return $this->adapter->fileSize($this->pathNormalizer->normalizePath($path))->fileSize();
}
public function mimeType(string $path): string
{
return $this->adapter->mimeType($this->pathNormalizer->normalizePath($path))->mimeType();
}
public function setVisibility(string $path, string $visibility): void
{
$this->adapter->setVisibility($this->pathNormalizer->normalizePath($path), $visibility);
}
public function visibility(string $path): string
{
return $this->adapter->visibility($this->pathNormalizer->normalizePath($path))->visibility();
}
public function publicUrl(string $path, array $config = []): string
{
$this->publicUrlGenerator ??= $this->resolvePublicUrlGenerator()
?? throw UnableToGeneratePublicUrl::noGeneratorConfigured($path);
$config = $this->config->extend($config);
return $this->publicUrlGenerator->publicUrl($this->pathNormalizer->normalizePath($path), $config);
}
public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []): string
{
$generator = $this->temporaryUrlGenerator ?? $this->adapter;
if ($generator instanceof TemporaryUrlGenerator) {
return $generator->temporaryUrl(
$this->pathNormalizer->normalizePath($path),
$expiresAt,
$this->config->extend($config)
);
}
throw UnableToGenerateTemporaryUrl::noGeneratorConfigured($path);
}
public function checksum(string $path, array $config = []): string
{
$config = $this->config->extend($config);
if ( ! $this->adapter instanceof ChecksumProvider) {
return $this->calculateChecksumFromStream($path, $config);
}
try {
return $this->adapter->checksum($path, $config);
} catch (ChecksumAlgoIsNotSupported) {
return $this->calculateChecksumFromStream($path, $config);
}
}
private function resolvePublicUrlGenerator(): ?PublicUrlGenerator
{
if ($publicUrl = $this->config->get('public_url')) {
return match (true) {
is_array($publicUrl) => new ShardedPrefixPublicUrlGenerator($publicUrl),
default => new PrefixPublicUrlGenerator($publicUrl),
};
}
if ($this->adapter instanceof PublicUrlGenerator) {
return $this->adapter;
}
return null;
}
/**
* @param mixed $contents
*/
private function assertIsResource($contents): void
{
if (is_resource($contents) === false) {
throw new InvalidStreamProvided(
"Invalid stream provided, expected stream resource, received " . gettype($contents)
);
} elseif ($type = get_resource_type($contents) !== 'stream') {
throw new InvalidStreamProvided(
"Invalid stream provided, expected stream resource, received resource of type " . $type
);
}
}
/**
* @param resource $resource
*/
private function rewindStream($resource): void
{
if (ftell($resource) !== 0 && stream_get_meta_data($resource)['seekable']) {
rewind($resource);
}
}
private function resolveConfigForMoveAndCopy(array $config): Config
{
$retainVisibility = $this->config->get(Config::OPTION_RETAIN_VISIBILITY, $config[Config::OPTION_RETAIN_VISIBILITY] ?? true);
$fullConfig = $this->config->extend($config);
/*
* By default, we retain visibility. When we do not retain visibility, the visibility setting
* from the default configuration is ignored. Only when it is set explicitly, we propagate the
* setting.
*/
if ($retainVisibility && ! array_key_exists(Config::OPTION_VISIBILITY, $config)) {
$fullConfig = $fullConfig->withoutSettings(Config::OPTION_VISIBILITY)->extend($config);
}
return $fullConfig;
}
}

View File

@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemAdapter
{
/**
* @throws FilesystemException
* @throws UnableToCheckExistence
*/
public function fileExists(string $path): bool;
/**
* @throws FilesystemException
* @throws UnableToCheckExistence
*/
public function directoryExists(string $path): bool;
/**
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function write(string $path, string $contents, Config $config): void;
/**
* @param resource $contents
*
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function writeStream(string $path, $contents, Config $config): void;
/**
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function read(string $path): string;
/**
* @return resource
*
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function readStream(string $path);
/**
* @throws UnableToDeleteFile
* @throws FilesystemException
*/
public function delete(string $path): void;
/**
* @throws UnableToDeleteDirectory
* @throws FilesystemException
*/
public function deleteDirectory(string $path): void;
/**
* @throws UnableToCreateDirectory
* @throws FilesystemException
*/
public function createDirectory(string $path, Config $config): void;
/**
* @throws InvalidVisibilityProvided
* @throws FilesystemException
*/
public function setVisibility(string $path, string $visibility): void;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function visibility(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function mimeType(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function lastModified(string $path): FileAttributes;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function fileSize(string $path): FileAttributes;
/**
* @return iterable<StorageAttributes>
*
* @throws FilesystemException
*/
public function listContents(string $path, bool $deep): iterable;
/**
* @throws UnableToMoveFile
* @throws FilesystemException
*/
public function move(string $source, string $destination, Config $config): void;
/**
* @throws UnableToCopyFile
* @throws FilesystemException
*/
public function copy(string $source, string $destination, Config $config): void;
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use Throwable;
interface FilesystemException extends Throwable
{
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemOperationFailed extends FilesystemException
{
public const OPERATION_WRITE = 'WRITE';
public const OPERATION_UPDATE = 'UPDATE'; // not used
public const OPERATION_EXISTENCE_CHECK = 'EXISTENCE_CHECK';
public const OPERATION_DIRECTORY_EXISTS = 'DIRECTORY_EXISTS';
public const OPERATION_FILE_EXISTS = 'FILE_EXISTS';
public const OPERATION_CREATE_DIRECTORY = 'CREATE_DIRECTORY';
public const OPERATION_DELETE = 'DELETE';
public const OPERATION_DELETE_DIRECTORY = 'DELETE_DIRECTORY';
public const OPERATION_MOVE = 'MOVE';
public const OPERATION_RETRIEVE_METADATA = 'RETRIEVE_METADATA';
public const OPERATION_COPY = 'COPY';
public const OPERATION_READ = 'READ';
public const OPERATION_SET_VISIBILITY = 'SET_VISIBILITY';
public const OPERATION_LIST_CONTENTS = 'LIST_CONTENTS';
public function operation(): string;
}

View File

@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemOperator extends FilesystemReader, FilesystemWriter
{
}

View File

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use DateTimeInterface;
/**
* This interface contains everything to read from and inspect
* a filesystem. All methods containing are non-destructive.
*
* @method string publicUrl(string $path, array $config = []) Will be added in 4.0
* @method string temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []) Will be added in 4.0
* @method string checksum(string $path, array $config = []) Will be added in 4.0
*/
interface FilesystemReader
{
public const LIST_SHALLOW = false;
public const LIST_DEEP = true;
/**
* @throws FilesystemException
* @throws UnableToCheckExistence
*/
public function fileExists(string $location): bool;
/**
* @throws FilesystemException
* @throws UnableToCheckExistence
*/
public function directoryExists(string $location): bool;
/**
* @throws FilesystemException
* @throws UnableToCheckExistence
*/
public function has(string $location): bool;
/**
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function read(string $location): string;
/**
* @return resource
*
* @throws UnableToReadFile
* @throws FilesystemException
*/
public function readStream(string $location);
/**
* @return DirectoryListing<StorageAttributes>
*
* @throws FilesystemException
* @throws UnableToListContents
*/
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function lastModified(string $path): int;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function fileSize(string $path): int;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function mimeType(string $path): string;
/**
* @throws UnableToRetrieveMetadata
* @throws FilesystemException
*/
public function visibility(string $path): string;
}

View File

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface FilesystemWriter
{
/**
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function write(string $location, string $contents, array $config = []): void;
/**
* @param mixed $contents
*
* @throws UnableToWriteFile
* @throws FilesystemException
*/
public function writeStream(string $location, $contents, array $config = []): void;
/**
* @throws UnableToSetVisibility
* @throws FilesystemException
*/
public function setVisibility(string $path, string $visibility): void;
/**
* @throws UnableToDeleteFile
* @throws FilesystemException
*/
public function delete(string $location): void;
/**
* @throws UnableToDeleteDirectory
* @throws FilesystemException
*/
public function deleteDirectory(string $location): void;
/**
* @throws UnableToCreateDirectory
* @throws FilesystemException
*/
public function createDirectory(string $location, array $config = []): void;
/**
* @throws UnableToMoveFile
* @throws FilesystemException
*/
public function move(string $source, string $destination, array $config = []): void;
/**
* @throws UnableToCopyFile
* @throws FilesystemException
*/
public function copy(string $source, string $destination, array $config = []): void;
}

View File

@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException as BaseInvalidArgumentException;
class InvalidStreamProvided extends BaseInvalidArgumentException implements FilesystemException
{
}

View File

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use InvalidArgumentException;
use function var_export;
class InvalidVisibilityProvided extends InvalidArgumentException implements FilesystemException
{
public static function withVisibility(string $visibility, string $expectedMessage): InvalidVisibilityProvided
{
$provided = var_export($visibility, true);
$message = "Invalid visibility provided. Expected {$expectedMessage}, received {$provided}";
throw new InvalidVisibilityProvided($message);
}
}

View File

@ -0,0 +1,434 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
use DateTimeInterface;
use Throwable;
use function compact;
use function method_exists;
use function sprintf;
class MountManager implements FilesystemOperator
{
/**
* @var array<string, FilesystemOperator>
*/
private $filesystems = [];
/**
* @var Config
*/
private $config;
/**
* MountManager constructor.
*
* @param array<string,FilesystemOperator> $filesystems
*/
public function __construct(array $filesystems = [], array $config = [])
{
$this->mountFilesystems($filesystems);
$this->config = new Config($config);
}
/**
* It is not recommended to mount filesystems after creation because interacting
* with the Mount Manager becomes unpredictable. Use this as an escape hatch.
*/
public function dangerouslyMountFilesystems(string $key, FilesystemOperator $filesystem): void
{
$this->mountFilesystem($key, $filesystem);
}
/**
* @param array<string,FilesystemOperator> $filesystems
*/
public function extend(array $filesystems, array $config = []): MountManager
{
$clone = clone $this;
$clone->config = $this->config->extend($config);
$clone->mountFilesystems($filesystems);
return $clone;
}
public function fileExists(string $location): bool
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->fileExists($path);
} catch (Throwable $exception) {
throw UnableToCheckFileExistence::forLocation($location, $exception);
}
}
public function has(string $location): bool
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->fileExists($path) || $filesystem->directoryExists($path);
} catch (Throwable $exception) {
throw UnableToCheckExistence::forLocation($location, $exception);
}
}
public function directoryExists(string $location): bool
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->directoryExists($path);
} catch (Throwable $exception) {
throw UnableToCheckDirectoryExistence::forLocation($location, $exception);
}
}
public function read(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->read($path);
} catch (UnableToReadFile $exception) {
throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
}
}
public function readStream(string $location)
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->readStream($path);
} catch (UnableToReadFile $exception) {
throw UnableToReadFile::fromLocation($location, $exception->reason(), $exception);
}
}
public function listContents(string $location, bool $deep = self::LIST_SHALLOW): DirectoryListing
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path, $mountIdentifier] = $this->determineFilesystemAndPath($location);
return
$filesystem
->listContents($path, $deep)
->map(
function (StorageAttributes $attributes) use ($mountIdentifier) {
return $attributes->withPath(sprintf('%s://%s', $mountIdentifier, $attributes->path()));
}
);
}
public function lastModified(string $location): int
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->lastModified($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::lastModified($location, $exception->reason(), $exception);
}
}
public function fileSize(string $location): int
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->fileSize($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::fileSize($location, $exception->reason(), $exception);
}
}
public function mimeType(string $location): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
return $filesystem->mimeType($path);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::mimeType($location, $exception->reason(), $exception);
}
}
public function visibility(string $path): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $location] = $this->determineFilesystemAndPath($path);
try {
return $filesystem->visibility($location);
} catch (UnableToRetrieveMetadata $exception) {
throw UnableToRetrieveMetadata::visibility($path, $exception->reason(), $exception);
}
}
public function write(string $location, string $contents, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->write($path, $contents, $this->config->extend($config)->toArray());
} catch (UnableToWriteFile $exception) {
throw UnableToWriteFile::atLocation($location, $exception->reason(), $exception);
}
}
public function writeStream(string $location, $contents, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
$filesystem->writeStream($path, $contents, $this->config->extend($config)->toArray());
}
public function setVisibility(string $path, string $visibility): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($path);
$filesystem->setVisibility($path, $visibility);
}
public function delete(string $location): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->delete($path);
} catch (UnableToDeleteFile $exception) {
throw UnableToDeleteFile::atLocation($location, $exception->reason(), $exception);
}
}
public function deleteDirectory(string $location): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->deleteDirectory($path);
} catch (UnableToDeleteDirectory $exception) {
throw UnableToDeleteDirectory::atLocation($location, $exception->reason(), $exception);
}
}
public function createDirectory(string $location, array $config = []): void
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($location);
try {
$filesystem->createDirectory($path, $this->config->extend($config)->toArray());
} catch (UnableToCreateDirectory $exception) {
throw UnableToCreateDirectory::dueToFailure($location, $exception);
}
}
public function move(string $source, string $destination, array $config = []): void
{
/** @var FilesystemOperator $sourceFilesystem */
/* @var FilesystemOperator $destinationFilesystem */
[$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
[$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
$sourceFilesystem === $destinationFilesystem ? $this->moveInTheSameFilesystem(
$sourceFilesystem,
$sourcePath,
$destinationPath,
$source,
$destination,
$config,
) : $this->moveAcrossFilesystems($source, $destination, $config);
}
public function copy(string $source, string $destination, array $config = []): void
{
/** @var FilesystemOperator $sourceFilesystem */
/* @var FilesystemOperator $destinationFilesystem */
[$sourceFilesystem, $sourcePath] = $this->determineFilesystemAndPath($source);
[$destinationFilesystem, $destinationPath] = $this->determineFilesystemAndPath($destination);
$sourceFilesystem === $destinationFilesystem ? $this->copyInSameFilesystem(
$sourceFilesystem,
$sourcePath,
$destinationPath,
$source,
$destination,
$config,
) : $this->copyAcrossFilesystem(
$sourceFilesystem,
$sourcePath,
$destinationFilesystem,
$destinationPath,
$source,
$destination,
$config,
);
}
public function publicUrl(string $path, array $config = []): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($path);
if ( ! method_exists($filesystem, 'publicUrl')) {
throw new UnableToGeneratePublicUrl(sprintf('%s does not support generating public urls.', $filesystem::class), $path);
}
return $filesystem->publicUrl($path, $config);
}
public function temporaryUrl(string $path, DateTimeInterface $expiresAt, array $config = []): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($path);
if ( ! method_exists($filesystem, 'temporaryUrl')) {
throw new UnableToGenerateTemporaryUrl(sprintf('%s does not support generating public urls.', $filesystem::class), $path);
}
return $filesystem->temporaryUrl($path, $expiresAt, $this->config->extend($config)->toArray());
}
public function checksum(string $path, array $config = []): string
{
/** @var FilesystemOperator $filesystem */
[$filesystem, $path] = $this->determineFilesystemAndPath($path);
if ( ! method_exists($filesystem, 'checksum')) {
throw new UnableToProvideChecksum(sprintf('%s does not support providing checksums.', $filesystem::class), $path);
}
return $filesystem->checksum($path, $this->config->extend($config)->toArray());
}
private function mountFilesystems(array $filesystems): void
{
foreach ($filesystems as $key => $filesystem) {
$this->guardAgainstInvalidMount($key, $filesystem);
/* @var string $key */
/* @var FilesystemOperator $filesystem */
$this->mountFilesystem($key, $filesystem);
}
}
private function guardAgainstInvalidMount(mixed $key, mixed $filesystem): void
{
if ( ! is_string($key)) {
throw UnableToMountFilesystem::becauseTheKeyIsNotValid($key);
}
if ( ! $filesystem instanceof FilesystemOperator) {
throw UnableToMountFilesystem::becauseTheFilesystemWasNotValid($filesystem);
}
}
private function mountFilesystem(string $key, FilesystemOperator $filesystem): void
{
$this->filesystems[$key] = $filesystem;
}
/**
* @param string $path
*
* @return array{0:FilesystemOperator, 1:string, 2:string}
*/
private function determineFilesystemAndPath(string $path): array
{
if (strpos($path, '://') < 1) {
throw UnableToResolveFilesystemMount::becauseTheSeparatorIsMissing($path);
}
/** @var string $mountIdentifier */
/** @var string $mountPath */
[$mountIdentifier, $mountPath] = explode('://', $path, 2);
if ( ! array_key_exists($mountIdentifier, $this->filesystems)) {
throw UnableToResolveFilesystemMount::becauseTheMountWasNotRegistered($mountIdentifier);
}
return [$this->filesystems[$mountIdentifier], $mountPath, $mountIdentifier];
}
private function copyInSameFilesystem(
FilesystemOperator $sourceFilesystem,
string $sourcePath,
string $destinationPath,
string $source,
string $destination,
array $config,
): void {
try {
$sourceFilesystem->copy($sourcePath, $destinationPath, $this->config->extend($config)->toArray());
} catch (UnableToCopyFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}
private function copyAcrossFilesystem(
FilesystemOperator $sourceFilesystem,
string $sourcePath,
FilesystemOperator $destinationFilesystem,
string $destinationPath,
string $source,
string $destination,
array $config,
): void {
$config = $this->config->extend($config);
$retainVisibility = (bool) $config->get(Config::OPTION_RETAIN_VISIBILITY, true);
$visibility = $config->get(Config::OPTION_VISIBILITY);
try {
if ($visibility == null && $retainVisibility) {
$visibility = $sourceFilesystem->visibility($sourcePath);
$config = $config->extend(compact('visibility'));
}
$stream = $sourceFilesystem->readStream($sourcePath);
$destinationFilesystem->writeStream($destinationPath, $stream, $config->toArray());
} catch (UnableToRetrieveMetadata | UnableToReadFile | UnableToWriteFile $exception) {
throw UnableToCopyFile::fromLocationTo($source, $destination, $exception);
}
}
private function moveInTheSameFilesystem(
FilesystemOperator $sourceFilesystem,
string $sourcePath,
string $destinationPath,
string $source,
string $destination,
array $config,
): void {
try {
$sourceFilesystem->move($sourcePath, $destinationPath, $this->config->extend($config)->toArray());
} catch (UnableToMoveFile $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
}
private function moveAcrossFilesystems(string $source, string $destination, array $config = []): void
{
try {
$this->copy($source, $destination, $config);
$this->delete($source);
} catch (UnableToCopyFile | UnableToDeleteFile $exception) {
throw UnableToMoveFile::fromLocationTo($source, $destination, $exception);
}
}
}

View File

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace League\Flysystem;
interface PathNormalizer
{
public function normalizePath(string $path): string;
}

Some files were not shown because too many files have changed in this diff Show More