Câu hỏi Tại sao tôi không nên sử dụng các hàm mysql_ * trong PHP?


Lý do kỹ thuật cho lý do tại sao người ta không nên sử dụng mysql_* chức năng? (ví dụ. mysql_query(), mysql_connect() hoặc là mysql_real_escape_string())?

Tại sao tôi nên sử dụng thứ gì đó khác ngay cả khi chúng hoạt động trên trang web của tôi?

Nếu chúng không hoạt động trên trang web của tôi, tại sao tôi gặp lỗi như

Cảnh báo: mysql_connect (): Không có tệp hoặc thư mục như vậy


2196
2017-10-12 13:18


gốc


Lỗi giống như: Lỗi nghiêm trọng: Lỗi không được bắt buộc: Gọi hàm không xác định mysql_connect () ... - Bimal Poudel
Không được chấp nhận một mình là đủ lý do để tránh chúng - Sasa1234


Các câu trả lời:


Phần mở rộng MySQL:

  • Không thuộc sự phát triển tích cực
  • chính thức không dùng nữa kể từ PHP 5.5 (phát hành tháng 6 năm 2013).
  • Đã được đã xóa hoàn toàn kể từ PHP 7.0 (phát hành tháng 12 năm 2015)
    • Điều này có nghĩa là Ngày 31 tháng 12 năm 2018 nó sẽ không tồn tại trong bất kỳ phiên bản PHP được hỗ trợ nào. Hiện tại, nó chỉ được Bảo vệ cập nhật.
  • Thiếu giao diện OO
  • Không hỗ trợ:
    • Truy vấn không đồng bộ, không chặn
    • Báo cáo chuẩn bị hoặc truy vấn được tham số hóa
    • Thủ tục lưu trữ
    • Nhiều câu lệnh
    • Giao dịch
    • Phương pháp xác thực mật khẩu "mới" (theo mặc định trong MySQL 5.6; yêu cầu trong 5.7)
    • Tất cả các chức năng trong MySQL 5.1

Kể từ khi nó không được chấp nhận, sử dụng nó làm cho mã của bạn ít bằng chứng trong tương lai.

Thiếu sự hỗ trợ cho các phát biểu đã chuẩn bị đặc biệt quan trọng vì chúng cung cấp một phương pháp thoát dễ dàng hơn, ít bị lỗi hơn và trích dẫn dữ liệu bên ngoài hơn là tự thoát nó bằng một cuộc gọi hàm riêng biệt.

Xem so sánh các phần mở rộng SQL.


1814
2018-01-01 11:52



Không được chấp nhận một mình là lý do đủ để tránh chúng. Họ sẽ không có mặt ở đó một ngày nào đó, và bạn sẽ không hạnh phúc nếu bạn dựa vào chúng. Phần còn lại chỉ là một danh sách những thứ sử dụng các phần mở rộng cũ đã giúp mọi người không thể học được. - Tim Post♦
Sự phản đối không phải là viên đạn ma thuật mà mọi người dường như nghĩ. PHP chính nó sẽ không có một ngày, nhưng chúng tôi dựa vào các công cụ chúng tôi có lúc xử lý của chúng tôi ngày hôm nay. Khi chúng ta phải thay đổi công cụ, chúng ta sẽ. - Lightness Races in Orbit
@LightnessRacesinOrbit - Khước từ không phải là một viên đạn ma thuật, nó là một lá cờ nói rằng "Chúng tôi nhận ra điều này hút nên chúng tôi sẽ không hỗ trợ nó lâu hơn nữa". Mặc dù việc kiểm tra mã trong tương lai tốt hơn là một lý do chính đáng để tránh xa các tính năng không được chấp nhận, nó không phải là tính năng duy nhất (hoặc thậm chí là chính). Thay đổi công cụ bởi vì có những công cụ tốt hơn, không phải vì bạn buộc phải. (Và thay đổi công cụ trước khi bạn buộc phải có nghĩa là bạn không học những cái mới chỉ vì mã của bạn đã ngừng hoạt động và cần sửa chữa ngày hôm qua ... đó là thời điểm tồi tệ nhất để học các công cụ mới). - Quentin
Một điều mà tôi chưa từng đề cập đến về việc thiếu các tuyên bố chuẩn bị là vấn đề hiệu suất. Mỗi khi bạn phát hành một tuyên bố, một cái gì đó phải biên dịch nó để daemon MySQL có thể hiểu nó. Với API này, nếu bạn phát hành 200.000 cùng một truy vấn trong một vòng lặp, 200.000 lần truy vấn phải được biên dịch để MySQL hiểu nó. Với các câu lệnh đã chuẩn bị, nó được biên dịch một lần, và sau đó các giá trị được tham số hóa thành SQL được biên dịch. - Goldentoa11
@symcbean, Chắc chắn không không phải hỗ trợ chuẩn bị báo cáo. Đó là thực tế lý do chính tại sao nó không được chấp nhận. Nếu không có (dễ sử dụng) các câu lệnh chuẩn bị thì phần mở rộng mysql thường là nạn nhân của các cuộc tấn công SQL injection. - rustyx


PHP cung cấp ba API khác nhau để kết nối với MySQL. Đây là những mysql(đã bị xóa khỏi PHP 7), mysqliPDO tiện ích mở rộng.

Các mysql_* các chức năng được sử dụng rất phổ biến, nhưng việc sử dụng chúng không được khuyến khích nữa. Nhóm tài liệu đang thảo luận về tình hình bảo mật cơ sở dữ liệu và giáo dục người dùng di chuyển ra khỏi phần mở rộng ext / mysql thường được sử dụng là một phần của điều này (kiểm tra php.internals: không dùng nữa ext / mysql).

Và nhóm phát triển PHP sau đó đã đưa ra quyết định tạo E_DEPRECATED lỗi khi người dùng kết nối với MySQL, cho dù thông qua mysql_connect(), mysql_pconnect() hoặc chức năng kết nối ngầm được tích hợp vào ext/mysql.

ext/mysql là chính thức không được chấp nhận như của PHP 5.5 và đã được đã xóa khỏi PHP 7.

Xem Hộp Đỏ?

Khi bạn đi vào bất kỳ mysql_* trang hướng dẫn sử dụng chức năng, bạn thấy một hộp màu đỏ, giải thích nó không nên được sử dụng nữa.

Tại sao


Di chuyển ra xa ext/mysql không chỉ về bảo mật, mà còn về việc có quyền truy cập vào tất cả các tính năng của cơ sở dữ liệu MySQL.

ext/mysql được xây dựng cho MySQL 3.23 và chỉ có rất ít bổ sung kể từ đó trong khi chủ yếu là giữ khả năng tương thích với phiên bản cũ này làm cho mã một chút khó khăn hơn để duy trì. Thiếu các tính năng không được hỗ trợ bởi ext/mysql bao gồm: (từ hướng dẫn sử dụng PHP).

Lý do không sử dụng mysql_* chức năng:

  • Không được phát triển tích cực
  • Đã xóa như của PHP 7
  • Thiếu giao diện OO
  • Không hỗ trợ truy vấn không đồng bộ, không chặn
  • Không hỗ trợ báo cáo đã chuẩn bị hoặc truy vấn được tham số hóa
  • Không hỗ trợ các thủ tục được lưu trữ
  • Không hỗ trợ nhiều câu lệnh
  • Không hỗ trợ giao dịch
  • Không hỗ trợ tất cả các chức năng trong MySQL 5.1

Điểm trên được trích dẫn từ câu trả lời của Quentin

Thiếu sự hỗ trợ cho các phát biểu đã chuẩn bị đặc biệt quan trọng vì chúng cung cấp một phương pháp rõ ràng hơn, ít bị lỗi hơn khi thoát và trích dẫn dữ liệu ngoài hơn là tự thoát nó bằng một cuộc gọi hàm riêng biệt.

Xem so sánh các phần mở rộng SQL.


Ngăn chặn cảnh báo không dùng nữa

Trong khi mã đang được chuyển đổi thành MySQLi/PDO, E_DEPRECATED lỗi có thể bị chặn bằng cách thiết lập error_reporting trong php.ini loại trừ E_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

