Class PHP tương tác với CSDL thông qua PDO

/ Lập trình web / Php & Mysql
Class PHP tương tác với CSDL thông qua PDO
1

Mục đich của class PHP mà chúng ta sắp tạo ra là để tương tác với hệ quản trị CSDL (cụ thể là MySQL). Mình tạm gọi tắt là tương tác với CSDL nhé. Tương tác với CSDL trong phạm vi bài viết này sẽ bao gồm các chức năng cơ bản như:

- Thêm dữ liệu vào các bảng.

- Xóa dữ liệu từ bảng.

- Cập nhật dữ liệu trong các bảng.

- Lấy dữ liệu từ các bảng, có hỗ trợ:

    + Lấy dữ liệu theo một hoặc nhiều điều kiện (where trong MySQL).

    + Giới hạn dữ liệu trả về (limit và offset trong MySQL).

    + Sắp xếp dữ liệu theo một hoặc nhiều cột (order by trong MySQL).

    + Nhóm dữ liệu theo một hoặc nhiều cột (group by trong MySQL).

 

Chúng ta sẽ xem qua code của class PHP dùng thao tác với CSDL trong MySQL:

class Db
{
    /*
     * The PDO connection using in this object to interact with database
     * You can specified the PDO connection in a method __construct() or setConnection() bellow
     * If you not specified the PDO connection, a new PDO connection will be create with config at define.php
     */
    protected static $connection;
    /*
     * The table you want to interact with
     * You can specified the table by method table() bellow
     */
    protected $table;
    /*
     * The error code when during in this object
     */
    protected $error = null;
    /*
     * The error message when during in this object
     */
    protected $errorText = null;
    /*
     * The statement that store the sql after binding value
     */
    protected $statement = null;
    /*
     * The result when exec the statement of this
     */
    private $result = null;
    /*
     * The where condition clause to using in get data, delete data, update data
     * You can add more one condition by using more than one time of method where bellow
     */
    private $where = [];
    /*
     * The fields that you want to get in get data
     * You can add more one fields by using more than one time of method select() bellow
     */
    private $select = [];
    /*
     * Ordering when get data
     * You can add more one order state by using method order() bellow
     */
    private $order = [];
    /*
     * Stored the position of row that you want to start get data from
     * You can specified the offset by using the method offset() 
     */
    private $offset = null;
    /*
     * Stored the number of row that you want to get data
     * You can specified this by using the method limit()
     */
    private $limit = null;
    /*
     * Stored the group clause to using in get data
     * You can add more than one group clase by using more than one method group()
     */
    private $group = [];

    /**
     * Create an instance of active record
     * You can passed the connection to handle database connection,
     * but you don't passed the connection, the connection with the parameter in define.php will use to create default connection.
     * 
     * @param object $connection PDO connection to use in active record
     */
    public function __construct($connection = null)
    {
        $this->error = null;
        $this->statement = null;
        if (null != $connection) {
            $this->setConnection($connection);
        } else if (!isset ($this::$connection)){
            $dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8";
            $this::$connection = new \PDO($dsn, DB_USER, DB_PASS);
            $this::$connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        }
    }

    /**
     * Set the PDO connection
     * 
     * you can set the PDO connection at whenever you want. The already connection will be replace by new that you passed in.
     * 
     * @param object $connection PDO connection to set into this object
     * @return object this object that after implement new connection
     */
    public function setConnection($connection)
    {
        $this::$connection = $connection;
        return $this;
    }

    /**
     * Get current connection object
     * 
     * @return object current connection object of this object
     */
    public function getConnection()
    {
        return $this::$connection;
    }

    /**
     * Set the effect table to action with
     * 
     * @param string $tableName Description
     * @return object ActiveRecord object that after specified the table name to action with
     */
    public function table($tableName)
    {
        $this->table = $tableName;
        return $this;
    }

    /**
     * Insert the data into the table specified before
     * 
     * @param mixed $data the data to insert into the table specified before
     * @return bool
     */
    public function insert($data = [])
    {
        $sql = 'INSERT INTO __table__(__fields__) VALUES (__values__)';
        $sql = str_replace('__table__', $this->table, $sql);

        $fields = array_keys($data);
        $sql = str_replace('__fields__', implode(", ", $fields), $sql);

        $values = [];
        foreach ($data as $key => $item) {
            array_push($values, ":" . $key);
        }
        $sql = str_replace('__values__', implode(", ", $values), $sql);
        $this->statement = $this::$connection->prepare($sql);
        foreach ($data as $key => $item) {
            $this->statement->bindValue(":" . $key, $item);
        }
        return $this->getResult();
    }

