【问题标题】:Opposite To PDO::FETCH_CLASS与 PDO::FETCH_CLASS 相反
【发布时间】:2017-07-30 03:34:53
【问题描述】:

所以我使用 PDO::FETCH_CLASS 来创建一个新对象,用数据库中同名列中的值填充属性:

$db = Connection::get();
$sql = $db->prepare("SELECT * FROM table WHERE id = :id");
$sql->bindParam(":id", "1");
$sql->setFetchMode(PDO::FETCH_CLASS|PDO::FETCH_PROPS_LATE, 'table');
$sql->execute();
$newobj = $sql->fetch();

将对象的属性插入到表中对应的列中是否有相反的方法,以节省键入整个 bindParam() 负载和长 SQL 查询?

非常感谢

【问题讨论】:

  • 是的,您可以在execute() 方法中传递一个数组,而不是使用bindParam()s。

标签: php mysql object pdo


【解决方案1】:

这是一件很有趣的事

  • 需要注意的是,这将获取类中的所有public 属性并尝试将它们用作查询的一部分。如果您确实需要,可以对它们进行更多过滤。

我们将使用所谓的反射。

这是一把双刃剑。

一方面,我们可以把它放在一个基类中,并用你所有的对象扩展它,它对他们来说就很好了。如果以后您在表格中添加一个字段,同样的交易您不必更改任何内容,它就可以工作。

另一方面,处理它需要一点时间,而不是把它写成一个字符串。其中一些问题可以通过在protected static properties 中缓存查询和缓存属性本身(但不是值)来解决

我将从创建一个抽象类开始。这是解决此问题的一种非常灵活的方法,并且可以重复使用 - 注意:我只测试了 Insert 方法,如果其他部分有任何错误,请见谅。

  abstract class BaseObject
  {

    public $id;
    protected static $_DB;

   public function getDB(\PDO $db ){
      if( !self::$_DB ){
          //@todo: connect to Database
      }
      return self::$_DB;
   }

    public function getId(){ return $this->id; }

    public function setId( $id ){ $this->id = $id }

    //returns the records id
    public function save(){
        //if there is no ID then we know it didn't come from the DB
        if( $this->id ) 
            return $this->update();
        else
            return $this->insert();
    }

    // query format = 'INSERT INTO table (id, ... )VALUES(:id, ... )'
    public function insert(){
        $this->validate();

        $db = $this->getDB(); //localize

        $R = new \ReflectionObject( $this );
        $props = (array)$R->getProperties()[0];

        $names = array_keys( $props );

        $sql = 'INSERT INTO '.$this->getTable().' ('.implode(',', $names).' )VALUES( :'.implode(', :', $names).' )';
        $params = array_combine(
            array_map(function($item){
                return ':'.$item;
            }, $names),
            $props
         );

        $stmt =  $db ->prepare( $sql );
        $stmt->execute( $params );

        $this->id = $db->lastInsertId(); //don't forget to update the id
        return $this->id;
    }

    // query format = 'UPDATE table SET prop=:prop, ... WHERE id=:id'
    public function Update(){
        $this->validate();

        $db = $this->getDB(); //localize

        $R = new \ReflectionObject( $this );
        $props = (array)$R->getProperties()[0];
        $names = array_keys( $props );

        $sql = 'UPATE '.$this->getTable().' SET ';
        $set = [];
        $params = [];
        foreach( $props as $name=>$value ){     
            $params[':'.$name] = $value;

            if( $name == 'id' ) continue;
            $set[] = "$name = :$name";
        }

        $sql .= implode(', ', $set).' WHERE id=:id'


        $stmt = $db->prepare( $sql );
        $stmt->execute( $params );

        return $this->_id;
    }

    abstract public function getTable();

    abstract public function vallidate();

  }

然后在您的具体类中,您只需要实现抽象方法,并添加特定于它们的其他属性

  class Dude extends BaseObject
  {
      public $name;

      public function getName(){ return $this->name ; }

      public function setName( $name ){ $this->name = $name }

      public function getTable(){
           return 'dudes';
      }

     public function validate(){
          if( empty( $this->name ) ) throw new \Exception( "Name cannot be empty" );

       //...etc.
     }
  }

我碰巧想到你应该知道的一件事,当通过 PDO 结果加载类时,不会调用类的构造函数。我以这种方式加载了一个类已经有一段时间了,可能有一种方法可以强制它调用构造函数,或者我可能只是回忆不正确。但这是值得一提的。

我提到的原因是抽象类需要一个 PDO 实例,所以我添加了getDB() 方法。这只是一个关于如何缓存所有类的数据库连接的简短示例,(我懒得做实际的连接部分,抱歉)

就我个人而言,我使用Singleton 来满足我的数据库需求,所以我只会调用类似self::$_DB = DB::getInstance();there 的东西,但这是另一天的故事。

我还建议添加一个delete 方法,这样您就可以获得所有程序员恶魔一直在谈论的CRUD 体验。

我能想到的另一个主要改进是您可以将其中一些内容存储在静态属性中,基本上在第一次运行后将其缓存。这将节省一些对类的多次重新处理(自省)。

虽然它有点棘手,但您可能想进一步分解。我可以给你的一个提示是确保在基类中使用static::$Var 而不是self::$Var,所以你使用所谓的Late Static Binding。这基本上是棘手的部分,因为你可以有完全不同的东西的后代类。我不确定这是否是正确的术语,但这是一个范围解析问题。

但我会把这些最后的事情留给你。这个例子应该为您指明正确的道路(或者至少是我知道可行的道路),并为您提供一些可能的想法。

最后一件事是使用这样的字符串访问属性或调用方法非常好(当然假设它们存在)

foreach( $props as $name=>$value ){
     //for this we will say $name = 'id'
    $method = "get".ucFirst( $name ); // 'getId'
    $a = $this->$method(); // calls $this->getId()
    $a = $this->$name;  //access property $this->id; 
}

我只是想我会把它放在那里给你另一种方式来访问你可能会发现有用的数据。

【讨论】:

  • 真正全面的答案,干杯。反射是我真正需要的,使用 $reflect = new ReflectionClass("Staff"); $props = $reflect->getProperties();并循环通过 $props 来获取名称。非常感谢。
  • 当然,很高兴我能派上用场。
  • 我想补充一点,我不会使用类名本身,您可以使用new ReflectionClass(__CLASS__);new ReflectionClass(get_called_class()); 甚至new ReflectionClass($this);。然后,如果您重命名课程或复制代码,您不必更改它。好吧,除非代码在课堂之外,否则您可以使用new ReflectionClass(get_class($obj));new ReflectionClass($obj::class); 甚至可以工作。
  • ReflectionClassReflectionObject 之间的区别在于类只获取类中定义的内容,其中获取的对象是类的当前实例中的内容。以防万一。
【解决方案2】:

我认为你想做这样的事情。参考(The only proper) PDO tutorial

$arr = [1,2,3];
$in  = str_repeat('?,', count($arr) - 1) . '?';
$sql = "SELECT * FROM table WHERE foo=? AND column IN ($in) AND bar=? AND baz=?";
$stm = $db->prepare($sql);
$params = array_merge([$foo], $arr, [$bar, $baz]);
$stm->execute($params);
$data = $stm->fetchAll();

这可能会让您对此有更好的了解,如果我没有正确理解您的问题,请告诉我。

【讨论】:

    猜你喜欢
    • 2011-12-24
    • 2014-08-10
    • 1970-01-01
    • 2012-05-26
    • 2016-05-21
    • 1970-01-01
    • 2012-02-12
    • 1970-01-01
    • 2013-02-18
    相关资源
    最近更新 更多