Skip to content
Draft
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
22 changes: 22 additions & 0 deletions features/eval.feature
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,25 @@ Feature: Evaluating PHP code and files.
Args: arg1 arg2
"""

Scenario: Eval-file with STDIN should work with alias groups
Given a WP installation in 'site1'
And a WP installation in 'site2'
And a wp-cli.yml file:
"""
@group:
- @site1
- @site2
@site1:
path: site1
@site2:
path: site2
"""
And a stdin-test.php file:
"""
<?php
echo "Executed from STDIN\n";
"""

When I run `cat stdin-test.php | wp @group eval-file -`
Then STDOUT should match /Executed from STDIN.*Executed from STDIN/s

99 changes: 98 additions & 1 deletion src/EvalFile_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ class EvalFile_Command extends WP_CLI_Command {
*/
const SHEBANG_PATTERN = '/^(#!.*)$/m';

/**
* Cache for STDIN contents to support alias groups.
*
* When using alias groups, the same command is executed multiple times
* in separate processes. STDIN can only be read once, so we cache it
* in a temporary file that persists across processes.
*
* @var string|null
*/
private static $stdin_cache = null;

/**
* Temporary file path for STDIN cache.
*
* @var string|null
*/
private static $stdin_cache_file = null;

/**
* Loads and executes a PHP file.
*
Expand Down Expand Up @@ -96,7 +114,57 @@ private static function execute_eval( $file, $positional_args, $use_include ) {
unset( $positional_args );

if ( '-' === $file ) {
eval( '?>' . file_get_contents( 'php://stdin' ) );
// Cache STDIN contents to support alias groups.
// When using alias groups, each site runs in a separate process,
// but they all share the same STDIN. Once STDIN is read, it's consumed.
// We cache it in a temporary file that persists across processes.
if ( null === self::$stdin_cache ) {
// Get a unique identifier for the cache file
// Use parent PID on Unix-like systems for sharing across child processes
// On Windows, this feature may have limitations due to lack of posix_getppid()
if ( function_exists( 'posix_getppid' ) ) {
$cache_id = posix_getppid();
} else {
// On Windows or systems without POSIX, use an environment variable if available
// or fall back to current PID (which won't share across processes)
$cache_id = getenv( 'WP_CLI_STDIN_CACHE_ID' ) ?: getmypid();
}
self::$stdin_cache_file = sys_get_temp_dir() . '/wp-cli-eval-stdin-' . $cache_id . '.php';

// Check if cache file already exists (created by a previous subprocess)
if ( file_exists( self::$stdin_cache_file ) ) {
$stdin_contents = file_get_contents( self::$stdin_cache_file );
if ( false === $stdin_contents ) {
WP_CLI::error( 'Failed to read from STDIN cache file.' );
}
self::$stdin_cache = (string) $stdin_contents;

// Clean up old cache files (older than 1 hour) to prevent accumulation
self::cleanup_old_cache_files();
} else {
// First process: read from STDIN and cache it
$stdin_contents = file_get_contents( 'php://stdin' );
if ( false === $stdin_contents ) {
WP_CLI::error( 'Failed to read from STDIN.' );
}
self::$stdin_cache = (string) $stdin_contents;

// Save to cache file for subsequent processes
$write_result = file_put_contents( self::$stdin_cache_file, self::$stdin_cache );
if ( false === $write_result ) {
WP_CLI::error( 'Failed to write STDIN cache file.' );
}

// Clean up old cache files (older than 1 hour) to prevent accumulation
self::cleanup_old_cache_files();

// Note: We intentionally don't clean up the current cache file immediately.
// If we delete it in the shutdown function, subsequent child processes
// won't be able to read it. The cleanup_old_cache_files() method handles
// removal of stale cache files.
}
}
eval( '?>' . self::$stdin_cache );
} elseif ( $use_include ) {
include $file;
} else {
Expand All @@ -113,4 +181,33 @@ private static function execute_eval( $file, $positional_args, $use_include ) {
eval( '?>' . $file_contents );
}
}

/**
* Clean up old STDIN cache files to prevent accumulation.
*
* Removes cache files older than 1 hour from the system temp directory.
* This is called when creating or reading a cache file to ensure stale
* files don't accumulate if processes terminate unexpectedly.
*/
private static function cleanup_old_cache_files() {
$temp_dir = sys_get_temp_dir();
$cache_pattern = $temp_dir . '/wp-cli-eval-stdin-*.php';
$cache_files = glob( $cache_pattern );
$one_hour_ago = time() - 3600;

if ( ! empty( $cache_files ) ) {
foreach ( $cache_files as $cache_file ) {
// Skip the current cache file
if ( $cache_file === self::$stdin_cache_file ) {
continue;
}

// Delete files older than 1 hour
$file_time = filemtime( $cache_file );
if ( false !== $file_time && $file_time < $one_hour_ago ) {
@unlink( $cache_file );
}
}
}
}
}
Loading