Lưu ý rằng điều này cũng sẽ ẩn cảnh báo không dùng nữa, tuy nhiên, có thể cho những thứ khác ngoài MySQL. (từ hướng dẫn sử dụng PHP)

Bài viết PDO so với MySQLi: Bạn nên sử dụng cái nào? bởi Dejan Marjanovic sẽ giúp bạn lựa chọn.

Và cách tốt hơn là PDOvà giờ tôi đang viết một cách đơn giản PDO hướng dẫn.


Hướng dẫn PDO đơn giản và ngắn gọn


Q. Câu hỏi đầu tiên trong đầu tôi là: 'PDO` là gì?

A. “PDO - Đối tượng dữ liệu PHP - là một lớp truy cập cơ sở dữ liệu cung cấp một phương thức thống nhất truy cập vào nhiều cơ sở dữ liệu. ”

alt text


Kết nối với MySQL

Với mysql_* hoặc chúng ta có thể nói nó theo cách cũ (không được chấp nhận trong PHP 5.5 trở lên)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

Với PDO: Tất cả những gì bạn cần làm là tạo một PDO vật. Hàm khởi tạo chấp nhận các tham số để chỉ định nguồn cơ sở dữ liệu PDO's constructor chủ yếu lấy bốn tham số DSN (tên nguồn dữ liệu) và tùy chọn username, password.

Ở đây tôi nghĩ bạn đã quen thuộc với tất cả ngoại trừ DSN; đây là mới trong PDO. A DSN về cơ bản là một chuỗi các tùy chọn cho biết PDO trình điều khiển để sử dụng và chi tiết kết nối. Để tham khảo thêm, hãy kiểm tra PDO MySQL DSN.

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

Chú thích: bạn cũng có thể dùng charset=UTF-8nhưng đôi khi nó gây ra lỗi, vì vậy tốt hơn nên sử dụng utf8.

Nếu có bất kỳ lỗi kết nối nào, nó sẽ ném một PDOException đối tượng có thể bị bắt để xử lý Exception thêm nữa.

Đọc tốt: Kết nối và quản lý kết nối ¶ 

Bạn cũng có thể truyền vào một số tùy chọn trình điều khiển dưới dạng một mảng cho tham số thứ tư. Tôi khuyên bạn nên chuyển tham số mà đặt PDO vào chế độ ngoại lệ. Bởi vì một số PDO trình điều khiển không hỗ trợ báo cáo được chuẩn bị gốc, vì vậy PDO thực hiện thi đua chuẩn bị. Nó cũng cho phép bạn tự kích hoạt mô phỏng này. Để sử dụng các câu lệnh được chuẩn bị phía máy chủ gốc, bạn nên đặt nó một cách rõ ràng false.

Cách khác là tắt chế độ mô phỏng được kích hoạt trong MySQLlái xe theo mặc định, nhưng chuẩn bị thi đua nên được tắt để sử dụng PDO an toàn.

Sau đó tôi sẽ giải thích lý do tại sao chuẩn bị thi đua nên được tắt. Để tìm lý do, hãy kiểm tra bài này.

Chỉ có thể sử dụng được nếu bạn đang sử dụng phiên bản cũ của MySQL mà tôi không khuyến khích.

Dưới đây là ví dụ về cách bạn có thể thực hiện:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

Chúng ta có thể thiết lập các thuộc tính sau khi xây dựng PDO không?

Vâng, chúng tôi cũng có thể đặt một số thuộc tính sau khi xây dựng PDO với setAttribute phương pháp:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

Xử lý lỗi


Xử lý lỗi dễ dàng hơn nhiều trong PDO hơn mysql_*.

Một thực tế phổ biến khi sử dụng mysql_* Là:

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die() không phải là cách tốt để xử lý lỗi vì chúng tôi không thể xử lý die. Nó sẽ chỉ kết thúc kịch bản đột ngột và sau đó echo lỗi đến màn hình mà bạn thường KHÔNG muốn hiển thị cho người dùng cuối của bạn, và để cho các hacker đẫm máu khám phá lược đồ của bạn. Cách khác, giá trị trả lại của mysql_* chức năng thường có thể được sử dụng kết hợp với mysql_error () để xử lý lỗi.

PDO cung cấp một giải pháp tốt hơn: ngoại lệ. Bất cứ điều gì chúng tôi làm với PDO nên được bọc trong một try- -catch khối. Chúng ta có thể bắt PDO vào một trong ba chế độ lỗi bằng cách đặt thuộc tính chế độ lỗi. Ba chế độ xử lý lỗi dưới đây.

  • PDO::ERRMODE_SILENT. Nó chỉ thiết lập các mã lỗi và hoạt động khá giống với mysql_* nơi bạn phải kiểm tra từng kết quả và sau đó xem xét $db->errorInfo(); để có được các chi tiết lỗi.
  • PDO::ERRMODE_WARNING Nâng cao E_WARNING. (Cảnh báo thời gian chạy (lỗi không nghiêm trọng). Việc thực thi tập lệnh không bị dừng lại.)
  • PDO::ERRMODE_EXCEPTION: Ném ngoại lệ. Nó đại diện cho một lỗi do PDO gây ra. Bạn không nên ném một PDOException từ mã của riêng bạn. Xem Ngoại lệ để biết thêm thông tin về các ngoại lệ trong PHP. Nó hoạt động rất giống or die(mysql_error());, khi nó không bị bắt. Nhưng không thích or die(), các PDOException có thể bị bắt và xử lý một cách duyên dáng nếu bạn chọn làm như vậy.

Đọc tốt:

Như:

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

Và bạn có thể quấn nó vào try- -catch, như dưới đây:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

Bạn không cần phải xử lý try- -catch ngay bây giờ. Bạn có thể bắt nó bất cứ lúc nào thích hợp, nhưng tôi khuyên bạn nên sử dụng try- -catch. Ngoài ra nó có thể có ý nghĩa hơn để bắt nó ở bên ngoài chức năng gọi PDO đồ đạc:

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

Ngoài ra, bạn có thể xử lý bằng or die() hoặc chúng ta có thể nói như mysql_*nhưng nó sẽ rất đa dạng. Bạn có thể ẩn các thông báo lỗi nguy hiểm trong sản xuất bằng cách xoay display_errors off và chỉ đọc nhật ký lỗi của bạn.

Bây giờ, sau khi đọc tất cả những điều trên, bạn có thể nghĩ: cái quái gì khi tôi chỉ muốn bắt đầu nghiêng đơn giản SELECT, INSERT, UPDATE, hoặc là DELETE các câu lệnh? Đừng lo lắng, ở đây chúng tôi đi:


Chọn dữ liệu

PDO select image

Vì vậy, những gì bạn đang làm trong mysql_* Là:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

Bây giờ trong PDO, bạn có thể làm như sau:

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

Hoặc là

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

chú thích: Nếu bạn đang sử dụng phương pháp như dưới đây (query()), phương thức này trả về một PDOStatement vật. Vì vậy, nếu bạn muốn tìm nạp kết quả, hãy sử dụng kết quả như trên.

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

Trong PDO Data, nó thu được thông qua ->fetch(), một phương thức xử lý câu lệnh của bạn. Trước khi gọi hàm tìm nạp, cách tiếp cận tốt nhất sẽ cho PDO biết bạn muốn lấy dữ liệu như thế nào. Trong phần dưới đây tôi giải thích điều này.

Chế độ tìm nạp

Lưu ý việc sử dụng PDO::FETCH_ASSOC bên trong fetch() và fetchAll() mã trên. Điều này cho biết PDO để trả về các hàng dưới dạng mảng kết hợp với tên trường là khóa. Có rất nhiều chế độ tìm nạp khác mà tôi sẽ giải thích từng cái một.

Trước hết, tôi giải thích cách chọn chế độ tìm nạp:

 $stmt->fetch(PDO::FETCH_ASSOC)

Ở trên, tôi đã sử dụng fetch(). Bạn cũng có thể dùng:

