Всем привет Стыдно в этом признаться - но не знаю как написать хороший класс для работы с БД (MySQL). Конечно, это никак не мешает работать с Yii2/Laravel, где все это уже написано, но внутренний перфекционизм постоянно негодует. И при попытках закрыть этот пробел в знаниях постоянно сталкиваюсь с двумя проблемами: 1. Понятное, что образцовый код можно посмотреть в том же Yii2/Laravel, но... он там сильно переусложнен различными наворотами. 2. Если искать готовые простые решения в интернете, коих, конечно, масса, то возникает другой вопрос - а где гарантии, что данный код действительно образцовый? Написать "абы как, лишь бы работало" я и сам могу (и даже защита от SQL-инъекций будет). Интересует именно образцовый код. У кого есть готовый пример кода - просьба дать ссылку.
Думаю, надо писать самому и дописывать по мере необходимости. Создаешь метод db_conect() и в конструкторе присваиваешь свойству conn соединение с БД. И пишешь пару методов db_select() и db_query(). Это для начала. И начинаешь всем этим пользоваться. Когда видишь, что какую-то работу делаешь часто - добавляешь наворот. Так у тебя постепенно появляется свой навороченный класс)
Шел по этому пути - делал подключение к БД через обычный объект, потом узнал, что нужно делать через синглтон. И еще много других ошибок можно сделать, с подходом "напишу, чтобы работало, а там, если что доработаю"
Не ошибается тот, кто ни чего не делает. Не возможно чему-то научиться и не набить шишек. Не нужно бояться ошибок.
PDO или mysqli - уже неплохие классы для работы с БД. Усовершенствовать можно, только надо сначала задачу поставить - чем тебя не устраивают эти классы, что ты хочешь сделать? Query Builder, как в Yii2 и Laravel, или просто удобную подстановку массивов в плейсхолдеры, как в SafeSQL, или как там его. Ну если делать что-то универсальное, то не синглтон. Ведь может потребоваться несколько подключений к БД одновременно. Соответственно, нужно будет несколько экземпляров этого класса создать. В тех же фрейморках - не синглтоны, если что.
вот я для себя начинал делать.. правда еще не доделал.. плюс там костыли есть.. но задумка такая) делал максимально просто что бы избавиться от синтаксиса mysqli который мне не нравится) PHP: /** * * @param string $query - запрос * с плейсхолдерами int|float|string|array_int|array_string * id={int:id} AND id_member IN(array_ind:ids) * @param array $values массив с даными для плейсходеров * [ id=>1, ids=>[1,2,3,4,5] ] * @return object mysqli_result */ public function dbQuery(string $query, array $values) { /* Типы плейсхолдеров */ $pattern = '~{(int|float|string|array_int|array_string):(\S+?)}~'; /* Выбираем из строки запрос возможные плейсходеры */ preg_match_all($pattern, $query, $matches, PREG_SET_ORDER); /* Инициализируем массив с данными плейсхолтера */ $prepareData = []; /* и массив с типами плейсхолдера mysqli * i - соответствующая переменная имеет тип integer * d - соответствующая переменная имеет тип double * s - соответствующая переменная имеет тип string * b - соответствующая переменная является большим двоичным объектом (blob) и будет пересылаться пакетами */ $typesPlaceholders = ''; /* Обходим найденные плейсхолдеры */ foreach ($matches as $ph) { /* Временный плейсхоллер нужен для типов плейсхолдеров array_XXX знак вопроса ? для самого запроса */ $tmpPlaceholers = ''; switch ($ph['1']) { case 'int': $typesPlaceholders .= 'i'; $prepareData[] = (int) $values[$ph['2']]; $tmpPlaceholers = '?'; break; case 'array_int': $tmpPlaceholers = ''; foreach ($values[$ph['2']] as $intVal) { $typesPlaceholders .= 'i'; $prepareData[] = (int) $intVal; $tmpPlaceholers .= '?,'; } $tmpPlaceholers = trim($tmpPlaceholers, ','); break; case 'string': $typesPlaceholders .= 's'; $prepareData[] = (string) $values[$ph['2']]; $tmpPlaceholers = '?'; break; default: die('Неверный тип даннных ' . $ph['1']); break; } /* Заменили плейсхолдер на нужное количество знаков вопросов */ $query = str_replace($ph['0'], $tmpPlaceholers, $query); } /* Заменили плейсхолдер префикса таблицы на сам префикс */ $query = str_replace('{db_prefix}', Config::get('dbPrefix'), $query); /* Собрали массив для вызова метода */ $params['db_types'] = $typesPlaceholders; foreach ($prepareData as $key => $val) { $params['db_' . $key] = &$prepareData[$key]; } /* Готовим запрос и выполняем */ $stmt = App::$db->stmt_init(); $stmt->prepare($query); // var_dump($stmt->error); if (!empty($params['db_types'])) { call_user_func_array([$stmt, 'bind_param'], $params); } $stmt->execute(); //Если INSERT то возвращаем то что показал stmp if (!empty($stmt->insert_id)) { $result = new \stdClass(); foreach ($stmt as $key => $value) { $result->$key = $stmt->$key; } } //Если запрос был SELECT, UPDATE, DELETE - тогда берем результат else { $result = $stmt->get_result(); } $stmt->close(); return $result; } --- Добавлено --- сделал поверх этого метода еще вот такой вот метод (это тоже пример) PHP: public function getRows($query, $values) { $result = $this->dbQuery($query, $values); $rows = []; if ($this->dbNumRows($result) > 0) { while ($row = $this->dbFetchAssoc($result)) { $rows[] = $row; } } $this->dbFreeResult($result); return $rows; } и потом если хочу получить какие то данные уже в модели то делаю вот так вот.. PHP: /** * * @param int $user_id * @param int $offset * @param int $limit * @return type */ public function getByUserId(int $user_id, int $offset = 0, int $limit = 0) { $queryLimit = (empty($offset) && empty($limit)) ? ('') : ('' . $offset . ', ' . $limit); return $this->getRows('SELECT us.id AS site_id, us.name AS site_name, us.blank_id, us.params, sb.name AS blank_name, usr.id AS user_id, usr.name AS user_name FROM {db_prefix}user_site AS us LEFT JOIN {db_prefix}site_blank AS sb ON(sb.id = us.blank_id) LEFT JOIN {db_prefix}user AS usr ON(usr.id = us.user_id) WHERE us.user_id={int:user_id} ' . $queryLimit . '', [ 'user_id' => $user_id ]); } --- Добавлено --- как видишь в самой модели надо только запрос написать с удобными плейсхолдерами и отдельно предоставить данные для плейсхолдеров.. вообще я решил его делать, эту прослойку, в первую очередь изза плейсхолдера array_int )) мне очень не хватало в mysqli тупо отправки в запрос массива)
и тут приходит Мышка со своей Lerma Правда еще не залил dev обновленку с планшетуса, там имба функционал, мона плейсхолдеры вставлять SELECT * FROM table WHERE id = :id
у Мауса есть Лерма.. там более продвинуто чем у меня.. --- Добавлено --- сделай плейсхолдеры по моему примеру)) удобно вроде)) и тип сразу указываешь))
так у меня вроде плейсхолдеры.. только вместо знака вопроса и отдельно типов данных - у меня {int:id}
От плейсхолдеров тоже по хорошему бы отказаться, из - за лишних действий, где программист сам может лишнюю переменную повторно пропихнуть. --- Добавлено --- пробежался глазами, увидел. Так и так у тебя всеголишь конструкция другая, куда данные впиливаться будут, да и еще самому типы указывать - Это плагин / мод / дополнение. От таких супер пупер пожалуй откажусь и дам Лерме самой определять какой тип приходит, если не понравится - посылает куда надо.
фиг его знает.. мне с одной стороны нравится реализация работы с базой в том же AR.. а с другой стороны поработаешь с AR забываешь mysql)) --- Добавлено --- ну да.. мне показалось так удобнее)) а лерму посмотрю) все время никак нет..
Обновил... Готово. PHP: <?php use Aero\Supports\Lerma; Lerma::prepare ( 'SELECT * FROM table WHERE cr = :one OR pr = :one', [ ':one' => 123 ] ) -> fetchAll( Lerma::FETCH_KEY_PAIR | Lerma::FETCH_NAMED ); /* ... */ Спойлер: Ещё PHP: <?php use Aero\Supports\Lerma; require '../autoload.php'; # ------------------------ /* fetch() FETCH_NUM FETCH_ASSOC FETCH_OBJ FETCH_BIND FETCH_BIND | FETCH_COLUMN FETCH_COLUMN FETCH_KEY_PAIR FETCH_FUNC FETCH_CLASS FETCH_CLASSTYPE fetchAll() FETCH_NUM FETCH_ASSOC FETCH_OBJ FETCH_COLUMN FETCH_KEY_PAIR FETCH_KEY_PAIR | FETCH_NAMED FETCH_UNIQUE FETCH_GROUP FETCH_GROUP | FETCH_COLUMN FETCH_FUNC FETCH_CLASS FETCH_CLASSTYPE Lerma::FETCH_CLASSTYPE | Lerma::FETCH_UNIQUE */ # ------------------------ $table = 'lerma'; $sql = [ 'SELECT * FROM %s', $table ]; # or 'SELECT * FROM lerma' $query = Lerma::query( $sql ) -> fetchAll( Lerma::FETCH_OBJ ); # ------------------------ $sql = [ [ 'SELECT * FROM %s WHERE id IN ( :id,?,?,? )', $table ], [ 3,9,81,':id'=>1 ] ]; $prepare = Lerma::prepare( ...$sql ) -> fetchAll( Lerma::FETCH_OBJ ); # ------------------------ print_r ( compact ( 'query', 'prepare' ) ); --- Добавлено --- http://phpfaq.ru/pdo/fetch constants
Нашел такой пример подключения к базе (хотя ищу пример не только подключения к бд, но и обработке запросов), вроде бы на уважаемом ресурсе, но, попытался использовать код - он не рабочий. Даже с правками - все равно не работает: Я что-то в нем не понимаю, или пример откровенная хрень? Например там указан __construct () у класса, к которому обращаются через статический метод. Разумеется, __construct () не отрабатывает. Складывается ощущение, что его автор даже азы ООП не знает.
там есть return new self; вот когда конструктор срабатывает.. а так вроде нормальный пример.. синглтон..
Спасибо. На этот момент тоже указал знакомый программист, и что после этого к статическому свойству можно обращаться через $this-> Но, все равно непонятно что это такое: PHP: private static DB_HOST = ''; private static DB_NAME = ''; private static DB_USER = ''; private static DB_PASS = ''; Если это переменная - не хватает $, если константа - она задается по-другому. И при попытке запустить код (поправив строчки выше), получаю: Strict Standards: Accessing static property DB::$_instance as non static in /var/www/....php on line 15 bool(false) +к этому в коде используется нестрогое сравнение (==, !=), меня учили использовать только строгое, что нестрогие сравнения - первый шаг к говнокоду.
Поговорили со знакомым программистом еще на эту тему, что пример, на который ссылался выше, так или иначе не работает - он таки сказал, что да, пример говно и обращаться к статическому свойству через $this-> нельзя.