Абстракционен слой на базата данни на PDO с множество заявки в една заявка

Създадох Слой на абстракция на база данни върху PDO, за да се въздържа от създаване на множество заявки около моите скриптове, което би било доста трудно за поддържане.

Моят DBAL не е много широк; Той се грижи за прости задачи като ВМЪКВАНЕ, АКТУАЛИЗИРАНЕ и ИЗБОР (със или без присъединяване). Не обхваща по-сложни неща, като избор от множество таблици и т.н.

Проблемът, който възникна с моя DBAL, е, че е объркващо заявките, когато има повече от същия тип в една HTTP заявка. Например има три оператора за избор в моя скрипт, първият работи, другите два не. Опитах се да създам метод flush за изчистване на предварително попълнените атрибути от заявката, но не работи и ми липсват идеи. Не съм готов да се отърва от моя клас и да се върна към писането на заявки навсякъде - толкова е лесно да ги пиша по този начин.

Както и да е, ето как правя някои запитвания с моя клас:

$insert_update_select = array(
    'COLUMNS' => array(
        'column_name1' => 'data_to_update_or_insert1', 
        'column_name2' => 'data_to_update_or_insert2'
    ),
    'WHERE' => array('x > y', 'y < x'),
    'ORDER' => array('ASC' => 'column_name1'),
    'LIMIT' => array(0, 5),
);

// This query works with updating, inserting and selecting
$db = new db();

$db->insert('table_name', $insert_update_select);
$db->update('table_name', $insert_update_select);
$db->select('table_name', $insert_update_select);

Не ме питайте как да обединявам маси; Всъщност забравих как работи собственият ми синтаксис за това, хаха. (Трябва да се опитам да си спомня)

Както и да е, ето моят клас:

<?php
class db
{   
    private $db_type = 'mysql';
    private $db_host = 'localhost';
    private $db_user = 'root';
    private $db_pass = '';
    private $db_name = 'imgzer';

    private $db;
    private $db_connection      = '';
    private $insert_data        = '';
    private $update_data        = '';
    private $select_data        = '';
    private $condition_data     = '';
    private $order_data         = '';
    private $limit_data         = '';   
    private $join_data          = array();
    private $query;
    private $table;

    private $return_data;
    private $affected_rows;
    private $return_id;

    // Database tables
    const USERS_TABLE   = 'imgzer_users';
    const CONFIG_TABLE  = 'imgzer_config';

    public function __construct()
    {
        $this->db_connection = "$this->db_type:host=$this->db_host;dbname=$this->db_name";

        $this->db = new PDO($this->db_connection, $this->db_user, $this->db_pass);
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 

        unset($this->db_pass);
    }

    public function open()
    {
        if ($this->db)
        {
            return true;    
        }

        return false;
    }

    public function close()
    {
        if ($this->db->close())
        {
            return true;    
        }

        return false;
    }

    private function build_array($type, $data, $join_data = array())
    {
        if (empty($data))
        {
            return; 
        }

        $type = strtoupper($type);              

        $this->update_data = '';
        $this->select_data = '';

        $data_index  = 0;
        $data_length = sizeof($data);
        $last_row    = $data_length - 1;

        switch ($type)
        {
            case 'INSERT':

                if (!is_array($data))
                {
                    return;     
                }

                $this->insert_data = '(';

                foreach ($data as $column => $value)
                {
                    $this->insert_data .= $column . (($data_index != $last_row) ? ', ' : '');                   
                    $data_index++;
                }

                $data_index  = 0;

                $this->insert_data .= ') ';
                $this->insert_data .= 'VALUES (';

                foreach ($data as $column => $value)
                {
                    $this->insert_data .= '?' . (($data_index != $last_row) ? ', ' : '');   
                    $data_index++;
                }

                $this->insert_data .= ') ';

            break;  

            case 'UPDATE':

                $this->update_data = '';

                foreach ($data as $column => $value)
                {
                    $this->update_data .= $column . ' = ?' . (($data_index != $last_row) ? ', ' : '');
                    $data_index++;
                }

            break;

            case 'SELECT':

                if (empty($join_data))
                {
                    return; 
                }

                if (is_array($join_data))
                {
                    $from_table = array_keys($join_data['FROM']);
                    $join_table = array_keys($join_data['TABLES']);

                    $this->select_data = implode(', ', array_flip($data)) . ' FROM ' ;
                    $this->select_data .= $from_table[0] . ' ' . $join_data['FROM'][$from_table[0]] . ' ';

                    for ($i = 0; $i < sizeof($this->join_data); $i++)
                    {
                        $this->select_data .= $this->get_join_type($join_data['JOIN']). ' ';
                        $this->select_data .= $join_table[$i] . ' ' . $join_data['TABLES'][$join_table[$i]];
                        $this->select_data .= $this->join_data[$i]; 
                    }

                    $this->select_data = rtrim($this->select_data, ' ');
                }
                else
                {
                    if (!isset($data[0]))
                    {
                        $data = array_flip($data);
                    }

                    $this->select_data = implode(', ', $data) . ' FROM ' . $this->table . ' ';
                }

            break;
        }
    }