Bây giờ tôi đến để lấy chế độ:

  • PDO::FETCH_ASSOC: trả về một mảng được lập chỉ mục theo tên cột như được trả về trong tập kết quả của bạn
  • PDO::FETCH_BOTH (mặc định): trả về một mảng được lập chỉ mục bởi cả tên cột và số cột được lập chỉ mục 0 như được trả về trong tập kết quả của bạn

Thậm chí còn có nhiều lựa chọn hơn! Đọc tất cả chúng trong PDOStatement Tìm nạp tài liệu..

Lấy số lượng hàng:

Thay vì sử dụng mysql_num_rows để có được số hàng trả về, bạn có thể nhận được PDOStatement và làm rowCount(), như:

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

Lấy ID được chèn lần cuối

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

Chèn và cập nhật hoặc xóa câu lệnh

Insert and update PDO image

Những gì chúng tôi đang làm mysql_* chức năng là:

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

Và trong pdo, điều tương tự này có thể được thực hiện bằng cách:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

Trong truy vấn trên PDO::exec thực thi câu lệnh SQL và trả về số hàng bị ảnh hưởng.

Chèn và xóa sẽ được đề cập sau.

Phương thức trên chỉ hữu ích khi bạn không sử dụng biến trong truy vấn. Nhưng khi bạn cần sử dụng biến trong truy vấn, đừng bao giờ thử như trên và ở đó cho câu lệnh chuẩn bị hoặc câu lệnh được tham số hóa Là.


Báo cáo chuẩn bị

Q. Tuyên bố chuẩn bị là gì và tại sao tôi cần chúng?
A. Một câu lệnh chuẩn bị là một câu lệnh SQL được biên dịch trước có thể được thực hiện nhiều lần bằng cách chỉ gửi dữ liệu đến máy chủ.

Luồng công việc điển hình của việc sử dụng một câu lệnh chuẩn bị như sau (trích dẫn từ Wikipedia 3 điểm):

  1. Chuẩn bị: Mẫu lệnh được tạo bởi ứng dụng và được gửi đến hệ thống quản lý cơ sở dữ liệu (DBMS). Các giá trị nhất định không được chỉ định, được gọi là tham số, trình giữ chỗ hoặc biến liên kết (được gắn nhãn ? phía dưới):

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. DBMS phân tích cú pháp, biên dịch và thực hiện tối ưu hóa truy vấn trên mẫu lệnh và lưu trữ kết quả mà không thực thi nó.

  3. Thi hành: Sau đó, ứng dụng cung cấp (hoặc liên kết) các giá trị cho các tham số và DBMS thực thi câu lệnh (có thể trả lại kết quả). Ứng dụng có thể thực thi câu lệnh nhiều lần như nó muốn với các giá trị khác nhau. Trong ví dụ này, nó có thể cung cấp 'Bánh mì' cho tham số đầu tiên và 1.00cho tham số thứ hai.

Bạn có thể sử dụng một câu lệnh chuẩn bị bằng cách bao gồm các trình giữ chỗ trong SQL của bạn. Về cơ bản có ba cái không có phần giữ chỗ (không thử điều này với biến của nó ở trên), một với phần giữ chỗ không tên, và một với phần giữ chỗ được đặt tên.

Q. Vì vậy, bây giờ, những người giữ chỗ được đặt tên là gì và làm cách nào để sử dụng chúng?
A. Các trình giữ chỗ được đặt tên. Sử dụng tên mô tả trước dấu hai chấm, thay vì dấu chấm hỏi. Chúng tôi không quan tâm đến vị trí / thứ tự của giá trị trong người giữ chỗ tên:

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

Bạn cũng có thể liên kết bằng cách sử dụng một mảng thực thi:

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

Một tính năng thú vị khác cho OOP bạn bè là các trình giữ chỗ được đặt tên có khả năng chèn các đối tượng trực tiếp vào cơ sở dữ liệu của bạn, giả sử các thuộc tính khớp với các trường được đặt tên. Ví dụ:

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

Q. Vì vậy, bây giờ, trình giữ chỗ không được đặt tên là gì và làm cách nào để sử dụng chúng?
A. Hãy có một ví dụ:

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));

Ở trên, bạn có thể thấy những ? thay vì tên như trong một người giữ chỗ tên. Bây giờ trong ví dụ đầu tiên, chúng tôi gán các biến cho các trình giữ chỗ khác nhau ($stmt->bindValue(1, $name, PDO::PARAM_STR);). Sau đó, chúng tôi gán các giá trị cho các trình giữ chỗ đó và thực thi câu lệnh. Trong ví dụ thứ hai, phần tử mảng đầu tiên chuyển đến phần tử đầu tiên ? và thứ hai đến giây ?.

CHÚ THÍCH: Trong trình giữ chỗ không được đặt tên chúng ta phải chăm sóc thứ tự thích hợp của các phần tử trong mảng mà chúng ta đang chuyển đến PDOStatement::execute() phương pháp.


SELECT, INSERT, UPDATE, DELETE truy vấn đã chuẩn bị

  1. SELECT:

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    
  2. INSERT:

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
    
  3. DELETE:

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
    
  4. UPDATE:

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();
    

CHÚ THÍCH:

Tuy nhiên PDO và / hoặc MySQLi không hoàn toàn an toàn. Kiểm tra câu trả lời PDO có được chuẩn bị đầy đủ để ngăn chặn việc tiêm SQL không? bởi ircmaxell. Ngoài ra, tôi trích dẫn một phần từ câu trả lời của anh ấy:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));

1163
2017-10-12 13:28



Những gì tốt đọc trên nên đề cập đến đề cập đến: tuyên bố chuẩn bị lấy đi bất kỳ sử dụng có ý nghĩa của IN (...) construct. - Eugen Rieck
@Amine, Không, không phải vậy! :] Trong khi NullPoiиteя thực sự đã làm một công việc tuyệt vời để viết nó, điều này chắc chắn không phải là một đọc tốt, bởi vì nó là cách để dài. Tôi khá chắc chắn rằng 8 trong số 10 khách truy cập sẽ đơn giản bỏ qua nó. Và bạn cũng có lời giải thích, tại sao câu trả lời này không được bình chọn hàng đầu. A tl;dr một phần trong đầu sẽ là một ý tưởng hay, tôi nghĩ vậy. - trejder
Câu hỏi đặt ra là "Tại sao tôi không nên sử dụng các hàm mysql_ * trong PHP". Câu trả lời này, trong khi ấn tượng và đầy đủ các thông tin hữu ích, đi WAY ngoài phạm vi và như @trejder nói - 8 trong số 10 người sẽ bỏ lỡ thông tin đó đơn giản chỉ vì họ không có 4 giờ để cố gắng làm việc thông qua nó. Điều này sẽ có giá trị hơn rất nhiều và được sử dụng làm câu trả lời cho một số câu hỏi chính xác hơn. - Alex McMillan
Persoanlly tôi thích mysqli và PDO hơn. Nhưng để xử lý chết, tôi đã thử thay thế ngoại lệ function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx(); Nó làm việc cho ném ngoại lệ. - kuldeep.kamboj


Trước tiên, hãy bắt đầu với nhận xét chuẩn mà chúng tôi cung cấp cho mọi người:

Làm ơn, đừng dùng mysql_* chức năng trong mã mới. Chúng không còn được duy trì nữa và được chính thức ngừng sử dụng. Xem hộp màu đỏ? Tìm hiểu về báo cáo chuẩn bị thay vào đó và sử dụng PDO hoặc là MySQLi - - bài viết này sẽ giúp bạn quyết định cái nào. Nếu bạn chọn PDO, đây là một hướng dẫn tốt.

Hãy xem qua câu này, từng câu một, và giải thích:

  • Chúng không còn được duy trì và được chính thức ngừng sử dụng

    Điều này có nghĩa là cộng đồng PHP đang dần giảm hỗ trợ cho các chức năng rất cũ này. Chúng có khả năng không tồn tại trong một phiên bản PHP (tương lai) trong tương lai! Việc tiếp tục sử dụng các chức năng này có thể làm hỏng mã của bạn trong tương lai xa (không phải như vậy).

    MỚI! - ext / mysql bây giờ chính thức không được chấp nhận như của PHP 5.5!

    Mới hơn! ext / mysql đã bị xóa trong PHP 7.

  • Thay vào đó, bạn nên tìm hiểu các câu lệnh chuẩn bị

    mysql_* tiện ích mở rộng không hỗ trợ báo cáo chuẩn bị, đó là (trong số những thứ khác) một biện pháp đối phó rất hiệu quả chống lại SQL Injection. Nó đã sửa một lỗ hổng rất nghiêm trọng trong các ứng dụng phụ thuộc vào MySQL, cho phép kẻ tấn công truy cập vào kịch bản lệnh của bạn và thực hiện mọi truy vấn có thể trên cơ sở dữ liệu của bạn.

    Để biết thêm thông tin, hãy xem Làm thế nào tôi có thể ngăn chặn SQL injection trong PHP?

  • Xem Hộp Đỏ?

    Khi bạn đi đến bất kỳ mysql trang hướng dẫn sử dụng chức năng, bạn thấy một hộp màu đỏ, giải thích nó không nên được sử dụng nữa.

  • Sử dụng PDO hoặc MySQLi

    Có lựa chọn thay thế tốt hơn, mạnh mẽ hơn và được xây dựng tốt hơn, PDO - Đối tượng cơ sở dữ liệu PHP, cung cấp phương pháp tiếp cận OOP hoàn chỉnh cho tương tác cơ sở dữ liệu và MySQLi, đó là một cải tiến cụ thể của MySQL.


280
2017-12-24 23:30



Có một điều nữa: tôi nghĩ rằng chức năng đó vẫn tồn tại trong PHP chỉ vì một lý do - tương thích với cũ, lỗi thời nhưng vẫn đang chạy CMS, thương mại điện tử, hệ thống bảng thông báo… Cuối cùng nó sẽ bị xóa và bạn sẽ phải viết lại ứng dụng... - Kamil
@Kamil: Đó là sự thật, nhưng nó không thực sự là một lý do tại sao bạn không nên sử dụng nó. Lý do không sử dụng nó là bởi vì nó cổ đại, không an toàn, vv :) - Madara Uchiha♦
@Mario - các nhà phát triển PHP có một quy trình, và họ đã bỏ phiếu ủng hộ việc chính thức không chấp nhận ext / mysql như là 5.5. Nó không còn là một vấn đề giả định nữa. - SDC
Thêm một vài dòng bổ sung với một kỹ thuật đã được chứng minh như PDO hoặc MySQLi vẫn dành cho sự dễ sử dụng PHP luôn được cung cấp. Tôi hy vọng vì lợi ích của nhà phát triển anh ta / cô ấy biết rằng nhìn thấy các chức năng mysql_ * thần kinh khủng khiếp trong bất kỳ hướng dẫn nào thực sự làm giảm bài học, và nên nói với OP rằng loại mã này là soooo 10 năm trước- và nên đặt câu hỏi sự liên quan của hướng dẫn, quá! - FredTheWebGuy
Những câu trả lời nên đề cập đến: tuyên bố chuẩn bị lấy đi bất kỳ sử dụng có ý nghĩa của IN (...) construct. - Eugen Rieck


Dễ sử dụng

Các lý do phân tích và tổng hợp đã được đề cập. Đối với những người mới đến, có một động lực đáng kể hơn để ngừng sử dụng các hàm mysql_ ngày tháng.

API cơ sở dữ liệu hiện đại chỉ là dễ dàng hơn để sử dụng.

Nó chủ yếu là thông số bị ràng buộc có thể đơn giản hóa mã. Và với hướng dẫn tuyệt vời (như đã thấy ở trên) quá trình chuyển đổi sang PDO không quá gian khổ.

Viết lại một cơ sở mã lớn hơn cùng một lúc tuy nhiên cần có thời gian. Raison d'être cho phương án thay thế trung gian này:

Hàm pdo_ * tương đương thay cho mysql_ *

Sử dụng <pdo_mysql.php> bạn có thể chuyển từ các hàm cũ của mysql_ Nỗ lực tối thiểu. Nó cho biết thêm pdo_ hàm bao hàm thay thế mysql_ đối tác.

  1. Đơn giản include_once("pdo_mysql.php"); trong mỗi kịch bản lệnh gọi phải tương tác với cơ sở dữ liệu.

  2. Gỡ bỏ mysql_ tiền tố hàm mọi nơi và thay thế bằng pdo_.

    • mysql_connect() trở thành pdo_connect()
    • mysql_query() trở thành pdo_query()
    • mysql_num_rows() trở thành pdo_num_rows()
    • mysql_insert_id() trở thành pdo_insert_id()
    • mysql_fetch_array() trở thành pdo_fetch_array()
    • mysql_fetch_assoc() trở thành pdo_fetch_assoc()
    • mysql_real_escape_string() trở thành pdo_real_escape_string()
    • và vân vân ... 

       
  3. Mã của bạn sẽ hoạt động giống nhau và hầu như vẫn giống nhau:

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }
    

Et voilà.
Mã của bạn là sử dụng PDO.
Bây giờ là lúc để thực sự sử dụng nó.

Các thông số ràng buộc có thể dễ sử dụng

Bạn chỉ cần một API ít khó sử dụng hơn.

pdo_query() thêm hỗ trợ rất dễ dàng cho các tham số ràng buộc. Chuyển đổi mã cũ đơn giản:

Di chuyển các biến của bạn ra khỏi chuỗi SQL.

  • Thêm chúng dưới dạng tham số hàm phân tách bằng dấu phẩy vào pdo_query().
  • Đặt dấu chấm hỏi ? như trình giữ chỗ, nơi các biến trước đây.
  • Thoát khỏi ' dấu ngoặc kép duy nhất mà trước đó kèm theo chuỗi giá trị / biến.

Lợi thế trở nên rõ ràng hơn cho mã dài hơn.

Các biến chuỗi thường không chỉ được nội suy vào SQL, mà còn nối với các cuộc gọi thoát ở giữa.

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

Với ? trình giữ chỗ được áp dụng mà bạn không phải bận tâm với điều đó:

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

Hãy nhớ rằng pdo_ * vẫn cho phép hoặc là.
Chỉ cần không thoát khỏi một biến  liên kết nó trong cùng một truy vấn.

  • Tính năng giữ chỗ được cung cấp bởi PDO thực sự đằng sau nó.
  • Như vậy cũng được cho phép :named danh sách trình giữ chỗ sau này.

Quan trọng hơn là bạn có thể chuyển các biến $ _REQUEST [] một cách an toàn sau bất kỳ truy vấn nào. Khi được gửi <form> các trường khớp với cấu trúc cơ sở dữ liệu chính xác nó thậm chí còn ngắn hơn:

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

Rất đơn giản. Nhưng hãy quay lại một số lời khuyên và lý do kỹ thuật viết lại nhiều hơn về lý do tại sao bạn có thể muốn loại bỏ mysql_và trốn thoát.

Sửa chữa hoặc loại bỏ bất kỳ oldschool sanitize() chức năng

Khi bạn đã chuyển đổi tất cả mysql_ cuộc gọi đến pdo_query với các tham số ràng buộc, loại bỏ tất cả các dự phòng pdo_real_escape_string cuộc gọi.

Cụ thể là bạn nên sửa bất kỳ sanitize hoặc là clean hoặc là filterThis hoặc là clean_data các chức năng như được quảng cáo bởi các hướng dẫn ngày trong một biểu mẫu hoặc dạng khác:

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

