PHP Classes

How to use a PHP network tools class that can run faster executing tasks in parallel with PHP Swoole using the package Network Tools Queued: Asynchronous diagnostics to test Internet services

Recommend this page to a friend!
     
  Info   Example   View files Files   Install with Composer Install with Composer   Download Download   Reputation   Support forum   Blog    
Ratings Unique User Downloads Download Rankings
Not yet rated by the usersTotal: Not yet counted Not yet ranked
Version License PHP version Categories
networktoolsqueued 1.0.0MIT/X Consortium ...8.1Networking, Tools, PHP 8
Description 

Author

This package can perform asynchronous diagnostics to test Internet services.

It provides an alternative implementation of the Network Tools package by Thomas Trautner to access multiple types of Internet services, to check their responses, and display the responses on a Web page.

This package uses the PHP Swoole extension to query multiple network services in parallel.

The package also provides score results based on the responses of the network services.

Innovation Award
PHP Programming Innovation award nominee
November 2025
Number 3
Sometimes problems of Web applications are caused by Internet based services that the applications depend on.

Tools to test those Internet services are useful to create diagnostics of the problems.

This package provides an improved version of another PHP network tools package. This package use the PHP swoole extension to execute the diagnostics of several network tools in parallel to return the results faster.

Manuel Lemos
Picture of Henrik Skov
  Performance   Level  
Name: Henrik Skov <contact>
Classes: 1 package by
Country: Denmark Denmark
Age: ???
All time rank: Not yet ranked
Week rank: Not yet ranked
Innovation award
Innovation award
Nominee: 1x

Instructions

This package is inspired by the original Network Tools package by Thomas Trautner.

Please check this example script to see an example of querying network services.

This package requires the PHP Swoole extension.

Example

<?php
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/NetworkToolsQueued.php';

use Colors\Color;

use function Laravel\Prompts\spin;

function isWSL(): bool {
   
    return str_contains(strtolower(php_uname('r')), 'microsoft') ||
            file_exists('/proc/sys/fs/binfmt_misc/WSLInterop');
}

/**
 * Open an URL in system browser
 *
 *
 * @param string $url
 * @return void
 */
function openBrowser(string $url) : void {

    if (isWSL()) {

        if (strpos($url, 'file://') === 0) {
            $filePath = substr($url, 7); // Remove 'file://'
           
            // Copy to accessible location and open with cmd
            $windowsTempFile = '/mnt/c/Windows/Temp/' . basename($filePath);
            shell_exec("cp " . escapeshellarg($filePath) . " " . escapeshellarg($windowsTempFile));
           
            $windowsPath = 'C:\\Windows\\Temp\\' . basename($filePath);
            shell_exec("cmd.exe /c start \"\" \"" . $windowsPath . "\" 2>/dev/null");
        } else {
            shell_exec("wslview \"$url\"");
        }
    } else {
        shell_exec("xdg-open \"$url\"");
    }
}

class StdinHelper {

    /**
     * Check if there is any data available on STDIN using select()
     * This is the most reliable method for real-time checking
     *
     * @param int $timeout Timeout in seconds (0 for immediate return)
     * @return bool True if data is available, false otherwise
     */
    public static function hasDataSelect(float $timeout = 0): bool {
        $read = array(STDIN);
        $write = null;
        $except = null;
       
        return stream_select($read, $write, $except, $timeout) > 0;
    }

    /**
     * Forward stdin to a subprocess
     *
     *
     * @param string $cmd
     * @return void
     */
    public static function forwardStdInTo(string $cmd) : void {

        // Make STDIN non-blocking
        stream_set_blocking(STDIN, false);

        // Create a subprocess (example using 'cat', replace with your desired command)
        $descriptorspec = array(
        0 => array("pipe", "r"), // stdin
        1 => array("pipe", "w"), // stdout
        2 => array("pipe", "w") // stderr
        );

        $process = proc_open($cmd, $descriptorspec, $pipes);

        if (is_resource($process)) {
            // Make subprocess input/output streams non-blocking
            stream_set_blocking($pipes[0], false);
            stream_set_blocking($pipes[1], false);
            stream_set_blocking($pipes[2], false);

            while (true) {
                // Read from STDIN
                $input = fgets(STDIN);
               
                if ($input !== false) {
                    // Write to subprocess stdin
                    fwrite($pipes[0], $input);
                   
                    // Flush the pipe to ensure immediate writing
                    fflush($pipes[0]);
                }
               
                // Read from subprocess stdout
                $output = fgets($pipes[1]);
                if ($output !== false) {
                    echo $output;
                }
               
                // Read from subprocess stderr
                $error = fgets($pipes[2]);
                if ($error !== false) {
                    fwrite(STDERR, $error);
                }
               
                // Small delay to prevent CPU overload
                usleep(10000); // 10ms delay
               
                // Check if STDIN has closed
                if (feof(STDIN)) {
                    break;
                }
            }

            // Clean up
            fclose($pipes[0]);
            fclose($pipes[1]);
            fclose($pipes[2]);
           
            // Close the process
            proc_close($process);
        }
    }

