-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Background
In PR #1790, we introduced the handleEntityWrite method in BaseController to provide centralized error handling for entity write operations. This method specifically handles schema validation exceptions and presents validation errors in a user-friendly format.
Current Implementation
The handleEntityWrite method (in zmsadmin/src/Zmsadmin/BaseController.php) provides:
- Centralized exception handling for entity write operations
- Automatic transformation of validation errors to user-friendly format
- Consistent error display using the
schemavalidation.twigtemplate - Fallback to appropriate error templates when available
Already Implemented
The following controllers have been updated to use handleEntityWrite:
Department.phpDepartmentAddScope.phpOrganisation.phpProfile.phpScope.phpSourceEdit.phpUseraccountAdd.phpUseraccountEdit.php
Controllers That Need Implementation
The following controllers currently perform direct readPostResult calls without centralized error handling and would benefit from handleEntityWrite:
1. Owner.php (Line 35)
Why: Updates owner entities via POST. Currently redirects on success but lacks validation error handling. Users won't see helpful validation messages if the API rejects the input.
Current code:
$entity = \App::$http->readPostResult('/owner/' . $entity->id . '/', $entity)
->getEntity();Impact: Medium - Owner updates are administrative operations that benefit from clear validation feedback.
2. OwnerAdd.php (Line 28)
Why: Creates new owner entities via POST. Without proper error handling, validation failures during owner creation won't be presented clearly to users.
Current code:
$entity = \App::$http->readPostResult('/owner/add/', $entity)
->getEntity();Impact: Medium - Owner creation is less frequent but critical for system setup.
3. OwnerAddOrganisation.php (Line 28)
Why: Creates organisations under an owner via POST. Validation errors (e.g., missing required fields, invalid data) should be displayed to help users correct their input.
Current code:
$entity = \App::$http->readPostResult('/owner/' . $parentId . '/organisation/', $entity)
->getEntity();Impact: High - Organisation creation is a common administrative task and proper validation feedback is important.
4. OrganisationAddDepartment.php (Line 32)
Why: Creates departments under an organisation via POST. Complex form with dayoff lists and links that can have validation errors.
Current code:
$department = \App::$http->readPostResult('/organisation/' . $organisationId . '/department/', $entity)
->getEntity();Impact: High - Department creation is frequent and the form has multiple fields that can fail validation.
5. DepartmentAddCluster.php (Line 33)
Why: Creates clusters under a department via POST. Involves scope assignments that can have validation errors.
Current code:
$entity = \App::$http
->readPostResult('/department/' . $department->id . '/cluster/', $entity)
->getEntity();Impact: Medium - Cluster creation involves complex relationships that benefit from validation feedback.
6. Cluster.php (Line 45)
Why: Updates cluster entities via POST. Currently lacks error handling for validation failures during updates.
Current code:
$entity = \App::$http->readPostResult('/cluster/' . $entity->id . '/', $entity)->getEntity();Impact: Medium - Cluster updates should provide clear feedback when validation fails.
7. ConfigInfo.php (Line 49)
Why: Updates system configuration via POST. Configuration changes should have robust validation to prevent system misconfigurations.
Current code:
$entity = \App::$http->readPostResult(
'/config/',
$entity
)->getEntity();Impact: High - Configuration changes are critical system operations where validation errors must be clearly communicated.
8. DayoffByYear.php (Line 35)
Why: Updates dayoff collections via POST. Validation errors (e.g., invalid dates, missing names) should be displayed to users.
Current code:
\App::$http->readPostResult('/dayoff/' . $year . '/', $collection);Impact: Medium - Dayoff management is important for scheduling but less frequently performed.
9. TicketprinterStatusByScope.php (Line 45)
Why: Updates scope status and preferences via POST. Should provide validation feedback if preferences are invalid.
Current code:
$scope = \App::$http->readPostResult('/scope/' . $scope->id . '/', $scope)->getEntity();Impact: Low-Medium - Status updates are generally simple but should still handle validation errors gracefully.
Implementation Guidelines
For each controller:
-
Create a helper method (following the pattern established in this PR):
protected function writeUpdatedEntity($input, $entityId = null) { $entity = (new Entity($input))->withCleanedUpFormData(); // ... entity preparation ... return $this->handleEntityWrite(function () use ($entity) { return \App::$http->readPostResult('/endpoint/', $entity)->getEntity(); }); }
-
Update the readResponse method to:
- Call the new helper method
- Check if result is an Entity (success) or array (error)
- Redirect on success
- Preserve submitted input and pass error data to template on failure
-
Update templates to:
- Display validation errors using the error data structure
- Repopulate form fields with submitted data on validation errors
-
Add form repopulation logic:
- Create a variable (e.g.,
$entityData) that uses submitted input on error or current entity data otherwise - Pass this to the template for form rendering
- Create a variable (e.g.,
Benefits
Implementing handleEntityWrite in these controllers will:
- Provide consistent validation error handling across the admin interface
- Improve user experience by showing clear, actionable error messages
- Reduce confusion when API validation fails
- Maintain form state when validation errors occur
- Follow the established pattern for error handling in the codebase
Priority
High priority: ConfigInfo, OrganisationAddDepartment, OwnerAddOrganisation
Medium priority: Owner, OwnerAdd, Cluster, DepartmentAddCluster, DayoffByYear
Low priority: TicketprinterStatusByScope
Related
- PR chore(ZMSKVR): fix schema validation in zmsadmin forms and remove random password generation when editing basic auth users #1790
zmsadmin/src/Zmsadmin/BaseController.phpzmsadmin/templates/exception/bo/zmsentities/exception/schemavalidation.twig
Architectural Assessment
Is This a Clean Solution for Handling JSON Errors?
Yes, this is a clean and well-designed solution for handling JSON validation errors without a standard framework.
✅ Strong Points
1. Centralized Error Handling
The handleEntityWrite method eliminates scattered try-catch blocks across controllers:
// Before: Repeated in every controller
try {
$entity = App::$http->readPostResult(...)->getEntity();
} catch (\BO\Zmsclient\Exception $exception) {
// Handle error...
}
// After: Single reusable wrapper
return $this->handleEntityWrite(function () use ($entity) {
return App::$http->readPostResult(...)->getEntity();
});2. Smart Error Transformation
Converting JSON pointers to field names bridges the gap between backend JSON Schema validation and frontend form fields:
// Transforms "/id" → "id", "/contact/email" → "contact/email"
$fieldName = (strpos($pointer, '/') === 0) ? ltrim($pointer, '/') : $pointer;3. Graceful Degradation
The fallback logic handles various scenarios:
- Schema validation → custom template with transformed errors
- Known exception templates → render if template exists
- Unknown exceptions → rethrow for higher-level handling
4. User Experience
- Preserves form state on validation errors
- Filters out technical "must match schema" messages
- Provides clear, actionable error messages
- Consistent error display across all forms
📋 Considerations
Mixed Return Types
Returns either Entity (success) or array (error):
$result = $this->writeUpdatedEntity($input, $entityId);
if ($result instanceof Entity) {
// Success path
} else {
// Error path - $result is array
}This is acceptable for PHP without strict typing when consistently checking with instanceof.
Manual Form Repopulation
Each controller needs logic like:
$entityData = ($result instanceof Entity)
? $entity->toArray()
: array_merge($entity->toArray(), $input);This is reasonable without a framework's form component and could be extracted to a helper method if it becomes repetitive.
🎯 Comparison to Framework Approaches
This solution mirrors what frameworks provide:
| Framework Pattern | This Solution |
|---|---|
| Symfony Form Component + Validator | handleEntityWrite + transformValidationErrors |
Laravel's validate() + redirectBack()->withErrors() |
Entity/array return + form repopulation |
| Framework middleware | BaseController method |
For a non-framework codebase, this is architecturally sound.
💡 Optional Future Enhancement
Consider a Result/Response object for type safety (optional, not necessary):
class WriteResult {
public function __construct(
public ?Entity $entity = null,
public ?array $errors = null
) {}
public function isSuccess(): bool { return $this->entity !== null; }
}However, the current approach is perfectly clean and practical given the codebase constraints.
Summary
This solution:
- ✅ Follows DRY principle
- ✅ Provides consistent error handling
- ✅ Enhances user experience
- ✅ Maintains code clarity
- ✅ Scales well (9 additional controllers can adopt it)
This is a well-thought-out approach that brings framework-like validation handling to a non-framework codebase without over-engineering.