Lỗi rõ ràng nhất ở đây là thiếu tài liệu. Thứ tự lọc đáng kể hơn theo thứ tự sai.

  • Thứ tự chính xác sẽ là: không được chấp nhận stripslashes như cuộc gọi trong cùng, sau đó trim, sau đó strip_tags, htmlentities cho ngữ cảnh đầu ra và chỉ cuối cùng là _escape_string vì ứng dụng của nó nên trực tiếp trước SQL xen kẽ.

  • Nhưng như bước đầu tiên Thoát khỏi _real_escape_string gọi điện.

  • Bạn có thể phải giữ phần còn lại của sanitize() ngay bây giờ nếu cơ sở dữ liệu và luồng ứng dụng của bạn mong đợi các chuỗi HTML-context-safe. Thêm một nhận xét rằng nó chỉ áp dụng HTML thoát từ nay trở đi.

  • Xử lý chuỗi / giá trị được gán cho PDO và các câu lệnh được tham số hóa của nó.

  • Nếu có bất kỳ đề cập đến stripslashes() trong chức năng vệ sinh của bạn, nó có thể chỉ ra một sự giám sát cấp cao hơn.

    Lưu ý lịch sử về magic_quotes. Tính năng đó bị phản đối đúng cách. Nó thường được mô tả không chính xác là không thành công Bảo vệ tính năng tuy nhiên. Nhưng magic_quotes là một tính năng bảo mật thất bại nhiều như quả bóng tennis đã thất bại như nguồn dinh dưỡng. Đó không phải là mục đích của họ.

    Việc thực hiện ban đầu trong PHP2 / FI đã giới thiệu nó một cách rõ ràng chỉ với "dấu ngoặc kép sẽ tự động được thoát ra giúp việc chuyển dữ liệu biểu mẫu trực tiếp đến các truy vấn msql dễ dàng hơn"Đáng chú ý là việc sử dụng an toàn một cách tình cờ với mSQL, chỉ hỗ trợ ASCII.
      Sau đó, PHP3 / Zend giới thiệu lại magic_quotes cho MySQL và misdocumented nó. Nhưng ban đầu nó chỉ là một tính năng tiện lợi, không có ý định bảo mật.

Các câu lệnh chuẩn bị khác nhau như thế nào

Khi bạn tranh giành các biến chuỗi thành các truy vấn SQL, nó không chỉ phức tạp hơn để bạn theo dõi. Đó cũng là nỗ lực không liên quan để MySQL tách biệt mã và dữ liệu một lần nữa.

SQL injection đơn giản là khi dữ liệu chảy máu thành mã bối cảnh. Một máy chủ cơ sở dữ liệu không thể phát hiện ra nơi PHP ban đầu dán các biến inbetween điều khoản truy vấn.

Với các tham số bị ràng buộc, bạn phân tách các mã SQL và các giá trị ngữ cảnh SQL trong mã PHP của bạn. Nhưng nó không bị xáo trộn trở lại đằng sau hậu trường (ngoại trừ với PDO :: EMULATE_PREPARES). Cơ sở dữ liệu của bạn nhận được các câu lệnh SQL chưa được trộn lẫn và các giá trị biến 1: 1.

Trong khi câu trả lời này nhấn mạnh rằng bạn nên quan tâm đến những lợi thế dễ đọc của việc giảm mysql_. Đôi khi cũng có một lợi thế hiệu suất (INSERTs lặp đi lặp lại chỉ với các giá trị khác nhau) do việc tách dữ liệu / mã có thể nhìn thấy và kỹ thuật này.

Hãy coi chừng ràng buộc tham số vẫn không phải là một giải pháp một cửa ma thuật chống lại tất cả các SQL injection. Nó xử lý việc sử dụng phổ biến nhất cho dữ liệu / giá trị. Nhưng không thể đưa vào danh sách trắng tên / bảng định danh bảng trắng, trợ giúp xây dựng mệnh đề động hoặc chỉ các danh sách giá trị mảng đơn giản.

Sử dụng PDO lai

Những pdo_* các hàm bao bọc tạo ra API dừng lỗ mã hóa thân thiện với mã hóa. (Nó khá là nhiều MYSQLI có thể đã được nếu nó không phải là cho sự thay đổi chữ ký chức năng theo phong cách riêng). Họ cũng phơi bày PDO thực sự nhiều nhất.
Viết lại không phải dừng sử dụng tên hàm pdo_ mới. Bạn có thể từng người một chuyển đổi pdo_query () thành một lệnh $ pdo-> prepare () -> execute () đơn giản.

Tốt nhất là nên bắt đầu đơn giản hóa lại. Ví dụ: tìm nạp kết quả phổ biến:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

Có thể được thay thế bằng một lần lặp lại foreach:

foreach ($result as $row) {

Hoặc tốt hơn là truy xuất mảng trực tiếp và đầy đủ:

$result->fetchAll();

Bạn sẽ nhận được nhiều cảnh báo hữu ích hơn trong hầu hết các trường hợp so với PDO hoặc mysql_ thường cung cấp sau khi truy vấn không thành công.

Sự lựa chọn khác

Vì vậy, điều này hy vọng hình dung một số thực tế lý do và con đường đáng tin cậy để giảm mysql_.

Chỉ cần chuyển sang  không hoàn toàn cắt nó. pdo_query() cũng chỉ là một lối vào trên nó.

Trừ khi bạn cũng giới thiệu tham số ràng buộc hoặc có thể sử dụng cái gì khác từ API đẹp hơn, đó là một công tắc vô nghĩa. Tôi hy vọng nó được miêu tả đơn giản, đủ để không làm nản lòng những người mới đến. (Giáo dục thường hoạt động tốt hơn là cấm.)

Trong khi nó đủ điều kiện cho thể loại đơn giản nhất có thể-có-thể-làm việc, nó cũng vẫn còn rất thử nghiệm mã. Tôi vừa viết nó vào cuối tuần. Có rất nhiều lựa chọn thay thế. Chỉ cần google cho PHP trừu tượng cơ sở dữ liệu và duyệt một chút. Luôn luôn có và sẽ có rất nhiều thư viện tuyệt vời cho các nhiệm vụ như vậy.

Nếu bạn muốn đơn giản hóa tương tác cơ sở dữ liệu của mình hơn nữa, những người vẽ bản đồ thích Paris / Idiorm đáng để thử. Cũng giống như không ai sử dụng DOM nhạt nhẽo trong JavaScript nữa, bạn không cần phải giữ một giao diện cơ sở dữ liệu thô ngày nay.


201
2017-10-12 13:22



Hãy cẩn thận với pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST); chức năng - tức là: pdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true'); - rickyduck
@Tom Chắc chắn, mặc dù nó không được duy trì nhiều (0.9.2 là cuối cùng), bạn có thể tạo tài khoản hóa thạch, thêm vào wiki hoặc tệp báo cáo lỗi (không đăng ký IIRC). - mario


Các mysql_ chức năng:

  1. đã lỗi thời - chúng không được duy trì nữa
  2. không cho phép bạn di chuyển dễ dàng đến một chương trình cơ sở dữ liệu khác
  3. không hỗ trợ các câu lệnh chuẩn bị, do đó
  4. khuyến khích các lập trình viên sử dụng nối để xây dựng các truy vấn, dẫn đến lỗ hổng SQL injection

136
2018-01-01 17:42



# 2 cũng đúng như vậy mysqli_ - eggyal
công bằng, với các biến thể trong phương ngữ SQL, ngay cả PDO cũng không cung cấp cho bạn # 2 bất kỳ mức độ chắc chắn nào. Bạn sẽ cần một wrapper ORM thích hợp cho điều đó. - SDC
@SDC thực sự - vấn đề với tiêu chuẩn là có vì thế nhiều người trong số họ... - Alnitak
xkcd.com/927 - Lightness Races in Orbit
các mysql_* function là một shell vào các hàm mysqlnd cho các phiên bản PHP mới hơn. Vì vậy, ngay cả khi thư viện khách hàng cũ không được duy trì nữa, mysqlnd được duy trì :) - hakre


Nói về kỹ thuật lý do, chỉ có một vài, cực kỳ cụ thể và hiếm khi được sử dụng. Nhiều khả năng bạn sẽ không bao giờ sử dụng chúng trong cuộc sống của bạn.
Có lẽ tôi quá ngu dốt, nhưng tôi chưa bao giờ có cơ hội sử dụng chúng như

  • truy vấn không đồng bộ, không chặn
  • thủ tục lưu trữ trả về nhiều bộ kết quả
  • Mã hóa (SSL)
  • Nén

Nếu bạn cần chúng - đây không phải là lý do kỹ thuật để di chuyển ra khỏi phần mở rộng mysql hướng tới một cái gì đó phong cách và hiện đại hơn.

