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

ImageStream implementation #147

Closed
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
115 changes: 67 additions & 48 deletions psalm-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@
<code>__construct</code>
</UnusedConstructor>
</file>
<file src="src/ImageStream.php">
<ImplementedReturnTypeMismatch>
<code>null|GdImage</code>
</ImplementedReturnTypeMismatch>
<MoreSpecificImplementedParamType>
<code>$resource</code>
</MoreSpecificImplementedParamType>
<NonInvariantDocblockPropertyType>
<code>$resource</code>
</NonInvariantDocblockPropertyType>
</file>
<file src="src/MessageTrait.php">
<DocblockTypeContradiction>
<code>! is_string($name)</code>
Expand Down Expand Up @@ -101,7 +112,7 @@
<code>$protocolVersion</code>
<code>$requestTarget</code>
<code>$uri</code>
<code>self::getValueFromKey($serializedRequest, 'body')</code>
<code><![CDATA[self::getValueFromKey($serializedRequest, 'body')]]></code>
</MixedArgument>
<MixedAssignment>
<code>$headers</code>
Expand Down Expand Up @@ -154,7 +165,7 @@
<code>$protocolVersion</code>
<code>$reasonPhrase</code>
<code>$statusCode</code>
<code>self::getValueFromKey($serializedResponse, 'body')</code>
<code><![CDATA[self::getValueFromKey($serializedResponse, 'body')]]></code>
</MixedArgument>
<MixedAssignment>
<code>$headers</code>
Expand Down Expand Up @@ -230,7 +241,7 @@
</file>
<file src="src/ServerRequestFactory.php">
<LessSpecificReturnStatement>
<code>$requestFilter(new ServerRequest(
<code><![CDATA[$requestFilter(new ServerRequest(
$server,
$files,
UriFactory::createFromSapi($server, $headers),
Expand All @@ -241,10 +252,10 @@
$query ?: $_GET,
$body ?: $_POST,
marshalProtocolVersionFromSapi($server)
))</code>
))]]></code>
</LessSpecificReturnStatement>
<MixedArgument>
<code>$headers['cookie']</code>
<code><![CDATA[$headers['cookie']]]></code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code>$headers</code>
Expand Down Expand Up @@ -365,10 +376,10 @@
</file>
<file src="src/functions/create_uploaded_file.php">
<MixedArgument>
<code>$spec['error']</code>
<code>$spec['name'] ?? null</code>
<code>$spec['tmp_name']</code>
<code>$spec['type'] ?? null</code>
<code><![CDATA[$spec['error']]]></code>
<code><![CDATA[$spec['name'] ?? null]]></code>
<code><![CDATA[$spec['tmp_name']]]></code>
<code><![CDATA[$spec['type'] ?? null]]></code>
</MixedArgument>
</file>
<file src="src/functions/marshal_headers_from_sapi.legacy.php">
Expand All @@ -393,8 +404,8 @@
<code>string</code>
</MixedInferredReturnType>
<MixedReturnStatement>
<code>$server['REQUEST_METHOD'] ?? 'GET'</code>
<code>$server['REQUEST_METHOD'] ?? 'GET'</code>
<code><![CDATA[$server['REQUEST_METHOD'] ?? 'GET']]></code>
<code><![CDATA[$server['REQUEST_METHOD'] ?? 'GET']]></code>
</MixedReturnStatement>
</file>
<file src="src/functions/marshal_protocol_version_from_sapi.legacy.php">
Expand All @@ -404,7 +415,7 @@
</file>
<file src="src/functions/marshal_protocol_version_from_sapi.php">
<MixedArgument>
<code>$server['SERVER_PROTOCOL']</code>
<code><![CDATA[$server['SERVER_PROTOCOL']]]></code>
</MixedArgument>
</file>
<file src="src/functions/marshal_uri_from_sapi.legacy.php">
Expand All @@ -424,14 +435,14 @@
<code>static function (string $name, array $headers, $default = null) {</code>
</MissingClosureReturnType>
<MixedArgument>
<code>$getHeaderFromArray('x-forwarded-proto', $headers, '')</code>
<code><![CDATA[$getHeaderFromArray('x-forwarded-proto', $headers, '')]]></code>
<code>$host</code>
<code>$host</code>
<code>$host</code>
<code>$host</code>
<code>$port</code>
<code>$requestUri</code>
<code>$server['QUERY_STRING']</code>
<code><![CDATA[$server['QUERY_STRING']]]></code>
</MixedArgument>
<MixedArgumentTypeCoercion>
<code>$headers[$header]</code>
Expand All @@ -448,15 +459,15 @@
<code>string</code>
</MixedInferredReturnType>
<MixedOperand>
<code>$server['SERVER_ADDR']</code>
<code><![CDATA[$server['SERVER_ADDR']]]></code>
</MixedOperand>
<MixedReturnStatement>
<code>$defaults</code>
<code>$origPathInfo</code>
<code>$unencodedUrl</code>
</MixedReturnStatement>
<PossiblyFalseOperand>
<code>strrpos($host, ':')</code>
<code><![CDATA[strrpos($host, ':')]]></code>
</PossiblyFalseOperand>
</file>
<file src="src/functions/normalize_server.legacy.php">
Expand All @@ -467,13 +478,13 @@
</file>
<file src="src/functions/normalize_server.php">
<MixedArrayAccess>
<code>$apacheRequestHeaders['Authorization']</code>
<code>$apacheRequestHeaders['authorization']</code>
<code><![CDATA[$apacheRequestHeaders['Authorization']]]></code>
<code><![CDATA[$apacheRequestHeaders['authorization']]]></code>
</MixedArrayAccess>
<MixedAssignment>
<code>$apacheRequestHeaders</code>
<code>$server['HTTP_AUTHORIZATION']</code>
<code>$server['HTTP_AUTHORIZATION']</code>
<code><![CDATA[$server['HTTP_AUTHORIZATION']]]></code>
<code><![CDATA[$server['HTTP_AUTHORIZATION']]]></code>
</MixedAssignment>
</file>
<file src="src/functions/normalize_uploaded_files.legacy.php">
Expand Down Expand Up @@ -501,25 +512,25 @@
$nameTree[$key] ?? null,
$typeTree[$key] ?? null
)</code>
<code>$recursiveNormalize(
<code><![CDATA[$recursiveNormalize(
$files['tmp_name'],
$files['size'],
$files['error'],
$files['name'] ?? null,
$files['type'] ?? null
)</code>
)]]></code>
</MixedFunctionCall>
<MixedInferredReturnType>
<code>array</code>
</MixedInferredReturnType>
<MixedReturnStatement>
<code>$recursiveNormalize(
<code><![CDATA[$recursiveNormalize(
$files['tmp_name'],
$files['size'],
$files['error'],
$files['name'] ?? null,
$files['type'] ?? null
)</code>
)]]></code>
</MixedReturnStatement>
</file>
<file src="src/functions/parse_cookie_header.legacy.php">
Expand All @@ -541,6 +552,11 @@
<code>$ret</code>
</MixedAssignment>
</file>
<file src="test/ImageStreamTest.php">
<InvalidArgument>
<code>$resourceToAttach</code>
</InvalidArgument>
</file>
<file src="test/Integration/UploadedFileTest.php">
<PossiblyNullArgument>
<code><![CDATA[$stream->getSize()]]></code>
Expand Down Expand Up @@ -599,7 +615,7 @@
</file>
<file src="test/ServerRequestFactoryTest.php">
<InvalidArgument>
<code>$normalizedFiles['fooFiles']</code>
<code><![CDATA[$normalizedFiles['fooFiles']]]></code>
</InvalidArgument>
</file>
<file src="test/ServerRequestTest.php">
Expand All @@ -616,6 +632,9 @@
<DeprecatedMethod>
<code>setMethods</code>
</DeprecatedMethod>
<UnusedClosureParam>
<code>$errno</code>
</UnusedClosureParam>
</file>
<file src="test/TestAsset/CallbacksForCallbackStreamTest.php">
<PossiblyUnusedMethod>
Expand Down Expand Up @@ -650,23 +669,23 @@
</file>
<file src="test/functions/NormalizeUploadedFilesTest.php">
<MixedArgument>
<code>$normalised['my-form']['details']['avatars']</code>
<code>$normalised['slide-shows'][0]['slides']</code>
<code><![CDATA[$normalised['my-form']['details']['avatars']]]></code>
<code><![CDATA[$normalised['slide-shows'][0]['slides']]]></code>
</MixedArgument>
<MixedArrayAccess>
<code>$normalised['my-form']['details']['avatar']</code>
<code>$normalised['my-form']['details']['avatars']</code>
<code>$normalised['my-form']['details']['avatars']</code>
<code>$normalised['my-form']['details']['avatars']</code>
<code>$normalised['my-form']['details']['avatars']</code>
<code>$normalised['my-form']['details']['avatars'][0]</code>
<code>$normalised['my-form']['details']['avatars'][1]</code>
<code>$normalised['my-form']['details']['avatars'][2]</code>
<code>$normalised['slide-shows'][0]['slides']</code>
<code>$normalised['slide-shows'][0]['slides']</code>
<code>$normalised['slide-shows'][0]['slides']</code>
<code>$normalised['slide-shows'][0]['slides'][0]</code>
<code>$normalised['slide-shows'][0]['slides'][1]</code>
<code><![CDATA[$normalised['my-form']['details']['avatar']]]></code>
<code><![CDATA[$normalised['my-form']['details']['avatars']]]></code>
<code><![CDATA[$normalised['my-form']['details']['avatars']]]></code>
<code><![CDATA[$normalised['my-form']['details']['avatars']]]></code>
<code><![CDATA[$normalised['my-form']['details']['avatars']]]></code>
<code><![CDATA[$normalised['my-form']['details']['avatars'][0]]]></code>
<code><![CDATA[$normalised['my-form']['details']['avatars'][1]]]></code>
<code><![CDATA[$normalised['my-form']['details']['avatars'][2]]]></code>
<code><![CDATA[$normalised['slide-shows'][0]['slides']]]></code>
<code><![CDATA[$normalised['slide-shows'][0]['slides']]]></code>
<code><![CDATA[$normalised['slide-shows'][0]['slides']]]></code>
<code><![CDATA[$normalised['slide-shows'][0]['slides'][0]]]></code>
<code><![CDATA[$normalised['slide-shows'][0]['slides'][1]]]></code>
</MixedArrayAccess>
<MixedMethodCall>
<code>getClientFilename</code>
Expand All @@ -677,14 +696,14 @@
<code>getClientFilename</code>
</MixedMethodCall>
<UndefinedInterfaceMethod>
<code>$normalised['my-form']</code>
<code>$normalised['my-form']</code>
<code>$normalised['my-form']</code>
<code>$normalised['my-form']</code>
<code>$normalised['my-form']</code>
<code>$normalised['slide-shows']</code>
<code>$normalised['slide-shows']</code>
<code>$normalised['slide-shows']</code>
<code><![CDATA[$normalised['my-form']]]></code>
<code><![CDATA[$normalised['my-form']]]></code>
<code><![CDATA[$normalised['my-form']]]></code>
<code><![CDATA[$normalised['my-form']]]></code>
<code><![CDATA[$normalised['my-form']]]></code>
<code><![CDATA[$normalised['slide-shows']]]></code>
<code><![CDATA[$normalised['slide-shows']]]></code>
<code><![CDATA[$normalised['slide-shows']]]></code>
</UndefinedInterfaceMethod>
</file>
</files>
64 changes: 64 additions & 0 deletions src/ImageStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

