Skip to content

Commit 1c9d2cb

Browse files
authored
Merge pull request #30 from tenantcloud/next-major
Laravel 12
2 parents 0ff5716 + 6dc0150 commit 1c9d2cb

File tree

10 files changed

+214
-49
lines changed

10 files changed

+214
-49
lines changed

.github/workflows/tests.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
fail-fast: true
1919
matrix:
2020
php: [8.2, 8.3]
21-
test-bench: [^8.0, ^9.0]
21+
test-bench: [^8.0, ^9.0, ^10.0]
2222

2323
runs-on: packages
2424
container: chialab/php:${{ matrix.php }}
@@ -80,7 +80,7 @@ jobs:
8080
fail-fast: true
8181
matrix:
8282
php: [8.2, 8.3]
83-
test-bench: [^8.0, ^9.0]
83+
test-bench: [^8.0, ^9.0, ^10.0]
8484

8585
runs-on: packages
8686
container: chialab/php:${{ matrix.php }}

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ Also you can change default column name `is_deleted` to any other by setting sta
3535
Versions compatibility
3636

3737
```bash
38-
For Laravel 9 - laravel-boolean-softdeletes 4.*
3938
For Laravel 10 - laravel-boolean-softdeletes 5.*
4039
For Laravel 11 - laravel-boolean-softdeletes 6.*
40+
For Laravel 11 - laravel-boolean-softdeletes 7.*
4141
```
4242

4343
## Change log

composer.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@
44
"license": "MIT",
55
"require": {
66
"php": ">=8.2",
7-
"illuminate/contracts": "^10.0|^11.0",
8-
"illuminate/console": "^9.0|^10.0|^11.0",
9-
"illuminate/database": "^9.0|^10.0|^11.0",
10-
"illuminate/support": "^9.0|^10.0|^11.0"
7+
"illuminate/contracts": "^10.0|^11.0|^12.0",
8+
"illuminate/console": "^9.0|^10.0|^11.0|^12.0",
9+
"illuminate/database": "^9.0|^10.0|^11.0|^12.0",
10+
"illuminate/support": "^9.0|^10.0|^11.0|^12.0"
1111
},
1212
"require-dev": {
13-
"pestphp/pest": "^2.8",
13+
"pestphp/pest": "^2.8|^3.7",
1414
"php-cs-fixer/shim": "^3.54",
1515
"tenantcloud/php-cs-fixer-rule-sets": "~3.3.1",
16-
"phpstan/phpstan": "~1.10.21",
17-
"phpstan/phpstan-phpunit": "^1.3",
18-
"phpstan/phpstan-webmozart-assert": "^1.2",
19-
"phpstan/phpstan-mockery": "^1.1",
20-
"nunomaduro/larastan": "^2.6",
21-
"orchestra/testbench": "^8.5|^9.0"
16+
"phpstan/phpstan": "~1.10.21|^2.1",
17+
"phpstan/phpstan-phpunit": "^1.3|^2.0",
18+
"phpstan/phpstan-webmozart-assert": "^1.2|^2.0",
19+
"phpstan/phpstan-mockery": "^1.1|^2.0",
20+
"nunomaduro/larastan": "^2.6|^3.1",
21+
"orchestra/testbench": "^8.5|^9.0|^10.0"
2222
},
2323
"autoload": {
2424
"psr-4": {

phpunit.xml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
colors="true"
55
cacheDirectory=".phpunit.cache">
66
<testsuites>
7-
<testsuite name="Test Suite">
8-
<directory>./tests</directory>
7+
<testsuite name="Feature">
8+
<directory suffix="Test.php">./tests/Feature</directory>
99
</testsuite>
1010
</testsuites>
1111
<coverage/>
@@ -16,6 +16,7 @@
1616
</source>
1717
<php>
1818
<env name="APP_ENV" value="testing"/>
19-
<env name="DB_CONNECTION" value="testing"/>
19+
<env name="DB_CONNECTION" value="sqlite"/>
20+
<env name="DB_DATABASE" value=":memory:"/>
2021
</php>
2122
</phpunit>

src/Commands/MigrateSoftDeletes.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,26 @@ public function handle(): void
2929
{
3030
$table = $this->ask('Provide table name');
3131

32-
if (!Schema::hasTable($table)) {
32+
if (!is_string($table) || !Schema::hasTable($table)) {
3333
$this->error('Wrong table name. Try again, please');
34+
35+
return;
3436
}
3537

3638
$field_name = $this->ask('Provide field name. If is_deleted than leave blank', 'is_deleted');
3739

3840
$old_field_name = $this->ask('Provide old field name. If deleted_at than leave blank', 'deleted_at');
3941

42+
if (!is_string($field_name) || !is_string($old_field_name)) {
43+
$this->error('Wrong field name or old field name.');
44+
45+
return;
46+
}
47+
4048
DB::table($table)->update([
41-
$field_name => DB::raw("IF({$old_field_name} IS NULL, 0, 1)"),
49+
$field_name => DB::raw("CASE WHEN {$old_field_name} IS NULL THEN 0 ELSE 1 END"),
4250
]);
51+
52+
$this->info('Table has been migrated!');
4353
}
4454
}

src/SoftDeletesBoolean.php

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@
1010
*/
1111
trait SoftDeletesBoolean
1212
{
13-
/**
14-
* Indicates if the model is currently force deleting.
15-
*
16-
* @var bool
17-
*/
18-
protected $forceDeleting = false;
13+
/** Indicates if the model is currently force deleting. */
14+
protected bool $forceDeleting = false;
1915

2016
/**
2117
* Boot the soft deleting trait for a model.
@@ -28,7 +24,7 @@ public static function bootSoftDeletesBoolean(): void
2824
/**
2925
* Force a hard delete on a soft deleted model.
3026
*/
31-
public function forceDelete(): ?bool
27+
public function forceDelete(): bool
3228
{
3329
$this->forceDeleting = true;
3430

@@ -38,13 +34,13 @@ public function forceDelete(): ?bool
3834
if ($deleted) {
3935
$this->fireModelEvent('forceDeleted', false);
4036
}
41-
});
37+
}) ?? false;
4238
}
4339

