%PDF- %PDF-
Direktori : /www/varak.net/losik.varak.net/vendor/dibi/dibi/src/Dibi/ |
Current File : //www/varak.net/losik.varak.net/vendor/dibi/dibi/src/Dibi/Fluent.php |
<?php /** * This file is part of the Dibi, smart database abstraction layer (https://dibiphp.com) * Copyright (c) 2005 David Grudl (https://davidgrudl.com) */ declare(strict_types=1); namespace Dibi; /** * SQL builder via fluent interfaces. * * @method Fluent select(...$field) * @method Fluent distinct() * @method Fluent from($table, ...$args = null) * @method Fluent where(...$cond) * @method Fluent groupBy(...$field) * @method Fluent having(...$cond) * @method Fluent orderBy(...$field) * @method Fluent limit(int $limit) * @method Fluent offset(int $offset) * @method Fluent join(...$table) * @method Fluent leftJoin(...$table) * @method Fluent innerJoin(...$table) * @method Fluent rightJoin(...$table) * @method Fluent outerJoin(...$table) * @method Fluent as(...$field) * @method Fluent on(...$cond) * @method Fluent and(...$cond) * @method Fluent or(...$cond) * @method Fluent using(...$cond) * @method Fluent update(...$cond) * @method Fluent insert(...$cond) * @method Fluent delete(...$cond) * @method Fluent into(...$cond) * @method Fluent values(...$cond) * @method Fluent set(...$args) * @method Fluent asc() * @method Fluent desc() */ class Fluent implements IDataSource { use Strict; public const REMOVE = false; /** @var array */ public static $masks = [ 'SELECT' => ['SELECT', 'DISTINCT', 'FROM', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 'OFFSET', ], 'UPDATE' => ['UPDATE', 'SET', 'WHERE', 'ORDER BY', 'LIMIT'], 'INSERT' => ['INSERT', 'INTO', 'VALUES', 'SELECT'], 'DELETE' => ['DELETE', 'FROM', 'USING', 'WHERE', 'ORDER BY', 'LIMIT'], ]; /** @var array default modifiers for arrays */ public static $modifiers = [ 'SELECT' => '%n', 'FROM' => '%n', 'IN' => '%in', 'VALUES' => '%l', 'SET' => '%a', 'WHERE' => '%and', 'HAVING' => '%and', 'ORDER BY' => '%by', 'GROUP BY' => '%by', ]; /** @var array clauses separators */ public static $separators = [ 'SELECT' => ',', 'FROM' => ',', 'WHERE' => 'AND', 'GROUP BY' => ',', 'HAVING' => 'AND', 'ORDER BY' => ',', 'LIMIT' => false, 'OFFSET' => false, 'SET' => ',', 'VALUES' => ',', 'INTO' => false, ]; /** @var array clauses */ public static $clauseSwitches = [ 'JOIN' => 'FROM', 'INNER JOIN' => 'FROM', 'LEFT JOIN' => 'FROM', 'RIGHT JOIN' => 'FROM', ]; /** @var Connection */ private $connection; /** @var array */ private $setups = []; /** @var string|null */ private $command; /** @var array */ private $clauses = []; /** @var array */ private $flags = []; /** @var array|null */ private $cursor; /** @var HashMap normalized clauses */ private static $normalizer; public function __construct(Connection $connection) { $this->connection = $connection; if (self::$normalizer === null) { self::$normalizer = new HashMap([self::class, '_formatClause']); } } /** * Appends new argument to the clause. */ public function __call(string $clause, array $args): self { $clause = self::$normalizer->$clause; // lazy initialization if ($this->command === null) { if (isset(self::$masks[$clause])) { $this->clauses = array_fill_keys(self::$masks[$clause], null); } $this->cursor = &$this->clauses[$clause]; $this->cursor = []; $this->command = $clause; } // auto-switch to a clause if (isset(self::$clauseSwitches[$clause])) { $this->cursor = &$this->clauses[self::$clauseSwitches[$clause]]; } if (array_key_exists($clause, $this->clauses)) { // append to clause $this->cursor = &$this->clauses[$clause]; // TODO: really delete? if ($args === [self::REMOVE]) { $this->cursor = null; return $this; } if (isset(self::$separators[$clause])) { $sep = self::$separators[$clause]; if ($sep === false) { // means: replace $this->cursor = []; } elseif (!empty($this->cursor)) { $this->cursor[] = $sep; } } } else { // append to currect flow if ($args === [self::REMOVE]) { return $this; } $this->cursor[] = $clause; } if ($this->cursor === null) { $this->cursor = []; } // special types or argument if (count($args) === 1) { $arg = $args[0]; // TODO: really ignore true? if ($arg === true) { // flag return $this; } elseif (is_string($arg) && preg_match('#^[a-z:_][a-z0-9_.:]*\z#i', $arg)) { // identifier $args = [$clause === 'AS' ? '%N' : '%n', $arg]; } elseif (is_array($arg) || ($arg instanceof \Traversable && !$arg instanceof self)) { // any array if (isset(self::$modifiers[$clause])) { $args = [self::$modifiers[$clause], $arg]; } elseif (is_string(key($arg))) { // associative array $args = ['%a', $arg]; } } // case $arg === false is handled above } foreach ($args as $arg) { if ($arg instanceof self) { $arg = new Literal("($arg)"); } $this->cursor[] = $arg; } return $this; } /** * Switch to a clause. */ public function clause(string $clause): self { $this->cursor = &$this->clauses[self::$normalizer->$clause]; if ($this->cursor === null) { $this->cursor = []; } return $this; } /** * Removes a clause. */ public function removeClause(string $clause): self { $this->clauses[self::$normalizer->$clause] = null; return $this; } /** * Change a SQL flag. */ public function setFlag(string $flag, bool $value = true): self { $flag = strtoupper($flag); if ($value) { $this->flags[$flag] = true; } else { unset($this->flags[$flag]); } return $this; } /** * Is a flag set? */ final public function getFlag(string $flag): bool { return isset($this->flags[strtoupper($flag)]); } /** * Returns SQL command. */ final public function getCommand(): ?string { return $this->command; } final public function getConnection(): Connection { return $this->connection; } /** * Adds Result setup. */ public function setupResult(string $method): self { $this->setups[] = func_get_args(); return $this; } /********************* executing ****************d*g**/ /** * Generates and executes SQL query. * @return Result|int|null result set or number of affected rows * @throws Exception */ public function execute(?string $return = null) { $res = $this->query($this->_export()); switch ($return) { case \dibi::IDENTIFIER: return $this->connection->getInsertId(); case \dibi::AFFECTED_ROWS: return $this->connection->getAffectedRows(); default: return $res; } } /** * Generates, executes SQL query and fetches the single row. * @return Row|array|null */ public function fetch() { return $this->command === 'SELECT' && !$this->clauses['LIMIT'] ? $this->query($this->_export(null, ['%lmt', 1]))->fetch() : $this->query($this->_export())->fetch(); } /** * Like fetch(), but returns only first field. * @return mixed value on success, null if no next record */ public function fetchSingle() { return $this->command === 'SELECT' && !$this->clauses['LIMIT'] ? $this->query($this->_export(null, ['%lmt', 1]))->fetchSingle() : $this->query($this->_export())->fetchSingle(); } /** * Fetches all records from table. */ public function fetchAll(?int $offset = null, ?int $limit = null): array { return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->fetchAll(); } /** * Fetches all records from table and returns associative tree. * @param string $assoc associative descriptor */ public function fetchAssoc(string $assoc): array { return $this->query($this->_export())->fetchAssoc($assoc); } /** * Fetches all records from table like $key => $value pairs. */ public function fetchPairs(?string $key = null, ?string $value = null): array { return $this->query($this->_export())->fetchPairs($key, $value); } /** * Required by the IteratorAggregate interface. */ public function getIterator(?int $offset = null, ?int $limit = null): ResultIterator { return $this->query($this->_export(null, ['%ofs %lmt', $offset, $limit]))->getIterator(); } /** * Generates and prints SQL query or it's part. */ public function test(?string $clause = null): bool { return $this->connection->test($this->_export($clause)); } public function count(): int { return Helpers::intVal($this->query([ 'SELECT COUNT(*) FROM (%ex', $this->_export(), ') [data]', ])->fetchSingle()); } private function query($args): Result { $res = $this->connection->query($args); foreach ($this->setups as $setup) { $method = array_shift($setup); $res->$method(...$setup); } return $res; } /********************* exporting ****************d*g**/ public function toDataSource(): DataSource { return new DataSource($this->connection->translate($this->_export()), $this->connection); } /** * Returns SQL query. */ final public function __toString(): string { try { return $this->connection->translate($this->_export()); } catch (\Throwable $e) { trigger_error($e->getMessage(), E_USER_ERROR); return ''; } } /** * Generates parameters for Translator. */ protected function _export(?string $clause = null, array $args = []): array { if ($clause === null) { $data = $this->clauses; if ($this->command === 'SELECT' && ($data['LIMIT'] || $data['OFFSET'])) { $args = array_merge(['%lmt %ofs', $data['LIMIT'][0] ?? null, $data['OFFSET'][0] ?? null], $args); unset($data['LIMIT'], $data['OFFSET']); } } else { $clause = self::$normalizer->$clause; if (array_key_exists($clause, $this->clauses)) { $data = [$clause => $this->clauses[$clause]]; } else { return []; } } foreach ($data as $clause => $statement) { if ($statement !== null) { $args[] = $clause; if ($clause === $this->command && $this->flags) { $args[] = implode(' ', array_keys($this->flags)); } foreach ($statement as $arg) { $args[] = $arg; } } } return $args; } /** * Format camelCase clause name to UPPER CASE. * @internal */ public static function _formatClause(string $s): string { if ($s === 'order' || $s === 'group') { $s .= 'By'; trigger_error("Did you mean '$s'?", E_USER_NOTICE); } return strtoupper(preg_replace('#[a-z](?=[A-Z])#', '$0 ', $s)); } public function __clone() { // remove references foreach ($this->clauses as $clause => $val) { $this->clauses[$clause] = &$val; unset($val); } $this->cursor = &$foo; } }