Somewhat new to OOP - class interaction...?

Not for 'how-to' coding questions but PHP theory instead, this forum is here for those of us who wish to learn about design aspects of programming with PHP.

Moderator: General Moderators

User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Somewhat new to OOP - class interaction...?

Post 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
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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
	}

	// ...

}

?>
Last edited by Maugrim_The_Reaper on Thu May 25, 2006 11:15 am, edited 1 time in total.
User avatar
TheMoose
Forum Contributor
Posts: 351
Joined: Tue May 23, 2006 10:42 am

Post 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
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
Maugrim_The_Reaper
DevNet Master
Posts: 2704
Joined: Tue Nov 02, 2004 5:43 am
Location: Ireland

Post 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.
User avatar
TheMoose
Forum Contributor
Posts: 351
Joined: Tue May 23, 2006 10:42 am

Post 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);
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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?
    }
}
User avatar
TheMoose
Forum Contributor
Posts: 351
Joined: Tue May 23, 2006 10:42 am

Post 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();
    }
}
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post 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?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Post 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.
(#10850)
User avatar
TheMoose
Forum Contributor
Posts: 351
Joined: Tue May 23, 2006 10:42 am

Post 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
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

makes perfect sense... thank you for explaining that to me.
User avatar
RobertGonzalez
Site Administrator
Posts: 14293
Joined: Tue Sep 09, 2003 6:04 pm
Location: Fremont, CA, USA

Post 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).
User avatar
Luke
The Ninja Space Mod
Posts: 6424
Joined: Fri Aug 05, 2005 1:53 pm
Location: Paradise, CA

Post by Luke »

yes... definately a good question...
Post Reply