2

I have created a class which connects to the DB. All other classes can then use the connect function within this class to open a connection to the DB. At the end of the function, I return the results. So how do I close the connection after I have returned the results?

<?php

class DbhPdo {
  private $servername;
  private $username;
  private $pwd;
  private $dbname;

  protected function connect() {
    $this->servername = "localhost";
    $this->username = "someUser";
    $this->pwd = "somePswd";
    $this->dbname = "someDB";
    $this->charset = "utf8mb4";

    try{
      $dsn = "mysql:host=" . $this->servername . ";dbname=" . $this->dbname . ";charset=" . $this->charset;
      $pdo = new PDO($dsn, $this->username, $this->pwd);
      $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
      return $pdo;
    } catch(PDOException $e) {
      echo "Message: " . $e->getMessage();
    }
  }
}

If I set the following after the return, then it is never called to close the connection:

$pdo = null;

Or does PDO close this connection automatically because it is done executing commands after - return $pdo?

Or do I have to close the connection in the class that extended the connection?

The following is the class that extends the aforementioned class, but I am returning the results as well in this class, so I can not set stmt to null here either:

<?php

require_once('dbh.pdo.inc.php');

class LoginPdo extends DbhPdo {
  public $name;
  public $pass1;
  public $hashed;
  public $salted;


  protected function getSomething($name) {
    try{
      $stmt = $this->connect()->query("select something from table where name ='" . $name . "'");
      return $stmt->fetchAll(PDO::FETCH_ASSOC);
    }catch (PDOException $e){
      echo 'Message: ' . $e->getMessage();
    }
  }
}

Which leaves the original class that started this:

  try{
    $this->result = $this->getSomething($this->name);
    echo json_encode($this->result);
    $this->result = null;
  }catch (PDOException $e){
    echo 'Message: ' . $e->getMessage();
  }

Is setting the $this->result = null going to close the connection? If it does, then I do not understand. Can someone explain it to me? Or do I understand it correctly when setting $pdo = null would be where I need to close the connection?

If so, then how can I set it to null after the return $pdo is executed?

Thanks in advance

Update: Just in case someone else wants to be able to verify if a connection was closed, these were the steps that I took to confirm @Don'tPanic comments to tail the general log files:

mysql -u root -p
show variables like '%log%';
set global general_log=ON;

tail -f /usr/local/mysql/data/<your log file's name>.log

Which then showed the connection, as I used PostMan to post the following query were opened and closed instantly:

190130 11:00:17  2581 Query show variables like '%log%'
190130 11:02:14  2582 Connect   root@localhost on <table name>
    2582 Query  select * from something where name ='whatever'
    2582 Quit

This was accomplished by following @Don'tPanic answer to add a new function in the DbhPdo class:

protected function disconnect() {
  $this->pdo = null;
}

Then I added $this->pdo = null; after echo json_encode($this->result);

Thank you for all the comments

