使用动态设置器和获取器的Symfony Doctrine

6

我正在使用symfony和doctrine。

服务器收到了一个HTTP PATCH请求,URL为/company/{id},包含一个模型属性及其值,如{"name": "我的新名称"},新值需要持久化到数据库中。

$request = Request::createFromGlobals();
$requestContentJSON = $request->getContent();
$requestContentObj = json_decode($requestContentJSON);

$repository = $this->getDoctrine()->getRepository('MyBundle:Company');
$company = $repository->find($id);

现在我可以输入$company->setName($requestContentObj[0]);,但接收的属性会有所不同。现在我正在使用以下代码来处理每个属性:
foreach($requestContentObj as $key => $value){
    switch($key){
        case 'name':
            $company->setName($value);
            break;
        case 'department':
            $company->setDepartment($value);
            break;
        case 'origin':
            $company->setOrigin($value);
            break;
        case 'headquarters':
            $company->setHeadquarters($value);
            break;
        case 'email':
            $company->setEmail($value);
            break;
        case 'twitterid':
            $company->setTwitterId($value);
            break;
        case 'description':
            $company->setDescription($value);
            break;
    }
}

但是这看起来不太明智,特别是因为我知道我将拥有其他实体,例如新闻、产品、用户等,它们的属性也将以同样的方式进行更新。我想做类似于这样的事情:

$company->set("property", "value");

我首先考虑的是将此 switch 语句放在公司类中的 set 函数内,并将其放在我拥有的所有其他实体类中。但是否有更好的方法呢?也许 symfony/doctrine 已经内置了解决方案,但我没有找到适合我的东西。

我仍然希望使用 setter 和 getter 作为长期投资。

谢谢。

3个回答

11
假设您的属性名称与方法名称类似,您可以这样做。 要设置多个属性,请执行以下操作。
Class customer {

    protected $_email;

    public function __construct(array $config = array()){
         $this->setOptions($config);
     }

    public function getEmail(){
        return $this->_email;
    }

    public function setEmail($email){
        $this->_email = $email;
    }

    public function setOptions(array $options)
    {
        $_classMethods = get_class_methods($this);
        foreach ($options as $key => $value) {
            $method = 'set' . ucfirst($key);
            if (in_array($method, $_classMethods)) {
                $this->$method($value);
            } else {
                throw new Exception('Invalid method name');
            }
        }
        return $this;
    }

    public function setOption($key, $value){
        return $this->setOptions(array($key, $value));
    }

}

现在您可以简单地执行此操作:

$array = array('email' => 'abc.@gmail.com');
$customer = new Customer($array);
echo $customer->getEmail();

1
+1 是因为这是一个很好的通用解决方案,适用于 symfony/doctrine 上下文之外。顺便提一下,您可以删除 ucfirst(),因为 PHP 方法和函数调用实际上是不区分大小写的! - Darragh Enright
Jay,感谢你的帮助!虽然@Darragh刚刚向我展示了一种替代getter和setter的方法,即使用Symfony的注释验证。它支持回调,因此除了验证之外,如果需要,我还可以记录错误,这使得getter和setter变得无用。否则,我会采用你的方法来解决问题。 - Yuri Borges
@yurihbss 如果你认为Jay的回答有用,请不要忘记给他点赞 :) - Darragh Enright
@yurihbss 这个方法应该在类的上下文中使用。抱歉我忘记提到了,我已经更新了我的答案。 - Jay Bhatt
我们也可以使用method_exists() - GusDeCooL
公共函数设置选项($key, $value) { 返回$this->setOptions(array($key => $value)); } - David from Studio.201

4

我的初步想法是在你的类中添加一个merge方法,如下所示:

<?php

// example Company entity
class Company
{
    private $name;

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

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

    function merge(\stdClass $obj)
    {
        // get the object vars of the passed object
        // iterate, and replace matching properties
        foreach (get_object_vars($obj) as $prop => $val) {
            if (property_exists($this, $prop)) {
                $this->$prop = $val;
            }
        }
    }
}

$company = new Company();

// mocking your request object
$requestContentObj = new stdClass();
$requestContentObj->name = 'acme';

$company->merge($requestContentObj);

var_dump($company);

产出:

class Company#1 (1) {
    private $name =>
    string(4) "acme"
}

这会默默地丢弃任何不匹配你的Company类中任何属性的传递值,这可能是你想要的,也可能不是。希望这能有所帮助 :)


嗨!感谢您的帮助。这可能就是我想要的。如果我错了,请纠正我,但我认为即使从实体类内部直接设置属性也不明智。如果有一种实现您的“合并”方法并仍将值重定向到setter的方法,那会更好,对吧?然后,如果需要,在setter函数中可以检查传递的值。 - Yuri Borges
1
很好的观点,这要看情况!如果你有额外的输入验证(这是非常常见的),调用该方法是有意义的。对于Symfony/Doctrine,我倾向于在实体属性上使用验证注释,这在某种程度上消除了实体setter中大部分需求额外逻辑的需要。我使用 @PrePersist@PreUpdate 钩子来计算或合成字段。也就是说,这只是一种方法 - 我想说你的观点是正确的 - 你很可能仍然想要使用 setter。@Jay的答案清晰地解决了这个问题,在那种情况下,我可能会做类似的事情。 - Darragh Enright

2
我可以建议的是不使用setter方法,但它似乎很适合你的问题。 在Doctrine 1.2.4中,您可以使用以下DQL:
$q = Doctrine_Core::getTable("myTable")->createQuery("q")
        ->update()
        ->where("id = ?", $id);

    foreach($requestContentObj as $key => $value)
    {
        $q->set($key, "?", $value);
    }

    $q->execute();

感谢分享。这个例子将来对我可能会有用处。 - Yuri Borges

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接