Tuy nhiên, cũng có một số vấn đề phi kỹ thuật, có thể làm cho trải nghiệm của bạn khó hơn một chút

  • việc sử dụng thêm các chức năng này với các phiên bản PHP hiện đại sẽ làm tăng các thông báo mức không được chấp nhận. Họ chỉ đơn giản là có thể được tắt.
  • trong một tương lai xa, chúng có thể bị loại bỏ khỏi bản dựng PHP mặc định. Không phải là một vấn đề lớn, như mydsql ext sẽ được chuyển vào PECL và mỗi hoster sẽ rất vui khi biên dịch PHP với nó, vì họ không muốn mất khách hàng mà các trang web của họ hoạt động trong nhiều thập kỷ.
  • sức đề kháng mạnh mẽ từ cộng đồng Stackoverflow. Еverytime bạn đề cập đến các chức năng trung thực, bạn đang được nói rằng họ đang bị cấm kỵ nghiêm ngặt.
  • là người dùng PHP trung bình, rất có thể ý tưởng của bạn về việc sử dụng các hàm này là dễ bị lỗi và sai. Chỉ vì tất cả các hướng dẫn và hướng dẫn sử dụng này sẽ dạy bạn một cách sai lầm. Không phải bản thân các chức năng - tôi phải nhấn mạnh nó - nhưng cách chúng được sử dụng.

Vấn đề thứ hai này là một vấn đề.
Nhưng, theo ý kiến ​​của tôi, giải pháp được đề xuất cũng không tốt hơn.
Dường như với tôi quá lý tưởng một giấc mơ là tất cả những người dùng PHP đó sẽ học cách xử lý các truy vấn SQL một cách chính xác cùng một lúc. Nhiều khả năng họ sẽ chỉ thay đổi mysql_ * thành mysqli_ * một cách máy móc, rời khỏi phương pháp tương tự. Đặc biệt là bởi vì mysqli làm cho các câu phát biểu chuẩn bị sử dụng đáng kinh ngạc đau đớn và phiền hà.
Không phải đề cập đến đó tự nhiên báo cáo chuẩn bị không đủ để bảo vệ từ việc tiêm SQL và cả mysqli lẫn PDO đều không cung cấp giải pháp.

Vì vậy, thay vì chiến đấu với phần mở rộng trung thực này, tôi muốn chống lại những thực hành sai trái và giáo dục con người theo đúng cách.

Ngoài ra, có một số lý do sai hoặc không quan trọng, như

  • Không hỗ trợ Quy trình được lưu trữ (chúng tôi đã sử dụng mysql_query("CALL my_proc"); cho lứa tuổi)
  • Không hỗ trợ Giao dịch (giống như trên)
  • Không hỗ trợ nhiều câu lệnh (ai cần chúng?)
  • Không được phát triển tích cực (vì vậy nó có ảnh hưởng gì? bạn theo bất kỳ cách thực tế nào?)
  • Thiếu một giao diện OO (để tạo một giao diện là một vài giờ)
  • Không hỗ trợ báo cáo được chuẩn bị hoặc truy vấn được tham số

Người cuối cùng là một điểm thú vị. Mặc dù mysql ext không hỗ trợ tự nhiên tuyên bố chuẩn bị, họ không cần thiết cho sự an toàn. Chúng ta có thể dễ dàng giả mạo các câu lệnh chuẩn bị bằng cách sử dụng các trình giữ chỗ được xử lý thủ công (giống như PDO):

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

voila, mọi thứ đều được tham số hóa và an toàn.

Nhưng được rồi, nếu bạn không thích hộp màu đỏ trong hướng dẫn sử dụng, một vấn đề của sự lựa chọn phát sinh: mysqli hoặc PDO?

Vâng, câu trả lời sẽ như sau:

  • Nếu bạn hiểu sự cần thiết của việc sử dụng lớp trừu tượng cơ sở dữ liệu và tìm kiếm một API để tạo một, mysqli là một lựa chọn rất tốt, vì nó thực sự hỗ trợ nhiều tính năng cụ thể của mysql.
  • Nếu, giống như đại đa số người dùng PHP, bạn đang sử dụng các lệnh gọi API thô ngay trong mã ứng dụng (thực tế là thực tế sai) - PDO là lựa chọn duy nhất, vì phần mở rộng này giả vờ không chỉ là API mà là một nửa bán DAL, vẫn chưa hoàn chỉnh nhưng cung cấp nhiều tính năng quan trọng, với hai trong số chúng làm cho PDO phân biệt rõ ràng với mysqli:

    • không giống như mysqli, PDO có thể ràng buộc trình giữ chỗ theo giá trị, làm cho các truy vấn được xây dựng động khả thi mà không cần một số màn hình có mã khá lộn xộn.
    • không giống như mysqli, PDO luôn có thể trả về kết quả truy vấn trong một mảng thông thường đơn giản, trong khi mysqli chỉ có thể thực hiện việc đó trên các cài đặt mysqlnd.

Vì vậy, nếu bạn là một người dùng PHP trung bình và muốn tiết kiệm cho mình một tấn nhức đầu khi sử dụng các câu lệnh tự chế, PDO - một lần nữa - là lựa chọn duy nhất.
Tuy nhiên, PDO không phải là một viên đạn bạc và có những khó khăn của nó.
Vì vậy, tôi đã viết giải pháp cho tất cả các cạm bẫy phổ biến và các trường hợp phức tạp trong PDO tag wiki

Tuy nhiên, mọi người nói về tiện ích mở rộng luôn thiếu 2 sự kiện quan trọng về Mysqli và PDO:

  1. Tuyên bố được chuẩn bị không phải là viên đạn bạc. Có các số nhận dạng động không thể bị ràng buộc sử dụng các câu lệnh đã chuẩn bị. Có các truy vấn động với số lượng tham số không xác định khiến truy vấn xây dựng một nhiệm vụ khó khăn.

  2. Cả mysqli_ * lẫn các hàm PDO đều không xuất hiện trong mã ứng dụng.
    Phải có một lớp trừu tượng giữa chúng và mã ứng dụng, sẽ làm tất cả các công việc bẩn thỉu của ràng buộc, lặp, xử lý lỗi, vv bên trong, làm cho mã ứng dụng KHÔ và sạch sẽ. Đặc biệt đối với các trường hợp phức tạp như xây dựng truy vấn động.

Vì vậy, chỉ cần chuyển sang PDO hoặc mysqli là không đủ. Người ta phải sử dụng ORM hoặc trình tạo truy vấn hoặc bất kỳ lớp trừu tượng cơ sở dữ liệu nào thay vì gọi hàm API thô trong mã của họ.
Và ngược lại - nếu bạn có một lớp trừu tượng giữa mã ứng dụng của bạn và API mysql - nó không thực sự quan trọng mà động cơ được sử dụng. Bạn có thể sử dụng mysql ext cho đến khi nó không được chấp nhận và sau đó dễ dàng viết lại lớp trừu tượng của bạn cho một công cụ khác, có tất cả mã ứng dụng còn nguyên vẹn.

Dưới đây là một số ví dụ dựa trên lớp safemysql để cho thấy lớp trừu tượng như vậy phải như thế nào:

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

So sánh một dòng này với số lượng mã bạn cần với PDO.
Sau đó so sánh với số lượng mã điên bạn sẽ cần với các câu lệnh chuẩn bị của Mysqli. Lưu ý rằng việc xử lý lỗi, lược tả, ghi nhật ký truy vấn đã được xây dựng và chạy.

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

So sánh nó với chèn PDO thông thường, khi mỗi tên trường duy nhất được lặp lại từ sáu đến mười lần - trong tất cả các trình giữ chỗ, gắn kết và định nghĩa truy vấn được đặt tên.

Một vi dụ khac:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

Bạn khó có thể tìm thấy một ví dụ cho PDO để xử lý trường hợp thực tế như vậy.
Và nó sẽ quá dài dòng và rất có thể không an toàn.

