编写将SQL结果行转换为对象的函数

时间:2011-07-18 21:21:18

标签: php sql database object orm

假设我有这样的数据库模式:

http://img98.imageshack.us/img98/786/samplequ.png

我使用以下SQL从 customer 表中选择所有行,以及所有相关行:

SELECT c.*,
cty.id AS country__id, cty.name AS country__name,
o.id AS order__id, o.date AS order__date, o.total AS order__total,
ol.id AS order__orderline__id, ol.quantity AS order__orderline__quantity, ol.sub_total AS order__orderline__sub_total,
p.id AS order__orderline__product__id, p.name AS order__orderline__product__name, p.price AS order__orderline__product__price,
s.id AS order__shop__id, s.name AS order__shop__name
FROM customer c
JOIN country cty ON cty.id=c.country_id
JOIN order o ON o.customer_id=c.id
JOIN shop s ON s.id=o.shop_id
JOIN orderline ol ON ol.order_id=o.id
JOIN product p ON ol.product_id=p.id
ORDER BY
c.last_name ASC,
c.first_name ASC,
o.date DESC,
ol.id ASC

您可以看到,尽管来自 customer 表的列,但所有其他列都使用反映其可用于构造对象的关系的别名。别名中的双下划线用于标识关系层次结构。

我想编写一个PHP函数,它接受结果行并返回 Customer 对象的数组。这些 Customer 对象应该加载其他相关对象:

$customers=convert_result_rows_to_objects($result_rows,'Customer');
foreach($customers as $cust){
 echo $cust->id;
 echo $cust->get_full_name();
 echo $cust->country->name;
 foreach($cust->orders as $o){
  echo $o->id;
  echo $o->date;
  echo $o->shop->name;
  foreach($o->orderlines as $ol){
   echo $ol->product->name
   echo $ol->quantity;
   echo $ol->sub_total;
  }
  echo $o->total;
 }
}

我已经为每个数据库表编写了模型,例如:

class Customer{
 public function get_full_name(){...}
}

为简单起见,我忽略了所有其他字段的getter和setter。

但是如何编写 convert_result_rows_to_objects 函数?

我还不想使用ORM。

我现在脑子里的功能应该是这样的,

