Page Menu
Home
DevCentral
Search
Configure Global Search
Log In
Files
F25568567
D3881.id10613.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
10 KB
Referenced Files
None
Subscribers
None
D3881.id10613.diff
View Options
diff --git a/omnitools/src/HTTP/Requests/Request.php b/omnitools/src/HTTP/Requests/Request.php
--- a/omnitools/src/HTTP/Requests/Request.php
+++ b/omnitools/src/HTTP/Requests/Request.php
@@ -8,5 +8,6 @@
use WithAcceptedLanguages;
use WithRemoteAddress;
use WithURL;
+ use WithLocalURL;
}
diff --git a/omnitools/src/HTTP/Requests/WithLocalURL.php b/omnitools/src/HTTP/Requests/WithLocalURL.php
new file mode 100644
--- /dev/null
+++ b/omnitools/src/HTTP/Requests/WithLocalURL.php
@@ -0,0 +1,164 @@
+<?php
+
+namespace Keruald\OmniTools\HTTP\Requests;
+
+use RuntimeException;
+
+trait WithLocalURL {
+
+ ///
+ /// Properties from configuration
+ ///
+
+ private string $baseUrl = "";
+
+ private ?string $siteUrl = null;
+
+ ///
+ /// Properties
+ ///
+
+ private function getSiteUrl () : string {
+ return $this->siteUrl ?? Request::getServerURL();
+ }
+
+ public function withBaseUrl (string $url) : static {
+ $this->baseUrl = $url;
+
+ return $this;
+ }
+
+ public function withSiteUrl (string $url) : static {
+ $this->siteUrl = $url;
+
+ return $this;
+ }
+
+ ///
+ /// Methods to get or build URLs
+ ///
+
+ /**
+ * Gets the URL matching the specified resource.
+ *
+ * Example:
+ * <code>
+ * $ship = new Ship();
+ * $ship->id = "S00001";
+ *
+ * $request = new Request();
+ * $url = $request->buildUrl("ship", $ship->id);
+ * echo $url; // This should print "/ship/S00001"
+ * </code>
+ *
+ * @param string ...$parts The URL parts
+ * @return string the URL matching the specified resource
+ */
+ public function buildUrl (string ...$parts) : string {
+ if (self::hasParts($parts)) {
+ $baseUrl = $this->baseUrl;
+ if (!str_ends_with($baseUrl, "/")) {
+ $baseUrl .= "/";
+ }
+
+ return $baseUrl . implode("/", $parts);
+ }
+
+ if ($this->baseUrl == "" || $this->baseUrl == $_SERVER["PHP_SELF"]) {
+ return "/";
+ }
+
+ return $this->baseUrl;
+ }
+
+ private static function hasParts (array $parts) : bool {
+ if (count($parts) == 0) {
+ return false;
+ }
+
+ if (count($parts) == 1 && $parts[0] == "") {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Gets $_SERVER["PATH_INFO"] or computes the equivalent if not defined.
+ *
+ * This function allows the entry point controllers to get the current URL
+ * consistently, for any redirection configuration.
+ *
+ * So with /foo/bar, /index.php/foo/bar, /zed/index.php/foo/bar or /zed/foo/bar
+ * `Request::getCurrentUrl()` will return /foo/bar each time.
+ *
+ * @return string the relevant URL part
+ */
+ public function getCurrentUrl () : string {
+ // CASE 1. PATH_INFO is defined.
+ // This is a straightforward case, we just return it, server configuration
+ // is responsible for properly cut the URL.
+ if (array_key_exists("PATH_INFO", $_SERVER)) {
+ return $_SERVER["PATH_INFO"];
+ }
+
+ // Useful parts of the URL
+ $siteUrl = $this->getSiteUrl();
+ $serverUrl = $this->getServerURL();
+ $currentUrl = $serverUrl . $_SERVER["REQUEST_URI"];
+
+ $server_len = strlen($serverUrl);
+ $len = $server_len + strlen($this->baseUrl); // Relevant URL part starts after the site URL
+
+ // Allow configuration to add an extraneous trailing slash from base URL
+ if (str_ends_with($this->baseUrl, "/")) {
+ $len--;
+ }
+
+ // Throw an exception if the site URL is not the beginning of the URL
+ // because in that case, we can't determine where to cut the URL.
+ if (substr($currentUrl, 0, $server_len) != $siteUrl) {
+ throw new RuntimeException(
+ "Site URL mismatch: a value starting by $serverUrl is expected, but got $siteUrl."
+ );
+ }
+
+ // CASE 2. REDIRECT_URL is defined.
+ //
+ // A popular legacy configuration is Apache + mod_rewrite
+ // to redirect content clean URLs to an entry point script.
+ //
+ // In that case, we take the part after this entry point script.
+ if (array_key_exists("REDIRECT_URL", $_SERVER)) {
+ return substr($serverUrl . $_SERVER["REDIRECT_URL"], $len);
+ }
+
+ // CASE 3. Use REQUEST_URI but remove QUERY_STRING
+ $url = substr($currentUrl, $len);
+ if (array_key_exists("QUERY_STRING", $_SERVER) && $_SERVER["QUERY_STRING"] != "") {
+ return substr($url, 0, strlen($url) - strlen($_SERVER["QUERY_STRING"]) - 1);
+ }
+
+ return $url;
+ }
+
+ /**
+ * Gets an array of url fragments to be processed by controller
+ * @see self::getCurrentUrl()
+ *
+ * This method is used by the controllers' entry points to know the URL and
+ * call relevant subcontrollers.
+ *
+ * @return string[] an array of string, one for each URL fragment
+ */
+ public function getCurrentUrlFragments () : array {
+ $source = $this->getCurrentUrl();
+
+ if ($source == $_SERVER["PHP_SELF"]) {
+ return [];
+ }
+
+ return explode("/", substr($source, 1));
+ }
+
+}
diff --git a/omnitools/tests/HTTP/Requests/RequestWithLocalUrlTest.php b/omnitools/tests/HTTP/Requests/RequestWithLocalUrlTest.php
new file mode 100644
--- /dev/null
+++ b/omnitools/tests/HTTP/Requests/RequestWithLocalUrlTest.php
@@ -0,0 +1,148 @@
+<?php
+
+namespace Keruald\OmniTools\Tests\HTTP\Requests;
+
+use Keruald\OmniTools\HTTP\Requests\Request;
+
+use PHPUnit\Framework\Attributes\DataProvider;
+use PHPUnit\Framework\TestCase;
+
+class RequestWithLocalUrlTest extends TestCase {
+
+ private Request $request;
+
+ protected function setUp() : void {
+ $this->request = new Request();
+ }
+
+ ///
+ /// Data providers
+ ///
+
+ /**
+ * Provides equivalent base URLs for testing code handles trailing slash
+ */
+ public static function provideBaseUrls () : iterable {
+ yield ["/tracker"];
+ yield ["/tracker/"];
+ }
+
+ public static function provideUrlFragments () : iterable {
+ yield [[""], "/"];
+ yield [["foo"], "/foo"];
+ yield [["foo", "bar"], "/foo/bar"];
+ }
+
+ public static function provideBaseUrlsAndUrlFragments () : iterable {
+ yield ["/tracker", [], "/tracker"]; // respects base URL exact value
+ yield ["/tracker", [""], "/tracker"]; // respects base URL exact value
+ yield ["/tracker", ["foo"], "/tracker/foo"];
+ yield ["/tracker", ["foo", "bar"], "/tracker/foo/bar"];
+
+ yield ["/tracker/", [], "/tracker/"]; // respects base URL exact value
+ yield ["/tracker/", [""], "/tracker/"]; // respects base URL exact value
+ yield ["/tracker/", ["foo"], "/tracker/foo"];
+ yield ["/tracker/", ["foo", "bar"], "/tracker/foo/bar"];
+ }
+
+ ///
+ /// Tests for buildUrl()
+ ///
+
+ #[dataProvider("provideUrlFragments")]
+ public function testBuildUrl($parts, $url) : void {
+ $this->assertEquals($url, $this->request->buildUrl(...$parts));
+ }
+
+ #[dataProvider("provideBaseUrlsAndUrlFragments")]
+ public function testBuildUrlWithBaseUrl($baseUrl, $parts, $url) : void {
+ $this->request
+ ->withSiteUrl("http://localhost")
+ ->withBaseUrl("$baseUrl");
+
+ $this->assertEquals($url, $this->request->buildUrl(...$parts));
+ }
+
+ ///
+ /// Tests for getCurrentUrl() scenarii
+ ///
+
+ public function testGetCurrentUrlWithPathInfo () : void {
+ $_SERVER["PATH_INFO"] = "/foo";
+
+ $this->assertEquals("/foo", $this->request->getCurrentUrl());
+ }
+
+ public function testGetCurrentUrlRegularRequestCase () : void {
+ $_SERVER["REQUEST_URI"] = "/foo";
+
+ $this->assertEquals("/foo", $this->request->getCurrentUrl());
+ }
+
+ public function testGetCurrentUrlRequestCaseWithQueryString () : void {
+ $_SERVER["REQUEST_URI"] = "/foo?a=b";
+ $_SERVER["QUERY_STRING"] = "a=b";
+
+ $this->assertEquals("/foo", $this->request->getCurrentUrl());
+ }
+
+ public function testGetCurrentUrlWithRedirect () : void {
+ // Let's redirect /T42 to /tasks/legacy/42
+ // Controller needs /tasks/legacy/42 to parse route
+ $_SERVER["REQUEST_URI"] = "/T42";
+ $_SERVER["REDIRECT_URL"] = "/tasks/legacy/42";
+
+ $this->assertEquals("/tasks/legacy/42", $this->request->getCurrentUrl());
+ }
+
+ #[DataProvider("provideBaseUrls")]
+ public function testGetCurrentUrlWithRedirectAndCustomEntryPoint ($baseUrl) : void {
+ // Site root is https://site.domain.tld/tracker/
+ // Let's redirect /T42 to /tracker/tasks/legacy/42
+ // Controller still needs /tasks/legacy/42 to parse route
+ $_SERVER["REQUEST_URI"] = "/T42";
+ $_SERVER["REDIRECT_URL"] = "/tracker/tasks/legacy/42";
+
+ $this->request
+ ->withSiteUrl("http://localhost")
+ ->withBaseUrl("$baseUrl");
+
+ $this->assertEquals("/tasks/legacy/42", $this->request->getCurrentUrl());
+ }
+
+ #[DataProvider("provideBaseUrls")]
+ public function testGetCurrentUrlRegularRequestCaseWithBaseUrl ($baseUrl) : void {
+ $_SERVER["REQUEST_URI"] = "/tracker/tasks/legacy/42";
+
+ $this->request
+ ->withSiteUrl("http://localhost")
+ ->withBaseUrl("$baseUrl");
+
+ $this->assertEquals("/tasks/legacy/42", $this->request->getCurrentUrl());
+ }
+
+ ///
+ /// Tests for getCurrentUrlFragments() scenarii
+ ///
+
+ #[DataProvider("provideBaseUrls")]
+ public function testGetCurrentUrlFragments ($baseUrl) : void {
+ // This case has arbitrarily been picked, as this helper function
+ // first call getCurrentUrl(). It's not needed to browse again all tbe cases.
+ $_SERVER["REQUEST_URI"] = "/tracker/tasks/legacy/42";
+
+ $this->request
+ ->withSiteUrl("http://localhost")
+ ->withBaseUrl("$baseUrl");
+
+ $this->assertEquals(["tasks", "legacy", "42"], $this->request->getCurrentUrlFragments());
+ }
+
+ public function testGetCurrentUrlFragmentsWhenSourceMatchesPhpSelf () : void {
+ $_SERVER["REQUEST_URI"] = "/index.php";
+ $_SERVER["PHP_SELF"] = "/index.php";
+
+ $this->assertEquals([], $this->request->getCurrentUrlFragments());
+ }
+
+}
diff --git a/phpunit.xml b/phpunit.xml
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -26,6 +26,7 @@
displayDetailsOnTestsThatTriggerNotices="true"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
+ backupGlobals="true"
stopOnFailure="false">
<php>
<ini name="display_errors" value="On" />
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Fri, Apr 17, 17:53 (16 h, 28 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3631653
Default Alt Text
D3881.id10613.diff (10 KB)
Attached To
Mode
D3881: Move get_current_url() and co in OmniTools Request class
Attached
Detach File
Event Timeline
Log In to Comment