runArray($values); } public function retype(...$bindings): bool { return $this->retypeArray($bindings); } public static function mungeQuery(string $query, array $types, ...$extraData): string { return $query; } public function retypeArray(array $bindings, bool $append = false): bool { if (!$append) { $this->types = []; } foreach ($bindings as $binding) { if (is_array($binding)) { // recursively flatten any arrays, which may be provided for SET or IN() clauses $this->retypeArray($binding, true); } else { $binding = trim(strtolower($binding)); if (strpos($binding, "strict ")===0) { // "strict" types' values may never be null; null values will later be cast to the type specified $this->isNullable[] = false; $binding = substr($binding, 7); } else { $this->isNullable[] = true; } if (!array_key_exists($binding, self::TYPES)) { throw new Exception("paramTypeInvalid", $binding); // @codeCoverageIgnore } $this->types[] = self::TYPES[$binding]; } } if (!$append) { $this->prepare(static::mungeQuery($this->query, $this->types)); } return true; } protected function cast($v, string $t, bool $nullable) { switch ($t) { case "datetime": $v = Date::transform($v, "sql"); if (is_null($v) && !$nullable) { $v = 0; $v = Date::transform($v, "sql"); } return $v; case "integer": return ValueInfo::normalize($v, ValueInfo::T_INT | ($nullable ? ValueInfo::M_NULL : 0), null, "sql"); case "float": return ValueInfo::normalize($v, ValueInfo::T_FLOAT | ($nullable ? ValueInfo::M_NULL : 0), null, "sql"); case "binary": case "string": return ValueInfo::normalize($v, ValueInfo::T_STRING | ($nullable ? ValueInfo::M_NULL : 0), null, "sql"); case "boolean": $v = ValueInfo::normalize($v, ValueInfo::T_BOOL | ($nullable ? ValueInfo::M_NULL : 0), null, "sql"); return is_null($v) ? $v : (int) $v; default: throw new Exception("paramTypeUnknown", $type); // @codeCoverageIgnore } } protected function bindValues(array $values, int $offset = null): int { $a = (int) $offset; foreach ($values as $value) { if (is_array($value)) { // recursively flatten any arrays, which may be provided for SET or IN() clauses $a += $this->bindValues($value, $a); } elseif (array_key_exists($a, $this->types)) { $value = $this->cast($value, $this->types[$a], $this->isNullable[$a]); $this->bindValue($value, $this->types[$a], ++$a); } else { throw new Exception("paramTypeMissing", $a+1); } } // once the last value is bound, check that all parameters have been supplied values and bind null for any missing ones // SQLite will happily substitute null for a missing value, but other engines (viz. PostgreSQL) produce an error if (is_null($offset)) { while ($a < sizeof($this->types)) { $this->bindValue(null, $this->types[$a], ++$a); } } return $a - $offset; } }