Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added rename function #3

Merged
merged 15 commits into from
Nov 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 97 additions & 9 deletions src/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,49 @@

namespace Ambimax\File;

use RuntimeException;
use Symfony\Component\Filesystem\Path;

class File implements FileInterface
{
public const MODE_READ = 'r';
public const MODE_READ_PLUS = 'r+';
public const MODE_WRITE = 'w';
public const MODE_WRITE_PLUS = 'w+';
/**
* @var resource
*/
protected $fileHandle;

protected string $filePath;
protected string $mode;

/**
* @param self::MODE_* $mode
*/
public function __construct(string $filePath, string $mode)
{
$this->openStream($filePath, $mode);
$this->filePath = $filePath;
}

protected function openStream(string $filePath, string $mode): void
{
if (!file_exists($filePath)) {
throw new RuntimeException("File '$filePath' does not exist.");
if (
!in_array($mode, [
self::MODE_WRITE,
self::MODE_WRITE_PLUS,
]) && !file_exists($filePath)
) {
throw new \RuntimeException("File '$filePath' does not exist.");
}

$tmpFileHandle = fopen($filePath, $mode);
if (false === $tmpFileHandle) {
throw new RuntimeException("Could not open file '$filePath'.");
throw new \RuntimeException("Could not open file '$filePath'.");
}

$this->fileHandle = $tmpFileHandle;
$this->filePath = $filePath;
$this->mode = $mode;
}

public function __destruct()
Expand All @@ -53,19 +66,94 @@ public function getBasename(): string
return basename($this->filePath);
}

/**
* @return resource
*/
public function getFileHandle()
{
return $this->fileHandle;
}

/**
* @return false|string
*/
public function getContent()
{
return stream_get_contents($this->fileHandle);
$pointerLocation = ftell($this->fileHandle);
if (false === $pointerLocation) {
throw new \RuntimeException('unable to get current location of the file pointer. exception thrown to prevent unpredictable pointer jumping');
}
rewind($this->fileHandle);

$content = stream_get_contents($this->fileHandle);
fseek($this->fileHandle, $pointerLocation);

return $content;
}

/**
* @return resource
* Changes relative path to an absolut path relative to the current file location.
* This is to prevent confusion of e.g. rename() where the path would be relative to the php working directory.
* If the path is already absolute or has a protocol defined the path won't be affected by this.
*
* If the path ends with the directory separator ("/") the current file name will get appended
*/
public function getFileHandle()
protected function ensureAbsolutePath(string $path): string
{
return $this->fileHandle;
if (
true === Path::isLocal($path) &&
false === Path::isAbsolute($path)
) {
$path = Path::join(dirname($this->filePath), $path);
}

if (DIRECTORY_SEPARATOR === $path[-1]) {
$path = Path::join($path, $this->getBasename());
}

return $path;
}

public function rename(string $newPath): bool
{
$newPath = $this->ensureAbsolutePath($newPath);

fclose($this->fileHandle);

$success = rename($this->filePath, $newPath);
$this->openStream($newPath, $this->mode);

return $success;
}

public function move(string $newPath): bool
{
return $this->rename($newPath);
}

/**
* @param int<0, max>|null $length
*/
public function fwrite(string $data, ?int $length = null): int|false
{
return fwrite($this->fileHandle, $data, $length);
}

/**
* @param int<0, max>|null $length
*/
public function fread(?int $length = null): string|false
{
return fread($this->fileHandle, $length);
}

public function ftell(): int|false
{
return ftell($this->fileHandle);
}

public function fseek(int $offset, int $whence = SEEK_SET): int
{
return fseek($this->fileHandle, $offset, $whence);
}
}
22 changes: 17 additions & 5 deletions src/FileInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@

interface FileInterface
{
/**
* @return false|string
*/
public function getContent();

public function getPath(): string;

public function getBasename(): string;
Expand All @@ -19,4 +14,21 @@ public function getBasename(): string;
* @return resource
*/
public function getFileHandle();

/**
* @return false|string
*/
public function getContent();

public function rename(string $newPath): bool;

public function move(string $newPath): bool;

public function fwrite(string $data, ?int $length = null): int|false;

public function fread(?int $length = null): string|false;

public function ftell(): int|false;

public function fseek(int $offset, int $whence = SEEK_SET): int;
}
24 changes: 22 additions & 2 deletions src/Remote/SftpFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,26 @@
namespace Ambimax\File\Remote;

use Ambimax\File\File;
use phpseclib3\Net\SFTP;
use phpseclib3\Net\SFTP\Stream;
use RuntimeException;

class SftpFile extends File
{
protected string $hostname;
protected string $username;
protected string $password;
protected SFTP $sftp;

/**
* @param File::MODE_* $mode
*/
public function __construct(string $hostname, string $username, string $password, string $filePath, string $mode)
{
$this->hostname = $hostname;
$this->username = $username;
$this->password = $password;
$this->sftp = new SFTP($hostname);
$this->sftp->login($username, $password);
parent::__construct($filePath, $mode);
}

Expand All @@ -34,9 +40,23 @@ protected function openStream(string $filePath, string $mode): void
$mode);

if (false === $tmpFileHandle) {
throw new RuntimeException(sprintf('Could not open file \'%s\'.', $filePath));
throw new \RuntimeException(sprintf('Could not open file \'%s\'.', $filePath));
}

$this->fileHandle = $tmpFileHandle;
$this->filePath = $filePath;
$this->mode = $mode;
}

public function rename(string $newPath): bool
{
$newPath = $this->ensureAbsolutePath($newPath);

fclose($this->fileHandle);

$success = $this->sftp->rename($this->filePath, $newPath);
$this->openStream($newPath, $this->mode);

return $success;
}
}
32 changes: 32 additions & 0 deletions src/Test/FileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,36 @@ public function testGetFileHandle(): void
$this->assertIsResource($this->readableFile->getFileHandle());
$this->assertIsNotClosedResource($this->readableFile->getFileHandle());
}

public function testRename(): void
{
$oldFileHandle = $this->readableFile->getFileHandle();

$this->readableFile->rename('newtest');
$this->assertSame($this->root->url().'/newtest', $this->readableFile->getPath());

$newFileHandle = $this->readableFile->getFileHandle();
$this->assertSame('testContent', stream_get_contents($newFileHandle));

$this->assertIsClosedResource($oldFileHandle);
}

public function testMoveToDifferentFolderAbsolutePath(): void
{
$oldFileHandle = $this->readableFile->getFileHandle();

mkdir($this->root->url().'/testFolder');
$this->readableFile->rename($this->root->url().'/testFolder/');
$this->assertSame($this->root->url().'/testFolder/test', $this->readableFile->getPath());
$this->assertFileExists($this->root->url().'/testFolder/test');

$this->readableFile->rename('../testFile');
$this->assertSame($this->root->url().'/testFile', $this->readableFile->getPath());
$this->assertFileExists($this->root->url().'/testFile');

$newFileHandle = $this->readableFile->getFileHandle();
$this->assertSame('testContent', stream_get_contents($newFileHandle));

$this->assertIsClosedResource($oldFileHandle);
}
}