PHP OO - How to use a class within another class?

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • hoopy
    New Member
    • Feb 2009
    • 88

    PHP OO - How to use a class within another class?

    Hi,

    Im just learning OO in PHP and not sure the best method to use the functions for example in a database wrapper class within another class for example which handles all user authentication. I have put together this test code of how I am currently implementing it:

    Code:
    <?
    class DB
    {
      public function __construct($host,$user,$pass,$dbname)
      {
        mysql_connect($host,$user,$pass)
          or die(mysql_error());
        mysql_select_db($dbname)
          or die(mysql_error());
      }
      public function qry($sql)
      {
        return mysql_query($sql)
          or die(mysql_error());
      }
    }
    
    class User
    {
      public $user_id;
      public function __construct($user_id)
      {
        $this->user_id = $user_id;
      }
      public function update_user()
      {
        global $db;
        $db->qry(" UPDATE users SET active = 1 
          WHERE user_id = '" . $this->user_id . "' ");
      }
    }
    
    $db   = new DB("localhost","root","","test_database");
    $user = new User(2);
    
    $user->update_user();
    ?>
    Is that use of global considered bad practice? I was browsing the source of PHPBB3 and they use the global method when accessing its database class from other classes however I believe I can instead pass a reference to the object via a param so would that method be better? If someone could give me a few pointers it would be appreciated.

    Cheers.
  • Markus
    Recognized Expert Expert
    • Jun 2007
    • 6092

    #2
    Have a database property for your User class.

    Code:
    class DB {
    
       // ...
    
    }
    
    class User { 
      
        private $_db;
    
        public function User ( ) 
        {
            $this->_db = new DB;
        }
    
    }

    Comment

    • hoopy
      New Member
      • Feb 2009
      • 88

      #3
      Hi Markus,

      Thanks for the response, however I do have a question.

      The __construct() for the DB class opens the connection from the login parameters passed. Therefore with your method I need to have the login details hard coded into the USER class itself rather than in the code which creates the instances.

      Is this the standard practice for using classes within classes? Can you see my problem and advise of a possible workaround?

      Thanks.

      Comment

      • Dormilich
        Recognized Expert Expert
        • Aug 2008
        • 8694

        #4
        a common practice for DB classes is implementing the Singleton pattern (which can also hold the DB access data (maybe coded through constants)

        the User class doesn't need to pass DB data, unless you want to (be able to) connect to different databases.

        Comment

        • hoopy
          New Member
          • Feb 2009
          • 88

          #5
          Thanks Dormilich.

          I checked out about the Singleton pattern, follows some tutorials and came up with this which works well in that it only calls the database connection once and I can use that class within any other classes, here is my example code:

          Code:
          <?
          class DB
          {
            private static $dbInstance;
            
            public function __construct($host,$user,$pass,$dbname)
            {
              echo("Would have connected to " . $host . " DB.<br />");
            }
            
            public static function getInstance($host=null,$user=null,
              $pass=null,$dbname=null)
            {
              if (!self::$dbInstance)
              {
                self::$dbInstance = new DB($host,$user,$pass,$dbname);
              }
              return self::$dbInstance;
            }
            
            public function qry($sql)
            {
              echo("Would have executed: " . $sql . "<br />");
            }
          }
          
          class User
          {
            private $db;
            public function __construct()
            {
              $this->db = DB::getInstance();
            }
            public function SelectUsers()
            {
              $this->db->qry(" select * from users ");
            }
          }
          
          class News
          {
            private $db;
            public function __construct()
            {
              $this->db = DB::getInstance();
            }
            public function SelectNews()
            {
              $this->db->qry("select * from news");
            }
          }
          
          $dbc = DB::getInstance("localhost", "root", "", "test_database");
          $usr = new User();
          $usr->SelectUsers();
          $usr->SelectUsers();
          $nws = new News();
          $nws->SelectNews();
          $usr->SelectUsers();
          ?>
          This will produce the following output:

          Code:
          Would have connected to localhost DB.
          Would have executed: select * from users
          Would have executed: select * from users
          Would have executed: select * from news
          Would have executed: select * from users
          So you can see it only connects once which is great and I dont need to provide DB login details within any of the classes.

          Thanks for everyones help.

          Cheers.

          Comment

          • Dormilich
            Recognized Expert Expert
            • Aug 2008
            • 8694

            #6
            [EDIT] Unfortunately, you got the Singleton Pattern wrong. what about
            Code:
            $a = new DB(…);
            $b = new DB(…);
            ?

            you must leave the __construct() and __clone() methods empty in a Singleton pattern! (OK, you may throw an Exception or leave a note)

            you can also try to pass the DB parameters via static properties.

            Code:
            require "db.config.php";
            
            class DB
            {
              private static $dbInstance = NULL;
              public static $host;
            // or using a default value via constants
            // public static $host = DB_DEFAULT_HOST;
              public static $user;
              public static $pass;
              public static $dbname;
             
              public function __construct()  {  }
             
              public function __clone()  {  }
             
              public static function getInstance()
              {
                if (self::$dbInstance === NULL)
                {
                  self::$dbInstance = new self;
                  self::connect();
                }
                return self::$dbInstance;
              }
            
              private static function connect()
              {
                # do DB connection here
              }
            }
            
            // optional if you use your defaults
            DB::$host = "localhost";
            DB::$user = "****";
            DB::$pass = "****";
            DB::$dbname = "my_db_name";
            $dbc = DB::getInstance();

            Comment

            • hoopy
              New Member
              • Feb 2009
              • 88

              #7
              Dormilich, thanks for that clarification.

              I have implemented what you desribed and its working fine.

              Thank you.

              Comment

              • Dormilich
                Recognized Expert Expert
                • Aug 2008
                • 8694

                #8
                I'm glad I could be of help.

                OOP rulez.

                Originally posted by Dormilich
                (OK, you may throw an Exception or leave a note)
                incorrect, but I noticed that way too late.

                Comment

                • Dormilich
                  Recognized Expert Expert
                  • Aug 2008
                  • 8694

                  #9
                  just for additional information a Singleton like DB class using PDO

                  Code:
                  // used for connecting to MySQL
                  // stores the Prepared Statements
                                               (the interface, so that all DB_User classes 
                                                can safely call the methods)
                  abstract class DB implements DB_connector
                  {
                  	/* PDO instance */
                  	private static $PDO = NULL;
                  
                  	/* collection of Prepared Statements */
                  	private static $PS = array();
                  
                  	/* DB name to set up */
                  	public static $dbname = DB_DEFAULT_NAME;
                  	
                  	/* close DB connection on script end */
                  	function __destruct()
                  	{
                  		self::$PDO = NULL;
                  	}
                  
                  	/* create a single instance of PDO */
                  	public static function connect()
                  	{
                  		if (self::$PDO === NULL)
                  		{
                  			try {
                  				// server, login & password hardly ever change in a project
                  				$dsn = 'mysql:host=' . DB_SERVER . ';dbname=' . self::$dbname;
                  				self::$PDO = new PDO($dsn, DB_USER, DB_PASS);
                  			}
                  			catch (PDOException $pdo)
                  			{
                  				// any kind of error logging*
                  				ErrorLog::logException($pdo);
                  
                  				// throw Exception so you can safely quit the script
                  				$emsg = "MySQL connection failed.";
                  				throw new ErrorException($emsg, 500, 0);
                  			}
                  		}
                  	}
                  
                  	/* save the Prepared Statements. I prefer to have them where the
                  	   PDO object is. just personal preference */
                  	public static function prepare($index, $sql)
                  	{
                  		# check if $index already exists (implement your own way)
                  		
                  		self::connect();
                  
                  		try {
                  			self::$PS[$index] = self::$PDO->prepare($sql);
                  		}
                  		catch (PDOException $pdo)
                  		{
                  			ErrorLog::logException($pdo);
                  			return false;
                  		}
                  		return true;
                  	}
                  
                  	/* since the Prepared Statements are private, a getter method */
                  	public static function getStatement($index)
                  	{
                  		if (isset(self::$PS[$index]))
                  		{
                  			return self::$PS[$index];
                  		}
                  
                  		// do some error handling here
                  	}
                  }
                  
                  * ErrorLog is an Abstract Registry Pattern class that collects caught 
                    Exceptions (and is able to display them later)

                  Comment

                  Working...