code standards tightening

This commit is contained in:
Funky Waddle 2025-12-15 13:29:43 -06:00
parent 875cd8bb46
commit 45b9987764
6 changed files with 144 additions and 19 deletions

View file

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
php: [ '8.1', '8.2', '8.3' ]
php: [ '8.2', '8.3' ]
services:
mysql:
image: mysql:8

2
.gitignore vendored
View file

@ -48,3 +48,5 @@ com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
.junie.json

View file

@ -1 +1 @@
{"version":2,"defects":{"Pairity\\Tests\\BelongsToManyMysqlTest::testBelongsToManyEagerAndHelpers":1,"Pairity\\Tests\\BelongsToManySqliteTest::testBelongsToManyEagerAndPivotHelpers":7,"Pairity\\Tests\\CastersAndAccessorsSqliteTest::testCustomCasterAndDtoAccessorsMutators":7,"Pairity\\Tests\\JoinEagerMysqlTest::testJoinEagerHasManyAndBelongsTo":1,"Pairity\\Tests\\JoinEagerSqliteTest::testHasManyJoinEagerWithProjectionAndSoftDeleteScope":7,"Pairity\\Tests\\JoinEagerSqliteTest::testBelongsToJoinEagerSingleLevel":8,"Pairity\\Tests\\MongoAdapterTest::testCrudCycle":1,"Pairity\\Tests\\MongoDaoTest::testCrudViaDao":8,"Pairity\\Tests\\MongoOptimisticLockTest::testVersionIncrementOnUpdate":8,"Pairity\\Tests\\MongoPaginationTest::testPaginateAndSimplePaginateWithScopes":8,"Pairity\\Tests\\MongoRelationsTest::testEagerAndLazyRelations":8,"Pairity\\Tests\\MysqlSmokeTest::testCreateAndDropTable":1,"Pairity\\Tests\\PaginationSqliteTest::testPaginateAndSimplePaginateWithScopesAndRelations":7,"Pairity\\Tests\\RelationsNestedConstraintsSqliteTest::testNestedEagerAndPerRelationFieldsConstraint":7,"Pairity\\Tests\\SchemaBuilderSqliteTest::testCreateAlterDropCycle":8,"Pairity\\Tests\\UnitOfWorkCascadeMongoTest::testDeleteByIdCascadesToChildren":8,"Pairity\\Tests\\UnitOfWorkCascadeSqliteTest::testDeleteByIdCascadesToChildren":7,"Pairity\\Tests\\UnitOfWorkMongoTest::testDeferredUpdateAndDeleteCommit":8,"Pairity\\Tests\\UnitOfWorkMongoTest::testRollbackOnExceptionClearsOps":8,"Pairity\\Tests\\MongoEventSystemTest::testDaoEventsFireOnCrud":8,"Pairity\\Tests\\PostgresSmokeTest::testCreateAlterDropCycle":1},"times":{"Pairity\\Tests\\BelongsToManyMysqlTest::testBelongsToManyEagerAndHelpers":0.001,"Pairity\\Tests\\BelongsToManySqliteTest::testBelongsToManyEagerAndPivotHelpers":0.004,"Pairity\\Tests\\CastersAndAccessorsSqliteTest::testCustomCasterAndDtoAccessorsMutators":0,"Pairity\\Tests\\JoinEagerMysqlTest::testJoinEagerHasManyAndBelongsTo":0,"Pairity\\Tests\\JoinEagerSqliteTest::testHasManyJoinEagerWithProjectionAndSoftDeleteScope":0,"Pairity\\Tests\\JoinEagerSqliteTest::testBelongsToJoinEagerSingleLevel":0,"Pairity\\Tests\\MongoAdapterTest::testCrudCycle":0.002,"Pairity\\Tests\\MongoDaoTest::testCrudViaDao":0.001,"Pairity\\Tests\\MongoOptimisticLockTest::testVersionIncrementOnUpdate":0,"Pairity\\Tests\\MongoPaginationTest::testPaginateAndSimplePaginateWithScopes":0,"Pairity\\Tests\\MongoRelationsTest::testEagerAndLazyRelations":0,"Pairity\\Tests\\MysqlSmokeTest::testCreateAndDropTable":0,"Pairity\\Tests\\OptimisticLockSqliteTest::testVersionLockingIncrementsAndBlocksBulkUpdate":0,"Pairity\\Tests\\PaginationSqliteTest::testPaginateAndSimplePaginateWithScopesAndRelations":0.001,"Pairity\\Tests\\RelationsNestedConstraintsSqliteTest::testNestedEagerAndPerRelationFieldsConstraint":0,"Pairity\\Tests\\SchemaBuilderSqliteTest::testCreateAlterDropCycle":0.001,"Pairity\\Tests\\SoftDeletesTimestampsSqliteTest::testTimestampsAndSoftDeletesFlow":0,"Pairity\\Tests\\UnitOfWorkCascadeMongoTest::testDeleteByIdCascadesToChildren":0,"Pairity\\Tests\\UnitOfWorkCascadeSqliteTest::testDeleteByIdCascadesToChildren":0,"Pairity\\Tests\\UnitOfWorkMongoTest::testDeferredUpdateAndDeleteCommit":0,"Pairity\\Tests\\UnitOfWorkMongoTest::testRollbackOnExceptionClearsOps":0,"Pairity\\Tests\\UnitOfWorkSqliteTest::testDeferredUpdateAndDeleteCommit":0,"Pairity\\Tests\\UnitOfWorkSqliteTest::testRollbackOnExceptionClearsOps":0,"Pairity\\Tests\\EventSystemSqliteTest::testDaoEventsForInsertUpdateDeleteAndFind":0,"Pairity\\Tests\\EventSystemSqliteTest::testUowBeforeAfterCommitEvents":0,"Pairity\\Tests\\MongoEventSystemTest::testDaoEventsFireOnCrud":0,"Pairity\\Tests\\PostgresSmokeTest::testCreateAlterDropCycle":0}}
{"version":2,"defects":{"Pairity\\Tests\\BelongsToManyMysqlTest::testBelongsToManyEagerAndHelpers":1,"Pairity\\Tests\\BelongsToManySqliteTest::testBelongsToManyEagerAndPivotHelpers":7,"Pairity\\Tests\\CastersAndAccessorsSqliteTest::testCustomCasterAndDtoAccessorsMutators":7,"Pairity\\Tests\\JoinEagerMysqlTest::testJoinEagerHasManyAndBelongsTo":1,"Pairity\\Tests\\JoinEagerSqliteTest::testHasManyJoinEagerWithProjectionAndSoftDeleteScope":7,"Pairity\\Tests\\JoinEagerSqliteTest::testBelongsToJoinEagerSingleLevel":8,"Pairity\\Tests\\MongoAdapterTest::testCrudCycle":1,"Pairity\\Tests\\MongoDaoTest::testCrudViaDao":8,"Pairity\\Tests\\MongoOptimisticLockTest::testVersionIncrementOnUpdate":8,"Pairity\\Tests\\MongoPaginationTest::testPaginateAndSimplePaginateWithScopes":8,"Pairity\\Tests\\MongoRelationsTest::testEagerAndLazyRelations":8,"Pairity\\Tests\\MysqlSmokeTest::testCreateAndDropTable":1,"Pairity\\Tests\\PaginationSqliteTest::testPaginateAndSimplePaginateWithScopesAndRelations":7,"Pairity\\Tests\\RelationsNestedConstraintsSqliteTest::testNestedEagerAndPerRelationFieldsConstraint":7,"Pairity\\Tests\\SchemaBuilderSqliteTest::testCreateAlterDropCycle":8,"Pairity\\Tests\\UnitOfWorkCascadeMongoTest::testDeleteByIdCascadesToChildren":8,"Pairity\\Tests\\UnitOfWorkCascadeSqliteTest::testDeleteByIdCascadesToChildren":7,"Pairity\\Tests\\UnitOfWorkMongoTest::testDeferredUpdateAndDeleteCommit":8,"Pairity\\Tests\\UnitOfWorkMongoTest::testRollbackOnExceptionClearsOps":8,"Pairity\\Tests\\MongoEventSystemTest::testDaoEventsFireOnCrud":8,"Pairity\\Tests\\PostgresSmokeTest::testCreateAlterDropCycle":1},"times":{"Pairity\\Tests\\BelongsToManyMysqlTest::testBelongsToManyEagerAndHelpers":0.001,"Pairity\\Tests\\BelongsToManySqliteTest::testBelongsToManyEagerAndPivotHelpers":0.008,"Pairity\\Tests\\CastersAndAccessorsSqliteTest::testCustomCasterAndDtoAccessorsMutators":0.003,"Pairity\\Tests\\JoinEagerMysqlTest::testJoinEagerHasManyAndBelongsTo":0,"Pairity\\Tests\\JoinEagerSqliteTest::testHasManyJoinEagerWithProjectionAndSoftDeleteScope":0,"Pairity\\Tests\\JoinEagerSqliteTest::testBelongsToJoinEagerSingleLevel":0,"Pairity\\Tests\\MongoAdapterTest::testCrudCycle":0.002,"Pairity\\Tests\\MongoDaoTest::testCrudViaDao":0.001,"Pairity\\Tests\\MongoOptimisticLockTest::testVersionIncrementOnUpdate":0,"Pairity\\Tests\\MongoPaginationTest::testPaginateAndSimplePaginateWithScopes":0,"Pairity\\Tests\\MongoRelationsTest::testEagerAndLazyRelations":0,"Pairity\\Tests\\MysqlSmokeTest::testCreateAndDropTable":0,"Pairity\\Tests\\OptimisticLockSqliteTest::testVersionLockingIncrementsAndBlocksBulkUpdate":0.001,"Pairity\\Tests\\PaginationSqliteTest::testPaginateAndSimplePaginateWithScopesAndRelations":0.001,"Pairity\\Tests\\RelationsNestedConstraintsSqliteTest::testNestedEagerAndPerRelationFieldsConstraint":0,"Pairity\\Tests\\SchemaBuilderSqliteTest::testCreateAlterDropCycle":0.004,"Pairity\\Tests\\SoftDeletesTimestampsSqliteTest::testTimestampsAndSoftDeletesFlow":0,"Pairity\\Tests\\UnitOfWorkCascadeMongoTest::testDeleteByIdCascadesToChildren":0,"Pairity\\Tests\\UnitOfWorkCascadeSqliteTest::testDeleteByIdCascadesToChildren":0.001,"Pairity\\Tests\\UnitOfWorkMongoTest::testDeferredUpdateAndDeleteCommit":0,"Pairity\\Tests\\UnitOfWorkMongoTest::testRollbackOnExceptionClearsOps":0,"Pairity\\Tests\\UnitOfWorkSqliteTest::testDeferredUpdateAndDeleteCommit":0,"Pairity\\Tests\\UnitOfWorkSqliteTest::testRollbackOnExceptionClearsOps":0,"Pairity\\Tests\\EventSystemSqliteTest::testDaoEventsForInsertUpdateDeleteAndFind":0,"Pairity\\Tests\\EventSystemSqliteTest::testUowBeforeAfterCommitEvents":0,"Pairity\\Tests\\MongoEventSystemTest::testDaoEventsFireOnCrud":0,"Pairity\\Tests\\PostgresSmokeTest::testCreateAlterDropCycle":0}}

