63

The code below almost works, but it's not what I really meant:

ob_start();
echo 'xxx';
$contents = ob_get_contents();
ob_end_clean();
file_put_contents($file,$contents);

Is there a more natural way?

chaos
  • 122,029
  • 33
  • 303
  • 309
omg
  • 136,412
  • 142
  • 288
  • 348
  • 1
    the example code is redirecting OUTPUT to a file, not STDOUT, Bas's solution solutions only appears to work because CLI (and to a lesser extend CGI) enviroments uses theses streams interchangably. apache-module PHP does not. – user340140 Nov 06 '12 at 22:57

8 Answers8

133

It is possible to write STDOUT directly to a file in PHP, which is much easier and more straightforward than using output bufferering.

Do this in the very beginning of your script:

fclose(STDIN);
fclose(STDOUT);
fclose(STDERR);
$STDIN = fopen('/dev/null', 'r');
$STDOUT = fopen('application.log', 'wb');
$STDERR = fopen('error.log', 'wb');

Why at the very beginning you may ask? No file descriptors should be opened yet, because when you close the standard input, output and error file descriptors, the first three new descriptors will become the NEW standard input, output and error file descriptors.

In my example here I redirected standard input to /dev/null and the output and error file descriptors to log files. This is common practice when making a daemon script in PHP.

To write to the application.log file, this would suffice:

echo "Hello world\n";

To write to the error.log, one would have to do:

fwrite($STDERR, "Something went wrong\n"); 

Please note that when you change the input, output and error descriptors, the build-in PHP constants STDIN, STDOUT and STDERR will be rendered unusable. PHP will not update these constants to the new descriptors and it is not allowed to redefine these constants (they are called constants for a reason after all).

