以数据映射器模式

时间:2015-11-27 15:24:29

标签: php mysql oop pdo

编辑:问题底部的输出代码

我刚发布了一个问题,认为我的问题是查询,但事实证明它是我的PHP代码。

这就是问题所在。我有一个GoalChallenge类,它有许多属性,其中一个属性应该是一个,或者是一组ProductService对象;请参阅下面的GoalChallenge类(注意我已经删除了其他的getter和setter,并留下了与ProductService类相关的那些。

当我使用GoalChallenge :: findByPersonaId时,会创建一个ProductService对象并与匹配的GoalChallenge对象相关联,但GoalChallenge-> product_service属性中应该有2个ProductService对象(查询应该匹配2行)。相反,会创建一个重复的GoalChallenge对象,其中包含除product_service属性以外的所有内容的相同属性值,该属性包含查询中的第二个匹配对象。

我需要两个匹配的ProductService对象成为同一个GoalChallenge对象的一部分(与查询匹配) - 我该如何实现?

如果您还有其他需要,请询问。非常感谢任何帮助!代码如下;

GoalChallenge.class.php

<?php

class GoalChallenge
{
    private $id;
    private $persona_id;
    private $title;
    private $item_category;
    private $description;
    private $solution;
    private $product_service;
    private $research_checklist;
    private $subtopics;
    private $keywords;
    private $status;


    public function __construct(
        $id = null, 
        $persona_id = null, 
        $title = null, 
        $item_category = null, 
        $description = null,
        $solution = null,
        ProductService $product_service = null,
        $research_checklist = null,
        $subtopics = null, 
        $keywords = null, 
        $status = null
    ) {
        $this->id = $id;
        $this->persona_id = $persona_id;
        $this->title = $title;
        $this->item_category = $item_category;
        $this->description = $description;
        $this->solution = $solution;
        $this->product_service = $product_service;
        $this->research_checklist = $research_checklist;
        $this->subtopics = $subtopics;
        $this->keywords = $keywords;
        $this->status = $status;
    }

    public function getProductService()
    {
        return $this->product_service;
    }

    public function setProductService(ProductService $product_service)
    {
        $this->product_service = $product_service;
    }

}

我的GoalChallengeMapper.class.php;

class GoalChallengeMapper
{

    protected $dblayer;

    public function __construct(PDO $dblayer)
    {
        $this->dblayer = $dblayer;
    }

    public function saveField($id, $field, $data)
    {
        try {
            $this->dblayer->beginTransaction();
            $stmt = $this->dblayer->prepare("UPDATE goals_challenges SET $field = :data WHERE id = :id");
            $stmt->bindParam(':id', $id);
            $stmt->bindParam(':data', $data);
            $stmt->execute();

            $this->dblayer->commit();

            return $stmt->rowCount();

        } catch(PDOException $e) {
            $this->dblayer->rollBack();
            echo $e->getMessage();
            exit;
        }

    }

    public function findByPersonaId($persona_id)
    {
        try {
            $this->dblayer->beginTransaction();
            $stmt = $this->dblayer->prepare("SELECT goals_challenges.*, products_services.id as psid, products_services.url, products_services.feature_benefit from goals_challenges LEFT JOIN products_services ON goals_challenges.id = products_services.goal_challenge_id WHERE goals_challenges.persona_id = :persona_id");
            $stmt->bindParam(':persona_id', $persona_id);
            $stmt->execute();

            $this->dblayer->commit();

            $result_set = array();

            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
                $result_set[] = $this->mapObject($row); 
            }

            return $result_set;

        } catch (PDOException $e) {
            $this->dblayer->rollBack();
            echo $e->getMessage();
            exit;
        }
    }

    public function mapObject(array $row)
    {
        $entry = new GoalChallenge();
        $entry->setId($row['id']);
        $entry->setPersonaId($row['persona_id']);
        $entry->setTitle($row['title']);
        $entry->setItemCategory($row['item_category']);
        $entry->setDescription($row['description']);
        $entry->setSolution($row['solution']);
        $entry->setProductService(new ProductService($row['psid'], $row['id'], $row['url'], explode(',', $row['feature_benefit'])));
        $entry->SetResearchChecklist($row['research_checklist']);
        $entry->setSubtopics($row['subtopics']);
        $entry->setKeywords($row['keywords']);
        $entry->setStatus($row['status']);

        return $entry;
    }
}

