diff --git a/lib/Traits/CanModifyWordPressDatabase.php b/lib/Traits/CanModifyWordPressDatabase.php index 56e8556..626d99c 100644 --- a/lib/Traits/CanModifyWordPressDatabase.php +++ b/lib/Traits/CanModifyWordPressDatabase.php @@ -3,9 +3,12 @@ namespace PHPNomad\Integrations\WordPress\Traits; use PHPNomad\Datastore\Exceptions\DatastoreErrorException; +use PHPNomad\Integrations\WordPress\Traits\LogsDatabaseErrors; trait CanModifyWordPressDatabase { + use LogsDatabaseErrors; + /** * @param string $query * @param ...$args @@ -20,7 +23,8 @@ protected function wpdbQuery(string $query, ...$args): void $wpdb->query($this->maybePrepare($query, ...$args)); if ($wpdb->last_error) { - throw new DatastoreErrorException('Query responded with error: ' . $wpdb->last_error); + $this->logDatabaseError('Query failed', $wpdb->last_error); + throw new DatastoreErrorException('Query failed.'); } } diff --git a/lib/Traits/CanQueryWordPressDatabase.php b/lib/Traits/CanQueryWordPressDatabase.php index 143d530..c42a864 100644 --- a/lib/Traits/CanQueryWordPressDatabase.php +++ b/lib/Traits/CanQueryWordPressDatabase.php @@ -10,9 +10,12 @@ use PHPNomad\Integrations\WordPress\Database\ClauseBuilder; use PHPNomad\Integrations\WordPress\Database\QueryBuilder as WordPressQueryBuilder; use PHPNomad\Utils\Helpers\Arr; +use PHPNomad\Integrations\WordPress\Traits\LogsDatabaseErrors; trait CanQueryWordPressDatabase { + use LogsDatabaseErrors; + use CanGetDataFormats; /** @@ -28,15 +31,16 @@ protected function wpdbGetResults(QueryBuilder $queryBuilder): array $query = $queryBuilder->build(); $result = $wpdb->get_results($query, ARRAY_A); } catch (QueryBuilderException $e) { - throw new DatastoreErrorException('Get results failed. Invalid query: ' . $e->getMessage(), 500, $e); + throw new DatastoreErrorException('Get results failed: invalid query.', 500, $e); } if (is_null($result)) { - throw new DatastoreErrorException('Get results failed - ' . $wpdb->last_error); + $this->logDatabaseError('Get results failed', $wpdb->last_error); + throw new DatastoreErrorException('Get results failed.'); } if (empty($result)) { - throw new RecordNotFoundException('No records found for query: ' . $query); + throw new RecordNotFoundException('No records found for the query.'); } return $result; @@ -61,10 +65,11 @@ protected function wpdbGetRow(QueryBuilder $queryBuilder): array if (!$result) { if (!empty($wpdb->last_error)) { - throw new DatastoreErrorException('Get row failed - ' . $wpdb->last_error); + $this->logDatabaseError('Get row failed', $wpdb->last_error); + throw new DatastoreErrorException('Get row failed.'); } - throw new RecordNotFoundException('No record found for query: ' . $query); + throw new RecordNotFoundException('No record found for the query.'); } return $result; @@ -88,7 +93,8 @@ protected function wpdbInsert(Table $table, array $data): array } if (false === $inserted) { - throw new DatastoreErrorException('Insert failed - ' . $wpdb->last_error); + $this->logDatabaseError('Insert failed', $wpdb->last_error); + throw new DatastoreErrorException('Insert failed.'); } $fields = $table->getFieldsForIdentity(); @@ -127,7 +133,8 @@ protected function wpdbUpdate(Table $table, array $data, array $where): void $result = $wpdb->update($table->getName(), $data, $where, $this->getFormats($data), $this->getFormats($where)); if (false === $result) { - throw new DatastoreErrorException('Update failed - ' . $wpdb->last_error); + $this->logDatabaseError('Update failed', $wpdb->last_error); + throw new DatastoreErrorException('Update failed.'); } // When no records were updated, we need to figure out if this is because the record couldn't be found. @@ -173,7 +180,8 @@ protected function wpdbDelete(Table $table, array $where): void global $wpdb; if (false === $wpdb->delete($table->getName(), $where, $this->getFormats($where))) { - throw new DatastoreErrorException('Delete failed - ' . $wpdb->last_error); + $this->logDatabaseError('Delete failed', $wpdb->last_error); + throw new DatastoreErrorException('Delete failed.'); } } @@ -197,9 +205,10 @@ protected function wpdbGetVar(QueryBuilder $queryBuilder): string if (is_null($result)) { if (empty($wpdb->last_error)) { - throw new RecordNotFoundException('No value found for query: ' . $query); + throw new RecordNotFoundException('No value found for the query.'); } else { - throw new DatastoreErrorException('Get var failed - ' . $wpdb->last_error); + $this->logDatabaseError('Get var failed', $wpdb->last_error); + throw new DatastoreErrorException('Get var failed.'); } } diff --git a/lib/Traits/LogsDatabaseErrors.php b/lib/Traits/LogsDatabaseErrors.php new file mode 100644 index 0000000..fd85d5e --- /dev/null +++ b/lib/Traits/LogsDatabaseErrors.php @@ -0,0 +1,25 @@ +createMock(QueryBuilder::class); $queryBuilder->expects($this->once()) @@ -53,9 +53,22 @@ public function getResults(QueryBuilder $queryBuilder): array }; $this->expectException(DatastoreErrorException::class); - $this->expectExceptionMessage('Get results failed - Replica read failed'); - - $subject->getResults($queryBuilder); + // The thrown message must stay stable and free of MySQL error detail — + // it can surface in REST payloads or rendered fatals. The detail is + // recorded via error_log instead (redirected to a temp file below). + $this->expectExceptionMessage('Get results failed.'); + + $errorLog = tempnam(sys_get_temp_dir(), 'phpnomad-log'); + $previousLog = ini_set('error_log', $errorLog); + + try { + $subject->getResults($queryBuilder); + } finally { + ini_set('error_log', $previousLog); + $logged = file_get_contents($errorLog); + unlink($errorLog); + $this->assertStringContainsString('Replica read failed', $logged); + } } public function testWpdbUpdateIncludesTableIdentityAndPayloadWhenRecordIsMissing(): void