I'm working on a flash gaming website. I have two models: Game and User and an intermediate table in which I keep the user's actions as for example: "User 1 likes Game 3".
For performance reasons, I increment likes field in game table too.
I’ve omitted to check if user already likes the game just to keep it simple.
Here are my options:
First version:
$user->like(12345);
class User
{
public function like($game_id)
{
$like = new User_Game();
$like->user_id = $this->id;
$like->game_id = $game_id;
$like->save();
$obj = new Game($game_id);
$obj->likes++;
$obj->save();
}
}
Second version:
$game->like(); // by current user
class Game
{
public function like()
{
$like = new User_Game();
$like->user_id = $_SESSION[‘user_id’];
$like->game_id = $this->id;
$like->save();
$this->likes++;
$this->save();
}
}
To be perfectly honest, I'm not sure if this is the best place for a question such as this. Perhaps codereview is a better fit. All things aside, IMO, neither of the two takes you suggest are "the best approach". But as always, that might be a personal thing.
In my view, the best way to go about OOP, is to push all your data in objects ASAP, and implement a service layer that takes care of operations that require multiple queries, or several objects.
If I may assume you're using an MVC-ish pattern, your controller receives the data. There, you instantiate a Game
object, and set the id to 123456
. You can pass that instance to a service method called fillGameModel(Game $gameInstance)
. This method connects to the DB, and sets all other properties of the Game
object and returns it. Same goes for the User
object. Both of these objects can then be passed to another service method: likeGame(Game $game, User $user)
. That method can take care of the rest.
Personally, I'd go even one step further and use mappers for my DB access, but I'm not going into that now. Here's an example using a service, and a more OO approach:
//controller:
$user = new User();
$user->setId($_SESSION['user_id']);
$game = new Game();
$game->setId(123456);//wherever you get this from
$service = new MainService();
$service->userLikes($game,$user);
//service:
public function userLikes(Game $game, User $user)
{
$user = $this->_completeUser($user);
$game = $this->_completeGame($game);
//insert or update whatever data you need...
}
protected function _completeUser(User $user)
{
$db = $this->_getConnection();//asuming PDO, to keep things simple
$query = 'SELECT * FROM my_db.users WHERE id = ?';
$stmt = $db->prepare($query);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
foreach ($row as $field => $value)
{//this implies getters and setters in your model
$user->{'set'.ucfirst(strtolower($field))}($value);
}
return $user;
}
protected function _completeGame(Game $game)
{
$db = $this->_getConnection();
$query = 'SELECT * FROM my_db.games WHERE id = ?';
$stmt = $db->prepare($query);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
foreach ($row as $field => $value)
{//field id becomes "setId" method, field name "setName" etc...
$game->{'set'.ucfirst(strtolower($field))}($value);
}
return $game;
}
//just for show: a pseudo-overloader method, if your models all share the same
//abstract class.
protected function _completeAny(Model_Abstract $model)
{
$modelName = get_class($model);
if (method_exists($this,'_complete'.$modelName))
{
return $this->{'_complete'.$modelName}($model);
}
throw new Exception('No completion method for '.$modelName.' found');
}
Again, the loops through the resultset could be replaced with a method in an abstract model class that takes an array as argument, and transforms the fieldnames to their corresponding setters. Plenty of room for abstraction, I'd say ;-)