Improved Grav\Framework\File classes to use better type hints and the new Filesystem class

This commit is contained in:
Matias Griese
2018-12-03 11:26:55 +02:00
parent 2b099c2ed5
commit cb4ea86310
5 changed files with 151 additions and 139 deletions

View File

@@ -4,39 +4,40 @@
1. [](#new)
* Added `select()` and `unselect()` methods to `CollectionInterface` and its base classes
* Added `orderBy()` and `limit()` methods to `ObjectCollectionInterface` and its base classes
* Flex: Added support for custom object index classes (API compatibility break)
* Added `user-data://` which is a writable stream (`user://data` is not and should be avoided)
* Added support for `/action:{$action}` (like task but used without nonce when only receiving data)
* Added `onAction.{$action}` event
* Added `Grav\Framework\Form\FormFlash` class to contain AJAX uploaded files in more reliable way
* Added `Grav\Framework\Form\FormFlashFile` class which implements `UploadedFileInterface` from PSR-7
* Added `Grav\Framework\Filesystem\Filesystem` class with methods to manipulate stream URLs
* Grav 1.6: Flex: Added support for custom object index classes (API compatibility break)
1. [](#improved)
* Improved Flex storage classes
* Improved `Grav\Framework\File\Formatter` classes to have abstract parent class and some useful methods
* Grav 1.6: Improved Flex storage classes
* Grav 1.6: Improved `Grav\Framework\File` classes to use better type hints and the new `Filesystem` class
1. [](#bugfix)
* Fixed handling of `append_url_extension` inside of `Page::templateFormat()` [#2264](https://github.com/getgrav/grav/issues/2264)
* Fixed a broken language string [#2261](https://github.com/getgrav/grav/issues/2261)
* Fixed clearing cache having no effect on Doctrine cache
* Fixed `Medium::relativePath()` for streams
* Fixed `FlexObject::update()` call with partial object update
* Fixed `Object` serialization breaking if overriding `jsonSerialize()` method
* Grav 1.6: Fixed `FlexObject::update()` call with partial object update
# v1.6.0-beta.6
## 11/12/2018
1. [](#new)
* Added `CsvFormatter` and `CsvFile` classes
* Added `$grav->setup()` to simplify CLI and custom access points
* Grav 1.6: Added `CsvFormatter` and `CsvFile` classes
1. [](#improved)
* Support negotiated content types set via the Request `Accept:` header
* Support negotiated language types set via the Request `Accept-Language:` header
* Allow custom Flex form views
* Cleaned up and sorted the Service `idMap`
* Grav 1.6: Allow custom Flex form views
1. [](#bugfix)
* Fixed `Uri::hasStandardPort()` to support reverse proxy configurations [#1786](https://github.com/getgrav/grav/issues/1786)
* Use `append_url_extension` from page header to set template format if set [#2604](https://github.com/getgrav/grav/pull/2064)
* Fixed some bugs in environment selection
* Fixed some bugs in Grav environment selection logic
# v1.6.0-beta.5
## 11/05/2018
@@ -55,7 +56,7 @@
* Set session name based on `security.salt` rather than `GRAV_ROOT` [#2242](https://github.com/getgrav/grav/issues/2242)
* Added option to configure list of `xss_invalid_protocols` in `Security` config [#2250](https://github.com/getgrav/grav/issues/2250)
* Smarter `security.salt` checking now we use `security.yaml` for other options
* Merged Grav 1.5.4 fixes in
* Grav 1.6: Merged Grav 1.5.4 fixes in
# v1.6.0-beta.4
## 10/24/2018
@@ -84,15 +85,15 @@
## 10/09/2018
1. [](#new)
* Added Flex support for custom media tasks
* Grav 1.6: Added Flex support for custom media tasks
1. [](#improved)
* Added support for syslog and syslog facility logging (default: 'file')
* Improved usability of `System` configuration blueprint with side-tabs
1. [](#bugfix)
* Fixed asset manager to not add empty assets when they don't exist in the filesystem
* Regression: Fixed asset manager methods with default legacy attributes
* Update `script` and `style` Twig tags to use the new `Assets` classes
* Fixed asset pipeline to rewrite remote URLs as well as local [#2216](https://github.com/getgrav/grav/issues/2216)
* Grav 1.6: Regression: Fixed asset manager methods with default legacy attributes
# v1.6.0-beta.1
## 10/01/2018

View File

@@ -12,9 +12,13 @@ declare(strict_types=1);
namespace Grav\Framework\File;
use Grav\Framework\File\Interfaces\FileInterface;
use Grav\Framework\Filesystem\Filesystem;
class AbstractFile implements FileInterface
{
/** @var Filesystem */
private $filesystem;
/** @var string */
private $filepath;
@@ -36,8 +40,13 @@ class AbstractFile implements FileInterface
/** @var bool */
private $locked = false;
public function __construct($filepath)
/**
* @param string $filepath
* @param Filesystem|null $filesystem
*/
public function __construct(string $filepath, Filesystem $filesystem = null)
{
$this->filesystem = $filesystem ?? new Filesystem();
$this->setFilepath($filepath);
}
@@ -51,11 +60,26 @@ class AbstractFile implements FileInterface
}
}
/**
* Prevent cloning.
*/
private function __clone()
public function __clone()
{
$this->handle = null;
$this->locked = false;
}
/**
* @return string
*/
public function serialize(): string
{
return serialize($this->doSerialize());
}
/**
* @param string $serialized
*/
public function unserialize($serialized): void
{
$this->doUnserialize(unserialize($serialized, ['allowed_classes' => false]));
}
/**
@@ -63,7 +87,7 @@ class AbstractFile implements FileInterface
*
* @return string
*/
public function getFilePath() : string
public function getFilePath(): string
{
return $this->filepath;
}
@@ -73,7 +97,7 @@ class AbstractFile implements FileInterface
*
* @return string
*/
public function getPath() : string
public function getPath(): string
{
if (null === $this->path) {
$this->setPathInfo();
@@ -87,7 +111,7 @@ class AbstractFile implements FileInterface
*
* @return string
*/
public function getFilename() : string
public function getFilename(): string
{
if (null === $this->filename) {
$this->setPathInfo();
@@ -101,7 +125,7 @@ class AbstractFile implements FileInterface
*
* @return string
*/
public function getBasename() : string
public function getBasename(): string
{
if (null === $this->basename) {
$this->setPathInfo();
@@ -113,10 +137,10 @@ class AbstractFile implements FileInterface
/**
* Return file extension.
*
* @param $withDot
* @param bool $withDot
* @return string
*/
public function getExtension($withDot = false) : string
public function getExtension(bool $withDot = false): string
{
if (null === $this->extension) {
$this->setPathInfo();
@@ -130,7 +154,7 @@ class AbstractFile implements FileInterface
*
* @return bool
*/
public function exists() : bool
public function exists(): bool
{
return is_file($this->filepath);
}
@@ -138,21 +162,21 @@ class AbstractFile implements FileInterface
/**
* Return file modification time.
*
* @return int|bool Timestamp or false if file doesn't exist.
* @return int Unix timestamp. If file does not exist, method returns current time.
*/
public function getCreationTime()
public function getCreationTime(): int
{
return is_file($this->filepath) ? filectime($this->filepath) : false;
return is_file($this->filepath) ? filectime($this->filepath) : time();
}
/**
* Return file modification time.
*
* @return int|bool Timestamp or false if file doesn't exist.
* @return int Unix timestamp. If file does not exist, method returns current time.
*/
public function getModificationTime()
public function getModificationTime(): int
{
return is_file($this->filepath) ? filemtime($this->filepath) : false;
return is_file($this->filepath) ? filemtime($this->filepath) : time();
}
/**
@@ -162,7 +186,7 @@ class AbstractFile implements FileInterface
* @return bool
* @throws \RuntimeException
*/
public function lock($block = true) : bool
public function lock(bool $block = true): bool
{
if (!$this->handle) {
if (!$this->mkdir($this->getPath())) {
@@ -184,7 +208,7 @@ class AbstractFile implements FileInterface
*
* @return bool
*/
public function unlock() : bool
public function unlock(): bool
{
if (!$this->handle) {
return false;
@@ -204,25 +228,39 @@ class AbstractFile implements FileInterface
*
* @return bool True = locked, false = not locked.
*/
public function isLocked() : bool
public function isLocked(): bool
{
return $this->locked;
}
/**
* Check if file exists and can be read.
*
* @return bool
*/
public function isReadable(): bool
{
return is_readable($this->filepath) && is_file($this->filepath);
}
/**
* Check if file can be written.
*
* @return bool
*/
public function isWritable() : bool
public function isWritable(): bool
{
return is_writable($this->filepath) || $this->isWritableDir($this->getPath());
if (!file_exists($this->filepath)) {
return $this->isWritablePath($this->getPath());
}
return is_writable($this->filepath) && is_file($this->filepath);
}
/**
* (Re)Load a file and return RAW file contents.
* (Re)Load a file and return file contents.
*
* @return string
* @return string|array|false
*/
public function load()
{
@@ -235,7 +273,7 @@ class AbstractFile implements FileInterface
* @param mixed $data
* @throws \RuntimeException
*/
public function save($data)
public function save($data): void
{
$lock = false;
if (!$this->locked) {
@@ -266,7 +304,7 @@ class AbstractFile implements FileInterface
* @param string $path
* @return bool
*/
public function rename($path) : bool
public function rename(string $path): bool
{
if ($this->exists() && !@rename($this->filepath, $path)) {
return false;
@@ -282,7 +320,7 @@ class AbstractFile implements FileInterface
*
* @return bool
*/
public function delete() : bool
public function delete(): bool
{
return @unlink($this->filepath);
}
@@ -293,7 +331,7 @@ class AbstractFile implements FileInterface
* @throws \RuntimeException
* @internal
*/
protected function mkdir($dir) : bool
protected function mkdir(string $dir): bool
{
// Silence error for open_basedir; should fail in mkdir instead.
if (!@is_dir($dir)) {
@@ -310,20 +348,27 @@ class AbstractFile implements FileInterface
}
/**
* @param string $dir
* @return bool
* @internal
* @return array
*/
protected function isWritableDir($dir) : bool
protected function doSerialize(): array
{
if ($dir && !file_exists($dir)) {
return $this->isWritableDir(\dirname($dir));
}
return $dir && is_dir($dir) && is_writable($dir);
return [
'filepath' => $this->filepath
];
}
protected function setFilepath($filepath) : void
/**
* @param array $serialized
*/
protected function doUnserialize(array $serialized): void
{
$this->setFilepath($serialized['filepath']);
}
/**
* @param string $filepath
*/
protected function setFilepath(string $filepath): void
{
$this->filepath = $filepath;
$this->filename = null;
@@ -332,64 +377,32 @@ class AbstractFile implements FileInterface
$this->extension = null;
}
protected function setPathInfo() : void
protected function setPathInfo(): void
{
$pathInfo = static::pathinfo($this->filepath);
$this->filename = $pathInfo['filename'];
$this->basename = $pathInfo['basename'];
$this->path = $pathInfo['dirname'];
$this->extension = $pathInfo['extension'];
$pathInfo = $this->filesystem->pathinfo($this->filepath);
$this->filename = $pathInfo['filename'] ?? null;
$this->basename = $pathInfo['basename'] ?? null;
$this->path = $pathInfo['dirname'] ?? null;
$this->extension = $pathInfo['extension'] ?? null;
}
/**
* Multi-byte-safe pathinfo replacement.
* Replacement for pathinfo(), but stream, multibyte and cross-platform safe.
*
* @see http://www.php.net/manual/en/function.pathinfo.php
*
* @param string $path A filename or path, does not need to exist as a file
* @param int|string $options Either a PATHINFO_* constant,
* or a string name to return only the specified piece
*
* @return string|array
* @param string $dir
* @return bool
* @internal
*/
public static function pathinfo($path, $options = null)
protected function isWritablePath(string $dir): bool
{
$ret = ['scheme' => '', 'dirname' => '', 'basename' => '', 'extension' => '', 'filename' => ''];
$pathinfo = [];
if (preg_match('#^((.*?)://)?(.*?)[\\\\/]*(([^/\\\\]*?)(\.([^\.\\\\/]+?)|))[\\\\/\.]*$#um', $path, $pathinfo)) {
if (array_key_exists(1, $pathinfo)) {
$ret['scheme'] = $pathinfo[2];
$ret['dirname'] = $pathinfo[1];
}
if (array_key_exists(3, $pathinfo)) {
$ret['dirname'] .= $pathinfo[3];
}
if (array_key_exists(4, $pathinfo)) {
$ret['basename'] = $pathinfo[4];
}
if (array_key_exists(7, $pathinfo)) {
$ret['extension'] = $pathinfo[7];
}
if (array_key_exists(5, $pathinfo)) {
$ret['filename'] = $pathinfo[5];
}
if ($dir === '') {
return false;
}
switch ($options) {
case PATHINFO_DIRNAME:
case 'dirname':
return $ret['dirname'];
case PATHINFO_BASENAME:
case 'basename':
return $ret['basename'];
case PATHINFO_EXTENSION:
case 'extension':
return $ret['extension'];
case PATHINFO_FILENAME:
case 'filename':
return $ret['filename'];
default:
return $ret;
if (!file_exists($dir)) {
// Recursively look up in the directory tree.
return $this->isWritablePath($this->filesystem->parent($dir));
}
return is_dir($dir) && is_writable($dir);
}
}

View File

@@ -34,7 +34,7 @@ class DataFile extends AbstractFile
/**
* (Re)Load a file and return RAW file contents.
*
* @return array
* @return array|false
* @throws RuntimeException
*/
public function load()
@@ -42,7 +42,7 @@ class DataFile extends AbstractFile
$raw = parent::load();
try {
return $this->formatter->decode($raw);
return $raw !== false ? $this->formatter->decode($raw) : false;
} catch (RuntimeException $e) {
throw new RuntimeException(sprintf("Failed to load file '%s': %s", $this->getFilePath(), $e->getMessage()), $e->getCode(), $e);
}
@@ -54,9 +54,10 @@ class DataFile extends AbstractFile
* @param string|array $data Data to be saved.
* @throws RuntimeException
*/
public function save($data)
public function save($data): void
{
if (\is_string($data)) {
// Make sure that the string is valid data.
try {
$this->formatter->decode($data);
} catch (RuntimeException $e) {

View File

@@ -16,11 +16,11 @@ class File extends AbstractFile
/**
* Load a file from the filesystem.
*
* @return string
* @return string|false
*/
public function load()
{
return (string) parent::load();
return parent::load();
}
/**
@@ -29,8 +29,12 @@ class File extends AbstractFile
* @param string $data
* @throws \RuntimeException
*/
public function save($data)
public function save($data): void
{
if (!\is_string($data)) {
throw new \RuntimeException('Cannot save data, string required');
}
parent::save($data);
}
}

View File

@@ -11,64 +11,64 @@ declare(strict_types=1);
namespace Grav\Framework\File\Interfaces;
interface FileInterface
interface FileInterface extends \Serializable
{
/**
* Get full path to the file.
*
* @return string
*/
public function getFilePath() : string;
public function getFilePath(): string;
/**
* Get path to the file.
*
* @return string
*/
public function getPath() : string;
public function getPath(): string;
/**
* Get filename.
*
* @return string
*/
public function getFilename() : string;
public function getFilename(): string;
/**
* Return name of the file without extension.
*
* @return string
*/
public function getBasename() : string;
public function getBasename(): string;
/**
* Return file extension.
*
* @param $withDot
* @param bool $withDot
* @return string
*/
public function getExtension($withDot = false) : string;
public function getExtension(bool $withDot = false): string;
/**
* Check if file exits.
*
* @return bool
*/
public function exists() : bool;
public function exists(): bool;
/**
* Return file modification time.
*
* @return int|bool Timestamp or false if file doesn't exist.
* @return int Unix timestamp. If file does not exist, method returns current time.
*/
public function getCreationTime();
public function getCreationTime(): int;
/**
* Return file modification time.
*
* @return int|bool Timestamp or false if file doesn't exist.
* @return int Unix timestamp. If file does not exist, method returns current time.
*/
public function getModificationTime();
public function getModificationTime(): int;
/**
* Lock file for writing. You need to manually unlock().
@@ -77,33 +77,40 @@ interface FileInterface
* @return bool
* @throws \RuntimeException
*/
public function lock($block = true) : bool;
public function lock(bool $block = true): bool;
/**
* Unlock file.
*
* @return bool
*/
public function unlock() : bool;
public function unlock(): bool;
/**
* Returns true if file has been locked for writing.
*
* @return bool True = locked, false = not locked.
*/
public function isLocked() : bool;
public function isLocked(): bool;
/**
* Check if file exists and can be read.
*
* @return bool
*/
public function isReadable(): bool;
/**
* Check if file can be written.
*
* @return bool
*/
public function isWritable() : bool;
public function isWritable(): bool;
/**
* (Re)Load a file and return RAW file contents.
*
* @return string
* @return string|array|false
*/
public function load();
@@ -113,7 +120,7 @@ interface FileInterface
* @param mixed $data
* @throws \RuntimeException
*/
public function save($data);
public function save($data): void;
/**
* Rename file in the filesystem if it exists.
@@ -121,26 +128,12 @@ interface FileInterface
* @param string $path
* @return bool
*/
public function rename($path) : bool;
public function rename(string $path): bool;
/**
* Delete file from filesystem.
*
* @return bool
*/
public function delete() : bool;
/**
* Multi-byte-safe pathinfo replacement.
* Replacement for pathinfo(), but stream, multibyte and cross-platform safe.
*
* @see http://www.php.net/manual/en/function.pathinfo.php
*
* @param string $path A filename or path, does not need to exist as a file
* @param int|string $options Either a PATHINFO_* constant,
* or a string name to return only the specified piece
*
* @return string|array
*/
public static function pathinfo($path, $options = null);
public function delete(): bool;
}