Working with an MVC framework and the controller renders the page in the destructor. I am downloading a file through php, so at the end of the action the script should end.
How to end the script without calling the destructor?
Is there a better solution?
exit and die call the destructor.
See this answer.
Try creating a destructor in the class that downloads the file that checks if a file was indeed sent to the browser, and if so, call exit() in that destructor.
As David said: you'll need to call exit() inside a destructor to stop it.
If however you just want to halt the visual output caused by these destructors but not any other side-effects (file closing, database connection closing) they might do, then it's best to just kill the further output but leave the destructors alone. Which in my opinion would be the route to take since destructors tend to be there for one important reason: cleaning up.
you can do that by manipulating buffered output:
<?php
class Tester{
public function devNull($string){
return "";
}
public function runAndBreak(){
print "run" . PHP_EOL;
// if any output buffer is working, flush it and close it
if(ob_get_level()) ob_end_flush();
// redirect new buffer to our devNull
ob_start(array($this,'devNull'));
exit();
print "still running";
}
function __destruct(){
print "destructor" . PHP_EOL;
}
}
$t = new Tester();
$t->runAndBreak();
this will only print run and not what's in the destructor. It's done by giving your own callback routine to ob_start that handles and then returns the output. In our case here we simply discard it by returning an empty string.
This will certainly stop your script but be careful, if you run such a server where one process serves several PHP requests those will stop as well:
$pid = getmypid();
exec("kill $pid");
exec("TSKILL $pid");
Related
I'm working on speeding up the response time of some php code that generates html. One of the issues with the code, is that when determines a piece of information does not need to be displayed, it makes a sql call to delete the item from the database. This isn't visible to the user, and the won't be visible to the server until the next time the page is loaded, so that sql query does not need to be run as soon as the system knows that it should be run.
What I would like to do is return the response to the user, with the generated html, and then make the sql queries. I was trying this flush and ob_flush, but the page response is still not loaded until I make a call to die.
Is there anyway in PHP to run code after a call to die() so that the user gets their data and then I can run my database clean up code and the client is no long waiting on me to close the connection?
You can register shutdown functions using register_shutdown_function:
register_shutdown_function(function () {
// cleanup stuff
});
Or in older versions of PHP:
function myFunc() {
// cleanup stuff
}
register_shutdown_function("myFunc");
Thanks to #robbrit and #Luis Siquot. I was looking at register_shutdown_function and because of Luis' comment, I was reading the comments on that page, and I came across "When using php-fpm, fastcgi_finish_request() should be used instead of register_shutdown_function() and exit()"
which lead me to fastcgi_finish_request which says:
"This function flushes all response data to the client and finishes the request. This allows for time consuming tasks to be performed without leaving the connection to the client open."
So it looks like fastcgi_finish_request() is what I'm looking for, not register_shutdown_function()
Edit: It seems that fastcgi_finish_request() requires another library, so instead use:
ob_end_clean();
header("Connection: close");
ignore_user_abort(true); // just to be safe
ob_start();
echo "The client will see this!";
$size = ob_get_length();
header("Content-Length: $size");
//Both of these flush methods must be called, otherwise strange things happen.
ob_end_flush();
flush();
echo "The client will never see this";
Is there a way to immediately stop PHP code execution?
I am aware of exit but it clearly states:
Terminates execution of the script. Shutdown functions and object destructors will always be executed even if exit is called.
So what I want to achieve is to stop the PHP code execution exactly when I call exit or whatever.
Any help?
Edit: After Jenson's answer
Trial 1:
function newExit() {
__halt_compiler();
}
echo "start";
newExit();
echo "you should not see this";
Shows Fatal error: __HALT_COMPILER() can only be used from the outermost scope in which was pretty expected.
Trial 2:
function newExit() {
include 'e.php';
}
echo "start";
newExit();
echo "you should not see this";
e.php just contains __halt_compiler();
This shows startyou should not see this
Edit: Why I want to do this?
I am working on an application that includes a proprietary library (required through virtual host config file to which I don't have access) that comes as encrypted code. Is a sort of monitoring library for security purpose. One of it's behaviours is that it registers some shutdown functions that log the instance status (it saves stats to a database)
What I want to do is to disable this logging for some specific conditions based on (remote IP)
Please see the following information from user Pekka 웃
According to the manual, destructors are executed even if the script gets terminated using die() or exit():
The destructor will be called even if script execution is stopped using exit(). Calling exit() in a destructor will prevent the remaining shutdown routines from executing.
According to this PHP: destructor vs register_shutdown_function, the destructor does not get executed when PHP's execution time limit is reached (Confirmed on Apache 2, PHP 5.2 on Windows 7).
The destructor also does not get executed when the script terminates because the memory limit was reached. (Just tested)
The destructor does get executed on fatal errors (Just tested) Update: The OP can't confirm this - there seem to be fatal errors where things are different
It does not get executed on parse errors (because the whole script won't be interpreted)
The destructor will certainly not be executed if the server process crashes or some other exception out of PHP's control occurs.
Referenced in this question
Are there any instances when the destructor in PHP is NOT called?
whats wrong with return ?
echo "you will see this";
return;
echo "you will not see this";
You can use __halt_compiler function which will Halt the compiler execution
http://www.php.net/manual/en/function.halt-compiler.php
You could try to kill the PHP process:
exec('kill -9 ' . getmypid());
Apart from the obvious die() and exit(), this also works:
<?php
echo "start";
__halt_compiler();
echo "you should not see this";
?>
I'm not sure you understand what "exit" states
Terminates execution of the script. Shutdown functions and object destructors will always be executed even if exit is called.
It's normal to do that, it must clear it's memmory of all the variables and functions you called before. Not doing this would mean your memmory would remain stuck and ocuppied in your RAM, and if this would happen several times you would need to reboot and flush your RAM in order to have any left.
or try
trigger_error('Die', E_ERROR);
Basically, I have a script that is included at the top of a page that does a bunch of things, the most important being an ob_start(). Then in the body of the page I have a variety of tags that will be replaced, such as {hello_word}. Then at the very end, I include another script that ends the output buffer, and makes the tag replacements with other code, then prints.
Is there any possible way to do this without having to include my second file at the end? Is there some simple way I can automatically execute a function or include a file at the very end?
You can register a function to be executed at the very end of script using register_shutdown_function
Any objects that you have remaining will be destroyed at the end of the script, and their destructors will be called (manual). You can put code that you want executed at the end in the destructor.
For example:
Class Waitforme {
function __destruct() {
echo "I'm here!";
}
}
$hello = new Waitforme();
This will do nothing until $hello is destroyed, at which time we'll see "I'm here!"
You can use the auto_append setting in php.ini, but you'll sacrifice portability. If you don't plan on distributing your application, this is a good option.
I have the following code:
ignore_user_abort(true);
while(!connection_aborted()) {
// do stuff
}
and according to the PHP documentation, this should run until the connection is closed, but for some reason, it doesn't, instead it keeps running until the script times out. I've looked around online and some recommended adding
echo chr(0);
flush();
into the loop, but that doesn't seem to do anything either. Even worse, if I just leave it as
while(true) {
// do stuff
}
PHP still continues to run the script after the client disconnects. Does anyone know how to get this working? Is there a php.ini setting that I'm missing somewhere?
If it matters, I'm running PHP 5.3.5. Thanks in advance!
I'm a bit late to this party, but I just had this problem and got to the bottom of it. There are multiple things going on here -- a few of them mentioned here:
PHP doesn't detect connection abort at all
The gist of it: In order for connection_aborted() to work, PHP needs to attempt to send data to the client.
Output Buffers
As noted, PHP will not detect the connection is dead until it tries to actually send data to the client. This is not as simple as doing an echo, because echo sends the data to any output buffers that may exist, and PHP will not attempt a real send until those buffers are full enough. I will not go into the details of output buffering, but it's worth mentioning that there can be multiple nested buffers.
At any rate, if you'd like to test connection_abort(), you must first end all buffers:
while (ob_get_level()){ ob_end_clean(); }
Now, anytime you want to test if the connection is aborted, you must attempt to send data to the client:
echo "Something.";
flush();
// returns expected value...
// ... but only if ignore_user_abort is false!
connection_aborted();
Ignore User Abort
This is a very important setting that determines what PHP will do when the above flush() is called, and the user has aborted the connection (eg: hit the STOP button in their browser).
If true, the script will run along merrily. flush() will do essentially nothing.
If false, as is the default setting, execution will immediately stop in the following manner:
If PHP is not already shutting down, it will begin its shutdown
process.
If PHP is already shutting down, it will exit whatever shutdown
function it is in and move on to the next.
Destructors
If you'd like to do stuff when the user aborts the connection, you need to do three things:
Detect the user aborted the connection. This means you have to attempt to flush to the user periodically, as described further above. Clear all output buffers, echo, flush.
a. If ignore_connection_aborted is true, you need to manually test connection_aborted() after each flush.
b. If ignore_connection_aborted is false, a call to flush will cause the shutdown process to begin. You must then be especially careful not to cause flush from within your shutdown functions, or else PHP will immediate cease execution of that function and move on to the next shutdown function.
Putting it all together
Putting this all together, lets make an example that detects the user hitting "STOP" and does stuff.
class DestructTester {
private $fileHandle;
public function __construct($fileHandle){
// fileHandle that we log to
$this->fileHandle = $fileHandle;
// call $this->onShutdown() when PHP is shutting down.
register_shutdown_function(array($this, "onShutdown"));
}
public function onShutdown() {
$isAborted = connection_aborted();
fwrite($this->fileHandle, "PHP is shutting down. isAborted: $isAborted\n");
// NOTE
// If connection_aborted() AND ignore_user_abort = false, PHP will immediately terminate
// this function when it encounters flush. This means your shutdown functions can end
// prematurely if: connection is aborted, ignore_user_abort=false, and you try to flush().
echo "Test.";
flush();
fwrite($this->fileHandle, "This was written after a flush.\n");
}
public function __destruct() {
$isAborted = connection_aborted();
fwrite($this->fileHandle, "DestructTester is getting destructed. isAborted: $isAborted\n");
}
}
// Create a DestructTester
// It'll log to our file on PHP shutdown and __destruct().
$fileHandle = fopen("/path/to/destruct-tester-log.txt", "a+");
fwrite($fileHandle, "---BEGINNING TEST---\n");
$dt = new DestructTester($fileHandle);
// Set this value to see how the logs end up changing
// ignore_user_abort(true);
// Remove any buffers so that PHP attempts to send data on flush();
while (ob_get_level()){
ob_get_contents();
ob_end_clean();
}
// Let's loop for 10 seconds
// If ignore_user_abort=true:
// This will continue to run regardless.
// If ignore_user_abort=false:
// This will immediate terminate when the user disconnects and PHP tries to flush();
// PHP will begin its shutdown process.
// In either case, connection_aborted() should subsequently return "true" after the user
// has disconnected (hit STOP button in browser), AND after PHP has attempted to flush().
$numSleeps = 0;
while ($numSleeps++ < 10) {
$connAbortedStr = connection_aborted() ? "YES" : "NO";
$str = "Slept $numSleeps times. Connection aborted: $connAbortedStr";
echo "$str<br>";
// If ignore_user_abort = false, script will terminate right here.
// Shutdown functions will being.
// Otherwise, script will continue for all 10 loops and then shutdown.
flush();
$connAbortedStr = connection_aborted() ? "YES" : "NO";
fwrite($fileHandle, "flush()'d $numSleeps times. Connection aborted is now: $connAbortedStr\n");
sleep(1);
}
echo "DONE SLEEPING!<br>";
die;
The comments explain everything. You can fiddle with ignore_user_abort and look at the logs to see how this changes things.
I hope this helps anyone having trouble with connection_abort, register_shutdown_function, and __destruct.
Try using ob_flush(); just before flush(); and some browsers just won't update the page before some data is added.
Try doing something like
<? php
// preceding scripts
ignore_user_abort(true);
$i = 0;
while(!connection_aborted())
{ $i++;
echo $i;
echo str_pad('',4096); // yes i know this will increase the overhead but that can be reduced afterwords
ob_flush();
flush();
usleep(30000); // see what happens when u run this on my WAMP this runs perfectly
}
// Ending scripts
?>
Google Chrome has issues with this code, actually; it doesn't support streaming very nicely.
Try:
ignore_user_abort(true);
echo "Testing connection handling";
while (1) {
if (connection_status() != CONNECTION_NORMAL)
break;
sleep(1);
echo "test";
flush();
}
Buffering seems to cause issues depending on your server settings.
I tried disabling the buffer with ob_end_clean but that wasn't enough, I had to send some data to cause the buffer to fully flush out. Here is the final code that ended up working for me.
set_time_limit(0); // run the delay as long as the user stays connected
ignore_user_abort(false);
ob_end_clean();
echo "\n";
while ($delay-- > 0 && !connection_aborted())
{
echo str_repeat("\r", 1000) . "<!--sleep-->\n";
flush();
sleep(1);
}
ob_start();
I am using flash to call a PHP page that needs to do a bit of processing. Is it possible to let PHP continue processing but show a response anyway so flash doesn't stall waiting?
My answer from here:
You can send Connection:Close headers,
which finishes the page for your user,
but enables you to execute things
"after page loads".
There is a simple way to ignore user
abort (see php manual too):
ignore_user_abort(true);
Use output control aka output buffering to do this. http://www.php.net/manual/en/function.ob-flush.php
You could try using flush()
As an example, try these two different pieces of code:
// without flush()
foreach ( range(1, 5) as $num ) {
echo "Beep $num<br>";
sleep(1);
}
// with flush()
foreach ( range(1, 5) as $num ) {
echo "Beep $num<br>";
flush();
sleep(1);
}
You can close the connection within a registered function within register_shutdown_function if you do not need to wait for the processing to be over to output content (i.e., if you do not need to output anything related to the outcome of the processing you wish to do).
See : http://www.php.net/manual/en/features.connection-handling.php#93441
The reason to put it in a register_shutdown_function is that even if the client aborts the connection, the processing will keep on going to the very end.