Skip to content

Commit

Permalink
Refactor File::class to use file pointer internally
Browse files Browse the repository at this point in the history
  • Loading branch information
olivervogel committed Jul 21, 2024
1 parent 6daaedf commit e8001ac
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 119 deletions.
41 changes: 0 additions & 41 deletions src/EncodedImage.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,4 @@

class EncodedImage extends File implements EncodedImageInterface
{
/**
* Create new instance
*
* @param string $data
* @param string $mediaType
*/
public function __construct(
protected string $data,
protected string $mediaType = 'application/octet-stream'
) {
}

/**
* {@inheritdoc}
*
* @see EncodedImageInterface::mediaType()
*/
public function mediaType(): string
{
return $this->mediaType;
}

/**
* {@inheritdoc}
*
* @see EncodedImageInterface::mimetype()
*/
public function mimetype(): string
{
return $this->mediaType();
}

/**
* {@inheritdoc}
*
* @see EncodedImageInterface::toDataUri()
*/
public function toDataUri(): string
{
return sprintf('data:%s;base64,%s', $this->mediaType, base64_encode($this->data));
}
}
90 changes: 81 additions & 9 deletions src/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Intervention\Image;

use Intervention\Image\Exceptions\InputException;
use Intervention\Image\Exceptions\NotWritableException;
use Intervention\Image\Interfaces\FileInterface;
use Intervention\Image\Traits\CanBuildFilePointer;
Expand All @@ -12,13 +13,27 @@ class File implements FileInterface
{
use CanBuildFilePointer;

/**
* Data stream
*
* @var resource $stream
*/
protected mixed $stream;

/**
* Create new instance
*
* @param string $data
* @param mixed $data
* @throws InputException
* @return void
*/
public function __construct(protected string $data)
public function __construct(mixed $data)
{
$this->stream = match (true) {
is_resource($data) => $data,
is_string($data) => $this->buildFilePointer($data),
default => throw new InputException('Argument #1 ($data) must be of type string or resource.'),
};
}

/**
Expand All @@ -42,13 +57,30 @@ public function save(string $filepath): void
);
}

// write data
$saved = @file_put_contents($filepath, (string) $this);
if ($saved === false) {
// create output file pointer
if (!$output = fopen($filepath, 'a')) {
throw new NotWritableException(
"Can't write image data to path ({$filepath})."
"Can't write image to path. File path is not writable."
);
}

// create source file pointer
$source = $this->toFilePointer();

while (!feof($source)) {
$buffer = fread($source, 8192);

if ($buffer === false) {
throw new NotWritableException(
"Can't write image to path. Unable to read source."
);
}

// write buffer to output
fwrite($output, $buffer);
}

fclose($output);
}

/**
Expand All @@ -58,7 +90,9 @@ public function save(string $filepath): void
*/
public function toString(): string
{
return $this->data;
rewind($this->stream);

return stream_get_contents($this->stream);
}

/**
Expand All @@ -68,7 +102,9 @@ public function toString(): string
*/
public function toFilePointer()
{
return $this->buildFilePointer($this->toString());
rewind($this->stream);

return $this->stream;
}

/**
Expand All @@ -78,7 +114,43 @@ public function toFilePointer()
*/
public function size(): int
{
return mb_strlen($this->data);
return fstat($this->stream)['size'];
}

/**
* {@inheritdoc}
*
* @see EncodedImageInterface::mediaType()
*/
public function mediaType(): string
{
$detected = mime_content_type($this->stream);

if ($detected === false) {
return 'application/x-empty';
}

return $detected;
}

/**
* {@inheritdoc}
*
* @see EncodedImageInterface::mimetype()
*/
public function mimetype(): string
{
return $this->mediaType();
}

/**
* {@inheritdoc}
*
* @see EncodedImageInterface::toDataUri()
*/
public function toDataUri(): string
{
return sprintf('data:%s;base64,%s', $this->mediaType(), base64_encode((string) $this));
}

/**
Expand Down
9 changes: 4 additions & 5 deletions tests/Traits/CanDetectInterlacedPng.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Intervention\Image\Tests\Traits;

use Intervention\Image\EncodedImage;
use Intervention\Image\Traits\CanBuildFilePointer;

trait CanDetectInterlacedPng
Expand All @@ -13,14 +14,12 @@ trait CanDetectInterlacedPng
/**
* Checks if the given image data is interlaced encoded PNG format
*
* @param string $imagedata
* @param EncodedImage $image
* @return bool
*/
private function isInterlacedPng(string $imagedata): bool
private function isInterlacedPng(EncodedImage $image): bool
{
$f = $this->buildFilePointer($imagedata);
$contents = fread($f, 32);
fclose($f);
$contents = fread($image->toFilePointer(), 32);

return ord($contents[28]) != 0;
}
Expand Down
4 changes: 2 additions & 2 deletions tests/Unit/Drivers/Gd/Encoders/PngEncoderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function testEncode(): void
$encoder = new PngEncoder();
$result = $encoder->encode($image);
$this->assertMediaType('image/png', (string) $result);
$this->assertFalse($this->isInterlacedPng((string) $result));
$this->assertFalse($this->isInterlacedPng($result));
}