4440
/**
4541
* Restore a soft-deleted model instance.
4642
*/
47-
public function restore(): ?bool
43+
public function restore(): bool
4844
{
4945
// If the restoring event does not return false, we will proceed with this
5046
// restore operation. Otherwise, we bail out so the developer will stop
@@ -53,7 +49,7 @@ public function restore(): ?bool
5349
return false;
5450
}
5551

56-
$this->{$this->getIsDeletedColumn()} = 0;
52+
$this->{$this->getIsDeletedColumn()} = false;
5753

5854
// Once we have saved the model, we will fire the "restored" event so this
5955
// developer will do anything they need to after a restore operation is
@@ -77,30 +73,24 @@ public function trashed(): bool
7773

7874
/**
7975
* Register a restoring model event with the dispatcher.
80-
*
81-
* @param Closure|string $callback
8276
*/
83-
public static function restoring($callback)
77+
public static function restoring(Closure|string $callback): void
8478
{
8579
static::registerModelEvent('restoring', $callback);
8680
}
8781

8882
/**
8983
* Register a restored model event with the dispatcher.
90-
*
91-
* @param Closure|string|array $callback
9284
*/
93-
public static function restored($callback): void
85+
public static function restored(Closure|string|array $callback): void
9486
{
9587
static::registerModelEvent('restored', $callback);
9688
}
9789

9890
/**
9991
* Register a "forceDeleted" model event callback with the dispatcher.
100-
*
101-
* @param Closure|string|array $callback
10292
*/
103-
public static function forceDeleted($callback): void
93+
public static function forceDeleted(Closure|string|array $callback): void
10494
{
10595
static::registerModelEvent('forceDeleted', $callback);
10696
}
@@ -118,15 +108,17 @@ public function isForceDeleting(): bool
118108
*/
119109
public function getIsDeletedColumn(): string
120110
{
121-
return defined('static::IS_DELETED') ? constant('static::IS_DELETED') : 'is_deleted';
111+
return defined(static::class . '::IS_DELETED')
112+
? constant(static::class . '::IS_DELETED')
113+
: 'is_deleted';
122114
}
123115

124116
/**
125117
* Get the fully qualified "deleted at" column.
126118
*/
127119
public function getQualifiedIsDeletedColumn(): string
128120
{
129-
return $this->getTable() . '.' . $this->getIsDeletedColumn();
121+
return "{$this->getTable()}.{$this->getIsDeletedColumn()}";
130122
}
131123

