class ZoneMetadataController extends PublicApiController (View source)

Constants

protected MAX_PAGE_SIZE

Properties

protected PDO $db from  BaseController
protected array $requestData from  BaseController
protected MessageService $messageService from  BaseController
protected ConfigurationManager $config from  BaseController
protected LoggerInterface $logger from  PublicApiController
protected Request $request from  AbstractApiController
protected array $pathParameters from  PublicApiController
protected int $authenticatedUserId from  PublicApiController
protected ApiKeyScope|null $apiKeyScope

Permission scope of the API key used for this request. Null when the request authenticated via HTTP Basic auth or no key scope could be resolved, in which case {self::getApiKeyScope()} returns an unrestricted scope.

from  PublicApiController

Methods

void
run()

Abstract method to be implemented by subclasses to run the controller logic.

__construct(array $request, array $pathParameters = [])

PublicApiController constructor

bool
requiresCsrfValidation()

API requests authenticate per call with an API key or HTTP Basic credentials, so there is no session-bound form token to check.

bool
isPost()

Checks if the current request is a POST request.

getConfig()

Gets the application configuration.

mixed
getModuleConfig(string $module, string $key, mixed $default = null)

Get a module config value with legacy fallback.

void
render(string $template, array $params)

Renders a template with the given parameters.

getPdnsCapabilities()

Build a PdnsCapabilities snapshot from the session-cached PowerDNS version. Constant-time and synchronous - never triggers detection, never makes a network call. Safe to call from render() on every page.

getRecordTypeCapabilities()

Capabilities to use when filtering selectable record types, or null to skip version filtering entirely.

void
refreshPdnsCapabilities()

Trigger a session-cached refresh of PowerDNS version + capabilities.

void
validateCsrfToken()

Validates the CSRF token from the request.

void
redirect(string $url, array $args = [])

Redirects to a specified URL with optional arguments.

void
setMessage(string $script, string $type, string $content)

Sets a message to be displayed for a specific script.

array|null
getMessages(string $script)

Gets messages for a specific script.

void
checkCondition(bool $condition, string $errorMessage)

Checks a condition and displays an error message if the condition is true.

string
presentPagination(int $totalItems, int $itemsPerPage, string $path, array $queryParams = [])

Renders a pagination widget for a paginated listing.

bool
hasPermission(string $permission)

Check if the logged-in user has the given permission

bool
getWideLayout()

Whether the logged-in user wants the page to span the full browser width

bool
isZoneOwner(int $zoneId)

Check if the logged-in user owns the given zone directly or via group membership

void
requireZoneView(int $zoneId)

Stops the request unless the current user may open the zone (showError exits).

createZoneManagementService()

The zone service the API uses, told what the connected server supports.

moduleRegistry()

Enabled modules, loaded once per request.

array
moduleCapabilityData(string $capability, array $context = [])

What the enabled modules offer for a capability (wizard actions, lookup links, export formats), honouring each module's admin restriction.

bool
moduleProvides(string $capability)

No description

string|null
zoneOwnerOptionsBlocker()

Why the current user cannot pick any zone owner (groups_only mode), or null.

resolveZoneOwnershipFromForm(Request $request)

Owner and groups of the add-zone forms, checked against the shared ownership rules.

array
groupNamesById()

Group names keyed by id, for the group column of the zone lists.

void
rectifyZoneAfterWrite(string $zoneName)

Rebuilds ordername/auth after a record write so signed zones keep answering.

getRepositoryFactory(DnsBackendProvider|null $backendProvider = null)

No description

int|null
getCurrentUserId()

Get current user ID

void
reportZoneWrite(string $script, ZoneWriteResult $result, string $successMessage)

Flash the outcome of a zone write to a page: the given text on success, the result's reason on failure.

getUserContextService()

Get the user context service

void
setCurrentPage(string $page)

Sets the current page identifier used for navigation highlighting.

void
setPageTitle(string $title)

Sets the page title displayed in the header.

void
checkPermission(string $permission, string $errorMessage)

Checks if the user has a specific permission and displays an error message if not.

void
showError(string $error, string|null $recordName = null)

Displays an error message.

array
getRequest()

Gets the request data.

string
getSafeRequestValue(string $key)

Gets a safe value from the request data.

void
setValidationConstraints(array $constraints)

Sets validation constraints for the request data.

