views:

454

answers:

3

So I'm trying to adopt good object oriented programming techniques with PHP. Most (read all) of my projects involve a MySQL database. My immediate problem deals with the users model I need to develop.

My current project has Agents and Leads. Both Agents and Leads are Users with much of the same information. So, obviously, I want a class Agents and a class Leads to extend a common class Users. Now, my question is as follows:

How should the SQL best be handled for loading these objects? I don't want to execute multiple SQL statements when I instantiate an Agent or a Lead. However, logic tells me that when the Users constructor is fired, it should execute a SQL statement to load the common information between Agents and Leads (username, password, email, contact information, etc). Logic also tells me that when the Agents or Leads constructor is fired, I want to execute SQL to load the data unique to the Agents or Leads class....But, again, logic also tells me that it's a bad idea to execute 2 SQL statements every time I need an Agent or Lead (as there may be thousands of each).

I've tried searching for examples of how this is generally handled with no success...Perhaps I'm just searching for the wrong thing?

+5  A: 

You basically have three approaches to this problem (one of which I'll eliminate immediately):

  1. One table per class (this is the one I'll eliminate);
  2. A record type with optional columns; and
  3. A record type with a child table depending on type that you join to.

For simplicity I generally recommend (2). So once you have your table:

CREATE TABLE users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  type VARCHAR(10),
  name VARCHAR(100)
);

where type can be 'AGENT' or 'LEAD' (for example). Alternatively you can use one character type codes. You can then start to fill in the blanks with the object model:

  • You have a User parent class;
  • You have two child classes: Lead and Agent;
  • Those children have a fixed type.

and it should fall into place quite easily.

As for how to load in one statement, I would use some kind of factory. Assuming these barebones classes:

class User {
  private $name;
  private $type;
  protected __construct($query) {
    $this->type = $query['type'];
    $this->name = $query['name'];
  }
  ...
}

class Agent {
  private $agency;
  public __construct($query) {
    parent::constructor($query);
    $this->agency = $query['agency'];
  }
  ...
}

class Lead {
  public __consruct($query) {
    parent::constructor($query);
  }
  ...
}

a factory could look like this:

public function loadUserById($id) {
  $id = mysql_real_escape_string($id);  // just in case
  $sql = "SELECT * FROM user WHERE id = $id";
  $query = mysql_query($sql);
  if (!query) {
    die("Error executing $sql - " . mysql_error());
  }
  if ($query['type'] == 'AGENT') {
    return new Agent($query);
  } else if ($query['type'] == 'LEAD') {
    return new Lead($query);
  } else {
    die("Unknown user type '$query[type]'");
  }
}

Alternatively, you could have the factory method be a static method on, say, the User class and/or use a lookup table for the types to classes.

Perhaps polluting the classes with the query result resource like that is a questionable design in the strictest OO sense, but it's simple and it works.

cletus
I really like this idea, I think. But the thing that nags at me the most is where to execute the SQL to load all the data...I can't put it in the User class because depending on the type, I'll need different SQL. I don't want to put all of it in the Agent and Lead class because much of the code would be the same and I want to avoid copy and paste...Ideally, I would like the User class to be able to load the common information and the Agent/Lead classes to load the unique information......but then I'm executing 2 SQL statements each time I instantiate an Agent or Lead, which is undesirable.....
SpaDusA
You can do it in 1 SQL statement. Tell me though, what are you using to load a particular user? What do you pass in? An ID? Something else?
cletus
I have 2 load functions: loadAll and loadById. Every Lead/Agent has a firstname, lastname, username, password, 2 phone numbers, email address. A Lead has an area of interest and a 'how did you hear about us'. An Agent has a coverage area, and what languages they speak.
SpaDusA
So I'm liking this a lot. I'm thinking about having a Users table with all the common data inside it. An Agents table with agent-specific data and a Leads table with lead-specific data. The Agents and Leads tables would have foreign keys to link back to the Users table. Then the factory function would select * out of all 3 tables in the database (I'm leaving the SQL out to make this easier to read) and pass the resulting recordset to the respective child class? No need for Agents or Leads to add "extends User" ???
SpaDusA
We are working on creating a proprietary framework-ish system to make projects more consistent. What would be a better, more strictly OO approach to avoid polluting the classes with the query result?
SpaDusA
If there is a common table I would still have Agent and Lead extend User. You'll probably find that a useful attribute in code (where code either expects or returns a User object that can then be any of the subtypes). More useful in strict typing languages but still useful in PHP.
cletus
As for separate tables for each subclass that are joined to a common parent table, that is a common design ((3) from my list above). You can still get all the data out in one query by doing left outer joins to each child table but this has more scalability issues than nullable columns does (imagine 100 types so 100 left outer joins).
cletus
A: 

Will you ever have a user that's not a Lead or Agent? Does that class really need to pull data from the database at all?

If it does, why not pull the SQL query into a function you can override when you create the child class.

acrosman
This is actually the recommendation from the other guys on the team. The only reason I didn't want to put the SQL into a function that I could override is because much of the SQL is the same across the 3 classes; I didn't want to have to copy and paste the functions...I would much rather inherit the SQL, so to speak.....Is this clear at all?
SpaDusA
A: 

Could you not inherit say a skeleton of the SQL, then use a function in each sub-class to complete the query based on its needs?

Using a really basic example:

<?php
//our query which could be defined in superclass
$query = "SELECT :field FROM :table WHERE :condition";

//in our subclass
$field = "user, password, email";
$table = "agent";
$condition = "name = 'jim'";

$dbh->prepare($query);
$sth->bindParam(':field', $field);
$sth->bindParam....;//etc

$sth->execute();
?>

As you can see my example isn't amazing, but should allow you to see what I am getting at. If your query is very similar between subclasses then I think my suggestion could work.

Obviously it will need some tweaking but it is probably the approach I would take.

Peter Spain