    private function set_join($on)
    {
        if (empty($on))
        {
            return; 
        }

        if (is_array($on))
        {
            for ($i = 0; $i < sizeof($on); $i++)
            {
                $on[$i] = ' ON (' . implode(' AND ', $on[$i]) . ') ';   
            }   
        }

        $this->join_data = $on;
    }

    private function set_order($order)
    {
        if (empty($order))
        {
            return; 
        }

        $this->order_data = ' ORDER BY ';

        if (is_array($order))
        {
            $data_index = 0;
            $data_size  = sizeof($order) - 1;

            foreach ($order as $order_type => $column)
            {
                if ($order_type != 'ASC' && $order_type != 'DESC')
                {
                    throw new Exception('Order type in SQL has to be either ASC or DESC');
                    return; 
                }

                $this->order_data .= $column . ' ' . $order_type . (($data_index != $data_size) ? ', ' : ''); 
                $data_index++;
            }

            return;
        }

        $this->order_data .= $order;
    }

    private function set_limit($limit)
    {
        if (empty($limit))
        {
            return; 
        }

        if (sizeof($limit) > 2)
        {
            return; 
        }

        if (sizeof($limit) == 1)
        {
            $limit = array(0, $limit[0]);   
        }

        if (is_array($limit))
        {
            $limit = implode(', ', $limit); 
        }

        $this->limit_data = " LIMIT {$limit}";
    }

    private function set_where($condition)
    {
        if (empty($condition))
        {
            return; 
        }

        if (is_array($condition))
        {
            $condition = implode(' AND ', $condition);  
        }

        $this->condition_data = " WHERE $condition";
    }

    public function in_set($where_ary)
    {
        $where_str = implode(', ', $where_ary); 
        $where_str = substr($where_str, 0, -2);

        $where_str = 'IN (' . $where_str . ')';

        return $where_str;
    }

    /*
    * Example usage:
    * $insert_ary = array('col_1' => 'col_data_1', 'col_2' => 'col_data_2');
    * $condition_ary = array('col_1 > 5', 'col_2 <> 10');
    * $order_ary = array('ASC' => 'col_1', 'DESC' => 'col_2');
    * $limit = array($start = 0, $limit = 5);
    * $instance->insert('my_table', $insert_ary, $condition_ary, $order_ary, $limit);
    */
    public function insert($table, $data, $return_id = false)
    {
        $data = $this->data_abstract($data);

        // Prepare the arrays
        $this->build_array('INSERT', $data['COLUMNS']);
        $this->set_where($data['WHERE']);
        $this->set_order($data['ORDER']);
        $this->set_limit($data['LIMIT']);

        $sql = 'INSERT INTO ' . $table . ' ';
        $sql .= $this->insert_data;
        $sql .= $this->condition_data;
        $sql .= $this->order_data;
        $sql .= $this->limit_data;

        $this->query = $this->db->prepare($sql);

        $param_index = 1;

        foreach ($data['COLUMNS'] as $column => &$value)
        {           
            $this->query->bindParam($param_index, $value);  
            $param_index++;
        }

        $this->query->execute();

        if ($return_id)
        {
            $this->return_id = $this->query->last_insert_id();
        }
        else
        {
            $this->affected_rows = $this->query->rowCount();
        }
    }

    public function update($table, $data, $return_id = false)
    {
        $data = $this->data_abstract($data);

        // Prepare the arrays
        $this->build_array('UPDATE', $data['COLUMNS']);
        $this->set_where($data['WHERE']);
        $this->set_order($data['ORDER']);
        $this->set_limit($data['LIMIT']);

        $sql = 'UPDATE ' . $table . ' SET ';
        $sql .= $this->update_data;
        $sql .= $this->condition_data;
        $sql .= $this->order_data;
        $sql .= $this->limit_data;

        $this->query = $this->db->prepare($sql);

        $param_index = 1;

        foreach ($data['COLUMNS'] as $column => &$value)
        {           
            $this->query->bindParam($param_index, $value);  
            $param_index++;
        }

        $this->query->execute();

        if ($return_data)
        {
            $this->return_id = $this->query->last_insert_id();
        }
        else
        {
            $this->affected_rows = $this->query->rowCount();
        }
    }

