Page 1 of 3

Somewhat new to OOP - class interaction...?

Posted: Thu May 25, 2006 10:57 am
by Luke
I am sort of a OOP beginner...

My question is... how do people generally make their classes interact. Let's say I have a database functionality class that does things like accepts queries and returns results, and I have another class that needs information from a database... is it proper to call a class from within a class, or is it generally a better idea to have a seperate file that does all of the interaction?

this question barely makes sense

Posted: Thu May 25, 2006 11:12 am
by Maugrim_The_Reaper
Look up the three Design Patterns mentioned in the topic title - viewtopic.php?t=48945

I'm assuming you are referring to injecting a class dependency, where one class needs others in order to do something useful. You can either pass an instance of the required objects into your object's constructor, or grab them from the objects constructor using some sort of Singleton (either of the objects needed, or something else which knows where to get them), etc. An example with the linked to ServiceLocator might use for a simple News class:

Code: Select all

<?php

class News {

	private $db = false; // ADOdb connection object (database abstraction)

	public function __construct()
	{
		// include database connection (this class is dependent on ServiceLocator to get other object instances)
		$sl = ServiceLocator::getInstance();
		$this->db = $sl->getService('DatabaseAbstraction');
	}

	public function getNewsById($newsId) {
		$result = $this->db->Execute('SELECT * FROM table_news WHERE news_id = ?', array($newsId));
		if(!$result)
		{
			trigger_error($this->db->ErrorMsg(), E_USER_ERROR);
		}
		return $result->fields; 
	}

	// ...

}

?>
Am on the right track in what you were asking?

I usually prefer using something similar to above (though usually a Registry) since you reduce the classes dependency to one object (whih can fetch instances of what you need) rather than grabbing each object individually (which makes for either a hard to change constructor, or a long constructor parameter list).

You can also do (as a parameter):

Code: Select all

<?php

class News {

	private $db = false;

	public function __construct($db)
	{
		$this->db = $db
	}

	// ...

}

?>

Posted: Thu May 25, 2006 11:13 am
by TheMoose
If I'm understanding your quesion properly, you mean something along the lines of Class 1 instantiating Class 2 and using member objects/functions of it? I find no problem with that. This sort of thing falls under the class inheritance/extension principle of programming. I don't think you'll run into the need for extensions if your using a database class alongside a class that needs to interact with a database. It's definitely a sound way to have the database class have built in error-protection/checking for modularity for later use.

And if it is improper, well, uh oh, because that's the way I do a lot of my projects :P

Posted: Thu May 25, 2006 11:21 am
by Luke
you guys actually know exactly what I was asking... man.. you're good!

Code: Select all

class orderUpload{
	var $products = array();
	var $order_id;
	function orderUpload($order_id, $dbh){
		// Class constructor
		// Takes an order id as it's first argument and a database handle as its second.
		// Constructer gets products in order and applies them to the property $this->products
		
		$this->order_id = $order_id;
		$this->dbh = $dbh; // This is the database instance
	
		// Get id and quantities of products in order and assign them to $products array
		$sql = "SELECT product_id, code, quantity FROM s01_OrderItems WHERE order_id = " . $this->order_id;
		if($r = $this->dbh->select($sql)){ // This line is using the instance
			while($row = $this->dbh->get_row($r, "MYSQL_ASSOC")){ // This line is using the instance
				$this->products[] = $row;
			}
		}
		else{
			// failed to run sql query
			echo "error";
		}
	}
	function checkPhotoNeeded($product_id){
		// This method takes a product id as an argument and returns true if it finds that this product needs a photo.
		// This method makes use of a table in the database called s01_ProdValues
		
		$sql = "SELECT value FROM s01_CFM_ProdValues WHERE product_id = " .$product_id;
		strtolower($this->dbh->select_one($sql)) == "yes" ? return true : return false; // This line is using the dbh instance
	}
}
Do you see what I am trying to accomplish? I just don't want to have a class that does nothing but return queries... seems a waste of space

Posted: Thu May 25, 2006 11:23 am
by Maugrim_The_Reaper
If I'm understanding your quesion properly, you mean something along the lines of Class 1 instantiating Class 2 and using member objects/functions of it? I find no problem with that.
That gets very hard to maintain as the source code grows. What if you change a class name? Go back and edit the umpteen classes using the older class name to create an instance? Using some sort of class to package instances is easier to manage - I can just change the DatabaseAbstraction reference to point to another Abstraction class entirely from a single point - where ever I called the ServiceLocator's registerService('DatabaseAbstraction', $adodb)... Maybe not a great example (any app likely has only the one database objects in use)...but the principle is the same.

In general I don't think classes should be responsible for instantiating other classes they need unless it's essential or obviously makes good sense.

Posted: Thu May 25, 2006 11:31 am
by Maugrim_The_Reaper

Code: Select all

