Skip to content

Commit 2a94e22

Browse files
committed
Added laravel 11, PHP 8.3 support; Added handling for ids not in relation scope
1 parent 6056632 commit 2a94e22

File tree

12 files changed

+306
-42
lines changed

12 files changed

+306
-42
lines changed

.github/workflows/lint.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
with:
1515
command: install
1616
only_args: -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-reqs
17-
php_version: 8.1
17+
php_version: 8.3
1818

1919
- name: Run PHP-CS-Fixer
2020
run: vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff --dry-run
@@ -30,7 +30,7 @@ jobs:
3030
with:
3131
command: install
3232
only_args: -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-reqs
33-
php_version: 8.1
33+
php_version: 8.3
3434

3535
- name: Run PHP CodeSniffer
3636
run: vendor/bin/phpcs --extensions=php

.github/workflows/static-analysis.yml

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: Static Analysis
2+
3+
on: [ push ]
4+
5+
jobs:
6+
PHPUnit:
7+
strategy:
8+
matrix:
9+
include:
10+
# Laravel 10.*
11+
- php: 8.1
12+
laravel: 10.*
13+
composer-flag: '--prefer-stable'
14+
- php: 8.2
15+
laravel: 10.*
16+
composer-flag: '--prefer-stable'
17+
- php: 8.3
18+
laravel: 10.*
19+
composer-flag: '--prefer-stable'
20+
- php: 8.1
21+
laravel: 10.*
22+
composer-flag: '--prefer-lowest'
23+
- php: 8.2
24+
laravel: 10.*
25+
composer-flag: '--prefer-lowest'
26+
- php: 8.3
27+
laravel: 10.*
28+
composer-flag: '--prefer-lowest'
29+
# Laravel 11.*
30+
- php: 8.2
31+
laravel: 11.*
32+
testbench: 9.*
33+
composer-flag: '--prefer-stable'
34+
- php: 8.3
35+
laravel: 11.*
36+
testbench: 9.*
37+
composer-flag: '--prefer-stable'
38+
- php: 8.2
39+
laravel: 11.*
40+
testbench: 9.*
41+
composer-flag: '--prefer-lowest'
42+
- php: 8.2
43+
laravel: 11.*
44+
testbench: 9.*
45+
composer-flag: '--prefer-lowest'
46+
47+
runs-on: ubuntu-latest
48+
49+
steps:
50+
- uses: actions/checkout@v4
51+
52+
- name: Setup PHP
53+
uses: shivammathur/setup-php@v2
54+
with:
55+
php-version: ${{ matrix.php }}
56+
extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv
57+
coverage: none
58+
59+
- name: Install dependencies
60+
run: composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update
61+
62+
- name: Update dependencies
63+
run: composer update ${{ matrix.composer-flag }} --prefer-dist --no-interaction
64+
65+
- name: Run PhpStan
66+
run: composer analyse

.github/workflows/unittests.yml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ jobs:
1616
laravel: 10.*
1717
testbench: 8.*
1818
composer-flag: '--prefer-stable'
19+
- php: 8.3
20+
laravel: 10.*
21+
testbench: 8.*
22+
composer-flag: '--prefer-stable'
1923
- php: 8.1
2024
laravel: 10.*
2125
testbench: 8.*
@@ -24,6 +28,27 @@ jobs:
2428
laravel: 10.*
2529
testbench: 8.*
2630
composer-flag: '--prefer-lowest'
31+
- php: 8.3
32+
laravel: 10.*
33+
testbench: 8.*
34+
composer-flag: '--prefer-lowest'
35+
# Laravel 11.*
36+
- php: 8.2
37+
laravel: 11.*
38+
testbench: 9.*
39+
composer-flag: '--prefer-stable'
40+
- php: 8.3
41+
laravel: 11.*
42+
testbench: 9.*
43+
composer-flag: '--prefer-stable'
44+
- php: 8.2
45+
laravel: 11.*
46+
testbench: 9.*
47+
composer-flag: '--prefer-lowest'
48+
- php: 8.2
49+
laravel: 11.*
50+
testbench: 9.*
51+
composer-flag: '--prefer-lowest'
2752

2853
runs-on: ubuntu-latest
2954

@@ -47,4 +72,6 @@ jobs:
4772
run: XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml
4873

4974
- name: Upload coverage reports to Codecov
50-
uses: codecov/codecov-action@v3
75+
uses: codecov/codecov-action@v4
76+
env:
77+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ composer require korridor/laravel-has-many-merged "^1"
2828

2929
This package is tested for the following Laravel and PHP versions:
3030

31-
- 10.* (PHP 8.1, 8.2)
31+
- 10.* (PHP 8.1, 8.2, 8.3)
32+
- 11.* (PHP 8.2, 8.3)
3233

3334
## Usage
3435

composer.json

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,16 @@
1717
],
1818
"require": {
1919
"php": ">=8.1",
20-
"illuminate/database": "^10",
21-
"illuminate/support": "^10"
20+
"illuminate/database": "^10|^11",
21+
"illuminate/support": "^10|^11"
2222
},
2323
"require-dev": {
24-
"orchestra/testbench": "^8",
25-
"phpunit/phpunit": "^10.0",
2624
"friendsofphp/php-cs-fixer": "^3",
25+
"larastan/larastan": "^2.0",
26+
"orchestra/testbench": "^8|^9",
27+
"phpunit/phpunit": "^10.0",
2728
"squizlabs/php_codesniffer": "^3.5"
2829
},
29-
"minimum-stability": "stable",
3030
"autoload": {
3131
"psr-4": {
3232
"Korridor\\LaravelHasManySync\\": "src"
@@ -51,9 +51,14 @@
5151
"@php vendor/bin/phpunit --coverage-html coverage"
5252
],
5353
"fix": "@php ./vendor/bin/php-cs-fixer fix",
54-
"lint": "@php ./vendor/bin/phpcs --extensions=php"
54+
"lint": "@php ./vendor/bin/phpcs --extensions=php",
55+
"analyse": [
56+
"@php ./vendor/bin/phpstan analyse --memory-limit=2G"
57+
]
5558
},
5659
"config": {
5760
"sort-packages": true
58-
}
61+
},
62+
"minimum-stability": "dev",
63+
"prefer-stable": true
5964
}

