СоХабр закрыт.

С 13.05.2019 изменения постов больше не отслеживаются, и новые посты не сохраняются.

H ActiveRecord своими руками на PHP в черновиках Из песочницы

Доброго времени суток! Я работаю над веб-проектом X. Проект большой, сложный и перспективный. Но, как и в любом другом проекте, есть много кода, написанного наспех с пометкой «когда-нибудь исправлю».

Пришло время исправлять.

Читая очередной мануал, я обнаружил шаблон проектирования «Active Record». Простой запрос в Google, и вот уже подруга Википедия коротко и ясно рассказала обо всём (https://ru.wikipedia.org/wiki/ActiveRecord). Смысл шаблона прост: для работы с таблицами в базе данных решено было создать специальный класс, который бы выполнял все основные действия (CRUD).

Вот всё, что нужно для подключения: поместим в массив $dbConfig (тут будет 5 элементов):
host, user, pass, db, table.

Пусть функция setConfigDB заполнит нужными значениями $dbConfig.

protected $dbConfig = array();

    function setConfigDB($host, $user, $pass, $db, $table) {
        $this->dbConfig["host"] = $host;
        $this->dbConfig["user"] = $user;
        $this->dbConfig["pass"] = $pass;
        $this->dbConfig["db"] = $db;
        $this->dbConfig["table"] = $table;
    }

Теперь создадим метод подключения к БД:

protected function init_connected_db() {
        mysql_connect($this->dbConfig["host"], $this->dbConfig["user"], $this->dbConfig["pass"]);
        mysql_select_db($this->dbConfig["db"]);
        mysql_query("SET NAMES cp1251");
    }

Тут все как в книжке. Единственное, что mysql_query(«SET NAMES cp1251»); позволит читать данные в нужной кодировке. База данных настроена на эту кодировку.
Я не стал выбирать какие-то особенные имена, не стал придерживаться именования CRUD (Create, Update, Delete), а писал как есть.
Для того чтобы вставить запись, будем использовать эту функцию:

 function insert($keys, $values) {
        $table = $this->dbConfig["table"];
        $query = "INSERT INTO $table (";
        foreach ($keys as $value) {
            $query.="$value, ";
        }

        $query = substr($query, 0, -2);
        $query.=") VALUES (";
        foreach ($values as $value) {
            $query.="$value, ";
        }
        $query = substr($query, 0, -2);
        $query.=");";
        $this->init_connected_db();
        mysql_query($query);
    }

Главное потом надо не забыть, что здесь в качестве параметров передаются массивы. Foreach работает с ними, а без него проблемно создать запрос с несколькими полями и соответствующими значениями. Параметр $key у нас принимает имена полей, параметр $value — значения, которые надо записать в соответствующие поля. В функции два цикла, сначала пишем все поля, а потом все значения. Пользуемся стандартными возможностями PHP, чтобы получить нужную нам строку. В конце подключаемся к БД и выполняем запрос. Подключение к БД скрыто, чтобы программист только лишь передал нужную информацию в метод.

Для чтения данных и таблицы будем использовать две функции selectAll() и select($fields). По смыслу их можно объединить в одну, но по факту проще создать две.

 function selectAll() {
        $table = $this->dbConfig["table"];
        $query = "SELECT * FROM $table;";
        $this->init_connected_db();
        return $result = mysql_query($query);
    }

    function select($fields) {
        $table = $this->dbConfig["table"];
        $query = "SELECT ";
        foreach ($fields as $value) {
        $query.="$value, ";
        }
        $query = substr($query, 0, -2);
        $query.=" FROM $table";

        $this->init_connected_db();
        return $result = mysql_query($query);
    }

Так как инструкция SELECT вернет нам результат, то в return поместим $result = mysql_query($query);
Далее удаление.

function delete($key, $value) {
        $table = $this->dbConfig["table"];
        $query = "DELETE FROM $table WHERE $key=$value;";
        $this->init_connected_db();
        return $result = mysql_query($query);
    }

Метод — одноразовый. Один вызов — удалена одна строка. Известно, что инструкция DELETE возвращает число удаленных строк, для удобства вернем их return $result = mysql_query($query);.
Теперь обновим наши записи в табличке:

function update($key, $value, $where_key, $where_value) {
        $table = $this->dbConfig["table"];
        $query = "UPDATE $table SET $key =$value WHERE $where_key=$where_value;";
        $this->init_connected_db();
        return $result = mysql_query($query);
    }

Функция содержит 4 входных параметра $key, $value, $where_key, $where_value. Может показаться, что их слишком много, но с другой стороны инструкция UPDATE довольно подробная.

Так вот, $key=$value это то, что мы поместим на место старой записи, а $where_key и $where_value это то, где нам искать. То есть: находим строку с полем $where_key значение которого равно $where_value, в ней записываем значение $value в поле $key. Вроде сложно, но если прочесть команду в SQL, то сразу становиться понятным, что к чему.
Класс готов, теперь поработаем с экземпляром:

Создадим новый объект, запишем все необходимое для дальнейшей работы:

 $ar = new ActiveRecord(); 
 $ar->setConfigDB("localhost", "root", "", "prime", "tabl"); 

Далее вставим запись в табличку:

 	  $fields = array("text");
        $values = array("'Привет!'");
        $ar->insert($fields, $values); 

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

 $result = $ar->selectAll(); 

И для просмотра результата, как в учебниках напишем цикл:

while ($row = mysql_fetch_array($result)) {

            echo " || " . $row['id'] . " => " . $row['text'];
        }

Здесь я специально не стал искать и придумывать способа вывода данных, так как основная задача нашего класса именно работа с БД. Ввод данных программист сам сформирует как ему надо, ну или сверстает отдельный класс для этого.
Теперь проведем выборку по конкретным полям. В моем случае одно.

	  $fields = array('id');
        $result = $ar->select($fields); 
       
        while ($row = mysql_fetch_array($result)) {
            echo $row['id'] . "; ";
        }

Что-нибудь удалим:

$ar->delete( "text","'Привет!'"); 
        $result = $ar->selectAll();
  

И обновим:

$ar->update("text", "'Пока!'", "id", 57); .

В общем всё. Получилось то, что хотелось бы считать приемлемым для работы. Относить этот класс к настоящим Active Record или нет, не знаю. Но уверен, что в столь нехитрой, а местами даже незатейливой реализации, его можно применять в простых проектах. А если хорошенько поработать над ним, то и в сложных ему найдётся место.
–28
2377

комментарии (26)

+2
DOC_tr ,  
Главное потом надо не забыть, что здесь в качестве параметров передаются массивы.

function insert(array $keys, array $values) {

А вообще этот класс больше обертка для стандартных функций, и в качестве Active Record его сложно представить.
+3
DOC_tr ,  
Глянул код — ужас.
Переменные в двойных кавычках, свобода для инъекций. Много ненужного кода.
foreach ($keys as $value) {
  $query.="$value, ";
}
$query = substr($query, 0, -2);

Это все можно сделать (конечно не нужно, но можно) проще
$query = join(', ', $keys);

А вообще лучше используйте этот класс не в качестве обертки для стандартных функций php, а в качестве обертки для PDO
–2
Delphinum ,  
А вообще лучше используйте этот класс не в качестве обертки для стандартных функций php, а в качестве обертки для PDO

Зачем вы советуете это?
+9
annenkov ,   * (был изменён)
Не вижу ничего общего с приведенным по указанной ссылке ru.wikipedia.org/wiki/ActiveRecord описанием.
Да и вообще сомнительная разработка, возможно для автора это какое-то достижение, но вряд ли оно стоит того, что бы его пиарить, т.к. больше похоже на «пробу пера».
Что в первую очередь бросилось в глаза — прямая вставка данных в запрос, это вообще детский сад и создает такое впечатление, что сами не пробовали этим пользоваться.
0
+1 –1
DrNemo ,  
Я правильно понял, что у вас каждая копия ActiveRecord будет создавать свое подключение к БД?)
В метод insert логичнее было бы отдавать один ассоциативный массив
$values = array(
    'table_key_1' => 'value1',
    'table_key_2' => 'value2',
    ....
);

И совершенно не раскрыта тема связей между таблицами)
+7
kanikeev ,  
Всего 2 слова: SQL injection
+4
WebSpider ,  
Всего 1 слово: говнокод
–4
akalend ,  
пол слова нах…
+12
youlose ,  
Не знаю где этому набираются, но mysql_* функции c php 5.5 — deprecated и то что они не будут больше использоваться известно давно.
0
+1 –1
mariner ,   * (был изменён)
тоже не понимаю, чем PDO не угодило
0
ButscH ,  
Чтобы не было споров, какой драйвер выбрать, лучше сделать возможность использования разных драйверов
0
nazarpc ,  
Зачем, тем более в этом случае? Эта возможность не используется на практике почти никогда. Как система установлена в первый раз, как привык разработчик — так она и работает. Вот разве что MySQL на MariaDB заменить ради некоторых фич можно, но тут полная обратная совместимость присутствует.
+1
Delphinum ,  
Это используется крайне часто, когда система рассчитывается на массовый рынок. Клиент системы может захочет запустить ее на той СУБД, которая в данный момент используется у него, а не на той, к которой «привык разработчик».
0
ButscH ,  
Это был компромисс, чтобы не было споров на тему, какой драйвер БД использовать для основы (PDO, mysql, mysqli, sqlite и т.д.), не более.
P.S. Лично для меня, как для разработчика CMS, драйвера подключения к БД актуальны, т.к. у всех пользователей свои требования.
+14
eandr_67 ,   * (был изменён)
Я понимаю, если бы эта поделка была написана лет 12 назад… Но использовать в 2014 году официально объявленную устаревшей библиотеку mysql по меньшей мере странно. Особенно на фоне того, что Ваш код не обеспечивает даже самую примитивную защиту от SQL-инъекций. А ведь обеспечение этой защиты — обязательное требование к любой библиотеке работы с БД.

