Thinking about MVC models
Posted: Fri May 19, 2006 3:44 pm
I took my stab at article model.
Feels clumsy. Bunch of static methods that return arrays are so much simpler. In fact, I decided to use a bunch of static methods for the time being. Two main problems with OO approach are that I need to synchronize object with database in at least 3 different ways (create, update, read) and that I want to avoid redundant typing, redundant quesries and redundant method calls. Simple actions should be coded simple.
Some people do it like this:
$o = new Article();
$article->setX($x);
$article->setY($y);
$article->create();
Lots of redundant typing and methods calls.
Other way:
$id = Article::create(array('x'=>$x, 'y'=>$y));
IMO better, but later I will need to update the same article in some way.
So I do something like this, right?
$article = Article::get($id); //query
$article->bla = 11;
$article->update(); //query
But that's 1 extra query. Not lethal, but it feels wrong.
Another possibility:
$article = new Article(); //no query
$article->bla = 11;
$article->mergeWith($id); //query
The problem is that good object should represent something.
Almost the same thing:
$correction = Article::correctionsFor($id); //no query
$correction->bla = 11;
$correction->perform(); //query
Do I need to create separate ArticleCorrection class? Smells of incorrectly done Java program.
And another one:
$correct = array('bla' => 11);
Article::update($id, $correct);
Looks okay, but that's not object-oriented.
The "right" way:
$article = new Article($id); //no query
$article->bla = 11; //no query, but the class notes that article with creatain $id is updated
//...
$article->__destruct(); //that's where we do cleanup, and save article
//or
$someOtheAttr = $article->someOtheAttr; //article updates db, then reads db to sychronize it's attributes
The only problem with this approach is that class will be bloated, and execution flow would be quite complex. This is how I would do it in Java, but we're speaking about PHP.
Any ideas how to do it "properly"? Am I overanalyzing insignificant problem?
Code: Select all
<?php
class Article(){
static protected $_statuses = array('', 'deleted', 'archived', 'updatedByEditor', 'updatedByAuthor', 'published', 'hidden');
public __construct($attrs = array(), $save = FALSE){
foreach ($attrs as $name => $value) {
$this->attrs[$name] = $value;
}
if ($save) {
$this->save();
}
}
function save(){
if (isset($this->id)) {
$this->update();
} else {
$this->create();
}
}
function update(){
$attrs = array();
foreach ($this as $name => $var) {
if ($name{0} != '_') { //underscore denotes "system" attributes
$attrs[$name] = $var;
}
}
unset($attrs['id']);
self::checkId($this->id);
if (isset($attrs['category'])) {
self::checkCategory($attrs['category']);
}
if (isset($attrs['status'])) {
self::checkStatus($attrs['status']);
}
if (isset($attrs['title'])) {
self::checkTitle($attrs['title']);
}
if (isset($attrs['preface'])) {
self::checkPreface($attrs['preface']);
}
if (isset($attrs['text'])) {
self::checkText($attrs['text']);
}
if (isset($attrs['time'])) {
$attrs['time'] = time2iso($attrs['time']);
}
if (isset($attrs['info'])) {
$attrs['info'] = Ori::fold($attrs['info']);
}
$set = QMaster::makeUpdate($attrs);
$query = "UPDATE articles SET $set
WHERE `id` = '$id'";
$result = mysql_query($query);
if (!$result) {
throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
}
if (mysql_affected_rows() < 0) {
return FALSE;
}
return TRUE;
}
function create(){
$attrs = array();
foreach ($this as $name => $var) {
if ($name{0} != '_') {
$attrs[$name] = $var;
}
}
self::checkCategory($attrs['category']);
self::checkStatus($attrs['status']);
self::checkTitle($attrs['title']);
self::checkPreface($attrs['preface']);
self::checkText($attrs['text']);
$attrs['time'] = time2iso($attrs['time']);
$attrs['info'] = Ori::fold($attrs['info']);
list($names, $values) = QMaster::makeInsert($attrs);
$query = "INSERT INTO `articles` ($names) VALUES ($values)";
$result = mysql_query($query);
if (!$result) {
throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
}
if ($this->attrs['id'] == 0) {
return FALSE;
}
$this->id = mysql_insert_id();
return $this->id;
}
static function checkId($value){
if ((int) $value < 1) {
throw new UserException('Invalid id');
}
}
static function checkCategory($value){
if ((int) $value < 0) {
throw new UserException('Invalid category');
}
}
static function checkStatus($value){
if (!in_array($value, self::$_statuses)) {
throw new UserException('Invalid status');
}
}
static function checkTitle($value){
if (strlen($value) > 100 || empty($value)
|| strpos($value, "\n") !== FALSE) {
throw new UserException('Invalid title');
}
}
static function checkPreface($value){
if (strlen($value) > 3000) {
throw new UserException('Invalid preface');
}
}
static function checkText($value){
if (strlen($value) > 200000) {
throw new UserException('Invalid text');
}
}
static function get($id){
self::checkId($id);
$query = "SELECT * FROM `articles` WHERE `id` = '$id'";
$result = mysql_query($query);
if (!$result) {
throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
}
$article = mysql_fetch_assoc($result);
if (!$article) {
return NULL;
}
$article['time'] = iso2unix();
$article['info'] = Ori::unfold($article['info']);
return new Article($article);
}
static function getPage($pageSize, $pageNum = 0, $where = ''){
$pageSize = (int) $pageSize;
if ($pageSize < 1) {
throw new Exception("Invalid page size");
}
$offset = $pageNum * $pageSize;
$query = "SELECT * FROM `articles`";
if (!empty($where)) {
$query .= " WHERE $where";
}
$query .= " LIMIT $pageSize";
if ($offset > 0) {
$query .= " OFFSET $offset";
}
$result = mysql_query($query);
if (!$result) {
throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
}
$articles = array();
while ($article = mysql_fetch_assoc($result)) {
$article['time'] = iso2unix();
$article['info'] = Ori::unfold($article['info']);
$articles[] = new Article($article);
}
return $articles;
}
static function delete($id){
self::checkId($id);
$query = "DELETE FROM `articles`
WHERE `id` = '$id'";
$result = mysql_query($query);
if (!$result) {
throw new Exception("Invalid query: [<i>$query</i>] ".mysql_error());
}
if (mysql_affected_rows() < 0) {
return FALSE;
} else {
return TRUE;
}
}
}
?>Some people do it like this:
$o = new Article();
$article->setX($x);
$article->setY($y);
$article->create();
Lots of redundant typing and methods calls.
Other way:
$id = Article::create(array('x'=>$x, 'y'=>$y));
IMO better, but later I will need to update the same article in some way.
So I do something like this, right?
$article = Article::get($id); //query
$article->bla = 11;
$article->update(); //query
But that's 1 extra query. Not lethal, but it feels wrong.
Another possibility:
$article = new Article(); //no query
$article->bla = 11;
$article->mergeWith($id); //query
The problem is that good object should represent something.
Almost the same thing:
$correction = Article::correctionsFor($id); //no query
$correction->bla = 11;
$correction->perform(); //query
Do I need to create separate ArticleCorrection class? Smells of incorrectly done Java program.
And another one:
$correct = array('bla' => 11);
Article::update($id, $correct);
Looks okay, but that's not object-oriented.
The "right" way:
$article = new Article($id); //no query
$article->bla = 11; //no query, but the class notes that article with creatain $id is updated
//...
$article->__destruct(); //that's where we do cleanup, and save article
//or
$someOtheAttr = $article->someOtheAttr; //article updates db, then reads db to sychronize it's attributes
The only problem with this approach is that class will be bloated, and execution flow would be quite complex. This is how I would do it in Java, but we're speaking about PHP.
Any ideas how to do it "properly"? Am I overanalyzing insignificant problem?