我通过扩展Customer
创建了DataObject
Member
。 Customer
与many_many
Package
有DataObject
数据关系。
基于Credits
,当Customer
通过CMS链接/取消链接时,我想在DataObject
Package
中增加/减少Limit
字段Package
表中的字段。
客户
class Customer extends Member {
private static $db = array(
'Gender' => 'Varchar(2)',
'DateOfBirth' => 'Date',
'Featured' => 'Boolean',
'Credits' => 'Int'
);
private static $many_many = array(
'Packages' => 'Package'
);
public function getCMSFields() {
$fields = new FieldList();
$config = GridFieldConfig_RelationEditor::create();
$config->removeComponentsByType('GridFieldAddNewButton');
$packageField = new GridField(
'Packages',
'Package',
$this->Packages(),
$config
);
$fields->addFieldToTab('Root.Package', $packageField);
Session::set('SingleID', $this->ID);
$this->extend('updateCMSFields', $fields);
return $fields;
}
}
封装
class Package extends DataObject {
private static $db = array(
'Title' => 'Varchar(255)',
'Limit' => 'Int'
);
private static $belongs_many_many = array(
'Customers' => 'Customer'
);
}
答案 0 :(得分:4)
创建或删除多对多关系时,只需在数据库中修改一条记录 - 表中的一条记录,用于连接关系两边的元素。因此,关系所基于的对象都不会更新。这就是为什么类似:onBeforeWrite
,onAfterWrite
,onBeforeDelete
和onAfterDelete
等方法根本不会被调用的原因,您无法使用它们来检测此类更改。
但是,Silverstripe提供了类ManyManyList
,它负责连接到多对多关系的所有操作。您有两种方法:添加和删除。您可以覆盖它们并将内部操作放在您需要的位置。显然,无论对象类型是什么,每个链接或取消链接操作都会调用这些方法,因此您应该对您特别感兴趣的类进行一些过滤。
覆盖ManyManyList
类的正确方法是使用Injector机制,以免修改框架或cms文件夹中的任何内容。以下示例使用Silverstripe中的成员和组之间的关系,但您可以根据需要轻松采用它(客户 - >会员;包 - >组)。
<强> app.yml 强>
Injector:
ManyManyList:
class: ManyManyListExtended
<强> ManyManyListExtended.php 强>
/**
* When adding or removing elements on a many to many relationship
* neither side of the relationship is updated (written or deleted).
* SilverStripe does not provide any built-in actions to get information
* that such event occurs. This is why this class is created.
*
* When it is uses together with SilverStripe Injector mechanism it can provide
* additional actions to run on many-to-many relations (see: class ManyManyList).
*/
class ManyManyListExtended extends ManyManyList {
/**
* Overwritten method for adding new element to many-to-many relationship.
*
* This is called for all many-to-many relationships combinations.
* 'joinTable' field is used to make actions on specific relation only.
*
* @param mixed $item
* @param null $extraFields
* @throws Exception
*/
public function add($item, $extraFields = null) {
parent::add($item, $extraFields);
if ($this->isGroupMembershipChange()) {
$memberID = $this->getMembershipID($item, 'MemberID');
$groupID = $this->getMembershipID($item, 'GroupID');
SS_Log::log("Member ($memberID) added to Group ($groupID)", SS_Log::INFO);
// ... put some additional actions here
}
}
/**
* Overwritten method for removing item from many-to-many relationship.
*
* This is called for all many-to-many relationships combinations.
* 'joinTable' field is used to make actions on specific relation only.
*
* @param DataObject $item
*/
public function remove($item) {
parent::remove($item);
if ($this->isGroupMembershipChange()) {
$memberID = $this->getMembershipID($item, 'MemberID');
$groupID = $this->getMembershipID($item, 'GroupID');
SS_Log::log("Member ($memberID) removed from Group ($groupID)", SS_Log::INFO);
// ... put some additional actions here
}
}
/**
* Check if relationship is of Group-Member type.
*
* @return bool
*/
private function isGroupMembershipChange() {
return $this->getJoinTable() === 'Group_Members';
}
/**
* Get the actual ID for many-to-many relationship part - local or foreign key value.
*
* This works both ways: make action on a Member being element of a Group OR
* make action on a Group being part of a Member.
*
* @param DataObject|int $item
* @param string $keyName
* @return bool|null
*/
private function getMembershipID($item, $keyName) {
if ($this->getLocalKey() === $keyName)
return is_object($item) ? $item->ID : $item;
if ($this->getForeignKey() === $keyName)
return $this->getForeignID();
return false;
}
}
3dgoo提供的解决方案也应该可以正常工作但IMO代码可以做得更多&#34; hacking&#34;这就是为什么它的可维护性要低得多。如果您想要进行任何其他链接/取消链接管理,例如添加自定义管理模块或某些表单,则需要进行更多修改(在两个类中)并且需要倍增。
答案 1 :(得分:2)
问题在于在多对多关系中添加或删除项目时,不会写入关系的任何一方。因此,不会在任何一个对象上调用onAfterWrite
和onBeforeWrite
。
我之前遇到过这个问题。我使用的解决方案并不是很好,但它是唯一对我有用的东西。
我们可以做的是在调用getCMSFields
时将包的ID列表设置为会话变量。然后,当在网格字段上添加或删除项目时,我们刷新CMS面板以再次调用getCMSFields
。然后,我们检索上一个列表并将其与当前列表进行比较。如果列表不同,我们可以做点什么。
<强>客户强>
class Customer extends Member {
// ...
public function getCMSFields() {
// Some JavaScript to reload the panel each time a package is added or removed
Requirements::javascript('/mysite/javascript/cms-customer.js');
// This is the code block that saves the package id list and checks if any changes have been made
if ($this->ID) {
if (Session::get($this->ID . 'CustomerPackages')) {
$initialCustomerPackages = json_decode(Session::get($this->ID . 'CustomerPackages'), true);
$currentCustomerPackages = $this->Packages()->getIDList();
// Check if the package list has changed
if($initialCustomerPackages != $currentCustomerPackages) {
// In here is where you put your code to do what you need
}
}
Session::set($this->ID . 'CustomerPackages', json_encode($this->Packages()->getIDList()));
}
$fields = parent::getCMSFields();
$config = GridFieldConfig_RelationEditor::create();
$config->removeComponentsByType('GridFieldAddNewButton');
$packageField = GridField::create(
'Packages',
'Package',
$this->Packages(),
$config
);
// This class needs to be added so our javascript gets called
$packageField->addExtraClass('refresh-on-reload');
$fields->addFieldToTab('Root.Package', $packageField);
Session::set('SingleID', $this->ID);
$this->extend('updateCMSFields', $fields);
return $fields;
}
}
if ($this->ID) { ... }
代码块是我们所有会话代码发生的地方。另请注意,我们在网格字段中添加了一个类,因此我们的JavaScript刷新工作$packageField->addExtraClass('refresh-on-reload');
如前所述,每次在列表中添加或删除包时,我们都需要添加一些JavaScript来重新加载面板。
<强> CMS-customer.js 强>
(function($) {
$.entwine('ss', function($){
$('.ss-gridfield.refresh-on-reload').entwine({
reload: function(e) {
this._super(e);
$('.cms-content').addClass('loading');
$('.cms-container').loadPanel(location.href, null, null, true);
}
});
});
})(jQuery);
在if($initialCustomerPackages != $currentCustomerPackages) { ... }
代码块中,您可以执行许多操作。
您可以使用$this->Packages()
来获取与该客户关联的所有当前包。
您可以致电array_diff
和array_merge
来获取已添加和删除的软件包:
$changedPackageIDs = array_merge(array_diff($initialCustomerPackages, $currentCustomerPackages), array_diff($currentCustomerPackages, $initialCustomerPackages));
$changedPackages = Package::get()->byIDs($changedPackageIDs);
上述代码会将此功能添加到关系的Customer
侧。如果您还希望管理关系Package
方面的多对多关系,则需要在Package
getCMSFields
函数中添加类似的代码。
希望有人可以提出更好的解决方案。如果没有,我希望这适合你。
答案 2 :(得分:0)
注意:实际上没有检查模型是否有效但通过目视检查这可以帮助您:
在您提供的链接上,您正在使用
$customer = Customer::get()->Filter...
除非您从DataList指定所需的对象,否则返回对象的DataList,而不是单个对象。
如果您要过滤客户,那么您希望从DataList获得SPECIFIC客户,例如在这种情况下的第一个。
$customer = Customer::get()->filter(array('ID' => $this->CustomerID))->first();
但是你应该能够得到奇异的DataObject:
$customer = $this->Customer();
当您将客户定义为“has_one”时。如果关系是Has很多,using()会得到一个对象的DataList。
Protip:
您无需在SilverStripe中编写我们自己的调试文件。它有自己的功能。例如Debug::log("yay");
将输出写入文件的内容和将Debug::dump("yay")
直接转储出来的文件Debug::dump(get_class($customer));
。
提示您可以检查您正确访问的对象是什么。 URI u1 = new URI("ssh://root:zstackqwe:!@#@172.16.36.184/");
System.out.println(u1.getAuthority());
System.out.println(u1.getHost());
只输出对象的类。