Hỏi đáp

PDO trong PHP

Nếu bạn là một PHP Developer, chắc hẳn bạn đã rất nhiều quen thuộc với việc truy xuất Database (Cơ sở dữ liệu) chỉ bằng các extensions MySQL và MySQLi. Từ PHP 5.1 ta có một cách thức tối ưu hơn đó là sử dụng PHP Data Objects. PDO cung cấp các cơ chế Prepared Statements, Stored Procedures , và giúp bạn thao tác với database thông qua các Object (đối tượng) làm cho công việc trở nên hiệu quả, dễ dàng hơn.

So sánh PDO , MySQLi

Pdo chính là gì

PDO MySQLi Database trợ giúp Hơn 12 loại Chỉ trợ giúp MySQL API Hướng đối tượng (OOP) Hướng đối tượng (OOP) – Hướng thủ tục (Procedural) Kết nối Database Dễ dàng Dễ dàng Đặt tên tham số Có Không Object Mapping Có Có Prepared Statements Có Không Hiệu năng Cao Cao Stored Procedures Có Có

Bạn đang đọc: PDO trong PHP

1. Giới thiệu PDO – PHP Data Objects

PHP Data Objects (PDO) là một lớp truy xuất cơ sở dữ liệu cung ứng một cách thống nhất để thực hiện việc với nhiều các loại cơ sở dữ liệu khác nhau. Khi thực hiện việc với PDO bạn cũng sẽ không cần phải viết các câu lệnh SQL cụ thể mà chỉ sử dụng các phương thức mà PDO cung cấp, giúp tiết kiệm thời gian và thực hiện cho việc chuyển đổi Hệ quản trị cơ sở dữ liệu trở nên dễ dàng hơn, chỉ đơn giản chính là thay đổi Connection String (chuỗi kết nối CSDL).

Bạn chỉ cần nắm rõ API mà PDO cung ứng chính là có thể làm việc được với nhiều Hệ quản trị cơ sở dữ liệu khác nhau như MySQL, SQLite, PostgreSQL, Microsoft SQL Server,… và có thể dễ dàng chuyển đổi chúng.

Các Hệ quản trị cơ sở dữ liệu (Database Management System) mà PDO trợ giúp gồm có:

Tìm hiểu thêm: Giọng đầu (head voice) trong ca hát

Tên driver DBMS PDO_CUBRID Cubrid PDO_DBLIB FreeTDS Microsoft SQL Server / Sybase PDO_FIREBIRD Firebird PDO_IBM IBM DB2 PDO_INFORMIX IBM Informix Dynamic Server PDO_MYSQL MySQL 3.x/4.x/5.x PDO_OCI Oracle Call Interface PDO_ODBC ODBC v3 (IBM DB2, unixODBC and win32 ODBC) PDO_PGSQL PostgreSQL PDO_SQLITE SQLite 3 and SQLite 2 PDO_SQLSRV Microsoft SQL Server / SQL Azure PDO_4D 4D

2. Kết nối cơ sở dữ liệu

Mỗi DBMS cũng sẽ có các phương thức kết nối khác nhau (có loại cần Username, Password, đường dẫn đới Database, Port, có các loại không). Connection String của các DBMS phổ biến hầu hết đều có dạng như sau:

$conn = new PDO(‘mysql:host=localhost;dbname=izlearn’, $username, $password);

Với mysql chính là tên của DBMS, localhost có ý nghĩa database đã được đặt ở trên cùng server, izlearn chính là tên của database. $username , và $password là 2 biến chứa thông tin xác thực.

Đối với SQLite, DBMS này chưa có cơ chế xác thực chỉ bằng Username , và Password mà chỉ đơn giản chính là đường dẫn tới file dữ liệu:

$conn = new PDO(“sqlite:your/database/path/izlearn.db”);

Đây chính là lúc để bạn quên đi Connection String lỗi thời mysql_connect(‘localhost’, ‘username’, ‘password’) or die(‘Could not connect: ‘ . mysql_error()); Hiện vẫn còn rất nhiều bài viết ở Việt Nam hướng dẫn người mới sử dụng cách kết nối CSDL dạng này vì họ cho rằng nó đơn giản. Thực ra họ chỉ đang dẫn bạn đi về quá khứ mà thôi. Đế ngắt kết nối khi không cần thao tác với database nữa, các bạn chỉ cần sét biến $conn về null;

$conn = null;

3. Insert , Update

Thêm mới (insert) và cập nhật (update) dữ liệu chính là những hoạt động cơ bản khi thao tác với database. Với PDO, mỗi hoạt động insert hay là update được thực hiện qua 3 quá trình sử dụng cơ chế Prepared Statement