    /*
    * Joining example:
    * $join_data = array(
    *   'TABLES'    => array('table_2' => 't2', 'table_3' => 't3'),
    *   'JOIN'      => 'LEFT',
    *   'ON'        => array(
    *                   array('colx > 15', 'coly < 20'),
    *                   array('fieldx > 15', 'fieldy < 20')
    *               ),
    *);
    */
    public function select($table, $data, $join = false, $fetch_type = 'assoc')
    {       
        $data = $this->data_abstract($data);

        if ($join)
        {
            if (!is_array($table))
            {
                throw new Exception('Table has to be associated with a short index');
                return; 
            }

            $this->set_join($join['ON']);
            $table = array_merge(array('FROM' => $table), $join);
        }

        // Globalize table name if not joins are used
        $this->table = $table;

        // Prepare the arrays
        $this->build_array('SELECT', $data['COLUMNS'], $table);
        $this->set_where($data['WHERE']);
        $this->set_order($data['ORDER']);
        $this->set_limit($data['LIMIT']);

        $sql = 'SELECT ';
        $sql .= $this->select_data;
        $sql .= $this->condition_data;
        $sql .= $this->order_data;
        $sql .= $this->limit_data;

        $this->query = $this->db->prepare($sql);                    
        $result = $this->query->execute();

        $fetch_type     = ($fetch_type == 'assoc') ? PDO::FETCH_ASSOC : PDO::FETCH_NUM;
        $fetched_data   = $this->query->fetchAll($fetch_type);
        $data_result    = $fetched_data;

        if (sizeof($fetched_data) == 1)
        {
            $data_result = $fetched_data[0];
        }

        $this->return_data = $data_result;

        // Clear the result
        //$this->query->closeCursor();
    }

    public function fetch()
    {
        return $this->return_data;
    }

    public function affected_rows()
    {
        return $this->affected_rows;
    }

    private function data_abstract($data)
    {
        $abstract_ary = array('COLUMNS' => '', 'WHERE' => '', 'ORDER' => '', 'LIMIT' => 0);
        return array_merge($abstract_ary, $data);   
    }

    private function get_join_type($type)
    {
        switch ($type)
        {
            default:
            case 'LEFT':
                return 'LEFT JOIN';
            break;
            case 'RIGHT':
                return 'RIGHT JOIN';
            break;  
            case 'INNER':
                return 'INNER JOIN';
            break;
            case 'NORMAL':
            case 'JOIN':
                return 'JOIN';
            break;  
        }
    }

    private function flush()
    {
        unset($this->query, $this->insert_data, $this->update_data, $this->select_data);
    }
}

$db = new db();
?>

Какво не е наред с него (може да е много) и как всъщност да го накарам да работи ефективно?


person aborted    schedule 18.12.2013    source източник
comment
Там има твърде много код. Можете ли да изберете само проблемната част?   -  person nice ass    schedule 19.12.2013
comment
Не мога да определя коя част е проблемна? Предполагам, че методът select би бил мястото, с което да започнете, тъй като той не поддържа множество връщания за една HTTP заявка   -  person aborted    schedule 19.12.2013


Отговори (1)


Не го правете състояние.

Дори без да гледам кода, ще ви кажа какъв е проблемът: отървете се от променливата $this->stmt.
По някаква причина всички автори на DBAL имат силна склонност към такава променлива... въвеждайки състояние в своя клас и по този начин правейки неизползваемо е.

Всички извиквания на метода трябва да бъдат атомични, като всяко от тях изпълнява всички необходими операции и връща всички поискани данни. Докато не записва нищо в променливите на класа. Толкова просто. В такъв рядък случай, когато PDOStatement обект трябва да се използва допълнително - върнете този обект, не го запазвайте вътре. В противен случай просто върнете исканите данни.

Също така бих ви посъветвал да се отървете от целия си DBAL, тъй като е написан от добри намерения, но мога да кажа със сигурност, че внедряването се оказва по-малко полезно, но всъщност прави кода ви по-лош в много аспекти - четливост, гъвкавост, поддръжка. В преследване на измислената използваемост вие си спестявате само дума или две от SQL, но правите целия код на приложението ненадежден.

Ти обаче няма да ме слушаш. Необходим е известен опит в поддържането на приложения, за да разбера мнението ми.

person Your Common Sense    schedule 19.12.2013
comment
Моля, предложете алтернативен метод за опростяване на процеса на писане на заявка, без да използвате ORM или нещо подобно. Има ли добър DBAL, базиран на PDO? - person aborted; 20.12.2013
comment
Няма много смисъл от създаването на DBAL над PDO, тъй като PDO вече е такъв. Това, което търсите, се нарича конструктор на заявки. Можете да намерите много примери, търсейки в Google за термина. въпреки че всъщност не се нуждаете дори от този. Не разбирам това силно желание на всички начинаещи програмисти да се отърват от ценния SQL. - person Your Common Sense; 20.12.2013
comment
Приемам отговора ви, тъй като включва много полезни (и честни) съвети. Отървах се от моя клас и отново започнах да използвам заявки навсякъде. Всъщност не съм чак толкова начинаещ програмист, просто нямам достатъчно време да стана професионалист. - person aborted; 21.12.2013