简体   繁体   中英

I'm trying to combine PDO and OOP, and can't get it to work

So I basically have two files. At the end I would have more, but I would like to create a class called DB that would use PDO database operations, and then I would extend this class to make all of my functions for working with the database. So DB class, would then extend to class dbADD , that would have all the add functions for different database tables.

This is called config.php :

<?php

DEFINE ('DBHOST', 'localhost');
DEFINE ('DBUSER', 'REMOVED');
DEFINE ('DBPSW', 'REMOVED');
DEFINE ('DBNAME', 'REMOVED');

class DB {
    public $db;
    private static $instance;   

    public function __constructor(){
        $config ['db'] = array(
        'host'      =>  DBHOST,
        'username'  =>  DBUSER,
        'password'  =>  DBPSW,
        'dbname'    =>  DBNAME,
        );

        $this->db = new PDO('mysql:host ='  . $config['db']['host'] . ';dbname='  .   $config['db']['dbname'],$config['db']['username'],$config['db']['password']) ;
    }

    public static function getInstance()
    {
        if (!isset(self::$instance))
        {
            $object = __CLASS__;
            self::$instance = new $object;
        }
        return self::$instance;
    }

    public function GetArticles ($search){
        $sql = "SELECT `FirstColumn`, `SrcColumn`, `article` FROM `test_table`  WHERE `FirstColumn` = 23";  

        //$dbs = new DB();
        $dbs = DB::getInstance();
        $query = $dbs->db->prepare($sql);
        //$query->bindValue(':search', $search, PDO::PARAM_INT);
        $query->execute();

        while ($row = $query->fetch(PDO::FETCH_OBJ)) {
            // = $row['article'],'</br>';
            $return = $row['article'];
        }   
        return $return;
    }
}
?>

This file is my test file, which is not that important just a testing-ground. Called test.php :

<?php
require_once('app_core/config.php');
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Untitled Document</title>

    <link rel="stylesheet" href="/style/style.css" type="text/css" media="screen" />
</head>

<body>
    <?php
    $db = new DB();
    //echo $db->db;
    //echo $db->GetTestDB();
    //$test = $db->TestThis();
    //print_r($test);
    echo $db->GetArticles('23');
    ?>
</body>
</html>

If it is possible I also have two other concerns: the first question is a matter of securit -- is this a good practice or not? The other question is how do I hide files with this password data, so I can use them but no one can read them?

Ok, you've got a lot going on here, so I'll try to address issues one at a time to make this class behave properly and in an object-oriented manner (instead of a collection of not-entirely-related methods).

First, your constructor:

//  Make these private, will find out why in a moment...
private $db;
// __construct, not __constructor!!
private function __construct() {
    // This whole array doesn't serve any purpose because the constants
    // are defined and available in all scopes
    // while this array is local to the __construct().  
    // Just get rid of it and use the 
    // constants directly in the PDO connection
    //$config ['db'] = array(
    //    'host'          =>      DBHOST,
    //    'username'      =>      DBUSER,
    //    'password'      =>      DBPSW,
    //    'dbname'        =>      DBNAME,
    //);

    // Use some error checking when establishing your connection
    try {
      // Some extra bad whitespace removed around =
      $this->db = new PDO('mysql:host=' . DBHOST . ';dbname=' . DBNAME, DBUSER, DBPSW); 
    } catch (PDOException $e) {
      echo 'Connection failed: ' . $e->getMessage();
    }
}

Next your singleton accessor getInstance().

// No code changes necessary....
public static function getInstance()
{
    if (!isset(self::$instance))
    {
        $object = __CLASS__;
        self::$instance = new $object;
    }
    return self::$instance;
}

Since you have defined a method to access the class as a singleton, the $db property and the __construct() are made private . At no point will you ever call $DB_class-instance = new DB() to instantiate it, or call $DB_class_instance->db to access the connection directly. Instead, you will call DB::getInstance() to access the singleton instance and your methods like GetArticles() to execute queries.

