tags:

views:

200

answers:

8

I'm relatively new using OOP in PHP. It's helped immensely in the organization and maintenance of my code, but I'd like to get better at designing my classes and using OOP as efficiently as I can. I've read the Gang of Four Design Patterns book, but still need some help. After building a few small apps, here's one thing I keep running across.

Let's say I'm building an application that keeps track of enrollment information for a school.

The way I would currently approach this is to have a class called student, and methods within that class for CRUD on an individual student's record. It seems logical that I would have a constructor method for this class that took the student_id as an argument, so I could reference it from within the object for all of those different CRUD operations.

But then, as I continue building the app, I run across situations where I need to run queries that return multiple students. For instance, something like, get_all_students_from_grade($grade), get_dropdown_of_all_students(), etc. These methods don't apply to just one student, so it seems odd that I would have them as methods in my student class, since I instantiated the object with one student_id in mind. Obviously I can make it work this way, but it seems like I'm 'doing it wrong.' What is the best way to approach this?

A: 

You should make them class methods on Student.

Ignacio Vazquez-Abrams
I don't understand the downvotes on this one.
Vulcan Eager
+4  A: 

Separate the student class (which is a domain class) from the operations on it (the business logic or data access, depending on the case) like:

  • student - the domain object contains only data
  • student_service or student_dao (Data Access Object) - performs operations

This is sometimes considered as breaking the encapsulation, but it is an accepted best practice.

Here's more information on the matter. It provides more drawbacks from OOP point of view than the breaking of encapsulation. So even though it appears to be an accepted practice, it is not quite OOP.

Bozho
This sounds like the simplest way to approach it. Can you describe / provide a link that described the concept of a "domain" class / object? I couldn't find anything with a quick Google.
Matthew
@Matthew : It's well described in the Martin Fowler book (Applying UML and Patterns) but you can have an idea here :http://martinfowler.com/eaaCatalog/domainModel.htmland here http://en.wikipedia.org/wiki/Domain_model
wj
@Matthew : If you want object oriented interface for SQL tables, then I recommend using something like Active Records, etc. Controller classes can use these objects for CRUD operations (they also support manual SQL queries).The way you are thinking is correct. I just think you shouldn't invent the wheel again, but for all non database operations you should use the approach @Bozho is talking about. On another matter, quick question from me; Why don't people use static class methods more for controller classes?
Kristinn Örn Sigurðsson
+4  A: 

Break it into two classes:

  1. student
  2. student_repository

Your student class knows nothing about how it is stored relationally.

$students = student_repository.get_all_students_from_grade($grade)
ChaosPandion
A: 

You could go with the factory method and have the factory decide what the new student's ID should be.

Of course this factory would have to read a database to see where to start the index at (based upon how many students are in your database) and you'd have to decide on what to do about deleted students (if that's a possibility).

As for the methods you described, I don't see why you have to include those in your student class. You could but it should be a static method and not a member method.

bobber205
+1  A: 

I don't pretend to know "the best" way, but it may help to approach the problem differently. Instead of making one class to represent an individual student, you could make the class represent a data interface between your app and the database.

This class would know how to retrieve a bunch (possibly one) of student rows from the db, cache them in a local array, allow the app to browse through the cached records, allow modifications on the cached records, and when done, write the cached modifications back to the db (by generating SQL to account for the changes).

This way, you avoid firing a single SQL stateement for each change (you still work with a set of rows instead) and at the same time, offer access to individual objects (by maintaining an index to the current location in the cache, and allow this "pointer" to be advanced by the app, as it calls methods of your class)

Roland Bouman
A: 

Like Neil said in his comment (not sure why we didn't make it an answer), you should probably have Course and School classes as well. You could have school-specific methods (getting all students in a given grade, with a given number of days absent, etc) in School and course-specific methods (all students in a course with a certain grade, etc) in the Course class.

You are correct in thinking that in standard CRUD you don't want to give a class that represents an individual (i.e. the Student class) the responsibility of loading multiples of itself. On the other hand, if you were going for more of an ActiveRecord style of data loading (which is what Ruby on Rails employs), then you would actually make all the Student loader methods static methods on the Student class itself. It just depends on how you want to style it, which depends in part on how complex your data model is going to become.

Marc W
+1  A: 

There is always a starting point. In your case it would be WHAT you are getting the students from (i.e. school, class, etc..).

$class = new Model_Class;

$students = $class->students;

foreach($students as $student)
{
    print $student->name. ' is in class '. $class->name;
}
Xeoncross
+1  A: 

I've come accross this same problem, I'm guessing your using MySQL? this is one of the common OOP design challenges becuase SQL has a tendency to flatten everything.

I've solved this by doing the following

1.) make a class that has three forms of instantiation,

one where it's new

$myStudent = new $Student();

another where you know the id but need ids data

$myStudent = new $Student($student_id);

and another where you already have it's data in an associative array

$data = array('id'=13,'name' => 'studentname', 'major' => 'compsci');
$myStudent = new $Student($data['id'], $data);

This allows you to make a factory class that can run a query from mysql, get an associative array of the data and then create instances of student from that array data without hitting the database for each instance of student.

here is the constructor for such a class:

public function __construct($id=FALSE, $data=FALSE)
{
    if(!$id) $this->is_new = true;
    else if($id && !$data) $this->get_data_from_db($id);
    else if($id && $data) $this->set_data($data);

}
Fire Crow
That's an interesting approach, but wouldn't it be easier to work with the class if you always know what to expect? If I see the object in the code, I wouldn't be able to tell what 'kind' of student object it is without finding the line where it was instantiated.
Matthew
I'm not sure I understand your question, in the above example they are all the same kind, oh I forgot to show optional arguments, I'll add that to my explanation.
Fire Crow
I see what you're saying now. But in all three examples, the class is instantiated as one student, and the problem I was having was separating out CRUD activity on one student from methods that involve multiple students.
Matthew
take a closer look at this "This allows you to make a factory class that can run a query from mysql..." part of my answer
Fire Crow