declare(strict_types=1);

namespace Laminas\Diactoros;

use GdImage;

use function sprintf;

/**
* This class purposely does not override the $resource property in order to
* allow it to extend Stream. If defined here with a type, PHP will raise a
* fatal error complaining that it must not define a type for the property.
*/
class ImageStream extends Stream
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not extend Stream at all. It is not compatible with any of its methods.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree here, extending Stream makes no sense as we have to handle almost every method in a specific way.
Especially due to the GdImage/resource problematic, we could also just provide GdImageStream as it is for GdImage only.
Not sure if some1 wants to use this for anything else, so renaming it might be helpful but I do not insist on this.

{
/**
* This purposely does not explicitly override the $resource property in
* order to allow it to extend Stream. If defined here with a type, PHP will
* raise a fatal error complaining that it must not define a type for the
* property.
*
* @var null|GdImage
*/
protected $resource;

public function __construct(
GdImage $resource,
) {
$this->resource = $resource;
}

/**
* {@inheritdoc}
*
* @return null|GdImage
*/
public function detach(): ?GdImage
{
$resource = $this->resource;
$this->resource = null;
return $resource;
}

/**
* Attach a new stream/resource to the instance.
*
* @param GdImage $resource
* @param string $mode Unused
* @throws Exception\InvalidArgumentException When provided a non-GdImage resource.
*/
public function attach($resource, string $mode = 'r'): void
{
if (! $resource instanceof GdImage) {
throw new Exception\InvalidArgumentException(sprintf(
'When attaching a resource to %s, resource must be a GdImage',
$this::class,
));
}

$this->resource = $resource;
}
}
10 changes: 10 additions & 0 deletions src/Stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
use function stream_get_contents;
use function stream_get_meta_data;
use function strstr;
use function trigger_error;

