diff --git a/docs/en/topics/datamodel.md b/docs/en/topics/datamodel.md index f11afe2fb..6633690a3 100755 --- a/docs/en/topics/datamodel.md +++ b/docs/en/topics/datamodel.md @@ -569,6 +569,45 @@ the described relations). } } +## Validation and Constraints + +Traditionally, validation in SilverStripe has been mostly handled on the controller +through [form validation](/topics/form-validation). +While this is a useful approach, it can lead to data inconsistencies if the +record is modified outside of the controller and form context. +Most validation constraints are actually data constraints which belong on the model. +SilverStripe provides the `[api:DataObject->validate()]` method for this purpose. + +By default, there is no validation - objects are always valid! +However, you can overload this method in your +DataObject sub-classes to specify custom validation, +or use the hook through `[api:DataExtension]`. + +Invalid objects won't be able to be written - a [api:ValidationException]` +will be thrown and no write will occur. +It is expected that you call validate() in your own application to test that an object +is valid before attempting a write, and respond appropriately if it isn't. + +The return value of `validate()` is a `[api:ValidationResult]` object. +You can append your own errors in there. + +Example: Validate postcodes based on the selected country + + :::php + class MyObject extends DataObject { + static $db = array( + 'Country' => 'Varchar', + 'Postcode' => 'Varchar' + ); + public function validate() { + $result = parent::validate(); + if($this->Country == 'DE' && $this->Postcode && strlen($this->Postcode) != 5) { + $result->error('Need five digits for German postcodes'); + } + return $result; + } + } + ## Maps A map is an array where the array indexes contain data as well as the values. You can build a map diff --git a/docs/en/topics/form-validation.md b/docs/en/topics/form-validation.md index ce1abad68..a6a549537 100644 --- a/docs/en/topics/form-validation.md +++ b/docs/en/topics/form-validation.md @@ -1,15 +1,16 @@ # Form Validation -Form validation is a combination of PHP and JavaScript +SilverStripe provides PHP form validation out of the box, +but doesn't come with any built-in JavaScript validation +(the previously used `Validator.js` approach has been deprecated). -## PHP - -### Introduction - -Validators are implemented as an argument to the `[api:Form]` constructor. You create a required fields validator like -so. In this case, we're creating a `[api:RequiredFields]` validator - the `[api:Validator]` class itself is an abstract -class. +## Required Fields +Validators are implemented as an argument to the `[api:Form]` constructor, +and are subclasses of the abstract `[api:Validator]` base class. +The only implementation which comes with SilverStripe is +the `[api:RequiredFields]` class, which ensures fields are filled out +when the form is submitted. :::php public function Form() { @@ -19,7 +20,7 @@ class. new TextField('MyOptionalField') ), new FieldList( - new FormAction('submit', 'Submit') + new FormAction('submit', 'Submit form') ), new RequiredFields(array('MyRequiredField')) ); @@ -28,7 +29,108 @@ class. return $form; } -### Subclassing Validator +## Form Field Validation + +Form fields are responsible for validating the data they process, +through the `[api:FormField->validate()] method. There are many fields +for different purposes (see ["form field types"](/reference/form-field-types) for a full list). + +## Adding your own validation messages + +In many cases, you want to add PHP validation which is more complex than +validating the format or existence of a single form field input. +For example, you might want to have dependent validation on +a postcode which depends on the country you've selected in a different field. + +There's two ways to go about this: Either you can attach a custom error message +to a specific field, or a generic message for the whole form. + +Example: Validate postcodes based on the selected country (on the controller). + + :::php + class MyController extends Controller { + public function Form() { + return Form::create($this, 'Form', + new FieldList( + new NumericField('Postcode'), + new CountryDropdownField('Country') + ), + new FieldList( + new FormAction('submit', 'Submit form') + ), + new RequiredFields(array('Country')) + ); + } + public function submit($data, $form) { + // At this point, RequiredFields->validate() will have been called already, + // so we can assume that the values exist. + + // German postcodes need to be five digits + if($data['Country'] == 'de' && isset($data['Postcode']) && strlen($data['Postcode']) != 5) { + $form->addErrorMessage('Postcode', 'Need five digits for German postcodes', 'bad'); + return $this->redirectBack(); + } + + // Global validation error (not specific to form field) + if($data['Country'] == 'IR' && isset($data['Postcode']) && $data['Postcode']) { + $form->sessionMessage("Ireland doesn't have postcodes!", 'bad'); + return $this->redirectBack(); + } + + // continue normal processing... + } + } + +## JavaScript Validation + +While there are no built-in JavaScript validation handlers in SilverStripe, +the `FormField` API is flexible enough to provide the information required +in order to plug in custom libraries. + +### HTML5 attributes + +HTML5 specifies some built-in form validations ([source](http://www.w3.org/wiki/HTML5_form_additions)), +which are evaluated by modern browsers without any need for JavaScript. +SilverStripe supports this by allowing to set custom attributes on fields. + + :::php + // Markup contains + TextField::create('MyText')->setAttribute('required', true); + + // Markup contains + TextField::create('MyText') + ->setAttribute('type', 'url') + ->setAttribute('pattern', 'https?://.+') + +### HTML5 metadata + +In addition, HTML5 elements can contain custom data attributes with the `data-` prefix. +These are general purpose attributes, but can be used to hook in your own validation. + + :::php + // Validate a specific date format (in PHP) + // Markup contains + DateField::create('MyDate')->setConfig('dateformat', 'dd.MM.yyyy'); + + // Limit extensions on upload (in PHP) + // Markup contains + $exts = array('jpg', 'jpeg', 'gif'); + $validator = new Upload_Validator(); + $validator->setAllowedExtensions($exts); + $upload = Upload::create()->setValidator($validator); + $fileField = FileField::create('MyFile')->setUpload(new); + $fileField->setAttribute('data-allowed-extensions', implode(',', $exts)); + +Note that these examples don't have any effect on the client as such, +but are just a starting point for custom validation with JavaScript. + +## Model Validation + +An alternative (or additional) approach to validation is to place it directly +on the model. SilverStripe provides a `[api:DataObject->validate()]` method for this purpose. +Refer to the ["datamodel" topic](/topics/datamodel#validation-and-constraints) for more information. + +## Subclassing Validator To create your own validator, you need to subclass validator and define two methods: @@ -36,42 +138,6 @@ To create your own validator, you need to subclass validator and define two meth * **php($data)** Should return true if the given data is valid, and call $this->validationError() if there were any errors. -## JavaScript - -### Default validator.js implementation - -TODO Describe behaviour.js solution easily, how to disable it - -Setting fieldEl.requiredErrorMsg or formEl.requiredErrorMsg will override the default error message. Both can include -the string '$FieldLabel', which will be replaced with the field's label. Otherwise, the message is "Please fill out -"$FieldLabel", it is required". - -You can use Behaviour to load in the appropriate value: - - :::js - Behaviour.register({ - '#Form_Form' : { - requiredErrorMsg: "Please complete this question before moving on.", - } - }); - -### Other validation libraries - -By default, SilverStripe forms with an attached Validator instance use the custom Validator.js clientside logic. It is -quite hard to customize, and might not be appropriate for all use-cases. You can disable integrated clientside -validation, and use your own (e.g. [jquery.validate](http://docs.jquery.com/Plugins/Validation)). - -Disable for all forms (in `mysite/_config.php`): - - :::php - Validator::set_javascript_validation_handler('none'); - -Disable for a specific form: - - :::php - $myForm->getValidator()->setJavascriptValidationHandler('none'); - - ## Related * Model Validation with [api:DataObject->validate()] \ No newline at end of file