最后,我的ProductService类(减去getter和setter)

class ProductService
{
    private $id;
    private $goal_challenge_id;
    private $url;
    private $feature_benefit = [];

    public function __construct($id = null, $goal_challenge_id = null, $url = null, array $feature_benefit = null)
    {   
        $this->id = $id;
        $this->goal_challenge_id = $goal_challenge_id;
        $this->url = $url;
        $this->feature_benefit = $feature_benefit;
    }
}

这是输出

GoalChallenge Object
(
    [id:GoalChallenge:private] => 173
    [persona_id:GoalChallenge:private] => 14
    [title:GoalChallenge:private] => Lead Gen
    [item_category:GoalChallenge:private] => Business Challenge
    [description:GoalChallenge:private] => 


    [solution:GoalChallenge:private] => Advertising
    [product_service:GoalChallenge:private] => ProductService Object
        (
            [id:ProductService:private] => 1
            [goal_challenge_id:ProductService:private] => 173
            [url:ProductService:private] => www.google.com
            [feature_benefit:ProductService:private] => Array
                (
                    [0] => good for testing
                    [1] =>  mobile
                )

        )

    [research_checklist:GoalChallenge:private] => 0,0,0,0,0,0
    [subtopics:GoalChallenge:private] => 
    [keywords:GoalChallenge:private] => ,,,,
    [status:GoalChallenge:private] => 1
)

GoalChallenge Object
(
    [id:GoalChallenge:private] => 173
    [persona_id:GoalChallenge:private] => 14
    [title:GoalChallenge:private] => Lead Gen
    [item_category:GoalChallenge:private] => Business Challenge
    [description:GoalChallenge:private] => 


    [solution:GoalChallenge:private] => Advertising
    [product_service:GoalChallenge:private] => ProductService Object
        (
            [id:ProductService:private] => 3
            [goal_challenge_id:ProductService:private] => 173
            [url:ProductService:private] => www.test.com
            [feature_benefit:ProductService:private] => Array
                (
                    [0] => good for searching
                    [1] =>  well known
                )

        )

    [research_checklist:GoalChallenge:private] => 0,0,0,0,0,0
    [subtopics:GoalChallenge:private] => 
    [keywords:GoalChallenge:private] => ,,,,
    [status:GoalChallenge:private] => 1
)

的MySQL&GT; SELECT goals_challenges。*,products_services.id as psid,products_services.url,products_services.feature_benefit FROM goals_challenges LEFT JOIN products_services ON goals_challenges.id = products_services.goal_challenge_id WHERE goals_challenges.persona_id = 14;

+-----+------------+----------+--------------------+-------------+-------------+-----------------+--------------------+-----------+----------+--------+------+----------------+--------------------------------+
| id  | persona_id | title    | item_category      | description | solution    | product_service | research_checklist | subtopics | keywords | status | psid | url            | feature_benefit                |
+-----+------------+----------+--------------------+-------------+-------------+-----------------+--------------------+-----------+----------+--------+------+----------------+--------------------------------+
| 173 |         14 | Lead Gen | Business Challenge |             | Advertising | NULL            | 0,0,0,0,0,0        | NULL      | ,,,,     |      1 |    1 | www.google.com | good for testing, mobile       |


| 173 |         14 | Lead Gen | Business Challenge |             | Advertising | NULL            | 0,0,0,0,0,0        | NULL      | ,,,,     |      1 |    3 | www.test.com   | good for searching, well known |


+-----+------------+----------+--------------------+-------------+-------------+-----------------+--------------------+-----------+----------+--------+------+----------------+--------------------------------+

2行(0.00秒)

print_r($ goals_challenges)

