我的猜测是您关闭了错误报告,这就是为什么您只看到这样一个含义较少的错误消息。
你应该在php.ini、.htaccess 或_ss_environment.php 中打开display_errors 和error_reporting(重要提示:在_config.php 中设置它不起作用,因为它会被错误处理程序覆盖)
我在您的代码中看到的问题是尝试在FragrancesPage 上使用$this->Ingredients(),但据我所知,只有Fragrances 类有一个方法Ingredients(该方法是“神奇地”为many_many 关系)。
另外,我认为你的setDisplayFields()
所以基本上你需要使用$fragrance->Ingredients() 而不是$this->Ingredients()。
但这导致我们遇到下一个问题:您还没有香水。
不幸的是,此时,GridField 仅在您已有对象时才有效。这意味着您必须将其拆分为两种形式或使用另一种形式。
选项 1: 使用 CheckboxSetField 管理 many_many 关系。
这将不允许动态创建成分,它只会为您提供可以勾选以链接项目的复选框。
public function FragranceAddForm() {
$fragrances_field = new CheckboxSetField('Ingredients', 'Ingredient', Ingredient::get()->map());
$fields = new FieldList(
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$fragrances_field
);
$actions = new FieldList(
new FormAction('doFragranceAdd', 'Add Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
return new Form($this, __FUNCTION__, $fields, $actions, $validator);
}
public function doFragranceAdd($data, $form) {
$submission = new Fragrance();
$form->saveInto($submission);
$submission->write();
return $this->redirectBack();
}
选项 2: 以第二种形式使用 GridField
这将允许动态创建成分,但工作量更大。你可能会在使用 GridField 时遇到一些麻烦,因为它还没有在前端进行全面测试。
(最近有一个问题,我写了一点关于前端的 GridField 问题https://stackoverflow.com/a/22059197/1119263)
我想这个地方和任何地方一样好,可以最终为前端 GridFields 编写教程/工作示例。
我冒昧地重构了您的代码以包含编辑功能,在编辑页面上使用 GridField 比在单独的表单上要好得多。
如前所述,GridField 在前端工作得不是很好,有一个模块可以减轻痛苦,但它的边缘仍然很粗糙,需要你做一些造型来让它看起来很漂亮。
在 Packagist 或 GitHub 上找到模块
(你需要
class Ingredient extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $belongs_many_many = array(
'Fragrances' => 'Fragrance'
);
public function getCMSFields() {
// fields used by the GridField, don't let the CMSFields mislead you
return new FieldList(
TextField::create('Name', 'Name'),
TextAreaField::create('Description', 'Description')
);
}
}
class Fragrance extends DataObject {
private static $db = array(
'Name' => 'Text',
'Description' => 'Text'
);
private static $many_many = array(
'Ingredients' => 'Ingredient'
);
}
/**
* Form in a separate class, so we can reuse it.
* @param Controller $controller
* @param string $name
* @param Null|Fragrance $fragrance Either null to create a new one, or pass an existing to edit it and add Ingredients
* @return Form
*/
class FragranceForm extends Form {
public function __construct($controller, $name, $fragrance = null) {
if ($fragrance && $fragrance->isInDB()) {
// we can only use a GridField if the object exists and has already been saved
// gridfield needs jQuery
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.min.js');
// ensure we don't have 2 versions of jQuery
Requirements::block(THIRDPARTY_DIR . '/jquery/jquery.js');
$config = FrontEndGridFieldConfig_RelationEditor::create();
$config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
'Name' => 'Name',
'Description' => 'Description',
));
$ingredientField = new FrontEndGridField(
'Ingredients',
'Ingredient',
$fragrance->Ingredients(),
$config
);
} else {
$ingredientField = new LiteralField('Ingredients', '<p>Ingredients can be added after saving</p>');
}
$fields = new FieldList(
new HiddenField('ID', ''),
new TextField('Name', 'Fragrance Name'),
new TextareaField('Description', 'Fragrance Description'),
$ingredientField
);
$actions = new FieldList(
new FormAction('doFragranceSave', 'Save Fragrance')
);
$validator = new RequiredFields('Name', 'Description');
// populate the fields (ID, Name and Description) with the values from $fragrance. This does not effect the GridField
if ($fragrance && $fragrance->exists()) {
$fields->fieldByName('ID')->setValue($fragrance->ID);
$fields->fieldByName('Name')->setValue($fragrance->Name);
$fields->fieldByName('Description')->setValue($fragrance->Description);
// there is actually a method for that, but we can't use it here,
// because fields are not set yet. we could do it after __construct, but then we would
// overwrite things set by the error handler, so lets just do it by hand
// $this->loadDataFrom($fragrance);
}
parent::__construct($controller, $name, $fields, $actions, $validator);
}
public function doFragranceSave($data, $form) {
if (isset($data['ID']) && $data['ID']) {
$id = (int)$data['ID'];
$fragrance = Fragrance::get()->byID($id);
}
if (!isset($fragrance) || !$fragrance || !$fragrance->exists()) {
// if the ID was invalid or we don't have one, create a new Fragrance
$fragrance = new Fragrance();
}
$form->saveInto($fragrance);
$fragrance->write();
// redirect to the edit page.
$controller = $this->getController();
$editLink = $controller->EditLink($fragrance->ID);
return $controller->redirect($editLink);
}
}
class FragrancesPage extends Page {
}
class FragrancesPage_Controller extends Page_Controller {
private static $allowed_actions = array(
'edit',
'AddForm',
'EditForm',
);
/**
* the default action
* @return ViewableData_Customised
*/
public function index() {
// $this->customise() lets you overwrite variables that you can use in the template later.
return $this->customise(array(
// set the AddForm to $Form instead of $AddForm, this way you can use $Form in template and can reuse the template
'Form' => $this->AddForm(),
));
}
/**
* edit action to edit an existing Fragrance
* links will look like this /FragrancesPage/edit/$ID
*
* @param SS_HTTPRequest $request
* @return SS_HTTPResponse|ViewableData_Customised
*/
public function edit(SS_HTTPRequest $request) {
$id = (int)$request->param('ID');
$fragrance = Fragrance::get()->byID($id);
if (!$fragrance || !$fragrance->exists()) {
// fragrance not found? display a 404 error page
return ErrorPage::response_for(404);
}
// now that we have a $fragrance, overwrite EditForm with a EditForm that contains the $fragrance
$form = $this->EditForm($fragrance);
$return = $this->customise(array(
// also overwrite Title and Content, to display info about what the user can do here
// if you don't overwrite that, it will display the Title and Content of the page
'Title' => 'Edit: ' . $fragrance->Name,
'Content' => '<p>you are editing an existing fragrance</p>',
// set the Form to $Form instead of $EditForm, this way you can use $Form in template and can reuse the template
'Form' => $form,
));
// per default SilverStripe will try to use the following templates: FragrancesPage_edit.ss > FragrancesPage.ss > Page.ss
// if you want to use a custom template here, you can specify that with ->renderWith()
// but you probably won't need that anyway
// $return = $return->renderWith(array('MyCustomTemplateName', 'Page'));
return $return;
}
public function AddForm() {
return new FragranceForm($this, __FUNCTION__);
}
public function EditForm($fragranceOrRequest = null) {
// unfortunately, GridField / FormFields in general are a bit clumsy and do forget what item they where
// suppose to edit, so we have to check what $fragranceOrRequest is and set/get the fragrance to/from session
if ($fragranceOrRequest && is_a($fragranceOrRequest, 'Fragrance')) {
$fragrance = $fragranceOrRequest;
Session::set('FragrancesPage.CurrentFragrance', $fragrance->ID);
} else {
$fragrance = Fragrance::get()->byID(Session::get('FragrancesPage.CurrentFragrance'));
}
if (!$fragrance || !$fragrance->exists()) {
// that's bad, some error has occurred, lets display an ugly 404 page
return $this->httpError(404);
}
return new FragranceForm($this, __FUNCTION__, $fragrance);
}
public function EditLink($ID) {
return $this->Link("edit/$ID");
}
}
作为仍在学习 SilverStripe 的人,我知道有很多东西需要吸收,如果您有任何问题,请随时发表评论或在 IRC 上戳我