Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 23 additions & 17 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
38 changes: 18 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ Advisory Locking Features of Postgres/MySQL/MariaDB on Laravel

## Requirements

| Package | Version | Mandatory |
|:--------|:-------------------------------------|:---------:|
| PHP | <code>^8.0.2</code> ||
| Laravel | <code>^9.0 &#124;&#124; ^10.0</code> ||
| PHPStan | <code>&gt;=1.1</code> | |
| Package | Version | Mandatory |
|:--------|:--------------------------------------|:---------:|
| PHP | <code>^8.2</code> ||
| Laravel | <code>^11.0 &#124;&#124; ^12.0</code> ||
| PHPStan | <code>&gt;=2.0</code> | |

> [!NOTE]
> Older versions have outdated dependency requirements. If you cannot prepare the latest environment, please refer to past releases.
| RDBMS | Version |
|:---------|:--------------------------|
Expand All @@ -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
Expand Down Expand Up @@ -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:**
> <ins>Acquire the lock at the transaction nesting level 1</ins>, then rely on automatic release mechanisms.
> Ensure the current context is <ins>inside the transaction</ins>, 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()
Expand All @@ -196,11 +198,11 @@ When transactions and advisory locks are related, either locking approach can be
> [!NOTE]
> **Session-Level Locks:**
> <ins>Acquire the lock at the transaction nesting level 0</ins>, then proceed to call `DB::transaction()` call.
> Ensure the current context is <ins>outside the transaction</ins>, 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()
Expand All @@ -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.
Expand Down
8 changes: 2 additions & 6 deletions _ide_helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ public function advisoryLocker(): LockerFactory;

class Connection implements ConnectionInterface
{
public function advisoryLocker(): LockerFactory
{
}
public function advisoryLocker(): LockerFactory {}
}
}
}
Expand All @@ -28,9 +26,7 @@ public function advisoryLocker(): LockerFactory
if (false) {
class DB extends Facade
{
public static function advisoryLocker(): LockerFactory
{
}
public static function advisoryLocker(): LockerFactory {}
}
}
}
20 changes: 10 additions & 10 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 1 addition & 3 deletions phpstan/AdvisoryLockerMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
use PHPStan\Type\ObjectType;
use PHPStan\Type\Type;

use function is_a;

final class AdvisoryLockerMethod implements MethodReflection
{
private ClassReflection $class;
Expand All @@ -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
Expand Down
6 changes: 2 additions & 4 deletions phpstan/ConnectionClassExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
}

Expand Down
21 changes: 16 additions & 5 deletions phpunit.xml
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" bootstrap="vendor/autoload.php" colors="true" processIsolation="false" stopOnFailure="false" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.0/phpunit.xsd" cacheDirectory=".phpunit.cache" backupStaticProperties="false">
<coverage>
<phpunit backupGlobals="false"
bootstrap="vendor/autoload.php"
colors="true"
processIsolation="false"
stopOnFailure="false"
cacheDirectory=".phpunit.cache"
backupStaticProperties="false">

<source>
<include>
<directory suffix=".php">src</directory>
<directory>./src</directory>
</include>
</coverage>
</source>

<coverage/>

<testsuites>
<testsuite name="Package Test Suite">
<directory suffix="Test.php">./tests/</directory>
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>

<php>
<env name="PG_HOST" value="postgres"/>
<env name="PG_PORT" value="5432"/>
Expand Down
4 changes: 0 additions & 4 deletions tests/PostgresTransactionErrorRecoveryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -20,7 +19,6 @@ public function testWithoutTransactions(): void
$passed = false;

$conn = DB::connection('pgsql');
assert($conn instanceof Connection);
$conn->enableQueryLog();

$conn
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -115,7 +112,6 @@ public function testWithLockingRollbacksToSavepoint(): void
public function testDestructorReleasesLocksAfterTransactionTerminated(): void
{
$conn = DB::connection('pgsql');
assert($conn instanceof Connection);
$conn->enableQueryLog();

try {
Expand Down
4 changes: 0 additions & 4 deletions tests/PostgresTransactionErrorRefreshDatabaseRecoveryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -23,7 +22,6 @@ class PostgresTransactionErrorRefreshDatabaseRecoveryTest extends TestCase
public function testImplicitTransactionRollbacksToSavepoint(): void
{
$conn = DB::connection('pgsql');
assert($conn instanceof Connection);
$conn->enableQueryLog();

try {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -131,7 +128,6 @@ public function testWithLockingRollbacksToSavepoint(): void
public function testDestructorReleasesLocksAfterRollingBackToSavepoint(): void
{
$conn = DB::connection('pgsql');
assert($conn instanceof Connection);
$conn->enableQueryLog();

try {
Expand Down
12 changes: 6 additions & 6 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down