Skip to content

Commit a1eae70

Browse files
authored
Merge pull request #148 from laravel-shift/l13-compatibility
Laravel 13.x Compatibility
2 parents fbcf91e + 4d7d8f1 commit a1eae70

20 files changed

Lines changed: 140 additions & 108 deletions

.github/workflows/tests.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
fail-fast: true
1313
matrix:
1414
php: [8.1, 8.2, 8.3]
15-
laravel: ["10.*", "11.*", "12.*"]
15+
laravel: ['10.*', '11.*', '12.*', '13.*']
1616
dependency-version: [prefer-stable]
1717
include:
1818
- laravel: 10.*
@@ -21,13 +21,19 @@ jobs:
2121
testbench: 9.*
2222
- laravel: 12.*
2323
testbench: 10.*
24+
- laravel: 13.*
25+
testbench: 11.*
2426
exclude:
2527
- laravel: 9.*
2628
php: 8.3
2729
- laravel: 11.*
2830
php: 8.1
2931
- laravel: 12.*
3032
php: 8.1
33+
- laravel: 13.*
34+
php: 8.1
35+
- laravel: 13.*
36+
php: 8.2
3137

3238
name: PHP ${{ matrix.php }} - LARAVEL ${{ matrix.laravel }} - ${{ matrix.dependency-version }}
3339

