ХОЖДЕНИЕ ПО БАЗАМ.

1928
Статья в помощь таким же нубам, как и не я. Поэтому при наездах попрошу сильно не умничать.
Решил я для себя набросать компонент для музыкальной странички. Но вот что то шибко много получилось запросов к БД. И тогда я решил сначала их поделить, но затем подумав пришёл к выводу, что будет лучше вообще посадить компонент на другую базу. Что бы «не мешался под ногами». А так как программер из меня не ахти, пришлось затарится травой и сесть курить манны.

Примерно к окончанию вторых суток я понял, что «лучше б маленьким я сдох», ибо настала пора выбрать какое использовать расширение для моего подключения. И здесь пришлось принимать во внимание не только качество этих самых расширений, но и уровень моей подготовки. Поэтому как бы мне не хотелось попробовать этот класс, но у него все пояснения только на чешском. Был просмотрен ещё зтот, но что то я не проникся. В конце концов встал выбор Mysqli или PDO. Последний критиковали поменьше, поэтому выбор пал на него. Да и хотелось попробовать что то новое, с одной стороны, а с другой,что бы присутствовала совместимость. Мануал на русском здесь. Статья на хабре здесь. В двух словах из контекста.
Расширение Объекты данных PHP (PDO) определяет простой и согласованный интерфейс для доступа к базам данных в PHP.
PDO обеспечивает абстракцию (доступа к данным). Это значит, что вне зависимости от того, какая конкретная база данных используется, вы можете пользоваться одними и теми функциями для выполнения запросов и выборки данных.
Сказано-сделано. Ещё пару дней курения и хождения по простором страны Google и оформился классдля подключения базы к нашей, горячо любимой системе. Как говорится в какой то рекламе, «теперь стало ещё легче», или как то так. Короче я сделал так.
Поставил на hosts Ubuntu Server 14.04. и такой же на гостевую на Vbox.
Залил на них мой сайт.
Для начала я решил попробовать перенести скрипт для работы с файлами(загрузка, проверки, редактирование и т. п.) и сами файлы на гостевой сервер. Так как на гостевой мне нужна была только страница загрузки файлов моего компонента, поэтому все лишние модули, компоненты и таблицы я удалил.
Закинул класс в соответственное для него место на хосте /core/classes/.
Способов для подключения второй базы есть несколько. Я выбрал следующий. В /includes/ поместил файл конфигурации conf.ini с настройками для подключения.
 
Код PHP:
  1. db_driver=mysql
  2. db_user=nik
  3. db_password=123456  
  4. [dsn]
  5. host=music2
  6. port=3306
  7. dbname=music2
  8. [db_options]
  9. PDO::MYSQL_ATTR_INIT_COMMAND=set names utf8
  10. [db_attributes]
  11. ATTR_ERRMODE=ERRMODE_EXCEPTION
Здесь, думаю всё понятно.
Код для подключения такой:
Код PHP:
  1.  
  2. $DBH = new PDO("mssql:host=$host;dbname=$dbname", $user, $pass); // красавец!
В методе класса он превращается в такой:
Код PHP:
  1.  
  2. public static function getLink ( ) {
  3. $ini = PATH.'/includes/config.ini' ;
  4. $parse = parse_ini_file ( $ini , true ) ;
  5. $driver = $parse [ "db_driver" ] ;
  6. $dsn = "${driver}:" ;
  7. $user = $parse [ "db_user" ] ;
  8. $password = $parse [ "db_password" ] ;
  9. $options = $parse [ "db_options" ] ;
  10. $attributes = $parse [ "db_attributes" ] ;
  11. foreach ( $parse [ "dsn" ] as $k => $v ) {
  12. $dsn .= "${k}=${v};" ;
  13. }
  14. [b]$db_link = new PDO ( $dsn, $user, $password, $options ) ; [/b]
  15. $db_link->exec('SET NAMES utf8');
  16. foreach ( $attributes as $k => $v ) {
  17. $db_link -> setAttribute ( constant ( "PDO::{$k}" )
  18. , constant ( "PDO::{$v}" ) ) ;
  19. }
  20. return $db_link ;
  21. }
Далее, все подготовительные запросы, я решил тоже помещать в этот класс, так как пока тестируемый компонент только один. А так, удобней им наверное будет в model.php. Разница в синтаксисе не большая. Для примера:
Mysqli
Код PHP:
  1. public function get_table($table, $where='', $fields='*'){
  2. $list = array();
  3. $sql = "SELECT $fields FROM $table";
  4. if ($where) { $sql .= ' WHERE '.$where; }
  5. $result = $this->query($sql);
  6. if ($this->num_rows($result)){
  7. while($data = $this->fetch_assoc($result)){
  8. $list[] = $data;
  9. }
  10. return $list;
  11. } else {
  12. return false;
  13. }  
  14. }
 
