- Статьи и примеры
- | Разное полезное
- | Mysql2php или создание php объектов для хранения в реляционных базах данных
Mysql2PHP или создание PHP объектов для хранения в реляционных базах данных
Много воды утекло с тех пор, как в языке программирования PHP появились первые реализации классов. До недавнего времени мне не удавалось найти время, чтобы оценить возможности OOP в PHP. Но вот не так давно, благодаря стечению обстоятельств я вернулся к разработке на этом прекрасном языке и готов поделиться парочкой умозаключений. Возможно, они станут для кого-то полезными, ведь так много людей сегодня занимается созданием сайтов.)
Сегодня я хочу поговорить о такой вещи как объектно-реляционная проекция. Ну а если говорить проще, то о том, как преобразовать объекты для сохранения в базу данных и как извлечь их обратно и снова преобразовать в объекты.
Итак, начнем сразу с примера - спроектируем простую базу данных. Берем Mysql Workbench 5.2.16 OSS Beta Revision 5249 и создаем Entity-Relationship модель. Рисуем красивые таблички и просто генерируем базу данных.
Теперь создадим классы для каждой таблицы. Пишем такой скрипт:
<? $c="\r\n/*\r\n* This class generated by Mysql2php script.\r\n* Author: Alexander Smelkov (alex@microgames.ru)\r\n* Web page: www.microgames.ru\r\n*/\r\n"; /* * SETTINGS */ $host = "localhost"; $port = "3306"; $dbname = "mydb"; $user = "root"; $password = ""; $output_dir = "C:/Apache/my_site/entity/"; $extends = "Entity"; /* * CONNECT TO DB */ $db_connect = mysql_connect($host,$user,$password); $select_db = mysql_select_db($dbname, $db_connect); /* * TEMPLATES */ $template_h1 = "<?\r\n".$c."\r\n"; $template_h2 = "require_once(\"<<>>.php\");\r\n\r\n"; $template_h3 = "class <<>>"; $template_h4 = " extends <<>> {\r\n\r\n"; $template_h5 = " {\r\n\r\n"; $template_h6 = " var $<<>>\r\n"; $template_h7 = "\r\n public function getTableName(){\r\n"; $template_h8 = " return \"<<>>\";\r\n"; $template_h9 = " }\r\n"; $template_h10 = "\r\n}\r\n\r\n"; $template_h11 = "?>"; /* * get tables list and prepare class names */ $result = mysql_query("show tables from ".$dbname); $names = array(); while($row = mysql_fetch_row($result)){ $n = array(); $n["table"] = $row[0]; $className = ""; foreach(explode("_", $n["table"]) as $piece){ $className .= ucwords($piece); } $n["class"] = $className; $names[] = $n; } foreach($names as $name){ $class_file = fopen($output_dir.$name["class"].".php", "w"); //Open for writing only; place the file pointer at the beginning of the file and truncate the file to zero length. If the file does not exist, attempt to create it. //get column name from table $columns = mysql_query("show columns from ".$name["table"]); $columnArray = array(); while($row = mysql_fetch_assoc($columns)){ $c = array(); $c["name"] = $row["Field"]; $key = ($row["Key"] == "") ? "" : ", key: ".$row["Key"]; $default = ($row["Default"] == "") ? "" : ", default: ".$row["Default"]; $extra = ($row["Extra"] == "") ? "" : ", extra: ".$row["Extra"]; $c["comment"] = "Type: ".$row["Type"].$key.$default.$extra; $columnArray[] = $c; } //start buid class $classText = ""; $classText .= $template_h1; if($extends != "") $classText .= templateReplace($template_h2, "ENTITY_NAME", $extends); $classText .= templateReplace($template_h3, "CLASS_NAME", $name["class"]); $classText .= ($extends != "") ? templateReplace($template_h4, "ENTITY_NAME", $extends) : $template_h5; foreach($columnArray as $column){ $classText .= templateReplace($template_h6, "VAR_NAME", $column["name"]."; // ".$column["comment"]); } $classText .= $template_h7; $classText .= templateReplace($template_h8, "TABLE_NAME", $name["table"]); $classText .= $template_h9; $classText .= $template_h10; $classText .= $template_h11; //write to file fwrite($class_file, $classText); fclose($class_file); echo "Class: ".$name["class"]." from table ".$name["table"]." READY! "; } function templateReplace($template, $replaceKey, $replaceVal) { $search = "/\<\<\<".strtoupper($replaceKey)."\>\>\>/"; return preg_replace($search, $replaceVal, $template); } ?>
Что он делает? Соединятся с базой данных и последовательно, для всех таблиц, генерирует и сохраняет в заданной папке файлы с классами таблиц - Entity. Для того, чтобы все заработало сразу, надо убедиться что у скрипта хватает прав записывать в выбранную папку. Ну и заменить параметры подключения к базе данных. Мы получим для каждой таблицы файл такого вида:
require_once("Entity.php"); class SiteUser extends Entity { var $id; // Type: int(11), key: PRI, extra: auto_increment var $name; // Type: varchar(255) var $lastName; // Type: varchar(255) var $createdOn; // Type: timestamp, default: CURRENT_TIMESTAMP, extra: on update CURRENT_TIMESTAMP public function getTableName(){ return "site_user"; } }
Не трудно догадаться в каком направлении двигаться дальше. Вот сейчас я попробую придумать класс Entity, который экстендят все сгенерированные классы.
Во первых, он должен уметь читать из базы данных. Неплохо бы чтобы он туда еще и записывал сам. И обновлял. Это самые простые и необходимые вещи.
Добавляем:
public function select(){ } public function insert() { } public function update(){ }
Затем, каким-то образом надо запихнуть внутрь соединение с базой данных. Лучше это дело вынести в отдельный класс. И использовать статический метод для выполнения SQL запроса.
Одно ограничение для использования этого метода: как нетрудно заметить сгенерированные классы содержат public свойства, которые соответствуют полям таблицы. Для того чтобы построить запрос к конкретной таблице достаточно из объекта выбрать все public свойства и их значения. Исходя из вышесказанного понятно, что использовать другие public переменные в классах таблиц и Entity нельзя. Но можно с успехом заменить их на protected или private или static.
Для того, чтобы продемонстрировать как применить на практике такой фильтр, я имплементировал метод insert.
Еще одно небольшое замечание: названия сгенерированных классов начинаются с заглавной буквы, а все подчеркивания в названиях таблиц удалены, и следующее за подчеркиванием слова тоже начинается с большой буквы. Если кто не знает зачем - так принято. А вот для того чтобы получить название таблицы для соответствующего объекта я добавил метод getTableName(). Конечно же можно без труда восстановить название таблицы из имени класса. Кому как нравиться.
Вот они два метода для тестирования:
public function insert() { $class = new ReflectionClass(get_class($this)); $classVars = get_object_vars($this); $props = $class->getProperties(); $propsAarray = array(); foreach( $props as $prop ) { if ( !$prop->isPublic() || $prop->isStatic() ) continue; $propsAarray[] = $prop->getName(); } $query = "INSERT INTO ".$this->getTableName()." SET "; $sql = array(); foreach($propsAarray as $k => $v){ $sql[] = (is_null($classVars[$v])) ? $v."=NULL":$v."='".mysql_real_escape_string($classVars[$v])."'"; } $query .= implode(", ", $sql); try{ $this->execQuery($query); }catch(Exception $e){ echo "There is an exception: ". $e->getMessage(); } } public function execQuery($query) { $host = "localhost"; $port = "3306"; $dbname = "mydb"; $user = "root"; $db_connect = mysql_connect($host,$user,$password); $select_db = mysql_select_db($dbname, $db_connect); $result = @mysql_query ($query); if (!$result) throw new Exception (mysql_error ()); }
Таким образом, получились достаточно простая и вместе с тем мощная и гибкая конструкция, которая позволяет легко создать объект и сохранить его в базе данных или же наоборот взять данные по id или другому признаку и работать с ними как с объектом. А вот небольшой пример:
<? require_once("entity/SiteUser.php"); $my_class = new SiteUser(); $my_class->name = "Vasily"; $my_class->lastName = "Pupkin"; $my_class->insert(); ?>
Никто не мешает расширить и углубить описанный выше подход. Например, создать триггеры – специальные методы beforeInsert() и afterInsert() и пр. Но самое интересное это конечно же объединения. И тут можно использовать совершенно различные подходы. Как мне кажется, создать универсальный способ объединения очень непросто и гораздо продуктивнее в каждом проекте для таких запросов формировать SQL индивидуально вручную. В любом случае это большая отдельная тема на которую возможно позднее я попробую поговорить. Те кому совсем лениво набирать буквы могут скачать пример вышеописанного кода тут.
Санкт-Петербург Зима 2014