composer.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
],
1919
"require": {
2020
"php": "^8.1",
21-
"illuminate/database": "^10.0||^11.0||^12.0",
22-
"illuminate/events": "^10.0||^11.0||^12.0"
21+
"illuminate/database": "^10.0||^11.0||^12.0||^13.0",
22+
"illuminate/events": "^10.0||^11.0||^12.0||^13.0"
2323
},
2424
"require-dev": {
25-
"orchestra/testbench": "^8.0||^9.0||^10.0",
26-
"phpunit/phpunit": "^10.5||^11.5.3",
25+
"orchestra/testbench": "^8.0||^9.0||^10.0||^11.0",
26+
"phpunit/phpunit": "^10.5||^11.5.3||^12.5.12",
2727
"tightenco/duster": "^3.1"
2828
},
2929
"autoload": {

src/HasChildren.php

Lines changed: 24 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,6 @@
1616
*/
1717
trait HasChildren
1818
{
19-
/**
20-
* @var bool
21-
*/
22-
protected static $parentBootMethods;
23-
2419
/**
2520
* @var bool
2621
*/
@@ -46,38 +41,39 @@ protected static function registerModelEvent($event, $callback): void
4641
{
4742
parent::registerModelEvent($event, $callback);
4843

44+
// Only the parent class should forward events to children. When called
45+
// from a child class, the child already received this event via the
46+
// parent::registerModelEvent call above, so we'll skip for that.
47+
if (static::class !== self::class) {
48+
return;
49+
}
50+
51+
// Don't forward events to children models during the parent's boot
52+
// lifecycle, as the children will also be booting and will also
53+
// register these events themselves via their boot lifecycle.
54+
if (self::parentIsBooting()) {
55+
return;
56+
}
57+
4958
$childTypes = (new static)->getChildTypes();
5059

51-
if (static::class === self::class && $childTypes !== []) {
52-
// We don't want to register the callbacks that happen in the boot method of the parent, as they'll be called
53-
// from the child's boot method as well.
54-
if (! self::parentIsBooting()) {
55-
foreach ($childTypes as $childClass) {
56-
if ($childClass !== self::class) {
57-
$childClass::registerModelEvent($event, $callback);
58-
}
59-
}
60+
foreach ($childTypes as $childClass) {
61+
if ($childClass !== self::class) {
62+
$childClass::registerModelEvent($event, $callback);
6063
}
6164
}
6265
}
6366

67+
/**
68+
* Determine if the parent model is currently in its boot lifecycle.
69+
*
70+
* This checks whether bootIfNotBooted is in the call stack, which covers
71+
* the entire boot lifecycle including boot(), booted(), and whenBooted() callbacks.
72+
*/
6473
protected static function parentIsBooting(): bool
6574
{
66-
if (! isset(self::$parentBootMethods)) {
67-
self::$parentBootMethods[] = 'boot';
68-
69-
foreach (class_uses_recursive(self::class) as $trait) {
70-
self::$parentBootMethods[] = 'boot' . class_basename($trait);
71-
}
72-
73-
self::$parentBootMethods = array_flip(self::$parentBootMethods);
74-
}
75-
7675
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $trace) {
77-
$class = isset($trace['class']) ? $trace['class'] : null;
78-
$function = isset($trace['function']) ? $trace['function'] : '';
79-
80-
if ($class === self::class && isset(self::$parentBootMethods[$function])) {
76+
if (($trace['function'] ?? '') === 'bootIfNotBooted') {
8177
return true;
8278
}
8379
}

src/HasParent.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,17 @@ trait HasParent
1919
* @throws ReflectionException
2020
*/
2121
public static function bootHasParent(): void
22+
{
23+
if (method_exists(static::class, 'whenBooted')) {
24+
static::whenBooted(function () {
25+
static::configureHasParentModels();
26+
});
27+
} else {
28+
static::configureHasParentModels();
29+
}
30+
}
31+
32+
protected static function configureHasParentModels(): void
2233
{
2334
// This adds support for using Parental with standalone Eloquent, outside a normal Laravel app.
2435
if (static::getEventDispatcher() === null) {

tests/Features/AuthUserMethodReturnsChildModel.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
namespace Parental\Tests\Features;
44

55
use Parental\Tests\TestCase;
6+
use PHPUnit\Framework\Attributes\Test;
67

78
class AuthUserMethodReturnsChildModel extends TestCase
89
{
9-
/** @test */
10+
#[Test]
1011
public function auth_user_returns_child_model_if_it_exists()
1112
{
1213
Admin::create();

tests/Features/ChildModelFillablesMergeWithParentModelFillablesTest.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
use Parental\Tests\Models\Event;
66
use Parental\Tests\Models\Workshop;
77
use Parental\Tests\TestCase;
8+
use PHPUnit\Framework\Attributes\Test;
89

910
class ChildModelFillablesMergeWithParentModelFillablesTest extends TestCase
1011
{
11-
/** @test */
12+
#[Test]
1213
public function child_fillables_are_merged_with_parent_fillables()
1314
{
1415
$workshop = Workshop::create([

tests/Features/ChildModelsActLikeParentModelsTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@
77
use Parental\Tests\Models\Passenger;
88
use Parental\Tests\Models\Vehicle;
99
use Parental\Tests\TestCase;
10+
use PHPUnit\Framework\Attributes\Test;
1011

1112
class ChildModelsActLikeParentModelsTest extends TestCase
1213
{
13-
/** @test */
14+
#[Test]
1415
public function vehicle_can_access_belongs_to_relationship_on_car_model()
1516
{
1617
$car = Car::create([
@@ -22,7 +23,7 @@ public function vehicle_can_access_belongs_to_relationship_on_car_model()
2223
$this->assertEquals($vehicle->driver->id, $car->driver->id);
2324
}
2425

25-
/** @test */
26+
#[Test]
2627
public function vehicle_can_access_has_many_relationship_on_car_model()
2728
{
2829
$car = Car::create();
@@ -35,7 +36,7 @@ public function vehicle_can_access_has_many_relationship_on_car_model()
3536
$this->assertEquals($vehicle->passengers->pluck('id'), $car->passengers->pluck('id'));
3637
}
3738

38-
/** @test */
39+
#[Test]
3940
public function vehicle_can_access_many_to_many_relationship_on_car_model()
4041
{
4142
$car = Car::create();
@@ -47,7 +48,7 @@ public function vehicle_can_access_many_to_many_relationship_on_car_model()
4748
$this->assertEquals($vehicle->fresh()->trips->pluck('id'), $car->fresh()->trips->pluck('id'));
4849
}
4950

50-
/** @test */
51+
#[Test]
5152
public function guarded_or_fillable_models_have_raw_attributes_like_timestamps()
5253
{
5354
$vehicle = Vehicle::create()->fresh();

tests/Features/ChildModelsAreAutomaticallyScopedTest.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
use Parental\Tests\Models\User;
1414
use Parental\Tests\Models\Vehicle;
1515
use Parental\Tests\TestCase;
16+
use PHPUnit\Framework\Attributes\Test;
1617

1718
class ChildModelsAreAutomaticallyScopedTest extends TestCase
1819
{
19-
/** @test */
20+
#[Test]
2021
public function child_is_scoped_based_on_type_column()
2122
{
2223
Car::create();
@@ -26,7 +27,7 @@ public function child_is_scoped_based_on_type_column()
2627
$this->assertCount(1, Car::all());
2728
}
2829

29-
/** @test */
30+
#[Test]
3031
public function child_without_type_column_isnt_scoped()
3132
{
3233
Admin::create();
@@ -36,7 +37,7 @@ public function child_without_type_column_isnt_scoped()
3637
$this->assertCount(2, Admin::all());
3738
}
3839

39-
/** @test */
40+
#[Test]
4041
public function child_is_scoped_when_accessed_from_belongs_to()
4142
{
4243
$car = Car::create();
@@ -52,7 +53,7 @@ public function child_is_scoped_when_accessed_from_belongs_to()
5253
$this->assertNotNull($passenger->fresh()->vehicle);
5354
}
5455

55-
/** @test */
56+
#[Test]
5657
public function child_is_scoped_when_accessed_from_has_many()
5758
{
5859
$driver = Driver::create(['name' => 'joe']);
@@ -63,7 +64,7 @@ public function child_is_scoped_when_accessed_from_has_many()
6364
$this->assertCount(1, $driver->cars);
6465
}
6566

66-
/** @test */
67+
#[Test]
6768
public function child_is_scoped_when_accessed_from_belongs_to_many()
6869
{
6970
$car = Car::create();
@@ -75,7 +76,7 @@ public function child_is_scoped_when_accessed_from_belongs_to_many()
7576
$this->assertCount(2, $trip->vehicles);
7677
}
7778

78-
/** @test */
79+
#[Test]
7980
public function child_is_scoped_when_accessed_from_has_one_through()
8081
{
8182
// Create root with children
@@ -96,7 +97,7 @@ public function child_is_scoped_when_accessed_from_has_one_through()
9697
$this->assertFalse(ChildNode::whereId($childC->id)->whereHas('parent')->exists());
9798
}
9899

99-
/** @test */
100+
#[Test]
100101
public function child_is_scoped_when_accessed_from_has_many_through()
101102
{
102103
// Create root with children

tests/Features/EagerLoadingOnOlderLaravelTest.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
use Parental\Tests\Models\Video;
88
use Parental\Tests\Models\VideoMessage;
99
use Parental\Tests\TestCase;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\Attributes\Test;
1012
use RuntimeException;
1113

1214
class EagerLoadingOnOlderLaravelTest extends TestCase
@@ -132,11 +134,8 @@ function () {
132134
];
133135
}
134136

135-
/**
136-
* @test
137-
*
138-
* @dataProvider eagerLoadingScenarios
139-
*/
137+
#[Test]
138+
#[DataProvider('eagerLoadingScenarios')]
140139
public function throws_exception_when_eager_loading_on_older_laravel_versions($scenario): void
141140
{
142141
$this->expectException(RuntimeException::class);

tests/Features/EagerLoadingTest.php

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Parental\Tests\Models\Video;
99
use Parental\Tests\Models\VideoMessage;
1010
use Parental\Tests\TestCase;
11+
use PHPUnit\Framework\Attributes\Test;
1112

1213
class EagerLoadingTest extends TestCase
1314
{
@@ -20,7 +21,7 @@ protected function setUp(): void
2021
}
2122
}
2223

23-
/** @test */
24+
#[Test]
2425
public function eager_load_children_on_collection(): void
2526
{
2627
$textMessage = TextMessage::create();
@@ -39,7 +40,7 @@ public function eager_load_children_on_collection(): void
3940
$this->assertTrue($messages->whereInstanceOf(VideoMessage::class)->every->relationLoaded('video'));
4041
}
4142

42-
/** @test */
43+
#[Test]
4344
public function eager_load_children_count_on_collection(): void
4445
{
4546
$textMessage = TextMessage::create();
@@ -56,7 +57,7 @@ public function eager_load_children_count_on_collection(): void
5657
$this->assertEquals($messages->firstWhere(fn ($message) => $message instanceof TextMessage)->images_count, 1);
5758
}
5859

59-
/** @test */
60+
#[Test]
6061
public function eager_load_children_on_paginator(): void
6162
{
6263
$textMessage = TextMessage::create();
@@ -77,7 +78,7 @@ public function eager_load_children_on_paginator(): void
7778
$this->assertEquals($messages->firstWhere(fn ($message) => $message instanceof TextMessage)->images_count, 1);
7879
}
7980

80-
/** @test */
81+
#[Test]
8182
public function eager_load_from_model(): void
8283
{
8384
$textMessage = TextMessage::create();
@@ -93,7 +94,7 @@ public function eager_load_from_model(): void
9394
$this->assertTrue($message->relationLoaded('images'));
9495
}
9596

96-
/** @test */
97+
#[Test]
9798
public function eager_load_counts_on_model(): void
9899
{
99100
$textMessage = TextMessage::create();
@@ -109,7 +110,7 @@ public function eager_load_counts_on_model(): void
109110
$this->assertEquals(1, $message->images_count);
110111
}
111112

112-
/** @test */
113+
#[Test]
113114
public function eager_load_children_from_query(): void
114115
{
115116
$textMessage = TextMessage::create();
@@ -128,7 +129,7 @@ public function eager_load_children_from_query(): void
128129
$this->assertTrue($messages->whereInstanceOf(VideoMessage::class)->every->relationLoaded('video'));
129130
}
130131

131-
/** @test */
132+
#[Test]
132133
public function eager_load_children_count_from_query(): void
133134
{
134135
$textMessage = TextMessage::create();
@@ -146,7 +147,7 @@ public function eager_load_children_count_from_query(): void
146147
$this->assertNull($messages->firstWhere(fn ($message) => $message instanceof VideoMessage)->images_count);
147148
}
148149

149-
/** @test */
150+
#[Test]
150151
public function eager_load_children_from_relationship_query(): void
151152
{
152153
$room = Room::create(['name' => 'General']);
@@ -167,7 +168,7 @@ public function eager_load_children_from_relationship_query(): void
167168
$this->assertTrue($messages->whereInstanceOf(VideoMessage::class)->every->relationLoaded('video'));
168169
}
169170

170-
/** @test */
171+
#[Test]
171172
public function eager_load_children_count_from_relationship_query(): void
172173
{
173174
$room = Room::create(['name' => 'General']);

0 commit comments

Comments
 (0)