Array
(
    [173] => Array
        (
            [id] => 173
            [persona_id] => 14
            [title] => Lead Gen
            [item_category] => Business Challenge
            [description] => 


            [solution] => Advertising
            [research_checklist] => 0,0,0,0,0,0
            [subtopics] => 
            [keywords] => ,,,,
            [status] => 1
            [psid] => 1
            [url] => www.google.com
            [feature_benefit] => good for testing, mobile
            [product_services] => Array
                (
                    [0] => Array
                        (
                            [0] => 1
                            [1] => www.google.com
                            [2] => good for testing, mobile
                        )

                    [1] => Array
                        (
                            [0] => 3
                            [1] => www.test.com
                            [2] => good for searching, well known
                        )

                )

        )

)

1 个答案:

答案 0 :(得分:1)

正如所怀疑的那样,JOIN查询的结果集需要更多逻辑来格式化您想要的方式而不是您给出的方式。 SQL结果集始终是一个二维结构,即使它包含的数据具有更复杂的关系(如您的一对多关系)。

有几种方法可以解决这个问题,我认为最接近你现有模式的方法是改变你获取行的方式。不是提取行然后立即映射它,而是在fetch循环中构建一些逻辑以创建连接表达的嵌套结构,其中ProductService是一个或多个对象的数组。然后,您将能够修改mapObject()方法以处理嵌套的ProductService对象数组。

因此,不要在获取时进行映射,而是创建一个数组,在该数组上附加所获取的行。在每次迭代时,您必须检查公共值(GoalChallenge的)是否已更改。如果没有,则继续为ProductService构建数组。如果它们已更改(例如,如果您的查询返回多个不同的GoalChallenge),则会启动新的外部结构。

$ result_set = array();

// Temp variable to remember what goals_challenges.id is being grouped
$current_id = null;
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    // Create a new structure if the id changed
    if($row['id'] !== $current_id) {
       $current_id = $row['id'];
       // Store a new row for goal_challenges, holding all 
       // the common columns in its outer structure
       $goal_challenges[$row['id']] = $row;
       // and it has a sub-array for product services
       $goal_challenges[$row['id']]['product_servies'] = array();
    }
    // Append the product_services columns as an array onto the subarray
    $goal_challenges[$row['id']]['product_services'][] = array('psid'=>$row['psid'], 'url'=>$row['url'], 'feature_benefit'=>$row['feature_benefit']);
}

// Now you can pass each row of the $goal_challenges array
// to mapObject. There should be only one row, but if you 
// use the same pattern for queries that return many rows it
// will work without much modification
$result_set = array();
foreach ($goal_challenges as $gc) {
    $result_set[] = $this->mapObject($gc);
}
// Return the array of results (which probably has only one element)
return $result_set;

好的,那应该修复获取模式以满足您的需求。另一个问题是使mapObject()方法处理内部产品服务数组。通过循环这很容易。

public function mapObject(array $row)
{
    $entry = new GoalChallenge();
    $entry->setId($row['id']);
    $entry->setPersonaId($row['persona_id']);
    $entry->setTitle($row['title']);
    $entry->setItemCategory($row['item_category']);
    $entry->setDescription($row['description']);
    $entry->setSolution($row['solution']);
    $entry->SetResearchChecklist($row['research_checklist']);
    $entry->setSubtopics($row['subtopics']);
    $entry->setKeywords($row['keywords']);
    $entry->setStatus($row['status']);

    // Create ProductService objects for each item in the sub-array
    foreach ($row['product_services'] as $ps) {
        $entry->setProductService(new ProductService($ps['psid'], $row['id'], $ps['url'], explode(',', $ps['feature_benefit'])));
    }

    return $entry;
}

最后,将setProductService()方法附加到数组而不是设置单个属性:

public function setProductService(ProductService $product_service)
{
    // Append onto an array
    $this->product_service[] = $product_service;
}

GoalChallenge::__construct()参数中,让它接受并默认数组而不是单个ProductService对象,更改为$product_service = array()

所以这有点复杂,它说明为什么预先构建ORM libraries like Doctrine是常用的。这个逻辑以一种易于重复使用的方式为您抽象出来。 PDO确实有FETCH_GROUP方法,但它只是将一列(如id)组合为外部数组键,将所有其他列组合为子数组。您的情况是这样的,大多数列都属于外层,只有与连接的ProductService相关的列作为内部子数组,因此实际上不起作用。