【问题标题】:Why can't I use public properties in Doctrine entities?为什么我不能在 Doctrine 实体中使用公共属性?
【发布时间】:2018-12-08 07:09:58
【问题描述】:

假设我在 PHP 中有一个非常简单的 CRUD 系统来管理数据库。假设它包含产品列表。使用 Doctrine ORM,我想查询数据库并查看/编辑/添加记录。根据Getting Started手册,

创建实体类时,所有字段都应该受到保护或 私有(非公共),每个都有 getter 和 setter 方法 ($id 除外)。使用 mutators 允许 Doctrine 挂钩调用 它以你无法做到的方式操纵实体 直接用 entity#field = foo 设置值;

这是提供的示例:

// src/Product.php
class Product
{
    /**
     * @var int
     */
    protected $id;
    /**
     * @var string
     */
    protected $name;

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

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

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

// Recording a new title
$product->setName("My new name");
$db->persist($product);
$db->flush();

// Echoing the title
echo $product->getName();

但是,这似乎过于复杂。假设我不需要挂钩来操纵实体的调用,如手册中所述。我可以使这段代码更短,然后这样做:

// src/Product.php
class Product
{
    /**
     * @var int
     */
    public $id;
    /**
     * @var string
     */
    public $name;
}

这将允许这样的事情:

$product = $db->getRepository('Product')->find(1);

// Recording a new name
$product->name = "My new title";
$db->persist($product);
$db->flush();

// Echoing the title
echo $product->name;

优点是:

  • 始终使用完全相同的名称
  • 实体类中没有额外的 setter 和 getter

使用这个有什么缺点?我在这里承担了一定的风险吗?

【问题讨论】:

  • 尽管有关于封装的帖子,但使用公共变量并没有错。然而,这样做意味着延迟加载不再起作用,因为 Doctrine 依赖于扩展 getter 来实现延迟加载。只要您总是提前加载数据,那么这不是问题。事实上,延迟加载通常是要避免的。
  • 谢谢,但有很多不同的答案。所以我现在很困惑什么是正确的答案......?!
  • 其他答案是主流。您可以默认使用它们。一旦你真正理解了这些论点,你就可以选择你自己的方式。在编程中,很少有单一的“最佳”方式。

标签: php doctrine-orm


【解决方案1】:

假设我不需要挂钩来操作实体的调用,如手册中所述。

不需要挂钩这些调用,但是Doctrine。 Doctrine 在内部覆盖了这些 getter 和 setter,以尽可能透明地实现 ORM。

是的,它过于复杂,但这是 PHP 语言的问题,而不是 Doctrine。使用公共属性本身并没有错,只是 PHP 语言不允许像其他一些语言那样创建自定义 getter/setter(例如KotlinC#)。

Doctrine 究竟做了什么?它生成所谓的代理对象来实现延迟加载。这些对象extend您的类并覆盖某些方法。

对于您的代码:

// src/Product.php
class Product
{
    /**
     * @var int
     */
    protected $id;
    /**
     * @var string
     */
    protected $name;

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

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

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

教义可能会产生:

class Product extends \YourNamespace\Product implements \Doctrine\ORM\Proxy\Proxy
{

    // ... Lots of generated methods ...

    public function getId(): int
    {
        if ($this->__isInitialized__ === false) {
            return (int)  parent::getId();
        }


        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getId', []);

        return parent::getId();
    }

    public function getName(): string
    {

        $this->__initializer__ && $this->__initializer__->__invoke($this, 'getName', []);

        return parent::getName();
    }

    // ... More generated methods ...
}

(这不是重点,重点是 Doctrine 必须能够覆盖 getter 和 setter 以实现 ORM)

要亲自查看,请检查 Doctrine 代理目录中生成的 Proxy 对象。有关配置代理目录的更多信息,请参阅Advanced Configuration 部分。另请参阅Working with Objects,了解有关代理对象以及 Doctrine 如何利用它们的更多信息。

【讨论】:

  • 谢谢,但在其中一个 cmets 中,据说 Doctrine 需要它的主要原因是延迟加载,如果我不需要它,我可以将它们公开。和你说的一样吗?我必须说我已经创建了一些带有公共变量的网站,而且一切似乎都运行得很好。我惊讶地发现我必须将它们设为私有......
  • @user32421 是的,Doctrine 这样做的原因之一是延迟加载。但 用于通过覆盖设置器来跟踪对象是否“脏”(例如,它已更改属性)。你根本无法解决这个问题。
  • 请注意,文档说您应该将它们设为受保护/私有。没有什么可以阻止您将它们公开但 Doctrine 无法知道您更改了对象的属性,因此无法知道 哪些 对象应该在数据库中更新和/或哪些它应该从数据库中加载的属性。
  • 这些 getter/setter 的全部意义在于它们允许 Doctrine 跟踪对象状态,因为在 PHP 世界中没有其他方法可以做到这一点。
  • @Pete - 您对 Doctrine 如何跟踪“脏”对象的评论是错误的。 docs.doctrine-project.org/projects/doctrine-orm/en/latest/… 延迟加载是你唯一失去的东西。
【解决方案2】:

为什么不在 Doctrine 中使用公共实体类?

其实你问的是:

这个 getter/setter 是从哪里来的?

来自OOP中的封装。

那么,什么是封装?

这是我们定义属性和方法可见性的方式。在创建类时,您必须问自己可以在类之外访问哪些属性和方法。

假设我们有一个名为 id 的属性。如果一个类扩展了您的类,是否允许直接操作和访问 id?如果有人创建了你的类的实例怎么办?是否允许他们操纵和访问 id?

不!他们应该无法直接访问 id 属性。

为什么不呢?

示例:如果您只接受以“IX”开头的 Invoice Id,而其他类可以访问您的类并直接设置 invoice_id,会发生什么情况?

如果他们不小心将 invoice_id 设置为“XX12345”会怎样?

当然,您不能做任何事情(例如验证输入),因为您使用的是公共属性而没有设置器。

但如果你的类中有一个 setter 方法,你可以这样做

private $invoiceId;

public function setInvoiceId($invoiceId) 
{
    if (is_null($invoiceId) || preg_match("#^IX(.*)$#i", $invoiceId) === 0)
        return false;

    $this->invoiceId = $invoiceId;

    return $this;
}

我希望它足够清楚。我会尝试扩展答案

【讨论】:

  • 谢谢,但我仍然不是 100% 清楚这一点。您提到其他人不应该能够操纵这些属性。然而,还有什么?我是唯一的开发者,这是一个没有第三方访问的独立网站....?而且我可以看到能够在发票 ID 上应用这些额外操作的优势,但我不能在我实际需要它的情况下(在我的情况下非常不可能)更改代码吗?
  • 如果你是一个独立的开发者,如果你不遵守标准,你会记住一切吗?比如 OOP-Standards、PSR、Principles.. 等等。也许有一天其他开发人员会加入你,或者你需要一个 API 来使用你的 Classes/Entities.. 等等。
  • "来自OOP中的封装。"不,这是因为 Doctrine 不能代理公共财产。所以这只是技术问题。具有 setter 的域模型仍然违反封装,因为我们没有将行为(操作数据的方法)放入聚合根中。我们通过调用低级 setter/getter 从外部操作数据。将两者结合在一个单元中的封装点。见doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-16
  • 1970-01-01
  • 2015-07-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多