Skip to content

Commit

Permalink
Added laravel 11, PHP 8.3 support; Added handling for ids not in rela…
Browse files Browse the repository at this point in the history
…tion scope
  • Loading branch information
korridor committed Mar 1, 2024
1 parent 6056632 commit 2a94e22
Show file tree
Hide file tree
Showing 12 changed files with 306 additions and 42 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
with:
command: install
only_args: -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist --ignore-platform-reqs
php_version: 8.1
php_version: 8.3

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

- name: Run PHP CodeSniffer
run: vendor/bin/phpcs --extensions=php
66 changes: 66 additions & 0 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Static Analysis

on: [ push ]

jobs:
PHPUnit:
strategy:
matrix:
include:
# Laravel 10.*
- php: 8.1
laravel: 10.*
composer-flag: '--prefer-stable'
- php: 8.2
laravel: 10.*
composer-flag: '--prefer-stable'
- php: 8.3
laravel: 10.*
composer-flag: '--prefer-stable'
- php: 8.1
laravel: 10.*
composer-flag: '--prefer-lowest'
- php: 8.2
laravel: 10.*
composer-flag: '--prefer-lowest'
- php: 8.3
laravel: 10.*
composer-flag: '--prefer-lowest'
# Laravel 11.*
- php: 8.2
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-stable'
- php: 8.3
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-stable'
- php: 8.2
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-lowest'
- php: 8.2
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-lowest'

runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: curl, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, iconv
coverage: none

- name: Install dependencies
run: composer require "laravel/framework:${{ matrix.laravel }}" --no-interaction --no-update

- name: Update dependencies
run: composer update ${{ matrix.composer-flag }} --prefer-dist --no-interaction

- name: Run PhpStan
run: composer analyse
29 changes: 28 additions & 1 deletion .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ jobs:
laravel: 10.*
testbench: 8.*
composer-flag: '--prefer-stable'
- php: 8.3
laravel: 10.*
testbench: 8.*
composer-flag: '--prefer-stable'
- php: 8.1
laravel: 10.*
testbench: 8.*
Expand All @@ -24,6 +28,27 @@ jobs:
laravel: 10.*
testbench: 8.*
composer-flag: '--prefer-lowest'
- php: 8.3
laravel: 10.*
testbench: 8.*
composer-flag: '--prefer-lowest'
# Laravel 11.*
- php: 8.2
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-stable'
- php: 8.3
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-stable'
- php: 8.2
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-lowest'
- php: 8.2
laravel: 11.*
testbench: 9.*
composer-flag: '--prefer-lowest'

runs-on: ubuntu-latest

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

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ composer require korridor/laravel-has-many-merged "^1"

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

- 10.* (PHP 8.1, 8.2)
- 10.* (PHP 8.1, 8.2, 8.3)
- 11.* (PHP 8.2, 8.3)

## Usage

Expand Down
19 changes: 12 additions & 7 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@
],
"require": {
"php": ">=8.1",
"illuminate/database": "^10",
"illuminate/support": "^10"
"illuminate/database": "^10|^11",
"illuminate/support": "^10|^11"
},
"require-dev": {
"orchestra/testbench": "^8",
"phpunit/phpunit": "^10.0",
"friendsofphp/php-cs-fixer": "^3",
"larastan/larastan": "^2.0",
"orchestra/testbench": "^8|^9",
"phpunit/phpunit": "^10.0",
"squizlabs/php_codesniffer": "^3.5"
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"Korridor\\LaravelHasManySync\\": "src"
Expand All @@ -51,9 +51,14 @@
"@php vendor/bin/phpunit --coverage-html coverage"
],
"fix": "@php ./vendor/bin/php-cs-fixer fix",
"lint": "@php ./vendor/bin/phpcs --extensions=php"
"lint": "@php ./vendor/bin/phpcs --extensions=php",
"analyse": [
"@php ./vendor/bin/phpstan analyse --memory-limit=2G"
]
},
"config": {
"sort-packages": true
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM php:8.1-cli
FROM php:8.3-cli

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

Expand Down
11 changes: 11 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
includes:
- ./vendor/larastan/larastan/extension.neon

parameters:

paths:
- src
- tests

# Level 9 is the highest level
level: 9
50 changes: 35 additions & 15 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Korridor\LaravelHasManySync;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Arr;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
Expand All @@ -17,37 +19,51 @@ class ServiceProvider extends BaseServiceProvider
*/
public function boot(): void
{
HasMany::macro('sync', function (array $data, bool $deleting = true): array {

HasMany::macro('sync', function (array $data, bool $deleting = true, bool $throwOnIdNotInScope = true): array {
$changes = [
'created' => [], 'deleted' => [], 'updated' => [],
];

/** @var HasMany $this */
/** @var HasMany<Model> $this */

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

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

// Cast the given key to an integer if it is numeric.
$castKey = function ($value) {
if (is_null($value)) {
return $value;
return null;
}

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

// Cast the given keys to integers if they are numeric and string otherwise.
$castKeys = function ($keys) use ($castKey) {
$castKeys = function ($keys) use ($castKey): array {
return (array) array_map(function ($key) use ($castKey) {
return $castKey($key);
}, $keys);
};

// The new ids, without null values
$dataIds = Arr::where($castKeys(Arr::pluck($data, $relatedKeyName)), function ($value) {
return !is_null($value);
});

$problemKeys = array_diff($dataIds, $currentIds);
if ($throwOnIdNotInScope && count($problemKeys) > 0) {
throw (new ModelNotFoundException())->setModel(
get_class($this->getRelated()),
$problemKeys
);
}

// Get any non-matching rows.
$deletedKeys = array_diff($current, $castKeys(Arr::pluck($data, $relatedKeyName)));
$deletedKeys = array_diff($currentIds, $dataIds);

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

// Separate the submitted data into "update" and "new"
// We determine "newRows" as those whose $relatedKeyName (usually 'id') is null.
$newRows = Arr::where($data, function ($row) use ($relatedKeyName) {
return null === Arr::get($row, $relatedKeyName);
$newRows = Arr::where($data, function (array $row) use ($relatedKeyName) {
$id = Arr::get($row, $relatedKeyName);
return $id === null;
});

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

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

foreach ($updatedRows as $row) {
$this->getRelated()
foreach ($updateRows as $row) {
$updateModel = $this->getRelated()
->newQuery()
->where($relatedKeyName, $castKey(Arr::get($row, $relatedKeyName)))
->first()
->update($row);
->firstOrFail();

$updateModel->update($row);
}

$changes['updated'] = $castKeys(Arr::pluck($updatedRows, $relatedKeyName));
$changes['updated'] = $castKeys(Arr::pluck($updateRows, $relatedKeyName));

return $changes;
});
Expand Down
Loading

0 comments on commit 2a94e22

Please sign in to comment.