An online store, being converted from procedural to OOP
At first I'm not mysqli guy and maybe somewhere I have mistake but the important part here Is the idea.

My advice to you is to throw exception instead of die or exit because you can catch them a lot easier. In your case you can put instantiation of MySQLDatabase in try/catch and if exception is occurred to show static page or whatever you wish.

class MySQLDatabase {

    private $connection;

    function __construct() {
        $this-&gt;open_connection();
    }

    public function open_connection() {
        $this-&gt;connection = mysqli_connect(DB_SERVER, DB_USER, DB_PASS, DB_NAME);
        if(!$this-&gt;connection) {
            throw new \Exception("Database connection failed: " .mysqli_connect_errno());
        }
    }
    /*
     * This function (mysqli_select_db) should only be used to change the default
     * database for the connection. You can select the default database with 4th
     * parameter in mysqli_connect().
     */
    public function select_db($db_name){
        mysqli_select_db($this-&gt;connection, $db_name);
        if(!$this-&gt;connection){
            throw new \Exception(" meaningfull message");
        }
        return $this;
    }

    public function close_connection() {
        mysqli_close($this-&gt;connection);
        unset($this-&gt;connection);
    }

    public function query($sql) {
        $result = mysqli_query($this-&gt;connection, $sql);
        return $this-&gt;confirm_query($result);
    }

    public function fetch_assoc($result_set) {
        return mysqli_fetch_assoc($result_set); 
    }

    private function confirm_query($result) {
        if(!$result) {
            throw new \Exception("Database query failed: " . mysqli_error($this-&gt;connection));
        }
        return $result;
    }

    public function free_result($result) {
        if($result) {
            return mysqli_free_result($result); 
        }
    }
}


I will make separate class for every table or group of futures(e.g. some kind of statistics) in separate class and all SQL queries are there. Important part of this is to not make validation in this class. You must consider that every parameter of method Is validated and correct.

class UserDomain
{
    protected $conn;

    public function __construct(MySQLDatabase $conn){
      $this-&gt;conn = $conn;
    }

    public function getUser($id){
        $sql = "SELECT * FROM users WHERE u.id = ?";
        $stmt = $this-&gt;conn-&gt;prepare($sql);
        $stmt-&gt;bind_param('i', $id);
        $stmt-&gt;execute();
        $tmp = array();
        while($res = $stmt-&gt;fetch()){
            $tmp[] = $res;
        }

        return $tmp;
    }

    public function setName($name, $user_id){}
    public function loginBy($user_id, $password){}
    public function changePassword($new_paswwrod, $user_id){}
    public function deleteUser($user_id){}
}


Here we have our class User. It can be tested very easy because your sql queries are now separated

// This class represents single user
class User {
    protected $id;

    protected $db;

    public function __construct($id, UserDomain $conn){
        $this-&gt;id = $id;
        $this-&gt;conn = $conn;
    }

    function getUserId(){
        return $this-&gt;id;
    }

    function changePassword($old_password, $new_password){

        if($this-&gt;conn-&gt;loginBy($this-&gt;id, $password)){
            // here you must validate new password against your business logic and if its true or pass try/catch you will update
            $this-&gt;changePassword($new_password, $this-&gt;id);
        }
    }

    function getUserInfo(){
        return $this-&gt;conn-&gt;getUserInfo($this-&gt;id);
    }
}


Finally I recommend you to watch this speak from Adam Culp he also have and book I think you will like it. And will give you nice ideas about refactoring legacy code and not write it