Утилита. Небольшая библиотека на php для работы с базами данных

October 10, 2007Comments Off on Утилита. Небольшая библиотека на php для работы с базами данных

Наверное, каждый программист проходит через этап “изобретения велосипеда”. Когда мы создаем собственные аналоги уже существующих систем, решений, библиотек и т.д. Причины, по которой мы это делаем, - самые различные. Самым худшим из них, наверное, является то, когда мы не в состоянии разобраться в некоторой технологии или библиотеке в сердцах махаем на нее руками и говорим: “какой лох это только мог сделать?”. Конечно, бывает так что тот продукт на который мы махаем руками действительно сделан не руками, а другими конечностями растущими из противоположной голове части тела. Но все же и гораздо чаще проблема в нас. Как говорят мудрые китайцы: “если ты не нравишься сам себе – измени себя, если тебе не нравятся окружающие тебя люди – снова изменяй сам себя”. Еще частой причиной является сложность системы, ее избыточная универсальность – это вполне нормальное состояние, когда тебе не нужна это пачка из тысячи и одной функции, которыми ты все равно не будешь пользоваться, и ты не готов платить за эту безделицу риском познакомиться с очередным багом или головоломной фишкой (feature). Стремясь угодить всем разработчики такой системы не могут угодить никому. Как тут не вспомнить классическое: “за двумя зайцами погнавшись – ни одного не поймаешь”. Написание собственного движка, библиотеки, фреймворка – это показатель культурной, моральной и технической зрелости. Только тогда когда ты съел не один пуд соли, работая с пачкой разномастных систем, когда ты можешь четко сформулировать причину по которой “я не буду больше пользоваться этой дрянью – я сделаю наново и свое”. Если ты можешь четко сформулировать то, какая экономия времени и твоих нервов будет достигнута. Если ты делаешь продукт с холодным мозгом и ледяной кровью, а не потому, что у Васи Пупкина из соседнего подъезда есть свой 3d-движок и четыре движка cms для сайта - а у тебя ни одного; и Ленка из соседней квартиры поэтому считает тебя лохом. Ты должен быть готов к тому, что на стадии работы реализации движка придется пересмотреть свои представления, плюнуть на потраченные месяцы работы и не тянуть воз возникших из-за неверно спланированной архитектуры багов дальше в светлое будущее. Если ты прежде чем сесть за клавиатуру взял лист бумаги, цветные фломастеры и нарисовал много разномастных кружков и квадратиков, показывающих как будет работать твоя библиотека. Если от слова документирование и комментирование кода тебя не клонит в сон. Если ты понимаешь, что ни один продукт не работает в вакууме и независимо от той чертовой кучи софта, который ты или твоя команда использует или планирует использовать в ближайшие год-два. Если ты можешь представить то, как созданная тобой наработка плавно и без матюгов войдет в отлаженный процесс или его улучшит. Если ты представляешь, что созданное тобою будет развиваться, шлифоваться и ты можешь найти еще хотя бы десяток человек которым понравится придуманная тобой идея. То только тогда ты можешь засучить рукава и сесть за клавиатуру. И это всего лишь необходимое условие, но не достаточное чтобы что-то из твоих потуг родилось хоть что-то.

Почти три года назад я написал собственную библиотеку-надстройку над стандартными расширениями php для доступа к базе mysql. Признаюсь сразу, что начинал я работу не потому, что меня не устраивали возможности mysql-расширения php. Тогда я с ним был знаком достаточно слабо, хотя был очень хорошо знаком с java и используемой в ней jdbc. По всем признакам jdbc api организован архитектурно лучше, чем его собрат в мире php. Но, имея опыт написания достаточно больших приложений и интенсивно работающих с базами, приложениями для которых каждое утро начиналось со слов “рефакторинг”, “все дрянь – переделай наново”, “заказчик был пьян и перепутал, что же он хочет на самом деле”. Я искал решения в java с помощью sqlj - и это мне нравилось. Я пробовал hibernate и castor - и это мне тоже нравилось. В конце концов, у меня было достаточно времени, чтобы экспериментировать с доступными библиотеками и искать свой путь.

Я начал с того, что сформулировал, что мне не нравится в php.
  1. php – язык с динамической типизацией и это круто, я постепенно перешел к тому, что мне нравятся больше языки с динамической, чем статической типизацией. Но мне не нравится то, когда я пишу код запроса, в котором использую некоторые поля, имена таблиц я никак не могу проверить до запуска приложения то, что я написал, не допустил элементарной опечатки, перепутал имя таблицы, поля. Все это умел делать sqlj - я писал в коде java встраиваемый запрос – а при компиляции выполнялась проверка того, что используемые мною объекты СУБД действительно существуют. Это было круто, но сама архитектура php не давало мне не единого шанса реализовать нечто подобное.
  2. Вы знаете, что при отправке некоторого запроса на сервер я должен заключать текстовые данные в кавычки и экранировать специальные символы внутри подобных строк. Я знаю, что я забывчив, и могу забыть сделать это. Я знаю, что в ходе переделки СУБД (эй, где те парни, которые пишут толстые книги, рассказывающие о суперски спланированной СУБД, которая не подвергается переделки вплоть до сдачи всего проекта?). Я переименовывал поля в таблицах, сами таблицы менял имена полей, удалял поля и должен был в чертовой куче файлов исходников искать все те sql-запросы, чтобы подогнать их под очередное видение СУБД.
  3. Я хотел иметь возможность создавать иерархии связанных записей хранящихся в оперативной памяти и много позже принимать решение нужно ли мне действительно сохранять данные в базу.
  4. хотел получить нормальную обработку ошибок. Четвертый php который в то время был на 80% хостингов не имел обработки исключений и мне приходилось строить лапшу из множества вложенных if-ов, чтобы не прочитать данные из выборки, которая на самом деле не была сформирована по какой-то причине. Ведь вам все еще попадаются дрянные сайты, авторы которых, похоже, живут в чудесной стране “ошибки – я не знаю что это такое, я не хочу знать, что это такое, и не собираюсь учиться как-то их учитывать и обрабатывать”.
  5. Я хотел автоматизировать написание запросов на отбор данных, так что бы я мог сконструировать php-объект/ассоциативный массив, содержащий шаблон отбираемых данных кто-то вместо меня занялся сборкой секции where, учтя типы полей и предупредив меня, если я попытаюсь задать фильтр по несуществующей таблице или полю.
  6. Я хотел бы получить из базы запись, и иметь возможность передавать эту запись из одной функции в другую, поместить набор нужных для обработки записей в массив, сессию. И когда мне, спустя какое то время потребуется изменить пару значений в полях и сохранить изменения назад в базу, не конструировать громоздкий “update”, судорожно вспоминая, не забыл ли я где-нибудь сохранить ключ этой записи.

Написанная мною библиотека не ставит целью “только благодаря нашей библиотеке вы можете менять используемую вами СУБД хоть каждый день”. Не пытаюсь написать самую быструю в мире надстройку над mysql-php-api. Не пытаюсь разорить производителей клавиатур, сокращая в разы количество набираемых вами букв в программе. Я не стремлюсь создать супер-продукт, которое заставляет принявшего необдуманное решение лечь в прокрустово ложе моего гениального видения api будущего. Если вам нужно написать скрипт размеров в сотню строчек, работающий с двумя таблицами, то, наверное, моя библиотека будет громоздка, пусть не как слон в посудной лавке, но как хороший бегемот, уж точно.

Прежде всего, необходимо выполнить подготовительные шаги. Я храню все файлы, относящиеся к моей библиотеке, ко всему связанному с доступом к СУБД, в размещенном в корне сайта подкаталоге db-support. А вот его содержимое:
  • Файл db_connection.inc содержит код подключения к СУБД mysql – учетные данные.
  • Файл lite_db_engine.inc – это часть облегченной библиотеки для прямого доступа к СУБД – я не буду про нее сегодня рассказывать.
  • Файл mysql_40.inc – в нем находятся overhead-функции вызывающие версию стандартных средств расширения php-mysql.
  • Файл mysql_41.inc – в нем находятся overhead-функции вызывающие версию стандартных средств расширения php-mysqli.
  • Файл mysql_40_41.inc – именно в нем принимается решение какую из упомянутых выше библиотек следует применять для доступа к mysql. Или старый добрый (морально и технически устаревший mysql) или обновленные и доступные начиная с php5 расширения mysqli.

  • Файл tbasetable.inc – один из двух файлов, в которых находится собственно код моей библиотеки.
  • Файл tgenericentityset.inc – второй и самый большой файл в составе библиотеки. Суммарный размер кода библиотеки составляет порядка 100 кб.
  • Файл names.php - этот файл содержит описание набора таблиц и полей, с которыми мы будем работать.
  • .htaccess – с его помощью вы запрещаете прямой доступ к содержимому каталога, чтобы злые хакеры не похитили ваши учетные данные и не узнали из каких таблиц и полей состоит СУБД.

