Form for add/edit apartment
August 19, 2025 · AI Conversations
$member = Security::getCurrentUser();
$apartment = Apartment::get()->filter('MemberID', $member->ID)->first();
$form=$this->step1Form();
if ($member) {
return $this->customise([
'Form' => $form,
'Title' => 'Inserat erstellen zu Vermietung einer Wohnung',
'ObjectNumber'=>$form->Fields()->dataFieldByName('ObjectNumber')->setValue($apartment->ObjectNumber),
'GroupNumber'=>$form->Fields()->dataFieldByName('GroupNumber')->setValue($apartment->GroupNumber),
'WohnungType'=>$form->Fields()->dataFieldByName('WohnungType')->setValue($apartment->WohnungType),
])->renderWith(['Layout/Apartment/step1', 'Page']);
}else{
return $this->redirect('/login');
}
}
public function step1Form(){
$fields = FieldList::create(
TextField::create('ObjectNumber', 'Objektnummer')->setTitle('Objektnummer'),
TextField::create('GroupNumber', 'Gruppennummer')->setTitle('Gruppennummer'),
OptionsetField::create('WohnungType', 'Wohnung Type*', [
'Wohnung' => 'Wohnung',
'WG Zimmer' => 'WG Zimmer',
'Haus' => 'Haus',
'Wohnung auf Zeit' => 'Wohnung auf Zeit',
]
)->addExtraClass('radio_group'),
);
$actions = FieldList::create(
FormAction::create('doStep1', 'Weiter')->addExtraClass('btn btn-primary')
);
$validator = RequiredFields::create(['ObjectNumber', 'GroupNumber','WohnungType']);
$form = Form::create($this, 'step1Form', $fields, $actions, $validator);
$form->setFormAction('/apartment/doStep1');
return $form;
}
public function doStep1(HTTPRequest $request){
$data = $request->postVars();
// $this->getRequest()->getSession()->set('ObjectNumber', $data['ObjectNumber']);
// $this->getRequest()->getSession()->set('GroupNumber', $data['GroupNumber']);
// $this->getRequest()->getSession()->set('WohnungType', $data['WohnungType']);
$apartment = new Apartment();
$apartment->ObjectNumber = $data['ObjectNumber'];
$apartment->GroupNumber = $data['GroupNumber'];
$apartment->WohnungType = $data['WohnungType'];
$apartment->MemberID = Security::getCurrentUser()->ID;
$apartment->write();
return $this->redirect('/apartment/step2');
}
I want to use same form form funcions for add/edit both
Here’s a clean pattern:
1) Add the actions to the controller (fixes “Action isn’t available” too)
private static $allowed_actions = [
'addapartment',
'step1Form',
'doStep1',
];
2) Build the form once, optionally preloading existing data
public function addapartment(HTTPRequest $request)
{
if (!$member = Security::getCurrentUser()) {
return $this->redirect('/login');
}
// fetch existing apartment for this member (if any)
$apartment = Apartment::get()->filter('MemberID', $member->ID)->first();
// pass record to prefill the form
$form = $this->step1Form($apartment);
return $this->customise([
'Form' => $form,
'Title' => 'Inserat erstellen zu Vermietung einer Wohnung',
])->renderWith(['Layout/Apartment/step1', 'Page']);
}
3) Make the form factory accept an optional record and use `loadDataFrom`
public function step1Form(?Apartment $record = null)
{
$fields = FieldList::create(
TextField::create('ObjectNumber', 'Objektnummer'),
TextField::create('GroupNumber', 'Gruppennummer'),
OptionsetField::create('WohnungType', 'Wohnung Type*', [
'Wohnung' => 'Wohnung',
'WG Zimmer' => 'WG Zimmer',
'Haus' => 'Haus',
'Wohnung auf Zeit' => 'Wohnung auf Zeit',
])->addExtraClass('radio_group')
);
$actions = FieldList::create(
FormAction::create('doStep1', 'Weiter')->addExtraClass('btn btn-primary')
);
$validator = RequiredFields::create(['ObjectNumber','GroupNumber','WohnungType']);
$form = Form::create($this, 'step1Form', $fields, $actions, $validator);
// IMPORTANT: use controller link, not a hardcoded path
$form->setFormAction($this->Link('doStep1'));
// Pre-fill when editing
if ($record) {
$form->loadDataFrom($record);
}
return $form;
}
4) In the action, upsert by `MemberID` and use `saveInto`
public function doStep1($data, Form $form)
{
if (!$member = Security::getCurrentUser()) {
return $this->redirect('/login');
}
// Find existing apartment or create a new one for this member
$apartment = Apartment::get()->filter('MemberID', $member->ID)->first()
?: Apartment::create(['MemberID' => $member->ID]);
// Save form fields into the record
$form->saveInto($apartment);
$apartment->MemberID = $member->ID; // ensure ownership
$apartment->write();
return $this->redirect($this->Link('step2'));
}
### Why this works
– **Single form function** (`step1Form`) is reused for both add & edit.
– **No ID needed**: we always look up by `MemberID` (your “one apartment per member” rule).
– **Prefill on edit** via `loadDataFrom($record)`.
– **Create-or-update** logic in `doStep1` using a simple “find-or-create” pattern.
– **Correct routing** using `$this->Link('doStep1')` avoids hardcoded `/apartment/…` paths and fixes the action-not-available issue (with `allowed_actions` set).
POST /apartment/doStep1
Line 95 in /var/www/html/app/src/ApartmentController.php
### Quick fix (do this now)
1) **Don’t override the form action URL.** Remove your `setFormAction('/apartment/doStep1')`. Let SilverStripe route the submit to the form handler.
2) Keep the submit button as `FormAction::create('doStep1', 'Weiter')`.
3) Ensure `doStep1` has the **two-parameter** signature and `use SilverStripeFormsForm;`.
4) Add the actions to `$allowed_actions`.
### Minimal working code
use SilverStripeFormsForm;
// ...
private static $allowed_actions = [
'addapartment',
'step1Form',
'doStep1',
];
public function step1Form(?Apartment $record = null)
{
$fields = FieldList::create(
TextField::create('ObjectNumber', 'Objektnummer'),
TextField::create('GroupNumber', 'Gruppennummer'),
OptionsetField::create('WohnungType', 'Wohnung Type*', [
'Wohnung' => 'Wohnung',
'WG Zimmer' => 'WG Zimmer',
'Haus' => 'Haus',
'Wohnung auf Zeit' => 'Wohnung auf Zeit',
])->addExtraClass('radio_group')
);
$actions = FieldList::create(
// Name must match the handler: doStep1()
FormAction::create('doStep1', 'Weiter')->addExtraClass('btn btn-primary')
);
$validator = RequiredFields::create(['ObjectNumber', 'GroupNumber', 'WohnungType']);
$form = Form::create($this, 'step1Form', $fields, $actions, $validator);
// IMPORTANT: Let SS set the action to this form endpoint; do NOT point it at /doStep1
// $form->setFormAction($this->Link('step1Form')); // (optional) you can set this, or just omit entirely
if ($record) {
$form->loadDataFrom($record);
}
return $form;
}
public function doStep1($data, Form $form)
{
if (!$member = Security::getCurrentUser()) {
return $this->redirect('/login');
}
$apartment = Apartment::get()
->filter('MemberID', $member->ID)
->first() ?: Apartment::create(['MemberID' => $member->ID]);
$form->saveInto($apartment);
$apartment->MemberID = $member->ID;
$apartment->write();
return $this->redirect($this->Link('step2'));
}
### Why this resolves it
– Posting to the **form endpoint** (`step1Form`) means SilverStripe invokes `doStep1($data, Form $form)` with the correct 2 parameters.
– Pointing directly to `/doStep1` makes it a **controller action**, which only receives `HTTPRequest`. That’s what caused the mismatch.
If you *really* wanted to keep posting to `/doStep1`, then change the signature to `doStep1(HTTPRequest $request)` **and** manually read `$request->postVars()`—but you’d lose the nice `saveInto()` flow and CSRF handling. The form-handler approach above is the right way.
$data = $request->postVars();
// $this->getRequest()->getSession()->set('ObjectNumber', $data['ObjectNumber']);
// $this->getRequest()->getSession()->set('GroupNumber', $data['GroupNumber']);
// $this->getRequest()->getSession()->set('WohnungType', $data['WohnungType']);
$apartment = new Apartment();
$apartment->ObjectNumber = $data['ObjectNumber'];
$apartment->GroupNumber = $data['GroupNumber'];
$apartment->WohnungType = $data['WohnungType'];
$apartment->MemberID = Security::getCurrentUser()->ID;
$apartment->write();
return $this->redirect('/apartment/step2');
}
I want to use above so adjust in above
– Point the form to the **controller action** (`$this->Link('doStep1')`).
– In `doStep1`, **find-or-create** by `MemberID` and **update**.
– (Optional but recommended) **CSRF check** with `SecurityToken`.
### 1) Allow the actions
private static $allowed_actions = [
'addapartment',
'step1Form',
'doStep1',
];
### 2) Controller action to render the form (prefill if editing)
public function addapartment(HTTPRequest $request)
{
if (!$member = Security::getCurrentUser()) {
return $this->redirect('/login');
}
$apartment = Apartment::get()->filter('MemberID', $member->ID)->first();
$form = $this->step1Form();
// Prefill on edit
if ($apartment) {
$form->loadDataFrom($apartment);
}
return $this->customise([
'Form' => $form,
'Title' => 'Inserat erstellen zu Vermietung einer Wohnung',
])->renderWith(['Layout/Apartment/step1','Page']);
}
### 3) One form for both add + edit (posts to controller)
public function step1Form()
{
$fields = FieldList::create(
TextField::create('ObjectNumber', 'Objektnummer'),
TextField::create('GroupNumber', 'Gruppennummer'),
OptionsetField::create('WohnungType', 'Wohnung Type*', [
'Wohnung' => 'Wohnung',
'WG Zimmer' => 'WG Zimmer',
'Haus' => 'Haus',
'Wohnung auf Zeit' => 'Wohnung auf Zeit',
])->addExtraClass('radio_group')
);
$actions = FieldList::create(
FormAction::create('doStep1', 'Weiter')->addExtraClass('btn btn-primary')
);
$validator = RequiredFields::create(['ObjectNumber','GroupNumber','WohnungType']);
$form = Form::create($this, 'step1Form', $fields, $actions, $validator);
// IMPORTANT: point to the controller action (since we're using HTTPRequest signature)
$form->setFormAction($this->Link('doStep1'));
return $form;
}
### 4) Your preferred handler, adjusted for add/edit
use SilverStripeSecuritySecurityToken;
public function doStep1(HTTPRequest $request)
{
// Optional but good: CSRF check when bypassing Form handlers
if (!SecurityToken::inst()->checkRequest($request)) {
return $this->httpError(400, 'Invalid security token');
}
if (!$member = Security::getCurrentUser()) {
return $this->redirect('/login');
}
$data = $request->postVars();
// Find existing apartment for this member or create one
$apartment = Apartment::get()->filter('MemberID', $member->ID)->first();
if (!$apartment) {
$apartment = Apartment::create();
$apartment->MemberID = $member->ID;
}
// Update fields (same form for add/edit)
$apartment->ObjectNumber = $data['ObjectNumber'] ?? $apartment->ObjectNumber;
$apartment->GroupNumber = $data['GroupNumber'] ?? $apartment->GroupNumber;
$apartment->WohnungType = $data['WohnungType'] ?? $apartment->WohnungType;
$apartment->write();
return $this->redirect($this->Link('step2'));
}
#### Notes
– Using the controller action means SilverStripe **won’t** run server-side `RequiredFields` automatically like a form handler would. You still get client-side validation. If you need server-side checks, add simple guards in `doStep1` (e.g., verify `$data['ObjectNumber']` etc. and set a session error + redirect back).
– If you ever switch to the **form handler** style (`doStep1($data, Form $form)`), remove `setFormAction()` and let SS wire it automatically.
Given your recent code, the most likely culprit is a **missing comma** in your `Apartment` DataObject:
private static $db = [
'ObjectNumber' => 'Varchar',
'GroupNumber' => 'Varchar',
'WohnungType' => 'Varchar',
'Uberschrift' => 'HTMLText',
'Mietwohnung' => 'HTMLText',
'Dieumgebung' => 'HTMLText',
'BilderId' => 'Int',
'VideoId' => 'Int',
'DocumentId' => 'Int' // << missing comma here
'GrundId' => 'Int',
'ContactId'=>'Int',
'MemberID' => 'Int'
];
### Fix it
'DocumentId' => 'Int',
'GrundId' => 'Int',
—
### Quick recovery checklist (do these in order)
1) **Syntax check** the changed files (catches commas, braces, etc.)
php -l app/src/Model/Apartment.php
php -l app/src/ApartmentController.php
2) **Ensure dev mode** in `.env` (helps show detailed errors)
SS_ENVIRONMENT_TYPE="dev"
3) **Flush caches + build** (CLI shows full stack traces)
vendor/bin/sake dev/build flush=all
# or, if using php-fpm user:
php vendor/bin/sake dev/build "flush=all"
4) If it still 500s in browser, **check logs**:
– PHP/Apache: `/var/log/apache2/error.log` (or nginx/php-fpm equivalent)
– SilverStripe: `silverstripe.log` in project root (if configured)
5) Common gotchas to scan for quickly:
– **YAML** in `app/_config/*.yml`: tabs, bad indent, wrong class names.
– **Namespace / class name** mismatches (e.g. `ApartmentController` vs `AppartmentController`).
– **Autoload** after adding/moving classes:
composer dump-autoload -o
– **DB creds** in `.env` (wrong DB causes 500 early in build).
Fix the comma, flush, and run `dev/build` again—you should be good.
—
### 1) Run the build from CLI (you’ll get the stack trace)
# from project root
php vendor/bin/sake dev/build "flush=all"
– If that prints an error, that’s your smoking gun.
– If `php` points to the wrong version, specify the right binary (e.g. `php8.1`).
—
### 2) Verify PHP version compatibility (common gotcha!)
Silverstripe CMS 4.x **does not** support PHP 8.3. If your server is on 8.3 you’ll get opaque 500s.
– Check runtime PHP:
php -v
– Check your SS version:
composer show silverstripe/framework | grep versions -i
**If you’re on SS4 and PHP 8.3:**
Downgrade PHP to **8.0/8.1** (or upgrade project to **Silverstripe CMS 5** which supports newer PHP). Then:
composer install
vendor/bin/sake dev/build "flush=all"
—
### 3) Surface the error in logs
– Web server/PHP log: `/var/log/apache2/error.log` (or php-fpm/nginx equivalent)
– Silverstripe log (if configured): `silverstripe.log` in project root
Tail while you hit `/dev/build?flush=all`:
tail -f /var/log/apache2/error.log
—
### 4) Lint and autoload (fast sanity checks)
# Syntax check likely-changed files
php -l app/src/Model/Apartment.php
php -l app/src/ApartmentController.php
# Rebuild autoload classmap
composer dump-autoload -o
—
### 5) YAML/config issues (very common after edits)
A single bad `_config/*.yml` line will 500.
– Look for tabs, mis‑indentation, stray colons, or wrong class names.
– Temporarily move recent YAML changes out of `_config` to confirm.
—
### 6) Injector/Controller signatures
You switched `doStep1` to `HTTPRequest $request`. That’s fine, but make sure:
– It’s listed in `private static $allowed_actions = ['doStep1','step1Form','addapartment'];`
– The form posts to the controller action:
$form->setFormAction($this->Link('doStep1'));
If you accidentally left a `FormAction::create('doStep1', …)` **without** setting the form action, SS will try the form-handler signature and mismatches can fatal during manifest/build in some setups.
—
### 7) Database connectivity
Bad DB creds or driver issues cause `/dev/build` 500s early.
– Confirm `.env`:
SS_DATABASE_CLASS="MySQLDatabase"
SS_DATABASE_NAME="..."
SS_DATABASE_SERVER="localhost"
SS_DATABASE_USERNAME="..."
SS_DATABASE_PASSWORD="..."
– Test connection quickly:
php -r 'new PDO("mysql:host=localhost;dbname=YOURDB;charset=utf8mb4","USER","PASS"); echo "okn";'
—
### 8) Cache/permissions
– Ensure web/PHP user can write `public/assets/` and any cache dirs.
– Flush hard:
vendor/bin/sake dev/build "flush=all"
If you’re using a file cache that got corrupted, remove any `silverstripe-cache` or `/.cache` directories if present.
—
### 9) Class/file naming & namespaces
– File `app/src/Model/Apartment.php` must declare:
namespace AppModel;
use SilverStripeORMDataObject;
class Apartment extends DataObject { ... }
– Controller file name and class must match (e.g. `ApartmentController` not `AppartmentController`).
—
### 10) If still blank 500
Paste the first error you get from **step 1 or 3** (CLI or error log). That one line will point exactly to the faulty file/line, and I’ll pinpoint the fix.
### What typically causes it
– `echo`, `print_r`, `var_dump`, `die`, `exit`, `dd()` left in:
– `app/_config.php` or any `_config/*.php`
– `requireDefaultRecords()` in any `DataObject`
– constructors, `onBefore/AfterWrite`, extensions
– your controller actions (e.g., temporary debug)
– **BOM or trailing whitespace** before “ in any PHP that loads during bootstrap.
– Accidental output in `mysite/_config.php` (older projects) or `app/_config/*.php`.
### Quick way to find & fix
1) **Search for debug output**
grep -RIn --color=always -E 'var_dump|print_rs*(|echos+|dds*(|dies*(|exits*(' app/
vendor/silverstripe/ -n | head -50
(You’re mainly looking in your `app/` and any custom modules.)
2) **Find files with BOM (UTF‑8 byte order mark)**
# lists files that start with BOM
grep -RIl $'xEFxBBxBF' app/ *.php
If any show up, **re-save as UTF‑8 (no BOM)** and **remove any closing `?>`** at EOF.
3) **Find trailing whitespace after closing tag**
grep -RIn --color=always -E '?>s*$' app/
Open those files and **delete the closing tag** `?>` (best practice for pure PHP files) to prevent stray spaces/newlines.
4) **Check your likely suspects**
– `app/_config.php` and everything in `app/_config/*.php`
– Any recent `DataObject` you edited (e.g., `Apartment`), especially if you added `requireDefaultRecords()`.
– Your `ApartmentController` or any extensions where you may have dumped something.
5) **Retry build from CLI to see real error**
php vendor/bin/sake dev/build "flush=all"
### If you still get the warning
Turn on output buffering at entry (temporary, just to surface the underlying error clearly), then remove after fixing:
// public/index.php, very top:
ob_start();
Run `/dev/build?flush=all`, check the detailed error/stack. Once you’ve removed the offending output, **remove `ob_start()`**.
—
### Fast checklist
– No `echo/var_dump/print_r/dd/die/exit` in any bootstrapped PHP.
– No `?>` at the end of PHP files.
– No BOM in files (especially under `app/_config/`).
– Re-run: `php vendor/bin/sake dev/build "flush=all"`.
Send me any match you get from steps 1–3 (file + line), and I’ll pinpoint the exact fix.
we want to handle this rather than full page error show
[Emergency] Uncaught SilverStripeORMValidationException: Das Passwort ist zu kurz, es muss mindestens 8 Zeichen lang sein
POST /public/registration/doRegister
Line 1422 in /home/wmb/public_html/vendor/silverstripe/framework/src/ORM/DataObject.php
### Recommended (form handler signature)
use SilverStripeORMValidationException;
use SilverStripeFormsForm;
use SilverStripeSecurityMember;
use SilverStripeSecuritySecurity;
public function doRegister($data, Form $form)
{
try {
$member = Member::create();
$form->saveInto($member); // includes Password field
$member->write(); // <-- can throw ValidationException
// (optional) log them in, then redirect
return $this->redirect('/public/dashboard');
} catch (ValidationException $e) {
// Push field-specific messages when available
$result = $e->getResult();
foreach ($result->getMessages() as $msg) {
$field = $msg['fieldName'] ?? null;
$text = $msg['message'] ?? $result->message();
if ($field) {
$form->addErrorMessage($field, $text, 'bad'); // show under that field
} else {
$form->sessionMessage($text, 'bad'); // top-level form error
}
}
// keep user-entered data on the form
$form->setSessionData($data);
return $this->redirectBack();
}
}
### If you’re using `HTTPRequest` (no `Form $form` available)
use SilverStripeORMValidationException;
use SilverStripeSecurityMember;
public function doRegister(SilverStripeControlHTTPRequest $request)
{
$data = $request->postVars();
try {
$member = Member::create();
$member->FirstName = $data['FirstName'] ?? '';
$member->Surname = $data['Surname'] ?? '';
$member->Email = $data['Email'] ?? '';
$member->Password = $data['Password'] ?? '';
$member->write(); // throws if too short, etc.
return $this->redirect('/public/dashboard');
} catch (ValidationException $e) {
// Store error + repopulate data manually
$session = $request->getSession();
$session->set('FormError.Register', $e->getResult()->message());
$session->set('FormData.Register', $data);
return $this->redirectBack();
}
}
> In your template or controller that renders the form, read `FormError.Register` and `FormData.Register` to show the message and prefill fields.
### Improve UX on the form itself
– Add client-side min length (doesn’t replace server-side validation):
PasswordField::create('Password', 'Passwort')
->setAttribute('minlength', 8)
->setAttribute('autocomplete', 'new-password');
– (Optional) Use `ConfirmedPasswordField` for password + confirm:
use SilverStripeFormsConfirmedPasswordField;
ConfirmedPasswordField::create('Password', 'Passwort')
->setAttribute('minlength', 8);
With this, short passwords won’t crash the page—users will see a friendly error on the form and can fix it immediately.
$fields = FieldList::create(
EmailField::create('Email', 'Emailadresse')->addExtraClass('form-control')->setAttribute('placeholder', 'Emailadresse')->setValue($emailAdrs),
PasswordField::create('Password', 'Passwort')->addExtraClass('form-control')->setAttribute('placeholder', 'Passwort'),
PasswordField::create('RePassword', 'Passwort wiederholen')->addExtraClass('form-control')->setAttribute('placeholder', 'Passwort wiederholen')
);
made password input to min 8 and Password and Repassword match before submit form
—
### 1) Update your fields (HTML5 min length + pattern)
use SilverStripeFormsEmailField;
use SilverStripeFormsPasswordField;
use SilverStripeFormsFieldList;
use SilverStripeViewRequirements;
$fields = FieldList::create(
EmailField::create('Email', 'Emailadresse')
->addExtraClass('form-control')
->setAttribute('placeholder', 'Emailadresse'),
PasswordField::create('Password', 'Passwort')
->addExtraClass('form-control')
->setAttribute('placeholder', 'Passwort')
->setAttribute('required', true)
->setAttribute('minlength', 8) // HTML5 check
->setAttribute('pattern', '.{8,}') // fallback for some browsers
->setAttribute('title', 'Mindestens 8 Zeichen'),
PasswordField::create('RePassword', 'Passwort wiederholen')
->addExtraClass('form-control')
->setAttribute('placeholder', 'Passwort wiederholen')
->setAttribute('required', true)
->setAttribute('minlength', 8)
->setAttribute('pattern', '.{8,}')
->setAttribute('title', 'Mindestens 8 Zeichen')
);
—
### 2) Client-side “match” check (blocks submit nicely)
Add this where you build the form (after `$form = Form::create(…)`):
$form->addExtraClass('js-register-form');
Requirements::customScript(<<<'JS'
(function(){
var f = document.querySelector('form.js-register-form');
if (!f) return;
f.addEventListener('submit', function(e){
var p = f.querySelector('input[name="Password"]');
var r = f.querySelector('input[name="RePassword"]');
if (!p || !r) return;
// HTML5 will already enforce minlength, but double-check:
if ((p.value || '').length < 8) {
alert('Das Passwort muss mindestens 8 Zeichen lang sein.');
e.preventDefault(); return;
}
if (p.value !== r.value) {
alert('Passwort und Wiederholung stimmen nicht überein.');
e.preventDefault(); return;
}
});
})();
JS);
—
### 3) Server-side guard (no crash, friendly message)
#### If your handler is `doRegister($data, Form $form)`:
use SilverStripeORMValidationException;
public function doRegister($data, Form $form)
{
// Simple server checks before write()
if (strlen($data['Password'] ?? '') < 8) {
$form->addErrorMessage('Password', 'Das Passwort muss mindestens 8 Zeichen lang sein.', 'bad');
$form->setSessionData($data);
return $this->redirectBack();
}
if (($data['Password'] ?? '') !== ($data['RePassword'] ?? '')) {
$form->addErrorMessage('RePassword', 'Passwort und Wiederholung stimmen nicht überein.', 'bad');
$form->setSessionData($data);
return $this->redirectBack();
}
try {
$member = Member::create();
$form->saveInto($member);
$member->write(); // still may throw for other rules
return $this->redirect('/public/dashboard');
} catch (ValidationException $e) {
$form->sessionMessage($e->getResult()->message(), 'bad');
$form->setSessionData($data);
return $this->redirectBack();
}
}
#### If your handler is `doRegister(HTTPRequest $request)`:
use SilverStripeORMValidationException;
public function doRegister(SilverStripeControlHTTPRequest $request)
{
$data = $request->postVars();
if (strlen($data['Password'] ?? '') < 8) {
$request->getSession()->set('FormError.Register', 'Das Passwort muss mindestens 8 Zeichen lang sein.');
$request->getSession()->set('FormData.Register', $data);
return $this->redirectBack();
}
if (($data['Password'] ?? '') !== ($data['RePassword'] ?? '')) {
$request->getSession()->set('FormError.Register', 'Passwort und Wiederholung stimmen nicht überein.');
$request->getSession()->set('FormData.Register', $data);
return $this->redirectBack();
}
try {
$member = Member::create();
$member->Email = $data['Email'] ?? '';
$member->Password = $data['Password'] ?? '';
$member->write();
return $this->redirect('/public/dashboard');
} catch (ValidationException $e) {
$request->getSession()->set('FormError.Register', $e->getResult()->message());
$request->getSession()->set('FormData.Register', $data);
return $this->redirectBack();
}
}
> In the template/controller that renders the form: read `FormError.Register` / `FormData.Register` to show the error and repopulate inputs.
—
### Optional (cleaner): use `ConfirmedPasswordField`
Instead of two separate fields, you can use:
use SilverStripeFormsConfirmedPasswordField;
$password = ConfirmedPasswordField::create('Password', 'Passwort');
$password->setAttribute('minlength', 8)->setAttribute('title', 'Mindestens 8 Zeichen');
$fields->push($password);
This gives you built-in **server-side match validation**, and you still keep the length checks above. Keep the server-side try/catch to avoid any full-page exceptions.
public function doRegister(HTTPRequest $request) {
$data = $request->postVars();
$this->getRequest()->getSession()->set('emailAddress', $data['Email']);
// Validate passwords match
if ($data['Password'] !== $data['RePassword']) {
$this->getRequest()->getSession()->set('FormError', 'Die Passwörter stimmen nicht überein. Bitte gib dasselbe Passwort ein.');
return $this->redirect('/registration');
}
// Check if email already exists
$existingMember = Member::get()->filter('Email', $data['Email'])->first();
if ($existingMember) {
$this->getRequest()->getSession()->set('FormError', 'E-Mail-Adresse bereits registriert.');
return $this->redirect('/registration');
}
// Create Member for login
$member = Member::create();
$member->Email = $data['Email'];
$member->changePassword($data['Password']);
$member->write();
$member->regenerateTempID();
$request->getSession()->set('userName', $data['Email']);
$request->getSession()->set('userPsd', $data['Password']);
$tempIDHash = $this->getMemberFieldValue($member->ID, 'TempIDHash');
$confirmationLink = Director::absoluteURL("/registration/validateuser/{$tempIDHash}");
$this->emailVerificationTemplate($confirmationLink, $data['Email']);
return $this->redirect("/registration/checkemail/{$tempIDHash}");
}
Add the `use` lines:
use SilverStripeORMValidationException;
use SilverStripeSecuritySecurityToken;
use SilverStripeCoreInjectorInjector;
use PsrLogLoggerInterface;
use SilverStripeControlDirector;
use SilverStripeSecurityMember;
Then replace your method with this:
public function doRegister(HTTPRequest $request)
{
$session = $request->getSession();
$data = $request->postVars();
// Remember the email in case of errors
$session->set('emailAddress', $data['Email'] ?? '');
// (Optional) CSRF check when bypassing Form handlers
if (!SecurityToken::inst()->checkRequest($request)) {
$session->set('FormError', 'Sicherheits-Token ist ungültig. Bitte versuche es erneut.');
return $this->redirect('/registration');
}
$email = trim((string)($data['Email'] ?? ''));
$password = (string)($data['Password'] ?? '');
$repassword = (string)($data['RePassword'] ?? '');
// Server-side guards (keep UX friendly; no exceptions)
if ($password !== $repassword) {
$session->set('FormError', 'Die Passwörter stimmen nicht überein. Bitte gib dasselbe Passwort ein.');
return $this->redirect('/registration');
}
if (strlen($password) < 8) {
$session->set('FormError', 'Das Passwort muss mindestens 8 Zeichen lang sein.');
return $this->redirect('/registration');
}
// Email already exists?
if (Member::get()->filter('Email', $email)->exists()) {
$session->set('FormError', 'E-Mail-Adresse bereits registriert.');
return $this->redirect('/registration');
}
try {
// Create member
$member = Member::create();
$member->Email = $email;
// Validate/set password (this can fail validation)
$result = $member->changePassword($password);
if ($result && method_exists($result, 'isValid') && !$result->isValid()) {
// Extract validator messages (e.g., too short, needs numbers, etc.)
$messages = array_map(function ($m) {
return is_array($m) && !empty($m['message']) ? $m['message'] : '';
}, $result->getMessages());
$msg = trim(implode(' ', array_filter($messages))) ?: 'Ungültiges Passwort.';
$session->set('FormError', $msg);
return $this->redirect('/registration');
}
// Persist
$member->write();
$member->regenerateTempID();
} catch (ValidationException $e) {
// Friendly error from Silverstripe validators (e.g., password policy)
$msg = $e->getResult() ? $e->getResult()->message() : $e->getMessage();
$session->set('FormError', $msg ?: 'Validierungsfehler bei der Registrierung.');
return $this->redirect('/registration');
} catch (Throwable $e) {
// Anything else (DB/email/etc.)—log and show generic error
Injector::inst()->get(LoggerInterface::class)->error($e->getMessage());
$session->set('FormError', 'Unerwarteter Fehler bei der Registrierung. Bitte versuche es erneut.');
return $this->redirect('/registration');
}
// Success — continue with verification flow
$session->set('userName', $email);
$session->set('userPsd', $password); // ⚠️ Consider NOT storing plain passwords in session.
$tempIDHash = $this->getMemberFieldValue($member->ID, 'TempIDHash');
$confirmationLink = Director::absoluteURL("/registration/validateuser/{$tempIDHash}");
// Don’t fail the whole flow if email sending throws
try {
$this->emailVerificationTemplate($confirmationLink, $email);
} catch (Throwable $e) {
Injector::inst()->get(LoggerInterface::class)->warning('Verification email failed: '.$e->getMessage());
$session->set('FormNotice', 'Registrierung erfolgreich, aber die Bestätigungs-E-Mail konnte nicht gesendet werden. Bitte kontaktiere den Support.');
}
return $this->redirect("/registration/checkemail/{$tempIDHash}");
}
**What this does**
– Blocks common errors (short password, mismatch, duplicate email) without exceptions.
– Catches `ValidationException` from password rules and shows the validator message.
– Logs unexpected errors and shows a safe, generic error.
– Keeps the email prefilled via session.
– Continues your verification flow on success.
**Tip:** avoid storing the plaintext password in session (`userPsd`). If you need to auto-login later, consider using a token/one-time login link instead.
use SilverStripeViewRequirements;
—not `SilverStripeControlRequirements`.
### Fix
– In any PHP file where you call `Requirements::…`, change the import:
// wrong
// use SilverStripeControlRequirements;
// correct
use SilverStripeViewRequirements;
– Or call it fully qualified:
SilverStripeViewRequirements::customScript('/* js */');
### If you added it in a template
Prefer the template API instead of PHP:
<% require javascript('app/client/registration.js') %>
<% require inlineScript $YourInlineJS %>
### After changing
composer dump-autoload -o
vendor/bin/sake dev/build "flush=all"
That will clear the “Class … Requirements not found” error.