Skip to content

Commit 6dc464a

Browse files
committed
test defer
1 parent a716c17 commit 6dc464a

File tree

5 files changed

+475
-60
lines changed

5 files changed

+475
-60
lines changed

index.php

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
<?php
2+
3+
use Rcalicdan\FiberAsync\Api\File;
4+
use Rcalicdan\FiberAsync\Defer\Defer;
5+
6+
require 'vendor/autoload.php';
7+
8+
if ($_SERVER["REQUEST_METHOD"] == "POST") {
9+
$requestTime = microtime(true);
10+
error_log("=== REQUEST STARTED ===");
11+
error_log("Registering terminate callback at " . date('Y-m-d H:i:s.u'));
12+
13+
Defer::terminate(function () use ($requestTime) {
14+
$terminateStart = microtime(true);
15+
$delayFromRequest = $terminateStart - $requestTime;
16+
17+
error_log("=== TERMINATE CALLBACK STARTED ===");
18+
error_log("Terminate callback started at " . date('Y-m-d H:i:s.u'));
19+
error_log("Delay from request: " . number_format($delayFromRequest, 3) . " seconds");
20+
21+
sleep(5);
22+
23+
try {
24+
File::write('txt.txt', "Hello World from terminate callback - " . date('Y-m-d H:i:s'))->await();
25+
$totalTime = microtime(true) - $terminateStart;
26+
error_log("File written successfully after " . number_format($totalTime, 3) . " seconds");
27+
} catch (Exception $e) {
28+
error_log("Error in terminate callback: " . $e->getMessage());
29+
}
30+
31+
$totalExecutionTime = microtime(true) - $requestTime;
32+
error_log("Terminate callback completed at " . date('Y-m-d H:i:s.u'));
33+
error_log("Total time from request: " . number_format($totalExecutionTime, 3) . " seconds");
34+
error_log("=== TERMINATE CALLBACK ENDED ===");
35+
});
36+
37+
Defer::global(function () use ($requestTime) {
38+
$globalStart = microtime(true);
39+
$delayFromRequest = $globalStart - $requestTime;
40+
error_log("Global defer executed at " . date('Y-m-d H:i:s.u'));
41+
error_log("Global defer delay from request: " . number_format($delayFromRequest, 3) . " seconds");
42+
});
43+
44+
$stats = Defer::getStats();
45+
error_log("Defer stats: " . json_encode($stats, JSON_PRETTY_PRINT));
46+
47+
$responseTime = microtime(true);
48+
error_log("Response being sent at " . date('Y-m-d H:i:s.u'));
49+
error_log("Response time: " . number_format(($responseTime - $requestTime) * 1000, 2) . " ms");
50+
error_log("=== RESPONSE SENT ===");
51+
52+
echo "<div style='padding: 20px; font-family: Arial, sans-serif;'>";
53+
echo "<h2>✅ Form Submitted Successfully!</h2>";
54+
echo "<p><strong>Response sent immediately</strong> - The background task is now running.</p>";
55+
echo "<p>Check <code>txt.txt</code> file in 5+ seconds and your error log for detailed timing.</p>";
56+
57+
echo "<h3>Environment Information:</h3>";
58+
echo "<div style='background: #f5f5f5; padding: 10px; border-radius: 5px; font-family: monospace;'>";
59+
echo "<strong>SAPI:</strong> " . $stats['environment']['sapi'] . "<br>";
60+
echo "<strong>FastCGI Environment:</strong> " . ($stats['environment']['fastcgi'] ? 'Yes' : 'No') . "<br>";
61+
echo "<strong>fastcgi_finish_request:</strong> " . ($stats['environment']['fastcgi_finish_request'] ? 'Available' : 'Not Available') . "<br>";
62+
echo "<strong>Output Buffering Level:</strong> " . $stats['environment']['output_buffering'] . "<br>";
63+
echo "<strong>Global Defers:</strong> " . $stats['global_defers'] . "<br>";
64+
echo "<strong>Terminate Callbacks:</strong> " . $stats['terminate_callbacks'] . "<br>";
65+
echo "<strong>Memory Usage:</strong> " . number_format($stats['memory_usage'] / 1024 / 1024, 2) . " MB<br>";
66+
echo "</div>";
67+
68+
echo "<h3>What Should Happen:</h3>";
69+
echo "<ul>";
70+
echo "<li>✅ You see this response immediately (not after 5 seconds)</li>";
71+
echo "<li>🔄 Background task runs after response is sent</li>";
72+
echo "<li>📝 File <code>txt.txt</code> appears in ~5+ seconds</li>";
73+
echo "<li>📋 Detailed timing in error log</li>";
74+
echo "</ul>";
75+
echo "</div>";
76+
77+
} else {
78+
echo "<div style='padding: 20px; font-family: Arial, sans-serif;'>";
79+
echo "<h1>Defer Terminate Test</h1>";
80+
echo "<p>This test demonstrates Laravel-style defer functionality that executes <strong>after</strong> the HTTP response is sent.</p>";
81+
82+
// Show current environment info
83+
$stats = Defer::getStats();
84+
echo "<h3>Current Environment:</h3>";
85+
echo "<div style='background: #f0f8ff; padding: 10px; border-radius: 5px; font-family: monospace;'>";
86+
echo "<strong>PHP SAPI:</strong> " . PHP_SAPI . "<br>";
87+
echo "<strong>FastCGI Available:</strong> " . (function_exists('fastcgi_finish_request') ? 'Yes' : 'No') . "<br>";
88+
echo "<strong>OS:</strong> " . PHP_OS_FAMILY . "<br>";
89+
echo "<strong>PHP Version:</strong> " . PHP_VERSION . "<br>";
90+
echo "</div>";
91+
92+
echo "<p><strong>Click submit to test:</strong></p>";
93+
echo "</div>";
94+
}
95+
96+
?>
97+
98+
<!DOCTYPE html>
99+
<html lang="en">
100+
<head>
101+
<meta charset="UTF-8">
102+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
103+
<title>Defer Terminate Test</title>
104+
<style>
105+
body {
106+
font-family: Arial, sans-serif;
107+
max-width: 800px;
108+
margin: 0 auto;
109+
padding: 20px;
110+
line-height: 1.6;
111+
}
112+
.form-container {
113+
background: #f9f9f9;
114+
padding: 20px;
115+
border-radius: 8px;
116+
margin: 20px 0;
117+
}
118+
button {
119+
background: #007cba;
120+
color: white;
121+
padding: 12px 24px;
122+
border: none;
123+
border-radius: 4px;
124+
font-size: 16px;
125+
cursor: pointer;
126+
}
127+
button:hover {
128+
background: #005a87;
129+
}
130+
.timing-info {
131+
background: #e8f5e8;
132+
padding: 15px;
133+
border-radius: 5px;
134+
margin: 20px 0;
135+
}
136+
</style>
137+
<script>
138+
let startTime;
139+
140+
function startTiming() {
141+
startTime = Date.now();
142+
document.getElementById('status').innerHTML =
143+
'<div style="color: #666;">⏳ Request sent, waiting for response...</div>';
144+
}
145+
146+
window.onload = function() {
147+
<?php if ($_SERVER["REQUEST_METHOD"] == "POST"): ?>
148+
const endTime = Date.now();
149+
const responseTime = endTime - (startTime || endTime);
150+
151+
document.getElementById('timing-result').innerHTML =
152+
'<div class="timing-info">' +
153+
'<h4>✅ Response Timing Verified!</h4>' +
154+
'<p>This response was received <strong>immediately</strong>, not after the 5-second sleep.</p>' +
155+
'<p>The background task is running separately and will complete in ~5+ seconds.</p>' +
156+
'</div>';
157+
158+
// Show a countdown for when to check the file
159+
let countdown = 6;
160+
const countdownElement = document.createElement('div');
161+
countdownElement.style.cssText = 'background: #fff3cd; padding: 10px; border-radius: 5px; margin: 10px 0;';
162+
document.body.appendChild(countdownElement);
163+
164+
const timer = setInterval(() => {
165+
countdown--;
166+
if (countdown > 0) {
167+
countdownElement.innerHTML = `<strong>⏰ Check txt.txt file in ${countdown} seconds...</strong>`;
168+
} else {
169+
countdownElement.innerHTML = '<strong>🔍 Check txt.txt file now! It should have been created.</strong>';
170+
countdownElement.style.background = '#d4edda';
171+
clearInterval(timer);
172+
}
173+
}, 1000);
174+
<?php endif; ?>
175+
};
176+
</script>
177+
</head>
178+
<body>
179+
<div class="form-container">
180+
<form method="post" onsubmit="startTiming()">
181+
<button type="submit">🚀 Test Defer Terminate</button>
182+
</form>
183+
<div id="status"></div>
184+
</div>
185+
186+
<div id="timing-result"></div>
187+
188+
<div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; font-size: 14px;">
189+
<h4>How to Verify It's Working:</h4>
190+
<ol>
191+
<li><strong>Immediate Response:</strong> You should see the response instantly (not after 5 seconds)</li>
192+
<li><strong>Background Execution:</strong> The <code>txt.txt</code> file will appear in ~5-8 seconds</li>
193+
<li><strong>Check Error Log:</strong> Detailed timing information will be logged</li>
194+
</ol>
195+
196+
<p><strong>💡 Tip:</strong> Open your browser's developer tools (F12) and watch the Network tab to see the actual response time.</p>
197+
</div>
198+
</body>
199+
</html>

