Статьи

Антология PHP Том I, Глава 3 — PHP и MySQL

Сегодня в Интернете контент — это король.

–Кевин Янк

В «старые времена» Интернета большинство веб-страниц были не более чем текстовыми файлами, содержащими HTML. Когда люди заходили на ваш сайт, ваш веб-сервер просто делал файл доступным для своего браузера, который анализировал содержимое и отображал что-то, что мог прочитать человек. Этот подход был хорош для начала, но по мере роста Веб-сайтов и того, что такие вопросы, как дизайн и навигация, становились все более важными, разработчики осознали, что поддержка сотен файлов HTML будет большой головной болью. Чтобы решить эту проблему, стало популярным отделять переменный контент (статьи, новости и т. Д.) От статических элементов сайта — его дизайна и макета.

Используя базу данных в качестве хранилища для хранения переменного содержимого, серверный язык, такой как PHP, выполняет задачу извлечения данных и размещения их в единой «структуре», при этом элементы дизайна и макета используются повторно. Это означает, что изменение общего внешнего вида сайта может рассматриваться как отдельная задача от добавления или обслуживания контента. Внезапно, запуск Веб-сайта больше не является задачей, которая потребляет разработчика каждый час бодрствования.

PHP поддерживает все реляционные базы данных, которые стоит упомянуть, включая те, которые обычно используются в крупных компаниях, таких как Oracle, IBM DB2 и Microsoft SQL Server. Двумя наиболее примечательными альтернативами с открытым исходным кодом являются PostgreSQL и MySQL. Хотя PostgreSQL, возможно, является лучшей базой данных, поскольку она поддерживает больше функций, общих для реляционных баз данных, MySQL лучше поддерживается в Windows и является популярным выбором среди веб-хостов, которые предоставляют поддержку PHP. Эти факторы в совокупности делают PHP и MySQL очень популярной комбинацией. Эта книга ориентирована на использование MySQL с PHP, но важно помнить, что существуют альтернативы с полной поддержкой таких функций, как хранимые процедуры, триггеры и ограничения, многие из которых становятся важными для приложений со сложными структурами данных.

В этой главе рассматриваются все общие операции, которые PHP-разработчики должны выполнять при работе с MySQL, от извлечения и изменения данных до поиска и резервного копирования базы данных. Примеры сосредоточены на использовании одной таблицы, поэтому здесь не обсуждаются отношения между таблицами. Полное обсуждение взаимосвязей таблиц см. В статье Кевина Янка «Создайте свой собственный веб-сайт, управляемый базой данных с использованием PHP и MySQL» (ISBN 0-9579218-1-0), или посмотрите их пример на практике, когда мы рассмотрим группы пользователей в главе 1, Контроль доступа.

Используемые здесь примеры работают с образцом базы данных под названием sitepoint, которая содержит следующую единственную таблицу:

Example 3.1. articles.sql   CREATE TABLE articles (  article_id INT(11)       NOT NULL AUTO_INCREMENT,  title      VARCHAR(255)  NOT NULL DEFAULT '',  intro      TEXT          NOT NULL,  body       TEXT          NOT NULL,  author     VARCHAR(255)  NOT NULL DEFAULT '',  published  VARCHAR(11)   DEFAULT NULL,  public     ENUM('0','1') NOT NULL DEFAULT '0',  PRIMARY KEY (article_id),  FULLTEXT KEY art_search (title, body, author)  ) 

Запрос для построения этой таблицы вместе с некоторыми примерами данных доступен в архиве кода, который содержится в файле sql / article.sql. Таблица будет использоваться для примеров в последующих главах книги.

Как получить доступ к базе данных MySQL?

Подключение к MySQL с помощью PHP не может быть проще. По сути, это двухэтапный процесс; сначала подключитесь к самому серверу базы данных MySQL, затем сообщите MySQL о базе данных, к которой вы хотите подключиться.

Основное соединение

Вот соединение с базой данных MySQL в его самой простой форме:

 Example 3.2. 1.php   <?php  $host   = 'localhost'; // Hostname of MySQL server  $dbUser = 'harryf';    // Username for MySQL  $dbPass = 'secret';    // Password for user  $dbName = 'sitepoint'; // Database name   // Make connection to MySQL server  if (!$dbConn = mysql_connect($host, $dbUser, $dbPass)) {  die('Could not connect to server');  }   // Select the database  if (!mysql_select_db($dbName, $dbConn)) {  die('Could not select database');  }   echo 'Connection successful!';  // ... some code here using MySQL   // Close the connection when finished  mysql_close($dbConn);  ?> 

Важно помнить, что MySQL — это отдельная серверная программа, очень похожая на Apache. Оба сервера могут работать на одном физическом компьютере (следовательно, мы используем $host = 'localhost'; в приведенном выше примере), но также возможно подключиться к MySQL на удаленном компьютере, например, $host = 'anothercomputer.com'; , Чтобы сделать вещи немного более интересными, MySQL также имеет свой собственный номер порта, который по умолчанию равен 3306. PHP предполагает, что 3306 будет номером порта, но если вам нужно использовать другой, все, что вам нужно, это $host = 'anothercomputer.com:4321'; ,

Другое концептуальное препятствие заключается в понимании того, что один сервер MySQL может предоставлять доступ ко многим базам данных, поэтому вам необходимо выбрать свою базу данных в PHP после подключения к серверу.

Возвращаясь к приведенному выше коду, стоит отметить несколько вещей. Во-первых, я поместил в переменные значения, которые мне нужны для подключения к MySQL. Это просто делает нашу жизнь проще; Обычно этот вид информации хранится в отдельных файлах, которые включены в каждый скрипт PHP, что позволяет изменять несколько скриптов одновременно. Мы рассмотрим другие приемы, которые мы можем использовать, чтобы облегчить жизнь в данный момент.

Функция mysql_connect выполняет работу по подключению к серверу MySQL. Возвращаемое значение является либо идентификатором ссылки (значение, предоставляемое PHP для идентификации соединения), либо FALSE , что означает, что соединение не установлено.

 if (!$dbConn = mysql_connect($host, $dbUser, $dbPass)) {  die('Could not connect to server');  } 

Это утверждение if задает вопрос «Успешно ли я подключился к серверу MySQL?». Если нет, для завершения сценария используется метод die .

