我花了很多时间研究这个并且没有从根本上修改ManyManyList
(因为它没有通过扩展系统公开必要的钩子),没有太多选择。
我是甜点第一的人,怎么办?
我完成这一壮举的唯一建议本质上是一个多对多的桥对象(即一个单独的实体连接Page
和Image
),$has_many
尽管它仍然需要相当多的修改。
这在论坛上进行了部分讨论,其中通过将版本化项目存储在实际对象而不是连接表中来破坏实际关系的解决方案。这会奏效,但我认为我们仍然可以做得更好。
我个人倾向于将关系的版本与Page
自身联系起来,下面的部分解决方案涵盖了这一点。阅读首屏以获取更多信息,尝试将此作为ManyManyList
.
这样的事情是一个开始:
class PageImageVersion extends DataObject
{
private static $db = array(
'Version' => 'Int'
);
private static $has_one = array(
'Page' => 'Page',
'Image' => 'Image'
);
}
这包含我们的 2-way 关系以及我们存储的版本号。您将需要指定getCMSFields
功能以添加所需的正确字段,以便您将其与现有图像相关联或上传新图像。我避免对此进行介绍,因为与实际版本处理部分相比,它应该相对简单。
现在,我们有一个这样has_many
的:Page
private static $has_many = array(
'Images' => 'PageImageVersion'
);
在我的测试中,我还添加了一个扩展来Image
添加匹配$has_many
,如下所示:
class ImageExtension extends DataExtension
{
private static $has_many = array(
'Pages' => 'PageImageVersion'
);
}
Pages
老实说,除了在关系方面添加功能之外,不确定这是否是必要Image
的。据我所知,对于这个特定的用例来说并不重要。
不幸的是,由于这种版本控制方式,我们不能使用标准的调用方式Images
,我们需要有点创意。像这样的东西:
public function getVersionedImages($Version = null)
{
if ($Version == null)
{
$Version = $this->Version;
}
else if ($Version < 0)
{
$Version = max($this->Version - $Version, 1);
}
return $this->Images()->filter(array('Version' => $Version));
}
当您调用getVersionedImages()
时,它将返回其上Version
设置与当前页面版本对齐的所有图像。还支持通过getVersionedImages(-1)
最新版本获取以前的版本,甚至通过传递任何位置编号来获取页面特定版本的图像。
好的,到目前为止一切顺利。我们现在需要确保每个页面写入我们都得到这个新版本页面的重复图像列表。
使用onAfterWrite
on 函数Page
,我们可以这样做:
public function onAfterWrite()
{
$lastVersionImages = $this->getVersionedImages(-1);
foreach ($lastVersionImages as $image)
{
$duplicate = $image->duplicate(false);
$duplicate->Version = $this->Version;
$duplicate->write();
}
}
对于那些在家玩的人来说,关于恢复以前的版本Page
会如何影响这一点,事情变得有点不确定。
因为我们将在 中进行编辑GridField
,所以我们需要做一些事情。首先是确保我们的代码可以处理该Add New
函数。
我的想法是onAfterWrite
关于PageImageVersion
对象:
public function onAfterWrite()
{
//Make sure the version is actually saved
if ($this->Version == 0)
{
$this->Version = $this->Page()->Version;
$this->write();
}
}
要让您的版本化项目显示在 中GridField
,您可以将其设置为类似于以下内容:
$gridFieldConfig = GridFieldConfig_RecordEditor::create();
$gridField = new GridField("Images", "Images", $this->getVersionedImages(), $gridFieldConfig);
$fields->addFieldToTab("Root.Images", $gridField);
您可能希望直接从GridField
via链接到图像,GridFieldConfig_RelationEditor
但这是事情变得糟糕的时候。
蔬菜的时间...
最大的困难之一是GridField
,对于链接和取消链接这些实体。使用标准GridFieldDeleteAction
将直接更新关系而无需正确的版本。
您将需要扩展GridFieldDeleteAction
和覆盖handleAction
以编写您的Page
对象(以触发另一个版本),为最后一个版本复制我们版本化图像对象的每个版本,同时使其跳过新版本中您不想要的那个。
我承认,这最后一点只是我的猜测。根据我的理解和调试,它应该可以工作,但要让它正确,需要做很多工作。
您的 then 扩展名GridFieldDeleteAction
需要添加到您的特定GridField
.
这基本上是您远离使该解决方案发挥作用的最后一步。一旦你完成了添加、删除、复制、版本更新部分,这真的只是使用getVersionedImages()
来获得正确的图像的问题。
结论
避免。many_many
我明白你为什么要这样做,但如果没有对 Silverstripe 中处理关系的方式进行相当大的更新,我真的看不出有一种干净的方式能够处理这个问题。
但我真的很想把它当作一个ManyManyList
!
我看到所需的更改ManyManyList
是具有 3 向密钥(外键、本地密钥、版本密钥)和用于添加/删除/获取等的各种方法已更新。
add
如果and函数中有钩子remove
,您也许可以将功能作为扩展(通过 Silverstripe 的扩展系统)潜入其中,并将所需的数据添加到many_many
关系允许的额外字段中。
虽然我可以通过直接扩展ManyManyList
然后通过强制ManyManyList
替换为我的自定义类来实现这种情况Object::useCustomClass
,但这将是一个更加混乱的解决方案。
对于我来说,在这个阶段给出一个纯解决方案的完整答案实在是太长/太复杂了ManyManyList
(尽管我稍后可能会回到这个问题并试一试)。
免责声明:我不是 Silverstripe Core 开发人员,可能有一个更简洁的解决方案来解决整个问题,但我根本看不出如何。