void
setRequestRules(array $rules)

Sets validation rules for the request data.

bool
doValidateRequest(array|null $data = null)

Validates the request data.

void
showFirstValidationError(array|null $data = null)

Displays the first validation error.

void
addSystemMessage(string $type, string $content)

Adds a system-wide message that will be displayed on any page

bool
isJsonRequest()

Checks if the current request is a JSON request

array|null
getJsonInput()

Get JSON input from request body

JsonResponse
returnJsonResponse(mixed $data, int $status = 200, array $headers = [])

Override to record every public API response in the audit log

JsonResponse
returnErrorResponse(string $message, int $status = 400, string|null $code = null)

Return an error response

string|null
inputString(array $input, string $key, string|null $default = null)

Extract a string value from input array.

int|null
inputInt(array $input, string $key, int|null $default = null)

Extract an integer value from input array.

bool|null
inputBool(array $input, string $key, bool|null $default = null)

Extract a boolean value from input array.

int|null
inputIntFromBool(array $input, string $key, int|null $default = 0)

Extract an integer value that also accepts JSON booleans and "true"/"false" strings.

string
inputTemplate(array $input)

Extract a template ID from input. Accepts string IDs, "none", and JSON integers.

void
authenticateApiRequest()

Authenticate the API request using API key or HTTP Basic auth

string|null
getApiKeyFromRequest()

Get API key from request headers

bool
validateApiKey(string|null $apiKey)

Validate the API key

bool
requiresAuthentication()

Check if the current API endpoint requires authentication Can be overridden in child classes for public endpoints

JsonResponse
returnApiResponse(mixed $data, bool $success = true, string|null $message = null, int $status = 200, array $additionalFields = [])

Return API response with standard format

JsonResponse
returnApiError(string $message, int $status = 400, mixed $data = null, array $headers = [])

Return API error response

string
zoneEditDeniedMessage(string|null $zoneType)

Pick the right "cannot edit this zone's records" message: read-only zones (Secondary, Consumer) replicate from a primary and are rejected for a different reason than a missing edit permission. Keeps the public error contract accurate for both cases.

int
getAuthenticatedUserId()

Get the authenticated user ID (stateless)

string
getAuthenticatedUsername()

No description

getApiKeyScope()

Get the permission scope of the API key for this request. Requests without a key scope (Basic auth, or an unresolvable key) are treated as unrestricted.

void
enforceApiKeyMethodScope()

Reject the request with 403 when the API key's read-only/operation scope does not permit the HTTP method. This is a request-global gate.

array
requiredApiKeyOperations()

Operations the current request performs, all of which the API key must permit. Defaults to the single operation implied by the HTTP method.

void
sendApiKeyOperationForbidden()

Send a 403 for an operation the API key may not perform, and stop.

JsonResponse|null
enforceApiKeyZoneScope(int $zoneId)

Guard a zone-scoped endpoint against the API key's zone restriction.

string
formatV2RecordContent(string $type, string $content)

Apply V2 record-content formatting.

string
recordWriteErrorMessage(RecordWriteResult $result, string $backendFailureText)

The API wording for a refused record write: a duplicate and a backend fault keep the contract strings, every other refusal carries the manager's reason.

string
normalizeV2RecordName(string $name, string $zoneName)

Record names are stored as punycode and always carry the zone suffix.

string
stripTxtQuotes(string $content, string $type)

Strip quotes from single-string TXT records for V2 API responses

JsonResponse
handleException(Throwable $e, string $context, string $userMessage = 'An error occurred', int $statusCode = 500)

Handle exception and return JSON error response

Details

void run()

Abstract method to be implemented by subclasses to run the controller logic.

Return Value

void

__construct(array $request, array $pathParameters = [])

PublicApiController constructor

Parameters

array $request
array $pathParameters

Optional path parameters for RESTful routes

protected bool requiresCsrfValidation()

API requests authenticate per call with an API key or HTTP Basic credentials, so there is no session-bound form token to check.

Return Value

bool

bool isPost()

Checks if the current request is a POST request.

Return Value

bool

True if the request method is POST, false otherwise.

ConfigurationManager getConfig()

Gets the application configuration.

Return Value

ConfigurationManager

The application configuration.

protected mixed getModuleConfig(string $module, string $key, mixed $default = null)

Get a module config value with legacy fallback.

Checks modules.. first, then falls back to . for backward compatibility with pre-module config layouts.

Parameters

string $module
string $key
mixed $default

Return Value

mixed

void render(string $template, array $params)

Renders a template with the given parameters.

Parameters

string $template

The template to render.

array $params

The parameters to pass to the template.

Return Value

void

protected PdnsCapabilities getPdnsCapabilities()

Build a PdnsCapabilities snapshot from the session-cached PowerDNS version. Constant-time and synchronous - never triggers detection, never makes a network call. Safe to call from render() on every page.

Controllers that want a freshly-detected version should call refreshPdnsCapabilities() explicitly before rendering so they own the latency cost rather than imposing it on every other page.

Return Value

PdnsCapabilities

protected PdnsCapabilities|null getRecordTypeCapabilities()

Capabilities to use when filtering selectable record types, or null to skip version filtering entirely.

Only API backends can report their PowerDNS version, so only there can we safely hide types a newer-than-server feature would need. On SQL backends the version is never known, and strict-unknown filtering would wrongly and permanently drop valid types (HTTPS, SVCB, ZONEMD, ...) from every form. Returning null there leaves the configured/default type list intact.

Return Value

PdnsCapabilities|null

protected void refreshPdnsCapabilities()

Trigger a session-cached refresh of PowerDNS version + capabilities.

Makes at most one API call per minute (rate-limited via the session timestamp pdns_version_last_attempt) and is a no-op when no PowerDNS API is configured. Runs in both API and SQL backend modes: the version display is useful in either mode whenever pdns_api is configured, even though capability gates only matter for API mode. Page renders that don't call this just read whatever is already cached.

Return Value

void

void validateCsrfToken()

Validates the CSRF token from the request.

Return Value

void

void redirect(string $url, array $args = [])

Redirects to a specified URL with optional arguments.

Automatically prepends base_url_prefix for subfolder deployments.

Parameters

string $url

The URL to redirect to.

array $args

The arguments to pass as query parameters.

Return Value

void

void setMessage(string $script, string $type, string $content)

Sets a message to be displayed for a specific script.

Parameters

string $script

The script to set the message for.

string $type

The type of message (error, warning, success, info).

string $content

The content of the message.

Return Value

void

array|null getMessages(string $script)

Gets messages for a specific script.

Parameters

string $script

The script to get messages for.

Return Value

array|null

The messages for the script, or null if no messages are set.

void checkCondition(bool $condition, string $errorMessage)

Checks a condition and displays an error message if the condition is true.

Parameters

bool $condition

The condition to check.

string $errorMessage

The error message to display if the condition is true.

Return Value

void

protected UserPreferenceService createUserPreferenceService()

No description

Return Value

UserPreferenceService

protected UserTimezoneService createUserTimezoneService()

No description

Return Value

UserTimezoneService

protected PaginationService createPaginationService()

No description

Return Value

PaginationService

protected string presentPagination(int $totalItems, int $itemsPerPage, string $path, array $queryParams = [])

Renders a pagination widget for a paginated listing.

$path is the route with the {PageNumber} placeholder already in place (e.g. '/zones/forward?start={PageNumber}'). $queryParams are extra key => value pairs appended urlencoded, in order, skipping absent and blank values.

Parameters

int $totalItems
int $itemsPerPage
string $path
array $queryParams

Return Value

string

protected DnsBackendProvider createDnsBackendProvider()

No description

Return Value

DnsBackendProvider

protected DnsDataService createDnsDataService()

No description

Return Value

DnsDataService

protected ZoneRepositoryInterface createZoneRepository()

No description

protected DomainRepositoryInterface createDomainRepository()

No description

protected RecordRepositoryInterface createRecordRepository()

No description

protected UserRepository createUserRepository()

No description

Return Value

UserRepository

protected PermissionService createPermissionService()

No description

Return Value

PermissionService

protected UserManagementService createUserManagementService()

No description

Return Value

UserManagementService

protected ApiPermissionService createApiPermissionService()

No description

Return Value

ApiPermissionService

protected bool hasPermission(string $permission)

Check if the logged-in user has the given permission

Parameters

string $permission

Return Value

bool

protected bool getWideLayout()

Whether the logged-in user wants the page to span the full browser width

Return Value

bool

protected bool isZoneOwner(int $zoneId)

Check if the logged-in user owns the given zone directly or via group membership

Parameters

int $zoneId

Return Value

bool

protected void requireZoneView(int $zoneId)

Stops the request unless the current user may open the zone (showError exits).

Parameters

int $zoneId

Return Value

void

protected UserGroupRepositoryInterface createUserGroupRepository()

No description

protected ZoneManagementService createZoneManagementService()

The zone service the API uses, told what the connected server supports.

Return Value

ZoneManagementService

protected ModuleRegistry moduleRegistry()

Enabled modules, loaded once per request.

Return Value

ModuleRegistry

protected array moduleCapabilityData(string $capability, array $context = [])

What the enabled modules offer for a capability (wizard actions, lookup links, export formats), honouring each module's admin restriction.

Parameters

string $capability
array $context

Placeholders for the modules' url patterns, e.g. zone_id

Return Value

array

protected bool moduleProvides(string $capability)

No description

Parameters

string $capability

Return Value

bool

protected AuditService createAuditService()

No description

Return Value

AuditService

protected ZoneMetadataService createZoneMetadataService()

No description

Return Value

ZoneMetadataService

protected ZoneSigningService createZoneSigningService()

No description

Return Value

ZoneSigningService

protected ZoneCreateOwnershipResolver createZoneCreateOwnershipResolver()

No description

protected string|null zoneOwnerOptionsBlocker()

Why the current user cannot pick any zone owner (groups_only mode), or null.

Return Value

string|null

protected ZoneOwnershipResolution resolveZoneOwnershipFromForm(Request $request)

Owner and groups of the add-zone forms, checked against the shared ownership rules.

Parameters

Request $request

Return Value

ZoneOwnershipResolution

protected UserGroupMemberRepositoryInterface createUserGroupMemberRepository()

No description

protected DbPermissionTemplateRepository createPermissionTemplateRepository()

No description

protected PermissionTemplateWriteService createPermissionTemplateWriteService()

No description

protected DashboardStatsService createDashboardStatsService()

No description

Return Value

DashboardStatsService

protected ZoneEditService createZoneEditService()

No description

Return Value

ZoneEditService

protected ZoneListPermissionService createZoneListPermissionService()

No description

protected array groupNamesById()

Group names keyed by id, for the group column of the zone lists.

Return Value

array

protected ZoneGroupRepositoryInterface createZoneGroupRepository()

No description

protected ReverseTtlResolver createReverseTtlResolver()

No description

Return Value

ReverseTtlResolver

protected RecordManagerInterface createRecordManager()

No description

Return Value

RecordManagerInterface

protected SOARecordManagerInterface createSOARecordManager()

No description

protected DnssecProvider createDnssecProvider()

No description

Return Value

DnssecProvider

protected void rectifyZoneAfterWrite(string $zoneName)

Rebuilds ordername/auth after a record write so signed zones keep answering.

Call after the transaction commits: the rectifier reads committed rows.

Parameters

string $zoneName

Return Value

void

protected RecordManagerService createRecordManagerService()

No description

Return Value

RecordManagerService

protected RecordAddService createRecordAddService()

No description

Return Value

RecordAddService

protected ReverseRecordCreator createReverseRecordCreator()

No description

Return Value

ReverseRecordCreator

protected BatchReverseRecordCreator createBatchReverseRecordCreator()

No description

protected DomainManagerInterface createDomainManager()

No description

Return Value

DomainManagerInterface

protected CatalogZoneService createCatalogZoneService()

No description

Return Value

CatalogZoneService

protected RepositoryFactory getRepositoryFactory(DnsBackendProvider|null $backendProvider = null)

No description

Parameters

DnsBackendProvider|null $backendProvider

Return Value

RepositoryFactory

protected int|null getCurrentUserId()

Get current user ID

Return Value

int|null

protected void reportZoneWrite(string $script, ZoneWriteResult $result, string $successMessage)

Flash the outcome of a zone write to a page: the given text on success, the result's reason on failure.

Parameters

string $script
ZoneWriteResult $result
string $successMessage

Return Value

void

protected UserContextService getUserContextService()

Get the user context service

Return Value

UserContextService

protected void setCurrentPage(string $page)

Sets the current page identifier used for navigation highlighting.

Parameters

string $page

The page identifier

Return Value

void

protected void setPageTitle(string $title)

Sets the page title displayed in the header.

Parameters

string $title

The page title

Return Value

void