kronus
  • 902
  • 2
  • 16
  • 34
  • You don't store your connection in a variable, so it's difficult to set it to null. If you set the connection as a property of your class (`$this->pdo`), then you can add a method that sets that property to null to close the connection. – Don't Panic Jan 30 '19 at 15:46
  • In fact, the way you have it now, if you use other methods that also use `connect()` like that, I think you'll create multiple connections. – Don't Panic Jan 30 '19 at 15:47
  • where is `$this->charset = "utf8mb4";` defined as a property? Reference `DbhPdo` class. – Jaquarh Jan 30 '19 at 15:49
  • @Jaquarh, thank you for replying and I cannot believe that I did not receive an error after all these months using it, but I have added private $charset; with the other variables. – kronus Jan 30 '19 at 15:54
  • @Don'tPanic, are you saying that I need to add private $pdo, but where would I set $this->pdo = null? As I understand it, $this->pdo is returning the connection to $stmt = $this->connect()->query(). If I set it to null, then how will $stmt be executed. In your second comment, I believe you are correct and that is why I am wondering if there is a way to close the connections from within the connect function? Or do I have to go into each of the originating calls to connect and close $this->pdo = null after I echo the json_encode()? – kronus Jan 30 '19 at 16:00
  • I would have thought that `LoginPdo extends DbhPdo` is a bad design, your saying that `LoginPdo` is a type of database, I would have thought that `Login` would use a database - slightly different. – Nigel Ren Jan 30 '19 at 16:12
  • Also worth a read [Is it necessary to close PDO connections](https://stackoverflow.com/questions/15444748/is-it-necessary-to-close-pdo-connections) – Nigel Ren Jan 30 '19 at 16:14
  • @NigelRen thank you for replying. As to your comment of extending DbhPdo, I do not know of any other way to reach a protected function. Can you show me an example of how to do that without extending a class? – kronus Jan 30 '19 at 16:16
  • Look for Dependency Injection (DI) - something like https://stackoverflow.com/questions/50641139/php-pdo-injection-inside-a-class may be useful. – Nigel Ren Jan 30 '19 at 16:17
  • @NigelRen looking at the post that you have provided, if class User is in another file, then do I use namespace for User to find class Database? Or does class User have to be in the same file as class Database? – kronus Jan 30 '19 at 16:29
  • @NigelRen, one other question. Since the web site will only be having somewhere around 80k users a month, with maybe 2/3 logging in, then is it worth it for me to redesign everything? I thought that this type of design was only beneficial for large sites with millions of users per month? BTW, there are only 14 products that the site will be featuring - very small result sets returning – kronus Jan 30 '19 at 16:43
  • It's not about performance - more about correct design. It also helps testing as you can pass a database connection to the class with a test database settings etc. – Nigel Ren Jan 30 '19 at 16:46
  • @NigelRen, I asked this question before, but how do I confirm the database connection has been closed? Can I do something like a tail on dbh.pdo.inc.php file? Do I tail the mysql logs? Is there a grep mysql command – kronus Jan 30 '19 at 17:13

2 Answers2

1

Setting the result to null does not set the connection to null.

It may not really be necessary to explicitly close the connection, but if you want to be able to do that, you need to have something you can set to null. You'll have to have some reference to the connection, and your current method does not store that reference anywhere. Defining the connection as a property of the class will take care of that for you.

I modified your connect function to show an example.

Basically instead of just returning a connection, you check for an existing connection and return that, or make a new connection if one has not been made yet, but the important part is that you set the property $this->pdo instead of using a $pdo variable that only exists in the connect function scope.

// ...
private $pdo;

protected function connect() {
    // I suggest setting these in the constructor rather than hard coding them here
    // $this->servername = "localhost";
    // $this->username = "someUser";
    // $this->pwd = "somePswd";
    // $this->dbname = "someDB";
    // $this->charset = "utf8mb4";

    if ($this->pdo) {
        return $this->pdo;
    else {
        try{
            $dsn = "mysql:host=" . $this->servername . ";dbname=" . $this->dbname . ";charset=" . $this->charset;
            $this->pdo = new PDO($dsn, $this->username, $this->pwd);
            $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
            return $this->pdo;
        } catch(PDOException $e) {
            echo "Message: " . $e->getMessage();
        }
    }
}

Setting $this->pdo instead of just using a local variable in the connect function will give you something you can set to null to disconnect.

protected function disconnect() {
    $this->pdo = null;
}

You can call disconnect() after executing all the queries you need for the request. If you disconnect before that, you'll have to reconnect to run additional queries, which is unnecessary and will hurt the performance of your application.

Don't Panic
  • 41,125
  • 10
  • 61
  • 80
  • @dont-panic, thank you for replying. I will give this try and let you know what I found. I am assuming that after adding disconnect to the DbhPdo class, then I can call $this-disconnect() just before return $stmt->fetchAll(PDO::FETCH_ASSOC); - am I correct in understanding this? – kronus Jan 30 '19 at 16:13
  • You should call disconnect when you're finished using the connection. If you're going to potentially execute multiple queries you should reuse the same connection rather than disconnecting/reconnecting. But don't disconnect before fetching, disconnect after. If you close the connection, you can't fetch the results. – Don't Panic Jan 30 '19 at 16:15
  • Just remember to add `$this->pdo` as a property to your `DbhPdo` class ensuring it is `private` as you have *getter/setter* methods to use the instance. This is a quick fix answer and works fine, but I just don't think it explains the OP true question about whether or not `PDOPrepareStatement` holds the `PDO` instance by reference and would, in your new answers case, calling `disconnect` prior to `fetchAll`, still work. – Jaquarh Jan 30 '19 at 16:20
  • 1
    @Jaquarh Thanks for the feedback. I made an edit to try to be more explicit about those points. – Don't Panic Jan 30 '19 at 16:34
  • @Don'tPanic, one more thing. How do I check/confirm that the connection(s) actually closed. Can I do something like a tail on dbh.pdo.inc.php file? – kronus Jan 30 '19 at 16:40
  • Yeah, it is strange because the [docs](http://php.net/manual/en/pdo.query.php) never mention about the reference to the pdo instance. It only states *executes an SQL statement in a single function call, returning the result set (if any) returned by the statement as a PDOStatement object* – Jaquarh Jan 30 '19 at 16:43
  • It does however mention *to release the database resources associated with the PDOStatement object* thus probably meaning the database assigns the pdo instance resources when the query method is executed which is executed later with variable thus needing the active connection. Either way, it fixes OP's issue so +1. – Jaquarh Jan 30 '19 at 16:45
  • @Don'tPanic BTW, I have tried mysqladmin --user=username --password=password -i 1 processlist, which is supposed to refresh the connection list every second, but I only see the main connection to localhost. I would like to verify that your answer is correct. I am using postman to post the values and it returns with the expected JSON, but I have no way of knowing if the connection was closed or not – kronus Jan 30 '19 at 17:26
  • @kronus You can use the general query log to see when clients connected and disconnected. – Don't Panic Jan 30 '19 at 17:36
  • @Don'tPanic thank you for replying. When I run mysql -se "SHOW VARIABLES" | grep -e log_error -e general_log -e slow_query_log to find the error log, then each time I try to do a tail -f on the log, the terminal responds with "No such file or directory". How do I setup a general query log for mysql? – kronus Jan 30 '19 at 18:11
0

Your instance of the PDO API is returned from $this->connect(). This is the only instance you use. If you wanted to save the state to later use or close it you could use:

$stmt = ($con =& $this->connect())->query(...);
$con  = null; # Or use the instance else where
return $stmt->fetchAll(PDO::FETCH_ASSOC);

If the PDOPrepareStatement uses the PDO instance by reference, you can use clone to copy the instance rather than write to it.

$stmt = ($con =& clone $this->connect())->query(...);

Your $stmt variable is a PDOPrepareStatement since PDO::query does not return the PDO instance so by doing so, you're already setting it to null.

Point to note: You should think about designing a singleton approach if you want to build models. You can look at the example I made for Laravel

trait Singleton
{
    private static $instance;

    public static function getInstance()
    {
        return self::$instance ?? (self::$instance = new self());
    }

    //
}

Then you can create your Super Model:

class Model
{
    use Singleton;

    protected $pdo;

    public function __construct()
    {
        // instance your PDO
    }

    public function select()
    {
        // super method all models can use
    }
}

Then just create your models!

class UserModel extends Model
{
    public function find($id)
    {
        return $this->select(...)->fetch();
    }
}

You can use it like so:

UserModel::getInstance()
    ->find(1);
Jaquarh
  • 6,493
  • 7
  • 34
  • 86
  • thank you for replying. I have never been clear on the concept of singleton, models and Super - do I have a link to an article that makes this concept clear? Also, are you saying that I can set $this-connect = null just before return $stmt->fetchAll(PDO::FETCH_ASSOC); without effecting $stmt? – kronus Jan 30 '19 at 16:09
  • You never capture the PDO instance. Your `connect` method in your `DbhPdo` class only returns the instance. A call to `$this->connect()->query()` never saves the state of the PDO instance, it uses it but is never stored in memory - thus doing `($con =& $this->connect())->query()` and then using `$con = null` will not make a difference **only unless** the `query()` return uses the `PDO` instance by reference. In this case, you would need to `clone`. Or, you could capture the rows in a variable, close the connection and finally, return the rows. – Jaquarh Jan 30 '19 at 16:14
  • You can reference [this question](https://stackoverflow.com/questions/7104957/building-a-singleton-trait-with-php-5-4) about using `trait` in PHP 7+ if you'd like to widen your scope in Singletons. **Super** just refers to the parent class and a **model** is something you use in an [ORM concept](https://en.wikipedia.org/wiki/Object-relational_mapping). – Jaquarh Jan 30 '19 at 16:14
  • thank you but the server is PHP 5.3 and my dev server is 5.6. Will all of this work on those versions of PHP? – kronus Jan 30 '19 at 16:37
  • I'd suggest updating your PHP version then. PHP 7+ depreciated a lot of things and imported a lot of new security fixes and programming features. PHP 7.2 changed the encryption extension to LibSodium and `openssl_*` is now more secure. You shouldn't be running a server lower than PHP 7 in this day and age - just update. – Jaquarh Jan 30 '19 at 16:40