    /**
     * Override the data into the table specified before
     * 
     * @param mixed $data the data to override into the table specified before
     * @return bool
     */
    public function update($data = [])
    {
        $sql = 'UPDATE __table__ SET __set__ __where__';
        $sql = str_replace('__table__', $this->table, $sql);

        $set = [];
        foreach ($data as $key => $item) {
            array_push($set, $key . "=" . ":set_" . $key);
        }
        $setStr = implode(", ", $set);
        $sql = str_replace('__set__', $setStr, $sql);

        $sql = str_replace('__where__', $this->getWhereString(), $sql);

        $this->statement = $this::$connection->prepare($sql);
        foreach ($data as $key => $item) {
            $this->statement->bindValue(":set_" . $key, $item);
        }
        $this->bindWhereStatement();
        return $this->getResult();
    }

    /**
     * Delete the data in the table that was specified earlier
     * 
     * You can set the condition to delete data. If not, all data of table will be deleted.
     * 
     * @return bool true if delete successful, false if get an error when deleting the data
     */
    public function delete()
    {
        $sql = 'DELETE FROM __table__ __where__';
        $sql = str_replace("__table__", $this->table, $sql);
        $sql = str_replace("__where__", $this->getWhereString(), $sql);
        $this->statement = $this::$connection->prepare($sql);

        $this->bindWhereStatement();

        return $this->getResult();
    }

    /**
     * Add the fields to select in the get data
     * 
     * If you not specified the select, "*" will be using when get the data
     * @param string $select fields to select in the get data.
     * @return object ActiveRecord object after add fields passed
     */
    public function select($select = '')
    {
        array_push($this->select, $select);
        return $this;
    }

    /**
     * Get the data from statement of ActiveRecord
     * 
     * @return array|bool An array of result if it is not empty, otherwise return false
     */
    public function result()
    {
        $data = $this->statement->fetchAll(\PDO::FETCH_ASSOC);
        if (empty($data)) {
            return false;
        }
        return $data;
    }

    /**
     * Create a statement for get data
     * 
     * @return object ActiveRecord that after create statement for get data
     */
    public function get()
    {
        $sql = 'SELECT __select__ FROM __table__ __where__ __group__ __order__ __limit__ __offset__';
        $sql = str_replace("__table__", $this->table, $sql);
        $sql = str_replace("__select__", $this->getSelectString(), $sql);
        $sql = str_replace("__where__", $this->getWhereString(), $sql);
        $sql = str_replace("__group__", $this->getGroupString(), $sql);
        $sql = str_replace("__order__", $this->getOrderString(), $sql);
        $sql = str_replace("__limit__", $this->getLimitString(), $sql);
        $sql = str_replace("__offset__", $this->getOffsetString(), $sql);
        $this->statement = $this::$connection->prepare($sql);
        $this->bindWhereStatement();
        $this->getResult();

        return $this;
    }

    /**
     * Add condition for actions on the the table
     * 
     * You can add the condition for deleting data, get data on the table specified
     * 
     * @param array|string $field An array (as format field=>value) of condition with operation of "=", or a string of field to adding into condition
     * @param string $value the value of condition 
     * @param string $operation the operation to compare, if you not specified, the operation "=" will be using.
     * @return object ActiveRecord object after add condition
     */
    public function where()
    {
        $input = func_get_args();
        if (1 == count($input)) {
            if (is_array($input[0])) {
                foreach ($input[0] as $key => $value) {
                    $this->addWhere($key, $value);
                }
            }
        } else {
            if (!isset($input[2])) {
                $input[2] = '=';
            }
            $this->addWhere($input[0], $input[1], $input[2]);
        }
        return $this;
    }

    /**
     * Add order to get data
     * 
     * @param array $order An array as orderField=>orderType
     * @return object ActiveRecord object after add order to get data
     */
    public function order($order = [])
    {
        foreach ($order as $key => $item) {
            $this->order[$key] = strtoupper($item);
        }
        return $this;
    }

    /**
     * Set the total of records to get data
     * 
     * @param int $limit The number of records
     * @return object ActiveRecord after set the total of records to get data
     */
    public function limit($limit = 1)
    {
        $this->limit = preg_replace('/[^0-9]/i', '', $limit);
        if ('' === trim($this->limit)) {
            $this->limit = 1;
        }
        return $this;
    }

    /**
     * Set the start offset the get data
     * 
     * @param int $offset the position of row to start get data
     * @return object ActiveRecord object after set the start offset to get data
     */
    public function offset($offset = 0)
    {
        $this->offset = preg_replace('/[^0-9]/i', '', $offset);
        if ('' === trim($this->offset)) {
            $this->offset = 0;
        }
        return $this;
    }

    /**
     * Add the fields to group data
     * 
     * @param string $group Groups separated by comma (",")
     * @return object ActiveRecord after add fields to group data
     */
    public function group($group)
    {
        array_push($this->group, $group);
        return $this;
    }