void checkPermission(string $permission, string $errorMessage)

Checks if the user has a specific permission and displays an error message if not.

Parameters

string $permission

The permission to check.

string $errorMessage

The error message to display if the user does not have the permission.

Return Value

void

void showError(string $error, string|null $recordName = null)

Displays an error message.

Parameters

string $error

The error message to display.

string|null $recordName

Optional record name for context

Return Value

void

array getRequest()

Gets the request data.

Return Value

array

The request data.

string getSafeRequestValue(string $key)

Gets a safe value from the request data.

Parameters

string $key

The key to get the value for.

Return Value

string

The safe value.

void setValidationConstraints(array $constraints)

Sets validation constraints for the request data.

Parameters

array $constraints

The validation constraints.

Return Value

void

void setRequestRules(array $rules)

Sets validation rules for the request data.

Parameters

array $rules

The validation rules.

Return Value

void

bool doValidateRequest(array|null $data = null)

Validates the request data.

Parameters

array|null $data

Optional data to validate. If not provided, uses $this->requestData

Return Value

bool

True if the request data is valid, false otherwise.

void showFirstValidationError(array|null $data = null)

Displays the first validation error.

Parameters

array|null $data

Optional data to validate. If not provided, uses $this->requestData

Return Value

void

void addSystemMessage(string $type, string $content)

Adds a system-wide message that will be displayed on any page

Parameters

string $type

The type of message (error, warning, success, info)

string $content

The content of the message

Return Value

void

protected bool isJsonRequest()

Checks if the current request is a JSON request

Return Value

bool

True if the request is JSON, false otherwise

protected array|null getJsonInput()

Get JSON input from request body

Return Value

array|null

Decoded JSON data or null if invalid

protected JsonResponse returnJsonResponse(mixed $data, int $status = 200, array $headers = [])

Override to record every public API response in the audit log

Parameters

mixed $data

The data to return

int $status

HTTP status code

array $headers

Additional headers to include

Return Value

JsonResponse

protected JsonResponse returnErrorResponse(string $message, int $status = 400, string|null $code = null)

Return an error response

Parameters

string $message

Error message

int $status

HTTP status code

string|null $code

Optional error code

Return Value

JsonResponse

protected string|null inputString(array $input, string $key, string|null $default = null)

Extract a string value from input array.

Returns default only when key is absent; returns null for present non-string values so callers can distinguish missing from malformed.

Parameters

array $input
string $key
string|null $default

Return Value

string|null

protected int|null inputInt(array $input, string $key, int|null $default = null)

Extract an integer value from input array.

Accepts integers and numeric strings. Returns default when key is absent; returns null for present non-numeric values.

Parameters

array $input
string $key
int|null $default

Return Value

int|null

protected bool|null inputBool(array $input, string $key, bool|null $default = null)

Extract a boolean value from input array.

Accepts native booleans, int 1/0, and strings "1"/"0"/"true"/"false" for form-encoded compatibility. Returns default when key is absent; returns null for present invalid values.

Parameters

array $input
string $key
bool|null $default

Return Value

bool|null

protected int|null inputIntFromBool(array $input, string $key, int|null $default = 0)

Extract an integer value that also accepts JSON booleans and "true"/"false" strings.

Use for fields like 'disabled' where the API documents boolean but stores as int. Returns default when key is absent; returns null for present invalid values.

Parameters

array $input
string $key
int|null $default

Return Value

int|null

protected string inputTemplate(array $input)

Extract a template ID from input. Accepts string IDs, "none", and JSON integers.

Rejects booleans, arrays, and non-numeric strings.

Parameters

array $input

Return Value

string

protected void authenticateApiRequest()

Authenticate the API request using API key or HTTP Basic auth

Return Value

void

protected string|null getApiKeyFromRequest()

Get API key from request headers

Return Value

string|null

The API key or null if not found

protected bool validateApiKey(string|null $apiKey)

Validate the API key

Parameters

string|null $apiKey

The API key to validate

Return Value

bool

True if the API key is valid, false otherwise

protected bool requiresAuthentication()

Check if the current API endpoint requires authentication Can be overridden in child classes for public endpoints

Return Value

bool

True if authentication is required, false otherwise

protected JsonResponse returnApiResponse(mixed $data, bool $success = true, string|null $message = null, int $status = 200, array $additionalFields = [])

Return API response with standard format

