Skip to content

Commit 12fca6b

Browse files
Merge pull request #1 from dynamik-dev/chore/laravel-13-and-v0.2.0-improvements
chore(release): v0.2.0 — Laravel 13 + PHP 8.5, security & correctness…
2 parents e81c5b3 + d1005f1 commit 12fca6b

23 files changed

Lines changed: 489 additions & 77 deletions

.bully.yml

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
1+
schema_version: 1
2+
13
# modman .bully.yml
24
#
3-
# Bully wraps the three existing gates so they run at edit-time instead of only
4-
# in CI: Laravel Pint (format), Larastan (static analysis), and the Pest
5-
# architecture suite. Each rule no-ops gracefully if its binary isn't installed
6-
# yet, so bully works in a fresh clone before `composer install`.
5+
# Bully wraps the project gates so they run at edit-time instead of only in
6+
# CI: Laravel Pint (format), Larastan (PHPStan static analysis), the Pest
7+
# architecture suite, and composer manifest validation. Each rule no-ops
8+
# gracefully if its binary isn't installed yet, so bully works in a fresh
9+
# clone before `composer install`.
10+
11+
skip:
12+
- "vendor/**"
13+
- "storage/**"
14+
- "bootstrap/cache/**"
15+
- "public/build/**"
16+
- ".phpunit.cache/**"
17+
- ".bully/**"
18+
- ".git/**"
19+
- ".idea/**"
20+
- ".vscode/**"
721

822
rules:
923
pint:
@@ -17,22 +31,34 @@ rules:
1731

1832
larastan:
1933
description: >
20-
Larastan (PHPStan level 9 via phpstan.neon) catches type errors,
21-
null-safety issues, and Laravel-specific mistakes. Running it per
22-
edited file gives fast feedback without waiting for the CI matrix.
34+
Larastan (PHPStan level 9 via phpstan.neon, with phpstan-strict-rules)
35+
catches type errors, null-safety issues, and Laravel-specific mistakes.
36+
Running it per edited file gives fast feedback without waiting for the
37+
CI matrix.
2338
engine: script
2439
scope: ["src/**/*.php", "database/factories/*.php"]
2540
severity: error
2641
script: "[ -x vendor/bin/phpstan ] && { vendor/bin/phpstan analyse --memory-limit=1G --no-progress {file} && exit 0 || exit 1; } || exit 0"
2742

2843
pest-arch:
2944
description: >
30-
The Pest architecture suite (tests/Arch/ArchTest.php) enforces
31-
package invariants: strict types everywhere in src/, contracts are
32-
interfaces only, graders/policies are final, events are final
33-
readonly, no debug calls, no direct Guzzle usage, and so on. Any
34-
change under src/ or tests/Arch/ should re-run it.
45+
The Pest architecture suite (tests/Arch/ArchTest.php) enforces package
46+
invariants: strict types everywhere in src/, contracts are interfaces
47+
only, graders/policies are final, events are final readonly, no debug
48+
calls, no direct Guzzle usage, and so on. Any change under src/ or
49+
tests/Arch/ should re-run it.
3550
engine: script
3651
scope: ["src/**/*.php", "tests/Arch/*.php"]
3752
severity: error
3853
script: "[ -x vendor/bin/pest ] && { vendor/bin/pest --testsuite=arch --compact && exit 0 || exit 1; } || exit 0"
54+
55+
composer-validate:
56+
description: >
57+
`composer validate --strict` checks composer.json for schema errors,
58+
version normalization, and required keys. It is sub-second and cheap
59+
to run on every manifest edit, so a malformed composer.json never
60+
reaches CI.
61+
engine: script
62+
scope: ["composer.json"]
63+
severity: error
64+
script: "composer validate --strict --no-check-publish"

.composer-require-checker.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"Illuminate\\Database\\Eloquent\\Relations\\HasMany",
1919
"Illuminate\\Database\\Eloquent\\Relations\\MorphMany",
2020
"Illuminate\\Database\\Eloquent\\Relations\\MorphTo",
21+
"Illuminate\\Database\\Eloquent\\Relations\\Relation",
2122
"Illuminate\\Foundation\\Bus\\Dispatchable",
2223
"Illuminate\\Http\\JsonResponse",
2324
"Illuminate\\Http\\Request",
@@ -26,6 +27,7 @@
2627
"Illuminate\\Routing\\Controller",
2728
"Illuminate\\Support\\Facades\\DB",
2829
"Illuminate\\Support\\Facades\\Event",
30+
"Illuminate\\Support\\Facades\\Gate",
2931
"Illuminate\\Support\\Facades\\Http",
3032
"Illuminate\\Support\\ServiceProvider",
3133
"Illuminate\\Support\\Str",