View file

@ -15,7 +15,7 @@ MIT
## Installation
- Requirements: PHP >= 8.1, PDO extension for your database(s)
- Requirements: PHP >= 8.2, PDO extension for your database(s)
- Install via Composer:
```

View file

@ -38,7 +38,7 @@
"source": "https://github.com/getphred/pairity"
},
"require": {
"php": ">=8.1",
"php": "^8.2",
"ext-mongodb": "*",
"mongodb/mongodb": "^2.0"
},

View file

@ -143,7 +143,15 @@ abstract class AbstractDao implements DaoInterface
public function findOneBy(array $criteria): ?AbstractDto
{
// Events: dao.beforeFind (criteria may be mutated)
try { $ev = ['dao' => $this, 'table' => $this->getTable(), 'criteria' => &$criteria]; Events::dispatcher()->dispatch('dao.beforeFind', $ev); } catch (\Throwable) {}
try {
$ev = [
'dao' => $this,
'table' => $this->getTable(),
'criteria' => &$criteria,
];
Events::dispatcher()->dispatch('dao.beforeFind', $ev);
} catch (\Throwable) {
}
$criteria = $this->applyDefaultScopes($criteria);
$this->applyRuntimeScopesToCriteria($criteria);
[$where, $bindings] = $this->buildWhere($criteria);
@ -167,7 +175,15 @@ abstract class AbstractDao implements DaoInterface
$this->eagerStrategy = null; // reset
$this->withStrategies = [];
// Events: dao.afterFind
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'dto' => $dto]; Events::dispatcher()->dispatch('dao.afterFind', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'dto' => $dto,
];
Events::dispatcher()->dispatch('dao.afterFind', $payload);
} catch (\Throwable) {
}
return $dto;
}
@ -190,7 +206,15 @@ abstract class AbstractDao implements DaoInterface
public function findAllBy(array $criteria = []): array
{
// Events: dao.beforeFind (criteria may be mutated)
try { $ev = ['dao' => $this, 'table' => $this->getTable(), 'criteria' => &$criteria]; Events::dispatcher()->dispatch('dao.beforeFind', $ev); } catch (\Throwable) {}
try {
$ev = [
'dao' => $this,
'table' => $this->getTable(),
'criteria' => &$criteria,
];
Events::dispatcher()->dispatch('dao.beforeFind', $ev);
} catch (\Throwable) {
}
$criteria = $this->applyDefaultScopes($criteria);
$this->applyRuntimeScopesToCriteria($criteria);
[$where, $bindings] = $this->buildWhere($criteria);
@ -212,7 +236,15 @@ abstract class AbstractDao implements DaoInterface
$this->eagerStrategy = null; // reset
$this->withStrategies = [];
// Events: dao.afterFind (list)
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'dtos' => $dtos]; Events::dispatcher()->dispatch('dao.afterFind', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'dtos' => $dtos,
];
Events::dispatcher()->dispatch('dao.afterFind', $payload);
} catch (\Throwable) {
}
return $dtos;
}
@ -300,7 +332,15 @@ abstract class AbstractDao implements DaoInterface
throw new \InvalidArgumentException('insert() requires non-empty data');
}
// Events: dao.beforeInsert (allow mutation of $data)
try { $ev = ['dao' => $this, 'table' => $this->getTable(), 'data' => &$data]; Events::dispatcher()->dispatch('dao.beforeInsert', $ev); } catch (\Throwable) {}
try {
$ev = [
'dao' => $this,
'table' => $this->getTable(),
'data' => &$data,
];
Events::dispatcher()->dispatch('dao.beforeInsert', $ev);
} catch (\Throwable) {
}
$data = $this->prepareForInsert($data);
$cols = array_keys($data);
$placeholders = array_map(fn($c) => ':' . $c, $cols);
@ -310,12 +350,28 @@ abstract class AbstractDao implements DaoInterface
$pk = $this->getPrimaryKey();
if ($id !== null) {
$dto = $this->findById($id) ?? $this->hydrate(array_merge($data, [$pk => $id]));
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'dto' => $dto]; Events::dispatcher()->dispatch('dao.afterInsert', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'dto' => $dto,
];
Events::dispatcher()->dispatch('dao.afterInsert', $payload);
} catch (\Throwable) {
}
return $dto;
}
// Fallback when lastInsertId is unavailable: return hydrated DTO from provided data
$dto = $this->hydrate($this->castRowFromStorage($data));
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'dto' => $dto]; Events::dispatcher()->dispatch('dao.afterInsert', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'dto' => $dto,
];
Events::dispatcher()->dispatch('dao.afterInsert', $payload);
} catch (\Throwable) {
}
return $dto;
}
@ -330,7 +386,16 @@ abstract class AbstractDao implements DaoInterface
throw new \InvalidArgumentException('No data provided to update and record not found');
}
// Events: dao.beforeUpdate (mutate $data)
try { $ev = ['dao' => $this, 'table' => $this->getTable(), 'id' => $id, 'data' => &$data]; Events::dispatcher()->dispatch('dao.beforeUpdate', $ev); } catch (\Throwable) {}
try {
$ev = [
'dao' => $this,
'table' => $this->getTable(),
'id' => $id,
'data' => &$data,
];
Events::dispatcher()->dispatch('dao.beforeUpdate', $ev);
} catch (\Throwable) {
}
$toStore = $this->prepareForUpdate($data);
$self = $this;
$conn = $this->connection;
@ -349,7 +414,15 @@ abstract class AbstractDao implements DaoInterface
$pk = $this->getPrimaryKey();
$result = array_merge($base, $data, [$pk => $id]);
$dto = $this->hydrate($this->castRowFromStorage($result));
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'dto' => $dto]; Events::dispatcher()->dispatch('dao.afterUpdate', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'dto' => $dto,
];
Events::dispatcher()->dispatch('dao.afterUpdate', $payload);
} catch (\Throwable) {
}
return $dto;
}
@ -359,17 +432,42 @@ abstract class AbstractDao implements DaoInterface
throw new \InvalidArgumentException('No data provided to update and record not found');
}
// Events: dao.beforeUpdate
try { $ev = ['dao' => $this, 'table' => $this->getTable(), 'id' => $id, 'data' => &$data]; Events::dispatcher()->dispatch('dao.beforeUpdate', $ev); } catch (\Throwable) {}
try {
$ev = [
'dao' => $this,
'table' => $this->getTable(),
'id' => $id,
'data' => &$data,
];
Events::dispatcher()->dispatch('dao.beforeUpdate', $ev);
} catch (\Throwable) {
}
$data = $this->prepareForUpdate($data);
$this->doImmediateUpdateWithLock($id, $data);
$updated = $this->findById($id);
if ($updated === null) {
$pk = $this->getPrimaryKey();
$dto = $this->hydrate($this->castRowFromStorage(array_merge($data, [$pk => $id])));
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'dto' => $dto]; Events::dispatcher()->dispatch('dao.afterUpdate', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'dto' => $dto,
];
Events::dispatcher()->dispatch('dao.afterUpdate', $payload);
} catch (\Throwable) {
}
return $dto;
}
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'dto' => $updated]; Events::dispatcher()->dispatch('dao.afterUpdate', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'dto' => $updated,
];
Events::dispatcher()->dispatch('dao.afterUpdate', $payload);
} catch (\Throwable) {
}
return $updated;
}
@ -378,7 +476,15 @@ abstract class AbstractDao implements DaoInterface
$uow = UnitOfWork::current();
if ($uow && !UnitOfWork::isSuspended()) {
$self = $this; $conn = $this->connection; $theId = $id;
try { $ev = ['dao' => $this, 'table' => $this->getTable(), 'id' => $id]; Events::dispatcher()->dispatch('dao.beforeDelete', $ev); } catch (\Throwable) {}
try {
$ev = [
'dao' => $this,
'table' => $this->getTable(),
'id' => $id,
];
Events::dispatcher()->dispatch('dao.beforeDelete', $ev);
} catch (\Throwable) {
}
$uow->enqueueWithMeta($conn, [
'type' => 'delete',
'mode' => 'byId',
@ -390,7 +496,15 @@ abstract class AbstractDao implements DaoInterface
// deferred; immediate affected count unknown
return 0;
}
try { $ev = ['dao' => $this, 'table' => $this->getTable(), 'id' => $id]; Events::dispatcher()->dispatch('dao.beforeDelete', $ev); } catch (\Throwable) {}
try {
$ev = [
'dao' => $this,
'table' => $this->getTable(),
'id' => $id,
];
Events::dispatcher()->dispatch('dao.beforeDelete', $ev);
} catch (\Throwable) {
}
if ($this->hasSoftDeletes()) {
$columns = $this->softDeleteConfig();
$deletedAt = $columns['deletedAt'] ?? 'deleted_at';
@ -400,7 +514,16 @@ abstract class AbstractDao implements DaoInterface
}
$sql = 'DELETE FROM ' . $this->getTable() . ' WHERE ' . $this->getPrimaryKey() . ' = :pk';
$affected = $this->connection->execute($sql, ['pk' => $id]);
try { $payload = ['dao' => $this, 'table' => $this->getTable(), 'id' => $id, 'affected' => $affected]; Events::dispatcher()->dispatch('dao.afterDelete', $payload); } catch (\Throwable) {}
try {
$payload = [
'dao' => $this,
'table' => $this->getTable(),
'id' => $id,
'affected' => $affected,
];
Events::dispatcher()->dispatch('dao.afterDelete', $payload);
} catch (\Throwable) {
}
return $affected;
}