/src/Automation/ServiceUtils/Options.php
PHP | 397 lines | 222 code | 46 blank | 129 comment | 47 complexity | 404819946703bbcae4e63a3f9a4bba03 MD5 | raw file
- <?php
- namespace Fie\Automation\ServiceUtils;
- use Fie\Automation\Service;
- final class Options
- {
- private $processed = false;
- private $knownOptionsGlobal = ['help', 'verbose', 'debug'];
- private $knownOptionsGlobalAPI = ['help', 'verbose'];
- private $knownOptionsBySection = [];
- private $options = [];
- private $flagValues = [];
- public function __construct() {
- if (Service::isApi()) {
- $this->knownOptionsGlobal = $this->knownOptionsGlobalAPI;
- }
- }
- /**
- * @return array
- */
- public function getKnownOptionsGlobal() {
- return $this->knownOptionsGlobal;
- }
- /**
- * @return array
- */
- final public static function getArgv() {
- $argv = array_key_exists('argv', $GLOBALS) ? $GLOBALS['argv'] : [];
- if (count($argv) > 0 && strpos($argv[0], 'phpunit') > -1) {
- return [];
- }
- if (count($argv) > 1 && strpos($argv[1], 'phpunit') > -1) {
- return [];
- }
- return $argv;
- }
- /**
- * Return raw assoc array of service options.
- *
- * @return array
- */
- public function getRawOptions() {
- return $this->options;
- }
- /**
- * Return raw assoc array of service flags.
- *
- * @return array
- */
- public function getRawFlagValues() {
- return $this->flagValues;
- }
- /**
- * @param array $knownOptionsBySection
- * @return Options $this
- */
- public function setKnownOptions(array $knownOptionsBySection = []) {
- $this->knownOptionsBySection = $knownOptionsBySection;
- return $this;
- }
- /**
- * Parse all web query string key/value pairs and command line arguments into one array.
- *
- * @param array|null $options
- * @throws OptionsException
- * @return Options $this
- */
- public function processOptions(?array $options = null) {
- if ($this->processed) {
- return $this;
- }
- $this->processed = true;
- if (isset($options)) {
- $params = [];
- foreach ($options as $option => $value) {
- if (strlen($value)) {
- $params[] = "--$option=$value";
- } else {
- $params[] = "--$option";
- }
- }
- $this->parseCommandLineOptions($params);
- } else {
- $argv = self::getArgv(); # assign to variable because array_splice() is destructive.
- if (Service::isPhpUnit()) {
- $commandLineOptions = $argv;
- } else {
- # drop script_name and service_name. Assumes script_name is not executable (e.g. `php script_name service_name`)
- $commandLineOptions = count($argv) > 1 ? array_splice($argv, 2) : []; // @codeCoverageIgnore
- }
- $queryStringOptions = $this->parseWebQueryStringIntoCliParameters();
- $this->parseCommandLineOptions(array_merge($queryStringOptions, $commandLineOptions));
- if (!array_key_exists('verbose', $this->flagValues)) {
- $this->flagValues['verbose'] = 0;
- }
- if ($this->getOptionFlag('debug')) {
- # debug implies verbose
- $this->options['verbose'] = null;
- $this->flagValues['verbose']++;
- }
- }
- if (!$this->getOptionFlag('help')) {
- $this->ensureKnownOptions();
- }
- $this->sanitizeOptionValues();
- return $this;
- }
- /**
- * Get value of option.
- *
- * @param string $optionName
- * @param string|null $default
- * @return string
- */
- public function getOption(string $optionName, ?string $default = null) {
- $optionName = strtolower($optionName);
- return array_key_exists($optionName, $this->options) ? $this->options[$optionName] : $default;
- }
- /**
- * Get boolean value of option.
- *
- * @param string $optionName
- * @param bool|null $default
- * @return bool|null
- */
- public function getOptionBoolean(string $optionName, ?bool $default = null) {
- $optionName = strtolower($optionName);
- $bool = array_key_exists($optionName, $this->options) ? strtolower($this->options[$optionName]) : null;
- if ($bool === 'true') {
- return true;
- } elseif ($bool === 'false') {
- return false;
- } else {
- return $default;
- }
- }
- /**
- * Get bool value of a given option.
- *
- * @param string $flagName
- * @return bool
- */
- public function getOptionFlag(string $flagName) {
- $flagName = strtolower($flagName);
- return array_key_exists($flagName, $this->options);
- }
- /**
- * Get the integer of a given option.
- *
- * @param string $flagName
- * @return int
- */
- public function getOptionFlagValue(string $flagName) {
- $flagName = strtolower($flagName);
- return array_key_exists($flagName, $this->flagValues) ? $this->flagValues[$flagName] : 0;
- }
- /**
- * Check service options for a given flag name and exit with error if it does not exist.
- *
- * @param string $flagName
- * @throws OptionsException
- * @return bool
- */
- public function requireFlag(string $flagName) {
- $flagName = strtolower($flagName);
- if (!array_key_exists($flagName, $this->options)) {
- $this->missingFlag([$flagName]);
- }
- return true;
- }
- /**
- * Check service options for a given option name and exit with error if value is missing.
- *
- * @param string $optionName
- * @param string $optionValuePlaceholder
- * @throws OptionsException
- * @return string|int value of option
- */
- public function requireOption(string $optionName, string $optionValuePlaceholder = 'VALUE') {
- $optionName = strtolower($optionName);
- if (!array_key_exists($optionName, $this->options) || strlen($this->options[$optionName]) === 0) {
- $this->missingOption([$optionName], $optionValuePlaceholder);
- }
- return $this->options[$optionName];
- }
- /**
- * Check service options and exit with error if value is missing
- *
- * @param array $optionNames
- * @param string $optionValuePlaceholder
- * @throws OptionsException
- * @return string|int value of option
- */
- public function requireOneOption(array $optionNames, string $optionValuePlaceholder = 'VALUE') {
- $lcOptionNames = array_map('strtolower', $optionNames);
- $lcOptionNames = array_filter($lcOptionNames); # remove empty elements
- $lcOneOption = array_filter($lcOptionNames, function ($optionName) {
- return !empty($this->options[$optionName]);
- });
- $lcOneOption = array_values($lcOneOption); # reindex
- if (count($lcOneOption) != 1) {
- $this->missingOption($optionNames, $optionValuePlaceholder);
- }
- /** @var string[] $lcOneOption */
- return $this->options[ $lcOneOption[0] ];
- }
- /**
- * Report missing flag.
- *
- * @param array $flagNames
- * @throws OptionsException
- */
- private function missingFlag(array $flagNames) {
- throw new OptionsException("Missing flag '" . join("' or '", $flagNames) . "'");
- }
- /**
- * Report missing option.
- *
- * @param array $optionNames
- * @param string $optionValue
- * @throws OptionsException
- */
- private function missingOption(array $optionNames, string $optionValue) {
- throw new OptionsException("Missing key/value pair " . join("=$optionValue or ", $optionNames) . "=$optionValue");
- }
- /**
- * PHP's getopt() will stop parsing command line arguments when it encounters something that is't defined by
- * its $options or $longopts. This one doesn't care and we'll ensure options are defined later in ensureKnownOptions()
- *
- * Parses $GLOBALS['argv'] (or other array, i.e. $_GET) for parameters and assigns them to an array.
- *
- * Supports:
- * -e
- * -e <value>
- * --long-param
- * --long-param=<value>
- * --long-param <value>
- * <value>
- *
- * @param array $params List of parameters to parse
- * @return Options $this
- */
- private function parseCommandLineOptions(array $params) {
- $options = [];
- $flagValues = [];
- for ($position = 0; $position < count($params); $position++) {
- $original = $params[$position];
- $descriptor = $params[$position];
- if ($descriptor{0} != '-') {
- # param doesn't belong to any option. `<value>`
- $options[] = $original;
- continue;
- }
- $descriptor = substr($descriptor, 1);
- $value = null;
- if ($descriptor{0} == '-') {
- # long-opt. `--<param>`
- $descriptor = substr($descriptor, 1);
- if (strpos($original, '=') !== false) {
- # value specified inline. `--<param>=<value>`
- list($descriptor, $value) = explode('=', $descriptor, 2);
- if ($value === '') {
- $value = null;
- }
- }
- }
- # check if next parameter is a descriptor or a value. long param `--<param>` or short param `-<param>`
- if ($value === null && array_key_exists($position+1, $params) && $params[$position+1]{0} != '-') {
- $value = $params[++$position];
- }
- $descriptor = strtolower($descriptor);
- $options[$descriptor] = $value;
- if ($value === null) {
- @$flagValues[$descriptor]++; # suppress warning, index will be defined if it doesn't exist.
- }
- }
- $this->options = $options;
- $this->flagValues = $flagValues;
- return $this;
- }
- /**
- * Convert query string into command line arguments
- *
- * @return array
- */
- private function parseWebQueryStringIntoCliParameters() {
- $queryString = array_key_exists('QUERY_STRING', $_SERVER) ? $_SERVER['QUERY_STRING'] : '';
- $options = [];
- if (strlen($queryString) == 0) {
- return $options;
- }
- $kvPairs = explode('&', $queryString);
- foreach ($kvPairs as $kvPair) {
- $kvPair = strpos($kvPair, '=') > -1 ? explode('=', $kvPair) : [$kvPair, ''];
- list($key, $value) = $kvPair;
- $key = ltrim($key, '-');
- if (strlen($key) == 1) {
- $options[] = "-$key";
- } else {
- if (strlen($value) > 0) {
- $value = "=$value";
- }
- $options[] = "--$key$value";
- }
- }
- return $options;
- }
- /**
- * Fail if we encounter an unknown option.
- *
- * @throws OptionsException
- * @return Options $this
- */
- private function ensureKnownOptions() {
- if (count($this->knownOptionsBySection) == 0) {
- return $this;
- }
- $knownOptions = [];
- foreach ($this->knownOptionsBySection as $sectionName => $options) {
- foreach ($options as $optionName => $optionDesc) {
- if (!is_int($optionName) && is_string($optionDesc)) {
- $optionName = preg_replace('/=.*$/', '', $optionName);
- $knownOptions[] = strtolower($optionName);
- }
- }
- }
- $knownOptions = array_unique(array_merge($this->knownOptionsGlobal, $knownOptions));
- foreach (array_keys($this->options) as $optionName) {
- if (strlen($optionName) > 0 && !in_array($optionName, $knownOptions)) {
- throw new OptionsException(sprintf("Unknown option '%s'", $optionName));
- }
- }
- return $this;
- }
- /**
- * Manipulate values of options to be a single string without whitespace for safe use in shell commands.
- *
- * @return $this
- */
- private function sanitizeOptionValues() {
- foreach ($this->options as $descriptor => $value) {
- $value = filter_var($value, FILTER_SANITIZE_STRING);
- while (preg_match('/%\d\d/', $value)) {
- $value = urldecode($value);
- }
- $value = trim($value);
- $value = preg_replace('/ .*$/', '', $value);
- $this->options[$descriptor] = $value;
- }
- return $this;
- }
- }