kael-shipman / php-executables
一套小型、抽象的可执行类和外设
Requires
- ext-sockets: *
- kael-shipman/php-std-traits: ^6.0.0 || ^7.0.0
Requires (Dev)
- phpunit/phpunit: ^6.0.0
This package is auto-updated.
Last update: 2024-01-10 14:25:41 UTC
README
已废弃
在持续的时间里这很有趣,但完全没有理由用PHP而不是Go或TypeScript来做可执行文件。如果还有人对此感兴趣,欢迎接管,但我已经继续前进。
似乎有一些尝试使PHP中的守护进程化变得容易。我没有深入研究这些,所以我不确定我通过添加另一个这样的工具是否真正对社区做出了贡献,但我仍然自私地想要尝试。
这个库包含创建通用可执行文件的类。我将可执行文件定义为可以在命令行上调用,通常带有命令行参数的应用程序。该应用程序可能是一个守护进程,它在后台运行直到被杀死,或者它可能是一个单次运行的作业应用程序,执行一个作业然后退出。
这些的主要功能如下
- 轻松处理命令行参数
- 处理多通道日志的标准方式
- 并行处理的标准和简单方式
这些都不是特别困难的任务。正因为如此,使用这样的框架能获得多少好处并不明确。同时,如果您构建了大量的应用程序,将它们都按照相同的方式构建是有意义的,因此使用一个(小型)框架来满足这些要求是合理的。
基本假设
这里的主要基本假设是您的运行时应用程序代码应与您的功能库分开。因此,这些库中类的任何扩展都应仅实现初始化功能依赖和分发命令所需的最少代码。它们还应通过在此库中定义的标准渠道与用户和日志保持通信。
使用方法
要创建一个可执行文件,您需要将此库作为composer依赖项包含到您的项目中,然后创建至少两个文件。
第一个是实际的入口点——一个引导文件,在Web领域中相当于index.php
。它将处理您的所有配置逻辑,包括命令行参数等,您可以根据自己的意愿进行操作(尽管我推荐commando)。
接下来,您需要创建一个扩展此库中某个抽象类的类。该类将是您的实际运行时应用程序,其run
方法应由您的引导文件调用。它应该看起来像这样
引导文件
<?php namespace KS; // src/bootstrap-cli.php $root = __DIR__; require_once "$root/vendor/autoload.php"; try { $cmd = new \Commando\Command(); $cmd->option('c') ->aka('config-file') ->describedAs("The path defining the daemon's config file"); // Get a config file path, or use default if (!$cmd['c']) { if (!($home = getenv("HOME"))) { $home = "/root"; } $defaultOverride = true; $configFile = "$home/.config/ks/exchange-daemon.conf"; } else { $defaultOverride = false; $configFile = realpath($cmd['c']); } // See notes on configuration below $config = require $configFile; $exec = new MyExec(); $exec->run($config); } catch (\Exception $e) { fwrite(STDERR, $e->getMessage()); } catch (\Throwable $e) { fwrite(STDERR, "Uh oh... Looks like an unrecoverable error :(. ".$e->getMessage()."\n\n"); }
可执行文件
<?php namespace KS; // src/MyExec.php class MyExec extends AbstractExecutable { public function run(array $config = []) { $this->checkConfig($config); do { // Run application } while (true) } protected function checkConfig(array $config) { // Do configuration checks here } }
配置
配置似乎是每个人都以不同的方式解决的问题之一。对于Web应用程序,我的倾向是使用一个提供配置选项定义接口的配置类。这些类通常通过提供受版本控制的“默认”配置文件config.php
和不受版本控制的、实例特定的config.local.php
来构建,后者覆盖默认值。这两个文件都返回键值配置对的数组。这对于Web环境来说效果很好,因为Web服务器是静态的“用户”。
然而,在更具交互性的环境中,您可能至少有3个配置来源
- 默认配置定义;
- 一个或多个用户特定的配置文件(例如,一个
config.d/
目录);以及 - 可选的命令行覆盖。
但这并不暗示有根本不同的方法:您将创建一个定义良好的配置接口(例如,$config->getRunDir()
、$config->getLogIdentifier()
、$config->getLogLevel()
),然后通过传递一个配置数组来填充它,这个数组简单地是所有给定配置来源的合并。从理论上讲,您应该在实例化时检查配置的完整性。
在实践中,这可能是这样的
// Static, default config $config = require __DIR__.'/default-config.php'; // Merge in the global config file $globalConfig = '/etc/my-exec/config'; if (!file_exists($globalConfig)) { throw new \RuntimeException("You must provide a global configuration file at `$globalConfig`"); } $config = array_replace_recursive($config, require $globalConfig); // Merge in user-defined config files $userConfig = []; if (is_dir("$globalConfig.d")) { $d = dir("$globalConfig.d"); while (($f = $d->read()) !== false) { if ($f[0] === '.') { continue; } $userConfig = "$globalConfig.d/$f"; } } sort($userConfig); foreach($userConfig as $c) { $config = array_replace_recursive($config, require $c); } // Merge in command-line arguments (we're ignore how to get those for now) $config = array_replace_recursive($config, $commandLineArgs); $config = new MyConfig($config); $config->checkConfiguration();