use const E_USER_DEPRECATED;
use const SEEK_SET;

/**
Expand Down Expand Up @@ -359,6 +361,14 @@ private function isValidStreamResourceType(mixed $resource): bool
}

if ($resource instanceof GdImage) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get type errors for pretty much all methods in Stream when GdImage is used as resource.

Uncaught TypeError: stream_get_contents(): Argument #1 ($stream) must be of type resource, GdImage given
Uncaught TypeError: stream_get_meta_data(): Argument #1 ($stream) must be of type resource, GdImage given
Uncaught TypeError: fseek(): Argument #1 ($stream) must be of type resource, GdImage given

and so on

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It did not work at all for a long time. I suggest "support" is dropped without deprecation as no actual users will be affected.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yah, there were no proper tests for PHP 8.1
Thats why I raised the whole concerns in the first place.

Also stream seeking on gdimage resources never worked and plenty of other stuff (i.e. writing to that stream).

Thats why the new ImageStream implementation has to return false for isSeekable, isWritable, etc.

Has to throw exceptions for getMetadata, etc.

trigger_error(
sprintf(
'When using GdImage resources, use %s; %s will drop support for GdImage in 3.0.0',
ImageStream::class,
self::class
),
E_USER_DEPRECATED,
);
return true;
}

Expand Down
7 changes: 7 additions & 0 deletions src/StreamFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Laminas\Diactoros;

use GdImage;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\StreamInterface;

Expand Down Expand Up @@ -35,9 +36,15 @@ public function createStreamFromFile(string $file, string $mode = 'r'): StreamIn

/**
* {@inheritDoc}
*
* @param resource|GdImage $resource
*/
Comment on lines +39 to 41
Copy link
Member

