尝试使用GridField在DataObjects之间创建关系时出现服务器错误

时间:2014-03-16 01:23:41

标签: silverstripe

我通过创建一个让用户管理香水(即香水/古龙水)的小网站来学习SilverStripe。用户添加成分(用于他们拥有的香水),然后添加他们的香水,此时他们选择哪些成分在他们添加的香水中。

我创建了Ingredient和Fragrance类,它们都扩展了DataObject。我还创建了IngredientsPage页面,允许用户添加/编辑/删除成分(由名称和描述组成)并列出到目前为止添加的所有成分,此页面功能齐全。我现在正在尝试创建FragrancesPage页面,该页面将允许用户添加/编辑/删除香水(由名称,描述和成分组成)并列出到目前为止添加的所有香水,但我有有些麻烦。

我所知道的唯一方法是创造一种香水和成分之间的关​​系(一种香水含有许多成分,一种成分属于许多香水)正在使用GridField(如果有更好的方法,请告诉我!),因为这是SilverStripe教程让你做的事情(虽然在教程中它是CMS而不是前端)。但是,只要我尝试将GridField添加到组合中,我就会进入错误页面,其中显示"服务器错误:抱歉,处理您的请求时出现问题。"。

我的代码如下。

为ingredient.php:

<?php

class Ingredient extends DataObject {
    private static $db = array(
        'Name' => 'Text',
        'Description' => 'Text'
    );

    private static $belongs_many_many = array(
        'Fragrances' => 'Fragrance'
    );
}

?>

Fragrance.php:

<?php

class Fragrance extends DataObject {
    private static $db = array(
        'Name' => 'Text',
        'Description' => 'Text'
    );

    private static $many_many = array(
        'Ingredients' => 'Ingredient'
    );
}

?>

FragrancesPage.php:

<?php

class FragrancesPage extends Page {
    private static $icon = 'cms/images/treeicons/reports-file.png';
    private static $description = 'Fragrances page';
}

class FragrancesPage_Controller extends Page_Controller {
    private static $allowed_actions = array('FragranceAddForm');

    function FragranceAddForm() {
        $config = GridFieldConfig_RelationEditor::create();
        $config->getComponentByType('GridFieldDataColumns')->setDisplayFields(array(
            'Name' => 'Name',
            'Ingredient.Name' => 'Ingredient'
        ));

        $fragrances_field = new GridField(
            'Ingredients',
            'Ingredient',
            $this->Ingredients(),
            $config
        );

        $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, 'FragranceAddForm', $fields, $actions, $validator);
    }

    public function doFragranceAdd($data, $form) {
        $submission = new Fragrance();
        $form->saveInto($submission);
        $submission->write();

        return $this->redirectBack();
    }

    public function FragranceList() {
        $submissions = Fragrance::get()->sort('Name');

        return $submissions;
    }
}

?>

如果我从FragrancesPage.php删除与GridField相关的所有内容,页面工作正常。我似乎无法让GridField工作,并且不知道在前端创建香水和成分之间关系的任何其他方式。如果IngredientsPage.php的代码也有帮助,请告诉我,我会添加它。

1 个答案:

答案 0 :(得分:3)

我的猜测是你关闭了错误报告,这就是为什么你只看到这样一个含义少的错误信息 您应该在display_errorserror_reportingphp.ini中启用.htaccess_ss_environment.php(重要提示:将其设置为_config.php将无效,因为它会被错误处理程序覆盖)

我在您的代码中看到的问题是尝试在$this->Ingredients()上使用FragrancesPage,但据我所知,只有类Fragrances有一个方法Ingredients (方法是'magically'为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的一些麻烦,因为它尚未在前端进行全面测试。

(最近有一个问题,我在前端https://stackoverflow.com/a/22059197/1119263写了一些关于GridField问题的内容)
我想这个地方和前面为前端GridFields编写教程/工作示例一样好 我冒昧地重构你的代码以包含编辑功能,它在编辑页面上比在单独的表单上使用GridField要好得多。

如前所述,GridField在前端工作得不好,有一个模块可以缓解疼痛,但它仍然会在边缘粗糙,并且需要你做一些样式才能让它看起来很漂亮。 在PackagistGitHub上找到该模块 (你需要t     

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上戳我