Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.2.0 - Refactoring #71

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# This way, the files would be available in the repository but it would not be downloaded when the package is required by another project.
/.gitattributes export-ignore
/.github export-ignore
/tests export-ignore
/phpunit.xml export-ignore
47 changes: 47 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# .github/workflows/tests.yaml
name: Tests

on: ["push", "pull_request"]

jobs:
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.0', '8.1', '8.2', '8.3']
stability: [ prefer-lowest, prefer-stable ]
name: PHP ${{ matrix.php }} - ${{ matrix.stability }} tests

steps:
# basically git clone
- uses: actions/checkout@v4

- name: Shutdown Ubuntu MySQL (SUDO)
run: sudo service mysql stop

- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.composer/cache/files
key: dependencies-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}

# Install MySQL
- uses: shogo82148/actions-setup-mysql@v1
with:
mysql-version: "8.0"
- run: mysql -uroot -e 'SELECT version()'
- run: mysql -uroot -e 'CREATE SCHEMA IF NOT EXISTS savemysql'

# use PHP of specific version
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mysql, pdo, pdo_mysql
coverage: pcov

- name: Install dependencies
run: composer update --${{ matrix.stability }} --prefer-dist --no-interaction --no-progress --no-suggest

- name: Execute tests
run: vendor/bin/phpunit --testdox
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
/vendor/
.DS_Store
.vscode
.phpunit.result.cache
.phpunit.cache/test-results
composer.lock
124 changes: 62 additions & 62 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@
SafeMySQL
=========

SafeMySQL is a PHP class for safe and convenient handling of MySQL queries.
- Safe because <b>every</b> dynamic query part goes into the query via <b>placeholder</b>
- Convenient because it makes application code short and meaningful, without useless repetitions, making it ''extra'' <abbr title="Don't Repeat Yourself">DRY</abbr>

This class is distinguished by three main features
- Unlike standard libraries, it is using **type-hinted placeholders**, for the **everything** that may be put into the query
- Unlike standard libraries, it requires no repetitive binding, fetching and such,
thanks to set of helper methods to get the desired result right out of the query
- Unlike standard libraries, it can parse placeholders not in the whole query only, but in the arbitary query part,
thanks to the indispensabe **parse()** method, making complex queries as easy and safe as regular ones.

Yet, it is very easy to use. You need to learn only a few things:

1. You have to **always** pass whatever dynamical data into the query via *placeholder*
2. Each placeholder have to be marked with data type. At the moment there are six types:
* ?s ("string") - strings (also ```DATE```, ```FLOAT``` and ```DECIMAL```)
* ?i ("integer") - the name says it all
* ?n ("name") - identifiers (table and field names)
* ?a ("array") - complex placeholder for ```IN()``` operator (substituted with string of 'a','b','c' format, without parentesis)
* ?u ("update") - complex placeholder for ```SET``` operator (substituted with string of `field`='value',`field`='value' format)
* ?p ("parsed") - special type placeholder, for inserting already parsed statements without any processing, to avoid double parsing.
3. To get data right out of the query there are helper methods for the most used:
* query($query,$param1,$param2, ...) - returns mysqli resource.
* getOne($query,$param1,$param2, ...) - returns scalar value
* getRow($query,$param1,$param2, ...) - returns 1-dimensional array, a row
* getCol($query,$param1,$param2, ...) - returns 1-dimensional array, a column
* getAll($query,$param1,$param2, ...) - returns 2-dimensional array, an array of rows
* getInd($key,$query,$par1,$par2, ...) - returns an indexed 2-dimensional array, an array of rows
* getIndCol($key,$query,$par1,$par2, ...) - returns 1-dimensional array, an indexed column, consists of key => value pairs
4. For the whatever complex case always use the **parse()** method. And insert

The rest is as usual - just create a regular SQL (with placeholders) and get a result:

