diff --git a/HISTORY.md b/HISTORY.md index c10b979f3..56b3e7fbf 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -16,6 +16,7 @@ ENH: Issue #977 - THttpRequest::onResolveRequest for custom service resolution a ENH: Issue #982 - General Logging update: Profiling, Flushing large logs for long running processes, optional Tracing, tracks PID for multi-threaded logging, TBrowserLogRoute colorizes the time delta, TDbLogRoute adds a new DB field 'prefix' and functions for getting the DB log, DB log count, and deleting DB logs, TDBLogRoute also adds a RetainPeriod for automatically removing old logs, Adds an event TLogger::OnFlushLogs and flushes as a register_shutdown_function, adds the TSysLogRoute, and adds unit tests for logging. (belisoful) ENH: Issue #984 - TEventSubscription for temporary event handlers. (belisoful) ENH: Issue #972 - TProcessHelper (isSystemWindows, forking, kill, priority) and TSignalsDispatcher for delegating signals to respective global events, alarm interrupt callbacks at specific times, and per child PIDs callbacks. TEventSubscription can subscribe to a PHP process signal, an integer, as an event "name" (in TSignalsDispatcher). (belisoful) +ENH: Issue #973 - Embedded PHP Development Web Server CLI Action. (belisoful) ## Version 4.2.2 - April 6, 2023 diff --git a/README.md b/README.md index 7eb81ee8e..d2de49db0 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,18 @@ composer create-project pradosoft/prado-app app The application will be installed in the "app" directory. +## Built-in PHP Test Web Server + +The built-in PHP Test Web Server can be used to immediately start developing and testing a web application. +The web server is started with command (assuming the above application in the directory "app") + +```sh +cd app/protected +./vendor/bin/prado-cli http +``` + +The application is then accessible on the machine's browser at `http://127.0.0.1:8080/`. The built-in web server is enabled when the application is in "Debug" mode or is enabled in the application configuration parameters. + #### Add PRADO to an existing application Just create a composer.json file for your project: @@ -51,7 +63,7 @@ Just create a composer.json file for your project: ``` The [asset-packagist](https://asset-packagist.org) repository is used to install javascript dependencies. -Assuming you already installed composer, run +Assuming you already installed composer, run the command ```sh composer install diff --git a/framework/IO/TOutputWriter.php b/framework/IO/TOutputWriter.php index d248ea247..5ac3b1eed 100644 --- a/framework/IO/TOutputWriter.php +++ b/framework/IO/TOutputWriter.php @@ -12,23 +12,35 @@ /** * TOutputWriter class. * - * TOutputWriter extends TTextWriter to fwrite the buffer to STDOUT - * when {@see flush}ed. This allows for testing of the Shell output. + * TOutputWriter extends TTextWriter to write the buffer to "Output" when {@see flush}ed. + * In a CLI execution, STDOUT is "Output" but this is not true for processing web + * pages. For web pages, "Output" goes to the browser and 'php://stdout' goes to + * the web server's output (either cli or file); while STDOUT is not defined. + * + * Once this class is flushed, PHP's flush need to be called to ensure the "Output" + * is written. If Output Buffering (ob_*) is used, ob_flush (or equivalent) also + * must be called before PHP's flush and after this class flushes. * * @author Brad Anderson * @since 4.2.0 */ class TOutputWriter extends TTextWriter { + /** The file path to open a data stream to Output. */ + public const OUTPUT_URI = 'php://output'; + + /** The type of stream for Output. */ + public const OUTPUT_TYPE = 'Output'; + /** - * Flushes the content that has been written. - * @return string the content being flushed + * Flushes the content that has been written. This does not call PHP's ob_flush + * or flush and those must be called to ensure the output is actually sent. + * @return string the content being flushed. */ public function flush() { $str = parent::flush(); - fwrite(STDOUT, $str); - flush(); + echo $str; return $str; } } diff --git a/framework/IO/TStdOutWriter.php b/framework/IO/TStdOutWriter.php new file mode 100644 index 000000000..4115d464b --- /dev/null +++ b/framework/IO/TStdOutWriter.php @@ -0,0 +1,63 @@ + + * @link https://github.com/pradosoft/prado + * @license https://github.com/pradosoft/prado/blob/master/LICENSE + */ + +namespace Prado\IO; + +/** + * TStdOutWriter class. + * + * TStdOutWriter extends TTextWriter to fwrite the buffer to STDOUT when {@see flush}ed. + * This allows for testing of the Shell output. + * + * STDOUT is only defined in the CLI. When processing a PHP web page, this opens + * a new handle to 'php://stdout'. + * + * @author Brad Anderson + * @since 4.2.3 + */ +class TStdOutWriter extends TTextWriter +{ + /** The file path to open a data stream in memory */ + public const STDOUT_URI = 'php://stdout'; + + /** @var mixed the Standard Out stream handle */ + private mixed $_stdout = null; + + /** + * Closes the StdOut handle when STDOUT is not defined + */ + public function __destruct() + { + if (!defined('STDOUT') && $this->_stdout) { + fclose($this->_stdout); + } + parent::__destruct(); + } + + /** + * Flushes the content that has been written. + * @return string the content being flushed. + */ + public function flush() + { + $str = parent::flush(); + + if (!$this->_stdout) { + if (!defined('STDOUT')) { + $this->_stdout = fopen(TStdOutWriter::STDOUT_URI, 'wb'); + } else { + $this->_stdout = STDOUT; + } + } + + fwrite($this->_stdout, $str); + + return $str; + } +} diff --git a/framework/ISingleton.php b/framework/ISingleton.php index ebd9f0af7..9cd053c71 100644 --- a/framework/ISingleton.php +++ b/framework/ISingleton.php @@ -21,7 +21,7 @@ interface ISingleton { /** * @param bool $create Should the singleton be created if it doesn't exist. - * @return ?object The singleton instance of the class + * @return ?object The singleton instance of the class. */ public static function singleton(bool $create = true): ?object; } diff --git a/framework/Shell/Actions/TDbParameterAction.php b/framework/Shell/Actions/TDbParameterAction.php index de4e0a19e..1027c5f32 100644 --- a/framework/Shell/Actions/TDbParameterAction.php +++ b/framework/Shell/Actions/TDbParameterAction.php @@ -56,12 +56,12 @@ public function setAll($value) /** * Properties for the action set by parameter - * @param string $actionID the action being executed + * @param string $methodID the action being executed * @return array properties for the $actionID */ - public function options($actionID): array + public function options($methodID): array { - if ($actionID === 'index') { + if ($methodID === 'index') { return ['all']; } return []; diff --git a/framework/Shell/Actions/TWebServerAction.php b/framework/Shell/Actions/TWebServerAction.php new file mode 100644 index 000000000..4f935dc93 --- /dev/null +++ b/framework/Shell/Actions/TWebServerAction.php @@ -0,0 +1,379 @@ + + * @link https://github.com/pradosoft/prado + * @license https://github.com/pradosoft/prado/blob/master/LICENSE + */ + +namespace Prado\Shell\Actions; + +use Prado\Prado; +use Prado\Shell\TShellAction; +use Prado\Shell\TShellApplication; +use Prado\TPropertyValue; +use Prado\Util\Helpers\TProcessHelper; + +/** + * TWebServerAction class + * + * This class serves the application with the built-in PHP testing web server. + * + * When no network address is specified, the web server will listen on the default + * 127.0.0.1 network interface and on port 8080. The application is accessible on + * the machine web browser at web address "http://127.0.0.1:8080/". + * + * The command option `--address=localhost` can specify the network address both + * with and without a port. A port can also be specified with the network address, + * eg `--address=localhost:8777`. + * + * if the machine "hosts" file maps a domain name to 127.0.0.1/localhost, then that + * domain name can be specified with `--address=testdomain.com` and is accessible + * on the machine web browser at "http://testdomain.com/". In this example, testdomain.com + * maps to 127.0.0.1 in the system's "hosts" file. + * + * The network address can be changed to IPv6 with the command line option `--ipv6`. + * To serve pages on all network addresses, including any internet IP address, include + * the option `--all`. These options only work when there is no address specified. + * + * The command line option `--port=8777` can be used to change the port; in this example + * to port 8777. Ports 1023 and below are typically reserved for application and + * system use and cannot be specified without administration access. + * + * To have more than one worker (to handle multiple requests), specify the `--workers=8` + * command option (with the number of page workers need for you). In this example, + * eight concurrent workers are created. + * + * The TWebServerAction is only available when the application is in "Debug" mode. + * In other Application modes, this action can be enabled with the Prado Application + * Parameter "Prado:PhpWebServer" set to "true". eg. Within the Application configuration: + * + * ```xml + * + * + * + * ``` + * + * @author Brad Anderson + * @since 4.2.3 + */ +class TWebServerAction extends TShellAction +{ + public const DEV_WEBSERVER_ENV = 'PRADO_DEV_WEBSERVER'; + public const WORKERS_ENV = 'PHP_CLI_SERVER_WORKERS'; + + public const DEV_WEBSERVER_PARAM = 'Prado:PhpWebServer'; + + protected $action = 'http'; + protected $methods = ['serve']; + protected $parameters = [[]]; + protected $optional = [['router-filepath']]; + protected $description = [ + 'Provides a Test PHP Web Server to serve the application.', + 'Runs a PHP Web Server after Initializing the Application.']; + + /** @var bool Listen on all network addresses assigned to the computer, when one is not provided. Default false. */ + private bool $_all = false; + + /** @var ?string The specific address to listen on. Default null. */ + private ?string $_address = null; + + /** @var int The port to listen on, default 8080 */ + private int $_port = 8080; + + /** @var bool Use a direct ip v6 address, when one is not provided. Default false. */ + private bool $_ipv6 = false; + + /** @var int the number of workers for the Web Server, default 1. */ + private int $_workers = 1; + + /** + * This option is only used when no network interface is specified. + * @return bool Respond on all network addresses, default false. + */ + public function getAll(): bool + { + return $this->_all; + } + + /** + * This option is only used when no network interface is specified. When called + * without a $value (null or ''), All is set to true. + * @param mixed $value + * @return bool Respond on all network addresses. + * @return static The current object. + */ + public function setAll($value): static + { + if ($value === null || $value === '') { + $this->_all = true; + } else { + $this->_all = TPropertyValue::ensureBoolean($value); + } + return $this; + } + + /** + * Gets the network address to serve pages from. When no network address is specified + * then this will return the proper network address based upon {@see self::getIpv6()} + * and {@see self::getAll()}. + * @return string The network address to serve pages. + */ + public function getAddress(): string + { + if(!$this->_address) { + if ($this->getIpv6()) { + if ($this->getAll()) { + return '[::0]'; + } else { + return 'localhost'; + } + } else { + if ($this->getAll()) { + return '0.0.0.0'; + } else { + return '127.0.0.1'; + } + } + } + return $this->_address; + } + + /** + * @param ?string $address The network address to serve pages from. + * @return static The current object. + */ + public function setAddress($address): static + { + if ($address) { + $address = TPropertyValue::ensureString($address); + $port = null; + + if (($address[0] ?? '') === '[') { + if ($pos = strrpos($address, ']')) { + if ($pos = strrpos($address, ':', $pos)) { + $port = substr($address, $pos + 1); + } + } + } else { + if (($pos = strrpos($address, ':')) !== false) { + $port = substr($address, $pos + 1); + } + } + + if (is_numeric($port)) { + $this->_port = (int) $port; + $address = substr($address, 0, $pos); + } + $this->_address = $address; + } else { + $this->_address = null; + } + + return $this; + } + + /** + * @return int The port to serve pages, default 8080. + */ + public function getPort(): int + { + return $this->_port; + } + + /** + * @param null|int|string $port The port to serve pages. + * @return static The current object. + */ + public function setPort($port): static + { + $this->_port = TPropertyValue::ensureInteger($port); + + return $this; + } + + /** + * @return bool Use the local IPv6 network address, default false. + */ + public function getIpv6(): bool + { + return $this->_ipv6; + } + + /** + * When called without a $value (null or ''), Ipv6 is set to true. + * @param null|bool|string $ipv6 Use the ipv6 local network address. + * @return static The current object. + */ + public function setIpv6($ipv6): static + { + if ($ipv6 === null || $ipv6 === '') { + $ipv6 = true; + } + $this->_ipv6 = TPropertyValue::ensureBoolean($ipv6); + + return $this; + } + + /** + * @return int The number of web server requests workers, default 1. + */ + public function getWorkers(): int + { + return $this->_workers; + } + + /** + * When called without a value (null or '') then the number of workers is set to 8. + * @param null|int|string $value The number of web server requests workers. + * @return static The current object. + */ + public function setWorkers($value): static + { + if ($value === null || $value === '') { + $this->_workers = 8; + } else { + $this->_workers = max(1, TPropertyValue::ensureInteger($value)); + } + + return $this; + } + + /** + * Properties for the action set by parameter. + * @param string $methodID the action being executed + * @return array properties for the $actionID + */ + public function options($methodID): array + { + if ($methodID === 'serve') { + return ['address', 'port', 'workers', 'ipv6', 'all']; + } + return []; + } + + /** + * Aliases for the properties to be set by parameter. 'i' is for 'interface'. + * @return array alias => property for the $actionID + */ + public function optionAliases(): array + { + return ['a' => 'address', 'p' => 'port', 'w' => 'workers', '6' => 'ipv6', 'i' => 'all']; + } + + + /** + * This runs the PHP Development Web Server. + * @param array $args parameters + * @return bool + */ + public function actionServe($args) + { + array_shift($args); + + $env = getenv(); + $env[static::DEV_WEBSERVER_ENV] = '1'; + + if (($workers = $this->getWorkers()) > 1) { + $env[static::WORKERS_ENV] = $workers; + } + + $address = $this->getAddress(); + $port = $this->getPort(); + $documentRoot = dirname($_SERVER['SCRIPT_FILENAME']); + + if ($router = array_shift($args)) { + if ($r = realpath($router)) { + $router = $r; + } + } + + $app = Prado::getApplication(); + $quiet = ($app instanceof TShellApplication) ? $app->getQuietMode() : 0; + + $writer = $this->getWriter(); + + if (!is_dir($documentRoot)) { + if ($quiet !== 3) { + $writer->writeError("Document root \"$documentRoot\" does not exist."); + $writer->flush(); + } + return true; + } + + if ($this->isAddressTaken($address, $port)) { + if ($quiet !== 3) { + $writer->writeError("http://$address is taken by another process."); + $writer->flush(); + } + return true; + } + + if ($router !== null && !file_exists($router)) { + if ($quiet !== 3) { + $writer->writeError("Routing file \"$router\" does not exist."); + $writer->flush(); + } + return true; + } + + $nullFile = null; + if ($quiet >= 2) { + $nullFile = TProcessHelper::isSystemWindows() ? 'NUL' : '/dev/null'; + $descriptors = [STDIN, ['file', $nullFile, 'w'], ['file', $nullFile, 'w']]; + } else { + $writer->writeline(); + $writer->write("Document root is \"{$documentRoot}\"\n"); + if ($router) { + $writer->write("Routing file is \"$router\"\n"); + } + $writer->writeline(); + $writer->write("To quit press CTRL-C or COMMAND-C.\n"); + $writer->flush(); + + $descriptors = [STDIN, STDOUT, STDERR]; + } + + $command = $this->generateCommand($address . ':' . $port, $documentRoot, $router); + $cwd = null; + + $process = proc_open($command, $descriptors, $pipes, $cwd, $env); + proc_close($process); + + if ($nullFile) { + fclose($descriptors[1]); + fclose($descriptors[2]); + } + + return true; + } + + /** + * @param string $address The web server address and port. + * @param string $documentRoot The path of the application. + * @param ?string $router The router file + * @return array + */ + public function generateCommand(string $address, string $documentRoot, $router): array + { + return TProcessHelper::filterCommand(array_merge(['@php', '-S', $address, '-t', $documentRoot], $router ? [$router] : [])); + } + + + /** + * This checks if a specific hostname and port are currently being used in the system + * @param string $hostname server address + * @param int $port server port + * @return bool if address is already in use + */ + protected function isAddressTaken($hostname, $port) + { + $fp = @fsockopen($hostname, $port, $errno, $errstr, 3); + if ($fp === false) { + return false; + } + fclose($fp); + return true; + } +} diff --git a/framework/Shell/TShellAction.php b/framework/Shell/TShellAction.php index 47c6e595c..820dea9aa 100644 --- a/framework/Shell/TShellAction.php +++ b/framework/Shell/TShellAction.php @@ -196,7 +196,7 @@ public function renderHelp() $str = ''; $length = 31; - $str .= $this->getWriter()->pad(" - {$action}", $length); + $str .= $this->getWriter()->pad(" {$action}", $length); $description = $this->getWriter()->wrapText($this->description[0], $length); $str .= $description . PHP_EOL; foreach ($this->methods as $i => $method) { diff --git a/framework/Shell/TShellApplication.php b/framework/Shell/TShellApplication.php index 206b34baa..47153d5bb 100644 --- a/framework/Shell/TShellApplication.php +++ b/framework/Shell/TShellApplication.php @@ -14,9 +14,10 @@ use Prado\Shell\Actions\THelpAction; use Prado\Shell\Actions\TFlushCachesAction; use Prado\Shell\Actions\TPhpShellAction; +use Prado\Shell\Actions\TWebServerAction; use Prado\IO\ITextWriter; -use Prado\IO\TOutputWriter; +use Prado\IO\TStdOutWriter; use Prado\Shell\TShellWriter; use Prado\TPropertyValue; @@ -94,14 +95,9 @@ public function run($args = null) $this->_arguments = $args; $this->detectShellLanguageCharset(); - $this->addShellActionClass(TFlushCachesAction::class); - $this->addShellActionClass(THelpAction::class); - $this->addShellActionClass(TPhpShellAction::class); - $this->addShellActionClass(TActiveRecordAction::class); - - $this->_outWriter = new TShellWriter(new TOutputWriter()); + $this->_outWriter = new TShellWriter(new TStdOutWriter()); - $this->registerOption('quiet', [$this, 'setQuietMode'], 'Quiets the output to [1..3], default 1', '='); + $this->registerOption('quiet', [$this, 'setQuietMode'], 'Quiets the output to [1..3], default 1 (when specified)', '='); $this->registerOptionAlias('q', 'quiet'); $this->attachEventHandler('onInitComplete', [$this, 'processArguments'], 20); @@ -146,11 +142,13 @@ public static function detectCronTabShell() */ public function processArguments($sender, $param) { - $options = array_merge(['quiet' => [$this, 'setQuietMode']], $this->_options); - $aliases = array_merge(['q' => 'quiet'], $this->_optionAliases); + $this->installShellActions(); + + $options = $this->_options; + $aliases = $this->_optionAliases; $skip = false; foreach ($this->_arguments as $i => $arg) { - $arg = explode('=', $arg); + $arg = explode('=', $arg, 2); $processed = false; foreach ($options as $option => $setMethod) { $option = '--' . $option; @@ -174,6 +172,23 @@ public function processArguments($sender, $param) $this->_arguments = array_values($this->_arguments); } + /** + * Installs the shell actions. + * @since 4.2.3 + */ + public function installShellActions() + { + $this->addShellActionClass(TFlushCachesAction::class); + $this->addShellActionClass(THelpAction::class); + $this->addShellActionClass(TPhpShellAction::class); + $this->addShellActionClass(TActiveRecordAction::class); + + $app = Prado::getApplication(); + if ($app->getMode() === \Prado\TApplicationMode::Debug || TPropertyValue::ensureBoolean($app->getParameters()[TWebServerAction::DEV_WEBSERVER_PARAM])) { + $this->addShellActionClass(TWebServerAction::class); + } + } + /** * Runs the requested service. * @since 4.2.0 @@ -370,6 +385,7 @@ public function printHelp($outWriter) $outWriter->write("usage: "); $outWriter->writeLine("php prado-cli.php command[/action] [optional]", [TShellWriter::BLUE, TShellWriter::BOLD]); $outWriter->writeLine(); + $outWriter->writeLine("example: prado-cli http"); $outWriter->writeLine("example: php prado-cli.php cache/flush-all"); $outWriter->writeLine("example: prado-cli help"); $outWriter->writeLine("example: prado-cli cron/tasks"); diff --git a/framework/Shell/TShellWriter.php b/framework/Shell/TShellWriter.php index 8f551419a..dae4c74c4 100644 --- a/framework/Shell/TShellWriter.php +++ b/framework/Shell/TShellWriter.php @@ -9,6 +9,7 @@ namespace Prado\Shell; +use Prado\IO\TStdOutWriter; use Prado\TPropertyValue; use Prado\Util\Helpers\TProcessHelper; @@ -309,11 +310,27 @@ public function unformat($str) */ protected function isColorSupported() { - if (TProcessHelper::isSystemWindows()) { - return getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON'; + static $hasColor = null; + + if ($hasColor === null) { + if (TProcessHelper::isSystemWindows()) { + return getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON'; + } + + if ($hasStdOut = !defined('STDOUT')) { + $stdOut = fopen(TStdOutWriter::STDOUT_URI, 'wb'); + } else { + $stdOut = STDOUT; + } + + $hasColor = function_exists('posix_isatty') && @posix_isatty($stdOut) && strpos(getenv('TERM'), '256color') !== false; + + if ($hasStdOut) { + fclose($stdOut); + } } - return function_exists('posix_isatty') && @posix_isatty(STDOUT) && strpos(getenv('TERM'), '256color') !== false; + return $hasColor; } /** diff --git a/framework/Util/TStdOutLogRoute.php b/framework/Util/TStdOutLogRoute.php new file mode 100644 index 000000000..c38022fe1 --- /dev/null +++ b/framework/Util/TStdOutLogRoute.php @@ -0,0 +1,138 @@ + + * @link https://github.com/pradosoft/prado + * @license https://github.com/pradosoft/prado/blob/master/LICENSE + */ + +namespace Prado\Util; + +use Prado\Exceptions\TConfigurationException; +use Prado\Exceptions\TLogException; +use Prado\IO\TStdOutWriter; +use Prado\Prado; +use Prado\TPropertyValue; +use Prado\Shell\Actions\TWebServerAction; +use Prado\Shell\TShellWriter; + +/** + * TStdOutLogRoute class. + * + * This sends the log to STDOUT and will corrupt web server log files. This is useful + * in presenting PRADO logs in the PHP (Test and Development) Web Server output. + * + * The TStdOutLogRoute can be turned off for all but the built-in Test web server + * with the configuration property {@see \Prado\Util\TStdOutLogRoute::getOnlyDevServer} + * set to true. + * + * @author Brad Anderson + * @since 4.2.3 + */ +class TStdOutLogRoute extends TLogRoute +{ + /** @var bool Enable this log route only when running the built-in PHP web server, default false */ + private bool $_onlyDevServer = false; + + /** + * This returns if the route is enabled. When OnlyDevServer is true, then this route + * will only enable when running a page in the PHP Web Server. + * @return bool Is the route enabled, default true. + */ + public function getEnabled(): bool + { + $enabled = parent::getEnabled(); + + if ($this->getOnlyDevServer()) { + $enabled &= (int) getenv(TWebServerAction::DEV_WEBSERVER_ENV); + } + + return $enabled; + } + + /** + * @param array $logs list of log messages + * @param bool $final is the final flush + * @param array $meta the meta data for the logs. + * @throws TLogException When failing to write to syslog. + */ + protected function processLogs(array $logs, bool $final, array $meta) + { + $writer = new TShellWriter(new TStdOutWriter()); + + foreach ($logs as $log) { + $writer->write('[' . static::getLevelName($log[TLogger::LOG_LEVEL]) . ']', $this->levelColor($log[TLogger::LOG_LEVEL])); + $writer->writeLine($this->formatLogMessage($log)); + } + $writer->flush(); + } + + /** + * Translates a PRADO log level attribute into one understood by syslog + * @param int $level prado log level + * @return int syslog priority + */ + protected static function levelColor($level) + { + switch ($level) { + case TLogger::PROFILE: + case TLogger::PROFILE_BEGIN: + case TLogger::PROFILE_END: + return TShellWriter::BG_LIGHT_GREEN; + case TLogger::DEBUG: + return TShellWriter::BG_GREEN; + case TLogger::INFO: + return null; + case TLogger::NOTICE: + return TShellWriter::BG_BLUE; + case TLogger::WARNING: + return TShellWriter::BG_CYAN; + case TLogger::ERROR: + return TShellWriter::BG_LIGHT_MAGENTA; + case TLogger::ALERT: + return TShellWriter::BG_MAGENTA; + case TLogger::FATAL: + return TShellWriter::BG_RED; + default: + return TShellWriter::BG_GREEN; + } + } + + /** + * {@inheritdoc} + */ + public function formatLogMessage(array $log): string + { + if (!is_string($log[TLogger::LOG_MESSAGE])) { + if ($log[TLogger::LOG_MESSAGE] instanceof \Exception || $log[TLogger::LOG_MESSAGE] instanceof \Throwable) { + $log[TLogger::LOG_MESSAGE] = (string) $log[TLogger::LOG_MESSAGE]; + } else { + $log[TLogger::LOG_MESSAGE] = \Prado\Util\TVarDumper::dump($log[TLogger::LOG_MESSAGE]); + } + } + + return '[' . $log[TLogger::LOG_CATEGORY] . '] ' . $log[TLogger::LOG_MESSAGE]; + } + + /** + * @return bool If the Route is only enabled when operating in the PHP Test Web + * Server, default false. + */ + public function getOnlyDevServer(): bool + { + return $this->_onlyDevServer; + } + + /** + * + * @param bool|string $value Only enable the route when running in the PHP Test Web Server. + * @return static The current object. + */ + public function setOnlyDevServer($value): static + { + $this->_onlyDevServer = TPropertyValue::ensureBoolean($value); + + return $this; + } +} diff --git a/framework/classes.php b/framework/classes.php index d0dd7acb6..739d0a4ed 100644 --- a/framework/classes.php +++ b/framework/classes.php @@ -223,6 +223,7 @@ 'IModule' => 'Prado\IModule', 'ITextWriter' => 'Prado\IO\ITextWriter', 'TOutputWriter' => 'Prado\IO\TOutputWriter', +'TStdOutWriter' => 'Prado\IO\TStdOutWriter', 'TStreamNotificationCallback' => 'Prado\IO\TStreamNotificationCallback', 'TStreamNotificationParameter' => 'Prado\IO\TStreamNotificationParameter', 'TTarFileExtractor' => 'Prado\IO\TTarFileExtractor', @@ -258,6 +259,7 @@ 'TFlushCachesAction' => 'Prado\Shell\Actions\TFlushCachesAction', 'THelpAction' => 'Prado\Shell\Actions\THelpAction', 'TPphShellAction' => 'Prado\Shell\Actions\TPphShellAction', +'TWebServerAction' => 'Prado\Shell\Actions\TWebServerAction', 'TShellAction' => 'Prado\Shell\TShellAction', 'TShellApplication' => 'Prado\Shell\TShellApplication', 'TShellLoginBehavior' => 'Prado\Shell\TShellLoginBehavior', @@ -343,6 +345,7 @@ 'TSignalParameter' => 'Prado\Util\TSignalParameter', 'TSignalsDispatcher' => 'Prado\Util\TSignalsDispatcher', 'TSimpleDateFormatter' => 'Prado\Util\TSimpleDateFormatter', +'TStdOutLogRoute' => 'Prado\Util\TStdOutLogRoute', 'TSysLogRoute' => 'Prado\Util\TSysLogRoute', 'TUtf8Converter' => 'Prado\Util\TUtf8Converter', 'TVarDumper' => 'Prado\Util\TVarDumper',