    /**
     * Run any sql command
     * 
     * @param string $sql The sql command to run
     * @return  int|bool if run sql with no error, return true, otherwise, return error code
     */
    public function execSql($sql)
    {
        $this->error = null;
        $this->statement = null;
        try {
            $this->statement = $this::$connection->prepare($sql);
            $result = $this->statement->execute();
        } catch (Exception $ex) {
            $result = $ex->getCode();
            $this->error = $ex->getCode();
        }
        return $result;
    }

    /**
     * Reset all state of ActiveRecord into default state as initial
     * 
     * @return object ActiveRecord after reset all state into default
     */
    public function reset()
    {
        $this->error = null;
        $this->errorText = null;
        $this->statement = null;
        $this->result = null;
        $this->where = [];
        $this->select = [];
        $this->order = [];
        $this->offset = null;
        $this->limit = null;
        $this->group = [];
        return $this;
    }
    public function getError(){
        return $this->error;
    }
    public function getErrorText(){
        return $this->errorText;
    }

    private function addWhere($field, $value, $comparation = '=')
    {
        array_push($this->where, [
            'field' => $field,
            'value' => $value,
            'comparation' => $comparation
        ]);
    }

    private function getResult()
    {
        try {
            $this->result = $this->statement->execute();
        } catch (Exception $ex) {
            $this->result = false;
            $this->error = $ex->getCode();
            $this->errorText = $ex->getMessage();
        }
        return $this->result;
    }

    private function getWhereString()
    {
        if (empty($this->where)) {
            return "";
        } else {
            $where = [];
            foreach ($this->where as $key => $item) {
                array_push($where, $item['field'] ." ". $item['comparation'] ." ". ":where_" . $key . "_" . $item['field']);
            }
            $whereStr = "WHERE " . implode(" AND ", $where);
            return $whereStr;
        }
    }

    private function getLimitString()
    {
        if (!isset($this->limit)) {
            return "";
        } else {
            return "LIMIT " . $this->limit;
        }
    }

    private function getOffsetString()
    {
        if (!isset($this->offset)) {
            return "";
        } else {
            return "OFFSET " . $this->offset;
        }
    }

    private function getOrderString()
    {
        if (empty($this->order)) {
            return '';
        } else {
            $orderAr = [];
            foreach ($this->order as $key => $value) {
                array_push($orderAr, $key . " " . $value);
            }
            return $order = "ORDER BY " . implode(", ", $orderAr);
        }
    }

    private function getGroupString()
    {
        if (empty($this->group)) {
            return '';
        } else {
            return $order = "GROUP BY " . implode(", ", $this->group);
        }
    }

    private function getSelectString()
    {
        if (empty($this->select)) {
            return "*";
        }
        return implode(", ", $this->select);
    }

    private function bindWhereStatement()
    {
        if (!empty($this->where)) {
            foreach ($this->where as $key => $item) {
                $this->statement->bindValue(":where_" . $key . "_" . $item['field'], $item['value']);
            }
        }
    }

}

 

Trong class trên, mình đã sử dụng PDO để kết nối với MySQL thay vì kết nối trực tiếp, mục đích là để tăng tính linh hoạt. Chúng ta có thể dễ dàng thay đổi để kết nối với các CDSL khác như SQL server...

 

Chức năng các thuộc tính và phương thức trong class thì mình đã ghi chú trong code, các bạn có thể xem trực tiếp.

Và sau đây, mình sẽ trình bày các ví dụ về việc sử dụng class trên cho các bạn dễ hình dung.

 

Sử dụng class PHP để thao tác với CSDL MySQL thông qua PDO

Ví dụ 1: Lấy tất cả thông tin trong một bảng:

 

// Khai báo các tham số để kết nối với CSDL
const DB_HOST = "your host"; // ví dụ: localhost
const DB_NAME = "your database name"; // ví dụ: myblog
const DB_USER = "your user name of DB"; // ví dụ: root
const DB_PASS = "your pass for user"; // ví dụ: "" (rỗng)

// Tạo đối tượng từ class Db
$db = new Db();
// Lấy tất cả dữ liệu từ bảng post_type
$postTypes = $db->table('post_type')->get()->result();
print_r($postTypes);

 

Ví dụ 2: Thêm một record mới vào một bảng:

 

// Tạo đối tượng từ class Db
$db = new Db();
// Thêm mới một record vào bảng post_type
$db->table('post_type')->insert([
    'm_title' => "Danh mục test 1",
    'm_id_parent' => 101
]);

 

Ví dụ 3: Xóa một record trong bảng:

 

// Tạo đối tượng từ class Db
$db = new Db();
// Xóa record từ bảng post_type với điều kiện id = 101
$db->table('post_type')->where([
    'id' => 101
])->delete();

 