Prepare statement: Chuẩn bị một câu lệnh SQL thực hiện khung/mẫu được gọi là Prepared Statement với các Placeholder (có thể hiểu placeholder đóng vai trò như tham số của các phương thức khi bạn khai báo hàm)Bind params: Gắn giá trị thực vào các placeholder (tương tự như khi bạn truyền giá trị vào các tham số của phương thức)Execute: Thực thi câu lệnh.

Prepared Statement

Có 2 loại Placeholder trong Prepared Statement là Placeholder chưa định danh (Unnamed Placeholder) , Placeholder định danh (Named Placeholder) như ví dụ sau:

$stmt = $conn->prepare(‘INSERT INTO users (name, email, age) values (?, ?, ?)’); $stmt = $conn->prepare(‘INSERT INTO users (name, email, age) values (:name, :mail, :age)’);

Dòng lệnh thứ số 1 sử dụng Placeholder chưa định danh chính là các dấu hỏi – ?. Dòng lệnh thứ 2 sử dụng Placeholder định danh: :name, :mail, :age (lưu ý dấu hai chấm , placeholder chưa nhất thiết phải giống tên column). Sau đây là toàn bộ quy trình Insert , và Update sử dụng 2 các loại Placeholder nêu trên.

Unnamed Placeholder

//Khởi tạo Prepared Statement từ biến $conn ở phần trước $stmt = $conn->prepare(‘INSERT INTO users (name, email, age) values (?, ?, ?)’); //Gán các biến (lúc này chưa mang giá trị) vào các placeholder theo thứ tự tương ứng $stmt->bindParam(1, $name); $stmt->bindParam(2, $mail); $stmt->bindParam(3, $age); //Gán giá trị và thực thi $name = “Vu Hoang Lam”< $mail = “[email protected]”; $age = 22; $stmt->execute(); //Gán những giá trị khác , tiếp tục thực thi $name = “Nguyen Van A”; $mail = “[email protected]”; $age = 23; $stmt->execute();

Các các loại triệu chứng chỉ dành cho người học tiếng Trung

Như mọi người thấy ta chỉ cần khởi tạo Prepared Statement một lần , có thể sử dụng lại nhiều lần. Với mỗi column – placeholder ta phải làm gán tham số một lần, điều này cũng sẽ không sao với những table có ít column như ví dụ trên, nhưng sẽ rất nhiều bất tiện nếu bảng có nhiều table, rất nhiều may mắn ta có cách khác để thực hiện việc này, đó chính là lưu toàn bộ giá trị vào trong một mảng và truyền mảng này vào phương thức execute(), cụ thể như sau:

$stmt = $conn->prepare(‘INSERT INTO users (name, email, age) values (?, ?, ?)’); $data = array(‘Vu Hoang Lam’, ‘[email protected]’, 22); //Phương thức execute() dưới đây cũng sẽ gán lần lượt giá trị trong mảng vào các Placeholder theo thứ tự $stmt->execute($data);

Named Placeholder

Đối với Named Placeholder, cách làm cũng khá tương đồng với Unnamed Placeholder, chỉ khác chính là ta không dùng thứ tự placeholder để gán giá trị (bind) mà dùng chính tên của placeholder:

//Khởi tạo Prepared Statement từ biến $conn ở phần trước $stmt = $conn->prepare(‘INSERT INTO users (name, email, age) values (:name, :mail, :age)’); //Gán các biến (lúc này chưa mang giá trị) vào các placeholder theo tên của chúng $stmt->bindParam(‘:name’, $name); $stmt->bindParam(‘:mail’, $mail); $stmt->bindParam(‘:age’, $age); //Gán giá trị , và thực thi $name = “Vu Hoang Lam”; $mail = “[email protected]”; $age = 22; $stmt->execute();

Các bạn cũng có thể sử dụng mảng để rút gọn:

//Lưu ý: Không cần thiết phải sử dụng dấu hai chấm cho các key $data = array(‘name’ => ‘Vu Hoang Lam’, ‘mail’ => ‘[email protected]’, ‘age’ => 22); Một mẹo hữu ích khác khi sử dụng Named Placeholder đó là insert Object class $user public $name; public $mail; public $age; $person = new $user(); $person->name = ‘Vu Hoang Lam’; $person->mail = ‘[email protected]’; $person->age = 22; $stmt = $conn->prepare(‘INSERT INTO users (name, email, age) values (:name, :mail, :age)’); $stmt->execute((array)$person);

Ở dòng cuối cùng, tôi đã làm “ép kiểu” (cast) Object $person thành array để truyền vào phương thức execute();

