pQL

Ti

Новичок
Привет всем.

Я делаю еще одну ORM для PHP.
Называется она pQL.
Название произошло от SQL где в первую букву съел синий слонёнок PHP.

Делаю я это умышленно, другие решения имеют весомые недостатки:
- избыточный синтаксис запросов
- сложная инициализация, конфигурация

Упор в разработке — на простоту использования.

Библиотека собрана в один файл pql-версия.php
Последнюю версию можно забрать с http://ti.y1.ru/pql
Код расположен на гитхабе: https://github.com/Ti-webdev/pQL/
Mini how-to: https://github.com/Ti-webdev/pQL/wiki

Возможности: [beta]
- Лаконичный jQuery-like API запросов SELECT, UPDATE, DELETE
- Фильтры (именованные группы условий [с параметрами], группа условий по умолчанию)
- Жадные выборки по &ссылке
- Автоматические JOIN-ы таблиц
- Выбор поля ключ/значение для ассоциативного результата выборки: $id=>$value
- Получение схемы из базы данных

Разработано через тестирование

Поддержка
PHP 5.1+
MySQL (mysql.so || PDO)
SQLite (PDO)

Как использовать?
1. Подключить библиотеку
2. Определить функцию с инициализацией, например такую:
PHP:
function db() {
	static $creater;
	if (!$creater) {
		// при первом вызове
		// подключаемся к базе
		$dbh = new PDO('mysql:host=localhost;dbname=test', 'test', 'test');

		// инициализируем pQL
		$pQL = pQL::PDO($dbh);
		$creater = $pQL->creater();
	}
	return $creater;
}
3. enjoy!

Enjoy:
PHP:
// создание
$user = db()->user();
$user->login = 'guest';
$user->save();

// обновление
$user->login = 'bugmenot';
$user->save();

// by ID
$user5 = db()->user(5);

// LIMIT 1
$firstUser = db()->user->one();

// SELECT id
$allUserIdArray = db()->user->id->toArray();

// key=>value
foreach(db()->user->id->key()->login as $id=>$login) {
   echo "$id = $login\n";
}

// жадная выборка по &ссылке и автоматический JOIN
foreach(db()->user->db()->role->bind($role) as $user) {
   echo "$user->name: $role->name\n";
}

// добавим фильтр по-умолчанию, пользователей он-лайн, ко всем запросам на таблицу user
db()->user->online->in('yes')->filter();

// следующие запросы идут этим с фильтром на борту
foreach(db()->user->name as $onlineUserName) {
   echo "$onlineUser\n";
}

// Именованный фильтр
pql()->news->status->in(1)->filter('published'); // создание фильтра
$firstPublishedNews = pql()->news->published()->one(); // использование фильтра

// Именованный фильтр с параметрами
db()->user->filter('page', function($query, $limit, $page = 1) {
   $query->limit($limit)->offset($limit * ($page - 1));
});

// в результате до 10 пользователей начиная с 30-го
foreach(db()->user->page(10, 3) as $user) {
   echo "$user->name\n";
}



Ликбез

1. Создатель
PHP:
$query = $creater->table;
$query = $creater->table($record); // см. ниже, $query->with
$record = $creater->table($id);
$newRecord = $creater->table();

2. Объект запроса

Реализует интерфейсы:
IteratorAggregate - что бы бегать с foreach
Countable - что бы знать сколько можно пробежать
ArrayAccess - что бы знать что поле или таблица существует (offsetUnset - заглушка, кидает RuntimeException)

Обращаясь к свойствам объекта запроса вы выбираете таблицу, или, если она выбрана, выбираете поле таблицы. Объект запроса меняет свое состояние и возвращает себя:
PHP:
$query = db()->user;
$query instanceof pQL_Query; // true
$query->id instanceof pQL_Query;  // true
$query === $query->id;  // true
$query->id === $query->name;  // true
$query instanceof pQL_Query;  // true

// Метод db() очищает выбранное поле и выбранную таблицу;
$query->db()->table1->field1;
$query->db()->table;
$query->field;

// Unbind отвязывает (привязанную по ссылке) переменную от запроса
$query->unbind($var);

// Методы, которые относятся к запросу
$query->limit($int);
$query->offset($int);
$query->one(); // выполнит запрос с LIMIT 1 и вернет первую запись
$count =  $query->update(); // выполняет UPDATE
$count = $query->delete(); // выполняет DELETE

$query->with($recordObject); // добавляет объект одной записи в условие, и JOIN при необходимости
// пример:
$query = db()->user($role); // короткий синтаксис, тоже самое
$query = db()->user->with($role); // пользователи только c привилегией $role