Далее я создам простую базу с несколькими взаимосвязанными таблицами, и покажу на простых примерах основные возможности моей библиотеки.

Предположим, что наша СУБД описывает сведения о подписных изданиях. У нас будут две таблицы-справочника: Клиенты, Издания. Связаны между собой эти таблицы отношением “многие-ко-многим”, а значит, нам необходима промежуточная таблица, в которой будут храниться сведения о том, какой клиент какие издания выписывает для себя. Естественно, что все таблицы будут в формате innodb, с установленными между собой связями для поддержание целостности с помощью cascade. Пример устройства таблиц и кода sql их создающего приведен на картинках ниже. В принципе, все очевидно. Таблица “клиент” содержит информацию об ФИО, дате рождения и поле клиента. Таблица “газеты” содержит сведения о названии газеты, ее цене и каком-то уникальном буквенном коде. Таблица “подписка”, очевидно должна содержать поля с идентификаторами клиента и газеты, а также два поля типа “дата” задающие срок действия подписки и поле “реальная цена” – очевидно с ценой реально заплаченной клиентом. На самом деле мои представления о работе почты и подписки на печатные издания довольно примитивны и я заранее прошу прощения у тех, кому поплохело в ходе чтения данного опуса.

Скопировав данную папку к себе на сервер, вы должны указать данные соединения с СУБД – правьте для этого файл db_connection.inc.

Именно переменным
  1. $param_db_host  = 'localhost';
  2. $param_db_user = 'root';
  3. $param_db_password = '';
  4. $param_db_db = 'mydbname';
  5. $param_db_setnames = 'cp1251';
Вы должны присвоить нужные сведения об подключении. Последний параметр $param_db_setnames – содержит имя кодировки соединения. Если вы укажите значение $param_db_setnames как false, то кодировка останется по умолчанию.

Затем мы должны сгенерировать набор классов описывающих модель данных – таблицы и их поля. Пример подобного класса приводится ниже:
  1. class TREF_entfaq  extends TBase_TREF{
  2. 	var $table_name =  'filips_entfaq';
  3. 	function TREF_entfaq () {$this->TranslateMe();}
  4. 	var $FAQID =  'FAQID';
  5. 	var $FAQCategoryID =  'FAQCategoryID';
  6. 	var $FAQQuestion =  'FAQQuestion';
  7. 	var $FAQAnswer =  'FAQAnswer';
  8. 	var $DateOfPublish = 'DateOfPublish';
  9. 	var $PREFIX =  'PREFIX';
  10. 	var $UserOrder = 'UserOrder';
  11. }
Всякий раз, когда вам нужно будет написать код обращающийся к какому-то полю-таблицы вы используете обращение к переменной-полю-класса, например, $FAQCategoryID. В качестве очевидного бонуса вы получаете возможность выполнять переименование полей и самих таблиц без правок кода использующего их. Но это была не главная цель введения такого посредника, главное - интеграция с системой подсказок zend, возможность контроля кода, избежать опечаток в именах объектов БД.

Естественно что вам не обязательно руками писать данный код. В случае если модель данных очень велика, то лучше воспользоваться утилитой generator.php находящейся рядом с основными файлами образующими мою библиотеку. Запустив данный скрипт, вы получите в каталоге generator набор файлов содержащих текст подобных классов с описаниями полей таблиц. Наберите в адресной строке браузера адрес этого сприпта и вы увидите выведенные строчки, показывающие какие таблицы базы (помните, что параметры подсоединения должны быть определены в файле db_connecion.inc).