То, что Вы пытаетесь написать, это не AR.

Главное назначение AR — это «наложение» связей между таблицами БД на нативные структуры языка программирования. Т.е. если у нас есть таблицы «master» и «slave», то я могу получить набор строк главной таблицы запросом вроде:
$data=master::findAll(что-то);

Но чтобы получить связанные данные из подчинённой таблицы в Active Record не требуется писать новый. Достаточно конструкции вида:
$data[10]->slave;
и AR выдаст все строки подчинённой таблицы, связанные с десятой строкой выборки из главной таблицы.

И изменить значение поля «name» в строке подчинённой таблицы в AR можно так:
$data[13]->slave[7]->name="Поликарп";
$data[13]->slave[7]->save();

Именно это отличает AR от DAO. Но возможности Вашей библиотеки даже на DAO не тянут.
+1
sefus ,  
То, что вы написали является просто конструктором запросов. Похожий синтаксис используется в CodeIgniter, но даже в упомянутой вами статье на Википедии сказано, что он не реализует шаблон Active Record.
+3
usualdesigner ,  
А почему пост в хабе «Zend Framework»?
+1
pandas ,  
1. ORM для PHP уже достаточно много: ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_ORM-%D0%B1%D0%B8%D0%B1%D0%BB%D0%B8%D0%BE%D1%82%D0%B5%D0%BA#PHP
также, не стоит забывать что ORM нужно использовать только там, где в этом действительно есть необходимость, иначе memory overhead неизбежен.
яркий надавний пример отличного «индуса»: использование ORM для одного маленького парсера, забирающего два раза в день информацию с dom-документа на каком-то одном, почти статичном сайте.

2. если изобретать свой велосипед, то лучше уж форкать чьи-нибудь разработки на гитхабе, а лучше пулл реквестить свои апдейты, и вообще становиться мейнтейнером какого-нибудь ORM. так и скилл быстрее прокачивается, и навыки коммуникации улучшаются, а главное — меньше шансов разбить свои «мечты о захвате мира» о какой-нибудь комментарий на хабре от действительно опытных и практикующих девелоперов.

3. Читая очередной мануал, я обнаружил шаблон проектирования «Active Record». Простой запрос в Google, и вот уже подруга Википедия коротко и ясно рассказала обо всём. Прежде, чем садиться за новый велосипед, бесспорно правильно изучать материал. Но важно помнить, что вики даёт лишь поверхностные теоретические знания. А тот же гитхаб может открыть целый реальный мир кода, трендов и тенденций.

4. Я ни в коем разе не хочу критиковать код, и не буду этого делать. Я просто оставлю это здесь: getjump.github.io/ru-php-the-right-way/
0
shumak93 ,  
Спасибо. Первый опыт. Поделился. Честно в городе нет нормальных девелоперов, и спросить не у кого. Мечты не разбились, а вот мотивации стать лучше прибавилось. Обязательно учту ваш совет.
+1
Delphinum ,  
Для вопрошания есть Тостер. Уверяю вас, там будет у кого спросить.
+1
thunderspb ,   * (был изменён)
1. mysql_* — rly?? вроде в 5.5 оно deprecated совсем?
2. а чем не устроил PDO?
3. еще и каждый раз подключение создается? а почему его не оставлять в классе?

зыж надо обновлять страницу чаще :(

вывод: статья из разряда «как делать НЕ надо»
+5
+6 –1
kix ,  
image
+1
Kodeks ,  
Обязательно почитайте свою «статью» через пару лет.
0
OnYourLips ,  
Чем меньше опыта — тем больше желания писать статьи.
В данном случае желание явно было нестерпимым.
0
Aliance ,  
А зачем писать так?
return $result = mysql_query($query);
0
blind_oracle ,  
Нет слов… одни фейспалмы