// Методы, которые применяются к таблице, и, если поле не указано
$query->bind($var);  // привязывает по ссылке переменную к объекту записи
$query->value(); // объект этой таблицы будет в значениях


// Методы, которые относятся к полю:
$query->bind($var); // привязывает по ссылке переменную к значению поля
$query->key(); // этот поле будет в ключах
$query->value(); // это поле будет в значениях
// так же работает короткий синтаксис
// когда последнее обращение в объекте запроса - поле:
foreach(db()->user->name as $userName) {}


// WHERE. Объединяются оператором AND
$query->in($array); // field IN ($array) или $field = reset($array), если одно значение
$query->in($value1[[, $value2][, $value3], ...]); // field IN ($array) или, если одно значение $field = reset($array)

$query->not($array); // field NOT IN ($array) или, если одно значение $field <> reset($array)
$query->not($value1[[, $value2][, $value3], ...]);

$query->lt($value);  // field < $value
$query->lte($value); // field <= $value

$query->gt($value);  // field > $value
$query->gte($value);  // field >= $value

$query->between($value1, $value2);  // field BETWEEN(value1, value2)

$query->like($expr); // field LIKE $expr
$query->notLike($expr); // field NOT LIKE $expr

// ORDER BY
$query->asc(); // ORDER BY field
$query->desc(); // ORDER BY field DESC

// GROUP BY
$query->group(); // GROUP BY field

// UPDATE
$query->set($value); // SET field = $value

3. Объект записи
PHP:
// установка свойства
$object->set($key, $value);
$object->$key = $value;
$object[$key] = $value;

// получение свойства
$object->get($key);
$object->$key;
$object[$key];

// сохранение измененных свойств (INSERT для нового или UPDATE для существующего)
$object->save();

// удаление
$object->delete();

Здесь мог быть ваш пример:
PHP:
$query = db()->user; // запрос к таблице user
$query->name; // выбор поля name
$query->id; // выбор поля id
$query->asc(); // по-возрастанию
$query->in(1,2,3,4); // WHERE user.id IN (1,2,3,4)
$query->db()->role; // Переключение на таблицу role. (при необходимости будет выполнен JOIN)
$query->id; // поле id таблицы role 

$query->not(1,2,3,4); // WHERE ... role.id NOT IN (1,2,3,4)
$query->not(null); // WHERE ... role.id NOT NULL
$query->lt(100); // WHERE ... role.id < 100
$query->lte(99); // WHERE ... role.id <= 99
$query->gt(10); // WHERE ... role.id > 10
$query->offset(50);
$query->limit(30);
$query->desc(); // по убыванию

// Тоже самое
$query = 
db()->user
               ->name
               ->id
                      ->in(1,2,3,4)
                      ->asc()
->db()->role
              ->id
                      ->not(1,2,3,4,null)
                      ->lt(100)
                      ->lte(99)
                      ->gt(10)
                      ->offset(50)
                      ->limit(30)
                      ->desc();

echo $query; /*
SELECT ... 
FROM user, role
WHERE user.role_id = role.id
   AND user.id IN (1,2,3,4)
   AND role.id NOT IN (1,2,3,4)
   AND role.id IS NOT NULL
   AND role.id < 100
   AND role.id <= 99
   AND role.id > 10
ORDER BY user.id, role.id DESC
LIMIT 50, 30
*/

На сколько это интересно не мне одному?
Предложения, ваши ворчания и вопросы оставляйте ниже (мы обязательно зададим их людям на улице)
 

Adelf

Administrator
Команда форума
А свои классы то можно писать, например для модели user? Для кастомизации некоторых вещей?
 

Ti

Новичок
А свои классы то можно писать, например для модели user? Для кастомизации некоторых вещей?
Можно:
PHP:
<?php
class MyApp_Definer implements pQL_Object_Definer_Interface {
	function getObject(pQL $pQL, $modelName, $properties) {
		$resultClass = 'MyApp_'.ucfirst($modelName);
		if (class_exists($resultClass)) return new $resultClass($pQL, $properties);
		return new pQL_Object_Model($pQL, $properties, $modelName);
	}
}


function db() {
    static $creater;
    if (!$creater) {
		// инициализация pQL
		// ...
		$pQL->objectDefinder(new MyApp_Definer);;
        $creater = $pQL->creater();
    }
    return $creater;
}


abstract class MyApp_Object extends pQL_Object {
   function getModel() {
      return substr(get_class($this), strlen('MyApp_'));
   }
}


class MyApp_User extends MyApp_Object {
	
}

db->user() instanceof MyApp_User; // true
 

varan

Б̈́̈̽ͮͣ̈Л̩̲̮̻̤̹͓ДͦЖ̯̙̭̥̑͆А͇̠̱͓͇̾ͨД͙͈̰̳͈͛ͅ
Код:
// key=>value
foreach(db()->user->id->key()->login as $id=>$login)

foreach(db()->user->db()->role->bind($role) as $user)

db()->user->online->in('yes')->filter();
Имхо нечитабельно. Если убрать комментарии, то какие-то шарады получились
 

tz-lom

Продвинутый новичок
Вам стоит написать подробную справку о том какие методы что делают , и добить phpDoc , та же магия с key() никак не прокомментирована (а в примере оно выглядит именно как магия)
так же неплохо было бы кешировать структуры таблиц хотя бы в контексте класса (а в идеале дать возможность перегрузки методов кеширования или сразу реализовать кеши на диск с последующей подгрузкой)
 

Ti

Новичок
какие-то шарады получились
а почему?

Вам стоит написать подробную справку о том какие методы что делают , и добить phpDoc , та же магия с key() никак не прокомментирована (а в примере оно выглядит именно как магия)
так же неплохо было бы кешировать структуры таблиц хотя бы в контексте класса (а в идеале дать возможность перегрузки методов кеширования или сразу реализовать кеши на диск с последующей подгрузкой)
Кеш структуры есть в драйвере, объект кеша можно менять на свой.
Согласен, мануал и подробный phpDoc нужен.
 

varan

Б̈́̈̽ͮͣ̈Л̩̲̮̻̤̹͓ДͦЖ̯̙̭̥̑͆А͇̠̱͓͇̾ͨД͙͈̰̳͈͛ͅ
Откуда я знаю, почему? Если не верите, попросите "людей с улицы" угадать что могут означать эти строчки, узнаете много нового :)
Как писал Роберт Мартин, читабельность кода - это самое важное
 

Ti

Новичок
Откуда я знаю, почему? Если не верите, попросите "людей с улицы" угадать что могут означать эти строчки, узнаете много нового :)
Как писал Роберт Мартин, читабельность кода - это самое важное
А давай, спросим.
Первый попавшийся на глаза кусок кода с production:
PHP:
$user = db()->User->email->in($_POST['email'])->one();
if ($user) {
	$user->sendRestorePasswordEmail();
	$template = 'restore/ok.html';
}
else {
	$template = 'restore/fail.html';
}
Вопрос для слушателей. Что делает этот код?
 

craz

Нестандартное звание
А давай, спросим.
Первый попавшийся на глаза кусок кода с production:
PHP:
$user = db()->User->email->in($_POST['email'])->one();
if ($user) {
	$user->sendRestorePasswordEmail();
	$template = 'restore/ok.html';
}
else {
	$template = 'restore/fail.html';
}
Вопрос для слушателей. Что делает этот код?
беспонятия, может спрашивает у базы данных ее пользователя? и просит у него мыло? потом что то в пост сохраняет? один? один, совсем один?
 

tz-lom

Продвинутый новичок
А давай, спросим.
Первый попавшийся на глаза кусок кода с production:
PHP:
$user = db()->User->email->in($_POST['email'])->one();
if ($user) {
	$user->sendRestorePasswordEmail();
	$template = 'restore/ok.html';
}
else {
	$template = 'restore/fail.html';
}
Вопрос для слушателей. Что делает этот код?
Выбирает из базы первого пользователя у которого email == $_POST['email'] , возвращает что то типа false 0 или NULL если не вышло
НО:
1е - хз нужно ли экранировать $_POST['email']
2е - хз что в $user потом будет,какие поля итд
 

varan

Б̈́̈̽ͮͣ̈Л̩̲̮̻̤̹͓ДͦЖ̯̙̭̥̑͆А͇̠̱͓͇̾ͨД͙͈̰̳͈͛ͅ
Да не, этот пример еще ничего, а вот db()->user->db()->role->bind($role) может ввести в ступор многих
 

Ti

Новичок
Выбирает из базы первого пользователя у которого email == $_POST['email'] , возвращает что то типа false 0 или NULL если не вышло
НО:
1е - хз нужно ли экранировать $_POST['email']
2е - хз что в $user потом будет,какие поля итд
1е. ORM предполагает автоматическую экранизацию данных
2е. в $user будет объект записи таблицы user, со всеми полями

Да не, этот пример еще ничего, а вот db()->user->db()->role->bind($role) может ввести в ступор многих
да, пример с привязкой переменной не тривиален, но согласись, делает он не тривиальную вещь
короткий туториал, вначале этой темы, поможет без проблем читать такие конструкции
 
Сверху