1

Should we set the pdo's function __construct to private or public? Will it be a subject of vulnerability if I set it to public?

Here is my db class,

class pdo_connection
{
    public $connection; // handle of the db connexion


    public function __construct($dsn = 'mysql:host=localhost;dbname=xxx_2013',$username = 'root',$password = 'xxx')
    {   
    $this->dsn = $dsn;
        $this->username = $username;
        $this->password = $password;
        $this->connection();
    }


    private function connection()
    {
        try
        {
            $this->connection = new PDO($this->dsn, $this->username, $this->password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
            $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
        }
        catch (PDOException $e) 
        {
            # call the get_error function
            $this->get_error($e);
        }
        // return $this->connection;
    }
...
}

EDIT:

class database extends common
{
    /**
     * Set the class property.
     */
    protected $connection = null;
    protected $dsn,$username,$password;

    /**
     * Set the class contructor.
     * @reference: http://us.php.net/manual/en/language.oop5.magic.php#object.sleep
     */
    public function __construct($dsn,$username,$password)
    {
        $this->dsn = $dsn;
        $this->username = $username;
        $this->password = $password;
        //$this->connect();


    }

    /**
     * make the pdo connection.
     * @return object $connection
     */
    public function connect()
    {
        try
        {
            # MySQL with PDO_MYSQL  
            # To deal with special characters and Chinese character, add charset=UTF-8 in $dsn and array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8").

            $this->connection = new PDO($this->dsn, $this->username, $this->password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
            $this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 
        }
        catch (PDOException $e) 
        {
            # call the get_error function
            $this->get_error($e);
        }
    }

    /**
     * get the number of rows in a result as a value string.
     * @param string $query
     * @param array $params
     * @return number
     */
    public function num_rows($query, $params = array())
    {
        try 
        {
            # create a prepared statement
            $stmt = $this->connection->prepare($query);

            # if $params is not an array, let's make it array with one value of former $params
            if (!is_array($params)) $params = array($params);

            # execute the query
            $stmt->execute($params);

            # return the result
            return $stmt->rowCount();
        } 
        catch (PDOException $e) 
        {
            # call the get_error function
            $this->get_error($e);
        }
    }

    /**
     * fetch a single row of result as an array ( =  one dimensional array).
     * @param string $query
     * @param array $params
     * @param boolean $array_to_object
     * @return object/ array
     */
    public function fetch_assoc($query, $params = array(), $array_to_object = true)
    {
        try
        {
            # prepare the query
            $stmt = $this->connection->prepare($query);

            # if $params is not an array, let's make it array with one value of former $params
            if (!is_array($params)) $params = array($params);

            # the line
            //$params = is_array($params) ? $params : array($params);
            # is simply checking if the $params variable is an array, and if so, it creates an array with the original $params value as its only element, and assigns the array to $params.

            # This would allow you to provide a single variable to the query method, or an array of variables if the query has multiple placeholders.

            # The reason it doesn't use bindParam is because the values are being passed to the execute() method. With PDO you have multiple methods available for binding data to placeholders:

            # bindParam
            # bindValue
            # execute($values)

            # The big advantage for the bindParam method is if you are looping over an array of data, you can call bindParam once, to bind the placeholder to a specific variable name (even if that variable isn't defined yet) and it will get the current value of the specified variable each time the statement is executed.

            # execute the query
            $stmt->execute($params);

            # return the result
            if($array_to_object) return parent::array_to_object($stmt->fetch());
                else return $stmt->fetch();
        }
        catch (PDOException $e) 
        {
            # call the get_error function.
            $this->get_error($e);
        }

        /*
        or,

        catch (Exception $e)
        {
            // Echo the error or Re-throw it to catch it higher up where you have more
            // information on where it occurred in your program.
            // e.g echo 'Error: ' . $e->getMessage(); 

            throw new Exception(
                __METHOD__ . 'Exception Raised for sql: ' . var_export($sql, true) .
                ' Params: ' . var_export($params, true) .
                ' Error_Info: ' . var_export($this->errorInfo(), true),
                0,
                $e);
        }
        */
    }

    /**
     * fetch a multiple rows of result as a nested array ( = multi-dimensional array).
     * @param string $query
     * @param array $params
     * @param boolean $array_to_object
     * @return object/ array
     */
    public function fetch_all($query, $params = array(), $array_to_object = true)
    {
        try
        {
            # prepare the query
            $stmt = $this->connection->prepare($query);

            # if $params is not an array, let's make it array with one value of former $params
            if (!is_array($params)) $params = array($params);

            # when passing an array of params to execute for a PDO statement, all values are treated as PDO::PARAM_STR.
            # use bindParam to tell PDO that you're using INTs
            # wrap the bindParam function in a foreach that scan your parameters array
            # it's $key + 1 because arrays in PHP are zero-indexed, but bindParam wants the 1st parameter to be 1, not 0 (and so on).
            /*
            foreach($params as $key => $param)
            {
              if(is_int($param))
                {
                    $stmt->bindParam($key + 1, $param, PDO::PARAM_INT);
                }
              else
                {
                    $stmt->bindParam($key + 1, $param, PDO::PARAM_STR);
                }
            }

            # execute the query
            $stmt->execute();
            */

            # execute the query
            $stmt->execute($params);

            # return the result
            if($array_to_object) return parent::array_to_object($stmt->fetchAll(PDO::FETCH_ASSOC));
                else return $stmt->fetchAll(PDO::FETCH_ASSOC);
        }
        catch (PDOException $e) 
        {
            # call the get_error function
            $this->get_error($e);
        }
    }

    /**
     * return the current row of a result set as an object.
     * @param string $query
     * @param array $params
     * @return object
     */
    public function fetch_object($query, $params = array())
    {
        try
        {
            # prepare the query
            $stmt = $this->connection->prepare($query);

            # if $params is not an array, let's make it array with one value of former $params
            if (!is_array($params)) $params = array($params);

            # execute the query
            $stmt->execute($params);

            # return the result
            return $stmt->fetchObject();
            //return $stmt->fetch(PDO::FETCH_OBJ);
        }
        catch (PDOException $e) 
        {
            # call the get_error function
            $this->get_error($e);
        }
    }

    /**
     * insert or update data.
     * @param string $query
     * @param array $params
     * @return boolean - true.
     */
    public function run_query($query, $params = array())
    {
        try
        {
            $stmt = $this->connection->prepare($query);
            $params = is_array($params) ? $params : array($params);
            $stmt->execute($params);
            return true;
        }
        catch (PDOException $e) 
        {
            # call the get_error function
            $this->get_error($e);
        }
    }

    /**
     * with __sleep, you return an array of properties you want to serialize. the point is to be able to exclude properties that are not serializable. eg: your connection property.
     * @return array
     */
    public function __sleep()
    {
        return array('dsn', 'username', 'password');
    }

    /**
     * the intended use of __wakeup() is to reestablish any database connections that may have been lost during serialization and perform other reinitialization tasks.
     */
    public function __wakeup()
    {
        $this->connect();
        //$this->connection =  new PDO($this->dsn, $this->username, $this->password, array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"));
        //$this->connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    }

    /**
     * display error.
     * @return string
     */
    public function get_error($e) 
    {
        $this->connection = null;
        die($e->getMessage());
    }

    /**
     * close the database connection when object is destroyed.
     */
    public function __destruct()
    {
        # set the handler to NULL closes the connection propperly
        $this->connection = null;
    }
}

Usage:

$database = new database(DSN,DB_USER,DB_PASS);
$connection = $database->connect();
var_dump($connection);

result,

null

it should be,

object(database)[1]
  protected 'connection' => 
    object(PDO)[2]
  protected 'dsn' => string 'mysql:host=localhost;dbname=xxx_2013' (length=42)
  protected 'username' => string 'xxx' (length=4)
  protected 'password' => string 'xxx' (length=5)
Run
  • 54,938
  • 169
  • 450
  • 748
  • Public. Otherwise you can't construct the object. – AmazingDreams Aug 05 '13 at 10:32
  • if you set it to private you can ensure only one instance of the db connection exist in a given session of the application. – DevZer0 Aug 05 '13 at 10:33
  • ^ And that's called a singleton. Which is **BAD** :D – Jimbo Aug 05 '13 at 10:33
  • 2
    @AmazingDreams please read more about object oriented design – DevZer0 Aug 05 '13 at 10:33
  • 2
    @Jimbo, singleton are not so bad in php world because there is no threading. – DevZer0 Aug 05 '13 at 10:34
  • @DevZer0 you are right. Thank you for learning me something new today. :) – AmazingDreams Aug 05 '13 at 10:35
  • so private is better then? – Run Aug 05 '13 at 10:36
  • 2
    @DevZer0 How is global state "not so bad"?? – PeeHaa Aug 05 '13 at 10:36
  • @DevZer0 You're kidding me, right? – Jimbo Aug 05 '13 at 10:37
  • Some parts of our code *has* a global state. Despite of any religion says it shouldn't. – Your Common Sense Aug 05 '13 at 10:37
  • if singleton is bad the registry becomes bad too, because registry relies on singleton. what your talking about? – DevZer0 Aug 05 '13 at 10:38
  • 2
    @DevZer0 registry *is* bad. – PeeHaa Aug 05 '13 at 10:39
  • For more info: http://misko.hevery.com/2008/07/24/how-to-write-3v1l-untestable-code/ – PeeHaa Aug 05 '13 at 10:40
  • @DevZer0 Next you're going to tell us that service locators are good practice... – Jimbo Aug 05 '13 at 10:41
  • @AmazingDreams Ignore the rude "go learn more about OOP". DevZer0 I hope you've learned something here today - that you need to go and learn more about OOP and best practice. – Jimbo Aug 05 '13 at 11:15
  • @Jimbo He just showed me that it is possible to mark `__construct()` as private, that's all. I myself work with a `HMVC` framework. What about singletons there? The singleton IS used across multiple requests. In fact, I use one to share information across requests which gave me 25-50% performance increase (e.g. logged in user and other common information) (accessed through a `Factory($instance_name = '')`) – AmazingDreams Aug 05 '13 at 11:17
  • @AmazingDreams Have you read [this](http://stackoverflow.com/questions/4595964/who-needs-singletons/4596323#4596323)? Why don't you use sessions to store the logged in user and other common information? – Jimbo Aug 05 '13 at 11:25
  • Because it is important that the user is exactly the same object (in the literal term). As I also load all kinds of stuff related to that user (language, country, stuff, stuff, stuff, other stuff) which is 'cached' inside that object. Besides, it is more than just the user object. All kinds of stuff is shared that way which are structured in a similar manner. – AmazingDreams Aug 05 '13 at 11:27

1 Answers1

7

No. You're thinking about this wrong. Your public methods are the API that you're giving to other developers who use your class.

At the end of writing your class, imagine you're handing your documentation for it to another developer. You'll list "endpoints" which other developers can use. These currently are:

  • __construct()
  • connection()

Clearly you want connection() to be more something along the lines of connect() as it makes more sense for another developer looking at your code to see what it does. Even makeConnection() is better.

Singletons are BAD

Setting a constructor to private is usually for the following reason: a singleton. You want to avoid global state, singletons etc - they're bad practice, make testing hard and more.

In languages where objects live in shared memory, Singletons can be used to keep memory usage low. Instead of creating two objects, you reference an existing instance from the globally shared application memory. In PHP there is no such application memory. A Singleton created in one Request lives for exactly that request. A Singleton created in another Request done at the same time is still a completely different instance. Thus, one of the two main purposes of a Singleton is not applicable here.

Read this post for more of an indepth understanding of why singletons are just daft. If you don't want more than one instance of a connection, don't create one. If you can't help but create one, then you need to rethink your architecture (which shouldn't be too bad because handling DB stuff is usually in the early stages of whatever you're doing).

In closing, your constructor needs to be public. Why? So the developers (imagine yourself as another developer) who use it can use it for it's intended purpose.


Extra notes:

  1. Your username / password should not be hard coded into the class constructor, you should pass these in when you create the object.

  2. Don't call $this->connection() from your constructor. You might as well just shove it all in there if you're doing that. Instead, call:

    $db = new pdo_connection($username, $password, $etc);
    $db->connection();

This means that you can pass your object around wherever you like, but you only use resources (create the connection) when you run the connection() method.

Community
  • 1
  • 1
Jimbo
  • 25,790
  • 15
  • 86
  • 131
  • ok. thanks for the answer. I won't go for Singletons as I find it hard to understand. – Run Aug 05 '13 at 11:00
  • I don't understand your second note. could you please give me an example how you would do it? thanks! – Run Aug 06 '13 at 13:19
  • 1
    So, where you create a new pdo_connection, you just run `connection()` straight afterwards. Remove `$this->connection();` from your constructor. – Jimbo Aug 06 '13 at 13:25
  • I am afraid the pdo it won't work if I follow your advice. It return `null`... please see my edit above. – Run Aug 06 '13 at 15:43
  • It seems I must put `$this->connection();` inside `__construct` for some reasons... – Run Aug 06 '13 at 15:50
  • 1
    So you tried $db = new pdo_connection(); $db->connection(); and it didn't work? – Jimbo Aug 06 '13 at 17:15
  • Yes, it works in that way that I never thought off and didn't read your codes carefully. Stupid me! Thank you!! :D – Run Aug 07 '13 at 06:50