Search code examples
phpooppdo

How to use PDO Prepared statements within a PHP Child Class


I'm losing my mind a little bit. I have two classess Database and Medication [extends Database].

Class Database is a singleton class that constructs my PDO statement:

protected function __construct()
{
    require_once(PRIVATE_PATH . "/config/db_credentials.php");
    try {
        $this->conn = new PDO("mysql:host=" . DB_SERVER . ";dbname=" . DB_NAME, DB_USER, DB_PASS);
        $this->conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    } catch (PDOException $pdo) {
        exit("PDOException: " . $pdo->getMessage() . "<br><br>");
    }
}

Connection works great, and non-prepared statements work great using it's query method:

protected function query($sql)
{
    try {
        $results = $this->conn->query($sql);
    } catch (PDOException $pdo) {
        exit("PDOException: " . $pdo->getMessage());
    }
    return $results;
}

In Medications, I am trying to protect my data as much as possible, so I'm trying to do prepared statements:

public function __construct()
{
    $this->conn = Database::getInstance();
}

public function edit_med_by_keyID($keyID, $attributes)
{
    try {
        $attributes['keyID'] = (int) $keyID;
        $sql = "UPDATE medications 
                SET `rxID` = :rxID,
                    `name` = :name,
                    `aka` = :aka,
                    `dosage` = :dosage,
                    `instructions` = :instructions,
                    `prescriber` = :prescriber,
                    `specialty` = :specialty,
                    `category` = :category,
                    `type` = :type,
                    `status` = :status,
                    `notes` = :notes
                WHERE `keyID` = :keyID LIMIT 1";
        $this->conn->prepare($sql);
    } catch (PDOException $pdo) {
        exit("PDOException: " . $pdo->getMessage());
    } catch (Exception $ex) {
        exit("Exception: " . $ex->getMessage());
    }

    return;
}

When I try and use this method, it returns the following:

Fatal error: Uncaught Error: Call to undefined method Database::prepare()

Part of me understands that this is because my Database class does not have a prepare method in it. On the other hand, PDO does... PDO::prepare

So my question is - how do I utilize prepared statements from inside a child class of my Database class.

Also, as an FYI - I'm going to be adding more Classes as child classes of Database, which is why I'm trying to do it this way.

I'm also aware there's no ->execute(array()) in there. I'm trying to nail down part one of the issue before I extend it to execute.


Solution

  • Singletons are both global state, which causes difficult-to-debug issues, and locks you into only ever being allowed to have a single DB connection ever. Plus, for some reason you're trying to wrap all the functions that you could possibly call on a PDO object for no reason.

    1. Don't wrap the PDO object. It's already an object. Wrapping it again is like putting a hat on a hat.
    2. Send the PDO object into the object via constructor arguments. [This is called Composition

    Eg:

    class Foo {
      public function __construct(protected \PDO $dbh) {}
    
      public function doThing($args) {
        foreach($args as $arg) {
          $b = new Bar($this->dbh);
          yield $b->something($arg);
        }
      }
    
    }
    
    class Bar {
      public function __construct(protected \PDO $dbh) {}
    
      public function something($arg) {
        $stmt = $this->dbh->prepare(...);
        $stmt->execute([$arg]);
        return $stmt->fetchAll();
      }
    }
    
    $dbh = new PDO(...);
    $f = new Foo($dbh);
    $f->doThing([1,2,3,4]);