3
\$\begingroup\$

Below is the code for a database class that wraps around a small set of the features that the mysqli extension provides. I'm looking for ways to improve its efficiency. Which parts of the code seem hack-ish and how can I make it more structured and organized?

class Database extends Component
{
    private static $mysqli = null;            // MySQLi object


    /* Connect to the database */
    public static function init()
    {
        if (Config::read('Database.enable'))
        {
            if (!self::connect(Config::read('Database.connections')))
                trigger_error('Database::connect() failed : Unable to connect to database', E_USER_ERROR);
        }
    }


    /* Close the database connection upon script determination */
    public function __destruct()
    {
        if (self::$mysqli)
            self::$mysqli->close(); 
    }


    /* Returns the type label (i, d, s, b) of an array of MySQLi input parameters */
    private static function getTypeLabel(array $args)
    {
        $return = ''; 

        foreach ($args as $input)
        {
            if (is_int($input))
                $return .= 'i'; 

            else if (is_double($input) || is_float($input))
                $return .= 'd'; 

            else if (is_string($input))
                $return .= 's'; 

            else
                $return .= 'b'; 
        }

        return $return; 
    }


    /* Returns whether the MySQL connection has been established */
    public static function connected()
    {
        return is_resource(self::$mysqli);
    }


    /* Returns the MySQL connection resource */
    public static function obj()
    {
        return self::$mysqli; 
    }


    /* Pass an array of login credentials and attempts to connect to the database */
    public static function connect($db_list)
    {
        foreach ($db_list as $db)
        {
            self::$mysqli = @(new mysqli($db['host'], $db['user'], $db['pass'], $db['db'])); 
            if (mysqli_connect_errno())
            {
                self::$mysqli = null; 
                continue; 
            }
            break;
        }

        return self::$mysqli !== null; 
    }


    /**
     * Execute a query given a query string and passed parameters. Returns
     * the result array or the number of affected rows if it is an update, insert
     * or delete query. Returns null and triggers error if query failed. 
     * This function basically combines executeQuery() and prepareQuery()

     * Example: 
     * 
     * $result = DB::query('SELECT * FROM my_table WHERE id = ?', $id); 
     * $num_rows = $result['num_rows']; 
     * foreach ($result['rows'] as $row)
     * {
     *     echo $row['field']; 
     * }
     *
    **/
    public static function query($query)
    {
        // If there is no connection to the MySQL server, then return null
        if (!self::$mysqli)
            return null; 

        if ($query = self::$mysqli->prepare($query))
        {
            $results = array(); 

            // Build args array and call mysqli_stmt::bind_param
            if (func_num_args() > 1)
            {
                $fga = func_get_args();  // For ASO servers (PHP 5.0.2), this is necessary
                $args = array_merge(array(func_get_arg(1)), array_slice($fga, 2)); 
                $args_ref = array(); 
                $args_ref[0] = self::getTypeLabel($args);  

                foreach ($args as $k => &$arg)
                    $args_ref[] =& $arg; 

                call_user_func_array(array($query, 'bind_param'), $args_ref); 
            }

            // Execute query
            $query->execute(); 

            // Most likely due to syntax errors
            if ($query->errno)
            {
                trigger_error(self::$mysqli->error, E_USER_ERROR); 
                return null; 
            }

            // If the query was insert, update or delete, then return affected rows
            if ($query->affected_rows > -1)
                return $query->affected_rows; 


            // Build results array and call mysqli_stmt::bind_result
            $params = array(); 
            $meta = $query->result_metadata(); 

            while ($field = $meta->fetch_field())
                $params[] =& $row[$field->name]; 

            call_user_func_array(array($query, 'bind_result'), $params); 

            // Fetch results and store them in returned array
            while ($query->fetch())
            {
                $r = array(); 
                foreach ($row as $key => $value)
                    $r[$key] = $value; 

                $result[] = $r; 
            }

            $query->close(); 
            return count($result) > 1 ? $result : $result[0];  
        }

        trigger_error(self::$mysqli->error, E_USER_WARNING); 
        return null; 
    }
}
\$\endgroup\$
5
\$\begingroup\$

It looks like you are trying to write object oriented code, however this is completely procedural. Remove static everywhere and use dependency injection if you want to make this OO.

Static code is hard to test. It is the equivalent a namespaced function.

The real benefit I see with OO is encapsulation and implementation hiding. This allows a program to be split into objects that can interact while sharing a small public interface (and hiding a lot of implementation details in protected and private methods). The problem with static is that it opens up all of the implementation details globally. It stops you being able to look at an object and just be worried about its public interface.

Calling a static function binds the calling code tightly to a specific implementation. Database::query is a much tighter binding than having injected a database object and then calling $this->db->query

\$\endgroup\$
  • \$\begingroup\$ If I used dependency injection to make those components actual objects instead of just static instances, what would be the practical benefit, besides making my code more OO? \$\endgroup\$ – Ryan Jan 13 '12 at 1:38
  • \$\begingroup\$ I updated my answer with some of the benefits that I see. \$\endgroup\$ – Paul Jan 13 '12 at 1:50
  • \$\begingroup\$ Interesting. But can't you also declare a variable in an object as private static? Then you can't access it from outside the class scope, but you can use public static methods to access the data. Would that adequately solve the problem of opening up the implementation details? Also, considering PHP is a loosely typed language, would the binding really matter? \$\endgroup\$ – Ryan Jan 13 '12 at 2:11
  • \$\begingroup\$ Trying to access an objects details is looking at it slightly the wrong way. Think of the object as being responsible for its area of responsibility (including its state data). Now the object may do something that it has responsibility over or describe some of its state to you in response to a method call. It is not the caller who does these things, it is the object. That is why you would use a get method rather than accessing it via static. The responsibility for the state data belongs with the object. Having control at the caller seems good at first, but it binds the calling code. \$\endgroup\$ – Paul Jan 13 '12 at 2:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.