Skip to content

Commit c006def

Browse files
committed
Prepare 3.2.0 release
1 parent 2ced5f9 commit c006def

4 files changed

Lines changed: 57 additions & 55 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ history, the old changelog, and committed file changes. Older Zemit-era entries
1515
are summarized where the commit history is too granular to be useful as
1616
release notes.
1717

18-
## 3.1.5 - Unreleased
18+
## 3.2.0 - 2026-06-16
1919

2020
### Added
2121

src/Mvc/Model/Behavior/Blameable.php

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,6 @@ public function createAudit(string $type, Model $model): bool
193193

194194
$audit = $this->requireAuditRecord($audit);
195195

196-
if (!$this->hasAuditModelStorage($audit)) {
197-
return true;
198-
}
199-
200196
// Populate core audit metadata
201197
$audit->setModel($model::class);
202198
$audit->setTable($model->getSource());
@@ -224,52 +220,49 @@ public function createAudit(string $type, Model $model): bool
224220
if ($this->isAuditDetailEnabled()) {
225221
$details = [];
226222
$detailClass = $this->auditDetailClass;
227-
$detailPrototype = $this->requireAuditDetailRecord(new $detailClass());
228-
229-
if ($this->hasAuditModelStorage($detailPrototype)) {
230-
foreach ($columns as $column) {
231-
$map = $columnMap[$column] ?? $column;
232-
$type = $columnTypes[$column] ?? null;
233-
234-
$before = $this->normalizeValue($snapshot[$map] ?? null, $type);
235-
$after = $this->normalizeValue($model->readAttribute($map), $type);
236-
237-
// Skip unchanged fields on update
238-
if (
239-
$event === 'update' &&
240-
$changed !== null &&
241-
$snapshot !== null &&
242-
($before === $after || !in_array($map, $changed, true))
243-
) {
244-
continue;
245-
}
246-
247-
$detail = $this->requireAuditDetailRecord(new $detailClass());
248-
249-
$detail->setColumn($column);
250-
$detail->setBefore($before);
251-
$detail->setAfter($after);
252-
253-
// Legacy compatibility fields
254-
$detail->assign([
255-
'model' => $audit->getModel(),
256-
'table' => $audit->getTable(),
257-
'primary' => $audit->getPrimary(),
258-
'event' => $event,
259-
'map' => $map,
260-
]);
261-
262-
$details[] = $detail;
263-
}
264223

265-
if ($details !== []) {
266-
$audit->assign(['AuditDetailList' => $details]);
224+
foreach ($columns as $column) {
225+
$map = $columnMap[$column] ?? $column;
226+
$type = $columnTypes[$column] ?? null;
227+
228+
$before = $this->normalizeValue($snapshot[$map] ?? null, $type);
229+
$after = $this->normalizeValue($model->readAttribute($map), $type);
230+
231+
// Skip unchanged fields on update
232+
if (
233+
$event === 'update' &&
234+
$changed !== null &&
235+
$snapshot !== null &&
236+
($before === $after || !in_array($map, $changed, true))
237+
) {
238+
continue;
267239
}
240+
241+
$detail = $this->requireAuditDetailRecord(new $detailClass());
242+
243+
$detail->setColumn($column);
244+
$detail->setBefore($before);
245+
$detail->setAfter($after);
246+
247+
// Legacy compatibility fields
248+
$detail->assign([
249+
'model' => $audit->getModel(),
250+
'table' => $audit->getTable(),
251+
'primary' => $audit->getPrimary(),
252+
'event' => $event,
253+
'map' => $map,
254+
]);
255+
256+
$details[] = $detail;
257+
}
258+
259+
if ($details !== []) {
260+
$audit->assign(['AuditDetailList' => $details]);
268261
}
269262
}
270263

271264
// Persist audit (and details via relationship)
272-
$saved = $audit->save();
265+
$saved = $this->saveAuditRecord($audit);
273266

274267
// Propagate audit validation errors back to the source model
275268
foreach ($audit->getMessages() as $message) {
@@ -341,24 +334,20 @@ protected function requireAuditRecord(mixed $audit): AuditInterface&Model
341334
}
342335

343336
/**
344-
* Return true when the configured audit model has backing metadata.
337+
* Save the audit record unless audit storage is explicitly absent.
345338
*
346339
* Missing audit tables are optional in some applications, so a concrete
347-
* `TableNotInDatabase` skips audit creation. An unavailable local database
348-
* is not the same signal; let the normal save path or test double decide
349-
* instead of silently disabling audit.
340+
* `TableNotInDatabase` skips audit creation. The guard is intentionally at
341+
* the save boundary so fake/custom audit models that override persistence
342+
* still run normally.
350343
*/
351-
protected function hasAuditModelStorage(Model $model): bool
344+
protected function saveAuditRecord(AuditInterface&Model $audit): bool
352345
{
353346
try {
354-
$model->getModelsMetaData()->getAttributes($model);
347+
return $audit->save();
355348
} catch (TableNotInDatabase) {
356-
return false;
357-
} catch (\PDOException) {
358349
return true;
359350
}
360-
361-
return true;
362351
}
363352

364353
/**

tests/Unit/Mvc/Model/BehaviorAndTraitsTest.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,12 @@ public function testBlameableNotifyAndCreateAudit(): void
610610
$this->assertSame('Audit.event', $model->messages[0]->getField());
611611

612612
FakeAudit::$messages = [];
613+
FakeAudit::reset();
614+
FakeAudit::$throwMissingTableOnSave = true;
615+
$this->assertTrue($behavior->createAudit('afterUpdate', $model));
616+
$this->assertNull(FakeAudit::$last);
617+
618+
FakeAudit::reset();
613619
$noSnapshot = new ModelBehaviorDouble();
614620
$this->assertFalse($behavior->notify('beforeUpdate', $noSnapshot));
615621

tests/Unit/Mvc/Model/Fixtures/FakeAudit.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313

1414
namespace PhalconKit\Tests\Unit\Mvc\Model\Fixtures;
1515

16+
use Phalcon\Mvc\Model\MetaData\Exceptions\TableNotInDatabase;
1617
use Phalcon\Mvc\ModelInterface;
1718
use PhalconKit\Models\Audit;
1819

1920
class FakeAudit extends Audit
2021
{
2122
public static ?self $last = null;
2223
public static bool $saveResult = true;
24+
public static bool $throwMissingTableOnSave = false;
2325
public static array $messages = [];
2426
public static mixed $nextId = 77;
2527

@@ -45,6 +47,10 @@ public function assign(array $data, $whiteList = null, $dataColumnMap = null): M
4547
#[\Override]
4648
public function save(): bool
4749
{
50+
if (self::$throwMissingTableOnSave) {
51+
throw new TableNotInDatabase($this->getSource(), self::class);
52+
}
53+
4854
self::$last = $this;
4955
$this->id = self::$nextId;
5056
return self::$saveResult;
@@ -60,6 +66,7 @@ public static function reset(): void
6066
{
6167
self::$last = null;
6268
self::$saveResult = true;
69+
self::$throwMissingTableOnSave = false;
6370
self::$messages = [];
6471
self::$nextId = 77;
6572
}

0 commit comments

Comments
 (0)