- Статьи и примеры
- | Разное полезное
- | 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
