Skip to content
Merged
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## Unreleased

PHP 8.5 deprecation fixes for the 0.3 line. No API change.

- `NativeSsrTransport` and `NativeSsrPurgeTransport` no longer call
`curl_close()` (deprecated in PHP 8.5, no effect since 8.0). It was logged
on every cURL render and purge.
- The streams fallback reads the response headers from
`stream_get_meta_data()`, not `$http_response_header` (deprecated in
PHP 8.5).
- Tests cover both transports over cURL and streams and fail on
deprecations.

## 0.3.0 — 2026-09-12

First public Packagist release. Composer name is `mapsight/embed` (MIT).
Expand Down
5 changes: 4 additions & 1 deletion phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
colors="true"
cacheDirectory=".phpunit.cache">
cacheDirectory=".phpunit.cache"
failOnDeprecation="true"
displayDetailsOnTestsThatTriggerDeprecations="true">
<testsuites>
<testsuite name="OpenMapsight Embed">
<directory>tests</directory>
<exclude>tests/fixtures</exclude>
</testsuite>
</testsuites>
</phpunit>
21 changes: 17 additions & 4 deletions src/Embed/NativeSsrPurgeTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ private function postWithCurl(
$body = curl_exec($handle);
$status = (int) curl_getinfo($handle, CURLINFO_HTTP_CODE);
$error = curl_error($handle);
curl_close($handle);
unset($handle);

if ($body === false) {
throw new \RuntimeException($error !== '' ? $error : 'SSR purge request failed');
Expand Down Expand Up @@ -76,14 +76,27 @@ private function postWithStreams(string $url, string $json, float $timeoutSecond
],
]);

$body = @file_get_contents($url, false, $context);
// Response headers come from the stream metadata: PHP 8.5 deprecates
// $http_response_header, and http_get_last_response_headers() needs 8.4.
$stream = @fopen($url, 'rb', false, $context);
if ($stream === false) {
throw new \RuntimeException('SSR purge request failed');
}

try {
$headers = stream_get_meta_data($stream)['wrapper_data'] ?? null;
$body = stream_get_contents($stream);
} finally {
fclose($stream);
}

if ($body === false) {
throw new \RuntimeException('SSR purge request failed');
}