Затем мы выбрали mysql_select_db базу данных с помощью mysql_select_db , используя ту же технику if:

 if (!mysql_select_db($dbName, $dbConn)) { 

Обратите внимание, что мы предоставили переменную, содержащую идентификатор ссылки, в качестве второго аргумента mysql_select_db . Обычно этого делать не нужно (аргумент является необязательным), но когда сложный скрипт манипулирует несколькими соединениями с базой данных, этот метод может помочь гарантировать, что PHP знает, на что вы ссылаетесь.

Наконец, мы использовали mysql_close чтобы снова отключиться от сервера:

 mysql_close($dbConn); 

Это происходит в нижней части скрипта, когда мы запустили некоторый воображаемый код PHP, который использовал соединение. Закрытие соединения обычно необязательно — PHP автоматически закрывает все соединения после завершения скрипта.

Обратите внимание, что соединения, сделанные с помощью mysql_pconnect , отличаются. Эта функция устанавливает постоянное соединение с базой данных для повторного использования несколькими сценариями PHP. Использование постоянного соединения делает ваши скрипты немного быстрее, поскольку PHP больше не нужно каждый раз переподключаться, но скорость достигается ценой: если ваш сайт работает на общем сервере, постоянные соединения могут монополизировать этот сервер, в результате чего другие сайты не смогут время от времени В таких средах обычно либо избегают mysql_pconnect , либо настраивают MySQL так, чтобы соединения прерывались в тот момент, когда они перестали что-либо делать, используя короткое значение времени ожидания соединения.

Код многократного использования

Вы только что видели самый простой способ подключения к MySQL. Однако зачастую более полезно «упаковать» приведенный выше код в функцию или класс, чтобы его можно было использовать повторно.

В качестве функции мы могли бы иметь:

 Example 3.3. 2.php   <?php  function &connectToDb($host, $dbUser, $dbPass, $dbName)  {  // Make connection to MySQL server  if (!$dbConn = @mysql_connect($host, $dbUser, $dbPass)) {    return false;  }   // Select the database  if (!@mysql_select_db($dbName)) {    return false;  }   return $dbConn;  }   $host   = 'localhost'; // Hostname of MySQL server  $dbUser = 'harryf';    // Username for MySQL  $dbPass = 'secret';    // Password for user  $dbName = 'sitepoint'; // Database name   $dbConn = &connectToDb($host, $dbUser, $dbPass, $dbName);  ?> 

Это сокращает процесс подключения к MySQL и выбора базы данных в одну строку (две, если считать оператор включения, который будет указывать на отдельный файл, содержащий функцию connectToDb ):

 $dbConn = &connectToDb($host, $dbUser, $dbPass, $dbName); 

Обратите внимание, что мы использовали оператор ссылки & . Этот оператор и его роль были подробно описаны в главе 2 «Объектно-ориентированный PHP».

Будьте ленивы: напишите хороший код!

Теперь ученые убедительно доказали, что знание PHP обратно пропорционально свободному времени, но прямо пропорционально выпадению волос. Единственный способ предотвратить эти эффекты — научиться писать масштабируемый, поддерживаемый и повторно используемый код как можно раньше. Использование преимуществ классов и объектной ориентации в PHP — большой шаг в правильном направлении. Как разработчик PHP, лень это добродетель.

Идя дальше, мы можем обернуть этот код в класс:

 Example 3.4. Database/MySQL.php (in SPLIB) (excerpt)   /**  * MySQL Database Connection Class  * @access public  * @package SPLIB  */  class MySQL {  /**   * MySQL server hostname   * @access private   * @var string   */  var $host;   /**   * MySQL username   * @access private   * @var string   */  var $dbUser;   /**   * MySQL user's password   * @access private   * @var string   */  var $dbPass;   /**   * Name of database to use   * @access private   * @var string   */  var $dbName;   /**   * MySQL Resource link identifier stored here   * @access private   * @var string   */  var $dbConn;   /**   * Stores error messages for connection errors   * @access private   * @var string   */  var $connectError;   /**   * MySQL constructor   * @param string host (MySQL server hostname)   * @param string dbUser (MySQL User Name)   * @param string dbPass (MySQL User Password)   * @param string dbName (Database to select)   * @access public   */  function MySQL($host, $dbUser, $dbPass, $dbName)  {    $this->host = $host;    $this->dbUser = $dbUser;    $this->dbPass = $dbPass;    $this->dbName = $dbName;    $this->connectToDb();  }   /**   * Establishes connection to MySQL and selects a database   * @return void   * @access private   */  function connectToDb()  {    // Make connection to MySQL server    if (!$this->dbConn = @mysql_connect($this->host,        $this->dbUser, $this->dbPass)) {      trigger_error('Could not connect to server');      $this->connectError = true;    // Select database    } else if (!@mysql_select_db($this->dbName,$this->dbConn)) {      trigger_error('Could not select database');      $this->connectError = true;    }  }   /**   * Checks for MySQL errors   * @return boolean   * @access public   */  function isError()  {    if ($this->connectError) {      return true;    }    $error = mysql_error($this->dbConn);    if (empty($error)) {      return false;    } else {      return true;    }  } 

Теперь это может показаться довольно обременительным, но самое главное не то, как кодируется сам класс (функция trigger_error будет обсуждаться в разделе «Как устранить ошибки в моих SQL-запросах?» Далее в этой главе), а как это сделать? вы используете его.

Самое главное, что теперь задача подключения к MySQL сводится к следующему:

 Example 3.5. 3.php   <?php  // Include the MySQL class  require_once 'Database/MySQL.php';   $host   = 'localhost'; // Hostname of MySQL server  $dbUser = 'harryf';    // Username for MySQL  $dbPass = 'secret';    // Password for user  $dbName = 'sitepoint'; // Database name   // Connect to MySQL  $db = &new MySQL($host, $dbUser, $dbPass, $dbName);  ?> 

Смысл использования класса здесь состоит в том, чтобы научиться использовать объектную модель PHP для решения типичных задач. Если вы новичок в объектно-ориентированном программировании на PHP, на этом этапе следует помнить, что вам не нужно понимать весь код, который вы найдете в классе, чтобы иметь возможность использовать его в своем коде.

Мы будем использовать этот и другие классы в этой книге, чтобы проиллюстрировать, как объектно-ориентированное программирование помогает повторно использовать код и может сэкономить время при разработке приложений.

Как мне получить данные из таблицы?

Быть подключенным к базе данных приятно, конечно. Но что хорошего в том, что мы ничего не можем получить от этого?

Есть несколько способов получить данные из MySQL, но наиболее широко используемый, вероятно, mysql_fetch_array в сочетании с mysql_query .

Нам просто нужно добавить немного больше к функции connectToDb мы видели в разделе «Как получить доступ к базе данных MySQL?», Чтобы получить данные из этой таблицы:

 Example 3.6. 4.php   // Connect to MySQL  $dbConn = &connectToDb($host, $dbUser, $dbPass, $dbName);   // A query to select all articles  $sql = "SELECT * FROM articles ORDER BY title";   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   // Fetch rows from MySQL one at a time  while ($row = mysql_fetch_array($queryResource, MYSQL_ASSOC)) {  echo 'Title: '  . $row['title']  . '<br />';  echo 'Author: ' . $row['author'] . '<br />';  echo 'Body: '   . $row['body']   . '<br />';  } 

По сути, есть три шага к получению ваших данных:

  1. Во-первых, поместите необходимый SQL-запрос в строку (если вы не знакомы с языком структурированных запросов (SQL), я расскажу об основах этой главы). Однако, для более полной обработки см. Раздел Создание собственного веб-сайта, управляемого базой данных, используя PHP & MySQL, 2-е издание (ISBN 0–9579218–1–0)) примерно так:
     $sql = "SELECT * FROM articles ORDER BY title"; 

    Удобно хранить его в отдельной переменной, так как когда мы начинаем писать более сложные запросы и что-то идет не так, мы можем перепроверить наш запрос с помощью одной строки:

     echo $sql; 
  2. Далее скажите MySQL выполнить запрос:
     $queryResource = mysql_query($sql, $dbConn); 

    Поначалу это может сбить с толку. Когда вы указываете MySQL выполнить запрос, он не сразу возвращает вам результаты. Вместо этого он хранит результаты в памяти, пока вы не скажете, что делать дальше. PHP отслеживает результаты с помощью идентификатора ресурса, который вы получаете от функции mysql_query . В приведенном выше коде мы сохранили идентификатор в $queryResource .

  3. Наконец, используйте mysql_fetch_array чтобы mysql_fetch_array одну строку за раз из набора результатов:
     while ($row = mysql_fetch_array($queryResource, MYSQL_ASSOC)) 

    Это помещает каждую строку результатов по очереди в переменную $row . Каждая из этих строк будет представлена ​​массивом. Используя дополнительный аргумент MYSQL_ASSOC с mysql_fetch_array , мы сказали функции дать нам массив, в котором ключи соответствуют именам столбцов в таблице. Если вы опустите аргумент MYSQL_ASSOC , каждый столбец появится в массиве дважды: один раз с числовым индексом (т. MYSQL_ASSOC $row[0] , $row[1] и т. Д.) И один раз со строковым индексом (т. Е. $row['title'] , $row['author'] и т. д.). Хотя обычно это не вызывает проблем, указание типа требуемого значения массива немного ускорит процесс.

Использование цикла while, как показано выше, является распространенным способом обработки каждой строки набора результатов по очереди. Цикл фактически говорит: «Продолжайте извлекать строки из MySQL до тех пор, пока я больше не смогу их получить», а тело цикла обрабатывает строки по мере их поступления.

Форего буферизация при больших запросах

Для больших запросов (то есть запросов, которые производят большие наборы результатов), вы можете значительно повысить производительность, сказав PHP не буферизировать результаты запроса. Когда запрос буферизуется, весь набор результатов извлекается из MySQL и сохраняется в памяти, прежде чем ваш сценарий может быть продолжен. С другой стороны, небуферизованный запрос позволяет MySQL сохранять результаты до тех пор, пока вы не запросите их, по одной строке за раз (например, с помощью mysql_fetch_array ). Это не только позволяет вашему скрипту продолжать работу, пока MySQL выполняет запрос, но также избавляет PHP от необходимости хранить все строки в памяти одновременно.

PHP позволяет выполнять небуферизованные запросы с помощью mysql_unbuffered_query :

 $queryResource = mysql_unbuffered_query ($sql, $dbConn); 

Конечно, все хорошие вещи имеют цену — с небуферизованными запросами вы больше не можете использовать функцию mysql_num_rows для подсчета количества строк. Очевидно, что PHP не хранит копию полного набора результатов, поэтому он не может сосчитать содержащиеся в нем строки! Вы также должны извлечь все строки в наборе результатов из MySQL, прежде чем сможете сделать еще один запрос.

Несмотря на то, что существуют другие функции для получения строк и ячеек из результатов запроса, такие как mysql_fetch_object и mysql_result , вы можете достичь более или менее одного и того же только с помощью mysql_fetch_array , а согласованность может помочь сделать ваш код простым.

Выборка с классами

Теперь, когда вы довольны основами извлечения данных из MySQL, пришло время добавить еще немного к классу MySQL из последнего решения.

Сначала давайте добавим метод для запуска запросов из класса:

 Example 3.7. Database/MySQL.php (in SPLIB) (excerpt)   /**   * Returns an instance of MySQLResult to fetch rows with   * @param $sql string the database query to run   * @return MySQLResult   * @access public   */  function &query($sql)  {    if (!$queryResource = mysql_query($sql, $this->dbConn)) {      trigger_error('Query failed: ' . mysql_error($this->dbConn)                    . ' SQL: ' . $sql);      return new MySQLResult($this, $queryResource);    }  } 

Этот новый метод принимает переменную, содержащую инструкцию SQL, запускает ее, а затем создает новый объект из другого класса, MySQLResult (описано ниже). Затем он возвращает этот объект в точку, где был вызван запрос.

Вот код для этого нового класса, MySQLResult :

 Example 3.8. Database/MySQL.php (in SPLIB) (excerpt)   /**  * MySQLResult Data Fetching Class  * @access public  * @package SPLIB  */  class MySQLResult {  /**   * Instance of MySQL providing database connection   * @access private   * @var MySQL   */  var $mysql;   /**   * Query resource   * @access private   * @var resource   */  var $query;   /**   * MySQLResult constructor   * @param object mysql   (instance of MySQL class)   * @param resource query (MySQL query resource)   * @access public   */  function MySQLResult(&$mysql, $query)  {    $this->mysql = &$mysql;    $this->query = $query;  }   /**   * Fetches a row from the result   * @return array   * @access public   */  function fetch()  {    if ($row = mysql_fetch_array($this->query, MYSQL_ASSOC)) {      return $row;    } else if ( $this->size() > 0 ) {      mysql_data_seek($this->query, 0);      return false;    } else {      return false;    }  }   /**   * Checks for MySQL errors   * @return boolean   * @access public   */  function isError()  {    return $this->mysql->isError();  }  } 

Теперь задержите дыхание немного дольше, пока не увидите, на что похоже использование этих классов:

 Example 3.9. 5.php   <?php  // Include the MySQL class  require_once 'Database/MySQL.php';   $host   = 'localhost'; // Hostname of MySQL server  $dbUser = 'harryf';    // Username for MySQL  $dbPass = 'secret';    // Password for user  $dbName = 'sitepoint'; // Database name   // Connect to MySQL  $db = &new MySQL($host, $dbUser, $dbPass, $dbName);   $sql = "SELECT * FROM articles ORDER BY title";   // Perform a query getting back a MySQLResult object  $result = $db->query($sql);   // Iterate through the results  while ($row = $result->fetch()) {  echo 'Title: '  . $row['title']  . '<br />';  echo 'Author: ' . $row['author'] . '<br />';  echo 'Body: '   . $row['body']   . '<br />';  }  ?> 

Если вы не привыкли к объектно-ориентированному программированию, это может показаться очень запутанным, но самое главное — сконцентрироваться на том, как вы можете использовать классы, а не на деталях, скрытых внутри них. Это одна из радостей объектно-ориентированного программирования, как только вы к этому привыкнете. Код может быть очень сложным за кулисами, но все, что вам нужно беспокоиться, это простой «интерфейс» (API), с которым ваш код использует класс.

Об API

Общепринято слышать термин API, упомянутый вокруг классов. API означает интерфейс прикладного программирования. Это относится к набору методов, которые действуют как «двери» для функциональности, содержащейся в классе. Хорошо разработанный API позволит разработчику класса вносить радикальные изменения за кулисы, не нарушая код, использующий этот класс.

Сравните, используя классы MySQL с более ранним процедурным кодом; должно быть легко увидеть сходство. Учитывая, что это так похоже, вы можете спросить: «Почему бы не придерживаться простого процедурного PHP?». В данном случае он скрывает многие детали, связанные с выполнением запроса. Такие задачи, как управление соединением, перехват ошибок и решение, в каком формате получать результаты запроса, все обрабатываются классом за кулисами. Классы также делают реализацию глобальных модификаций (таких как переключение с MySQL на PostgreSQL) относительно безболезненной (то есть вы можете просто переключиться на класс PostgreSQL который предоставляет тот же API).

Как устранить ошибки в моих SQL-запросах?

Если что-то идет не так, когда вы пытаетесь работать с PHP и SQL вместе, часто трудно найти причину. Хитрость заключается в том, чтобы заставить PHP сообщать вам, в чем проблема, имея в виду, что вы должны иметь возможность скрывать эту информацию от посетителей, когда сайт начинает работать.

PHP предоставляет функцию mysql_error , которая возвращает подробное сообщение об ошибке после последней выполненной операции MySQL.

Лучше всего его использовать в сочетании с функцией trigger_error (которая будет более подробно рассмотрена в главе 10, Обработка ошибок), которая позволяет вам контролировать вывод сообщения об ошибке. Давайте изменим базовый код подключения, который мы видели ранее:

 Example 3.10. 6.php (excerpt)   // Make connection to MySQL server  if (!$dbConn = mysql_connect($host, $dbUser, $dbPass)) {  trigger_error('Could not connect to server: ' . mysql_error());  die();  }   // Select the database  if (!mysql_select_db($dbName)) {  trigger_error('Could not select database: ' . mysql_error());  die();  } 

Тот же подход можно использовать с запросами:

 Example 3.11. 6.php (excerpt)   // A query to select all articles  $sql = "SELECT * FROM articles ORDER BY title";   // Run the query, identifying the connection  if (!$queryResource = mysql_query($sql, $dbConn)) {  trigger_error('Query error ' . mysql_error() . ' SQL: ' . $sql);  } 

Хорошей идеей будет вернуть сам полный запрос, как мы это делали в приведенном выше примере, особенно когда вы построили его с помощью переменных PHP. Это позволяет вам точно увидеть, какой запрос был выполнен, и, если необходимо, выполнить его непосредственно в MySQL, чтобы точно определить, где он произошел.

Класс MySQL, обсужденный выше, будет автоматически использовать mysql_error и trigger_error если он столкнется с проблемой.

Как добавить или изменить данные в моей базе данных?

Возможность получать данные из базы данных — это только начало, но как вы можете поместить их туда в первую очередь?

Опять же, ответ прост с PHP: используйте функцию mysql_query сочетании с командами SQL INSERT и UPDATE. INSERT используется для создания новых строк в таблице, а UPDATE используется для изменения существующих строк.

Вставка строки

Простой INSERT с использованием таблицы статей, определенной в начале этой главы, выглядит следующим образом:

 Example 3.12. 7.php (excerpt)   // A query to INSERT data  $sql = "INSERT INTO          articles        SET          title  = '$title',          body   = '$body',          author = '$author'";   // Run the query, identifying the connection  if (!$queryResource = mysql_query($sql, $dbConn)) {  trigger_error('Query error ' . mysql_error() . ' SQL: ' . $sql);  } 

Обновление строки

Прежде чем вы сможете использовать запрос UPDATE, вам необходимо определить, какие строки таблицы необходимо обновить. В этом примере я использовал запрос SELECT, чтобы получить уникальное значение article_id для статьи, озаглавленной «Как вставить данные»:

 Example 3.13. 8.php (excerpt)   // A query to select an article  $sql = "SELECT article_id FROM articles        WHERE title='How to insert data'";   if (!$queryResource = mysql_query($sql, $dbConn)) {  trigger_error('Query error ' . mysql_error() . ' SQL: ' . $sql);  }   // Fetch a single row from the result  $row = mysql_fetch_array($queryResource, MYSQL_ASSOC);   // A new title  $title = 'How to update data';   $sql = "UPDATE          articles        SET          title='$title'        WHERE          article_id='" . $row['article_id'] . "'";   if (!$queryResource = mysql_query($sql, $dbConn)) {  trigger_error('Query error ' . mysql_error() . ' SQL: ' . $sql);  } 

В приведенном выше примере мы использовали запрос SELECT, чтобы найти идентификатор для строки, которую мы хотели обновить.

В практических веб-приложениях ОБНОВЛЕНИЕ может происходить на странице, которая основана на вводе данных через веб-браузер, после того, как пользователь ввел значение (я), например, с помощью HTML-формы. Возможно, что строки в этих данных могут содержать апострофы, которые могут нарушить SQL и повлиять на безопасность. В свете этого убедитесь, что вы прочитали раздел под названием «Как решить ошибки базы данных, вызванные кавычками / апострофами?», В которых рассматриваются атаки с использованием SQL-инъекций.

Остерегайтесь глобальных обновлений
Будьте осторожны с UPDATE и не забудьте использовать предложение WHERE, чтобы указать, какие строки нужно изменить.

Например, рассмотрим этот запрос:

 UPDATE articles SET title = 'How NOT to update data' 

Это обновит каждую строку таблицы!

Еще один класс действий

Используя класс MySQL, который в последний раз видели в разделе «Как получить данные из таблицы?», Мы можем выполнять запросы INSERT и UPDATE без каких-либо дополнительных изменений. Повторяя приведенные выше примеры, используя класс, мы можем сначала вставить, как это:

 Example 3.14. 9.php (excerpt)   // Connect to MySQL  $db = &new MySQL($host, $dbUser, $dbPass, $dbName);   $title  = 'How to insert data';  $body   = 'This is the body of the article';  $author = 'HarryF';   // A query to INSERT data  $sql = "INSERT INTO          articles        SET          title  = '$title',          body   = '$body',          author = '$author'";   $db->query($sql);   if (!$db->isError()) {  echo 'INSERT successful';  } else {  echo 'INSERT failed';  } 

Мы можем ОБНОВИТЬ следующим образом:

 Example 3.15. 10.php (excerpt)   $db = &new MySQL($host, $dbUser, $dbPass, $dbName);   // A query to select an article  $sql = "SELECT article_id FROM articles        WHERE title='How to insert data'";   $result = $db->query($sql);   $row = $result->fetch();   // A new title  $title = 'How to update data';   $sql = "UPDATE          articles        SET          title='" . $title. "'        WHERE          article_id='" . $row['article_id'] . "'";   $db->query($sql);   if (!$db->isError()) {  echo 'UPDATE successful';  } else {  echo 'UPDATE failed';  } 

Как устранить ошибки базы данных, вызванные кавычками / апострофами?

Рассмотрим следующий оператор SQL:

 INSERT INTO articles SET title='The PHP Anthology'; 

Возможно, скрипт PHP, который сделал этот запрос, содержал что-то вроде этого:

 <?php  $title = "The PHP Anthology";   $sql = "INSERT INTO articles SET title='$title';";   $result = mysql_query($sql, $dbConn);  ?> 

Пока проблем нет, но посмотрите, что произойдет, если мы изменим название:

 $title = "PHP's Greatest Hits"; 

Заметили апостроф в названии? Когда мы помещаем это в оператор SQL, запрос MySQL будет выглядеть следующим образом:

 INSERT INTO articles SET title='PHP's Greatest Hits'; 

Видишь проблему? Когда MySQL прочитает это утверждение, оно получит только следующее:

 INSERT INTO articles SET title='PHP' 

Остальная часть оператора вызовет синтаксическую ошибку и запрос не будет выполнен. Достаточно легко избежать этой проблемы, когда вы пишете заголовок самостоятельно, но что происходит, когда ваш скрипт получает значение из пользовательского ввода?

Большой побег

Решение состоит в том, чтобы избежать символа апострофа, добавив обратную косую черту перед апострофом. Например, будет работать следующий запрос:

 INSERT INTO articles SET title='PHP's Greatest Hits'; 

Обратная косая черта и стандарт ANSI SQL

Обратите внимание, что использование обратной косой черты в качестве escape-символа не является стандартным ANSI SQL. Если MySQL является единственной базой данных, которую вы когда-либо используете, обратный слеш может быть приемлемым, но тот же оператор SQL, выполняемый в другой базе данных, вполне может дать сбой. В соответствии с ANSI SQL мы должны избегать апострофов с помощью еще одного апострофа:

 INSERT INTO articles SET title='PHP''s Greatest Hits'; 

Вопрос в том, как мы можем убедиться, что все наши апострофы экранированы, когда мы строим запрос на лету в PHP? Работа с этой ситуацией стала довольно запутанной из-за количества альтернативных решений.

Сначала у нас есть настройка php.ini magic_quotes_gpc . Магические кавычки — это функция PHP, которая при включении автоматически экранирует одинарные и двойные кавычки, а также обратные слэши и нулевые символы, обнаруженные во входящих переменных GET, POST и cookie, путем добавления обратных косых черт к строкам. Это может звучать замечательно, но на практике это быстро создает проблемы, как правило, когда речь идет о формах.

Скажем, у вас есть форма, которая используется для редактирования статей. Ваш скрипт берет текст, который вводит пользователь, и вставляет его в MySQL. Теперь, если пользователь не может заполнить какое-то важное поле, вы можете повторно отобразить детали, которые были введены в форму до сих пор. С волшебными кавычками вы должны были бы убрать все косые черты, которые он добавил к значениям (с помощью функции полоски в PHP)!

Тогда, что если вы хотите запустить код на сервере, где magic_quotes_gpc отключен? Затем ваш код должен проверить, включены ли магические кавычки, и обойти использование полосок. Головные боли неизбежны, и если вы совершите ошибку и в результате получите ложные обратные слеши, хранящиеся в вашей базе данных, у вас может возникнуть болезненный процесс очистки. Меня постоянно удивляет, как много профессионально разработанных сайтов не справляются с побегами персонажей должным образом! Следите за неожиданными обратными слешами в ваших собственных путешествиях через Интернет. См. Главу 1 «Основы PHP», где вы найдете мой совет о том, как этого избежать на своих сайтах.

Действительно, магические кавычки обсуждаются более подробно в главе 1 «Основы PHP». Если вы отключите magic_quotes_gpc как я советую, вы должны знать о потенциальных рисках для безопасности. См. Раздел «Атаки с использованием SQL» ниже и Приложение C, Контрольный список безопасности.

Далее у нас есть PHP-функция addslashes . Применяемая к любой строке, addslashes будет использовать обратную косую черту для экранирования одинарных кавычек, двойных кавычек, обратной косой черты и нулевых символов. Это делает его эффективным средством экранирования строк для использования в запросах.

Конечно, если включены магические кавычки, вы не должны использовать addslashes , иначе символы будут дважды экранированы! Чтобы разрешить этот конфликт, вы можете проверить, включены ли магические кавычки с помощью функции get_magic_quotes_gpc , которая возвращает TRUE, если магические кавычки включены, и FALSE, если это не так. Вы можете связать этот тест с функцией следующим образом:

 <?php  function safeAddSlashes($string)  {  if (get_magic_quotes_gpc()) {    return $string;  } else {    return addslashes($string);  }  }  ?> 

Третий способ, который очень похож на addslashes , использует функцию mysql_escape_string или mysql_real_escape_string (последний был добавлен в PHP 4.3.0). Эти функции используют базовый API MySQL C ++ (предоставляемый разработчиками MySQL, а не разработчиками PHP) для экранирования специальных символов.

Эти функции экранируют нулевые символы, переводы строки, возврат каретки, обратную косую черту, одинарные кавычки, двойные кавычки и символы конца файла. Начиная с PHP 4.3.0, обе функции использовали текущий набор символов для соединения между PHP и MySQL. Таким образом, в последних версиях PHP нет никакой разницы между этими двумя функциями, поэтому не стесняйтесь придерживаться более короткого из них, mysql_escape_string , если ваш сервер обновлен.

Поскольку этот метод, по сути, является встроенным механизмом, предоставляемым MySQL для экранирования строк, я рекомендую его для addslashes или magic_quotes_gpc . Конечно, если вы хотите, чтобы ваш SQL хорошо переносился на другие базы данных, вы можете подумать о том, чтобы «скрыть» вызов функции внутри метода класса, что позволит вам поменять класс — включая механизм escape — при переходе на другой база данных.

Опять же, если вы иначе не решите проблему магических кавычек, вам нужно проверить, magic_quotes_gpc ли magic_quotes_gpc :

 <?php  function safeEscapeString($string)  {  if (get_magic_quotes_gpc()) {    return $string;  } else {    return mysql_real_escape_string($string);  }  }  ?> 

Сценарии в этой книге используют файл MagicQuotes / strip_quotes.php, представленный в главе 1 «Основы PHP» и включенный в архив кода, чтобы эффективно отключать магические кавычки на серверах, где он включен, поэтому в последующих решениях будет использоваться mysql_real_escape_string свободно. Я бы посоветовал вам сделать то же самое в ваших собственных сценариях, если вы чувствуете себя уверенно, избегая кавычек и других специальных символов самостоятельно.

Атаки SQL-инъекций

Атака SQL-инъекции происходит, когда злоумышленник использует законный механизм пользовательского ввода на вашем сайте для отправки кода SQL, который ваш ничего не подозревающий скрипт передаст в базу данных для выполнения. Золотое правило: экранируйте все данные из внешних источников, прежде чем размещать их рядом с базой данных. Это правило применяется не только к запросам INSERT и UPDATE, но и к запросам SELECT.

Несомненно, многие разработчики PHP были защищены от худших атак с использованием SQL-инъекций благодаря ограничениям MySQL, которые позволят выполнять только один оператор SQL при каждом вызове mysql_query . В других базах данных эффект внедрения SQL может иметь катастрофические последствия, поскольку злоумышленник может отправить второй запрос, который, например, удаляет все содержимое таблицы. Однако с MySQL проблемы все еще могут возникать, как показывает следующий код:

 $sql = "SELECT * FROM users        WHERE username='" . $_POST['username'] . "'        AND password='" . $_POST['password'] . "'";   echo 'Query: ' . $sql . '<br />';   $result = mysql_query($sql);   $rows = mysql_num_rows($result);   if ($rows > 0) {  echo 'You are logged in!<br />';  } else {  echo 'You are not allowed here!<br />';  }  ?>  <form method="post" action="<?php echo $_SERVER['PHP_SELF']; ?>">  <input type="text" name="username" /><br />  <input type="text" name="password" /><br />  <input type="submit" />  </form> 

Опытный злоумышленник может просто ввести следующее в поле пароля формы:

 ' OR username LIKE '% 

Предполагая, что на вашем сервере отключены магические кавычки, и у вас нет других мер для его предотвращения, эта хитрая атака меняет смысл запроса:

 SELECT * FROM users  WHERE username='' AND password='' OR username LIKE '%' 

Измененный запрос выберет все записи в пользовательской таблице! Когда скрипт проверяет, соответствуют ли какие-либо пользователи предоставленной комбинации имени пользователя и пароля, он увидит этот большой набор результатов и предоставит доступ к сайту!

Это может быть предотвращено, если мы избежим входящих переменных:

 $sql = "SELECT * FROM users    WHERE username='" . safeEscapeString($_POST['username']) . "'    AND password='" . safeEscapeString($_POST['password']) . "'"; 

В некоторых случаях, в зависимости от обстоятельств, это может не потребоваться. Но если вы цените свой сон, помните это золотое правило: избегайте всех данных из внешних источников.

Как мне создать гибкие операторы SQL?

SQL является мощным языком для манипулирования данными. Используя PHP, мы можем создавать операторы SQL из переменных, что может быть полезно для сортировки таблицы по одному столбцу или отображения большого набора результатов на нескольких страницах.

Вот простой пример, который позволяет нам сортировать результаты запроса по столбцу таблицы:

 Example 3.16. 11.php (excerpt)   // A query to select all articles  $sql = "SELECT * FROM articles";   // Initialize $_GET['order'] if it doesn't exist  if (!isset($_GET['order']))  $_GET['order'] = FALSE;   // Use a conditional switch to determine the order  switch ($_GET['order']) {  case 'author':    // Add to the $sql string    $sql .= " ORDER BY author";    break;  default:    // Default sort by title    $sql .= " ORDER BY title";    break;  }   // Run the query, identifying the connection  if (!$queryResource = mysql_query($sql, $dbConn)) {  trigger_error('Query error ' . mysql_error() . ' SQL: ' . $sql);  }  ?>  <table>  <tr>  <th><a href="<?php echo $_SERVER['PHP_SELF']; ?>?order=title"  >Title</a></th>  <th><a href="<?php echo $_SERVER['PHP_SELF']; ?>?order=author"  >Author</a></th>  </tr>  <?php  while ($row = mysql_fetch_array($queryResource, MYSQL_ASSOC)) {  echo "<tr>n";  echo "<td>" . $row['title'] . "</td>";  echo "<td>" . $row['author'] . "</td>";  echo "</tr>n";  }  ?>  </table> 

В операторе switch я сгенерировал часть оператора SQL «на лету», в зависимости от переменной GET, которую скрипт получает из браузера.

Этот общий подход можно распространить на предложения WHERE, предложения LIMIT и все остальное, что вы хотите рассмотреть в SQL. Мы рассмотрим это более подробно при создании постраничного набора результатов в главе 9 «Элементы веб-страницы».

Уровни постоянства: взаимодействие с базой данных без SQL

Слои постоянства становятся популярными и хорошо поддерживаются в PHP сегодня. Уровень персистентности — это набор классов, представляющих таблицы в вашей базе данных, предоставляющий вам API, через который проходят все данные между базой данных и приложением PHP. Как правило, это избавляет вас от необходимости писать операторы SQL вручную, поскольку запросы генерируются и выполняются автоматически классами PHP, представляющими данные.

Поскольку SQL является довольно четко определенным стандартом, также становится возможным автоматически генерировать постоянный слой. Программа может проверить схему вашей базы данных и создать классы, которые будут автоматически читать и обновлять ее. Это может значительно сэкономить время; просто спроектируйте свою базу данных, запустите инструмент генерации кода, а все остальное — просто вопрос небольшого (X) HTML.

Основным примером персистентного уровня является PEAR :: DB_DataObject, который создается поверх библиотеки абстракций базы данных PEAR :: DB и автоматически генерирует слой классов, с помощью которого осуществляется доступ к вашим таблицам.

Слои персистентности в целом и PEAR :: DB_DataObject в частности обсуждаются в разделе «Мне действительно нужно писать SQL?».

Как узнать, сколько строк я выбрал?

Часто полезно иметь возможность подсчитывать количество строк, возвращаемых запросом, прежде чем что-либо делать с ними, например, когда вы разбиваете результаты по страницам или производите статистическую информацию. При выборе результатов вы можете использовать PHP или MySQL для подсчета количества строк для вас.

Подсчет строк с помощью PHP

В PHP функция mysql_num_rows возвращает количество выбранных строк, но ее применение может быть ограничено при использовании небуферизованных запросов (см. Раздел «Как получить данные из таблицы?»). Следующий код иллюстрирует использование mysql_num_rows :

 Example 3.17. 12.php (excerpt)   // A query to select all articles  $sql = "SELECT * FROM articles ORDER BY title";   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   // Fetch the number of rows selected  $numRows = mysql_num_rows($queryResource);   echo $numRows . ' rows selected<br />';   // Fetch rows from MySQL one at a time  while ($row = mysql_fetch_array($queryResource, MYSQL_ASSOC)) {    echo 'Title: '  . $row['title']  . '<br />';    echo 'Author: ' . $row['author'] . '<br />';    echo 'Body: '   . $row['body']   . '<br />';  } 

Функция mysql_num_rows , продемонстрированная в вышеприведенном примере, берет идентификатор ресурса набора результатов и возвращает количество строк в этом наборе результатов.

Обратите внимание, что связанная функция mysql_num_fields может использоваться для определения количества выбранных столбцов. Это может быть удобно, когда вы используете запросы типа SELECT * FROM table, но вы не знаете, сколько столбцов вы выбрали.

Подсчет строк с MySQL

Альтернативный подход заключается в использовании функции MySQL COUNT в запросе. Для этого необходимо выполнить два запроса — один для подсчета результатов и один для фактического получения результатов — что будет стоить вам немного с точки зрения производительности.

Вот как вы можете использовать функцию MySQL COUNT :

 Example 3.18. 13.php (excerpt)   // A query to select all articles  $sql = "SELECT COUNT(*) AS numrows FROM articles";   // Query to count the rows returned  $queryResource = mysql_query($sql, $dbConn);   $row = mysql_fetch_array($queryResource, MYSQL_ASSOC);   echo $row['numrows'] . " rows selected<br />";   // A query to select all articles  $sql = "SELECT * FROM articles ORDER BY title";   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   // Fetch rows from MySQL one at a time  while ($row = mysql_fetch_array($queryResource, MYSQL_ASSOC)) {  echo 'Title: '  . $row['title']  . '<br />';  echo 'Author: ' . $row['author'] . '<br />';  echo 'Body: '   . $row['body']   . '<br />';  } 

Заметьте, мы использовали псевдоним, чтобы поместить результат функции COUNT ?

 SELECT COUNT(*) AS numrows FROM articles 

Мы делаем это так, чтобы число строк можно было определить позже, используя $row['numrows'] . Альтернативой было бы опустить псевдоним:

 SELECT COUNT(*) FROM articles 

Для этого потребуется доступ к информации в виде $row['COUNT(*)']/#epc#/, which can make the code confusing to read.

Когда мы используем функцию COUNT , становится важно создавать запросы на лету, как мы видели в разделе «Как создать гибкие операторы SQL?». Вы должны убедиться, что ваш запрос COUNT содержит те же WHERE или LIMIT вы использовали в «реальном» запросе. Например, если запрос, который мы на самом деле используем для получения данных:

 SELECT * FROM articles WHERE author='HarryF' 

В PHP мы, вероятно, захотим что-то вроде этого:

 Example 3.19. 14.php (excerpt)   // Define reusable "chunks" of SQL  $table = " FROM articles";  $where = " WHERE author='HarryF'";  $order = " ORDER BY title";   // Query to count the rows returned  $sql = "SELECT COUNT(*) as numrows" . $table . $where;   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   $row = mysql_fetch_array($queryResource, MYSQL_ASSOC);   echo $row['numrows'] . " rows selected<br />";   // A query to fetch the rows  $sql = "SELECT * " . $table . $where . $order;   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   // Fetch rows from MySQL one at a time  while ($row = mysql_fetch_array($queryResource, MYSQL_ASSOC)) {    echo 'Title: '  . $row['title']  . '<br />';    echo 'Author: ' . $row['author'] . '<br />';    echo 'Body: '   . $row['body']   . '<br />';  } 

Подсчет строк с помощью классов

Давайте снова посмотрим на классы, которые мы разрабатывали в этом разделе. Мы можем добавить возможность узнать количество выбранных строк, введя следующий метод в класс MySQLResult:

 Example 3.20. Database/MySQL.php (in SPLIB) (excerpt)   /**   * Returns the number of rows selected   * @return int   * @access public   */  function size()  {    return mysql_num_rows($this->query);  } 

Вот как это использовать:

 Example 3.21. 15.php (excerpt)   // Connect to MySQL  $db = &new MySQL($host, $dbUser, $dbPass, $dbName);   // Select all results for a particular author  $sql = "SELECT * FROM articles WHERE author='HarryF'";   $result = $db->query($sql);   echo "Found " . $result->size() . " rows"; 

Подсчет затронутых рядов

Также можно узнать, сколько строк было затронуто запросом UPDATE , INSERT или DELETE , используя функцию PHP mysql_affected_rows . Использование mysql_affected_rows не распространено в типичных приложениях PHP, но это может быть хорошим способом информирования пользователей о том, что «Вы только что удалили 1854 записи из таблицы Customers. Хорошего дня!»

В отличие от mysql_num_rows , который принимает идентификатор ресурса набора результатов в качестве аргумента, mysql_affected_rows принимает идентификатор соединения с базой данных. Он возвращает количество строк, затронутых последним запросом, который изменил базу данных, для указанного соединения.

Вот как можно использовать mysql_affected_rows :

 Example 3.22. 16.php (excerpt)   // Connect to MySQL  $dbConn = &connectToDb($host, $dbUser, $dbPass, $dbName);   // A query which updates the database  $sql = "UPDATE          articles        SET          author='The Artist Formerly Known as...'        WHERE          author='HarryF'";   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   // Fetch the number of rows affected  $changedRows = mysql_affected_rows($dbConn);   echo $changedRows . ' rows changed<br />'; 

Поскольку ситуации, в которых mysql_affected_rows встречаются редко, я опущу это в классе MySQLResult простоты.

После вставки строки, как узнать номер строки?

Когда вы имеете дело со столбцами AUTO_INCREMENT в таблицах базы данных, часто бывает полезно узнать идентификатор только что вставленной строки, чтобы другие таблицы можно было обновлять с помощью этой информации. В конце концов, именно так строятся отношения между таблицами. PHP предоставляет функцию mysql_insert_id , которая, при наличии идентификатора ссылки, возвращает идентификатор, сгенерированный последним INSERT, выполненным с этим соединением. Вот как можно использовать mysql_insert_id :

 Example 3.23. 17.php (excerpt)   // A query to insert a row  $sql = "INSERT INTO          articles        SET          title='How to use mysql_insert_id()',          body='This is an example',          author='HarryF'";   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   // Fetch the inserted ID  $insertID = mysql_insert_id($dbConn);   echo 'The new row has ID: ' . $insertID; 

Идентификатор класса вставки

Чтобы использовать эту функцию в нашем классе MySQLResult , добавьте следующий метод:

 Example 3.24. Database/MySQL.php (in SPLIB) (excerpt)   /**   * Returns the ID of the last row inserted   * @return int   * @access public   */  function insertID()  {    return mysql_insert_id($this->mysql->dbConn);  } 

Как вы можете догадаться, использование этого метода довольно просто:

 Example 3.25. 18.php (excerpt)   // Include the MySQL class  require_once 'Database/MySQL.php';   $host   = 'localhost';   // Hostname of MySQL server  $dbUser = 'harryf';    // Username for MySQL  $dbPass = 'secret';    // Password for user  $dbName = 'sitepoint'; // Database name   $db = &new MySQL($host, $dbUser, $dbPass, $dbName);   // A query to insert a row  $sql="INSERT INTO        articles      SET        title='How to use mysql_insert_id()',        body='This is an example',        author='HarryF'";   $result = $db->query($sql);   echo 'The new row as ID: ' . $result->insertID(); 

Как мне искать в моей таблице?

Некоторые люди просто нетерпеливы; вместо того, чтобы бродить по вашему сайту с помощью удобной навигационной системы, которую вы предоставили, они теперь требуют информацию! Следовательно, разработчики PHP, такие как вы и я, должны реализовать функции поиска, чтобы предоставить посетителям «быстрый доступ» для поиска нужной информации. В дни хранения всего контента в виде HTML-файлов это могло быть довольно проблематично, но теперь, когда вы используете базу данных для хранения контента, поиск становится намного проще.

Выберите то, что вам нравится

Самая простая форма поиска происходит по одному столбцу с оператором LIKE:

 SELECT * FROM articles WHERE title LIKE 'How %' 

% Является символом подстановки. Приведенное выше утверждение выберет все статьи, в которых заголовок начинается со слова «Как». MySQL также поддерживает регулярные выражения POSIX (так же, как и функции ereg в PHP). Используя оператор RLIKE , мы можем сравнить столбец с помощью регулярного выражения:

 SELECT * FROM articles WHERE title RLIKE '^How ' 

Вышеприведенное утверждение также выбирает каждую статью, в которой заголовок начинается с «Как», за которым следует пробел.

После некоторой работы эти операторы предоставляют все необходимое для изучения ваших данных. Когда вышеуказанный подход становится бременем, заключается в выполнении поиска по нескольким столбцам. Например,

 SELECT * FROM articles  WHERE title LIKE '%how%' OR body LIKE '%how%' 

Для больших таблиц это может потребовать от вас написания очень сложных и неприятных запросов.

FULLTEXT поиски

MySQL предоставляет альтернативу, которая выполняет большую часть работы за вас - индекс FULLTEXT . Индексы в базе данных очень похожи на индекс книги; они предоставляют средства для быстрого поиска информации в базе данных из упорядоченного списка. Индекс FULLTEXT позволяет вам искать в таблице определенные слова.

  Индексы FULLTEXT были введены в MySQL с версией 3.23.  Реализация на этом этапе была довольно ограниченной, но все же полезной для базового поиска, что я продемонстрирую здесь.  В версии MySQL 4.0.1 эта функциональность была расширена, чтобы обеспечить полный логический механизм поиска, который дает вам возможность создавать что-то вроде расширенных функций поиска Google.  Индексы FULLTEXT также позволяют каждому результату возвращаться со значением «релевантности», так что, например, результаты поиска по нескольким словам могут отображаться с точки зрения того, насколько хорошо каждый результат соответствует конкретному поиску этого пользователя.

Чтобы воспользоваться преимуществами индексов FULLTEXT , сначала необходимо FULLTEXT MySQL начать создание индекса столбцов, которые вы хотите найти:

 ALTER TABLE articles ADD FULLTEXT art_search (title, body, author) 

После того, как вы это сделали, вам нужно ВСТАВИТЬ новую запись (или изменить существующую), чтобы MySQL создал индекс. Для FULLTEXT поиска FULLTEXT также необходимы как минимум три записи в базе данных, поскольку не-булевы поиски будут возвращать результаты только в том случае, если строка поиска произошла менее чем в 50% строк таблицы (если в строке только две строки). таблица, и ваш поиск соответствует одной строке, что составляет 50%). И последнее, о чем следует помнить: поиск по FULLTEXT будет совпадать только с поиском более трех букв; механизм индексации игнорирует слова из трех или менее символов, чтобы избежать необходимости создавать массивный индекс. Это очень похоже на указатель книги; Вы были бы очень удивлены, обнаружив в указателе книги, на каких страницах появилось слово «the»!

Вот основной поиск FULLTEXT :

 SELECT * FROM articles  WHERE MATCH (title,body,author) AGAINST ('MySQL'); 

Этот поиск вернет все строки, в которых заголовок, текст или автор содержали слово «MySQL».

Другое использование индексов FULLTEXT - поиск, который возвращает релевантность для каждого результата. Например:

 Example 3.26. 19.php (excerpt)   // Select all rows but display relvance  $sql = "SELECT          *, MATCH (title, body, author)        AGAINST          ('The PHP Anthology Released Long Word Matching')        AS          score        FROM          articles        ORDER BY score DESC";   // Run the query, identifying the connection  $queryResource = mysql_query($sql, $dbConn);   // Fetch rows from MySQL one at a time  while ($row = mysql_fetch_array($queryResource, MYSQL_ASSOC)) {  echo 'Title: '  . $row['title']  . '<br />';  echo 'Author: ' . $row['author'] . '<br />';  echo 'Body: '   . $row['body']   . '<br />';  echo 'Score: '  . $row['score']  . '<br />';  } 

Счет псевдонимов теперь содержит значение, определяющее, насколько релевантна строка для поиска. Значение не в процентах, а просто мера; 0 означает, что совпадений не было. Сопоставление с одним словом приведет к значению около 1. Чем больше совпадающих слов, тем больше число, поэтому ранжирование по пяти словам даст оценку релевантности около 13. Алгоритм релевантности MySQL разработан для больших таблиц, поэтому чем больше данных, тем больше данных. у вас есть, тем более полезным становится значение релевантности.

В целом, возможности поиска MySQL FULLTEXT предоставляют механизм, который легко реализовать и дает полезные результаты.


Как мне сделать резервную копию моей базы данных?

Чем больше становится база данных, тем более нервным может быть отсутствие резервного копирования данных, которые она содержит. Что делать, если ваш сервер падает, и все потеряно? К счастью, MySQL поставляется с двумя альтернативами: утилита командной строки под названием mysqldump и синтаксис запроса для резервного копирования таблиц.

Вот как вы можете экспортировать содержимое базы данных из командной строки с помощью mysqldump :

 mysqldump -uharryf -psecret sitepoint > sitepoint.sql 

Эта команда войдет в MySQL как пользователь «harryf» ( -uharryf) с паролем «secret» ( -psecret) и выведет содержимое базы данных sitepoint в файл sitepoint.sql. Содержимое sitepoint.sql будет серией запросов, которые могут быть запущены против MySQL, возможно, с использованием утилиты mysql для выполнения обратной операции из командной строки:

 mysql -uharryf -psecret sitepoint < sitepoint.sql 

Используя систему функций PHP, вы можете выполнить указанную выше команду из сценария PHP (для этого необходимо войти в систему и выполнить сценарии PHP из командной строки). Следующий класс объединяет все это в удобную форму PHP, которую вы можете использовать для регулярного резервного копирования вашего сайта.

 Example 3.27. Database/MySQLDump.php (in SPLIB)   /**  * MySQLDump Class<br />  * Backs up a database, creating a file for each day of the week,  * using the mysqldump utility.<br />  * Can compress backup file with gzip of bzip2<br />  * Intended for command line execution in conjunction with  * cron<br />  * Requires the user executing the script has permission to execute  * mysqldump.  * <code>  * $mysqlDump = new MySQLDump('harryf', 'secret', 'sitepoint',  *                            '/backups');  * $mysqlDump->backup();  * </code>  * @access public  * @package SPLIB  */  class MySQLDump {  /**   * The backup command to execute   * @private   * @var string   */  var $cmd;   /**   * MySQLDump constructor   * @param string dbUser (MySQL User Name)   * @param string dbPass (MySQL User Password)   * @param string dbName (Database to select)   * @param string dest (Full dest. directory for backup file)   * @param string zip (Zip type; gz - gzip [default], bz2 - bzip)   * @access public   */  function MySQLDump($dbUser, $dbPass, $dbName, $dest,                     $zip = 'gz')  {    $zip_util = array('gz'=>'gzip','bz2'=>'bzip2');    if (array_key_exists($zip, $zip_util)) {      $fname = $dbName . '.' . date("w") . '.sql.' . $zip;      $this->cmd = 'mysqldump -u' . $dbUser . ' -p' . $dbPass .                   ' ' . $dbName . '| ' . $zip_util[$zip] . ' >' .                   $dest . '/' . $fname;    } else {      $fname = $dbName . '.' . date("w") . '.sql';      $this->cmd = 'mysqldump -u' . $dbUser . ' -p' . $dbPass .                   ' ' . $dbName . ' >' . $dest . '/' . $fname;    }  }   /**   * Runs the constructed command   * @access public   * @return void   */  function backup()  {    system($this->cmd, $error);    if ($error) {      trigger_error('Backup failed: ' . $error);    }  }  } 

Заметка

MySQLDumpКласс делает некоторые предположения о конфигурации операционной системы. Предполагается, что утилита mysqldump доступна по пути пользователя, который выполняет этот сценарий. Если используются утилиты gzipили bzip2, они также должны присутствовать в пути пользователя, который выполняет этот сценарий. bzip2обеспечивает лучшее сжатие, чем gzip, помогая сэкономить дисковое пространство.

Следующий код демонстрирует, как можно использовать этот класс:

 Example 3.28. 20.php   <?php  // Include the MySQLDump class  require_once 'Database/MySQLDump.php';   $dbUser = 'harryf';               // db User  $dbPass = 'secret';               // db User Password  $dbName = 'sitepoint';            // db name  $dest   = '/home/harryf/backups'; // Path to directory  $zip    = 'bz2';                  // ZIP utility to compress with   // Instantiate MySQLDump  $mysqlDump = new MySQLDump($dbUser, $dbPass, $dbName, $dest,                           $zip);   // Perform the backup  $mysqlDump->backup();  ?> 

$destПеременная определяет путь к каталогу , в котором резервный файл должен быть помещен. Созданное имя файла будет в следующем формате:

 databaseName.dayOfWeek.sql.zipExtension 

Например:

 sitepoint.1.sql.bz2 

dayOfWeekЭлемент может быть любое число от 0 до 6 (0 означает воскресенье и 6 будучи в субботу). Это обеспечивает еженедельное «скользящее» резервное копирование, файлы на следующую неделю перезаписывают файлы с предыдущей недели. Это должно обеспечить адекватное резервное копирование, предоставляя вам неделю для выявления серьезных проблем и не требуя чрезмерного дискового пространства для хранения файлов.

Использование утилиты ZIP не является обязательным. Если значение $zipпеременной не равно одному gzили bz2, тогда сжатие не будет выполнено, хотя для больших баз данных очевидно, что целесообразно использовать инструмент сжатия, чтобы минимизировать необходимый объем дискового пространства.

Этот класс предназначен для использования с утилитой crontab, которая является функцией Unix, которая позволяет выполнять сценарии на регулярной (например, ежедневной) основе.

MySQL также предоставляет операторы SQL BACKUP TABLE и RESTORE TABLE, которые позволяют вам копировать содержимое таблицы в другое место в вашей файловой системе. В отличие от mysqldumpутилиты, резервные копии таблиц, сохраненных таким способом, сохраняют свой исходный формат (который не читается человеком), но этот механизм не требует доступа к утилите командной строки, поэтому он может быть выполнен через веб-страницу.

Общий синтаксис этих утверждений следующий:

 BACKUP TABLE tbl_name[, tbl_name ...]  TO '/path/to/backup/directory'   RESTORE TABLE tbl_name[, tbl_name ...]  FROM '/path/to/backup/directory' 

Обратите внимание, что в системах Windows лучше указывать пути, используя косую черту (например C:/backups).

Комбинируя их с некоторыми операторами «самоанализа», которые предоставляет MySQL, мы можем сделать резервную копию нашей базы данных, используя MySQLкласс, который мы создали в этой главе. Для начала нам нужно получить список таблиц в базе данных, что быстро достигается с помощью синтаксиса запроса SHOW TABLES:

 Example 3.29. 21.php (excerpt)   <?php  // Include the MySQL class  require_once 'Database/MySQL.php';   $host   = 'localhost'; // Hostname of MySQL server  $dbUser = 'harryf';    // Username for MySQL  $dbPass = 'secret';    // Password for user  $dbName = 'sitepoint'; // Database name   $db = &new MySQL($host, $dbUser, $dbPass, $dbName);   // A query to show the tables in the database  $sql = "SHOW TABLES FROM sitepoint";   // Execute query  $result = $db->query($sql); 

Мы также храним количество строк, возвращаемых этим запросом, чтобы помочь нам отформатировать строку, которую мы будем использовать для построения запроса BACKUP:

 Example 3.30. 21.php (excerpt)   // Get the number of tables found  $numTables = $result->size(); 

Затем мы перебираем результаты, создавая разделенный запятыми список таблиц для резервного копирования:

 Example 3.31. 21.php (excerpt)   // Build a string of table names  $tables = '';  $i = 1;  while ($table = $result->fetch()) {  $tables .= $table['Tables_in_sitepoint'];  if ($i < $numTables) {    $tables .= ', ';  }  $i++;  } 

Наконец, мы используем синтаксис запроса BACKUP TABLE, чтобы скопировать таблицы в каталог по нашему выбору (для которого, конечно, сценарию, выполняющему этот запрос, требуется разрешение на запись):

 Example 3.32. 21.php (excerpt)   // Build the backup query  $sql = "BACKUP TABLE $tables TO '/home/harryf/backup'";   // Perform the query  $db->query($sql);   if (!$db->isError()) {  echo 'Backup succeeded';  } else {  echo 'Backup failed';  }  ?> 


Как восстановить поврежденный стол?

Хотя это не должно происходить, иногда данные, хранящиеся в MySQL, повреждаются. Существует ряд (редких) обстоятельств, когда это может произойти; Windows особенно восприимчива, так как не имеет надежного механизма блокировки файлов в Unix-системах. Серверы с большой нагрузкой, на которых запросы INSERT и UPDATE распространены наряду с SELECT, также могут быть подвержены случайным повреждениям. Предполагая, что вы используете табличный тип MyISAM (вы будете использовать его, если не указано иное), есть хорошие новости; В общем, вы должны быть в состоянии восстановить все данные в поврежденной таблице.

Обратите внимание, что информация, представленная здесь, представляет собой краткий справочник для тех случаев, когда вам нужна срочная помощь. Стоит прочитать руководство MySQL по предотвращению и восстановлению после сбоев, чтобы вы точно знали, что делаете.

MySQL предоставляет две важные утилиты для работы с поврежденными таблицами, а также удобный синтаксис SQL для тех, кто может получить доступ к командной строке MySQL.

Во-первых, утилита perror может быть запущена из командной строки, чтобы дать вам приблизительное представление о том, что означают коды ошибок MySQL. Утилита должна быть доступна из binподкаталога вашей установки MySQL. perror 145Например, ввод текста скажет вам:

 145 = Table was marked as crashed and should be repaired 

Затем из командной строки вы можете использовать утилиту myisamchkдля проверки самих файлов базы данных:

 myisamchk /path/to/mysql/data/table_name 

Чтобы исправить поврежденную таблицу с myisamchkпомощью следующего синтаксиса:

 myisamchk -r /path/to/mysql/data/table_name 

Используя SQL, вы также можете проверять и исправлять таблицы, используя такой запрос:

 CHECK TABLE articles 

И это:

 REPAIR TABLE articles 

Если повезет, вам нужно будет использовать эти команды только один или два раза, но стоит подготовиться заранее, чтобы вы могли эффективно реагировать (даже без намека на панику в ваших действиях).

Мне действительно нужно писать SQL?

Хорошим качеством для программиста является лень - желание сделать как можно больше с минимальными усилиями. Хотя вы, возможно, не захотите ссылаться на это в качестве одного из своих сильных сторон на собеседовании, мотивация облегчить себе жизнь является значительным благом в разработке хорошо спроектированного приложения.

Теперь, когда вы прочитали эту главу о PHP и MySQL, я думаю, что сейчас самое время показать, что я ненавижу SQL не потому, что с ним что-то не так, а потому, что он всегда вызывает у меня горе. Например, если в моем PHP есть синтаксическая ошибка, PHP найдет ее для меня. Но PHP не найдет ошибки в операторах SQL, и сообщения об ошибках MySQL могут быть меньше, чем показывать. Если я вручную пишу код SQL в приложении, я потрачу немало времени на его отладку - время, которое я мог бы потратить на простоту!

Что если бы вы могли вообще не писать SQL-операторы? Если вы вспомните раздел под названием «Как создать гибкие операторы SQL?», Где мы строили строки SQL «на лету» на основе входящих переменных, возможно, у вас возникло предположение, что будет какое-то общее решение для сделать генерацию SQL еще проще Ну, есть! Это называется PEAR :: DB_DataObject .

 DB_DataObjectэто класс, который инкапсулирует процесс написания операторов SQL в простом API. Он использует преимущества родной «грамматики» SQL и предоставляет вам механизм, который устраняет практически любую потребность в написании любого SQL самостоятельно. В качестве подхода к работе с базами данных он обычно описывается как уровень постоянства базы данных или, альтернативно, как использование шаблона проектирования объектов доступа к данным (DAO). Вы найдете дальнейшее обсуждение общих методов, используемых DB_DataObjectв конце этой главы.

Здесь я приведу краткое введение, чтобы DB_DataObjectвы могли начать, так как это предмет, который может легко поглотить целую главу, если изучить его подробно. Документация DB_DataObject на веб-сайте PEAR должна предоставить вам дополнительную помощь. Версия, которую мы использовали здесь, была 1.1; обратите внимание, что для этого требуется, чтобы у вас была PEAR::DBустановлена ​​библиотека абстракции базы данных (см. Приложение D, Работа с PEAR для получения дополнительной информации об установке библиотек PEAR).

Первый шаг в начале работы DB_DataObject- навести его на вашу базу данных и сказать, чтобы он генерировал DataObjectклассы, которые будут составлять ваш интерфейс с таблицами. DB_DataObjectавтоматически проверяет вашу базу данных, используя функцию самоанализа MySQL, и генерирует класс для каждой таблицы в базе данных, а также файл конфигурации, содержащий сведения о столбцах, определенных в таблице. Чтобы сообщить DB_DataObject, где находится ваша база данных, вам нужно предоставить ей файл конфигурации, подобный следующему:

 Example 3.33. db_dataobject.ini   [DB_DataObject]  ; PEAR::DB DSN  database        = mysql://harryf:secret@localhost/sitepoint  ; Location where sitepoint.ini schema file should be created  schema_location = /htdocs/phpanth/SPLIB/ExampleApps/DataObject  ; Location where DataObject classes should be created  class_location  = /htdocs/phpanth/SPLIB/ExampleApps/DataObject  ; Prefix for including files from your code  require_prefix  = ExampleApps/DataObject  ; Classes should be prefixed with this string eg DataObject_User  class_prefix    = DataObject_  ; Debugging information: 0=off, 1=display sql, 2=display results,  ; 3=everything  debug = 0  ; Prevent SQL INSERT, UPDATE or DELETE from being performed  debug_ignore_updates = false  ; Whether to die of error with a PEAR_ERROR_DIE or not  dont_die = false 

Приведенный выше iniфайл подчиняется тем же правилам форматирования, что и php.ini. Наиболее важной является первая строка - строка DSN PEAR :: DB, которая определяет переменные, необходимые для подключения к базе данных. Этот файл используется как для генерации DataObjectклассов, так и для их использования при выполнении запросов.


Имея это в виду, мы можем использовать этот скрипт (который должен быть запущен из командной строки) для генерации классов:

 Example 3.34. 22.php   <?php  // Builds the DataObjects classes  $_SERVER['argv'][1] = 'db_dataobject.ini';  require_once 'DB/DataObject/createTables.php';  ?> 

Этот скрипт автоматически создает файлы классов, которые нам нужны для доступа к базе данных. Вот пример, разработанный для таблицы статей:

 Example 3.35. ExampleApps/DataObject/Articles.php (in SPLIB)   <?php  /**  * Table Definition for articles  */  require_once 'DB/DataObject.php';   class DataObject_Articles extends DB_DataObject  {   ###START_AUTOCODE  /* the code below is auto generated do not remove the above tag  */   var $__table = 'articles'; // table name  var $article_id; // int(11)  not_null primary_key auto_increment  var $title; // string(255)  not_null multiple_key  var $intro; // blob(65535)  not_null blob  var $body;  // blob(65535)  not_null blob  var $author; // string(255)  not_null  var $published; // string(11)  var $public; // string(1)  not_null enum   /* ZE2 compatibility trick*/  function __clone() { return $this;}   /* Static get */  function staticGet($k,$v=NULL) {    return DB_DataObject::staticGet('DataObject_Articles',$k,$v); }   /* the code above is auto generated do not remove the tag below */  ###END_AUTOCODE  }  ?> 

Давайте теперь используем этот класс для доступа к таблице статей:

 Example 3.36. 23.php   <?php  // Include the DataObjects_Articles class  require_once 'ExampleApps/DataObject/Articles.php';   // Parse the database ini file  $dbconfig = parse_ini_file('db_dataobject.ini', true);   // Load Database Settings  // (note main PEAR class is loaded by Articles.php)  foreach ($dbconfig as $class => $values) {  $options = &PEAR::getStaticProperty($class, 'options');  $options = $values;  }   // Instantiate the DataObject_Articles class  $articles = new DataObject_Articles();   // Assign a value to use to search the 'Author' column  $articles->author = 'Kevin Yank';   // Perform the query  $articles->find();   echo 'Kevin has written the following articles:<br />';   // Loop through the articles  while ($articles->fetch()) {  echo ' - ' . $articles->title . ', published: ' .       date('jS M Y', $articles->published) . '<br />';  }  ?> 

Прежде всего, где SQL? Там нет - отлично! parse_ini_fileФункция обеспечивается PHP (смотрите Главу 4, файлы для более подробной информации) и сделок с получением переменных из нашего db_dataobject.iniконфигурационного файла. Цикл foreach делает обязательные переменные доступными, DB_DataObjectкогда мы создаем его автоматически сгенерированный подкласс DataObject_Articles. Присваивая значение свойству author $articlesобъекта, мы подготавливаем WHEREусловие, которое DataObject_Articlesследует использовать при запросе к базе данных. Запрос фактически выполняется путем вызова метода find (см. DB_DataObjectДокументацию для получения полной информации), который, в свою очередь, выполняет следующий запрос:

 SELECT * FROM articles WHERE articles.author = 'Kevin Yank' 

Чтобы просмотреть результаты, мы используем fetchметод. Когда он вызывается, fetchзаполняет свойства $articlesобъекта текущим результатом строки. Это позволяет нам снова обращаться к ним через имена свойств, как с $articles->title.

Для усложнения запроса предусмотрены дополнительные методы, например, whereAddметод:

 Example 3.37. 24.php (excerpt)   // Instantiate the DataObject_Articles class  $articles = new DataObject_Articles();   // Assign a value to use to search the 'Author' column  $articles->author = 'Kevin Yank';   // Add a where clause  $articles->whereAdd('published > ' . mktime(0, 0, 0, 5, 1, 2002));   // Perform the query  $articles->find(); 

Это позволяет нам добавить еще одно условие к WHEREпредложению:

 SELECT * FROM articles  WHERE published > 1020204000 AND articles.author = 'Kevin Yank' 

Существуют и другие подобные методы, поэтому, если они не могут обеспечить то, что вам нужно, вы можете использовать метод запроса для выполнения запроса с ручным кодированием. Обратите внимание, что если вам нужно использовать метод запроса, может быть неплохо создать подкласс сгенерированного DataObjectкласса и обернуть запрос в полезное имя метода, которое точно его описывает.

 DB_DataObject также эффективно работает с объединениями таблиц, что, хотя и немного более подробно, чем в приведенном выше примере, безусловно, экономит время по сравнению с написанием сложных запросов на соединение вручную.

На этом наше краткое введение завершено DB_DataObject, но в этом разделе вы должны были понять, что он может для вас сделать. Большим преимуществом является то, что это делает запросы к вашей базе данных с SQL гораздо менее утомительным и подверженным ошибкам. Кроме того, благодаря централизации доступа к конкретной таблице в одном классе, это помогает упростить работу с изменениями в структуре таблицы.

Дальнейшее чтение

Посмотрите другие главы Антологии PHP на SitePoint в ближайшие недели! Если вы не можете ждать, загрузите образцы глав или закажите свою собственную копию прямо сейчас !