Việc sử dụng Prepared Statement sẽ giúp bạn tránh đã được SQL Injection, tôi sẽ đi sâu giải thích vấn đề này trong một bài viết khác.

4. Select Data – “Đọc” dữ liệu từ database

Khi đọc dữ liệu từ database, PDO sẽ trả về dữ liệu theo cấu trúc mảng (array) hoặc đối tượng (object) thông qua phương thức fetch(). chúng ta nên thiết lập trước cấu trúc dữ liệu trước khi gọi phương thức này, PDO hỗ trợ các tuỳ chọn sau:

PDO::FETCH_ASSOC: Trả về dữ liệu dạng mảng với key chính là tên của column (column của các table trong database)PDO::FETCH_BOTH (default): Trả về dữ liệu dạng mảng với key là tên của column , cả số thứ tự của columnPDO::FETCH_BOUND: Gán giá trị của từng column cho từng biến đã khởi tạo trước đó qua phương thức bindColumn()PDO::FETCH_CLASS: Gán giá trị của từng column cho từng thuộc tính (property/attribute) của một lớp Class theo tên column , tên thuộc tính.PDO::FETCH_INTO: Gán giá trị của từng column cho từng thuộc tính của một Class Instance (thể hiện của một lớp)PDO::FETCH_LAZY: Gộp chung PDO::FETCH_BOTH/PDO::FETCH_OBJPDO::FETCH_NUM: Trả về dữ liệu dạng mảng với key chính là số thứ tự của columnPDO::FETCH_OBJ: Trả về một Object của stdClass (link is external) với tên thuộc tính của Object là tên của column.

Trong thực tế, mọi người chỉ thường dùng 3 kiểu fetch đó là: FETCH_ASSOC, FETCH_CLASS và FETCH_OBJ. Để thiết lập cấu trúc dữ liệu (Fetch Style hay là Fetch Mode) trước khi fetch ta dùng câu lệnh sau:

$stmt->setFetchMode(PDO::FETCH_ASSOC);

Hoặc nếu muốn bạn cũng có thể thiết lập kiểu fetch khi gọi hàm fetch():

$stmt->fetch(PDO::FETCH_ASSOC);

FETCH_ASSOC

Kiểu fetch này sẽ tạo ra một mảng kết hợp lập chỉ mục theo tên column (nghĩa là các key của mảng là tên của column), tương tự như khi ta dùng MySQL/MySQLi Extension.

//Tạo Prepared Statement $stmt = $conn->prepare(‘SELECT email, age from users WHERE name = :name’); //Thiết lập kiểu dữ liệu trả về $stmt->setFetchMode(PDO::FETCH_ASSOC); //Gán giá trị , thực thi $stmt->execute(array(‘name’ => ‘a’)); //Hiển thị kết quả, vòng lặp sau đây sẽ dừng lại khi đã duyệt qua toàn bộ kết quả while($row = $stmt->fetch()) echo $row[‘name’] , ‘n’; echo $row[’email’] và ‘n’; echo $row[‘age’] , và ‘n’;

FETCH_OBJ

Kiểu fetch này trả về một Object của stdClass cho mỗi row của kết quả.

//Tạo Prepared Statement $stmt = $conn->prepare(‘SELECT email, age from users WHERE name = :name’); //Thiết lập kiểu dữ liệu trả về $stmt->setFetchMode(PDO::FETCH_OBJ); //Gán giá trị , và thực thi $stmt->execute(array(‘name’ => ‘a’)); //Hiển thị kết quả, vòng lặp sau đây cũng sẽ dừng lại khi đã duyệt qua toàn bộ kết quả trả về while($row = $stmt->fetch()) echo $row->name và ‘n’; echo $row->email , và ‘n’; echo $row->age , ‘n’;

FETCH_CLASS

Kiểu fetch này cho phép bạn đưa kết quả vào Object của một Class mà bạn chỉ định. Khi sử dụng FETCH_CLASS, thuộc tính của class sẽ được gán giá trị trước khi constructor của class đó được gọi (phải chú ý vì điều này rất quan trọng). Nếu chưa có thuộc tính khớp với tên của một column bất kỳ thì thuộc tính đó cũng sẽ đã được tự động gây nên (public).

Giả sử table users có một ta đã có Class User được định nghĩa như sau:

class User public $name; public $email; public $isAdmin = ‘No’; function __construct() if ($this->name == ‘Vu Hoang Lam’) $this->isAdmin = ‘Yes’;

Nơi cư trú, nơi thường trú, nơi tạm trú khác nhau như thế nào? – Luật Nhân Dân

Khi query data đến từ database sử dụng đoạn code sau:

//Tạo Prepared Statement $stmt = $conn->prepare(‘SELECT email, age from users WHERE name = :name’); //Thiết lập kiểu dữ liệu trả về, chỉ định dữ liệu được đưa vào object của class User $stmt->setFetchMode(PDO::FETCH_CLASS, ‘User’); //Gán giá trị , và thực thi $stmt->execute(array(‘name’ => ‘a’)); //Hiển thị kết quả, vòng lặp sau đây sẽ dừng lại khi đã duyệt qua toàn bộ kết quả trả về while($obj = $stmt->fetch()) echo $obj->email; echo $obj->isAdmin;

Vì constructor đã được gọi sau khi thuộc tính $name đã được gán bằng Vu Hoang Lam nên isAdmin sẽ mang giá trị Yes. Nếu muốn constructor của class đã được gọi trước khi các thuộc tính đã được gán giá trị, bạn phải sử dụng thêm PDO::FETCH_PROPS_LATE. Cách sử dụng như sau:

//Hãy thử sử dụng kiểu fetch trên và so sánh kết quả hiển thị $stmt->setFetchMode(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, ‘User’);

Nếu cần truyền các tham số cho constructor của class thông qua phương thức fetch(), mọi người có thể đặt chúng trong một array theo thứ tự tương ứng cụ thể như sau:

$stmt->setFetchMode(PDO::FETCH_CLASS, ‘User’, array(‘param1’, ‘param2’, ‘param3’));

Exceptions – Xử lý ngoại lệ

PDO dùng các Exceptions để xử lý các lỗi phát sinh khi làm việc với database, vì thế tất cả những gì bạn làm với PDO nên đã được đặt trong một try/catch block. PDO cung cấp 3 chế độ xử lý lỗi (Error Mode) được thiết lập thông qua phương thức setAttribute():

$conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT ); $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING ); $conn->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

PDO::ERRMODE_SILENT

Đây là chế độ xử lý lỗi mặc định của PDO, khi mắc mỗt lỗi bất kỳ, PDO sẽ im lặng (silent) , và chương trình vẫn tiếp tục chạy. chúng ta có thể lấy mã lỗi và thông tin về các lỗi đã xảy ra qua PDO::errorCode() , và PDO::errorInfo()

PDO::ERRMODE_WARNING

Ở chế độ này khi gặp phải lỗi PDO cũng sẽ ném ra một PHP Warning, chương trình sẽ tiếp tục chạy.

PDO::ERRMODE_EXCEPTION

Đây chính là mode mà bạn nên sử dụng nhất, khi đặt trong một try/catch block sẽ giúp bạn kiểm soát các lỗi phát sinh một cách uyển chuyển và giấu các thông báo lỗi có thể khiến Attacker khai thác hệ thống của bạn.

try $stmt = new PDO(‘mysql:host=localhost;dbname=izlearn’, ‘lamvh’, ‘talapassday’); $stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); //Sai cú pháp, FORM thay vì FROM $stmt->prepare(‘SELECT name FORM people’); catch(PDOException $e) echo “ERROR! Co loi xay ra voi PDO”; file_put_contents(‘PDOErrors.txt’, $e->getMessage(), FILE_APPEND);

Đoạn code ở trên cũng sẽ ghi thông báo lỗi vào một file text với tên hoctuvangiamsat.com

Một số phương thức hữu ích khác

$conn->lastInsertId();

Phương thức trên trả về Auto Incremented ID của rows được thêm gần nhất.

$conn->exec(‘DELETE FROM users WHERE uid = 1’);

Đối với các lệnh SQL không có dữ liệu trả về, , không cần thiết phải truyền tham số thì có thể sử dụng phương thức exec(). Phương thức này cũng sẽ trả về số lượng row bị tác động sau khi làm câu lệnh. Như ví dụ ở trên cũng sẽ trả về số lượng row bị xoá.

$conn = $DBH->e($foo);

Phương thức e() sẽ giúp cho bạn thêm dấu nháy cho một string để string đó an toàn khi sử dụng để truy vấn, nếu bạn không muốn sử dụng Prepared Statement.

$stmt->rowCount();

Phương thức rowCount() trả về số lượng row bị ảnh hưởng sau khi làm các thao tác DELETE, INSERT và UPDATE. Dùng rowCount() cho thao tác SELECT có thể cũng sẽ trả về kết quả không đúng với một số loại database.

Tham khảo: hoctuvangiamsat.com/

ATL, BTL, TTL là gì? Thuật ngữ bạn cần biết khi thực hiện marketing

Tìm hiểu thêm: Ngày Thanh Trừng là gì? Mọi điều về Ngày Thanh Trừng bạn chưa biết