Vì vậy, một lần nữa - nó không chỉ là trình điều khiển thô nên là mối quan tâm của bạn nhưng lớp trừu tượng, hữu ích không chỉ cho các ví dụ ngớ ngẩn từ hướng dẫn của người mới bắt đầu mà còn giải quyết bất kỳ vấn đề thực tế nào.


99
2017-10-12 13:23



mysql_* làm cho dễ bị tổn thương rất dễ xảy ra. Vì PHP được sử dụng bởi rất nhiều người dùng mới làm quen, mysql_* đang tích cực có hại trong thực tế, ngay cả khi trong lý thuyết nó có thể được sử dụng mà không có một xô. - Madara Uchiha♦
everything is parameterized and safe- nó có thể được tham số hóa, nhưng chức năng của bạn không sử dụng thực báo cáo chuẩn bị. - uınbɐɥs
Thế nào là Not under active development chỉ cho rằng make-up '0,01%'? Nếu bạn xây dựng một cái gì đó với chức năng đứng yên này, hãy cập nhật phiên bản mysql của bạn trong một năm và kết thúc với một hệ thống không hoạt động, tôi chắc chắn có rất nhiều người bất ngờ trong đó '0,01%'. Tôi muốn nói điều đó deprecated và not under active development có liên quan chặt chẽ. Bạn có thể nói rằng có "không có [lý do] xứng đáng" cho nó, nhưng thực tế là khi được cung cấp một sự lựa chọn giữa các tùy chọn, no active development gần như cũng tệ như deprecated Tôi sẽ nói? - Nanne
@ MadaraUchiha: Bạn có thể giải thích cách dễ bị tổn thương đến không? Đặc biệt trong trường hợp những lỗ hổng tương tự không ảnh hưởng đến PDO hoặc MySQLi ... Bởi vì tôi không nhận thức được một lỗi duy nhất mà bạn nói đến. - ircmaxell
@ShaquinTrifonoff: chắc chắn, nó không sử dụng báo cáo chuẩn bị. Nhưng PDO cũng không, mà hầu hết mọi người khuyên dùng MySQLi. Vì vậy, tôi không chắc chắn rằng có một tác động đáng kể ở đây. Đoạn mã trên (với phân tích nhiều hơn một chút) là những gì PDO thực hiện khi bạn chuẩn bị một câu lệnh theo mặc định ... - ircmaxell


Có nhiều lý do, nhưng có lẽ lý do quan trọng nhất là các chức năng đó khuyến khích thực hành lập trình không an toàn bởi vì chúng không hỗ trợ các câu lệnh chuẩn bị. Các câu lệnh chuẩn bị giúp ngăn chặn các cuộc tấn công SQL injection.

Khi đang sử dụng mysql_* các hàm, bạn phải nhớ chạy các tham số do người dùng cung cấp thông qua mysql_real_escape_string(). Nếu bạn quên chỉ ở một nơi hoặc nếu bạn tình cờ thoát khỏi phần đầu vào, cơ sở dữ liệu của bạn có thể bị tấn công.

Sử dụng câu lệnh đã chuẩn bị trong PDO hoặc là mysqli sẽ làm cho nó để các loại lỗi lập trình này khó thực hiện hơn.


88
2017-10-12 13:24



Thật không may sự hỗ trợ nghèo nàn trong MySQLi_ * để truyền một số biến tham số (như khi bạn muốn chuyển một danh sách các giá trị để kiểm tra trong một mệnh đề IN) khuyến khích không sử dụng các tham số, khuyến khích sử dụng chính xác các truy vấn được ghép nối giống nhau để lại các cuộc gọi MySQL_ * dễ bị tổn thương. - Kickstart
Nhưng, một lần nữa, sự bất an không phải là một vấn đề vốn có của các hàm mysql_ *, mà là một vấn đề sử dụng không chính xác. - Agamemnus
@Agamemnus Vấn đề là mysql_ * làm cho nó dễ dàng để thực hiện rằng "sử dụng không chính xác", đặc biệt là cho các lập trình viên thiếu kinh nghiệm. Các thư viện triển khai các câu lệnh đã được chuẩn bị làm cho việc tạo ra loại lỗi đó trở nên khó khăn hơn. - Trott


Bởi vì (trong số các lý do khác) khó khăn hơn nhiều để đảm bảo dữ liệu đầu vào được vệ sinh. Nếu bạn sử dụng truy vấn parametrized, như một trong những hiện với PDO hoặc mysqli bạn hoàn toàn có thể tránh được rủi ro.

Ví dụ, ai đó có thể sử dụng "enhzflep); drop table users" dưới dạng tên người dùng. Các chức năng cũ sẽ cho phép thực hiện nhiều câu lệnh cho mỗi truy vấn, vì vậy một cái gì đó giống như bugger khó chịu đó có thể xóa toàn bộ bảng.

Nếu một người sử dụng PDO của mysqli, tên người dùng sẽ kết thúc "enhzflep); drop table users".

Xem bobby-tables.com.


71
2017-09-18 12:28



The old functions will allow executing of multiple statements per query - không, họ sẽ không. Đó là loại tiêm là không thể với ext / mysql - cách duy nhất loại tiêm này là có thể với PHP và MySQL là khi sử dụng MySQLi và mysqli_multi_query() chức năng. Loại tiêm có thể với ext / mysql và các chuỗi không thoát là những thứ như ' OR '1' = '1 để trích xuất dữ liệu từ cơ sở dữ liệu không có khả năng truy cập được. Trong các tình huống nhất định có thể tiêm các truy vấn phụ, tuy nhiên nó vẫn không thể sửa đổi cơ sở dữ liệu theo cách này. - DaveRandom


Câu trả lời này được viết để chỉ ra tầm quan trọng của việc sử dụng mã xác thực người dùng PHP bằng văn bản, cách thức (và sử dụng cái gì) các cuộc tấn công này hoạt động và cách thay thế các hàm cũ của MySQL bằng một câu lệnh đã được chuẩn bị an toàn - và về cơ bản, tại sao người dùng StackOverflow (có lẽ với rất nhiều đại diện) đang sủa ở những người dùng mới đặt câu hỏi để cải thiện mã của họ.

Trước hết, xin vui lòng tạo cơ sở dữ liệu mysql thử nghiệm này (tôi đã gọi là chuẩn bị mỏ):

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

Khi thực hiện xong, chúng ta có thể chuyển sang mã PHP của mình.

Giả sử tập lệnh sau là quá trình xác minh cho quản trị viên trên trang web (được đơn giản hóa nhưng hoạt động nếu bạn sao chép và sử dụng nó để thử nghiệm):

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Có vẻ như đủ ngay từ cái nhìn đầu tiên.

Người dùng phải nhập tên đăng nhập và mật khẩu, phải không?

Rực rỡ, không nhập vào những điều sau đây:

user: bob
pass: somePass

và gửi nó.

Đầu ra như sau:

You could not be verified. Please try again...

Siêu! Làm việc như mong đợi, bây giờ hãy thử tên người dùng và mật khẩu thực tế:

user: Fluffeh
pass: mypass

Kinh ngạc! Hi-fives tất cả các vòng, mã xác nhận chính xác một admin. Thật hoàn hảo!

Vâng, không thực sự. Giả sử người dùng là một người ít thông minh. Cho phép nói rằng người đó là tôi.

Nhập nội dung sau:

user: bob
pass: n' or 1=1 or 'm=m

Và đầu ra là:

The check passed. We have a verified admin!

Xin chúc mừng, bạn chỉ cho phép tôi nhập phần quản trị viên được bảo vệ siêu dữ liệu của bạn khi tôi nhập tên người dùng sai và mật khẩu giả. Nghiêm túc, nếu bạn không tin tôi, hãy tạo cơ sở dữ liệu với mã tôi đã cung cấp và chạy mã PHP này - cái nhìn thoáng qua THỰC SỰ có vẻ như xác minh tên người dùng và mật khẩu khá độc đáo.

Vì vậy, trong câu trả lời, đó là lý do tại sao bạn đang được chào bán.