А в каталоге generator появится набор файлов с именами такими же как имена таблиц находящихся в вашей базе данных. Вот пример кода таких файлов:
  1. class TREF_newspaper  extends TBase_TREF {
  2. 	var $table_name =  'newspaper';
  3. 	function TREF_newspaper () {}
  4. 	var $NewspaperID =  'NewspaperID';
  5. 	var $NewspaperTitle =  'NewspaperTitle';
  6. 	var $Code =  'Code';
  7. 	var $DefaultPrice =  'DefaultPrice';
  8.  
  9. }
  10. $x_TREF_newspaper = new TREF_newspaper();
  11. $w_TREF_newspaper = new TGenericEntitySet(RESOURCE_MYSQL_ANGLE , $x_TREF_newspaper->table_name);
  12. $glob_PHP_JNDI_Lookup_Cache [$x_TREF_newspaper->table_name] = $w_TREF_newspaper;
  13. makeFilterCacheEnabled ($w_TREF_newspaper, '$x_TREF_newspaper');
  1. class TREF_subscription  extends TBase_TREF {
  2. 	var $table_name =  'subscription';
  3. 	function TREF_subscription () {}
  4. 	var $SubscriptionID =  'SubscriptionID';
  5. 	var $NewspaperID =  'NewspaperID';
  6. 	var $UserID =  'UserID';
  7. 	var $StartRange =  'StartRange';
  8. 	var $EndRange =  'EndRange';
  9. 	var $RealPrice =  'RealPrice';
  10. }
  11.  
  12. $x_TREF_subscription = new TREF_subscription();
  13. $w_TREF_subscription = new TGenericEntitySet(RESOURCE_MYSQL_ANGLE , $x_TREF_subscription->table_name);
  14. $glob_PHP_JNDI_Lookup_Cache [$x_TREF_subscription->table_name] = $w_TREF_subscription;
  15. makeFilterCacheEnabled ($w_TREF_subscription, '$x_TREF_subscription');
  1. class TREF_user  extends TBase_TREF {
  2. 	var $table_name =  'user';
  3. 	function TREF_user () {}
  4. 	var $UserID =  'UserID';
  5. 	var $UserFIO =  'UserFIO';
  6. 	var $BirthDate =  'BirthDate';
  7. 	var $Sex =  'Sex';
  8. }
  9.  
  10. $x_TREF_user = new TREF_user();
  11. $w_TREF_user = new TGenericEntitySet(RESOURCE_MYSQL_ANGLE , $x_TREF_user->table_name);
  12. $glob_PHP_JNDI_Lookup_Cache [$x_TREF_user->table_name] = $w_TREF_user;
  13. makeFilterCacheEnabled ($w_TREF_user, '$x_TREF_user');
Объявление класса не требует каких-либо пояснений, разве что не следует создавать таблицы, имя какого-нибудь из полей с именем table_name – вот и все.

Для каждого класса-таблицы создаются два объекта:
  • Объект-дескриптор – просто экземпляр класса описывающего внутреннее устройство таблицы.
  • Объект-набор данных (recordset) собственно и служащий для манипуляций над данными.

Затем все объекты-recordset помещаются внутрь глобального А-массива, в качестве ключа используется имя таблицы. $glob_PHP_JNDI_Lookup_Cache – только не спрашивайте почему я так назвал его – это тайна покрытая мраком.

Последняя строка служит для управления оптимизациями – дело в том, что как вы заметили объявление класса не содержит сведений о типах полей, том какие поля являются обязательными, а какие нет, нет сведения об внешних связях между таблицами. Я принял в свое время волевое решение извлекать сведения об подобных характеристиках полей из самой СУБД, а не хранить внутри полей класса-дескриптора. Это было нужно для более компактного и удобочитаемого кода. Затраты на извлечение подобной информации ничтожны, но если таблиц много, а некоторые из моих проектов могли похвастаться цифрами порядка двух сотен таблиц. Таким образом возникла потребоность заняться оптимизаций, это не только умная загрузка мето-информации об характеристиках, но и всяческие кэширования отбираемых данных, сходных sql-запросов. Как бы страшно это не звучала но прирост скорости был заметен. Одним словом. Если вы хотите чтобы была включена всевозможная оптимизация и кэширование результатов работы с БД, то оставьте последнюю строку как есть, иначе просто ее закомментируйте. Если же вы боитесь пользоваться КЭШами из-за высокой нагрузки на БД (т.е. велика вероятность, что за время выполнения одного запроса клиента к серверу данные в таблицах могут быть изменены параллельно работающим другим клиентом, то лучше оптимизацию выключить).

Собственно, все. Поздравляю всевозможные подготовительные действия были завершены. Пора написать немного кода. Прежде всего очистим таблицы от старой информации и внесем в них немного новых сведений.

Для набора кода я использую zend studio. Она умеет почти правильно подсвечивать синтаксис и подсказывать имена переменных, так как показано на следующем рисунке.