monitor.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
$filename = 'txt.txt';
4+
$startTime = time();
5+
6+
echo "Monitoring $filename for changes...\n";
7+
echo "Started at: " . date('Y-m-d H:i:s') . "\n\n";
8+
9+
$lastModified = file_exists($filename) ? filemtime($filename) : 0;
10+
11+
while (true) {
12+
if (file_exists($filename)) {
13+
$currentModified = filemtime($filename);
14+
if ($currentModified > $lastModified) {
15+
echo "✅ File updated at: " . date('Y-m-d H:i:s', $currentModified) . "\n";
16+
echo "Content: " . file_get_contents($filename) . "\n";
17+
echo "Time since start: " . ($currentModified - $startTime) . " seconds\n\n";
18+
$lastModified = $currentModified;
19+
}
20+
}
21+
22+
sleep(1);
23+
24+
if (time() - $startTime > 30) {
25+
echo "Monitoring stopped after 30 seconds.\n";
26+
break;
27+
}
28+
}
29+
?>

src/Defer/Defer.php

Lines changed: 31 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace Rcalicdan\FiberAsync\Defer;
44

5-
use Rcalicdan\FiberAsync\Defer\Handlers\FunctionScopeHandler;
65
use Rcalicdan\FiberAsync\Defer\Handlers\ProcessDeferHandler;
6+
use Rcalicdan\FiberAsync\Defer\Utilities\DeferInstance;
77

