Skip to content

Add Message cloner #49

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

Closed
wants to merge 2 commits into from
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
25 changes: 25 additions & 0 deletions spec/MessageClonerSpec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

namespace spec\Http\Message;

use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use Psr\Http\Message\MessageInterface;
use spec\Http\Message\Encoding\MemoryStream;

class MessageClonerSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType('Http\Message\MessageCloner');
}

public function it_clone_a_message(MessageInterface $message, MessageInterface $messageCloned)
Copy link
Member

Choose a reason for hiding this comment

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

clones

Copy link
Member

Choose a reason for hiding this comment

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

$clonedMessage

{
$stream = new MemoryStream('test');
$message->getBody()->willReturn($stream);
$message->withBody(Argument::type('Http\Message\MemoryClonedStream'))->willReturn($messageCloned);

$this->cloneMessage($message)->shouldEqual($messageCloned);
}
}
182 changes: 182 additions & 0 deletions src/MemoryClonedStream.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php

namespace Http\Message;

use Psr\Http\Message\StreamInterface;

/**
* Represent a stream cloned into memory.
*/
class MemoryClonedStream implements StreamInterface
{
const COPY_BUFFER = 8192;

private $resource;
Copy link
Member

Choose a reason for hiding this comment

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

Can you add type hints here too for internal usage?


private $size;
Copy link
Member

Choose a reason for hiding this comment

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

Same here


/**
* @param StreamInterface $stream Stream to clone
* @param bool $useFileBuffer Use a file buffer to avoid memory consumption on PHP script (default to true)
* @param int $memoryBuffer The amount of memory of which the stream is buffer into a file when setting
* $useFileBuffer to true (default to 2MB)
*/
public function __construct(StreamInterface $stream, $useFileBuffer = true, $memoryBuffer = 2097152)
{
$this->size = 0;

if ($useFileBuffer) {
$this->resource = fopen('php://temp/maxmemory:'.$memoryBuffer, 'rw+');
} else {
$this->resource = fopen('php://memory', 'rw+');
}

Copy link
Member

Choose a reason for hiding this comment

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

Maybe verify that $this->resource is not false.

$position = null;

if ($stream->isSeekable()) {
$position = $stream->tell();
$stream->rewind();
}

while (!$stream->eof()) {
$this->size += fwrite($this->resource, $stream->read(self::COPY_BUFFER));
}

if ($stream->isSeekable()) {
$stream->seek($position);
}

rewind($this->resource);
}

Copy link
Member

Choose a reason for hiding this comment

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

Add __destruct

public function __destruct()
{
    $this->close();
}

/**
* {@inheritdoc}
*/
public function __toString()
{
return $this->getContents();
Copy link
Member

Choose a reason for hiding this comment

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

You should rewind it first

}

/**
* {@inheritdoc}
*/
public function close()
{
fclose($this->resource);
Copy link
Member

Choose a reason for hiding this comment

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

Check if $this->resource is not null.

}

/**
* {@inheritdoc}
*/
public function detach()
{
$resource = $this->resource;
$this->resource = null;

return $resource;
Copy link
Member

@Nyholm Nyholm Aug 1, 2016

Choose a reason for hiding this comment

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

Size should be set to zero null

}

/**
* {@inheritdoc}
*/
public function getSize()
{
return $this->size;
}

/**
* {@inheritdoc}
*/
public function tell()
{
return ftell($this->resource);
Copy link
Member

Choose a reason for hiding this comment

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

Check if $this->resource is not null.

}

/**
* {@inheritdoc}
*/
public function eof()
{
return feof($this->resource);
Copy link
Member

Choose a reason for hiding this comment

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

Check if $this->resource is not null.

}

/**
* {@inheritdoc}
*/
public function isSeekable()
{
return true;
Copy link
Member

Choose a reason for hiding this comment

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

Return false if $this->resource is null.

}

/**
* {@inheritdoc}
*/
public function seek($offset, $whence = SEEK_SET)
{
return fseek($this->resource, $offset);
Copy link
Member

Choose a reason for hiding this comment

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

I think psr7 says you should throw exception of whence is not supported.

}

/**
* {@inheritdoc}
*/
public function rewind()
{
return rewind($this->resource);
Copy link
Member

Choose a reason for hiding this comment

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

Check if $this->resource is not null.

}

/**
* {@inheritdoc}
*/
public function isWritable()
{
return true;
Copy link
Member

Choose a reason for hiding this comment

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

Return false if $this->resource is null.

}

/**
* {@inheritdoc}
*/
public function write($string)
{
return fwrite($this->resource, $string);
Copy link
Member

Choose a reason for hiding this comment

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

Check if $this->resource is not null.

}

/**
* {@inheritdoc}
*/
public function isReadable()
{
return true;
Copy link
Member

Choose a reason for hiding this comment

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

Return false if $this->resource is null.

}

/**
* {@inheritdoc}
*/
public function read($length)
{
return fread($this->resource, $length);
Copy link
Member

Choose a reason for hiding this comment

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

Check if $this->resource is not null.

}

/**
* {@inheritdoc}
*/
public function getContents()
{
$this->rewind();
Copy link
Member

Choose a reason for hiding this comment

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

PSR7 says that getContents should return "the rest of the string" if I remember corectly. Do not rewind here


return $this->read($this->size);
}

/**
* {@inheritdoc}
*/
public function getMetadata($key = null)
{
$metadata = stream_get_meta_data($this->resource);
Copy link
Member

Choose a reason for hiding this comment

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

Check if $this->resource is not null.


if (null === $key) {
return $metadata;
}

return $metadata[$key];
Copy link
Member

Choose a reason for hiding this comment

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

Verify with isset

}
}
31 changes: 31 additions & 0 deletions src/MessageCloner.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Http\Message;

use Psr\Http\Message\MessageInterface;

/**
* Allow to clone request and response.
*
* A message cloner is only necessary when you also want to duplicate the stream of a request or a response, as by
* default object returned by `clone $message` call will have the same stream as the cloned one, so reading the body of
* one of the message will affect the other.
*/
class MessageCloner
{
/**
* Clone a message.
*
* When cloning you have to be careful that the original stream is rewindable. If not the original stream will be
* readed and cannot be readed one more time. To avoid this behavior, when the original stream is not seekable, you
* can clone the cloned message, and replace the original message with one of the clone, as there are always rewindable.
*
* @param MessageInterface $message
*
* @return MessageInterface|static
*/
public function cloneMessage(MessageInterface $message)
{
return $message->withBody(new MemoryClonedStream($message->getBody()));
}
}