    /**
     * Check if STDIN is connected to a pipe or redirected file
     * Useful for detecting if script is running interactively
     *
     * @return bool True if STDIN is a pipe/file, false if it's a terminal
     */
    public static function isPiped(): bool {
        return !posix_isatty(STDIN);
    }

    /**
     * Check if there is any data using non-blocking read
     * This method might be less reliable but doesn't require stream_select
     *
     * @return bool True if data is available, false otherwise
     */
    public static function hasDataNonBlocking(): bool {
        static $buffer = null;

        // If we previously read a byte, return true
        if ($buffer !== null) {
            return true;
        }

        // Store current blocking state
        $meta = stream_get_meta_data(STDIN);
        $blocking = $meta['blocked'] ?? true;

        // Temporarily set to non-blocking
        stream_set_blocking(STDIN, false);

        // Try to read one byte
        $char = fgetc(STDIN);

        // Restore original blocking state
        stream_set_blocking(STDIN, $blocking);

        if ($char !== false) {
            // Store the character for later retrieval
            $buffer = $char;
            return true;
        }

        return false;
    }

    public static function getStdInData(): string {
        static $buffer = null;
        $data = '';

        // If we previously read a byte, prepend it to the read data
        if ($buffer !== null) {
            $data .= $buffer;
            $buffer = null;
        }

        // Read the rest of the input
        $data .= stream_get_contents(STDIN);
        return $data;
    }

    /**
     * Comprehensive check that combines multiple methods
     *
     * @return array Array with detailed information about STDIN state
     */
    public static function getStdinInfo(): array {
        return [
            'has_data' => self::hasDataSelect(),
            'is_piped' => self::isPiped(),
            'is_readable' => is_readable("php://stdin"),
            'stream_type' => stream_get_meta_data(STDIN)['stream_type'],
            'blocked' => stream_get_meta_data(STDIN)['blocked'],
            'eof' => feof(STDIN)
        ];
    }
}

//============================================================
// MAIN EXAMPLE CODE
//============================================================

