Making a databaseDo() function for hooking PDO queries

Refresh

December 2018

Views

161 time

5

So I started this tutorial as an intro to the PHP PDO. Up until now I've only worked with basic mysql_* type queries.

I've noticed that, throughout the tutorial, the connect -> do action -> disconnect pattern is repeated, and only the do action part ever changes.

In a real-world setting, would it be a good idea to eliminate repetition by creating a function into which queries can be passed?

For example:

a function for handling queries:

<?php
function databaseDo($action) {
    $db_hostname = 'localhost';
    $db_username = 'root';
    $db_password = 'root';

    try {
        // Establish DB connection
        $dbh = new PDO("mysql:host=$hostname;dbname=mysql", 
                $db_username, $db_password);
        echo 'Connected to database';

        // Do something
        $action($dbh);        // <- here goes whatever action we wish to perform

        // Close connection
        $dbh = null;
    } 
    catch(PDOException $e) {
        echo $e->getMessage();
    }
?>

Then, suppose I want to perform the action in the first example of the PDO tutorial, I would set it like this:

<?php
// Define action
$insert = function($dbh) {
    $query = "INSERT INTO animals(animal_type, animal_name)
                VALUES ('kiwi', 'troy')";
    $exec = $dbh->exec($query);
    echo $exec; 
};

// Perform action
databaseDo($insert);
?>

Scope of $dbh

I am using $dbh as an argument. Is this the proper way of passing a variable to a function like this without making it global?

2 answers

4

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

abstract class DB
   {

    protected static $instance;

    protected $db;

    protected static $host = 'host';
    protected static $user = 'user';
    protected static $pass = 'pass';
    protected static $database;

    public static function getInstance()
    {
        if (!isset(self::$instance)) self::$instance = new static();

        return self::$instance;
    }

    public static function doStatement($statement, array $parameters)
    {

         $handler = self::sql()->prepare($statement);
         $handler->closeCursor();
         $handler->execute($parameters);
         return $handler;
    }
    protected function __construct()
    {
        $this->db = new PDO(sprintf('mysql:host=%s;dbname=%s', static::$host, static::$database), static::$user, static::$pass);
        $this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
        $this->db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ);

    }

    public static function db()
    {
        return static::getInstance()->db;
    }
}

class Main extends DB
{
    protected static $database = 'db';
}

использовать его

$db = Main::getInstance();
$results = $db::doStatement('SELECT * FROM table WHERE id = :id', array(':id' => 5));

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

1

В то время как предотвращение повторения (DRY) является принцип , который всегда следует учитывать в ваших кодирования решений, это должно быть достигнуто без нарушения другого важного принципа, который является разделение интересов ( SoC см и SRP ). Ваш пример, databaseDo ($ действие), имеет двойную цель: она (1) инициализирует соединение с базой данных и (2) она выполняет запрос.

Теперь, вы можете сказать: «Да! Это именно то, что я хочу! Убейте двух зайцев одним выстрелом!», И ваша причина сказать это было бы понятно. Однако, смесительные обязанности может стать проблематичным, потому что, когда вы должны внести изменения в способ, в котором обрабатывается одна ответственность, вы, вероятно, также придется внести изменения в пути другая ответственность обрабатывается.

Представьте, что вы были в какой-то момент в будущем, необходимых для поддержки двух соединений с базой данных, а не только один. Предположим, что один из двух баз данных поддерживает транзакции при работе с таблицами, для которых другие не делает. Ваша функция databaseDo () сначала придется вести переговоры, к которым база данных для подключения, а затем, для того, чтобы безопасно выполнить с «делать» действие, потребуется некоторая поддержка транзакций тестирования. Это будет выглядеть примерно так:

$context = 'production'; // but it could equally be 'development'

function databaseDo($action) {
  $db_hostname = ($context == 'production') ? 'http://remotehost.com' : 'localhost';
  $db_username = ($context == 'production') ? 'apache' : 'root';
  $pass = ($context == 'production') ? 'productionpassword' : 'developmentpassword';

  try {
    $dbh = new PDO("mysql:host=$db_hostname;dbname=mysql", $db_username, $db_password);
    echo 'Connected to database';

    if($context == 'production') {
      // ... some complicated logic to determine whether the production db 
      // will support your query, then execute it if so, exit if not ... 
    }

    if($context == 'development') {
      // ... some more complicated logic for testing and querying the 
      // development db ...
    }

    $dbh = null;
  } catch(PDOException $e) {
    echo $e->getMessage();
  }
}

Дополнительные требования к обращению с одной ответственности добавит сложности к обработке второй ответственности, и эта функция будет становиться все более и более трудно поддерживать.

Лучший подход к СУХОМУ в этом сценарии будет осуществлять управление подключением к базе данных в одном компоненте, например , как в одноплодном экземпляре класса контекстно-зависимый (общий подход), и обработке запроса в другом компоненте. Таким образом, ваша функция запроса не обязательно должны изменить за счет изменения в обработке соединения с базой данных. Учебник , который вы упомянули имеет инструкции по созданию и использованию такого одноэлементного экземпляра .