You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// Manual$repository->beginTransaction();
try {
$repository->save($product1);
$repository->save($product2);
$repository->commit();
} catch (\Throwable$e) {
$repository->rollback();
throw$e;
}
// Or use the helper$repository->transaction(function () use ($repository, $product1, $product2) {
$repository->save($product1);
$repository->save($product2);
});
Optimistic Locking
useMethorZ\SwiftDb\Trait\VersionTrait;
class Product extends AbstractEntity
{
use VersionTrait;
// ...publicfunctiongetColumnMapping(): array
{
return [
// ...
...$this->getVersionMapping(),
];
}
}
// Now updates will check the versiontry {
$repository->save($product);
} catch (OptimisticLockException$e) {
// Entity was modified by another process
}
Performance Tips
Use bulk operations for multiple inserts/updates
Use dirty tracking - only changed fields are updated
Batch your queries - use findMany() instead of multiple find() calls
Use the query builder for complex queries instead of multiple simple queries
Enable the mapping cache in production for faster hydration
Use convenience methods - whereBetween() generates more efficient SQL than two separate where() calls, and orWhereLike() is clearer than orWhere('col', 'LIKE', '?')
Development
Quick Start
# Install dependencies
composer install
# Run unit tests
make test-unit
# or
vendor/bin/phpunit --testsuite=unit
# Run integration tests (requires Docker)
make test-integration
# Run all quality checks
make quality
Docker Setup
The package includes a Docker setup for running integration tests with a real MySQL database.
# Start the database container
make start
# Run integration tests
make test-integration
# Stop containers
make stop
# View logs
make logs
# Access MySQL shell
make db-shell
paginate(int $perPage, int $page): PaginatedResult
Paginate results
Query Builder Examples
// Implicit equals (Laravel-style)$rows = $this->query()
->where('product_active', true)
->where('product_category_id', $categoryId)
->get();
// Array syntax$rows = $this->query()
->where([
'product_active' => true,
'product_status' => 'published',
])
->get();
// Nested conditions (AND with OR inside)// WHERE active = 1 AND (price < 10 OR featured = 1)$rows = $this->query()
->where('product_active', true)
->where(function ($q) {
$q->where('product_price', '<', 10)
->orWhere('product_featured', true);
})
->get();
// Conditional building$rows = $this->query()
->where('product_active', true)
->when($categoryId, fn($q, $id) => $q->where('product_category_id', $id))
->when($minPrice, fn($q, $min) => $q->where('product_price', '>=', $min))
->get();
// Join with conditions$rows = $this->query()
->leftJoin('discount', function ($join) use ($today) {
$join->on('product.product_id', 'discount.discount_product_id')
->where('discount.discount_active', true)
->where('discount.discount_start', '<=', $today);
})
->get();
// Subquery in whereIn$rows = $this->query()
->whereIn('product_category_id', function ($sub) {
$sub->table('category')
->select('category_id')
->where('category_active', true);
})
->get();
// EXISTS subquery$rows = $this->query()
->whereExists(function ($sub) {
$sub->table('inventory')
->select('1')
->whereColumn('inventory.product_id', 'product.product_id')
->where('inventory.quantity', '>', 0);
})
->get();
// OR convenience methods - Multi-field search// WHERE active = 1 AND (name LIKE ? OR sku LIKE ?)$rows = $this->query()
->where('product_active', true)
->where(function ($q) use ($searchTerm) {
$q->whereLike('product_name', "%{$searchTerm}%")
->orWhereLike('product_sku', "%{$searchTerm}%");
})
->get();
// OR BETWEEN - Price range or high stock// WHERE (price BETWEEN 10 AND 50) OR (stock BETWEEN 100 AND 500)$rows = $this->query()
->whereBetween('product_price', 10.0, 50.0)
->orWhereBetween('product_stock', 100, 500)
->get();
// OR NOT BETWEEN - Exclude middle range// WHERE price NOT BETWEEN 50 AND 100 OR stock NOT BETWEEN 10 AND 50$rows = $this->query()
->whereNotBetween('product_price', 50.0, 100.0)
->orWhereNotBetween('product_stock', 10, 50)
->get();
// OR IN - Multiple category sets// WHERE category_id IN (1, 2) OR status IN ('active', 'featured')$rows = $this->query()
->whereIn('product_category_id', [1, 2])
->orWhereIn('product_status', ['active', 'featured'])
->get();
// OR NOT IN - Exclude multiple sets// WHERE status NOT IN ('deleted', 'archived') OR category_id NOT IN (5, 6)$rows = $this->query()
->whereNotIn('product_status', ['deleted', 'archived'])
->orWhereNotIn('product_category_id', [5, 6])
->get();
// OR IN with subquery - Active categories OR featured tags$rows = $this->query()
->whereIn('product_category_id', function ($sub) {
$sub->table('category')->select('category_id')->where('category_active', true);
})
->orWhereIn('product_tag_id', function ($sub) {
$sub->table('tag')->select('tag_id')->where('tag_featured', true);
})
->get();
// OR EXISTS - Has inventory OR has pre-orders$rows = $this->query()
->whereExists(function ($sub) {
$sub->table('inventory')
->select('1')
->whereColumn('inventory.product_id', 'product.product_id')
->where('inventory.quantity', '>', 0);
})
->orWhereExists(function ($sub) {
$sub->table('pre_order')
->select('1')
->whereColumn('pre_order.product_id', 'product.product_id');
})
->get();
// OR NOT EXISTS - No reviews OR no ratings$rows = $this->query()
->whereNotExists(function ($sub) {
$sub->table('review')->select('1')->whereColumn('review.product_id', 'product.product_id');
})
->orWhereNotExists(function ($sub) {
$sub->table('rating')->select('1')->whereColumn('rating.product_id', 'product.product_id');
})
->get();
// OR RAW - Complex business logic// WHERE margin > 20 OR (price * discount_multiplier) < cost$rows = $this->query()
->whereRaw('(product_price - product_cost) / product_price > ?', [0.2])
->orWhereRaw('product_price * ? < product_cost', [0.8])
->get();
PaginatedResult
Paginated result set implementing Countable and IteratorAggregate.
Property/Method
Description
$items
Array of result rows
$total
Total record count
$perPage
Items per page
$currentPage
Current page number
lastPage(): int
Calculate last page number
hasMorePages(): bool
Check if more pages exist
hasPreviousPage(): bool
Check if previous page exists
isEmpty(): bool
Check if result is empty
isNotEmpty(): bool
Check if result has items
firstItem(): ?int
Get first item index (1-based)
lastItem(): ?int
Get last item index (1-based)
Connection
Database connection wrapper with lazy initialization and automatic reconnection.
Method
Description
getPdo(): PDO
Get underlying PDO (lazy-init)
prepare(string $sql): PDOStatement
Prepare statement with reconnect
query(string $sql): PDOStatement
Execute raw query with reconnect
execute(string $sql, array $params): int
Execute and return affected rows
fetchOne(string $sql, array $params): ?array
Fetch single row or null
fetchAll(string $sql, array $params): array
Fetch all matching rows
beginTransaction(): bool
Start transaction
commit(): bool
Commit transaction
rollback(): bool
Rollback transaction
inTransaction(): bool
Check if in transaction
lastInsertId(?string $name): string|false
Get last insert ID
executeWithReconnect(callable $op): mixed
Run operation with auto-reconnect
isConnected(): bool
Check if connected
connect(): void
Establish connection
disconnect(): void
Close connection
reconnect(): void
Disconnect and reconnect
BulkInsert
High-performance multi-row INSERT.
Method
Description
add(array|EntityInterface $row): self
Add row or entity to batch
addMany(array $rows): self
Add multiple rows
flush(): int
Execute and return affected rows
ignore(bool $ignore = true): self
Use INSERT IGNORE
getTotalAffected(): int
Get total rows affected
getPendingCount(): int
Get pending (unflushed) row count
Batch size is set via the constructor (default: 1000). Auto-flushes when batch is full.
BulkUpsert
INSERT ... ON DUPLICATE KEY UPDATE (extends BulkInsert).
Method
Description
onDuplicateKeyUpdate(array $columns): self
Set columns to update
updateColumn(string $col, string $expr): self
Custom update expression
incrementOnDuplicate(string $column): self
Increment column on duplicate
touchUpdatedOnDuplicate(string $col): self
Update timestamp on duplicate
(inherits all BulkInsert methods)
IdentityMap (Optional)
Caches loaded entities to prevent duplicate instances.