Bas Peters
  • 1,751
  • 1
  • 11
  • 10
  • Do you know where is this feature documented? – galymzhan Oct 25 '11 at 06:33
  • 2
    This feature is documented at [php.net](http://php.net/manual/en/features.commandline.io-streams.php) – icirellik Nov 28 '11 at 17:09
  • 8
    It's basic Unix behavior (that's also why it's not portable to windows). Unix gives the smallest file descriptor available to a file. Given that the fcloses close 0, 1 and 2, they become available for the next 3 open. Also Unix is awesome in its simplicity. – Eric May 31 '12 at 09:08
  • Very nice. I needed a way to get the PHP function `imagecreatefromjpeg` to stop printing `Not a JPEG file` for non-jpg files even when prefixed with `@`. Using `fclose(STDERR); $STDERR = fopen('nul', 'wb');` works perfectly. I can even put that block in a conditional so that I can toggle the output with a command-line argument if desired. Thanks! – Synetech Jul 21 '13 at 15:16
  • 3
    If this is being used with a cron script, you probably want to use `fopen` with mode `a` so your previous log is not overwritten. – nullability Mar 07 '14 at 18:53
  • 12
    Good grief. This works out of sheer luck. This is not a solution. – CXJ Sep 17 '14 at 22:28
  • It would be nice to mention "a hack to duplicate fd/1 to 2" from [Daemonising a PHP cli script on a posix system](http://andytson.com/blog/2010/05/daemonising-a-php-cli-script-on-a-posix-system/) by Andy Thompson: `$stdErr = fopen('php://stdout', 'w');`. – vvolodko Oct 01 '14 at 12:20
  • @BasPeters, What do you mean by "the first three new descriptors will become the NEW standard input, output and error file descriptors"? Do you mean that even if we did not name those variables as `$STDIN `$STDOUT`, **it still works**? – Pacerier Oct 14 '14 at 07:00
  • 2
    @Pacerier, exactly. It does not matter how you name the variables. I just kept them the same as their constant equivalents for readability. The only really important thing is the order in which you close the default descriptors and open the new ones. – Bas Peters Oct 16 '14 at 13:23
  • @BasPeters, So if we fclose STDERR, then STDIN, then STDOUT, Does it mean that the first fopen will be STDERR and the third fopen will be STDOUT? – Pacerier Oct 19 '14 at 18:34
  • 1
    In CLI it does not work. PHP displays the error message in stderr. With the above method no message is written to the $STDERR file - they get simple lost. For CLI use in php.ini -> then PHP writes the error messages into file specified there. – Peter VARGA Nov 27 '14 at 22:34
  • @AlBundy What platform are you using, because this should work perfectly in CLI. I use it a lot in my CLI scripts. – Bas Peters Nov 28 '14 at 09:10
  • SuSE Enterprise Server 11 with PHP 5.6.2. – Peter VARGA Nov 28 '14 at 10:11
  • 1
    `Warning: fclose() expects parameter 1 to be resource, string given` – mgutt Apr 01 '15 at 07:55
  • Something's bugging me: when closing the streams, does it close it for the current script only, or does it close the streams for all the PHP on the server? – user2700551 Sep 10 '15 at 12:43
  • @user2700551 It modifies the streams for the current script only. This is true for CLI scripts at least, as each script runs in its own process. Usage of this methodology in a webserver context is not recommended and will not always work as pointed out in the comments above. – Bas Peters Sep 11 '15 at 06:45
  • 1
    Works for STDOUT, but not for PHP errors. Tested with `call to undefined function` - the script dies silently. @AlBundy's recommendation to use `set_ini('error_log', 'error.log')` fixes this (it is also necessary to omit `fclose(STDERR)`). Would a new solution, or an edited one, be appropriate to make this clear? – Roger Dueck Jul 19 '16 at 21:40
  • What the @#^* am I looking at :o – iautomation Nov 06 '16 at 03:32
23

here's a way to divert OUTPUT which appears to be the original problem

$ob_file = fopen('test.txt','w');

function ob_file_callback($buffer)
{
  global $ob_file;
  fwrite($ob_file,$buffer);
}

ob_start('ob_file_callback');

more info here:

http://my.opera.com/zomg/blog/2007/10/03/how-to-easily-redirect-php-output-to-a-file

user340140
  • 628
  • 6
  • 10
  • 8
    If you are using PHP 5.3 or newer, I would suggest to use a closure function without the need of having `$ob_file` to be global: `$ob_file_callback = function($buffer) use ($ob_file) { fwrite($ob_file, $buffer); }; ob_start($ob_file_callback);` – Pascal Rosin Nov 08 '12 at 22:45
  • This work for me and should be the accepted answer in my view – Nam G VU Dec 12 '17 at 10:48
  • @user340140 If you know how to redirect stdin, please share. – Nam G VU Dec 12 '17 at 11:05
  • Your link to the opera.com website seems to have died (almost 15 years later). Do you have other sources? (but your answer is already clear, thanks) – Marten Koetsier Aug 25 '23 at 13:44
10

None of the answers worked for my particular case where I needed a cross platform way of redirecting the output as soon as it was echo'd out so that I could follow the logs with tail -f log.txt or another log viewing app. I came up with the following solution:

$logFp = fopen('log.txt', 'w');

ob_start(function($buffer) use($logFp){
    fwrite($logFp, $buffer);
}, 1); //notice the use of chunk_size == 1

echo "first output\n";
sleep(10)
echo "second output\n";

ob_end_clean();

I haven't noticed any performance issues but if you do, you can change chunk_size to greater values.

Now just tail -f the log file:

tail -f log.txt
Danilo Souza Morães
  • 1,481
  • 13
  • 18
  • "`ob_start(function($buffer) use($logFp){`" Woa, could you explain this magic? Since when can one pass functions as arguments? What is this 'use' thing? Since when is this valid PHP syntax? – Luc Aug 07 '22 at 16:26
8

No, output buffering is as good as it gets. Though it's slightly nicer to just do

ob_start();
echo 'xxx';
$contents = ob_get_flush();
file_put_contents($file,$contents);
chaos
  • 122,029
  • 33
  • 303
  • 309
7

Using eio pecl module eio is very easy, also you can capture PHP internal errors, var_dump, echo, etc. In this code, you can found some examples of different situations.

$fdout = fopen('/tmp/stdout.log', 'wb');
$fderr = fopen('/tmp/stderr.log', 'wb');

eio_dup2($fdout, STDOUT);
eio_dup2($fderr, STDERR);
eio_event_loop();

fclose($fdout);
fclose($fderr);

// output examples
echo "message to stdout\n";

$v2dump = array(10, "graphinux");
var_dump($v2dump);

// php internal error/warning
$div0 = 10/0;

// user errors messages
fwrite(STDERR, "user controlled error\n");

Call to eio_event_loop is used to be sure that previous eio requests have been processed. If you need append on log, on fopen call, use mode 'ab' instead of 'wb'.

Install eio module is very easy (http://php.net/manual/es/eio.installation.php). I tested this example with version 1.2.6 of eio module.

2

You can install Eio extension

pecl install eio

and duplicate a file descriptor

$temp=fopen('/tmp/my_stdout','a');
$my_data='my something';
$foo=eio_dup2($temp,STDOUT,EIO_PRI_MAX,function($data,$esult,$request){
    var_dump($data,$esult,$request);
    var_dump(eio_get_last_error($request));
},$my_data);
eio_event_loop();
echo "something to stdout\n";
fclose($temp);

this creates new file descriptor and rewrites target stream of STDOUT

this can be done with STDERR as well

and constants STD[OUT|ERR] are still usable

1

I understand that this question is ancient, but people trying to do what this question asks will likely end up here... Both of you.

If you are running under a particular environment...

  • Running under Linux (probably most other Unix like operating systems, untested)
  • Running via CLI (Untested on web servers)

You can actually close all of your file descriptors (yes all, which means it's probably best to do this at the very beginning of execution... for example just after a pcntl_fork() call to background the process in a daemon (which seems like the most common need for something like this)

fclose( STDIN );  // fd 3
fclose( STDERR);  // fd 2
fclose( STDOUT ); // fd 1

And then re-open the file descriptors, assigning them to a variable that will not fall out of scope and thus be garbage collected. Because Linux will predictably open them in the proper order.

$kept_in_scope_variable_fd1 = fopen(...); // fd 1
$kept_in_scope_variable_fd2 = fopen(...); // fd 2
$kept_in_scope_variable_fd3 = fopen( '/dev/null', ... ); // fd 3

You can use whatever files or devices you want for this. I gave /dev/null as the example for STDIN (fd3) because that's probably the most common case for this kind of code.

Once this is done you should be able to do normal things like echo, print_r, var_dump, etc without specifically needing to write to a file with a function. Which is useful when you're trying to background code that you do not want to, or aren't able to, rewrite to be file-pointer-output-friendly.

YMMV for other environments and things like having other FD's open, etc. My advice is to start with a small test script to prove that it works, or doesn't, in your environment and then move on to integration from there.

Good luck.

  • I somehow skipped the answer by Bas Peters, above, when reading. Their answer is the same as mine. Mine just gives more context. Upvote their answer. – Apokalyptik Apr 22 '22 at 19:36
0

Here is an ugly solution that was useful for a problem I had (need to debug).

if(file_get_contents("out.txt") != "in progress")
{
    file_put_contents("out.txt","in progress");
    $content = file_get_contents('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']);
    file_put_contents("out.txt",$content);
}

The main drawback of that is that you'd better not to use the $_POST variables. But you dont have to put it in the very beggining.

buburs
  • 9
  • 1