function convert_result_rows_to_objects($result_rows, $main_class_name){

    $main_objs = array(); //containing the main objects to be returned, customer objects in my example
    $main_obj = NULL;
    $previous_row = NULL;
    $next_row = NULL;

    for($i = 0; $i<count($result_rows); $i++){

        $previous_row = ($i > 0 ? ($result_rows[$i - 1]) : NULL);
        $this_row = $result_rows[$i];
        $next_row = $i === ( count($result_rows) - 1) ? NULL : ($result_rows[$i + 1]);

        if ($previous_row === NULL || $previous_row->id !== $this_row->id) {
            $main_obj = new $main_class_name(); //create the main object  

        //what should be done next?

    }
}

3 个答案:

答案 0 :(得分:3)

编辑:我在原帖中没有指出的一件事是我使用视图将所有相关列组合在一起。示例代码不会突出显示它,但如果您创建一个将所需内容整合在一起的视图,那么您可以创建一个基于该模型的模型,查询以及模型将非常简单。 / EDIT

这并不太难。您需要确保您的模型类看起来像您的数据库行,否则您将不得不进行手动映射。我有一个我的数据库模型的基类,如下所示:

class Ashurex_Model
{
    public function __construct($args = null)
    {
        if(is_array($args))
        {
            $this->setOptions($args);
        }
    }

    // Magic setter changes user_id to setUserId
    public function __set($name, $value)
    {
        $method = 'set' . Ashurex_Utilities::underscoreToCamelCase($name);
        if (method_exists($this, $method))
        {
            $this->$method($value);
        }
    }

    // Magic getter changes user_id to getUserId
    public function __get($name)
    {
        $method = 'get' . Ashurex_Utilities::underscoreToCamelCase($name);
        if (method_exists($this, $method))
        {
            return $this->$method();
        }
    }

    public function __call($name, $args)
    {
        if (method_exists($this, $name))
        {
            return call_user_func_array(array($this, $name), $args);
        }
    }

    // Used for initializing an object off the database row
    // transforms all the row names (like user_id to UserId)
    // from underscores to camel case
    protected function setOptions(array $options)
    {
        foreach($options as $key => $value)
        {
            $this->__set($key,$value);
        }
        return $this;
    }
}

示例用户类如下所示:

class Ashurex_Model_User extends Ashurex_Model
{
    protected $_id;
    protected $_username;
    protected $_userpass;
    protected $_firstName;

    { ... }

    public function getId(){ return $this->_id; }
    public function getUsername(){ return $this->_username; }
    public function getUserpass(){ return $this->_userpass; }
    public function getFirstName(){ return $this->_firstName; }

    { ... }

    public function setId($id){ $this->_id = $id; }
    public function setUsername($username){ $this->_username = $username; }
    public function setUserpass($password){ $this->_userpass = $password; }
    public function setFirstName($firstName){ $this->_firstName = $firstName; }

    { ... }

    // This function will help when automatically saving the object back to the database
    // The array keys must be named exactly what the database columns are called
    public function toArray()
    {
        $data = array(
            'id'                    => $this->getId(),
            'username'              => $this->getUsername(),
            'userpass'              => $this->getUserpass(),
            'first_name'            => $this->getFirstName(),
            { ... }
        );

        return $data;
    }
}

数据库表如下所示:

CREATE TABLE IF NOT EXISTS `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `userpass` varchar(160) NOT NULL,
  `first_name` varchar(64) NOT NULL,
  `last_name` varchar(64) NOT NULL,
  `email` varchar(64) NOT NULL,
  `role_id` tinyint(4) NOT NULL,
  `is_active` tinyint(1) NOT NULL DEFAULT '1',
  `force_password_change` tinyint(1) NOT NULL DEFAULT '0',
  `creation_datetime` datetime NOT NULL,
  `updated_datetime` datetime NOT NULL,
  `user_salt` varchar(32) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  KEY `role_id` (`role_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

如您所见,最重要的方面是您以适当的方式命名列,以便基本模型类可以“自动”将列名映射到方法。

最后,这就是保存代码的样子......

public function save(Ashurex_Model $obj)
    {
        try
        {
            $data = $obj->toArray();
                    // If no id is set, we are inserting a new row
            if(null === ($id = $data['id']))
            {
                unset($data['id']);
                $this->getDbTable()->insert($data);
                return $this->getDb()->lastInsertId();
            }
            else
            {
                            // We have an Id, do an update
                $where = $this->getDbTable()->getAdapter()->quoteInto('id = ?',array($id));
                $this->getDbTable()->update($data,$where);
                return $id;
            }
        }
        catch(Exception $e)
        {
            self::logException(__METHOD__,$e);
        }
        return false;
    }

示例查找代码如下所示,您可以看到它根据列名从数据库结果行初始化新对象:

public function find($id)
    {
        try
        {
            $table = $this->getDbView();
            $stmt = $table->select()
                        ->where('id = ?')
                        ->bind(array($id));

            $row = $table->fetchRow($stmt);
            if(!is_null($row))
            {
                $r = $row->toArray();
                $obj = new Ashurex_Model_User($r);
                return $obj;
            }
            else
            {
                return null;
            }
        }
        catch(Exception $ex)
        {
            self::logException(__METHOD__,$ex);
            return null;
        }
    }

答案 1 :(得分:2)

编辑:

对不起,当你说你已经有了这个型号时,我没有注意。首先,填充您需要的对象来识别字段(当然)。由于您有前缀,您可以使用 strstr strpos 来搜索对象标识(每个对象标识),并使用 substr获取字段的名称。然后您就可以填充对象了。如果你想要它是非常通用的,你需要通过双下划线来打破字段并动态创建对象。

如果您能够显示返回数据的示例,那就太好了。

答案 2 :(得分:1)

非常确定您想要的信息可以在这里找到: http://php.net/manual/en/book.reflection.php

我认为,当您处理所有边缘情况时,将数据库列映射到getter / setter时会出现一些奇怪现象,避免在启动时加载整个数据库等等,最终会编写ORM。