.gitattributes

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Files and directories below are stripped from the Composer dist archive.
2+
# Consumers running `composer require dynamik-dev/modman` get only runtime
3+
# code — no test fixtures, no CI metadata, no editor cruft. Reduces install
4+
# size and removes files that have no business in vendor/.
5+
6+
# Tests, fixtures, and test config
7+
/tests export-ignore
8+
/phpunit.xml export-ignore
9+
/.phpunit.cache export-ignore
10+
/.phpunit.result.cache export-ignore
11+
12+
# Static analysis and lint config
13+
/phpstan.neon export-ignore
14+
/pint.json export-ignore
15+
/.composer-require-checker.json export-ignore
16+
17+
# Agentic-lint (bully) config and telemetry
18+
/.bully.yml export-ignore
19+
/.bully export-ignore
20+
21+
# CI, GitHub metadata, and contributor docs
22+
/.github export-ignore
23+
/.gitattributes export-ignore
24+
/.gitignore export-ignore
25+
26+
# Project planning, agent context, and ADRs
27+
/CLAUDE.md export-ignore
28+
/backlog export-ignore
29+
/docs/superpowers export-ignore
30+
31+
# IDE and tooling caches
32+
/.idea export-ignore
33+
/.vscode export-ignore
34+
/.claude export-ignore

.github/workflows/ci.yml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ jobs:
1111
strategy:
1212
fail-fast: false
1313
matrix:
14-
php: ['8.3', '8.4']
15-
laravel: ['11.*', '12.*']
14+
php: ['8.3', '8.4', '8.5']
15+
laravel: ['11.*', '12.*', '13.*']
16+
# spatie/laravel-model-states 2.12.2+ (the first version supporting
17+
# Laravel 13) requires PHP ^8.4, so the 8.3 × L13 combination is not
18+
# an installable target even though Laravel 13 itself accepts PHP 8.3.
19+
exclude:
20+
- php: '8.3'
21+
laravel: '13.*'
1622

1723
steps:
1824
- uses: actions/checkout@v4
@@ -34,9 +40,6 @@ jobs:
3440
- name: PHPStan
3541
run: vendor/bin/phpstan analyse --memory-limit=1G --no-progress
3642

37-
- name: Rector (dry run)
38-
run: vendor/bin/rector process --dry-run --no-progress-bar
39-
4043
- name: Pest — arch
4144
run: vendor/bin/pest --testsuite=arch
4245

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ composer.lock
88
.rector/
99
.idea/
1010
.vscode/
11+
.bully/

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,18 +112,20 @@ When `routes.enabled` is `false`, the package registers no HTTP routes — wire
112112

113113
### Authorization gates
114114

115-
modman defines two gates with fail-closed defaults:
115+
modman defines three gates with fail-closed defaults:
116116

117+
- `modman.view` — checked before `GET /modman/reports/{report}`
117118
- `modman.resolve` — checked before `POST /modman/reports/{report}/resolve`
118119
- `modman.reopen` — checked before `POST /modman/reports/{report}/reopen`
119120

120-
Both deny by default (every request returns 403) until the host overrides them. Register replacements in any service provider that boots before `ModmanServiceProvider`, or simply at runtime — `Gate::has()` keeps the package from clobbering your definition:
121+
All three deny by default (every request returns 403) until the host overrides them. Register replacements in any service provider that boots before `ModmanServiceProvider`, or simply at runtime — `Gate::has()` keeps the package from clobbering your definition:
121122

122123
```php
123124
use Dynamik\Modman\Models\Report;
124125
use Illuminate\Support\Facades\Gate;
125126
use App\Models\User;
126127

128+
Gate::define('modman.view', fn (User $user, Report $report) => $user->is_moderator);
127129
Gate::define('modman.resolve', fn (User $user, Report $report) => $user->is_moderator);
128130
Gate::define('modman.reopen', fn (User $user, Report $report) => $user->is_moderator);
129131
```
@@ -142,8 +144,8 @@ The controllers respond with 401 when no user is authenticated, 403 when the aut
142144

