在不同php版本中实现model层重复代码的去除

问题

MVC架构中的model层主要负责和数据直接交互,如果没有使用ORM框架,那么很多原子操作(CRUD等)需要自己实现。一般来说一个系统会有多个Model,每一个Model都需要重复实现这些原子操作,而这些代码往往除了表名外都是一样的:

class User {
  public static function findById ( $id ) {
    return DBProxy::getDb( 'user' )->findOne( array( '_id' => new MongoId( $id ) ) );
  }
}
class Video {
  public static function findById ( $id ) {
    return DBProxy::getDb( 'video' )->findOne( array( '_id' => new MongoId( $id ) ) );
  }
}

当我们增加一个Model类的时候,比如Article,就要从其他类复制这些原子操作(比如find、findOne、findById、update、save、count等等)过来。如果有一个方法需要变更,比如增加一个参数或者添加log日志,就得修改所有Model类中的同名方法。这实在是一件很痛苦很无奈的事情。

《重构》一书告诉我们,重复的代码是坏味道,那么我们该怎么改良呢?

 PHP5.2下的解决方案

首先想到的方案是,让所有Model类都继承一个父类,把重复的方法都提到父类中让子类自动继承,在方法的实现中引用子类中声明的表名。

class Model {
  public static function findById ( $id ) {}
}
class User extends Model {
  public static $collection = 'user';
}
class Video extends Model {
  public static $collection = 'video';
}

但是经过试验发现,调用继承自父类的静态方法时,无法取得子类名以及子类中的属性。

1、调用静态方法时没有Object上下文,$this不能使用
2、父类方法中使用self::$var取得的是父类的属性而不是子类的属性
3、父类方法中使用__CLASS__获取到的是父类而不是子类的名称

结果只能用很别扭的方式来实现:

class Model {
  public static function findById ( $collection, $id ) {}
    return DBProxy::getDb( $collection )->findOne( array( '_id' => new MongoId( $id ) ) );
  }
class User extends Model {
  public static $collection = 'user';
  public static function findById ( $id ) {
    parent::findById( self::$collection, $id );
  }
}
class Video extends Model {
  public static $collection = 'video';
  public static function findById ( $id ) {
    parent::findById( self::$collection, $id );
  }
}

这样虽然把实现的细节提炼到了父类,但每个子类还需要对每个方法进行声明。可维护性是有所提高,但每个子类中仍然存在重复的代码。有没有更好的解决方案呢?

PHP5.3下的解决方案

PHP5.3提供了一个新特性“后期静态绑定”,可以在调用继承自父类的静态方法时,通过static::$var的形式取得子类的属性,或者通过static::method()的形式调用子类的放非啊。这下问题就得到了完美的解决:

class Model {
  public static function findById ( $id ) {
    return DBProxy::getDb( static::$collection )->findOne( array( '_id' => new MongoId( $id ) ) );
  }
}
class User extends Model {
  public static $collection = 'user';
}
class Video extends Model {
  public static $collection = 'video';
}

除了一行对表名的声明外,完全没有重复的代码,这就是我想要的理想结果,目前准备在项目中使用。如果你连表名都懒得改,还可以这样:

class User extends Model {
  public static $collection = __CLASS__;
}

PHP5.4下的解决方案

PHP5.4增加的特性traits可以将一些方法提炼出来,在需要的类中用use来引用。相当于可以在class的定义中include了(你别说,天真的我还真试过)。

trait CRUD {
  public static function findById ( $id ) {}
    return DBProxy::getDb( self::$collection )->findOne( array( '_id' => new MongoId( $id ) ) );
  }
}
class User {
  public static $collection = 'user';
  use CRUD;
}

这个方案的好处是不用引入父类和继承,而且可以更加灵活,一个方法可以在不同的类中引用,而无需继承特定的类。问题是5.4作为新版本,在生产环境中部署还不是很放心。

此条目发表在 IT技术, PHP 分类目录。将固定链接加入收藏夹。