132124
/**
@@ -137,7 +129,9 @@ protected function performDeleteOnModel(): ?bool
137129
if ($this->forceDeleting) {
138130
$this->exists = false;
139131

140-
return $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey())->forceDelete();
132+
return (bool) $this->newQueryWithoutScopes()
133+
->where($this->getKeyName(), $this->getKey())
134+
->forceDelete();
141135
}
142136

143137
$this->runSoftDelete();
@@ -151,17 +145,14 @@ protected function performDeleteOnModel(): ?bool
151145
protected function runSoftDelete(): void
152146
{
153147
$query = $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey());
154-
155148
$time = $this->freshTimestamp();
156149

157-
$columns = [$this->getIsDeletedColumn() => 1];
158-
159-
$this->{$this->getIsDeletedColumn()} = 1;
160-
161-
if ($this->timestamps && null !== $this->getUpdatedAtColumn()) {
162-
$this->{$this->getUpdatedAtColumn()} = $time;
150+
$this->{$this->getIsDeletedColumn()} = true;
151+
$columns = [$this->getIsDeletedColumn() => true];
163152

164-
$columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time);
153+
if ($this->timestamps && ($updatedAt = $this->getUpdatedAtColumn()) !== null) {
154+
$this->{$updatedAt} = $time;
155+
$columns[$updatedAt] = $this->fromDateTime($time);
165156
}
166157

167158
$query->update($columns);

testbench.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
providers:
2+
- Webkid\LaravelBooleanSoftdeletes\LaravelBooleanSoftdeletesServiceProvider
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Illuminate\Foundation\Testing\DatabaseMigrations;
6+
use Illuminate\Support\Facades\DB;
7+
use Orchestra\Testbench\Concerns\WithWorkbench;
8+
use Orchestra\Testbench\TestCase;
9+
use PHPUnit\Framework\Attributes\CoversClass;
10+
use Webkid\LaravelBooleanSoftdeletes\Commands\MigrateSoftDeletes;
11+
12+
#[CoversClass(MigrateSoftDeletes::class)]
13+
class MigrateSoftDeletesTest extends TestCase
14+
{
15+
use DatabaseMigrations;
16+
use WithWorkbench;
17+
18+
/**
19+
* Setup the test environment.
20+
*/
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
26+
}
27+
28+
/**
29+
* Test the soft deletes migration command.
30+
*/
31+
public function testCommandMigratesSoftDeletes(): void
32+
{
33+
// Insert test data
34+
DB::table('test_items')->insert([
35+
['id' => 1, 'deleted_at' => null, 'is_deleted' => false],
36+
['id' => 2, 'deleted_at' => now(), 'is_deleted' => false],
37+
]);
38+
39+
// Run the command with mock user inputs
40+
$this->artisan(MigrateSoftDeletes::class)
41+
->expectsQuestion('Provide table name', 'test_items')
42+
->expectsQuestion('Provide field name. If is_deleted than leave blank', 'is_deleted')
43+
->expectsQuestion('Provide old field name. If deleted_at than leave blank', 'deleted_at')
44+
->expectsOutput('Table has been migrated!')
45+
->assertExitCode(0);
46+
47+
// Assert database updates
48+
$this->assertDatabaseHas('test_items', ['id' => 1, 'is_deleted' => false]);
49+
$this->assertDatabaseHas('test_items', ['id' => 2, 'is_deleted' => true]);
50+
}
51+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Tests\Feature;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Foundation\Testing\DatabaseMigrations;
7+
use Orchestra\Testbench\Concerns\WithWorkbench;
8+
use Orchestra\Testbench\TestCase;
9+
use Webkid\LaravelBooleanSoftdeletes\SoftDeletesBoolean;
10+
11+
/**
12+
* Test case for the SoftDeletesBoolean trait.
13+
*/
14+
class SoftDeletesBooleanTest extends TestCase
15+
{
16+
use DatabaseMigrations;
17+
use WithWorkbench;
18+
19+
protected Model $model;
20+
21+
protected function setUp(): void
22+
{
23+
parent::setUp();
24+
25+
// Run migration for test model
26+
$this->loadMigrationsFrom(__DIR__ . '/../database/migrations');
27+
28+
$this->model = new class () extends Model {
29+
use SoftDeletesBoolean;
30+
31+
public $timestamps = false;
32+
33+
protected $table = 'test_items';
34+
};
35+
}
36+
37+
public function testSoftDeletesModel(): void
38+
{
39+
$model = $this->model::create();
40+
41+
// Ensure model is not deleted initially
42+
$this->assertFalse($model->trashed());
43+
44+
// Soft delete the model
45+
$model->delete();
46+
47+
// Ensure it is marked as deleted
48+
$this->assertTrue($model->trashed());
49+
$this->assertDatabaseHas('test_items', ['id' => $model->id, 'is_deleted' => true]);
50+
}
51+
52+
public function testRestoresSoftDeletedModel(): void
53+
{
54+
$model = $this->model::create();
55+
$model->delete();
56+
57+
// Restore the model
58+
$model->restore();
59+
60+
// Ensure it is no longer deleted
61+
$this->assertFalse($model->trashed());
62+
$this->assertDatabaseHas('test_items', ['id' => $model->id, 'is_deleted' => false]);
63+
}
64+
65+
public function testForceDeletesModel(): void
66+
{
67+
$model = $this->model::create();
68+
69+
// Ensure the model exists
70+
$this->assertDatabaseHas('test_items', ['id' => $model->id]);
71+
72+
// Force delete
73+
$model->forceDelete();
74+
75+
// Ensure the model is completely removed from DB
76+
$this->assertDatabaseMissing('test_items', ['id' => $model->id]);
77+
}
78+
79+
public function testDoesNotForceDeleteIfSoftDeleted(): void
80+
{
81+
$model = $this->model::create();
82+
83+
// Soft delete
84+
$model->delete();
85+
86+
// Ensure still exists but marked as deleted
87+
$this->assertDatabaseHas('test_items', ['id' => $model->id, 'is_deleted' => true]);
88+
}
89+
}

0 commit comments

Comments
 (0)