Skip to content

Commit 5aacc8e

Browse files
committed
Merge branch 'main' into develop
2 parents 05a73a3 + 1ec7ced commit 5aacc8e

16 files changed

+730
-811
lines changed

.php-cs-fixer.cache

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"php":"8.2.29","version":"3.88.2:v3.88.2#a8d15584bafb0f0d9d938827840060fd4a3ebc99","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"align_single_space_minimal"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"sort_algorithm":"alpha"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"single_quote":true,"no_unused_imports":true,"no_superfluous_phpdoc_tags":true,"phpdoc_trim":true,"phpdoc_align":{"align":"left"},"blank_line_before_statement":{"statements":["return"]},"simplified_null_return":true,"void_return":true},"hashes":{"src\/Helper.php":"8ef8db53eed02278815b175b445a2ee9","src\/DBTransactionRetryHelperOld.php":"358e3a95a390c013376e05cb0911b599","src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","src\/RetryServiceProvider.php":"b6642465f4ed70477d21c0460e3677df","tests\/TestCase.php":"6df2b13208f4952f10b306fad99e1c51","tests\/bootstrap.php":"8af7490a2832c4cce20f0980636bad41","tests\/DBTransactionRetryHelperTest.php":"5e9993c586d9318449b2181ece54bc73","\/tmp\/PHP CS Fixertemp_folder\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder1\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20",".php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder2\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder10\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder4\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder5\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder11\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder9\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder815\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder8\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder3\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder7\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder6\/src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","\/tmp\/PHP CS Fixertemp_folder\/src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","\/tmp\/PHP CS Fixertemp_folder1\/src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","tests\/Unit\/ExampleTest.php":"3bbd4ea8029698f723c35a66d8592087","tests\/Unit\/DBTransactionRetryHelperTest.php":"38a42cae2dcaf6fa55519bec4b64e252","tests\/Feature\/ExampleTest.php":"a1e5352ea369ad36f88f4f566c340371","tests\/Pest.php":"44a41307b2bca2c9b747aa2f40c5262b"}}
1+
{"php":"8.2.29","version":"3.88.2:v3.88.2#a8d15584bafb0f0d9d938827840060fd4a3ebc99","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"align_single_space_minimal"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":true,"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"sort_algorithm":"alpha"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"array_syntax":{"syntax":"short"},"single_quote":true,"no_unused_imports":true,"no_superfluous_phpdoc_tags":true,"phpdoc_trim":true,"phpdoc_align":{"align":"left"},"blank_line_before_statement":{"statements":["return"]},"simplified_null_return":true,"void_return":true},"hashes":{"src\/Helper.php":"8ef8db53eed02278815b175b445a2ee9","src\/DBTransactionRetryHelperOld.php":"358e3a95a390c013376e05cb0911b599","src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","src\/RetryServiceProvider.php":"b6642465f4ed70477d21c0460e3677df","tests\/TestCase.php":"78359ccc3cc8934fc6b788bb9f8455df","tests\/bootstrap.php":"8af7490a2832c4cce20f0980636bad41","tests\/DBTransactionRetryHelperTest.php":"5e9993c586d9318449b2181ece54bc73","\/tmp\/PHP CS Fixertemp_folder\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder1\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20",".php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder2\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder10\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder4\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder5\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder11\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder9\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder815\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder8\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder3\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder7\/.php-cs-fixer.php":"f08f20b53da80da9c95f886b732fca20","\/tmp\/PHP CS Fixertemp_folder6\/src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","\/tmp\/PHP CS Fixertemp_folder\/src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","\/tmp\/PHP CS Fixertemp_folder1\/src\/DBTransactionRetryHelper.php":"3dfeb60b0234603d2046f39592b6a547","tests\/Unit\/ExampleTest.php":"3bbd4ea8029698f723c35a66d8592087","tests\/Unit\/DBTransactionRetryHelperTest.php":"0a3d3a6bed6c3bd5af47a71e29a5be92","tests\/Feature\/ExampleTest.php":"a1e5352ea369ad36f88f4f566c340371","tests\/Pest.php":"44a41307b2bca2c9b747aa2f40c5262b","src\/Providers\/DatabaseRetryServiceProvider.php":"c4b1b48a744c843ed40bb818370ab922","src\/Services\/DeadlockTransactionRetrier.php":"62fc72973cf461c9029fdddaa4d721ce","src\/Support\/DeadlockLogWriter.php":"b298e47ae03b1255eb9d09d8c3758ef4","src\/Support\/BindingStringifier.php":"3aa21139dad20340d9518fa57e0845ca","src\/Support\/TraceFormatter.php":"13f19f8c9de611faa05847ae3890b73d"}}

README.md

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,14 @@ Resilient database transactions for Laravel applications that need to gracefully
3535
composer require ahed92wakim/laravel-mysql-deadlock-retry
3636
```
3737

38-
The package ships with a service provider that is auto-discovered. No additional setup is needed, and the helper functions in `src/Helper.php` are automatically loaded.
38+
The package ships with the `DatabaseRetryServiceProvider`, which Laravel auto-discovers. No additional setup is needed.
3939

4040
## Usage
4141

4242
```php
43-
use MysqlDeadlocks\RetryHelper\DBTransactionRetryHelper as Retry;
43+
use MysqlDeadlocks\RetryHelper\Services\DeadlockTransactionRetrier as Retry;
4444

45-
$order = Retry::transactionWithRetry(
45+
$order = Retry::runWithRetry(
4646
function () use ($payload) {
4747
$order = Order::create($payload);
4848
$order->logAuditTrail();
@@ -56,19 +56,35 @@ $order = Retry::transactionWithRetry(
5656
);
5757
```
5858

59-
`transactionWithRetry()` returns the value produced by your callback, just like `DB::transaction()`. If every attempt fails, the last `QueryException` is re-thrown so your calling code can continue its normal error handling.
59+
`runWithRetry()` returns the value produced by your callback, just like `DB::transaction()`. If every attempt fails, the last `QueryException` is re-thrown so your calling code can continue its normal error handling.
6060

6161
### Parameters
6262

6363
| Parameter | Default | Description |
6464
| ------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------- |
65-
| `maxRetries` | `3` | Total number of attempts (initial try + retries). |
66-
| `retryDelay` | `2` | Base delay (seconds). Actual wait uses exponential backoff with ±25% jitter. |
67-
| `logFileName` | `database/mysql-deadlocks` | Written to `storage/logs/{Y-m-d}/{logFileName}.log`. Can point to subdirectories. |
65+
| `maxRetries` | Config (`default: 3`) | Total number of attempts (initial try + retries). |
66+
| `retryDelay` | Config (`default: 2s`) | Base delay (seconds). Actual wait uses exponential backoff with ±25% jitter. |
67+
| `logFileName` | Config (`default: database/mysql-deadlocks`) | Written to `storage/logs/{Y-m-d}/{logFileName}.log`. Can point to subdirectories. |
6868
| `trxLabel` | `''` | Optional label injected into log titles and stored in the service container as `tx.label` for downstream consumers. |
6969

7070
Call the helper anywhere you would normally open a transaction—controllers, jobs, console commands, or domain services.
7171

72+
## Configuration
73+
74+
Publish the configuration file to tweak defaults globally:
75+
76+
```bash
77+
php artisan vendor:publish --tag=mysql-deadlock-retry-config
78+
```
79+
80+
Key options (`config/mysql-deadlock-retry.php`):
81+
82+
- `max_retries`, `retry_delay`, and `log_file_name` set the package-wide defaults when you omit parameters. Each respects the matching environment variable (`MYSQL_DEADLOCK_MAX_RETRIES`, `MYSQL_DEADLOCK_RETRY_DELAY`, `MYSQL_DEADLOCK_LOG_FILE`).
83+
- `logging.channel` points at any existing Laravel log channel so you can reuse stacks or third-party drivers.
84+
- `logging.config` provides a full configuration array for `Log::build()` when you want a dedicated writer.
85+
- `logging.via` accepts a container binding, class name, or callable that resolves a PSR-3 logger—ideal when you need to hand logs off to a completely custom pipeline.
86+
- `logging.levels.success` / `logging.levels.failure` let you tune the severity emitted for successful retries and exhausted attempts (defaults: `warning` and `error`).
87+
7288
## Retry Conditions
7389

7490
Retries are attempted only when the caught exception is an `Illuminate\Database\QueryException` that matches one of:
@@ -82,7 +98,7 @@ If no attempt succeeds and all retries are exhausted, the last `QueryException`
8298

8399
## Logging Behaviour
84100

85-
Logs are written using a dedicated single-file channel per day:
101+
By default, logs are written using a dedicated single-file channel per day. Override `logging.channel`, `logging.config`, or `logging.via` to integrate with your own logging stack:
86102

87103
- Success after retries → a warning entry titled `"[trxLabel] [MYSQL DEADLOCK RETRY - SUCCESS] After (Attempts: x/y) - Warning"`.
88104
- Failure after exhausting retries → an error entry titled `"[trxLabel] [MYSQL DEADLOCK RETRY - FAILED] After (Attempts: x/y) - Error"`.
@@ -96,6 +112,16 @@ Each log entry includes:
96112

97113
Set `logFileName` to segment logs by feature or workload (e.g., `logFileName: 'database/queues/payments'`).
98114

115+
## Helper Utilities
116+
117+
The package exposes dedicated support classes you can reuse in your own instrumentation:
118+
119+
- `MysqlDeadlocks\RetryHelper\Support\DeadlockLogWriter` writes structured entries using the same format as the retrier.
120+
- `MysqlDeadlocks\RetryHelper\Support\TraceFormatter` converts debug backtraces into log-friendly arrays.
121+
- `MysqlDeadlocks\RetryHelper\Support\BindingStringifier` sanitises query bindings before logging.
122+
123+
For testing scenarios, the retrier looks for a namespaced `MysqlDeadlocks\RetryHelper\sleep()` function before falling back to PHP's global `sleep()`, making it easy to assert backoff intervals without introducing delays.
124+
99125
## Testing the Package
100126

101127
Run the test suite with:

composer.json

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
{
22
"name": "ahed92wakim/laravel-mysql-deadlock-retry",
3-
"description": "Retrying database transaction when deadlocks occurred",
3+
"description": "Gracefully retry Laravel transactions on MySQL deadlocks and serialization failures with configurable backoff, structured logging, and reusable helpers.",
44
"type": "library",
55
"minimum-stability": "stable",
66
"require": {
7-
"php": ">=8.2",
8-
"laravel/framework": ">=11.0"
7+
"php": "^8.2",
8+
"laravel/framework": "^11.0"
99
},
10-
"license": "MIT",
10+
"license": [
11+
"MIT"
12+
],
1113
"autoload": {
1214
"psr-4": {
1315
"MysqlDeadlocks\\RetryHelper\\": "src/"
14-
},
15-
"files": [
16-
"src/Helper.php"
17-
]
16+
}
1817
},
1918
"autoload-dev": {
2019
"psr-4": {
@@ -24,7 +23,7 @@
2423
"extra": {
2524
"laravel": {
2625
"providers": [
27-
"MysqlDeadlocks\\RetryHelper\\RetryServiceProvider"
26+
"MysqlDeadlocks\\RetryHelper\\Providers\\DatabaseRetryServiceProvider"
2827
]
2928
}
3029
},

0 commit comments

Comments
 (0)