docker/Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM php:8.1-cli
1+
FROM php:8.3-cli
22

33
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
44

phpstan.neon

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
includes:
2+
- ./vendor/larastan/larastan/extension.neon
3+
4+
parameters:
5+
6+
paths:
7+
- src
8+
- tests
9+
10+
# Level 9 is the highest level
11+
level: 9

src/ServiceProvider.php

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
namespace Korridor\LaravelHasManySync;
66

7+
use Illuminate\Database\Eloquent\Model;
8+
use Illuminate\Database\Eloquent\ModelNotFoundException;
79
use Illuminate\Database\Eloquent\Relations\HasMany;
810
use Illuminate\Support\Arr;
911
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
@@ -17,37 +19,51 @@ class ServiceProvider extends BaseServiceProvider
1719
*/
1820
public function boot(): void
1921
{
20-
HasMany::macro('sync', function (array $data, bool $deleting = true): array {
22+
23+
HasMany::macro('sync', function (array $data, bool $deleting = true, bool $throwOnIdNotInScope = true): array {
2124
$changes = [
2225
'created' => [], 'deleted' => [], 'updated' => [],
2326
];
2427

25-
/** @var HasMany $this */
28+
/** @var HasMany<Model> $this */
2629

2730
// Get the primary key.
2831
$relatedKeyName = $this->getRelated()->getKeyName();
2932

3033
// Get the current key values.
31-
$current = $this->newQuery()->pluck($relatedKeyName)->all();
34+
$currentIds = $this->newQuery()->pluck($relatedKeyName)->all();
3235

3336
// Cast the given key to an integer if it is numeric.
3437
$castKey = function ($value) {
3538
if (is_null($value)) {
36-
return $value;
39+
return null;
3740
}
3841

3942
return is_numeric($value) ? (int) $value : (string) $value;
4043
};
4144

4245
// Cast the given keys to integers if they are numeric and string otherwise.
43-
$castKeys = function ($keys) use ($castKey) {
46+
$castKeys = function ($keys) use ($castKey): array {
4447
return (array) array_map(function ($key) use ($castKey) {
4548
return $castKey($key);
4649
}, $keys);
4750
};
4851

52+
// The new ids, without null values
53+
$dataIds = Arr::where($castKeys(Arr::pluck($data, $relatedKeyName)), function ($value) {
54+
return !is_null($value);
55+
});
56+
57+
$problemKeys = array_diff($dataIds, $currentIds);
58+
if ($throwOnIdNotInScope && count($problemKeys) > 0) {
59+
throw (new ModelNotFoundException())->setModel(
60+
get_class($this->getRelated()),
61+
$problemKeys
62+
);
63+
}
64+
4965
// Get any non-matching rows.
50-
$deletedKeys = array_diff($current, $castKeys(Arr::pluck($data, $relatedKeyName)));
66+
$deletedKeys = array_diff($currentIds, $dataIds);
5167

5268
if ($deleting && count($deletedKeys) > 0) {
5369
$this->getRelated()->destroy($deletedKeys);
@@ -56,13 +72,15 @@ public function boot(): void
5672

5773
// Separate the submitted data into "update" and "new"
5874
// We determine "newRows" as those whose $relatedKeyName (usually 'id') is null.
59-
$newRows = Arr::where($data, function ($row) use ($relatedKeyName) {
60-
return null === Arr::get($row, $relatedKeyName);
75+
$newRows = Arr::where($data, function (array $row) use ($relatedKeyName) {
76+
$id = Arr::get($row, $relatedKeyName);
77+
return $id === null;
6178
});
6279

6380
// We determine "updateRows" as those whose $relatedKeyName (usually 'id') is set, not null.
64-
$updatedRows = Arr::where($data, function ($row) use ($relatedKeyName) {
65-
return null !== Arr::get($row, $relatedKeyName);
81+
$updateRows = Arr::where($data, function (array $row) use ($relatedKeyName, $problemKeys) {
82+
$id = Arr::get($row, $relatedKeyName);
83+
return $id !== null && !in_array($id, $problemKeys, true);
6684
});
6785

6886
if (count($newRows) > 0) {
@@ -72,14 +90,16 @@ public function boot(): void
7290
);
7391
}
7492

75-
foreach ($updatedRows as $row) {
76-
$this->getRelated()
93+
foreach ($updateRows as $row) {
94+
$updateModel = $this->getRelated()
95+
->newQuery()
7796
->where($relatedKeyName, $castKey(Arr::get($row, $relatedKeyName)))
78-
->first()
79-
->update($row);
97+
->firstOrFail();
98+
99+
$updateModel->update($row);
80100
}
81101

82-
$changes['updated'] = $castKeys(Arr::pluck($updatedRows, $relatedKeyName));
102+
$changes['updated'] = $castKeys(Arr::pluck($updateRows, $relatedKeyName));
83103

84104
return $changes;
85105
});

0 commit comments

Comments
 (0)