Забегая вперед: если в ходе работы скрипта возникнет ошибка, например я обращусь к несуществующему полю или переменной то встроенный обработчик ошибок выдаст дерево calltrace, значения ряда переменных, сессии. Разумеется, что вы можете убрать вывод ненужных сведений подправив код функции обработчика ошибок. Также вы можете отключить столь жесткую реакцию на возникающие ошибки с помощью функции $ссылка_на_объект_recordset->SetStrictErrorMode (true);


 Пример каталога содержащего все необходимые для работы библиотеки файлы

 Пример дерева объектов тестовой базы данных

 Исходный код создания mysql таблиц (клиент, газета, подписка)

 Результат запуска скрипта generator.php - видим имена обработанных таблиц

 Подсказки в Zend - долой опечатки

 Вид сгенерированного сообщения об ошибке

Пример кода использующего библиотеку dbgenset и результаты его работы



Сначала пример использующего кода:
  1. include_once('db-support/names.php');
  2.  
  3. /* очищаем содержимое всех таблиц параметр false служит для указания того, 
  4. что нет особого критерия отбора записей подлежащих удалению - значит удалить нужно все */
  5. $w_TREF_subscription->DeleteRecordsByExample(false);
  6. $w_TREF_user->DeleteRecordsByExample(false);
  7. $w_TREF_newspaper->DeleteRecordsByExample(false);
  8.  
  9.  
  10. // теперь добавим парочку записей
  11. $vasyan = new TDummyRecord();
  12. /* это на самом деле хак для Zend-студии, 
  13. чтобы ее анализатор понял какому типу данных принадлежит переменная vasyan и 
  14. после набора символов "->" чтобы возникала подсказка о том, какие методы доступны для вас - это удобно */
  15.  
  16. $vasyan = $w_TREF_user->GetFreeRecord();
  17. /* получаем ссылку на свободную запись - свободная означает что она никак не связана 
  18. с хранящимися в базе данных записями, так что заполнив ее поля и сохранив мы получим в таблице клиентов новую запись */
  19. $vasyan->AppendRecord ();
  20.  
  21. /* 
  22. переводим запись в режим добавления следует указать набор значений полей для клиента
  23. для того чтобы вносить данные в поля записи есть две базовые функции - SetFieldValue - 
  24. в этом случае указав в качестве первого параметра имя поля а 
  25. в качестве второго значение данного поля
  26. вам не нужно заботиться об генерации корректного кода с 
  27. заключением тексовых полей в ковычки, а также 
  28. экранирования спец. символов в строке
  29. вторая функция SetFieldFunction - также получает первым 
  30. параметром имя некоторого поля, а второй параметр это строка текста с вызовом произвольной 
  31. mysql-функции -  здесь анализатор типа поля с последующим 
  32. декорированием данных отключается
  33. */
  34.  
  35. $vasyan->SetFieldFunction ($x_TREF_user->BirthDate, 'NOW()');
  36. $vasyan->SetFieldValue ($x_TREF_user->Sex, 'male');
  37. $vasyan->SetFieldValue ($x_TREF_user->UserFIO, 'Vasily Pupkin');
  38. // теперь запись нужно сохранить в базу
  39.  
  40. $vasyan->UpdateRecord();
  41.  
  42. // предположим что после этого вы приняли решение что необходимо исправить некоторые сведения об клиенте
  43.  
  44. $vasyan->EditRecord ();
  45. // переводим запись в режим редактирования
  46. $vasyan->SetFieldValue ($x_TREF_user->Sex, 'female');
  47. $vasyan->UpdateRecord ();
  48.  
  49.  
  50. /*
  51. после выполнения команды Update - общей для операции изменения данных и добавления новой записи, 
  52. автоматически получаются значения автоматически генерируемых полей - например поля счетчика 
  53. id - оно в таблице является auto_increment
  54. */
  55. $vid = $vasyan->GetIDValue ();
  56. // для доступа к значениям полей используется функция GetFieldValue с входным параметром - именем поля
  57. print 'значение id для клиента: ' .
  58. $vasyan->GetFieldValue ($x_TREF_user->UserFIO) . ' = ' . $vid . '<hr />';
  59.  
  60. // теперь создадим объект "газета"
  61.  
  62. $pravda = $w_TREF_newspaper->GetFreeRecord();
  63. $pravda->AppendRecord ();
  64. $pravda->SetFieldValue ($x_TREF_newspaper->Code, 'EA45');
  65. $pravda->SetFieldValue ($x_TREF_newspaper->DefaultPrice, 999.99);
  66. $pravda->SetFieldValue ($x_TREF_newspaper->NewspaperTitle, 'Pravda');
  67. $pravda->UpdateRecord();
  68.  
  69.  
  70. $pravda2 = $w_TREF_newspaper->GetFreeRecord();
  71. $pravda2->AppendRecord ();
  72. $pravda2->SetFieldValue ($x_TREF_newspaper->Code, 'EA45');
  73. // здесь должна быть ошибка добавления записи т.к. поле Code является уникальным
  74. $pravda2->SetFieldValue ($x_TREF_newspaper->DefaultPrice, 279.99);
  75. $pravda2->SetFieldValue ($x_TREF_newspaper->NewspaperTitle, 'Trud');
  76.  
  77. $pravda2->SetStrictErrorMode (false);
  78. /* отключаем обработку ошибок - как критическую, 
  79. теперь если в ходе работы с БД возник сбой то работа всего скрипта не будет прервана,
  80. а просто функция выполнившая ошибочное действие вернет флаг false 
  81. */
  82.  
  83. if (!$pravda2->UpdateRecord())
  84.   print 'не смог добавить дубль газеты' . '<hr />';
  85. else 
  86.   print 'ошибка, дубль добавился успешно'. '<hr />';
  87.  
  88. /* 
  89. теперь покажем как можно получить ссылку на добавленную внутрь базы запись
  90. в простейшем случае у нас есть id этой записи, тогда делаем так:
  91. */
  92. $trud = $w_TREF_newspaper->GetLinkedRecord($pravda->GetIDValue());
  93. /*
  94. хотя мы получили ссылку на существующую запись 
  95. но мы все равно можем перевести ее в режим добавления
  96. */
  97. $trud->AppendRecord ();
  98. $trud->SetFieldValue ($x_TREF_newspaper->Code, 'EA45');
  99. $trud->SetFieldValue ($x_TREF_newspaper->NewspaperTitle, 'Trud Updated');
  100. $trud->UpdateRecord ();
  101. /*
  102. в таком случае изменив часть полей и сделав UpdateRecord 
  103. я получу новую запись - частичную копию газеты
  104. */
  105.  
  106. /* теперь получим список всех газет отсортированных по некоторому полю
  107. есть несколько функций которые отбирают и сортируют даннные
  108. самая простая из них - FilterRecords - получающая два параметра 
  109. - корректное sql условие для отбора данных, а
  110. также второй параметр - имя поля по которому идет сортировка
  111. */
  112.  
  113. $flist = $w_TREF_newspaper->FilterRecords(false, $x_TREF_newspaper->DefaultPrice);
  114. for ($i = 0; $i < count($flist); $i++){
  115. $dum = $flist[$i];
  116. print 'Найдена газета: ' . 
  117. $dum->GetFieldValue ($x_TREF_newspaper->NewspaperTitle) . '<hr />';
  118.  
  119. $dum->EditRecord ();
  120. /*
  121. теперь еще один способ изменить данные в таблице - 
  122. так можно указать значения для множества полей, но обратите внимание, 
  123. что если  подам в качестве данных неверное имя поля то никаких ошибок не будет сгенерировано
  124. */
  125. $dum->InitFromAssoc (array (
  126. $x_TREF_newspaper->DefaultPrice => 2000,
  127. 'BAD_FIELD_NAME' => 1000
  128. ));
  129. $dum->UpdateRecord ();
  130. }
  131.  
  132. /*
  133. следующий способ отбора данных также предполагает что вы 
  134. должны сами сформилировать sql-запрос отбора
  135. но также можно указать параметр - количество отбираемых 
  136. записей и позицию с которой отбор должен быть выполнен - 
  137. используется возможность mysql - LIMIT
  138. указываем условие отбора, номер позиции и количество записей, затем сортировку по полю
  139. */
  140.  
  141. $flist = $w_TREF_newspaper->FilterRecordsInRange(false, 0, 1, $x_TREF_newspaper->DefaultPrice);
  142. print 'Отобраны записи: ';
  143. print_r ($flist);
  144. print '<hr />';
  145. /*
  146. из-за LIMIT (paging-а) так будет отобрана всего одна запись, 
  147. хотя под условие (раз условие не задано то слудует отобрать все записи)
  148. подошли все две записи находящиеся в таблице
  149. */
  150. print 'Полное количество отобранных записей: ' . 
  151. $w_TREF_newspaper->GetCountOfLastQueryRows() . '<hr />';
  152.  
  153. /*
  154. следующая фукнция - моя любимая - мы можем задать условие отбора 
  155. не ввиде простой строки, а в форме А-массива, ключи которого это имена полей а значения - 
  156. то чему должны быть равны записи при отборе
  157. */
  158.  
  159. $flist = $w_TREF_newspaper->FilterRecordsByExample(array (
  160.     $x_TREF_newspaper->Code => 'EA45',
  161.     FLD_ONLYLESSTHAN . $x_TREF_newspaper->DefaultPrice => 2000,
  162.     NOT_FIELD . $x_TREF_newspaper->NewspaperID => array (101, 102, 103, 104),
  163.  ), 
  164.  0, 1, $x_TREF_newspaper->DefaultPrice);
  165.  
  166. // также второй и третий параметр функции управляет отбором записей с помощью paging-а,четвертый параметр - задает сортировку
  167.  
  168. print 'Был сформирован следующий запрос на поиск: ' . 
  169. $w_TREF_newspaper->GetSQLCommand() . '<hr />';
  170.  
  171. // теперь удалим записи
  172.  
  173. $vasyan->DeleteRecord ();
  174.  
  175.  
  176. // список полей которые обязательны для заполнения
  177. print 'Список полей обязательных для заполнения: ';
  178. print_r($vasyan->GetNotNullFieldNames ());
  179. print '<hr />';
  180.  
  181. // получить тип поля
  182. print 'Тип поля UserFIO: ' .$vasyan->GetTypeName ($x_TREF_user->UserFIO) . '<hr />';
  183.  
  184. // получить текст SQL условия отобора записи
  185. print 'Уникальный идентификатор записи: ' . $vasyan->CreateIdentitySQL () . '<hr />';
  186.  
  187. // получаем имя поля Primary Key
  188. print 'Имя поля первичного ключа таблицы: ' . $vasyan->GetIDFieldName (). '<hr />';
  189.  
  190. // получить список записей по массиву их Primary Key ID
  191. print 'Ищем массив записей по набору значений их ключей: (1,2,3): ';
  192. print_r($w_TREF_newspaper->GetLinkedRecords (array (10, 11, 103)));
  193.  
  194.  
  195. // проверка существования некоторой таблицы
  196. if ($w_TREF_newspaper->IsTableExists ('megatable'))
  197.    print 'Таблица megatable существует'. '<hr />';
  198. else 
  199.    print 'Таблицы megatable нет'. '<hr />';
  200.  
  201.  
  202. // получаем значение переменной mysql
  203. print 'datadir = ' . $w_TREF_newspaper->GetMysqlVariable('datadir') . '<hr />';
  204.  
  205. // подсчет количества записей по некоторому условию
  206.  
  207. print 'всего количество записей газет: ' . $w_TREF_newspaper->CountRecords() . 
  208. ' а среди них цена которых превосходит 1000 : '
  209. . $w_TREF_newspaper->CountRecords(array (FLD_GREATEROREQUALTHAN . $x_TREF_newspaper->DefaultPrice => 1000) ) . '<hr />';
  210.  
  211. // можно скопировать все содержимое одной таблицы в другую та, вторая таблица должан существовать
  212.  
  213. /*
  214.  команда копирования данных в другую таблицу естественно работать не будет: 
  215.  $w_TREF_newspaper->CopyAllDataToTable ($x_TREF_foo);
  216. */
  217.  
  218. $w_TREF_newspaper->DropTable('CopyOf');
  219.  
  220. // получить максимальное значение для некоторого поля
  221. print 'Максимальная цена газеты: ' . $w_TREF_newspaper->GetMaxValueForField($x_TREF_newspaper->DefaultPrice) . '<hr />';
  222.  
  223. /*
  224. возможно конструировать и выполнять запросы без помощи моей библиотечки
  225. если запрос возвращает набор данных то они представляются в виде массива, 
  226. каждый элемент которого запись
  227. запись в свою очередь - это снова А-массив
  228. */
  229. print 'результат выполнения прямого sql-запроса: ';
  230. print_r ($w_TREF_newspaper->ExecuteSQLCommandWithResultSet('select * from ' . $x_TREF_newspaper->table_name));
  231. print '<hr />';
  232. // или же если команда не возвращает никаких данных
  233. if ($w_TREF_newspaper->ExecuteSQLCommandWithoutResultSet('truncate megatable'))
  234.   print 'Выполнена команда очистки несуществующей таблицы <hr />';
  235. else
  236.   print 'Команда очистки несуществующей таблицы как и ожидалось не была выполнена<hr />';
  237.  
  238. // записи можно удалять по значению ID
  239. if ($w_TREF_user->DeleteRecordByID ($vid))
  240.   print 'Порядок запись была удалена по ID = ' . $vid . '<hr />';
  241. else
  242.   print 'Увы но запись удалить не удалось по ID = ' . $vid . '; причина: '.$w_TREF_user->GetLastError().'<hr />';
  243.  
  244. // или же задать условие отбора в виде ассоциативного массива
  245. $w_TREF_user->DeleteRecordsByExample (array ($x_TREF_user->Sex => 'alien'));