Ví dụ 4: Lấy dữ liệu từ bảng dựa vào nhiều điều kiện

 

// Tạo đối tượng từ class Db
$db = new Db();
// Xóa record từ bảng post_type với điều kiện id = 101
$postTypes = $db->table('post_type')->where([
    'id' => 101,
    'm_title' => 'danh mục test 1'
])->get->result();
print_r($postTypes);

 

Hoặc

 

// Tạo đối tượng từ class Db
$db = new Db();
// Lấy dữ liệu từ bảng post_type với id = 101 và m_title = "danh muc test 1"
$postTypes = $db
        ->table('post_type')
        ->where('id', 101)
        ->where('m_title', 'danh muc test 1')
        ->get()
        ->result();
print_r($postTypes);

 

Ví dụ 5: Giới hạn dữ liệu lấy về từ record thứ 5 lấy 10 record:

 

// Tạo đối tượng từ class Db
$db = new Db();
// Lấy dữ liệu từ bảng post_type lấy 10 record từ record thứ 5
$postTypes = $db
        ->table('post_type')
        ->offset(5)
        ->limit(10)
        ->get()
        ->result();
print_r($postTypes);

 

Ví dụ 6: Sắp xếp dữ liệu trả về:

 

// Tạo đối tượng từ class Db
$db = new Db();
// Lấy dữ liệu từ bảng post_type với tên tăng dần theo alphabet
$postTypes = $db
        ->table('post_type')
        ->order([
            'm_title' => 'asc'
        ])
        ->get()
        ->result();
print_r($postTypes);

 

Ví dụ 7: Tìm dữ liệu trả về với tên gần đúng:

 

// Tạo đối tượng từ class Db
$db = new Db();
// Lấy dữ liệu từ bảng post_type với tên có chứa chữ "php", sử dụng toán tử 'like' để tìm kiếm
$postTypes = $db
        ->table('post_type')
        ->where('m_title', '%php%', 'like')
        ->get()
        ->result();
print_r($postTypes);

 

Mở rộng class tương tác với CSDL thành các model.

Ý tưởng: chúng ta sẽ tạo các file model để thao tác với các bảng trong CSDL của chúng ta. Như vậy, chúng ta mong muốn với N bảng trong CSDL thì sẽ có N class model tương ứng để xử lý. Và việc thao tác trực tiếp với CSDL thì chúng ta sẽ sử dụng class Db ở trên. 

Ví dụ:

Chúng ta có thể tạo class model thao tác với bảng post_type như sau:

 

class PostTypeModel extends Db{
    
    // Khai báo tên bảng trong CSDL
    const table = 'post_type';
    
    public function __construct($connection = null) {
        parent::__construct($connection);
        $this->table(self::table);
    }
    
    public function getAllData(){
        return $this->reset()->get()->result();
    }
}

$postTypeModel = new PostTypeModel();
$allTypes = $postTypeModel->getAllData();
print_r($allTypes);

 

Thông thường, chúng ta sẽ tạo các file riêng biệt để chứa các class trên.

 

Diễn giải thêm:

Trên đây là cách mà chúng ta có thể dùng PHP và cụ thể là PDO trong PHP để thao tác với CSDL. Chúng ta có thể dùng PHPMyadmin để chạy các câu lệnh SQL một cách trực tiếp, nhưng đó không phải là tất cả, trong đa số các trường hợp, chúng ta cần các câu lệnh đó chạy thông qua PHP.

Hãy hình dung một ví dụ đơn giản: Chúng ta có một người dùng truy cập vào website và chuyển đến trang đăng ký thành viên, khi đó chúng ta cần thêm một record vào bảng user để thể hiện cho một người dùng mới. Vấn đề là chúng ta làm sao để chạy câu lệnh SQL ? Khi này, máy chủ web hay nói chi tiết hơn là đứng ở file php của trang đăng ký, chúng ta nhận được các thông tin liên quan đến việc đăng ký thành viên, vậy giải pháp hay nhất lúc này phải là dùng luôn PHP để chạy câu lệnh SQL thêm mới một record vào bảng user.

 

Tạm kết:

Class thao tác với CSDL ở bài hướng dẫn là ở mức độ cơ bản, chúng ta có thể tham khảo và phát triển thêm trong các dự án thực tế của mình.

Tạo vào 04/03/2023, Cập nhật 1 năm trước

Bình luận

Hãy là nguời đầu tiên bình luận về Class PHP tương tác với CSDL thông qua PDO

Bài viết có vấn đề ? Hãy cho chúng tôi biết.

Gửi báo cáo sai phạm
Bạn đang đọc bài viết Class PHP tương tác với CSDL thông qua PDO

Hãy để nguồn Suta.media khi phát hành lại nội dung này !