* ```$name = $db->getOne('SELECT name FROM table WHERE id = ?i',$_GET['id']);```
* ```$data = $db->getInd('id','SELECT * FROM ?n WHERE id IN (?a)','table', array(1,2));```
* ```$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i",$table,$mod,$limit);```

The main feature of this class is a <i>type-hinted placeholders</i>.
And it's a really great step further from just ordinal placeholders used in prepared statements.
Simply because <b>dynamical parts of the query aren't limited to just scalar data!</b>
In the real life we have to add identifiers, arrays for ```IN``` operator, and arrays for ```INSERT``` and ```UPDATE``` queries.
So - we need <b>many</b> different types of data formatting. Thus, we need the way to tell the driver how to format this particular data.
Conventional prepared statements use toilsome and repeating bind_* functions.
But there is a way more sleek and useful way - to set the type along with placeholder itself. It is not something new - well-known ```printf()``` function uses exactly the same mechanism. So, I hesitated not to borrow such a brilliant idea.

To implement such a feature, no doubt one have to have their own query parser. No problem, it's not a big deal. But the benefits are innumerable.
Look at all the questions on Stack Overflow where developers are trying in vain to bind a field name.
Voila - with the identifier placeholder it is as easy as adding a field value:
# SafeMySQL

English | [Русский](https://github.com/Impeck/safemysql/blob/master/README.ru.md)

SafeMySQL is a PHP class designed for secure and efficient MySQL query handling.

Forked from [colshrapnel/safemysql](https://github.com/colshrapnel/safemysql).

It stands out for several key features:

- **Safety:** All dynamic query parts are incorporated into the query using placeholders, enhancing security.
- **Convenience:** It streamlines application code, reducing redundancy, and following the DRY (Don't Repeat Yourself) principle.

## Features

SafeMySQL offers three primary features that distinguish it from standard libraries:

1. **Type-Hinted Placeholders:** Unlike traditional libraries, SafeMySQL employs type-hinted placeholders for all query elements.
2. **Streamlined Usage:** It eliminates the need for repetitive binding and fetching, thanks to a range of helper methods.
3. **Partial Placeholder Parsing:** SafeMySQL allows placeholder parsing in any part of the query, making complex queries as easy as standard ones through the **parse()** method.

## Getting Started

Using SafeMySQL is straightforward. Here are the key steps:

1. Always use placeholders for dynamic data in your queries.
2. Mark each placeholder with a data type, including:
- ?s ("string"): For strings (including `DATE`, `FLOAT`, and `DECIMAL`).
- ?i ("integer"): For integers.
- ?n ("name"): For identifiers (table and field names).
- ?a ("array"): For complex placeholders used with the `IN()` operator (substituted with a string in 'a,'b,'c' format, without parentheses).
- ?u ("update"): For complex placeholders used with the `SET` operator (substituted with a string in `field`='value',`field`='value' format).
- ?p ("parsed"): A special placeholder type for inserting pre-parsed statements without further processing to avoid double parsing.
3. Utilize helper methods to retrieve data from queries, including:
- `query($query, $param1, $param2, ...)`: Returns a mysqli resource.
- `getOne($query, $param1, $param2, ...)`: Returns a scalar value.
- `getRow($query, $param1, $param2, ...)`: Returns a 1-dimensional array (a row).
- `getCol($query, $param1, $param2, ...)`: Returns a 1-dimensional array (a column).
- `getAll($query, $param1, $param2, ...)`: Returns a 2-dimensional array (an array of rows).
- `getInd($key, $query, $par1, $par2, ...)`: Returns an indexed 2-dimensional array (an array of rows).
- `getIndCol($key, $query, $par1, $par2, ...)`: Returns a 1-dimensional array (an indexed column) consisting of key => value pairs.
4. For complex cases, rely on the **parse()** method.

### Example Usage

Here are some examples of how to use SafeMySQL:

```php
$name = $db->getOne('SELECT name FROM table WHERE id = ?i', $_GET['id']);
$data = $db->getInd('id', 'SELECT * FROM ?n WHERE id IN (?a)', 'table', [1, 2]);
$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i", $table, $mod, $limit);
```

The standout feature of SafeMySQL is its type-hinted placeholders. This approach extends beyond simple scalar data, allowing you to include identifiers, arrays for the `IN` operator, and arrays for `INSERT` and `UPDATE` queries. No more struggling with binding field names or constructing complex queries manually.

For instance, consider binding a field name effortlessly:

```php
$field = $_POST['field'];
$value = $_POST['value'];
$sql = "SELECT * FROM table WHERE ?n LIKE ?s";
$data = $db->query($sql,$field,"%$value%");
$data = $db->query($sql, $field, "%$value%");
```

Nothing could be easier!

Of course we will have placeholders for the common types - strings and numbers.
But as we started inventing new placeholders - let's make some more!

Another trouble in creating prepared queries - arrays going to the IN operator. Everyone is trying to do it their own way, but the type-hinted placeholder makes it as simple as adding a string:
Simplifying queries involving arrays for the `IN` operator:

```php
$array = array(1,2,3);
$data = $db->query("SELECT * FROM table WHERE id IN (?a)",$array);
$array = [1, 2, 3];
$data = $db->query("SELECT * FROM table WHERE id IN (?a)", $array);
```

The same goes for such toilsome queries like ```INSERT``` and ```UPDATE```.
The same convenience extends to complex queries like `INSERT` and `UPDATE`.

And, of course, we have a set of helper functions to turn type-hinted placeholders into real brilliant, making almost every call to the database as simple as one or two lines of code for all the regular real life tasks.
SafeMySQL also provides a set of helper functions, making database calls for everyday tasks quick and straightforward.
74 changes: 74 additions & 0 deletions README.ru.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# SafeMySQL

[English](https://github.com/Impeck/safemysql/blob/master/README.md) | Русский

SafeMySQL - это класс PHP, разработанный для безопасной и удобной обработки запросов к MySQL.

Форк [colshrapnel/safemysql](https://github.com/colshrapnel/safemysql).

Он выделяется несколькими ключевыми особенностями:

- **Безопасность:** Все динамические части запросов включаются в запрос с использованием заполнителей, повышая безопасность.
- **Удобство:** Он упрощает код приложения, уменьшая избыточность и следуя принципу DRY (Don't Repeat Yourself).

## Особенности

SafeMySQL предлагает три основных особенности, которые отличают его от стандартных библиотек:

1. **Заполнители с подсказками типов:** В отличие от традиционных библиотек, SafeMySQL использует заполнители с подсказками типов для всех элементов запроса.
2. **Упрощенное использование:** Это устраняет необходимость в повторном привязывании и извлечении данных благодаря набору вспомогательных методов.
3. **Частичный анализ заполнителей:** SafeMySQL позволяет анализировать заполнители не только во всем запросе, но и в любой его части, с помощью метода **parse()**, что делает выполнение сложных запросов так же простым и безопасным, как стандартные.

## Начало работы

Использование SafeMySQL просто. Вот ключевые шаги:

1. Всегда используйте заполнители для динамических данных в ваших запросах.
2. Помечайте каждый заполнитель типом данных, включая:
- ?s ("строка"): для строк (включая `DATE`, `FLOAT` и `DECIMAL`).
- ?i ("целое число"): для целых чисел.
- ?n ("имя"): для идентификаторов (имен таблиц и полей).
- ?a ("массив"): для сложных заполнителей, используемых с оператором `IN()` (заменяется строкой в формате 'a,'b,'c', без скобок).
- ?u ("обновление"): для сложных заполнителей, используемых с оператором `SET` (заменяется строкой в формате `поле`='значение',`поле`='значение').
- ?p ("разобранный"): специальный тип заполнителя для вставки предварительно разобранных выражений без дополнительной обработки для избегания двойного анализа.
3. Используйте вспомогательные методы для извлечения данных из запросов, включая:
- `query($query, $param1, $param2, ...)`: Возвращает ресурс mysqli.
- `getOne($query, $param1, $param2, ...)`: Возвращает скалярное значение.
- `getRow($query, $param1, $param2, ...)`: Возвращает одномерный массив (строку).
- `getCol($query, $param1, $param2, ...)`: Возвращает одномерный массив (столбец).
- `getAll($query, $param1, $param2, ...)`: Возвращает двумерный массив (массив строк).
- `getInd($key, $query, $par1, $par2, ...)`: Возвращает индексированный двумерный массив (массив строк).
- `getIndCol($key, $query, $par1, $par2, ...)`: Возвращает одномерный массив (индексированный столбец), состоящий из пар ключ => значение.
4. Для сложных случаев полагайтесь на метод **parse()**.

### Пример использования

Вот несколько примеров использования SafeMySQL:

```php
$name = $db->getOne('SELECT name FROM table WHERE id = ?i', $_GET['id']);
$data = $db->getInd('id', 'SELECT * FROM ?n WHERE id IN (?a)', 'table', [1, 2]);
$data = $db->getAll("SELECT * FROM ?n WHERE mod=?s LIMIT ?i", $table, $mod, $limit);
```

Основная особенность SafeMySQL - это заполнители с подсказками типов. Этот подход расширяется за пределы простых скалярных данных, позволяя включать идентификаторы, массивы для оператора `IN`, а также массивы для запросов `INSERT` и `UPDATE`. Забудьте о сложностях при привязке имен полей или создании сложных запросов вручную.

Например, рассмотрим привязку имени поля без усилий:

```php
$field = $_POST['field'];
$value = $_POST['value'];
$sql = "SELECT * FROM table WHERE ?n LIKE ?s";
$data = $db->query($sql, $field, "%$value%");
```

Упрощение запросов, связанных с массивами для оператора `IN`:

```php
$array = [1, 2, 3];
$data = $db->query("SELECT * FROM table WHERE id IN (?a)", $array);
```

Та же удобство распространяется и на сложные запросы, такие как `INSERT` и `UPDATE`.

SafeMySQL также предоставляет набор вспомогательных функций, которые делают вызовы к базе данных для повседневных задач быстрыми и простыми, сокращая их к одной или двум строкам кода.
38 changes: 26 additions & 12 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
{
"name": "colshrapnel/safemysql",
"description": "A real safe and convenient way to handle MySQL queries.",
"name": "impeck/safemysql",
"description": "PHP class designed for secure and efficient MySQL query handling",
"type": "library",
"license": "Apache-2.0",
"keywords": [
"db",
"mysql"
"mysql",
"safemysql",
"safe",
"secure",
"query",
"handling"
],
"homepage": "https://github.com/colshrapnel/safemysql",
"license": "Apache-2.0",
"autoload": {
"psr-4": {
"Impeck\\": "src/"
}
},
"authors": [
{
"name": "Colonel Shrapnel",
"email": "[email protected]",
"role": "lead"
"email": "[email protected]"
},
{
"name": "Impeck",
"email": "[email protected]"
}
],
"homepage": "https://github.com/Impeck/safemysql",
"support": {
"issues": "https://github.com/colshrapnel/safemysql/issues"
"issues": "https://github.com/Impeck/safemysql/issues"
},
"require": {
"php": ">=5.0.0"
"php": "^8.0",
"ext-mysqli": "*"
},
"autoload": {
"files": ["safemysql.class.php"]
"require-dev": {
"phpunit/phpunit": "^9.5"
}
}
}
23 changes: 23 additions & 0 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
backupGlobals="false"
backupStaticAttributes="false"
bootstrap="vendor/autoload.php"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd">
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
</coverage>
<testsuites>
<testsuite name="Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>
Loading