public function testEncodeInterlaced(): void
Expand All @@ -32,6 +32,6 @@ public function testEncodeInterlaced(): void
$encoder = new PngEncoder(interlaced: true);
$result = $encoder->encode($image);
$this->assertMediaType('image/png', (string) $result);
$this->assertTrue($this->isInterlacedPng((string) $result));
$this->assertTrue($this->isInterlacedPng($result));
}
}
4 changes: 2 additions & 2 deletions tests/Unit/Drivers/Imagick/Encoders/PngEncoderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public function testEncode(): void
$encoder = new PngEncoder();
$result = $encoder->encode($image);
$this->assertMediaType('image/png', (string) $result);
$this->assertFalse($this->isInterlacedPng((string) $result));
$this->assertFalse($this->isInterlacedPng($result));
}

public function testEncodeInterlaced(): void
Expand All @@ -32,6 +32,6 @@ public function testEncodeInterlaced(): void
$encoder = new PngEncoder(interlaced: true);
$result = $encoder->encode($image);
$this->assertMediaType('image/png', (string) $result);
$this->assertTrue($this->isInterlacedPng((string) $result));
$this->assertTrue($this->isInterlacedPng($result));
}
}
60 changes: 0 additions & 60 deletions tests/Unit/EncodedImageTest.php

This file was deleted.

39 changes: 39 additions & 0 deletions tests/Unit/FileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,31 @@

namespace Intervention\Image\Tests\Unit;

use Intervention\Image\Exceptions\InputException;
use PHPUnit\Framework\Attributes\CoversClass;
use Intervention\Image\File;
use Intervention\Image\Tests\BaseTestCase;
use Intervention\Image\Traits\CanBuildFilePointer;
use stdClass;

#[CoversClass(\Intervention\Image\File::class)]
final class FileTest extends BaseTestCase
{
use CanBuildFilePointer;

public function testConstructor(): void
{
$file = new File('foo');
$this->assertInstanceOf(File::class, $file);

$file = new File($this->buildFilePointer('foo'));
$this->assertInstanceOf(File::class, $file);
}

public function testConstructorUnkownType(): void
{
$this->expectException(InputException::class);
new File(new stdClass());
}

public function testSave(): void
Expand Down Expand Up @@ -46,4 +60,29 @@ public function testSize(): void
$file = new File('foo');
$this->assertEquals(3, $file->size());
}

public function testToDataUri(): void
{
$image = new File('foo');
$this->assertEquals('data:text/plain;base64,Zm9v', $image->toDataUri());
}

public function testMimetype(): void
{
$image = new File('foo');
$this->assertEquals('text/plain', $image->mimetype());
$this->assertEquals('text/plain', $image->mediaType());

$image = new File($this->getTestResourceData());
$this->assertEquals('image/jpeg', $image->mimetype());
$this->assertEquals('image/jpeg', $image->mediaType());

$image = new File("\x000\x001");
$this->assertEquals('application/octet-stream', $image->mimetype());
$this->assertEquals('application/octet-stream', $image->mediaType());

$image = new File('');
$this->assertEquals('application/x-empty', $image->mimetype());
$this->assertEquals('application/x-empty', $image->mediaType());
}
}

0 comments on commit e8001ac

Please sign in to comment.