Vì vậy, cho phép có một cái nhìn vào những gì đã đi sai, và tại sao tôi chỉ nhận được vào siêu-admin-chỉ-bat-hang của bạn. Tôi đã đoán và giả định rằng bạn đã không được cẩn thận với các yếu tố đầu vào của bạn và chỉ cần thông qua chúng vào cơ sở dữ liệu trực tiếp. Tôi đã xây dựng đầu vào theo cách mà THAY ĐỔI truy vấn mà bạn đang thực sự chạy. Vì vậy, nó được cho là gì, và nó đã kết thúc như thế nào?

select id, userid, pass from users where userid='$user' and pass='$pass'

Đó là truy vấn, nhưng khi chúng tôi thay thế các biến bằng các đầu vào thực tế mà chúng tôi đã sử dụng, chúng tôi nhận được các thông tin sau:

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

Xem cách tôi xây dựng "mật khẩu" của tôi để nó sẽ đóng dấu ngoặc kép đầu tiên xung quanh mật khẩu, sau đó giới thiệu một so sánh hoàn toàn mới? Sau đó, chỉ để an toàn, tôi đã thêm một "chuỗi" khác để báo giá đơn lẻ sẽ bị đóng như mong đợi trong mã mà ban đầu chúng tôi có.

Tuy nhiên, đây không phải là về những người đang hét lên với bạn bây giờ, đây là cách chỉ cho bạn cách làm cho mã của bạn an toàn hơn.

Được rồi, vậy điều gì đã xảy ra và chúng ta có thể sửa nó như thế nào?

Đây là một cuộc tấn công SQL injection cổ điển. Một trong những điều đơn giản nhất cho vấn đề đó. Trên quy mô của các vectơ tấn công, đây là một đứa trẻ tấn công một chiếc xe tăng - và chiến thắng.

Vì vậy, làm thế nào để chúng tôi bảo vệ phần quản trị thiêng liêng của bạn và làm cho nó tốt đẹp và an toàn? Điều đầu tiên cần làm là ngừng sử dụng những thứ thực sự cũ và không được chấp nhận mysql_* chức năng. Tôi biết, bạn đã làm theo một hướng dẫn bạn tìm thấy trực tuyến và nó hoạt động, nhưng nó cũ, nó đã lỗi thời và trong không gian của một vài phút, tôi vừa phá vỡ nó mà không có quá nhiều mồ hôi.

Bây giờ, bạn có tùy chọn sử dụng tốt hơn mysqli_ hoặc là PDO. Cá nhân tôi là một fan hâm mộ lớn của PDO, vì vậy tôi sẽ sử dụng PDO trong phần còn lại của câu trả lời này. Có những người thân và con, nhưng cá nhân tôi thấy rằng người chuyên nghiệp vượt xa con người. Nó di động trên nhiều cơ sở dữ liệu - cho dù bạn đang sử dụng MySQL hay Oracle hay chỉ là về bất cứ thứ gì đẫm máu - chỉ bằng cách thay đổi chuỗi kết nối, nó có tất cả các tính năng ưa thích mà chúng tôi muốn sử dụng và nó đẹp và sạch sẽ. Tôi thích sạch.

Bây giờ, hãy xem lại mã đó, lần này được viết bằng cách sử dụng đối tượng PDO:

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

Sự khác biệt chính là không còn nữa mysql_* chức năng. Tất cả được thực hiện thông qua một đối tượng PDO, thứ hai, nó đang sử dụng một câu lệnh chuẩn bị. Bây giờ, bạn đã hỏi câu hỏi nào trước đây? Đó là một cách để nói với cơ sở dữ liệu trước khi chạy một truy vấn, truy vấn mà chúng ta sẽ chạy là gì. Trong trường hợp này, chúng tôi nói với cơ sở dữ liệu: "Xin chào, tôi sẽ chạy một câu lệnh chọn id, userid và pass từ người dùng bảng trong đó userid là một biến và pass cũng là một biến".

Sau đó, trong câu lệnh execute, chúng ta chuyển cơ sở dữ liệu một mảng với tất cả các biến mà nó mong đợi.

Kết quả thật tuyệt vời. Cho phép thử các kết hợp tên người dùng và mật khẩu đó từ trước:

user: bob
pass: somePass

Người dùng chưa được xác minh. Tuyệt vời.

Làm thế nào về:

user: Fluffeh
pass: mypass

Oh, tôi chỉ có một chút vui mừng, nó đã làm việc: Kiểm tra đã trôi qua. Chúng tôi có một quản trị viên đã được xác minh!

Bây giờ, hãy thử các dữ liệu mà một chap thông minh sẽ nhập để cố gắng vượt qua hệ thống xác minh nhỏ của chúng tôi:

user: bob
pass: n' or 1=1 or 'm=m

Lần này, chúng ta có được những điều sau đây:

You could not be verified. Please try again...

Đây là lý do tại sao bạn bị mắng khi đăng câu hỏi - đó là bởi vì mọi người có thể thấy rằng mã của bạn có thể bị bỏ qua ngay cả khi cố gắng. Xin vui lòng, sử dụng câu hỏi này và câu trả lời để cải thiện mã của bạn, để làm cho nó an toàn hơn và sử dụng các chức năng hiện tại.

Cuối cùng, đây không phải là để nói rằng đây là mã PERFECT. Có nhiều thứ bạn có thể làm để cải thiện nó, sử dụng mật khẩu băm ví dụ, đảm bảo rằng khi bạn lưu trữ thông tin nhạy cảm trong cơ sở dữ liệu, bạn không lưu trữ nó trong văn bản thuần túy, có nhiều cấp xác minh - nhưng thực sự, nếu bạn chỉ cần thay đổi mã dễ bị tiêm cũ của bạn thành điều này, bạn sẽ WELL trên đường viết mã tốt - và thực tế là bạn đã đạt được điều này và vẫn đọc cho tôi cảm giác hy vọng rằng bạn sẽ không chỉ thực hiện loại này của mã khi viết trang web và ứng dụng của bạn, nhưng bạn có thể đi ra ngoài và nghiên cứu những thứ khác mà tôi vừa đề cập - và hơn thế nữa. Viết mã tốt nhất bạn có thể, không phải là mã cơ bản nhất mà hầu như không hoạt động.


63
2017-09-02 07:20



Cảm ơn bạn vì câu trả lời! Có +1 của tôi! Cần lưu ý rằng mysql_* trên bản thân nó không phải là không an toàn, nhưng nó thúc đẩy mã không an toàn thông qua các hướng dẫn xấu và thiếu một API chuẩn bị tuyên bố thích hợp. - Madara Uchiha♦
mật khẩu chưa được giải mã, oh kinh dị! = oP Nếu không +1 để được giải thích chi tiết. - cryptic ツ


Phần mở rộng MySQL là phần mở rộng lâu đời nhất trong số ba và là cách ban đầu mà các nhà phát triển sử dụng để giao tiếp với MySQL. Tiện ích mở rộng này hiện đang được không dùng nữa ủng hộ người khác hai  lựa chọn thay thế vì các cải tiến được thực hiện trong các bản phát hành mới hơn của cả PHP và MySQL.

  • MySQLi là phần mở rộng 'được cải thiện' để làm việc với cơ sở dữ liệu MySQL. Nó tận dụng các tính năng có sẵn trong các phiên bản mới hơn của máy chủ MySQL, cho thấy cả một định hướng hàm và một giao diện hướng đối tượng cho nhà phát triển và một vài thứ tiện lợi khác.

  • PDO cung cấp một API hợp nhất hầu hết các chức năng mà trước đây đã trải rộng trên các phần mở rộng truy cập cơ sở dữ liệu chính, ví dụ như MySQL, PostgreSQL, SQLite, MSSQL, vv Giao diện hiển thị các đối tượng cấp cao để lập trình làm việc với các kết nối cơ sở dữ liệu, truy vấn và kết quả bộ và trình điều khiển cấp thấp thực hiện việc xử lý thông tin và tài nguyên với máy chủ cơ sở dữ liệu. Rất nhiều cuộc thảo luận và công việc đang đi vào PDO và nó được coi là phương pháp thích hợp để làm việc với các cơ sở dữ liệu trong mã chuyên nghiệp, hiện đại.


31
2017-09-07 15:06