try {

    define('OPS',[
            'a',
            'mx',
            'txt',
            'cert',
            'cname',
            'soa',
            'spf',
            'ptr',
            'tcp',
            'smtp',
            'http',
            'https',
            'ping',
            'simulatedping',
            'trace',
            'simulatedtrace',
            'fulldomain',
            'fullip',
            'securityscan',
            'emailanalysis',
            'quickcheck',
            'serverscan',
            'whois'
    ]);

    $output = NULL;

    if ($idx = array_search('--output', $_SERVER['argv'])) {
        $output = $_SERVER['argv'][$idx+1];
    }

    $c = new Color(); // See: https://github.com/kevinlebrun/colors.php
   
    $operation = $_SERVER['argv'][1];
    $operation = strtolower($operation);
   
    // Validation
    if (!in_array($operation, OPS)) {
        throw new Exception(sprintf('Invalid operation: "%s" - Valid operations: %s', $operation, implode(', ', OPS)));
    }
   
    $input = $_SERVER['argv'][2];
    $stdInData = '';

    if (StdInHelper::hasDataSelect()) {
        $stdInData = StdinHelper::getStdInData();
    }

    $inputs = match(TRUE) {
        (!empty($input) && is_file($input)) => file($input, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES),
        (!empty($stdInData)) => explode(PHP_EOL, trim($stdInData)),
        (!empty($input) && !is_file($input)) => [$input],
        default => []
    };

    // Abort if no inputs
    if (empty($inputs)) {
        throw new Exception(sprintf('No inputs for "%s" operation - Use an IP or domain as argument or send a list on STDIN', $operation));
    }

    $tools = new NetworkToolsQueued();

    // Enable auto-PTR if flag set
    //if ($this->enableAutoPtr) $tools->enableAutoPTR();

    // Enable problem summary
    //if ($this->enableProblemSummary) $tools->enableProblemSummary();
   
    // Determine if operation requires IP or domain
    $requiresIP = in_array($operation, ['ping', 'simulatedping', 'ptr']);
    $requiresDomain = !$requiresIP;

    // Validate and queue all targets
    foreach($inputs as $target) {

        $target = trim($target);
        if (empty($target)) continue;
       
        // Handle special chained operations
        if (in_array($operation, ['fulldomain', 'securityscan', 'emailanalysis', 'quickcheck', 'whois'])) {
           
            // Domain operations
            if (filter_var($target, FILTER_VALIDATE_DOMAIN) === FALSE) {
                throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires a domain', $operation, $target));
            }

            if ($operation == 'fulldomain') {
                $tools->fulldomain($target, in_array('--portscan', $_SERVER['argv']));
            } else {
                $tools->$operation($target);
            }

        } elseif ($operation === 'fullip') {
           
            // IP operation
            if (filter_var($target, FILTER_VALIDATE_IP) === FALSE) {
                throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires an IP', $operation, $target));
            }
            $tools->$operation($target, TRUE, TRUE);
           
        } elseif ($operation === 'serverscan') {
           
            // Works with both IP and domain
            $tools->$operation($target);
           
        } else {

            // Original single operations
            if ($requiresIP) {
                if (filter_var($target, FILTER_VALIDATE_IP) === FALSE) {
                    throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires an IP', $operation, $target));
                }
            } elseif ($requiresDomain) {
                if (empty($target) || preg_match('/^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$/i', $target) === 0) {
                    throw new Exception(sprintf('Invalid target for "%s": "%s" - Requires a domain', $operation, $target));
                }
            }
           
            $tools->$operation($target);
        }
    }

    // JSON output mode
    if (in_array('--json', $_SERVER['argv'])) {
        if (posix_isatty(STDOUT)) {
            $m = ($output)
                ? '<light_yellow>One moment please ...</light_yellow> - <light_cyan>Generating report</light_cyan> - <white>Will write</white> <light_green>'.$this->output.'</light_green> <white>momentarily...</white>'
                : '<light_yellow>One moment please ...</light_yellow> - <light_cyan>Generating report</light_cyan>';
           
            $array = spin(function() use($tools) {
                $tools->exec();
                return $tools->getResults();
            }, $c->colorize($m));

            // Group by target
            $byTarget = [];
            foreach ($array as $r) {
                $byTarget[$r['target']][] = $r;
            }
           
            ksort($byTarget);

            // Calculate and add scores to output
            $scores = $tools->getTargetScores();
            $output = [
                'targets' => $byTarget,
                'scores' => $scores
            ];

            if ($output) {
                file_put_contents($output, json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
                echo self::TAB.$c->colorize("<light_green>&#10003;</light_green> Results written to: <light_cyan>{$output}</light_cyan>\n");
            } else {
                echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
            }
           
        } else {

            $tools->exec();
            $array = $tools->getResults();

            // Group by target
            $byTarget = [];
            foreach ($array as $r) {
                $byTarget[$r['target']][] = $r;
            }

            ksort($byTarget);

            // Calculate and add scores to output
            $scores = $tools->getTargetScores();
            $output = [
                'targets' => $byTarget,
                'scores' => $scores
            ];

            if ($output) {
                file_put_contents($output, json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
            } else {
                echo json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n";
            }
        }
       
        return;
    }

    // HTML output mode (default)
    if (posix_isatty(STDOUT)) {
        $html = spin(function() use($tools) { // Spin is part of Laravel\Prompts\ package
            $tools->exec();
            // Enable grouping by target for better organization
            return $tools->getReport(TRUE);
        }, $c->colorize('<light_yellow>One moment please ...</light_yellow> - <light_cyan>Generating report</light_cyan> - <white>Will open in</white> <light_green>browser</light_green> <white>momentarily...</white>'));
    } else {
        $tools->exec();
        $html = $tools->getReport(TRUE); // Group by target
    }

    // Save and open HTML report
    $file = tempnam(sys_get_temp_dir(), 'nettool_') . '.html';
    file_put_contents($file, $html);

    openBrowser('file://'.$file);    

} catch(Exception $ex) {

    echo('ERROR :: ' . $ex->getMessage());
    exit(1);
} 


  Files folder image Files (3)  
File Role Description
Accessible without login Plain text file composer.json Conf. composer json
Accessible without login Plain text file example.php Example Short example
Plain text file NetworkToolsQueued.php Class The class

The PHP Classes site has supported package installation using the Composer tool since 2013, as you may verify by reading this instructions page.
Install with Composer Install with Composer
 Version Control Unique User Downloads  
 0%
Total:0
This week:0