Parameters

mixed $data

The data to return

bool $success

Whether the request was successful

string|null $message

Optional message

int $status

HTTP status code

array $additionalFields

Additional response fields (pagination, meta, etc.)

Return Value

JsonResponse

The JSON response object

protected JsonResponse returnApiError(string $message, int $status = 400, mixed $data = null, array $headers = [])

Return API error response

Parameters

string $message

Error message

int $status

HTTP status code

mixed $data

Additional error data

array $headers

Additional headers

Return Value

JsonResponse

The JSON response object

protected string zoneEditDeniedMessage(string|null $zoneType)

Pick the right "cannot edit this zone's records" message: read-only zones (Secondary, Consumer) replicate from a primary and are rejected for a different reason than a missing edit permission. Keeps the public error contract accurate for both cases.

Parameters

string|null $zoneType

Zone kind (MASTER, SLAVE, NATIVE, CONSUMER) when known

Return Value

string

Error message describing why record edits are not allowed

protected int getAuthenticatedUserId()

Get the authenticated user ID (stateless)

Return Value

int

The authenticated user ID or 0 if not authenticated

protected string getAuthenticatedUsername()

No description

Return Value

string

protected ApiKeyScope getApiKeyScope()

Get the permission scope of the API key for this request. Requests without a key scope (Basic auth, or an unresolvable key) are treated as unrestricted.

Return Value

ApiKeyScope

The resolved scope, never null

protected void enforceApiKeyMethodScope()

Reject the request with 403 when the API key's read-only/operation scope does not permit the HTTP method. This is a request-global gate.

Return Value

void

protected array requiredApiKeyOperations()

Operations the current request performs, all of which the API key must permit. Defaults to the single operation implied by the HTTP method.

Override for endpoints where the method is not a 1:1 operation mapping: return the exact set (e.g. [create, update] for an upsert), or [] to skip the central check and enforce the operation scope inside the handler.

Return Value

array

protected void sendApiKeyOperationForbidden()

Send a 403 for an operation the API key may not perform, and stop.

Return Value

void

protected JsonResponse|null enforceApiKeyZoneScope(int $zoneId)

Guard a zone-scoped endpoint against the API key's zone restriction.

Returns a 403 response when the zone is out of scope, or null when allowed. Callers return the response directly: if (($r = $this->enforceApiKeyZoneScope($id)) !== null) { return $r; }

Parameters

int $zoneId

The zone (domain) ID the request targets

Return Value

JsonResponse|null

A 403 response, or null when the zone is in scope

protected string formatV2RecordContent(string $type, string $content)

Apply V2 record-content formatting.

V2 always quotes single-string TXT records, even when dns.txt_auto_quote is off, so records round-trip: create quotes, read strips, update must re-quote. Used by create and update paths alike so stored content stays consistent.

Parameters

string $type
string $content

Return Value

string

protected string recordWriteErrorMessage(RecordWriteResult $result, string $backendFailureText)

The API wording for a refused record write: a duplicate and a backend fault keep the contract strings, every other refusal carries the manager's reason.

Parameters

RecordWriteResult $result
string $backendFailureText

Return Value

string

protected string normalizeV2RecordName(string $name, string $zoneName)

Record names are stored as punycode and always carry the zone suffix.

Parameters

string $name
string $zoneName

Return Value

string

protected string stripTxtQuotes(string $content, string $type)

Strip quotes from single-string TXT records for V2 API responses

V2 responses present single-string TXT content unquoted regardless of stored form (zone records are force-quoted on write, template records only when dns.txt_auto_quote is on). Multi-string TXT records (e.g., "part1" "part2") are preserved as-is since they represent long values split across multiple strings.

Parameters

string $content

The TXT record content from database

string $type

The record type

Return Value

string

The formatted content (quotes stripped for single-string TXT records)

protected JsonResponse handleException(Throwable $e, string $context, string $userMessage = 'An error occurred', int $statusCode = 500)

Handle exception and return JSON error response

Catches all throwables (Exception, TypeError, Error, etc.) and returns a proper JSON error response instead of letting PHP display HTML errors. Logs detailed error information for debugging.

Parameters

Throwable $e

The exception/error to handle

string $context

Context description (e.g., method name)

string $userMessage

User-friendly error message

int $statusCode

HTTP status code

Return Value

JsonResponse

JSON error response