$status = 0;
if (isset($http_response_header[0])
&& preg_match('/\s(\d{3})\s/', $http_response_header[0], $matches) === 1
if (is_array($headers) && is_string($headers[0] ?? null)
&& preg_match('/\s(\d{3})\s/', $headers[0], $matches) === 1
) {
$status = (int) $matches[1];
}
Expand Down
21 changes: 17 additions & 4 deletions src/Embed/NativeSsrTransport.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ private function postWithCurl(
$body = curl_exec($handle);
$status = (int) curl_getinfo($handle, CURLINFO_HTTP_CODE);
$error = curl_error($handle);
curl_close($handle);
unset($handle);

if ($body === false) {
throw new \RuntimeException($error !== '' ? $error : 'SSR request failed');
Expand Down Expand Up @@ -98,14 +98,27 @@ private function postWithStreams(string $url, string $json, float $timeoutSecond
],
]);

$body = @file_get_contents($url, false, $context);
// Response headers come from the stream metadata: PHP 8.5 deprecates
// $http_response_header, and http_get_last_response_headers() needs 8.4.
$stream = @fopen($url, 'rb', false, $context);
if ($stream === false) {
throw new \RuntimeException('SSR request failed');
}

try {
$headers = stream_get_meta_data($stream)['wrapper_data'] ?? null;
$body = stream_get_contents($stream);
} finally {
fclose($stream);
}

if ($body === false) {
throw new \RuntimeException('SSR request failed');
}

$status = 0;
if (isset($http_response_header[0])
&& preg_match('/\s(\d{3})\s/', $http_response_header[0], $matches) === 1
if (is_array($headers) && is_string($headers[0] ?? null)
&& preg_match('/\s(\d{3})\s/', $headers[0], $matches) === 1
) {
$status = (int) $matches[1];
}
Expand Down
109 changes: 109 additions & 0 deletions tests/NativeTransportsTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

declare(strict_types=1);

namespace OpenMapsight\Tests;

use OpenMapsight\Embed\NativeSsrPurgeTransport;
use OpenMapsight\Embed\NativeSsrTransport;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class NativeTransportsTest extends TestCase
{
private static int $port = 0;

/** @var resource|false */
private static $process = false;

public static function setUpBeforeClass(): void
{
self::$port = 18700 + random_int(0, 200);
self::$process = proc_open(
[PHP_BINARY, '-S', '127.0.0.1:' . self::$port, __DIR__ . '/fixtures/http-server.php'],
[
0 => ['file', '/dev/null', 'r'],
1 => ['file', '/dev/null', 'w'],
2 => ['file', '/dev/null', 'w'],
],
$pipes,
);
if (self::$process === false) {
self::markTestSkipped('could not start php -S');
}

for ($i = 0; $i < 50; $i++) {
$fp = @fsockopen('127.0.0.1', self::$port, $errno, $errstr, 0.1);
if (is_resource($fp)) {
fclose($fp);

return;
}
usleep(50000);
}

self::tearDownAfterClass();
self::markTestSkipped('php -S did not become ready');
}

public static function tearDownAfterClass(): void
{
if (self::$process !== false) {
proc_terminate(self::$process);
proc_close(self::$process);
self::$process = false;
}
}

/** @return iterable<string, array{string}> */
public static function paths(): iterable
{
yield 'curl' => ['postWithCurl'];
yield 'streams' => ['postWithStreams'];
}

#[DataProvider('paths')]
public function test_render_reads_body_on_2xx(string $method): void
{
$body = $this->render($method, '/render', ['X-Request-Id' => 'req-1']);

$data = json_decode($body, true, 512, JSON_THROW_ON_ERROR);
$this->assertSame(1, $data['v'] ?? null);
$this->assertSame('req-1', $data['state']['requestId'] ?? null);
}

#[DataProvider('paths')]
public function test_render_throws_on_5xx(string $method): void
{
$this->expectExceptionMessage('SSR HTTP status 503');
$this->render($method, '/render?status=503');
}

#[DataProvider('paths')]
public function test_purge_reads_status(string $method): void
{
$this->assertSame('["doc::1"]', $this->purge($method, '/purge'));
$this->assertSame('[]', $this->purge($method, '/purge?status=204'));
}

/** @param array<string, string> $headers */
private function render(string $method, string $path, array $headers = []): string
{
$transport = new NativeSsrTransport();
$url = 'http://127.0.0.1:' . self::$port . $path;
$args = $method === 'postWithCurl'
? [$url, '{"v":1}', 2.0, 0.5, $headers]
: [$url, '{"v":1}', 2.0, $headers];

return (new \ReflectionMethod($transport, $method))->invoke($transport, ...$args);
}

private function purge(string $method, string $path): string
{
$transport = new NativeSsrPurgeTransport();
$url = 'http://127.0.0.1:' . self::$port . $path;
$args = $method === 'postWithCurl' ? [$url, '{}', 2.0, 0.5] : [$url, '{}', 2.0];

return (new \ReflectionMethod($transport, $method))->invoke($transport, ...$args);
}
}
41 changes: 41 additions & 0 deletions tests/fixtures/http-server.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

$path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH);
$query = [];
parse_str((string) (parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_QUERY) ?? ''), $query);

if ($path === '/render') {
if (($query['status'] ?? '') === '503') {
http_response_code(503);
header('Content-Type: text/plain');
echo 'down';
exit;
}

http_response_code(200);
header('Content-Type: application/json');
echo json_encode([
'v' => 1,
'html' => '<div id="mapsight-embed-1" class="mapsight-embed"></div>',
'state' => ['requestId' => $_SERVER['HTTP_X_REQUEST_ID'] ?? null],
], JSON_THROW_ON_ERROR);
exit;
}

if ($path === '/purge') {
if (($query['status'] ?? '') === '204') {
http_response_code(204);
exit;
}

http_response_code(200);
header('Content-Type: application/json');
echo '["doc::1"]';
exit;
}

http_response_code(404);
header('Content-Type: text/plain');
echo 'not found';
Loading