@Xerkus Xerkus May 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if this is good. Expanding parameter types is considered type safe. The problem is anyone needing GdImage support can no longer depend on PSR factory interface because that would not be type safe.

Wouldn't that go against interoperability goal between libraries since this will be creating dependency on diactoros and this implementation?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GdImage is considered an opaque resource type. The general assumption of PSR-7 and PSR-17's stream support is that it can work with resources of any type. However, both pre-date the opaque resource types.

This suggests two things to me:

  • They should allow usage with opaque resource types where they make sense. Since you can have image resources spit out content, I'd argue they make sense here, and, in some ways, are better than normal file-based resources where you lose idempotency.
  • Alternately, PSR-7 and/or PSR-17 need updates to either explicitly allow or disallow usage with opaque resource types.

For now, I don't think this patch violates interoperability, as PSR-7 and PSR-17 do talk about resources, and this is a type of resource.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not support GdImage via StreamFactory TBH

PSR is clear here:
https://github.com/php-fig/http-factory/blob/e616d01114759c4c489f93b099585439f795fe35/src/StreamFactoryInterface.php#L44

If some1 wants to use GdImage with StreamFactoryInterface they are already lost. If they hardly depend on laminas as an implementation, they don't need PSR and thus can directly instantiate ImageStream rather than using the interchangeable factory. The whole point of using the StreamFactory is to provide interchangeability and if we now decide to support GdImage that would only fork for laminas but not for guzzle for example. Having these kind of incompatibility is super annoying and thus, I'd say we should not allow that at all.

public function createStreamFromResource($resource): StreamInterface
{
if ($resource instanceof GdImage) {
return new ImageStream($resource);
}

return new Stream($resource);
}
}
Loading