143145
## Requirements
144146

145-
- PHP 8.3+
146-
- Laravel 11 or 12
147+
- PHP 8.3+ (Laravel 13 requires PHP 8.4+ via spatie/laravel-model-states)
148+
- Laravel 11, 12, or 13
147149
- spatie/laravel-model-states ^2.7
148150

149151
## License

composer.json

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,25 @@
77
"php": "^8.3",
88
"ext-intl": "*",
99
"ext-mbstring": "*",
10-
"illuminate/contracts": "^11.0|^12.0",
11-
"illuminate/database": "^11.0|^12.0",
12-
"illuminate/http": "^11.0|^12.0",
13-
"illuminate/queue": "^11.0|^12.0",
14-
"illuminate/support": "^11.0|^12.0",
10+
"illuminate/contracts": "^11.0|^12.0|^13.0",
11+
"illuminate/database": "^11.0|^12.0|^13.0",
12+
"illuminate/http": "^11.0|^12.0|^13.0",
13+
"illuminate/queue": "^11.0|^12.0|^13.0",
14+
"illuminate/support": "^11.0|^12.0|^13.0",
1515
"spatie/laravel-model-states": "^2.7"
1616
},
1717
"require-dev": {
18-
"orchestra/testbench": "^9.0|^10.0",
19-
"pestphp/pest": "^3.0",
20-
"pestphp/pest-plugin-arch": "^3.0",
21-
"pestphp/pest-plugin-laravel": "^3.0",
18+
"orchestra/testbench": "^9.0|^10.0|^11.0",
19+
"pestphp/pest": "^3.0|^4.0",
20+
"pestphp/pest-plugin-arch": "^3.0|^4.0",
21+
"pestphp/pest-plugin-laravel": "^3.0|^4.0",
2222
"larastan/larastan": "^3.0",
2323
"laravel/pint": "^1.18",
24-
"rector/rector": "^2.0",
25-
"driftingly/rector-laravel": "^2.0",
24+
"phpstan/phpstan-strict-rules": "^2.0",
2625
"icanhazstring/composer-unused": "^0.8|^0.9",
27-
"maglnet/composer-require-checker": "^4.14"
26+
"maglnet/composer-require-checker": "^4.14",
27+
"phpstan/phpstan-deprecation-rules": "^2.0",
28+
"roave/security-advisories": "dev-latest"
2829
},
2930
"autoload": {
3031
"psr-4": {

phpstan.neon

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
includes:
22
- vendor/larastan/larastan/extension.neon
3+
- vendor/phpstan/phpstan-strict-rules/rules.neon
4+
- vendor/phpstan/phpstan-deprecation-rules/rules.neon
35

46
parameters:
57
level: 9
@@ -15,3 +17,9 @@ parameters:
1517
-
1618
identifier: missingType.generics
1719
path: src/Models/Decision.php
20+
# Laravel routes Builder/Request methods like lockForUpdate, pluck, and
21+
# validate through __call. They are declared static on the underlying
22+
# class but the framework expects instance-style calls; phpstan-strict-rules
23+
# flags these as false positives.
24+
-
25+
identifier: staticMethod.dynamicCall

rector.php

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/Concerns/Reportable.php

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,18 @@ public function report(?Model $reporter = null, ?string $reason = null): Report
4040
'state' => 'pending',
4141
]));
4242

43-
Event::dispatch(new ReportCreated($report));
44-
45-
RunModerationPipeline::dispatch($report->id)
46-
->onConnection(RunModerationPipeline::connectionName())
47-
->onQueue(RunModerationPipeline::queueName());
43+
// Defer both the domain event and the pipeline-job enqueue to commit.
44+
// If a host wraps this call in a larger transaction that rolls back,
45+
// listeners must NOT see a phantom Report and the queue must NOT receive
46+
// a job pointing at a row that never persisted. DB::afterCommit() runs
47+
// immediately when there is no active transaction.
48+
DB::afterCommit(static function () use ($report): void {
49+
Event::dispatch(new ReportCreated($report));
50+
51+
RunModerationPipeline::dispatch($report->id)
52+
->onConnection(RunModerationPipeline::connectionName())
53+
->onQueue(RunModerationPipeline::queueName());
54+
});
4855

4956
return $report;
5057
}

0 commit comments

Comments
 (0)