class orderUpload{
        var $products = array();
        var $order_id;
        function orderUpload($order_id, $dbh){
Looks fine in practice. :) Unless it gets more complex and you start pushing lots of objects in as constructor parameters, or end up passing the $dbh object across several layers, then you can probably do without a Registry. I would make one small change assuming you are coding for PHP4 (which seems likely). Use:

Code: Select all

function orderUpload($order_id, &$dbh){
This ensures the $dbh objects is passed by reference in PHP4, otherwise the object you receive will be a copy. Many copies doing the same things equals lots of wasted memory. In PHP5, all objects are automatically passed by reference - PHP4 needs those ampersands attached to object parameters.

Posted: Thu May 25, 2006 11:42 am
by TheMoose
Maugrim_The_Reaper wrote:That gets very hard to maintain as the source code grows. What if you change a class name? Go back and edit the umpteen classes using the older class name to create an instance? Using some sort of class to package instances is easier to manage - I can just change the DatabaseAbstraction reference to point to another Abstraction class entirely from a single point - where ever I called the ServiceLocator's registerService('DatabaseAbstraction', $adodb)... Maybe not a great example (any app likely has only the one database objects in use)...but the principle is the same.

In general I don't think classes should be responsible for instantiating other classes they need unless it's essential or obviously makes good sense.
Well, the way I do my projects keeps actual code maintenance at a very high priority. Before I even start programming, I lay out everything that I know, and everything that is required for the project, and I write down all the classes, objects, functions, pre/post conditions, etc for just about everything (pseudo code sometimes if I think of it). This way I have set class names before I've even written a single class.

And yes, instantiation of the actual object is best to be handled outside of classes. The wording was chosen pretty poorly on my part, when I was really just thinking of member objects having an instance of another class (and on a few projects of mine, the container class creates a new instance of an object, when the old object needs to be recreated for whatever reason), ie:

Code: Select all

class Liquid {
    function __construct($a) {
      $this->type = $a;
    }
}

class Cup {
   function __contstruct($liquid) {
     $this->contents = $liquid;
   }
}
$water = new Liquid("ice");
$mycup = new Cup($water);

Posted: Thu May 25, 2006 11:51 am
by Luke
Maugrim_The_Reaper wrote:

Code: Select all

class orderUpload{
        var $products = array();
        var $order_id;
        function orderUpload($order_id, $dbh){
Looks fine in practice. :) Unless it gets more complex and you start pushing lots of objects in as constructor parameters, or end up passing the $dbh object across several layers, then you can probably do without a Registry. I would make one small change assuming you are coding for PHP4 (which seems likely). Use:

Code: Select all

function orderUpload($order_id, &$dbh){
This ensures the $dbh objects is passed by reference in PHP4, otherwise the object you receive will be a copy. Many copies doing the same things equals lots of wasted memory. In PHP5, all objects are automatically passed by reference - PHP4 needs those ampersands attached to object parameters.
Thank you... very helpful :D

Can somebody elaborate on this methodology a little?

Code: Select all

#  Using non-instantiation:

class a {
  Function foo() {
     echo 'hello';
  }
}

class b{
    var $a;
    function b() {
      a::foo(); // What exactly does :: do?
    }
}

Posted: Thu May 25, 2006 12:17 pm
by TheMoose
The Ninja Space Goat wrote:Can somebody elaborate on this methodology a little?

Code: Select all

#  Using non-instantiation:

class a {
  Function foo() {
     echo 'hello';
  }
}

class b{
    var $a;
    function b() {
      a::foo(); // What exactly does :: do?
    }
}
It calls the member function foo() of class a. It works because the function foo() does not require an active instance of the object (class a in this example) for it to return a value or process something. It would not work for the following because foo() is trying to call an object of class a, and in this case there is no active instance of a to get the value for.

Code: Select all

class a {
  Function foo() {
     echo $this->something;
  }
}

class b{
    var $a;
    function b() {
      a::foo();
    }
}

Posted: Thu May 25, 2006 12:40 pm
by Luke
hmm... I think I get it...

it works in the first one because there is no need for the class to be instantiated, but not the second because the class would have to be called previously for $this->something to be set?

Close?

Posted: Thu May 25, 2006 12:54 pm
by Christopher

Code: Select all

class orderUpload{
        var dbh;
	var $products = array();
	var $order_id;

        function orderUpload($dbh){
                 $this->dbh = $dbh; // This is the database instance
        }

	function orderUpload($order_id){
		// Class constructor
		// Takes an order id as it's first argument and a database handle as its second.
		// Constructer gets products in order and applies them to the property $this->products
		
		$this->order_id = $order_id;

	....
}
he Ninja Space Goat wrote:Do you see what I am trying to accomplish? I just don't want to have a class that does nothing but return queries... seems a waste of space
Actually the goal as to have a lot of small, clean, focused classes. A class should be useful once initialized (hence passing he $db to the constructor) and be responsible for doing one thing well. If more needs to be done then extend or composite.

Posted: Thu May 25, 2006 12:54 pm
by TheMoose
The Ninja Space Goat wrote:hmm... I think I get it...

it works in the first one because there is no need for the class to be instantiated, but not the second because the class would have to be called previously for $this->something to be set?

Close?
Yep, sort of. In the second one, when you try to reference $this, it will return an error because $this is trying to reference an object that was never created.

If you did a "new a()", and did a->foo() using the second method, it would work but print nothing because $this->something is a null value. Whereas if you did not create a new a object, it would throw an error because there is no object to try to even call $this->something for.

I'm so bad at explanations :P

So be careful, because $this doesn't exist yet! :D

Posted: Thu May 25, 2006 12:57 pm
by Luke
makes perfect sense... thank you for explaining that to me.

Posted: Thu May 25, 2006 12:59 pm
by RobertGonzalez
OK, so I have a question about this. I was told on these forums (somewhere) that globalizing an object/var for use in a class was not the right thing to do. What I have done is something along this lines of...

Code: Select all

<?php
class MyClass
{
    function MyClass()
    {
        global $db; // This is my DBAL object
        /* Do something here using the $db object */
    }
}
?>
So should I be using a reference to the $db object instead of using a global? I have wondered about this for a bit and I think this is a good place to pose the question (seeing as Ninja's is somewhat similar).

Posted: Thu May 25, 2006 1:54 pm
by Luke
yes... definately a good question...