PDO
Код PHP:
  1. public function get_table($table, $where='', $fields='*'){
  2. $db_link = self::getLink();
  3. $list = array();
  4. $sql = "SELECT $fields FROM $table";
  5. if ($where) { $sql .= ' WHERE '.$where; }
  6. $result = $db_link->query($sql);
  7. while($data = $result->fetch()){
  8. $list[] = $data;
  9. }
  10. return $list;
  11. }
Вот собственно и всё. Ещё немного о безопасности. В системе встречаются не подготовленные запросы. Т.е. запросы формирующиеся в самом скрипте.В основном это INSERT. Например в файле /components/users/file.php 
Код PHP:
  1. $sql = "INSERT INTO cms_user_files(user_id, filename, pubdate, allow_who, filesize, hits, vimage,time,title)
  2. VALUES ({$usr['id']}, '$name', NOW(), 'all', '$size', 0, '$vimage','$time', '$uname')";
В таких случаях безопасней использовать placeholder. Для PDO данный запрос выглядит так
Код PHP:
  1. $STH = $DBH->prepare("INSERT INTO cms_user_files(user_id, filename, pubdate, allow_who, filesize, hits, vimage,time,title)
  2. VALUES ({$usr['id']}, '$name', NOW(), 'all', '$size', 0, '$vimage','$time', '$uname')");
  3. $STH->execute();
Двухшаговый способ дает все преимущества prepared statements(связываемые переменные).
Использование prepared statements укрепляет защиту от SQL-инъекций.
Так как теоритически становится невозможно провести SQL-инъекцию через данные, используемые в placeholder’ах. Вот так выглядет более «безопасный» вариант.
 
Код PHP:
  1. $data = array(user_id, filename, pubdate, allow_who, filesize, hits, vimage,time,title); 
  2. $STH = $DBH->prepare( "INSERT INTO cms_user_files(user_id, filename, pubdate, allow_who, filesize, hits, vimage,time,title)
  3. VALUES (?,?,?,?,?,?,?,?,?)");
  4. $STH->execute($data);
Вот теперь пожалуй и всё. Надеюсь моя писанина поможет тому,кто думает попробовать тоже, что и я.
 
История рейтинга комментариев
Комментарии (24)
r2 2 октября 2014 в 14:28 +6
Не проще ли было занаследовать cmsDatabase и переопределить только метод initConnection()? Чтобы не изобретать велосипед
Lora 2 октября 2014 в 15:38 +3
Наверное, да. Но вот с этим наследием пока не разобрался. Разберусь и поправлю. Так возможно и осилю все эти процедуры,методы, ф-ции и классы. smile
r2 2 октября 2014 в 19:30 +4
С наследием все просто. Допустим есть файл first.php с таким классом:
Код PHP:
  1. //first.php
  2. class first {
  3. public function sayOne() {
  4. echo '111';
  5. }
  6. }
тогда можно создать другой класс в файле second.php:
Код PHP:
  1. //second.php
  2. class second extends first {
  3. public function sayTwo() {
  4. echo '222';
  5. }
  6. }
Потом вы подключаете оба файла и создаете объект второго класса:
Код PHP:
  1. include 'first.php'; include 'second.php';
  2. $obj = new second();
Этот объект будет иметь все методы своего класса, и всех родительских:
Код PHP:
  1.  
  2. $obj->sayOne();
  3. $obj->sayTwo();
  4. // выведет 111222;
Из родительского класса доступны только публичные (public) и защищенные (protected) методы.
Lora 2 октября 2014 в 20:14 +1
Спасибо, с этим понятно, но при применении к данному случаю приходится private function __construct() прописывать в дочернем классе и превращать в public function __construct(), иначе ругается. Это на что нибудь отразится?
r2 2 октября 2014 в 21:03 +1
да, при наследовании от cmsDatabase вы должны определить свой конструктор, в котором можете сразу создать подключение к нужной базе. Алгоритм такой:
1. Создаете файл /core/classes/mydb.class.php
2. Определяете в нем класс, наследуемый от cmsDatabase:
Код PHP:
  1. class myDatabase extends cmsDatabase {
  2. public function __construct() {
  3. // ... получаете реквизиты из конфига ...
  4. // и создаете соединение:
  5. $this->db_link = mysqli_connect($host, $user, $pass, $base);
  6. }
  7. }
3. В нужном месте вашего компонента подключаете свой класс:
Код PHP:
  1. cmsCore::loadClass('mydb');
4. Создаете и используете объект своего класса. У него будут доступны все методы cmsDatabase:
Код PHP:
  1. $myDB = new myDatabase();
  2. $result = $myDB->query("SELECT * FROM table");
  3. // и т.д.
Lora 2 октября 2014 в 21:48 0
С базой всё получилось, а вот с model.php не хочет. Запутался в переменных. В конструкторе model.php пишу
Код PHP:
  1. cmsCore::loadClass('my_pdo');
  2. $this->inDB_S = new cmsMy_pdo();
при
Код PHP:
  1. $result = $this->inDB_S->query($sql);
Fatal error
Fuze 2 октября 2014 в 23:22 0
Fatal error
Так а текст ошибки то какой, на что ругается? Метод query в вашем классе есть?
Код PHP:
  1. $result = $this->inDB_S->query($sql);
Где пишите?

Вы в объекте модели (это так же обычный класс) создаете свойство inDB_S, в которое помещаете объект своего класса работы с базой. Если это свойство вы не определили как приватное, то к нему можно обращаться через объект модели извне, например $model->inDB_S->название_метода(); Или же внутри самого класса модели, в его методах через $this, при условии, что метод, из которого вы обращаетесь к свойству объекта не статический.
Lora 3 октября 2014 в 01:20 0
Вроде так и делаю.
Код PHP:
  1. class cmsMy_pdo extends cmsDatabase {
  2.  
  3. public function __construct(){
  4. $this->db_link = self::initConnection();
  5. }
  6.  
  7. private static function initConnection(){
  8. ....................................................................
  9. $db_link = new PDO ( $dsn, $user, $password, $options ) ;
  10. ........................................................
  11. }
class cms_model_music{

Код PHP:
  1. public function __construct(){
  2. cmsCore::loadClass('my_pdo');
  3. $this->inDB_S = new cmsMy_pdo();
  4. $DB = $this->inDB_S;
  5. }
  6. public function selectMusic_Albums(){
  7. .......................................
  8. $sql =("SELECT 1");
  9. $result = $DB->query($sql); // ругается на эту строку
  10. }
  11. }
Код PHP:
  1. Fatal error: Call to a member function query() on a non-object in
Я понимаю, что что то с этими $this, но что..., так как если я подключаю свой вариант класса на прямую, то всё работает. Ладно, утро вечера мудренее.
r2 3 октября 2014 в 09:28 0
Так вы объявляете локальную переменную $DB в конструкторе, а затем в другом методе пытаетесь к ней обратиться. Разумеется, будет ошибка. У вас должно быть обращение к $this->inDB_S, а не к $DB, которая в этом методе не существует.
Lora 3 октября 2014 в 17:18 0
С утра отключили сеть, так что "от нечего делать" я допёр, что
Код PHP:
  1. $DB = $this->inDB_S;
не работает. Но прикол в том, что
Код PHP:
  1. $this->inDB_S
тоже не работает, а работает только так
Код PHP:
  1.  
  2. $result = $this->inDB_S->db_link->query($sql); // в модели
  3. $result = $this->db_link->query($sql); // в class cmsMy_pdo extends cmsDatabase
  4.  
Подскажите можно ли как то избавиться от этого тройного обращения? ( Поэтому я и пытался $this->db_link поместить в переменную $DB).
Fuze 3 октября 2014 в 17:57 0
Подскажите можно ли как то избавиться от этого тройного обращения?
так это же ваш код, мы не можем знать зачем вы так обращаетесь. Полный листинг вашего кода никто не видел, а пальцем в небо "тыкать" можно долго.
не работает. Но прикол в том, что
дак ясное дело, r2 выше вам и описал почему так.
тоже не работает
значит в $this->inDB_S не объект базы со всеми методами, а что-то, ведомое только вам, некий объект класса cmsMy_pdo. В общем, вам надо прекратить делать методом научного тыка и пытаться понять что вы пишите.
а работает только так
так правильно, потому что вы сами же отправили в свойство вашего класса объект PDO судя по всему, ибо я не вижу что возвращает ваш метод initConnection. Иными словами ваш класс cmsMy_pdo не выполняет ничего и смысла в нем нет никакого. Проще в модели сразу подключать объект PDO
Код PHP:
  1. $this->inDB_S = new PDO ( $dsn, $user, $password, $options );
$this->db_link - тут подразумевается, что должен быть указатель соединения с базой, возвращаемый например функцией mysqli_connect и подобными.
Lora 3 октября 2014 в 19:01 0
Что значит
cmsMy_pdo не выполняет ничего и смысла в нем нет никакого
. Это дочерний класс cmsDatabase.
Спойлер
Вот и весь код.Соединение с базой он устанавливает, это главное, а вот отношение с родителем не важные. И в общем я понимаю что делаю, другой вопрос в уровне этого понимания, и в знании системы,но это дело времени.
В любом случае спасибо за помощь.
r2 3 октября 2014 в 19:49 +1
Так вы определитесь что вы хотите использовать PDO или cmsDatabase. Оба этих класса имеют реализацию метода query(). Если вы наследуетесь от cmsDatabase то в $this->db_link должен быть линк на подключение, который возвращается из mysqli_connect(). Нет смысла в db_link пихать объект PDO. Смысл наследования от cmsDatabase как раз заключается в том, чтобы обойтись без всяких PDO, а использовать стандартный класс системы, только подключенный к другой базе.
Lora 3 октября 2014 в 20:03 0
Понятно.
maxisoft 3 октября 2014 в 20:26 0
и то и другое, и всего побольше. Это как в мультике про Палкана.
Lora 3 октября 2014 в 21:21 0
Да нет, дело не в том, что бы побольше, а в том, что бы посадержательнее, т.к. пост писался не для знающих, а для стремящихся. Как бы там ни было это мой первый пост и тем не менее привлёк столько внимания уважаемых людей. За это им спасибо и тем спасибо кто язвил и подкалывал, ибо это тоже своего рода мативация для движения вперёд. laugh
Fuze 3 октября 2014 в 20:38 0
Вот и весь код
который подтвердил мои предположения о ненужности представленного вами класса.
В общем, в вашем случае, что пытается до вас донести r2, нужно сделать примерно вот так (актуально для InstantCMS 1.10.4 и php 5.3):
1. в системном классе cmsDatabase изменить строки
Код PHP:
  1. private function __construct(){
  2. $this->db_link = self::initConnection();
  3. $this->db_prefix = cmsConfig::getConfig('db_prefix').'_';
  4. }
на
Код PHP:
  1. protected function __construct(){ // сменили область видимости метода
  2. $this->db_link = static::initConnection();
  3. $this->db_prefix = cmsConfig::getConfig('db_prefix').'_';
  4. }
строку
Код PHP:
  1. private static function initConnection(){
заменить на
Код PHP:
  1. protected static function initConnection(){
Все вышеперечисленное будет в системе со следующего релиза.
2. Создать свой класс (имя файла например mydb.class.php), для подключения другой базы
Код PHP:
  1. <?php
  2. class cmsMyDatabase extends cmsDatabase {
  3.  
  4. private static $this_instance;
  5.  
  6. protected function __construct(){
  7. parent::__construct();
  8. }
  9.  
  10. public static function getInstance() {
  11. if (self::$this_instance === null) {
  12. self::$this_instance = new self;
  13. }
  14. return self::$this_instance;
  15. }
  16.  
  17. protected static function initConnection(){
  18.  
  19. $ini = PATH.'/includes/config.ini';
  20.  
  21. $parse = parse_ini_file($ini , true) ;
  22.  
  23. $db_link = mysqli_connect($parse['db_host'],
    $parse['db_user'],
    $parse['db_password'],
    $parse['db_base']);
  24.  
  25. die('Cannot connect to MySQL server: ' . mysqli_connect_error());
  26. }
  27.  
  28. mysqli_set_charset($db_link, 'utf8');
  29.  
  30. return $db_link;
  31.  
  32. }
  33.  
  34. }
3. Использовать этот класс например так (в модели):
в конструкторе:
Код PHP:
  1. cmsCore::loadClass('mydb');
  2. $this->myDB = cmsMyDatabase::getInstance();
в методах модели:
Код PHP:
  1. $result = $this->myDB->query('SELECT 1');
p.s. писал тут в окне, не проверял, но по идее все должно работать, если синтаксических ошибок не допустил)
Lora 3 октября 2014 в 21:25 0
В принципе у меня так и получилось, только я не в private изменил, в public. И в начале не понял r2 о смысле использования дочернего класса. Из за этого и затупил.
Lora 3 октября 2014 в 21:42 0
И наверное в модели
Код PHP:
  1. $this->myDB = new cmsMydb();
вместо
Код PHP:
  1. $this->myDB = cmsMyDatabase::getInstance();
?
Fuze 3 октября 2014 в 23:42 0
И наверное в модели
нет (будет фатал еррор), исходя из моего примера так работать не будет, да и наличие синглтона гарантирует, что соединение с базой будет один раз.
Lora 4 октября 2014 в 03:11 0
Красиво, особенно сразу не заметный синглтон. Разве для него не обязательны __clone и __wakeup?
Lora 2 октября 2014 в 15:46 +2
P.S. Спасибо за совет.
Alexneva 2 октября 2014 в 17:20 +6
Я так и не понял для чего все эти танцы... зачем компонент в другую базу пихать?
Lora 2 октября 2014 в 18:20 +2
Что бы когда приспичит, знать что и как делать.