From ce947e74adaf60f7db1aff3d0864fd9820d033aa Mon Sep 17 00:00:00 2001 From: John Robinson Date: Sat, 5 Oct 2024 06:53:42 -0400 Subject: [PATCH] Added orWhere method (#9) --- CHANGELOG.md | 6 ++ docs/query-builder.md | 27 ++++++- src/Query.php | 178 ++++++++++++++++++++++++++---------------- 3 files changed, 142 insertions(+), 69 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19ab028..1bbc089 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Fixed` for any bug fixes. - `Security` in case of vulnerabilities +## [5.1.0] - 2024.10.05 + +### Added + +- Added `orWhere` method. + ## [5.0.0] - 2024.09.16 ### Added diff --git a/docs/query-builder.md b/docs/query-builder.md index 7b9ce96..c3854be 100644 --- a/docs/query-builder.md +++ b/docs/query-builder.md @@ -30,6 +30,7 @@ $query = new Query($pdo); // $pdo as a PDO instance - [rightjoin](#rightjoin) - [select](#select) - [where](#where) +- [orWhere](#orwhere) - [orderBy](#orderby) - [orderByRand](#orderbyrand) - [limit](#limit) @@ -156,7 +157,7 @@ JSON fields which do not exist are returned with a value of `null`. **Description:** -Adds a `WHERE` clause to the query. +Adds a `WHERE/AND WHERE` clause to the query. If the column type is `JSON`, keys from within the JSON string can be searched with the format of `COLUMN->KEY`. JSON fields which do not exist are treated as `null`. @@ -205,6 +206,30 @@ The `VALUE_*` constants can be used for this purpose.
+### orWhere + +**Description:** + +Adds an `OR/AND OR` clause to the query. + +See [where](#where). + +**Parameters:** + +- `$column` (string) +- `$operator` (string) +- `$value` (mixed) + +**Returns:** + +- (self) + +**Throws:** + +- `Bayfront\SimplePdo\Exceptions\QueryException` + +
+ ### orderBy **Description:** diff --git a/src/Query.php b/src/Query.php index d204e5d..fde1591 100644 --- a/src/Query.php +++ b/src/Query.php @@ -267,53 +267,24 @@ private function parseConditionColumn(string $column): string public const VALUE_TRUE = 'true'; public const VALUE_FALSE = 'false'; + private const CONDITION_AND = 'AND'; + private const CONDITION_OR = 'OR'; + /** - * Adds a WHERE clause to the query. - * - * If the column type is JSON, keys from within the JSON string can be searched with the format of COLUMN->KEY. - * JSON fields which do not exist are treated as null. - * - * Available operators are: - * - * - eq (equals) - * - !eq (does not equal) - * - lt (less than) - * - gt (greater than) - * - le (less than or equal to) - * - ge (greater than or equal to) - * - sw (starts with) - * - !sw (does not start with) - * - ew (ends with) - * - !ew (does not end with) - * - has (has) - * - !has (does not have) - * - in (in) - * - !in (not in) - * - null (is or is not null) - * - * The OPERATOR_* constants can be used for this purpose. - * - * The in and !in operators accept multiple comma-separated values. - * - * The "null" operator accepts two values: true and false for is null or is not null. - * The VALUE_* constants can be used for this purpose. - * - * NOTE: Some native MySQL functions can be used as the $value, however, they will be - * injected into the query as strings, so they can be vulnerable to SQL injection. - * + * @param string $condition (and/or) * @param string $column * @param string $operator - * @param mixed $value - * @return self + * @param $value + * @return void * @throws QueryException */ - public function where(string $column, string $operator, mixed $value): self + private function addCondition(string $condition, string $column, string $operator, $value): void { if (!isset($this->query[self::QUERY_WHERE])) { - $this->query[self::QUERY_WHERE] = ' WHERE '; + $condition = ' WHERE ('; } else { - $this->query[self::QUERY_WHERE] .= ' AND '; + $condition = ' ' . $condition . ' ('; } if (!in_array($operator, [ @@ -356,78 +327,80 @@ public function where(string $column, string $operator, mixed $value): self // Check operators + $placeholders = []; + switch ($operator) { case self::OPERATOR_STARTS_WITH: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' LIKE ' . $value; + $condition .= $column . ' LIKE ' . $value; break; } - $this->placeholders[] = $value . '%'; - $this->query[self::QUERY_WHERE] .= $column . ' LIKE ?'; + $placeholders[] = $value . '%'; + $condition .= $column . ' LIKE ?'; break; case self::OPERATOR_DOES_NOT_START_WITH: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' NOT LIKE ' . $value; + $condition .= $column . ' NOT LIKE ' . $value; break; } - $this->placeholders[] = $value . '%'; - $this->query[self::QUERY_WHERE] .= $column . ' NOT LIKE ?'; + $placeholders[] = $value . '%'; + $condition .= $column . ' NOT LIKE ?'; break; case self::OPERATOR_ENDS_WITH: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' LIKE ' . $value; + $condition .= $column . ' LIKE ' . $value; break; } - $this->placeholders[] = '%' . $value; - $this->query[self::QUERY_WHERE] .= $column . ' LIKE ?'; + $placeholders[] = '%' . $value; + $condition .= $column . ' LIKE ?'; break; case self::OPERATOR_DOES_NOT_END_WITH: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' NOT LIKE ' . $value; + $condition .= $column . ' NOT LIKE ' . $value; break; } - $this->placeholders[] = '%' . $value; - $this->query[self::QUERY_WHERE] .= $column . ' NOT LIKE ?'; + $placeholders[] = '%' . $value; + $condition .= $column . ' NOT LIKE ?'; break; case self::OPERATOR_HAS: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' LIKE ' . $value; + $condition .= $column . ' LIKE ' . $value; break; } - $this->placeholders[] = '%' . $value . '%'; - $this->query[self::QUERY_WHERE] .= $column . ' LIKE ?'; + $placeholders[] = '%' . $value . '%'; + $condition .= $column . ' LIKE ?'; break; case self::OPERATOR_DOES_NOT_HAVE: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' NOT LIKE ' . $value; + $condition .= $column . ' NOT LIKE ' . $value; break; } - $this->placeholders[] = '%' . $value . '%'; - $this->query[self::QUERY_WHERE] .= $column . ' NOT LIKE ?'; + $placeholders[] = '%' . $value . '%'; + $condition .= $column . ' NOT LIKE ?'; break; case self::OPERATOR_IN: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' IN (' . $value . ')'; + $condition .= $column . ' IN (' . $value . ')'; break; } @@ -437,18 +410,18 @@ public function where(string $column, string $operator, mixed $value): self foreach ($in_values as $val) { - $this->placeholders[] = $val; + $placeholders[] = $val; } - $this->query[self::QUERY_WHERE] .= $column . ' IN (' . $in . ')'; + $condition .= $column . ' IN (' . $in . ')'; break; case self::OPERATOR_NOT_IN: if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' NOT IN (' . $value . ')'; + $condition .= $column . ' NOT IN (' . $value . ')'; break; } @@ -458,11 +431,11 @@ public function where(string $column, string $operator, mixed $value): self foreach ($in_values as $val) { - $this->placeholders[] = $val; + $placeholders[] = $val; } - $this->query[self::QUERY_WHERE] .= $column . ' NOT IN (' . $in . ')'; + $condition .= $column . ' NOT IN (' . $in . ')'; break; @@ -470,11 +443,11 @@ public function where(string $column, string $operator, mixed $value): self if ($value == self::VALUE_TRUE) { - $this->query[self::QUERY_WHERE] .= $column . ' IS NULL'; + $condition .= $column . ' IS NULL'; } else if ($value == self::VALUE_FALSE) { - $this->query[self::QUERY_WHERE] .= $column . ' IS NOT NULL'; + $condition .= $column . ' IS NOT NULL'; } else { @@ -488,23 +461,92 @@ public function where(string $column, string $operator, mixed $value): self if ($value == '') { // Empty string needs no placeholder - $this->query[self::QUERY_WHERE] .= $column . " " . $operator . " ''"; + $condition .= $column . " " . $operator . " ''"; } else if ($this->is_function($value)) { - $this->query[self::QUERY_WHERE] .= $column . ' ' . $operator . ' ' . $value; + $condition .= $column . ' ' . $operator . ' ' . $value; } else { - $this->placeholders[] = $value; - $this->query[self::QUERY_WHERE] .= $column . ' ' . $operator . ' ?'; + $placeholders[] = $value; + $condition .= $column . ' ' . $operator . ' ?'; } } + if (!isset($this->query[self::QUERY_WHERE])) { + $this->query[self::QUERY_WHERE] = $condition . ')'; + } else { + $this->query[self::QUERY_WHERE] .= $condition . ')'; + } + + $this->placeholders = array_merge($this->placeholders, $placeholders); + + } + + /** + * Adds a WHERE/AND WHERE clause to the query. + * + * If the column type is JSON, keys from within the JSON string can be searched with the format of COLUMN->KEY. + * JSON fields which do not exist are treated as null. + * + * Available operators are: + * + * - eq (equals) + * - !eq (does not equal) + * - lt (less than) + * - gt (greater than) + * - le (less than or equal to) + * - ge (greater than or equal to) + * - sw (starts with) + * - !sw (does not start with) + * - ew (ends with) + * - !ew (does not end with) + * - has (has) + * - !has (does not have) + * - in (in) + * - !in (not in) + * - null (is or is not null) + * + * The OPERATOR_* constants can be used for this purpose. + * + * The in and !in operators accept multiple comma-separated values. + * + * The "null" operator accepts two values: true and false for is null or is not null. + * The VALUE_* constants can be used for this purpose. + * + * NOTE: Some native MySQL functions can be used as the $value, however, they will be + * injected into the query as strings, so they can be vulnerable to SQL injection. + * + * @param string $column + * @param string $operator + * @param mixed $value + * @return self + * @throws QueryException + */ + public function where(string $column, string $operator, mixed $value): self + { + $this->addCondition(self::CONDITION_AND, $column, $operator, $value); return $this; + } + /** + * Adds an OR/AND OR clause to the query. + * + * See where(). + * + * @param string $column + * @param string $operator + * @param mixed $value + * @return $this + * @throws QueryException + */ + public function orWhere(string $column, string $operator, mixed $value): self + { + $this->addCondition(self::CONDITION_OR, $column, $operator, $value); + return $this; } /**