diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bcfd94c..83ad6d5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -51,18 +51,20 @@ jobs:
strategy:
matrix:
- php: ['8.0', 8.1, 8.2]
- lib:
- - { laravel: ^11.0 }
- - { laravel: ^10.0 }
- - { laravel: ^9.0 }
+ php: [8.2, 8.3, 8.4]
+ laravel: [^11.0, ^12.0, ^13.0.x-dev]
exclude:
- - { php: 8.0, lib: { laravel: ^10.0 } }
- - { php: 8.0, lib: { laravel: ^11.0 } }
- - { php: 8.1, lib: { laravel: ^11.0 } }
+ - php: 8.2
+ laravel: ^13.0.x-dev
include:
- - { lib: { laravel: ^9.0 }, stable: 1 }
- - { lib: { laravel: ^10.0 }, stable: 1 }
+ - php: 8.2
+ php-cs-fixer: 1
+ - php: 8.3
+ php-cs-fixer: 1
+ - laravel: ^11.0
+ larastan: 1
+ - laravel: ^12.0
+ larastan: 1
steps:
- uses: actions/checkout@v3
@@ -73,24 +75,28 @@ jobs:
php-version: ${{ matrix.php }}
coverage: xdebug
- - name: Remove impossible dependencies
- if: ${{ matrix.stable != 1 }}
- run: composer remove nunomaduro/larastan friendsofphp/php-cs-fixer --dev --no-update
+ - name: Remove impossible dependencies (nunomaduro/larastan)
+ if: ${{ matrix.larastan != 1 }}
+ run: composer remove nunomaduro/larastan --dev --no-update
+
+ - name: Remove impossible dependencies (friendsofphp/php-cs-fixer)
+ if: ${{ matrix.php-cs-fixer != 1 }}
+ run: composer remove friendsofphp/php-cs-fixer --dev --no-update
- name: Adjust Package Versions
run: |
- composer require "laravel/framework:${{ matrix.lib.laravel }}" --dev --no-update
+ composer require "laravel/framework:${{ matrix.laravel }}" --dev --no-update
composer update
- name: Prepare Coverage Directory
run: mkdir -p build/logs
- name: PHP-CS-Fixer
- if: ${{ matrix.stable == 1 }}
+ if: ${{ matrix.php-cs-fixer == 1 }}
run: composer cs
- name: PHPStan
- if: ${{ matrix.stable == 1 }}
+ if: ${{ matrix.larastan == 1 }}
run: composer phpstan
- name: Test
@@ -106,7 +112,7 @@ jobs:
env:
COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_PARALLEL: 'true'
- COVERALLS_FLAG_NAME: "laravel:${{ matrix.lib.laravel }} php:${{ matrix.php }}"
+ COVERALLS_FLAG_NAME: "laravel:${{ matrix.laravel }} php:${{ matrix.php }}"
with:
timeout_minutes: 1
max_attempts: 3
diff --git a/README.md b/README.md
index 40b2022..cb19713 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,14 @@ Advisory Locking Features of Postgres/MySQL/MariaDB on Laravel
## Requirements
-| Package | Version | Mandatory |
-|:--------|:-------------------------------------|:---------:|
-| PHP | ^8.0.2 | ✅ |
-| Laravel | ^9.0 || ^10.0 | ✅ |
-| PHPStan | >=1.1 | |
+| Package | Version | Mandatory |
+|:--------|:--------------------------------------|:---------:|
+| PHP | ^8.2 | ✅ |
+| Laravel | ^11.0 || ^12.0 | ✅ |
+| PHPStan | >=2.0 | |
+
+> [!NOTE]
+> Older versions have outdated dependency requirements. If you cannot prepare the latest environment, please refer to past releases.
| RDBMS | Version |
|:---------|:--------------------------|
@@ -19,7 +22,7 @@ Advisory Locking Features of Postgres/MySQL/MariaDB on Laravel
## Installing
```
-composer require mpyw/laravel-database-advisory-lock:^4.3
+composer require mpyw/laravel-database-advisory-lock:^4.4
```
## Basic usage
@@ -171,21 +174,20 @@ END
## Caveats about Transaction Levels
-### Key Principle
-
-Always avoid nested transactions when using advisory locks to ensure adherence to the **[S2PL (Strict 2-Phase Locking)](https://en.wikipedia.org/wiki/Two-phase_locking#Strict_two-phase_locking)** principle.
-
### Recommended Approach
-When transactions and advisory locks are related, either locking approach can be applied.
+When transactions and advisory locks are related, either locking approach can be applied.
+
+> [!TIP]
+> **For Postgres, always prefer Transaction-Level Locking.**
> [!NOTE]
> **Transaction-Level Locks:**
-> Acquire the lock at the transaction nesting level 1, then rely on automatic release mechanisms.
+> Ensure the current context is inside the transaction, then rely on automatic release mechanisms.
>
> ```php
-> if (DB::transactionLevel() > 1) {
-> throw new LogicException("Don't use nested transactions outside of this logic.");
+> if (DB::transactionLevel() < 1) {
+> throw new LogicException("Unexpectedly transaction is not active.");
> }
>
> DB::advisoryLocker()
@@ -196,11 +198,11 @@ When transactions and advisory locks are related, either locking approach can be
> [!NOTE]
> **Session-Level Locks:**
-> Acquire the lock at the transaction nesting level 0, then proceed to call `DB::transaction()` call.
+> Ensure the current context is outside the transaction, then proceed to call `DB::transaction()` call.
>
> ```php
> if (DB::transactionLevel() > 0) {
-> throw new LogicException("Don't use transactions outside of this logic.");
+> throw new LogicException("Unexpectedly transaction is already active.");
> }
>
> $result = DB::advisoryLocker()
@@ -215,10 +217,6 @@ When transactions and advisory locks are related, either locking approach can be
### Considerations
-> [!CAUTION]
-> **Transaction-Level Locks:**
-> Don't take transaction-level locks in nested transactions. They are unaware of Laravel's nested transaction emulation.
-
> [!CAUTION]
> **Session-Level Locks:**
> Don't take session-level locks in the transactions when the content to be committed by the transaction is related to the advisory locks.
diff --git a/_ide_helper.php b/_ide_helper.php
index 4515bff..459e092 100644
--- a/_ide_helper.php
+++ b/_ide_helper.php
@@ -14,9 +14,7 @@ public function advisoryLocker(): LockerFactory;
class Connection implements ConnectionInterface
{
- public function advisoryLocker(): LockerFactory
- {
- }
+ public function advisoryLocker(): LockerFactory {}
}
}
}
@@ -28,9 +26,7 @@ public function advisoryLocker(): LockerFactory
if (false) {
class DB extends Facade
{
- public static function advisoryLocker(): LockerFactory
- {
- }
+ public static function advisoryLocker(): LockerFactory {}
}
}
}
diff --git a/composer.json b/composer.json
index 5da0e34..2a4aa0f 100644
--- a/composer.json
+++ b/composer.json
@@ -22,21 +22,21 @@
}
},
"require": {
- "php": "^8.0.2",
+ "php": "^8.2",
"ext-pdo": "*",
- "illuminate/events": "^9.0 || ^10.0 || ^11.0",
- "illuminate/support": "^9.0 || ^10.0 || ^11.0",
- "illuminate/database": "^9.0 || ^10.0 || ^11.0",
- "illuminate/contracts": "^9.0 || ^10.0 || ^11.0"
+ "illuminate/events": "^11.0 || ^12.0 || ^13.0",
+ "illuminate/support": "^11.0 || ^12.0 || ^13.0",
+ "illuminate/database": "^11.0 || ^12.0 || ^13.0",
+ "illuminate/contracts": "^11.0 || ^12.0 || ^13.0"
},
"require-dev": {
"orchestra/testbench": "*",
- "orchestra/testbench-core": ">=7.0",
- "phpunit/phpunit": ">=9.5",
- "phpstan/phpstan": ">=1.1",
+ "orchestra/testbench-core": ">=9.0",
+ "phpunit/phpunit": ">=11.0",
+ "phpstan/phpstan": ">=2.0",
"phpstan/extension-installer": ">=1.1",
- "nunomaduro/larastan": ">=1.0",
- "friendsofphp/php-cs-fixer": "^3.9"
+ "nunomaduro/larastan": ">=3.1",
+ "friendsofphp/php-cs-fixer": "^3.70"
},
"scripts": {
"test": "vendor/bin/phpunit",
diff --git a/phpstan/AdvisoryLockerMethod.php b/phpstan/AdvisoryLockerMethod.php
index d66901f..e4f13b9 100644
--- a/phpstan/AdvisoryLockerMethod.php
+++ b/phpstan/AdvisoryLockerMethod.php
@@ -15,8 +15,6 @@
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;
-use function is_a;
-
final class AdvisoryLockerMethod implements MethodReflection
{
private ClassReflection $class;
@@ -33,7 +31,7 @@ public function getDeclaringClass(): ClassReflection
public function isStatic(): bool
{
- return is_a($this->class->getName(), DB::class, true);
+ return $this->class->is(DB::class);
}
public function isPrivate(): bool
diff --git a/phpstan/ConnectionClassExtension.php b/phpstan/ConnectionClassExtension.php
index dd993c9..2f7aaf8 100644
--- a/phpstan/ConnectionClassExtension.php
+++ b/phpstan/ConnectionClassExtension.php
@@ -10,16 +10,14 @@
use PHPStan\Reflection\MethodReflection;
use PHPStan\Reflection\MethodsClassReflectionExtension;
-use function is_a;
-
final class ConnectionClassExtension implements MethodsClassReflectionExtension
{
public function hasMethod(ClassReflection $classReflection, string $methodName): bool
{
return $methodName === 'advisoryLocker'
&& (
- is_a($classReflection->getName(), ConnectionInterface::class, true)
- || is_a($classReflection->getName(), DB::class, true)
+ $classReflection->is(ConnectionInterface::class)
+ || $classReflection->is(DB::class)
);
}
diff --git a/phpunit.xml b/phpunit.xml
index 4a14c2d..e484804 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,15 +1,26 @@
-
-
+
+
+
- src
+ ./src
-
+
+
+
+
- ./tests/
+ ./tests
+
diff --git a/tests/PostgresTransactionErrorRecoveryTest.php b/tests/PostgresTransactionErrorRecoveryTest.php
index cbf65d3..c7127a2 100644
--- a/tests/PostgresTransactionErrorRecoveryTest.php
+++ b/tests/PostgresTransactionErrorRecoveryTest.php
@@ -4,7 +4,6 @@
namespace Mpyw\LaravelDatabaseAdvisoryLock\Tests;
-use Illuminate\Database\Connection;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\DB;
@@ -20,7 +19,6 @@ public function testWithoutTransactions(): void
$passed = false;
$conn = DB::connection('pgsql');
- assert($conn instanceof Connection);
$conn->enableQueryLog();
$conn
@@ -64,7 +62,6 @@ public function testWithLockingRollbacksToSavepoint(): void
$passed = false;
$conn = DB::connection('pgsql');
- assert($conn instanceof Connection);
$conn->enableQueryLog();
$conn->transaction(function (ConnectionInterface $conn) use (&$passed): void {
@@ -115,7 +112,6 @@ public function testWithLockingRollbacksToSavepoint(): void
public function testDestructorReleasesLocksAfterTransactionTerminated(): void
{
$conn = DB::connection('pgsql');
- assert($conn instanceof Connection);
$conn->enableQueryLog();
try {
diff --git a/tests/PostgresTransactionErrorRefreshDatabaseRecoveryTest.php b/tests/PostgresTransactionErrorRefreshDatabaseRecoveryTest.php
index 841a439..f060ed1 100644
--- a/tests/PostgresTransactionErrorRefreshDatabaseRecoveryTest.php
+++ b/tests/PostgresTransactionErrorRefreshDatabaseRecoveryTest.php
@@ -4,7 +4,6 @@
namespace Mpyw\LaravelDatabaseAdvisoryLock\Tests;
-use Illuminate\Database\Connection;
use Illuminate\Database\ConnectionInterface;
use Illuminate\Database\QueryException;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -23,7 +22,6 @@ class PostgresTransactionErrorRefreshDatabaseRecoveryTest extends TestCase
public function testImplicitTransactionRollbacksToSavepoint(): void
{
$conn = DB::connection('pgsql');
- assert($conn instanceof Connection);
$conn->enableQueryLog();
try {
@@ -79,7 +77,6 @@ public function testWithLockingRollbacksToSavepoint(): void
$passed = false;
$conn = DB::connection('pgsql');
- assert($conn instanceof Connection);
$conn->enableQueryLog();
$conn->transaction(function (ConnectionInterface $conn) use (&$passed): void {
@@ -131,7 +128,6 @@ public function testWithLockingRollbacksToSavepoint(): void
public function testDestructorReleasesLocksAfterRollingBackToSavepoint(): void
{
$conn = DB::connection('pgsql');
- assert($conn instanceof Connection);
$conn->enableQueryLog();
try {
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 7b6c940..3556ed2 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -22,18 +22,18 @@ protected function getEnvironmentSetUp($app): void
{
config(['database.connections.mariadb' => config('database.connections.mysql')]);
config([
- 'database.connections.pgsql.host' => env('PG_HOST', 'postgres'),
- 'database.connections.pgsql.port' => env('PG_PORT', '5432'),
+ 'database.connections.pgsql.host' => getenv('PG_HOST') ?: 'postgres',
+ 'database.connections.pgsql.port' => getenv('PG_PORT') ?: '5432',
'database.connections.pgsql.database' => 'testing',
'database.connections.pgsql.username' => 'testing',
'database.connections.pgsql.password' => 'testing',
- 'database.connections.mysql.host' => env('MY_HOST', 'mysql'),
- 'database.connections.mysql.port' => env('MY_PORT', '3306'),
+ 'database.connections.mysql.host' => getenv('MY_HOST') ?: 'mysql',
+ 'database.connections.mysql.port' => getenv('MY_PORT') ?: '3306',
'database.connections.mysql.database' => 'testing',
'database.connections.mysql.username' => 'testing',
'database.connections.mysql.password' => 'testing',
- 'database.connections.mariadb.host' => env('MA_HOST', 'mariadb'),
- 'database.connections.mariadb.port' => env('MA_PORT', '3306'),
+ 'database.connections.mariadb.host' => getenv('MA_HOST') ?: 'mariadb',
+ 'database.connections.mariadb.port' => getenv('MA_PORT') ?: '3306',
'database.connections.mariadb.database' => 'testing',
'database.connections.mariadb.username' => 'testing',
'database.connections.mariadb.password' => 'testing',