88
/**
99
* Static defer utility with reliable function scope management
@@ -17,6 +17,8 @@ class Defer
1717

1818
/**
1919
* Create a new function-scoped defer instance
20+
*
21+
* @return DeferInstance Function-scoped defer instance
2022
*/
2123
public static function scope(): DeferInstance
2224
{
@@ -26,7 +28,7 @@ public static function scope(): DeferInstance
2628
/**
2729
* Global-scoped defer - executes at script shutdown
2830
*
29-
* @param callable $callback The callback to defer
31+
* @param callable $callback The callback to defer
3032
*/
3133
public static function global(callable $callback): void
3234
{
@@ -38,66 +40,46 @@ public static function global(callable $callback): void
3840
}
3941

4042
/**
41-
* Reset state (useful for testing)
42-
*/
43-
public static function reset(): void
44-
{
45-
self::$globalHandler = null;
46-
}
47-
}
48-
49-
/**
50-
* Function-scoped defer instance with method chaining
51-
*/
52-
class DeferInstance
53-
{
54-
/**
55-
* @var FunctionScopeHandler Function-scoped defer handler
56-
*/
57-
private FunctionScopeHandler $functionHandler;
58-
59-
/**
60-
* Initialize with a new function-scoped defer handler
61-
*/
62-
public function __construct()
63-
{
64-
$this->functionHandler = ProcessDeferHandler::createFunctionDefer();
65-
}
66-
67-
/**
68-
* Add a function-scoped defer
43+
* Terminate-scoped defer - executes after response is sent (like Laravel's defer)
44+
*
45+
* This is similar to Laravel's terminable middleware and defer() helper.
46+
* Callbacks are executed after the HTTP response has been sent to the client
47+
* or after the main CLI script execution completes.
6948
*
70-
* @param callable $callback The callback to defer
71-
* @return self For method chaining
49+
* @param callable $callback The callback to execute after response
7250
*/
73-
public function task(callable $callback): self
51+
public static function terminate(callable $callback): void
7452
{
75-
$this->functionHandler->defer($callback);
53+
if (self::$globalHandler === null) {
54+
self::$globalHandler = new ProcessDeferHandler;
55+
}
7656

77-
return $this;
57+
self::$globalHandler->terminate($callback);
7858
}
7959

8060
/**
81-
* Get the number of pending function-scoped defers
61+
* Reset state (useful for testing)
8262
*/
83-
public function count(): int
63+
public static function reset(): void
8464
{
85-
return $this->functionHandler->count();
65+
self::$globalHandler = null;
8666
}
8767

8868
/**
89-
* Get the underlying function defer handler (for advanced usage)
69+
* Get defer statistics
70+
*
71+
* @return array Statistics about defer usage and environment
9072
*/
91-
public function getHandler(): FunctionScopeHandler
73+
public static function getStats(): array
9274
{
93-
return $this->functionHandler;
94-
}
75+
if (self::$globalHandler === null) {
76+
return [
77+
'global_defers' => 0,
78+
'terminate_callbacks' => 0,
79+
'memory_usage' => memory_get_usage(true),
80+
];
81+
}
9582

96-
/**
97-
* Manually execute all function-scoped defers (useful for testing)
98-
*/
99-
public function executeAll(): void
100-
{
101-
$this->functionHandler->executeAll();
83+
return self::$globalHandler->getStats();
10284
}
103-
}
85+
}

0 commit comments

Comments
 (0)