И результат его работы:
значение id для клиента: Vasily Pupkin = 24
не смог добавить дубль газеты Найдена газета: Trud Updated
Отобраны записи: Array
(
    [0] => TGenericRecord Object
        (
            [sub_mirror_fields] => Array
                (
                )
 
            [is_auto_increment_field_as_primary] => 1
            [is_edit_mode] => 
            [is_append_mode] => 
            [is_deleted] => 
            [field_names] => Array
                (
                    [0] => NewspaperID
                    [1] => NewspaperTitle
                    [2] => Code
                    [3] => DefaultPrice
                )
 
            [field_types] => Array
                (
                    [0] => 1
                    [1] => 2
                    [2] => 2
                    [3] => 1
                )
 
            [field_sizes] => Array
                (
                    [0] => 11
                    [1] => 100
                    [2] => 10
                    [3] => 9
                )
 
            [field_cur_values] => Array
                (
                    [0] => 24
                    [1] => Trud Updated
                    [2] => EA45
                    [3] => 2000.000
                )
 
            [field_new_values] => Array
                (
                    [0] => 24
                    [1] => Trud Updated
                    [2] => EA45
                    [3] => 2000.000
                )
 
            [field_not_null_names] => Array
                (
                    [0] => NewspaperID
                    [1] => NewspaperTitle
                    [2] => Code
                )
 
            [table_name] => newspaper
            [id_field_name] => NewspaperID
            [type_of_id_field] => 1
            [is_readonly] => 
            [is_in_memory_cached] => 
            [is_was_stored_in_db] => 1
            [in_memory_cached_id] => 
            [is_modified] => 
            [smart_mode_enabled] => 
            [flink_objects] => Array
                (
                )
 
            [IsDebuggerEnabled] => 
            [link_to_database] => resource:mysql:angle
            [log_file_name] => global_log_db_support.err
            [verbose_level] => 1
            [last_error_message] => 
            [last_error_code] => 
            [sql_command] => 
            [optimizehints_lastqueryrowsenabled] => 1
        )
 
)
Полное количество отобранных записей: 1Был сформирован следующий запрос на поиск: 
SELECT 
SQL_CALC_FOUND_ROWS * FROM newspaper WHERE Code = 'EA45' AND DefaultPrice < 2000 AND ( NOT ( (
 NewspaperID = 101 )  OR  ( NewspaperID = 102 )  OR  ( NewspaperID = 103 )  OR  ( NewspaperID = 104 )
 ) )  ORDER BY DefaultPrice LIMIT 0, 1
 
Список полей обязательных для заполнения: Array
(
    [0] => UserID
    [1] => UserFIO
)
Тип поля UserFIO: string
Уникальный идентификатор записи: UserID = 24
Имя поля первичного ключа таблицы: UserID
 
Ищем массив записей по набору значений их ключей: (1,2,3): Array ()
 
Таблицы megatable нетdatadir = E:\Program_Files_2\mysql\data\всего количество записей газет: 1 
а среди них цена которых превосходит 1000 : 1
 
Максимальная цена газеты: 2000.000
 
результат выполнения прямого sql-запроса: Array
(
    [0] => Array
        (
            [NewspaperID] => 24
            [NewspaperTitle] => Trud Updated
            [Code] => EA45
            [DefaultPrice] => 2000.000
        )
 
)
Команда очистки несуществующей таблицы как и ожидалось не была выполнена
 
Порядок запись была удалена по ID = 24

Исходники библиотеки



https://github.com/study-and-dev-site-attachments/all-in-one/tree/master/php/extractedtgenset