diff --git a/.gitignore b/.gitignore index da734f1..0013978 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .puli/ -build/ vendor/ composer.lock -phpspec.yml phpunit.xml +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml index 3f32e86..cd3dbe9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,4 @@ language: php - sudo: false cache: @@ -7,11 +6,9 @@ cache: - $HOME/.composer/cache/files php: - - 5.5 - - 5.6 - - 7.0 - 7.1 - - hhvm + - 7.2 + - 7.3 env: global: @@ -24,14 +21,12 @@ branches: matrix: fast_finish: true include: - - php: 5.5 + - php: 7.3 env: COMPOSER_FLAGS="--prefer-stable --prefer-lowest" COVERAGE=true TEST_COMMAND="composer test-ci" -before_install: - - travis_retry composer self-update install: - - travis_retry composer update ${COMPOSER_FLAGS} --prefer-source --no-interaction + - composer update ${COMPOSER_FLAGS} --prefer-dist --no-interaction script: - $TEST_COMMAND diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0ee3d..146f73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Change Log +## UNRELEASED + +- Added support for PSR-17 factories. +- Dropped support for PHP < 7.2 + ## 1.0.0 - 2017-05-21 No changes from 0.2.0. diff --git a/composer.json b/composer.json index bc193ef..329e906 100644 --- a/composer.json +++ b/composer.json @@ -11,15 +11,16 @@ } ], "require": { - "php": "^5.5 || ^7.0", + "php": "^7.1", "psr/http-message": "^1.0", + "psr/http-factory": "^1.0", "php-http/message-factory": "^1.0.2", - "php-http/discovery": "^1.0" + "php-http/discovery": "^1.7" }, "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.4", + "phpunit/phpunit": "^7.5 || ^8.3", "php-http/message": "^1.5", - "zendframework/zend-diactoros": "^1.3.5" + "nyholm/psr7": "^1.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1277304..8c0f5b8 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -5,7 +5,7 @@ convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" - syntaxCheck="true"> +> ./tests diff --git a/src/MultipartStreamBuilder.php b/src/MultipartStreamBuilder.php index 8622c00..4652210 100644 --- a/src/MultipartStreamBuilder.php +++ b/src/MultipartStreamBuilder.php @@ -2,8 +2,11 @@ namespace Http\Message\MultipartStream; +use Http\Discovery\Exception\NotFoundException; +use Http\Discovery\Psr17FactoryDiscovery; use Http\Discovery\StreamFactoryDiscovery; -use Http\Message\StreamFactory; +use Http\Message\StreamFactory as HttplugStreamFactory; +use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\StreamInterface; /** @@ -16,7 +19,7 @@ class MultipartStreamBuilder { /** - * @var StreamFactory + * @var StreamFactory|StreamFactoryInterface */ private $streamFactory; @@ -36,11 +39,37 @@ class MultipartStreamBuilder private $data = []; /** - * @param StreamFactory|null $streamFactory + * @param StreamFactory|StreamFactoryInterface|null $streamFactory */ - public function __construct(StreamFactory $streamFactory = null) + public function __construct($streamFactory = null) { - $this->streamFactory = $streamFactory ?: StreamFactoryDiscovery::find(); + if ($streamFactory instanceof StreamFactoryInterface || $streamFactory instanceof HttplugStreamFactory) { + $this->streamFactory = $streamFactory; + + return; + } + + if (null !== $streamFactory) { + throw new \LogicException(sprintf( + 'First arguemnt to the constructor of "%s" must be of type "%s", "%s" or null. Got %s', + __CLASS__, + StreamFactoryInterface::class, + HttplugStreamFactory::class, + \is_object($streamFactory) ? \get_class($streamFactory) : \gettype($streamFactory) + )); + } + + // Try to find a stream factory. + try { + $this->streamFactory = Psr17FactoryDiscovery::findStreamFactory(); + } catch (NotFoundException $psr17Exception) { + try { + $this->streamFactory = StreamFactoryDiscovery::find(); + } catch (NotFoundException $httplugException) { + // we could not find any factory. + throw $psr17Exception; + } + } } /** @@ -58,7 +87,7 @@ public function __construct(StreamFactory $streamFactory = null) */ public function addResource($name, $resource, array $options = []) { - $stream = $this->streamFactory->createStream($resource); + $stream = $this->createStream($resource); // validate options['headers'] exists if (!isset($options['headers'])) { @@ -108,7 +137,7 @@ public function build() // Append end $streams .= "--{$this->getBoundary()}--\r\n"; - return $this->streamFactory->createStream($streams); + return $this->createStream($streams); } /** @@ -275,4 +304,31 @@ private function basename($path) return $filename; } + + /** + * @param string|resource|StreamInterface $resource + * + * @return StreamInterface + */ + private function createStream($resource) + { + if ($resource instanceof StreamInterface) { + return $resource; + } + + if ($this->streamFactory instanceof HttplugStreamFactory) { + return $this->streamFactory->createStream($resource); + } + + // Assert: We are using a PSR17 stream factory. + if (\is_string($resource)) { + return $this->streamFactory->createStream($resource); + } + + if (\is_resource($resource)) { + return $this->streamFactory->createStreamFromResource($resource); + } + + throw new \InvalidArgumentException(sprintf('First argument to "%s::createStream()" must be a string, resource or StreamInterface.', __CLASS__)); + } } diff --git a/tests/CustomMimetypeHelperTest.php b/tests/CustomMimetypeHelperTest.php index d8d1f10..185c35c 100644 --- a/tests/CustomMimetypeHelperTest.php +++ b/tests/CustomMimetypeHelperTest.php @@ -3,8 +3,9 @@ namespace tests\Http\Message\MultipartStream; use Http\Message\MultipartStream\CustomMimetypeHelper; +use PHPUnit\Framework\TestCase; -class CustomMimetypeHelperTest extends \PHPUnit_Framework_TestCase +class CustomMimetypeHelperTest extends TestCase { public function testGetMimetypeFromExtension() { diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php index 10d16d4..3d9c6ea 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -2,13 +2,18 @@ namespace tests\Http\Message\MultipartStream; +use Http\Message\MultipartStream\CustomMimetypeHelper; use Http\Message\MultipartStream\MultipartStreamBuilder; -use Zend\Diactoros\Stream; +use Nyholm\Psr7\Factory\HttplugFactory; +use Nyholm\Psr7\Factory\Psr17Factory; +use Nyholm\Psr7\Stream; +use PHPUnit\Framework\TestCase; +use Psr\Http\Message\StreamInterface; /** * @author Tobias Nyholm */ -class FunctionTest extends \PHPUnit_Framework_TestCase +class FunctionTest extends TestCase { public function testSupportStreams() { @@ -46,7 +51,7 @@ public function testSupportURIResources() $this->assertTrue(false !== strpos($multipartStream, 'Content-Type: image/png')); $urlContents = file_get_contents($url); - $this->assertContains($urlContents, $multipartStream); + $this->assertStringContainsString($urlContents, $multipartStream); } public function testResourceFilenameIsNotLocaleAware() @@ -139,20 +144,54 @@ public function testReset() $builder->reset(); $multipartStream = (string) $builder->build(); - $this->assertNotContains('foobar', $multipartStream, 'Stream should not have any data after reset()'); + $this->assertStringNotContainsString('foobar', $multipartStream, 'Stream should not have any data after reset()'); $this->assertNotEquals($boundary, $builder->getBoundary(), 'Stream should have a new boundary after reset()'); $this->assertNotEmpty($builder->getBoundary()); } + public function testThrowsExceptionIfNotStreamCompatible() + { + $builder = new MultipartStreamBuilder(); + $this->expectException(\InvalidArgumentException::class); + $builder->addResource('foo', []); + } + + public function testThrowsExceptionInConstructor() + { + $this->expectException(\LogicException::class); + new MultipartStreamBuilder(new CustomMimetypeHelper()); + } + + /** + * @dataProvider getStreamFactories + */ + public function testSupportDifferentFactories($factory) + { + $resource = fopen(__DIR__.'/Resources/httplug.png', 'r'); + + $builder = new MultipartStreamBuilder($factory); + $builder->addResource('image', $resource); + + $multipartStream = (string) $builder->build(); + $this->assertTrue(false !== strpos($multipartStream, 'Content-Disposition: form-data; name="image"; filename="httplug.png"')); + $this->assertTrue(false !== strpos($multipartStream, 'Content-Type: image/png')); + } + + public function getStreamFactories() + { + yield 'Httplug Stream Factory' => [new HttplugFactory()]; + yield 'PSR-17 Stream Factory' => [new Psr17Factory()]; + yield 'Null Stream Factory' => [null]; + } + /** * @param string $body * - * @return Stream + * @return StreamInterface */ private function createStream($body) { - $stream = new Stream('php://memory', 'rw'); - $stream->write($body); + $stream = Stream::create($body); $stream->rewind(); return $stream;