diff --git a/composer.json b/composer.json index f013f158e..3e9e5a467 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "ext-filter": "*", "ext-json": "*", "container-interop/container-interop": "^1.2", + "laminas/laminas-diactoros": "^2.13.0", "laminas/laminas-escaper": "^2.5", "laminas/laminas-eventmanager": "^3.4", "laminas/laminas-json": "^3.3", diff --git a/composer.lock b/composer.lock index 30a010dd0..c371b3641 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,107 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f9740d25a5b9d0482c3dedf4adc31831", + "content-hash": "2e054df72e825eb3aa69414dec0f03e6", "packages": [ + { + "name": "laminas/laminas-diactoros", + "version": "2.13.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-diactoros.git", + "reference": "34ba65010be9aa74e159d168c5ecfa5c01e4d956" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-diactoros/zipball/34ba65010be9aa74e159d168c5ecfa5c01e4d956", + "reference": "34ba65010be9aa74e159d168c5ecfa5c01e4d956", + "shasum": "" + }, + "require": { + "php": "^7.3 || ~8.0.0 || ~8.1.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.0" + }, + "conflict": { + "phpspec/prophecy": "<1.9.0", + "zendframework/zend-diactoros": "*" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "ext-dom": "*", + "ext-gd": "*", + "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.9.0", + "laminas/laminas-coding-standard": "~2.3.0", + "php-http/psr7-integration-tests": "^1.1.1", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psalm/plugin-phpunit": "^0.17.0", + "vimeo/psalm": "^4.24.0" + }, + "type": "library", + "extra": { + "laminas": { + "config-provider": "Laminas\\Diactoros\\ConfigProvider", + "module": "Laminas\\Diactoros" + } + }, + "autoload": { + "files": [ + "src/functions/create_uploaded_file.php", + "src/functions/marshal_headers_from_sapi.php", + "src/functions/marshal_method_from_sapi.php", + "src/functions/marshal_protocol_version_from_sapi.php", + "src/functions/marshal_uri_from_sapi.php", + "src/functions/normalize_server.php", + "src/functions/normalize_uploaded_files.php", + "src/functions/parse_cookie_header.php", + "src/functions/create_uploaded_file.legacy.php", + "src/functions/marshal_headers_from_sapi.legacy.php", + "src/functions/marshal_method_from_sapi.legacy.php", + "src/functions/marshal_protocol_version_from_sapi.legacy.php", + "src/functions/marshal_uri_from_sapi.legacy.php", + "src/functions/normalize_server.legacy.php", + "src/functions/normalize_uploaded_files.legacy.php", + "src/functions/parse_cookie_header.legacy.php" + ], + "psr-4": { + "Laminas\\Diactoros\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "PSR HTTP Message implementations", + "homepage": "https://laminas.dev", + "keywords": [ + "http", + "laminas", + "psr", + "psr-17", + "psr-7" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-diactoros/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-diactoros/issues", + "rss": "https://github.com/laminas/laminas-diactoros/releases.atom", + "source": "https://github.com/laminas/laminas-diactoros" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-07-07T12:31:03+00:00" + }, { "name": "laminas/laminas-escaper", "version": "2.10.0", @@ -393,6 +492,114 @@ "source": "https://github.com/php-fig/container/tree/1.1.2" }, "time": "2021-11-05T16:50:12+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, + "time": "2019-04-30T12:38:16+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, + "time": "2016-08-06T14:39:51+00:00" } ], "packages-dev": [ diff --git a/src/Helper/ServerUrl.php b/src/Helper/ServerUrl.php index 1de22fe8f..72de35fbc 100644 --- a/src/Helper/ServerUrl.php +++ b/src/Helper/ServerUrl.php @@ -15,12 +15,20 @@ /** * Helper for returning the current server URL (optionally with request URI) + * + * @psalm-suppress DeprecatedProperty,DeprecatedMethod + * @final */ class ServerUrl extends AbstractHelper { + use DeprecatedAbstractHelperHierarchyTrait; + /** * Host (including port) * + * @deprecated since 2.21.0, this property will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @var string|null */ protected $host; @@ -28,6 +36,9 @@ class ServerUrl extends AbstractHelper /** * Port * + * @deprecated since 2.21.0, this property will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @var int|null */ protected $port; @@ -35,6 +46,9 @@ class ServerUrl extends AbstractHelper /** * Scheme * + * @deprecated since 2.21.0, this property will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @var string|null */ protected $scheme; @@ -42,10 +56,20 @@ class ServerUrl extends AbstractHelper /** * Whether or not to query proxy servers for address * + * @deprecated since 2.21.0, this property will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @var bool */ protected $useProxy = false; + private ?string $serverUrl; + + public function __construct(?string $serverUrl = null) + { + $this->serverUrl = $serverUrl; + } + /** * View helper entry point: * Returns the current host's URL like http://site.com @@ -60,19 +84,28 @@ class ServerUrl extends AbstractHelper public function __invoke($requestUri = null) { if ($requestUri === true) { - $path = $_SERVER['REQUEST_URI']; - } elseif (is_string($requestUri)) { - $path = $requestUri; - } else { - $path = ''; + /** @var string|null $requestUri */ + $requestUri = $_SERVER['REQUEST_URI'] ?? null; } - return $this->getScheme() . '://' . $this->getHost() . $path; + $path = is_string($requestUri) ? $requestUri : ''; + $serverUrl = $this->serverUrl ?: $this->legacyServerUrlDetection(); + + return $serverUrl . $path; + } + + private function legacyServerUrlDetection(): string + { + /** @psalm-suppress DeprecatedMethod */ + return $this->getScheme() . '://' . $this->getHost(); } /** * Detect the host based on headers * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @return void */ protected function detectHost() @@ -118,6 +151,9 @@ protected function detectHost() /** * Detect the port + * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. */ protected function detectPort(): void { @@ -136,6 +172,9 @@ protected function detectPort(): void /** * Detect the scheme + * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. */ protected function detectScheme(): void { @@ -158,6 +197,10 @@ protected function detectScheme(): void $this->setScheme($scheme); } + /** + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + */ protected function isReversedProxy(): bool { return isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https'; @@ -166,6 +209,9 @@ protected function isReversedProxy(): bool /** * Detect if a proxy is in use, and, if so, set the host based on it * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @return bool */ protected function setHostFromProxy() @@ -194,6 +240,9 @@ protected function setHostFromProxy() /** * Set port based on detected proxy headers * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @return bool */ protected function setPortFromProxy() @@ -215,6 +264,9 @@ protected function setPortFromProxy() /** * Set the current scheme based on detected proxy headers * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @return bool */ protected function setSchemeFromProxy() @@ -248,6 +300,9 @@ protected function setSchemeFromProxy() /** * Sets host * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @param string $host * @return ServerUrl */ @@ -272,6 +327,9 @@ public function setHost($host) /** * Returns host * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @return string */ public function getHost() @@ -286,6 +344,9 @@ public function getHost() /** * Set server port * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @param int|numeric-string $port * @return ServerUrl */ @@ -299,6 +360,9 @@ public function setPort($port) /** * Retrieve the server port * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @return int|null */ public function getPort() @@ -313,6 +377,9 @@ public function getPort() /** * Sets scheme (typically http or https) * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @param string $scheme * @return ServerUrl */ @@ -326,6 +393,9 @@ public function setScheme($scheme) /** * Returns scheme (typically http or https) * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @return string */ public function getScheme() @@ -340,6 +410,9 @@ public function getScheme() /** * Set flag indicating whether or not to query proxy servers * + * @deprecated since 2.21.0, this method will be removed in version 3.0.0 of this component. + * The server url should be given to the constructor. + * * @param bool $useProxy * @return ServerUrl */ diff --git a/src/Helper/Service/ServerUrlFactory.php b/src/Helper/Service/ServerUrlFactory.php new file mode 100644 index 000000000..7bff65874 --- /dev/null +++ b/src/Helper/Service/ServerUrlFactory.php @@ -0,0 +1,56 @@ +fetchConfiguredServerUrl($container) ?: $this->detectServerUrlFromEnvironment() + ); + } + + private function fetchConfiguredServerUrl(ContainerInterface $container): ?string + { + $config = $container->has('config') ? $container->get('config') : []; + assert(is_array($config) || $config instanceof ArrayAccess); + + $helperConfig = $config['view_helper_config'] ?? []; + assert(is_array($helperConfig)); + + $serverUrl = $helperConfig['server_url'] ?? null; + assert(is_string($serverUrl) || $serverUrl === null); + + return $serverUrl; + } + + private function detectServerUrlFromEnvironment(): string + { + $serverRequest = ServerRequestFactory::fromGlobals($_SERVER); + $uri = $serverRequest->getUri() + ->withPath('') + ->withQuery('') + ->withFragment(''); + + if (! $uri->getHost() || ! $uri->getScheme()) { + throw new RuntimeException( + 'The current host or scheme cannot be detected from the environment' + ); + } + + return (string) $uri; + } +} diff --git a/src/HelperPluginManager.php b/src/HelperPluginManager.php index 3e7c75945..05702f542 100644 --- a/src/HelperPluginManager.php +++ b/src/HelperPluginManager.php @@ -286,7 +286,7 @@ class HelperPluginManager extends AbstractPluginManager Helper\Placeholder::class => InvokableFactory::class, Helper\RenderChildModel::class => InvokableFactory::class, Helper\RenderToPlaceholder::class => InvokableFactory::class, - Helper\ServerUrl::class => InvokableFactory::class, + Helper\ServerUrl::class => Helper\Service\ServerUrlFactory::class, Helper\Url::class => InvokableFactory::class, Helper\ViewModel::class => InvokableFactory::class, diff --git a/test/Helper/ServerUrlTest.php b/test/Helper/ServerUrlTest.php index 0ee10a40e..2dfade543 100644 --- a/test/Helper/ServerUrlTest.php +++ b/test/Helper/ServerUrlTest.php @@ -4,7 +4,7 @@ namespace LaminasTest\View\Helper; -use Laminas\View\Helper; +use Laminas\View\Helper\ServerUrl; use PHPUnit\Framework\TestCase; use stdClass; @@ -17,9 +17,6 @@ class ServerUrlTest extends TestCase */ protected $serverBackup; - /** - * Prepares the environment before running a test. - */ protected function setUp(): void { $this->serverBackup = $_SERVER; @@ -27,183 +24,218 @@ protected function setUp(): void unset($_SERVER['SERVER_PORT']); } - /** - * Cleans up the environment after running a test. - */ protected function tearDown(): void { $_SERVER = $this->serverBackup; } + public function testConfiguredServerUrlWillBeUsed(): void + { + $helper = new ServerUrl('https://example.com'); + self::assertEquals('https://example.com', $helper()); + } + + public function testPathWillBeAppendedToConfiguredServerUrl(): void + { + $helper = new ServerUrl('https://example.com'); + self::assertEquals('https://example.com/foo', $helper('/foo')); + } + + public function testRequestUriWillBeAppendedWhenArgumentIsTrue(): void + { + $helper = new ServerUrl('https://example.com'); + $_SERVER['REQUEST_URI'] = '/baz'; + self::assertEquals('https://example.com/baz', $helper(true)); + } + + /** @deprecated */ public function testConstructorWithOnlyHost(): void { $_SERVER['HTTP_HOST'] = 'example.com'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.com', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithOnlyHostIncludingPort(): void { $_SERVER['HTTP_HOST'] = 'example.com:8000'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.com:8000', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithHostAndHttpsOn(): void { $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['HTTPS'] = 'on'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('https://example.com', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithHostAndHttpsTrue(): void { $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['HTTPS'] = true; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('https://example.com', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithHostIncludingPortAndHttpsTrue(): void { $_SERVER['HTTP_HOST'] = 'example.com:8181'; $_SERVER['HTTPS'] = true; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('https://example.com:8181', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithHostReversedProxyHttpsTrue(): void { $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https'; $_SERVER['SERVER_PORT'] = 80; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('https://example.com', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithHttpHostIncludingPortAndPortSet(): void { $_SERVER['HTTP_HOST'] = 'example.com:8181'; $_SERVER['SERVER_PORT'] = 8181; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.com:8181', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithHttpHostAndServerNameAndPortSet(): void { $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['SERVER_NAME'] = 'example.org'; $_SERVER['SERVER_PORT'] = 8080; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.com:8080', $url->__invoke()); } + /** @deprecated */ public function testConstructorWithNoHttpHostButServerNameAndPortSet(): void { unset($_SERVER['HTTP_HOST']); $_SERVER['SERVER_NAME'] = 'example.org'; $_SERVER['SERVER_PORT'] = 8080; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.org:8080', $url->__invoke()); } + /** @deprecated */ public function testServerUrlWithTrueParam(): void { $_SERVER['HTTPS'] = 'off'; $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/foo.html'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.com/foo.html', $url->__invoke(true)); } + /** @deprecated */ public function testServerUrlWithInteger(): void { $_SERVER['HTTPS'] = 'off'; $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/foo.html'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.com', $url->__invoke(1337)); } + /** @deprecated */ public function testServerUrlWithObject(): void { $_SERVER['HTTPS'] = 'off'; $_SERVER['HTTP_HOST'] = 'example.com'; $_SERVER['REQUEST_URI'] = '/foo.html'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://example.com', $url->__invoke(new stdClass())); } + /** @deprecated */ public function testServerUrlWithScheme(): void { $_SERVER['HTTP_SCHEME'] = 'https'; $_SERVER['HTTP_HOST'] = 'example.com'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('https://example.com', $url->__invoke()); } + /** @deprecated */ public function testServerUrlWithPort(): void { $_SERVER['SERVER_PORT'] = 443; $_SERVER['HTTP_HOST'] = 'example.com'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('https://example.com', $url->__invoke()); } + /** @deprecated */ public function testServerUrlWithProxy(): void { $_SERVER['HTTP_HOST'] = 'proxyserver.com'; $_SERVER['HTTP_X_FORWARDED_HOST'] = 'www.firsthost.org'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $url->setUseProxy(true); $this->assertEquals('http://www.firsthost.org', $url->__invoke()); } + /** @deprecated */ public function testServerUrlWithMultipleProxies(): void { $_SERVER['HTTP_HOST'] = 'proxyserver.com'; $_SERVER['HTTP_X_FORWARDED_HOST'] = 'www.firsthost.org, www.secondhost.org'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $url->setUseProxy(true); $this->assertEquals('http://www.secondhost.org', $url->__invoke()); } + /** @deprecated */ public function testDoesNotUseProxyByDefault(): void { $_SERVER['HTTP_HOST'] = 'proxyserver.com'; $_SERVER['HTTP_X_FORWARDED_HOST'] = 'www.firsthost.org, www.secondhost.org'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://proxyserver.com', $url->__invoke()); } + /** @deprecated */ public function testCanUseXForwardedPortIfProvided(): void { $_SERVER['HTTP_HOST'] = 'proxyserver.com'; $_SERVER['HTTP_X_FORWARDED_HOST'] = 'www.firsthost.org, www.secondhost.org'; $_SERVER['HTTP_X_FORWARDED_PORT'] = '8888'; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $url->setUseProxy(true); $this->assertEquals('http://www.secondhost.org:8888', $url->__invoke()); } + /** @deprecated */ public function testUsesHostHeaderWhenPortForwardingDetected(): void { $_SERVER['HTTP_HOST'] = 'localhost:10088'; $_SERVER['SERVER_PORT'] = 10081; - $url = new Helper\ServerUrl(); + $url = new ServerUrl(); $this->assertEquals('http://localhost:10088', $url->__invoke()); } } diff --git a/test/Helper/Service/ServerUrlFactoryTest.php b/test/Helper/Service/ServerUrlFactoryTest.php new file mode 100644 index 000000000..606406b01 --- /dev/null +++ b/test/Helper/Service/ServerUrlFactoryTest.php @@ -0,0 +1,89 @@ + */ + private array $serverVariables; + + protected function setUp(): void + { + parent::setUp(); + $this->serverVariables = $_SERVER; + } + + protected function tearDown(): void + { + $_SERVER = $this->serverVariables; + parent::tearDown(); + } + + public function testThatWhenThereIsNoConfigurationTheHostUriWillBeDetectedFromGlobals(): void + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTPS'] = true; + $_SERVER['SERVER_PORT'] = 443; + + $helper = (new ServerUrlFactory())(new ServiceManager()); + self::assertEquals('https://example.com', $helper->__invoke()); + } + + public function testThatWhenThereIsNoConfigurationDetectedPathQueryAndFragmentWillBeOmitted(): void + { + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTPS'] = true; + $_SERVER['SERVER_PORT'] = 443; + $_SERVER['REQUEST_URI'] = '/some/#thing?foo=bar'; + + $helper = (new ServerUrlFactory())(new ServiceManager()); + self::assertEquals('https://example.com', $helper->__invoke()); + } + + public function testThatConfiguredHostIsPreferred(): void + { + $container = new ServiceManager(); + $container->setService('config', [ + 'view_helper_config' => [ + 'server_url' => 'https://other.example.com', + ], + ]); + + $_SERVER['HTTP_HOST'] = 'example.com'; + $_SERVER['HTTPS'] = true; + $_SERVER['SERVER_PORT'] = 443; + + $helper = (new ServerUrlFactory())($container); + self::assertEquals('https://other.example.com', $helper->__invoke()); + } + + public function testUndetectableEnvironmentAndZeroConfigurationYieldsException(): void + { + $_SERVER = []; + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('The current host or scheme cannot be detected from the environment'); + + (new ServerUrlFactory())(new ServiceManager()); + } + + public function testAnEmptyEnvironmentIsAcceptableWhenConfigurationIsFound(): void + { + $_SERVER = []; + $container = new ServiceManager(); + $container->setService('config', [ + 'view_helper_config' => [ + 'server_url' => 'https://other.example.com', + ], + ]); + $helper = (new ServerUrlFactory())($container); + self::assertEquals('https://other.example.com', $helper->__invoke()); + } +}