Избегайте вставки базы данных при сбое другого запроса [closed]

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

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

Прежде всего, позвольте мне показать класс, который обрабатывает соединение с базой данных.

# database.class.php
namespace Playground;

use PDO;

class Dbh {
  private static $instance;
  private $conn = null;
  private $database="playground";
  private $host="localhost";
  private $user="root";
  private $pass="root";
  private $option = array(
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_INIT_COMMAND => "SET time_zone="-3:00", lc_time_names="pt_BR""
  );

  /**
   * Construct
   */
  private function __construct() {
    $this->conn = new PDO(
      'mysql:charset=utf8mb4;host=".$this->host.";dbname=".$this->database,
      $this->user,
      $this->pass,
      $this->option
    );
  }

  /**
   * Prevent class cloning
   */
  private function __clone() {}

  /**
   * Initialize an instance of this class
   */
  public static function init() {
    if (!self::$instance) {
      self::$instance = new Dbh();
    }
    
    return self::$instance;
  }

  /**
   * Close database connection
   */
  public function close() {
    $this->conn = null;
  }

  /**
   * Load all data from a table and return as an array.
   * 
   * @param String  $sql Query to be executed
   * @param Array   $param Parameters to be binded
   * 
   * @return Array
   */
  public function load(String $sql, Array $param = null) {
    $q = $this->conn->prepare($sql);
    $q->execute($param);

    return  $q->fetchAll();
  }

  /**
   * Insert new record to the database and return the ID.
   * 
   * @param String  $sql Query to be executed
   * @param Array   $param Parameters to be binded
   * 
   * @return Number
   */
  public function insert(String $sql, Array $param = null) {
    $q = $this->conn->prepare($sql);
    $q->execute($param);

    return $this->conn->lastInsertId();
  }
}
# user.class.php

/**
 * Register a new user
 *
 * @param Object $jwt JWT Token
 * @param Object $post Data via $_POST
 *
 * @return Response<Null|Object>
 */
public function addUser(object $jwt, object $post) {
  // Init Dbh
  $dbh = Dbh::init();

  // Check for required fields
  if (!Utils::checkParams($post, $this->userAddRequiredParams)) {
    return $this->response->error("validation');
  }

  // Add new user
  $params = [
    ':id_group' => $post->id_group,
    ':master' => $post->master,
    ':email' => $post->email,
    ':pass' => Auth::generatePassword(), // Random for new users
  ];
  $id_user = $dbh->insert("INSERT INTO tb_user
    (id_group, master, email, pass) VALUES
    (:id_group, :master, :email, :pass)
  ", $params);
  if (!$id_user) {
    Log::error($jwt->uid, 'post', 'user', 'Fail to add new user. tb_user', $params);
    return $this->response->error();
  }

  // Add user detail
  $params = [
    ':id_user' => $id_user,
    ':first_name' => ucfirst($post->first_name),
    ':last_name' => $post->last_name,
    ':birthdate' => Utils::converterData($post->birthdate),
    // [...] There are many other fields here
  ];
  $id_user_info = $dbh->insert("INSERT INTO tb_user_info
    (id_user, first_name, last_name, birthdate, ...) VALUES
    (:id_user, :first_name, :last_name, :birthdate, ...)
  ", $params);
  if (!$id_user_info) {
    Log::error($jwt->uid, 'post', 'user', 'Fail to add new user. tb_user_info', $params);
    return $this->response->error();
  }

  // Add user address
  $params = [
    ':id_user' => $id_user,
    ':street' => $post->street,
    ':zipcode' => $post->zipcode,
    // [...] There are many other fields here
  ];
  $id_address = $dbh->insert("INSERT INTO tb_user_address
    (id_user, street, zipcode, ...) VALUES
    (:id_user, :street, :zipcode, ...)
  ", $params);
  if (!$id_address) {
    Log::error($jwt->uid, 'post', 'user', 'Fail to add new user. tb_user_address', $params);
    return $this->response->error();
  }

  // Create record to sync user and group
  $params = [
    ':id_author' => $jwt->uid,
    ':id_group' => $post->id_group,
    ':id_user' => $id_user,
  ];
  $insert = $dbh->insert("INSERT INTO tb_user_group
    (id_author, id_group, id_user) VALUES
    (:id_author, :id_group, :id_user)
  ", $params);
  if (!$insert) {
    Log::error($jwt->uid, 'post', 'user', 'Fail to add new user. tb_user_group', $params);
    return $this->response->error();
  }

  // Send an welcome e-mail to the new user
  // [...]
  // Some other irrelevant stuff

  // Return success
  $user = $this->loadUser($id_user);
  return $this->response->success('User added successfully', $user);
}

Этот код работает должным образом. Но допустим, что по какой-то причине нам не удалось правильно вставить адрес пользователя. Тогда функция сломается по адресу и сгенерирует недействительную регистрацию пользователя, поскольку у него не будет адреса.

Я знаю, что есть возможность сделать откат, но не знаю, как это работает, так как раньше я этого не делал. Посмотрев на другие материалы, я нашел кое-что, основанное на Try/Catch метод для всех запросов к базе данных, но когда я попытался, я не смог сделать ошибку, я имею в виду, я сам сделал недопустимый запрос, чтобы проверить его, но catch метод никогда не выполняется. Вот что я сделал.

/**
 * Register a new 
 *
 * @param Object $jwt JWT Token
 * @param Object $post Data via $_POST
 *
 * @return Response<Null|Object>
 */
public function addUser(object $jwt, object $post) {
  // Init Dbh
  $dbh = Dbh::init();

  // Check for required fields
  if (!Utils::checkParams($post, $this->userAddRequiredParams)) {
    return $this->response->error('validation');
  }

  try {
    // Note: This is the same query as before, I'm justi simplifying

    // Add new user
    $params = [':id_group' => $post->id_group, [...]];
    $id_user = $dbh->insert("INSERT INTO tb_user ...", $params);

    // Add user detail
    $params = [':id_user' => $id_user, [...]];
    $id_user_info = $dbh->insert("INSERT INTO tb_user_info ...", $params);

    // Add user address
    $params = [':id_user' => $id_user, ...];
    $id_address = $dbh->insert("INSERT INTO tb_user_address ...", $params);

    // Create record to sync user and group
    $params = [':id_author' => $jwt->uid, [...]];
    $insert = $dbh->insert("INSERT INTO tb_user_group ...", $params);
  } catch (Throwable $th) {
    $dbh->rollback();
    Log::error($jwt->uid, 'post', 'user', 'Fail to add new user. tb_user', $params);
    return $this->response->error();
  }

  // Send an welcome e-mail to the new user
  // [...]
  // Some other irrelevant stuff

  // Return success
  $user = $this->loadUser($id_user);
  return $this->response->success('User added successfully', $user);
}

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

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *