Compare commits

...

12 commits

Author SHA1 Message Date
73297feb82 fix: use isset() when checking table property value (#52)
We can't access the `$this->table` property before initialization, so let's use `isset()` to check if the property has a value.

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/52
2025-08-30 09:49:14 +02:00
0e367f797f fix: only set WHERE filters on delete() if conditions are provided (#51)
This PR fixes a bug where if no conditions are passed to `MySQL->from("table")->delete()`, the generated query will look like this:
```sql
DELETE FROM `table` WHERE
```

This is obviously invalid SQL syntax. This PR only adds the `WHERE` keyword and rules if conditions have been supplied to either `where()` or with `delete([])`

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/51
2025-07-29 09:46:46 +02:00
ddcd8a2961 fix: $offset not set when passing integer to $limit in limit() (#50)
This PR just sets the `OFFSET` value to `0` if no offset is provided for all calls to `limit()`. This fixes a bug where no `OFFSET` was set if the `$limit` parameter was given an integer.. which was the only allowed type except null anyways.

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/50
2025-07-17 11:09:02 +02:00
e062930c41 fix: add missing return statement from deprecated 'for()' method (#49)
Follow-up PR from #46. Forgot to return from the deprecated method.

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/49
2025-06-12 12:44:26 +02:00
vlw
814070a52e doc(fix): missed reference of "for()" to "from()" in README (#48)
Of course I missed to change one reference of `for()` to `from()`.

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/48
Co-authored-by: vlw <victor@vlw.se>
Co-committed-by: vlw <victor@vlw.se>
2025-06-12 12:44:10 +02:00
03868ae784 fix: MySQL->for() deprecation notice since version 3.5.7 (#47)
Wrong version referenced in the deprecation notice added in #46.

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/47
2025-06-08 12:32:38 +02:00
00cb7b3297
refactor: rename for() to from() for consistency with MySQL syntax (#46) 2025-06-08 12:01:21 +02:00
vlw
86f8f2ee76 doc: add short list of notable features as well as some style changes (#45)
Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/45
Co-authored-by: vlw <victor@vlw.se>
Co-committed-by: vlw <victor@vlw.se>
2025-06-08 11:50:38 +02:00
c64eb96049 fix: add proper Order Enum handling for MySQL->order() (#43)
I made a rushed merge with #41 and it doesn't work properly.. it throws an exception when passing an `Order` enum to the method, because we're only accepting strings. #42

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/43
2025-01-30 09:33:10 +00:00
e65c74797b feat: add ORDER BY statement Enum (#41)
Importable with:
```php
use vlw\MySQL\Order
```
To be used with the `MySQL->order()` method, for example:
```php
$db->for("table")->order(["column" => Order::ASC])->select("*");
```

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/41
2025-01-30 08:16:27 +00:00
64c7bae3cf feat: add protected array property for where statement columns (#40)
This PR adds a compliment for the `MySQL->filter_values` property but for filter columns which can be accessed from a protected scope with `MySQL->filter_columns`

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/40
2025-01-16 13:53:30 +00:00
d5f1efb9b9 feat: expose SQL class properties to protected scope (#39)
Makes sense to make these accessible when extending the MySQL class.

Reviewed-on: https://codeberg.org/vlw/php-mysql/pulls/39
2024-12-20 10:59:02 +00:00
3 changed files with 85 additions and 65 deletions

View file

@ -4,19 +4,19 @@ This is a simple abstraction library for MySQL DML operations.
For example: For example:
```php ```php
MySQL->for(string $table) MySQL->from(string $table)
->where(?array ...$conditions) ->where(?array ...$conditions)
->order(?array $order_by) ->order(?array $order_by)
->limit(int|array|null $limit) ->limit(?int $limit = null, ?int $offset = null)
->select(array $columns): array|bool; ->select(string|array|null $columns = null): mysqli_result|bool;
``` ```
which would be equivalent to the following in MySQL: which would be equivalent to the following in MySQL:
```sql ```sql
SELECT $columns FROM $table WHERE $filter ORDER BY $order_by LIMIT $limit; SELECT `columns` FROM `table` WHERE `filter` ORDER BY `order_by` LIMIT `limit`;
``` ```
> [!IMPORTANT] - All methods can be chained in any order (even multiple times) after a [`from()`](#from) as long as a [`select()`](#select), [`insert()`](#insert), [`update()`](#update), or [`delete()`](#delete) is the last method.
> This library requires the [`MySQL Improved`](https://www.php.net/manual/en/book.mysqli.php) extension and requires PHP 8.0 or newer. - Chaining the same method more than once will override its previous value. Passing `null` to any method that accepts it will unset its value completely.
## Install from composer ## Install from composer
@ -28,6 +28,9 @@ composer require vlw/mysql
use vlw\MySQL\MySQL; use vlw\MySQL\MySQL;
``` ```
> [!IMPORTANT]
> This library requires the [`MySQL Improved`](https://www.php.net/manual/en/book.mysqli.php) extension and PHP 8.0 or newer.
# Example / Documentation # Example / Documentation
Available statements Available statements
@ -60,30 +63,30 @@ $db = new MySQL($host, $user, $pass, $db);
All executor methods [`select()`](#select), [`update()`](#update), and [`insert()`](#insert) will return a [`mysqli_result`](https://www.php.net/manual/en/class.mysqli-result.php) object or boolean. All executor methods [`select()`](#select), [`update()`](#update), and [`insert()`](#insert) will return a [`mysqli_result`](https://www.php.net/manual/en/class.mysqli-result.php) object or boolean.
# FOR # FROM
```php ```php
MySQL->for( MySQL->from(
string $table string $table
): self; ): self;
``` ```
All queries start by chaining the `for(string $table)` method. This will define which database table the current query should be executed on. All queries start by chaining the `from(string $table)` method. This will define which database table the current query should be executed on.
*Example:* *Example:*
```php ```php
MySQL->for("beverages")->select("beverage_type"); MySQL->from("beverages")->select("beverage_type");
``` ```
# SELECT # SELECT
Chain `MySQL->select()` anywhere after a [`MySQL->for()`](#for) to retrieve columns from a database table. Chain `MySQL->select()` anywhere after a [`MySQL->from()`](#from) to retrieve columns from a database table.
Pass an associative array of strings, CSV string, or null to this method to filter columns. Pass an associative array of strings, CSV string, or null to this method to filter columns.
```php ```php
MySQL->select( MySQL->select(
array|string|null $columns string|array|null $columns
): mysqli_result|bool; ): mysqli_result|bool;
``` ```
@ -91,7 +94,7 @@ In most cases you probably want to select with a constraint. Chain the [`where()
### Example ### Example
```php ```php
$beverages = MySQL->for("beverages")->select(["beverage_name", "beverage_size"]); // SELECT beverage_name, beverage_size FROM beverages $`beverages` = MySQL->from("beverages")->select(["beverage_name", "beverage_size"]); // SELECT `beverage_name`, `beverage_size` FROM beverages
``` ```
``` ```
[ [
@ -109,7 +112,7 @@ $beverages = MySQL->for("beverages")->select(["beverage_name", "beverage_size"])
# INSERT # INSERT
Chain `MySQL->insert()` anywhere after a [`MySQL->for()`](#for) to append a new row to a database table. Chain `MySQL->insert()` anywhere after a [`MySQL->from()`](#from) to append a new row to a database table.
Passing a sequential array to `insert()` will assume that you wish to insert data for all defined columns in the table. Pass an associative array of `[column_name => value]` to INSERT data for specific columns (assuming the other columns have a [DEFAULT](https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html) value defined). Passing a sequential array to `insert()` will assume that you wish to insert data for all defined columns in the table. Pass an associative array of `[column_name => value]` to INSERT data for specific columns (assuming the other columns have a [DEFAULT](https://dev.mysql.com/doc/refman/8.0/en/data-type-defaults.html) value defined).
@ -117,20 +120,20 @@ Passing a sequential array to `insert()` will assume that you wish to insert dat
MySQL->insert( MySQL->insert(
// Array of values to INSERT // Array of values to INSERT
array $values array $values
): mysqli_result|bool ): bool
// Returns true if row was inserted // Returns true if row was inserted
``` ```
#### Example #### Example
```php ```php
MySQL->for("beverages")->insert([ MySQL->from("beverages")->insert([
null, null,
"coffee", "coffee",
"latte", "latte",
10 10
]); ]);
// INSERT INTO beverages VALUES (null, "coffee", "latte", 10); // INSERT INTO `beverages` VALUES (null, "coffee", "latte", 10);
``` ```
``` ```
true true
@ -138,12 +141,12 @@ true
# DELETE # DELETE
Chain `MySQL->delete()` anywhere after a [`MySQL->for()`](#for) to remove a row or rows from the a database table. Chain `MySQL->delete()` anywhere after a [`MySQL->from()`](#from) to remove a row or rows from the a database table.
```php ```php
MySQL->delete( MySQL->delete(
array ...$conditions array ...$conditions
): mysqli_result|bool ): bool
// Returns true if at least one row was deleted // Returns true if at least one row was deleted
``` ```
@ -152,13 +155,10 @@ This method takes at least one [`MySQL->where()`](#where)-syntaxed argument to d
#### Example #### Example
```php ```php
MySQL->for("beverages")->insert([ MySQL->from("beverages")->delete([
null, "beverage_name" => "coffee",
"coffee",
"latte",
10
]); ]);
// INSERT INTO beverages VALUES (null, "coffee", "latte", 10); // DELETE FROM `beverages` WHERE `beverage_name` = "coffee";
``` ```
``` ```
true true
@ -166,7 +166,7 @@ true
# UPDATE # UPDATE
Chain `MySQL->update()` anywhere after a [`MySQL->for()`](#for) to modify existing rows in a database table. Chain `MySQL->update()` anywhere after a [`MySQL->from()`](#from) to modify existing rows in a database table.
```php ```php
MySQL->update( MySQL->update(
@ -178,7 +178,7 @@ MySQL->update(
### Example ### Example
```php ```php
MySQL->for("beverages")->update(["beverage_size" => 10]); // UPDATE beverages SET beverage_size = 10 MySQL->from("beverages")->update(["beverage_size" => 10]); // UPDATE `beverages` SET `beverage_size` = 10
``` ```
```php ```php
true true
@ -201,7 +201,7 @@ MySQL->where(
### Example ### Example
```php ```php
$coffee = MySQL->for("beverages")->where(["beverage_type" => "coffee"])->select(["beverage_name", "beverage_size"]); // SELECT beverage_name, beverage_size FROM beverages WHERE (beverage_type = "coffee"); $coffee = MySQL->from("beverages")->where(["beverage_type" => "coffee"])->select(["beverage_name", "beverage_size"]); // SELECT `beverage_name`, `beverage_size` FROM `beverages` WHERE (`beverage_type` = "coffee");
``` ```
```php ```php
[ [
@ -229,7 +229,7 @@ MySQL->where([
]); ]);
``` ```
```sql ```sql
WHERE (beverage_type = 'coffee' AND beverage_size = 15) WHERE (`beverage_type` = 'coffee' AND `beverage_size` = 15)
``` ```
### OR ### OR
@ -250,7 +250,7 @@ $filter2 = [
MySQL->where($filter1, $filter2, ...); MySQL->where($filter1, $filter2, ...);
``` ```
```sql ```sql
WHERE (beverage_type = 'coffee' AND beverage_size = 15) OR (beverage_type = 'tea' AND beverage_name = 'black') WHERE (`beverage_type` = 'coffee' AND `beverage_size` = 15) OR (`beverage_type` = 'tea' AND `beverage_name` = 'black')
``` ```
## Define custom operators ## Define custom operators
@ -284,7 +284,7 @@ MySQL->order(
``` ```
```php ```php
$coffee = MySQL->for("beverages")->order(["beverage_name" => "ASC"])->select(["beverage_name", "beverage_size"]); // SELECT beverage_name, beverage_size FROM beverages ORDER BY beverage_name ASC $coffee = MySQL->from("beverages")->order(["beverage_name" => "ASC"])->select(["beverage_name", "beverage_size"]); // SELECT `beverage_name`, `beverage_size` FROM `beverages` ORDER BY `beverage_name` ASC
``` ```
```php ```php
[ [
@ -296,7 +296,7 @@ $coffee = MySQL->for("beverages")->order(["beverage_name" => "ASC"])->select(["b
"beverage_name" => "tea", "beverage_name" => "tea",
"beverage_size" => 15 "beverage_size" => 15
], ],
// ...etc for "beverage_name = coffee" // ...etc for "`beverage_name` = coffee"
] ]
``` ```
@ -315,7 +315,7 @@ MySQL->limit(
This will simply `LIMIT` the results returned to the integer passed This will simply `LIMIT` the results returned to the integer passed
```php ```php
$coffee = MySQL->for("beverages")->limit(1)->select(["beverage_name", "beverage_size"]); // SELECT beverage_name, beverage_size FROM beverages WHERE beverage_type = "coffee" LIMIT 1 $coffee = MySQL->from("beverages")->limit(1)->select(["beverage_name", "beverage_size"]); // SELECT `beverage_name`, `beverage_size` FROM `beverages` WHERE `beverage_type` = "coffee" LIMIT 1
``` ```
```php ```php
[ [
@ -330,7 +330,7 @@ $coffee = MySQL->for("beverages")->limit(1)->select(["beverage_name", "beverage_
This will `OFFSET` and `LIMIT` the results returned. The first argument will be the `LIMIT` and the second argument will be its `OFFSET`. This will `OFFSET` and `LIMIT` the results returned. The first argument will be the `LIMIT` and the second argument will be its `OFFSET`.
```php ```php
$coffee = MySQL->for("beverages")->limit(3, 2)->select(["beverage_name", "beverage_size"]); // SELECT beverage_name, beverage_size FROM beverages LIMIT 3 OFFSET 2 $coffee = MySQL->from("beverages")->limit(3, 2)->select(["beverage_name", "beverage_size"]); // SELECT `beverage_name`, `beverage_size` FROM `beverages` LIMIT 3 OFFSET 2
``` ```
```php ```php
[ [

View file

@ -8,22 +8,23 @@
use mysqli_stmt; use mysqli_stmt;
use mysqli_result; use mysqli_result;
use vlw\MySQL\Order;
use vlw\MySQL\Operators; use vlw\MySQL\Operators;
require_once "Order.php";
require_once "Operators.php"; require_once "Operators.php";
// Interface for MySQL_Driver with abstractions for data manipulation // Interface for MySQL_Driver with abstractions for data manipulation
class MySQL extends mysqli { class MySQL extends mysqli {
private string $table;
private ?string $limit = null;
private ?string $order_by = null;
private array $filter_values = [];
private ?string $filter_sql = null;
// Array of last SELECT-ed columns
public ?array $columns = null; public ?array $columns = null;
protected string $table;
protected ?string $limit = null;
protected ?string $order_by = null;
protected array $filter_columns = [];
protected array $filter_values = [];
protected ?string $filter_sql = null;
// Pass constructor arguments to driver // Pass constructor arguments to driver
function __construct() { function __construct() {
parent::__construct(...func_get_args()); parent::__construct(...func_get_args());
@ -34,7 +35,7 @@
*/ */
private function throw_if_no_table() { private function throw_if_no_table() {
if (!$this->table) { if (!isset($this->table)) {
throw new Exception("No table name defined"); throw new Exception("No table name defined");
} }
} }
@ -51,7 +52,11 @@
// Convert all boolean type values to tinyints in array // Convert all boolean type values to tinyints in array
private static function filter_booleans(array $values): array { private static function filter_booleans(array $values): array {
return array_map(fn($v): mixed => gettype($v) === "boolean" ? self::filter_boolean($v) : $v, $values); return array_map(fn(mixed $v): mixed => gettype($v) === "boolean" ? self::filter_boolean($v) : $v, $values);
}
private static function array_wrap_accents(array $input): array {
return array_map(fn(mixed $v): string => "`{$v}`", $input);
} }
/* /*
@ -61,7 +66,7 @@
*/ */
// Use the following table name // Use the following table name
public function for(string $table): self { public function from(string $table): self {
// Reset all definers when a new query begins // Reset all definers when a new query begins
$this->where(); $this->where();
$this->limit(); $this->limit();
@ -71,11 +76,20 @@
return $this; return $this;
} }
#[\Deprecated(
message: "use MySQL->from() instead",
since: "3.5.7",
)]
public function for(string $table): self {
return $this->from($table);
}
// Create a WHERE statement from filters // Create a WHERE statement from filters
public function where(?array ...$conditions): self { public function where(?array ...$conditions): self {
// Unset filters if null was passed // Unset filters if null was passed
if ($conditions === null) { if ($conditions === null) {
$this->filter_sql = null; $this->filter_sql = null;
$this->filter_columns = null;
$this->filter_values = null; $this->filter_values = null;
return $this; return $this;
@ -95,6 +109,8 @@
// Create SQL string and append values to array for prepared statement // Create SQL string and append values to array for prepared statement
foreach ($condition as $col => $operation) { foreach ($condition as $col => $operation) {
$this->filter_columns[] = $col;
// Assume we want an equals comparison if value is not an array // Assume we want an equals comparison if value is not an array
if (!is_array($operation)) { if (!is_array($operation)) {
$operation = [Operators::EQUALS->value => $operation]; $operation = [Operators::EQUALS->value => $operation];
@ -147,17 +163,8 @@
return $this; return $this;
} }
// Set LIMIT without range directly as integer // Coerce offset to zero if no offset is defined
if (is_int($limit)) { $offset = $offset ?? 0;
$this->limit = $limit;
return $this;
}
// No offset defined, set limit property directly as string
if (is_null($offset)) {
$this->limit = (string) $limit;
return $this;
}
// Set limit and offset as SQL CSV // Set limit and offset as SQL CSV
$this->limit = "{$offset},{$limit}"; $this->limit = "{$offset},{$limit}";
@ -172,12 +179,12 @@
return $this; return $this;
} }
// Create CSV from columns // Assign Order Enum entries from array of arrays<Order|string>
$sql = implode(",", array_keys($order_by)); $orders = array_map(fn(Order|string $order): Order => $order instanceof Order ? $order : Order::tryFrom($order), array_values($order_by));
// Create pipe DSV from values // Create CSV string with Prepared Statement abbreviations from length of fields array.
$sql .= " " . implode("|", array_values($order_by)); $sql = array_map(fn(string $column, Order|string $order): string => "`{$column}` " . $order->value, array_keys($order_by), $orders);
$this->order_by = $sql; $this->order_by = implode(",", $sql);
return $this; return $this;
} }
@ -194,7 +201,7 @@
$this->columns = is_array($columns) || is_null($columns) ? $columns : explode(",", $columns); $this->columns = is_array($columns) || is_null($columns) ? $columns : explode(",", $columns);
// Create CSV from columns or default to SQL NULL as a string // Create CSV from columns or default to SQL NULL as a string
$columns_sql = $this->columns ? implode(",", $this->columns) : "NULL"; $columns_sql = $this->columns ? implode(",", self::array_wrap_accents($this->columns)) : "NULL";
// Create LIMIT statement if argument is defined // Create LIMIT statement if argument is defined
$limit_sql = !is_null($this->limit) ? " LIMIT {$this->limit}" : ""; $limit_sql = !is_null($this->limit) ? " LIMIT {$this->limit}" : "";
@ -216,7 +223,7 @@
$this->throw_if_no_table(); $this->throw_if_no_table();
// Create CSV string with Prepared Statement abbreviations from length of fields array. // Create CSV string with Prepared Statement abbreviations from length of fields array.
$changes = array_map(fn($column) => "{$column} = ?", array_keys($entity)); $changes = array_map(fn($column) => "`{$column}` = ?", array_keys($entity));
$changes = implode(",", $changes); $changes = implode(",", $changes);
// Get array of SQL WHERE string and filter values // Get array of SQL WHERE string and filter values
@ -261,9 +268,14 @@
$this->throw_if_no_table(); $this->throw_if_no_table();
// Set DELETE WHERE conditions from arguments // Set DELETE WHERE conditions from arguments
$this->where(...$conditions); if ($conditions) {
$this->where(...$conditions);
}
$sql = "DELETE FROM `{$this->table}` WHERE {$this->filter_sql}"; // Get array of SQL WHERE string and filter values
$filter_sql = !is_null($this->filter_sql) ? " WHERE {$this->filter_sql}" : "";
$sql = "DELETE FROM `{$this->table}`{$filter_sql}";
return $this->execute_query($sql, self::to_list_array($this->filter_values)); return $this->execute_query($sql, self::to_list_array($this->filter_values));
} }

8
src/Order.php Normal file
View file

@ -0,0 +1,8 @@
<?php
namespace vlw\MySQL;
enum Order: string {
case ASC = "ASC";
case DESC = "DESC";
}