Now onto your querying method:

public function GetArticles ($search){
    // Ok a SQL string, no problem...
    $sql = "SELECT `FirstColumn`, `SrcColumn`, `article` FROM `test_table`  WHERE `FirstColumn` = :search";

    // There's no need for this.  You already defined $db as 
    // a class property, so you should be using $this->db
    // $dbs = DB::getInstance();

    $query = $this->db->prepare($sql);
    // bind the $search input parameter...
    $query->bindParam(':search', $search);

    // Test for success
    if ($query->execute()) {

      $row = $query->fetch(PDO::FETCH_OBJ) {
      // I suppose you know what you want here. If you're only expecting 
      // one article, there's no real need for the while loop.
      // You can just fetch() once.
      $return = $row->article;

      // OR.....

      // However, if you are expecting *multiple* rows, you should be accumulating them
      // into an array like this:
      $return = array();
      while ($row = $query->fetch(PDO::FETCH_OBJ)) {
         // Append to an array
         $return[] = $row->article;
         // OR to get multiple columns returned as an object...
         $return[] = $row;
      }
      return $return;
   }
   else {
     // Query failed, return false or something
     return FALSE;
   }
}

Finally your controller code:

// The constructor is private, so you can't do this
// $db = new DB();
// Instead you need to use getInstance()
$db = DB::getInstance();
// Returns an array, so print_r()
print_r($db->GetArticles('23'));

Since we made the class' $db property private , it cannot be accessed outside the class. Therefore you would need to define querying methods similar to GetArticles() for any other queries you plan to run as well. If you think you will need to build ad-hoc queries that are not class methods sometimes, then you can change it to

public $db

Then, you could do things outside the class like the following instead of having to build a class method to do it. You do still need to call getInstance() however.

$dbs = DB::getInstance();
// Run a query via the PDO connection $dbs->db
$result = $dbs->db->query('SELECT * FROM sometable');

Little style issue:

This won't actually cause a problem since identifiers are case-insensitive, but stylistically it is weird. define() is a function call and is usually used lowercase:

define('DBHOST', 'localhost');
define('DBUSER', 'REMOVED');
define('DBPSW', 'REMOVED');
define('DBNAME', 'REMOVED');

About your file security

As long as your web server is correctly configured, no one else can read the files. Provided the web server sends .php files to the PHP interpreter rather than dumping their contents to the browser, the files are safe. If you are on a shared host and the host does not properly segregate your files from other tenants, that is their problem and the only good solution would be to get a better host.

It is wise, however, to store your sensitive files above your web server's document root directory. Then even a misconfigured web server could not accidentally dump their contents to a client. They are only accessible to PHP via include .

As PDO is already an object, maybe you don't need to create a singleton class for it at all.

Instead, make a generic model class which you pass the PDO object to.

<?php

class Model {
    private $pdo;

    __construct(PDO $pdo) {
        $this->pdo = $pdo
    }

    // generic model methods go here
}

Then you can subclass this and flesh out the functionality for each model that you create.

Usage would be something like:

$PDO = new PDO("mysql:host={DBHOST};dbname={DBNAME}", DBUSER, DBPSW);
$myModel = new MyModel($pdo);
$bob = $myModel->getByName('bob');
$articles = new Articles($pdo);
$recentArticles = $articles->getRecent(new Date());

In regards to security, this article offers some nice general tips, http://www.tuxradar.com/practicalphp/17/0/0 . In fact, the entire guide is quite helpful.

Advices:

  1. If possible, don't use inline SQL, because if you change the scheme of your database, let's say you rename a column from foo to bar, you will have to find all the inline SQLs where foo is used and change that to bar.

  2. Your idea is good, to have a general behavior for different action types, but there is no point of implementing it, because this good idea was implemented before. For instance you can try out Flourish, which is an ORM and can help you a lot.

  3. Don't store passwords in separate files if you can store them in databases, but don't forget to encrypt passwords and to use a salt too to improve the encryption.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM