*/
abstract class SqlFunction extends SqlExpression {
- /**
- * @var array
- */
- protected static $params = [];
-
/**
* @var array[]
*/
protected function initialize() {
$arg = trim(substr($this->expr, strpos($this->expr, '(') + 1, -1));
foreach ($this->getParams() as $idx => $param) {
- $prefix = $this->captureKeyword($param['prefix'], $arg);
+ $prefix = NULL;
+ if ($param['name']) {
+ $prefix = $this->captureKeyword([$param['name']], $arg);
+ // Supply api_default
+ if (!$prefix && isset($param['api_default'])) {
+ $this->args[$idx] = [
+ 'prefix' => [$param['name']],
+ 'expr' => array_map([parent::class, 'convert'], $param['api_default']['expr']),
+ 'suffix' => [],
+ ];
+ continue;
+ }
+ if (!$prefix && !$param['optional']) {
+ throw new \API_Exception("Missing {$param['name']} for SQL function " . static::getName());
+ }
+ }
+ elseif ($param['flag_before']) {
+ $prefix = $this->captureKeyword(array_keys($param['flag_before']), $arg);
+ }
$this->args[$idx] = [
- 'prefix' => $prefix,
+ 'prefix' => (array) $prefix,
'expr' => [],
- 'suffix' => NULL,
+ 'suffix' => [],
];
- if ($param['expr'] && isset($prefix) || in_array('', $param['prefix']) || !$param['optional']) {
- $this->args[$idx]['expr'] = $this->captureExpressions($arg, $param['expr'], $param['must_be'], $param['cant_be']);
- $this->args[$idx]['suffix'] = $this->captureKeyword($param['suffix'], $arg);
- }
- }
- }
+ if ($param['max_expr'] && (!$param['name'] || $param['name'] === $prefix)) {
+ $exprs = $this->captureExpressions($arg, $param['must_be'], TRUE);
+ if (count($exprs) < $param['min_expr'] || count($exprs) > $param['max_expr']) {
+ throw new \API_Exception('Incorrect number of arguments for SQL function ' . static::getName());
+ }
+ $this->args[$idx]['expr'] = $exprs;
- /**
- * Shift a keyword off the beginning of the argument string and return it.
- *
- * @param array $keywords
- * Whitelist of keywords
- * @param string $arg
- * @return mixed|null
- */
- private function captureKeyword($keywords, &$arg) {
- foreach (array_filter($keywords) as $key) {
- if (strpos($arg, $key . ' ') === 0) {
- $arg = ltrim(substr($arg, strlen($key)));
- return $key;
+ $this->args[$idx]['suffix'] = (array) $this->captureKeyword(array_keys($param['flag_after']), $arg);
}
}
- return NULL;
}
/**
- * Shifts 0 or more expressions off the argument string and returns them
+ * Change $dataType according to output of function
*
- * @param string $arg
- * @param int $limit
- * @param array $mustBe
- * @param array $cantBe
- * @return array
- * @throws \API_Exception
- */
- private function captureExpressions(&$arg, $limit, $mustBe, $cantBe) {
- $captured = [];
- $arg = ltrim($arg);
- while ($arg) {
- $item = $this->captureExpression($arg);
- $arg = ltrim(substr($arg, strlen($item)));
- $expr = SqlExpression::convert($item, FALSE, $mustBe, $cantBe);
- $this->fields = array_merge($this->fields, $expr->getFields());
- $captured[] = $expr;
- // Keep going if we have a comma indicating another expression follows
- if (count($captured) < $limit && substr($arg, 0, 1) === ',') {
- $arg = ltrim(substr($arg, 1));
- }
- else {
- break;
- }
- }
- return $captured;
- }
-
- /**
- * Scans the beginning of a string for an expression; stops when it hits delimiter
- *
- * @param $arg
+ * @see \Civi\Api4\Utils\FormattingUtil::formatOutputValues
+ * @param string $value
+ * @param string $dataType
* @return string
*/
- private function captureExpression($arg) {
- $chars = str_split($arg);
- $isEscaped = $quote = NULL;
- $item = '';
- $quotes = ['"', "'"];
- $brackets = [
- ')' => '(',
- ];
- $enclosures = array_fill_keys($brackets, 0);
- foreach ($chars as $index => $char) {
- if (!$isEscaped && in_array($char, $quotes, TRUE)) {
- // Open quotes - we'll ignore everything inside
- if (!$quote) {
- $quote = $char;
- }
- // Close quotes
- elseif ($char === $quote) {
- $quote = NULL;
- }
- }
- if (!$quote) {
- // Delineates end of expression
- if (($char == ',' || $char == ' ') && !array_filter($enclosures)) {
- return $item;
- }
- // Open brackets - we'll ignore delineators inside
- if (isset($enclosures[$char])) {
- $enclosures[$char]++;
- }
- // Close brackets
- if (isset($brackets[$char]) && $enclosures[$brackets[$char]]) {
- $enclosures[$brackets[$char]]--;
- }
- }
- $item .= $char;
- // We are escaping the next char if this is a backslash not preceded by an odd number of backslashes
- $isEscaped = $char === '\\' && ((strlen($item) - strlen(rtrim($item, '\\'))) % 2);
+ public function formatOutputValue($value, &$dataType) {
+ if (static::$dataType) {
+ $dataType = static::$dataType;
}
- return $item;
+ return $value;
}
/**
*/
public function render(array $fieldList): string {
$output = '';
- $params = $this->getParams();
- foreach ($this->args as $index => $arg) {
- $rendered = $this->renderArg($arg, $params[$index], $fieldList);
+ foreach ($this->args as $arg) {
+ $rendered = $this->renderArg($arg, $fieldList);
if (strlen($rendered)) {
$output .= (strlen($output) ? ' ' : '') . $rendered;
}
/**
* @param array $arg
- * @param array $param
* @param array $fieldList
* @return string
*/
- private function renderArg($arg, $param, $fieldList): string {
- // Supply api_default
- if (!isset($arg['prefix']) && !isset($arg['suffix']) && empty($arg['expr']) && !empty($param['api_default'])) {
- $arg = [
- 'prefix' => $param['api_default']['prefix'] ?? reset($param['prefix']),
- 'expr' => array_map([parent::class, 'convert'], $param['api_default']['expr'] ?? []),
- 'suffix' => $param['api_default']['suffix'] ?? reset($param['suffix']),
- ];
- }
- $rendered = $arg['prefix'] ?? '';
+ private function renderArg($arg, $fieldList): string {
+ $rendered = implode(' ', $arg['prefix']);
foreach ($arg['expr'] ?? [] as $idx => $expr) {
if (strlen($rendered) || $idx) {
$rendered .= $idx ? ', ' : ' ';
}
$rendered .= $expr->render($fieldList);
}
- if (isset($arg['suffix'])) {
- $rendered .= (strlen($rendered) ? ' ' : '') . $arg['suffix'];
+ if ($arg['suffix']) {
+ $rendered .= (strlen($rendered) ? ' ' : '') . implode(' ', $arg['suffix']);
}
return $rendered;
}
* Get the param metadata for this sql function.
* @return array
*/
- public static function getParams(): array {
+ final public static function getParams(): array {
$params = [];
- foreach (static::$params as $param) {
+ foreach (static::params() as $param) {
// Merge in defaults to ensure each param has these properties
$params[] = $param + [
- 'prefix' => [],
- 'expr' => 1,
- 'suffix' => [],
+ 'name' => NULL,
+ 'min_expr' => 1,
+ 'max_expr' => 1,
+ 'flag_before' => [],
+ 'flag_after' => [],
'optional' => FALSE,
- 'must_be' => [],
- 'cant_be' => ['SqlWild'],
+ 'must_be' => ['SqlField', 'SqlFunction', 'SqlString', 'SqlNumber', 'SqlNull'],
'api_default' => NULL,
];
}
return $params;
}
+ abstract protected static function params(): array;
+
/**
* Get the arguments passed to this sql function instance.
* @return array[]
*/
abstract public static function getTitle(): string;
+ /**
+ * @return string
+ */
+ abstract public static function getDescription(): string;
+
}