├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── DiffResult.php ├── DiffResultLine.php ├── Differ.php ├── Enums └── DiffResultLineType.php ├── Exceptions └── CannotDiff.php ├── Normalisers └── ObjectNormaliser.php ├── Renderers ├── Renderer.php └── SimpleRenderer.php └── TypeDiffers ├── ArrayDiffer.php ├── ObjectDiffer.php ├── ScalarDiffer.php └── TypeDiffer.php /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `diff` will be documented in this file. 4 | 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) spatie 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compare stuff in PHP 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/diff.svg?style=flat-square)](https://packagist.org/packages/spatie/diff) 4 | [![Tests](https://img.shields.io/github/actions/workflow/status/spatie/diff/run-tests.yml?branch=main&label=tests&style=flat-square)](https://github.com/spatie/diff/actions/workflows/run-tests.yml) 5 | [![Total Downloads](https://img.shields.io/packagist/dt/spatie/diff.svg?style=flat-square)](https://packagist.org/packages/spatie/diff) 6 | 7 | This is where your description should go. Try and limit it to a paragraph or two. Consider adding a small example. 8 | 9 | ## Support us 10 | 11 | [](https://spatie.be/github-ad-click/diff) 12 | 13 | We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us). 14 | 15 | We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards). 16 | 17 | ## Installation 18 | 19 | You can install the package via composer: 20 | 21 | ```bash 22 | composer require spatie/diff 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```php 28 | $skeleton = new Spatie\Diff(); 29 | echo $skeleton->echoPhrase('Hello, Spatie!'); 30 | ``` 31 | 32 | ## Testing 33 | 34 | ```bash 35 | composer test 36 | ``` 37 | 38 | ## Changelog 39 | 40 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 41 | 42 | ## Contributing 43 | 44 | Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details. 45 | 46 | ## Security Vulnerabilities 47 | 48 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 49 | 50 | ## Credits 51 | 52 | - [Freek Van der Herten](https://github.com/freekmurze) 53 | - [All Contributors](../../contributors) 54 | 55 | ## License 56 | 57 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 58 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spatie/diff", 3 | "description": "Compare stuff in PHP", 4 | "keywords": [ 5 | "spatie", 6 | "diff" 7 | ], 8 | "homepage": "https://github.com/spatie/diff", 9 | "license": "MIT", 10 | "authors": [ 11 | { 12 | "name": "Freek Van der Herten", 13 | "email": "freek@spatie.be", 14 | "role": "Developer" 15 | } 16 | ], 17 | "require": { 18 | "php": "^8.1" 19 | }, 20 | "require-dev": { 21 | "pestphp/pest": "^2.15", 22 | "laravel/pint": "^1.0", 23 | "spatie/ray": "^1.28" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Spatie\\Diff\\": "src" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Spatie\\Diff\\Tests\\": "tests" 33 | } 34 | }, 35 | "scripts": { 36 | "test": "vendor/bin/pest", 37 | "test-coverage": "vendor/bin/pest --coverage", 38 | "format": "vendor/bin/pint" 39 | }, 40 | "config": { 41 | "sort-packages": true, 42 | "allow-plugins": { 43 | "pestphp/pest-plugin": true, 44 | "phpstan/extension-installer": true 45 | } 46 | }, 47 | "minimum-stability": "dev", 48 | "prefer-stable": true 49 | } 50 | -------------------------------------------------------------------------------- /src/DiffResult.php: -------------------------------------------------------------------------------- 1 | */ 11 | protected array $lines = []; 12 | 13 | public function __construct( 14 | protected mixed $first, 15 | protected mixed $second) 16 | { 17 | 18 | } 19 | 20 | public function add( 21 | DiffResultLineType $type, 22 | ?string $key, 23 | mixed $value, 24 | mixed $oldValue = null 25 | ): self { 26 | $this->lines[] = new DiffResultLine($type, $key, $value, $oldValue); 27 | 28 | return $this; 29 | } 30 | 31 | /** @return array<\Spatie\Diff\DiffResultLine> */ 32 | public function getLines(): array 33 | { 34 | return $this->lines; 35 | } 36 | 37 | public function render(Renderer $renderer): string 38 | { 39 | return $renderer->render($this); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/DiffResultLine.php: -------------------------------------------------------------------------------- 1 | */ 14 | protected array $differs = [ 15 | ScalarDiffer::class, 16 | ObjectDiffer::class, 17 | Arraydiffer::class, 18 | ]; 19 | 20 | public function diff(mixed $first, mixed $second): DiffResult 21 | { 22 | $this->ensureSameTypes($first, $second); 23 | 24 | $typeDiffer = $this->determineTypeDiffer($first, $second); 25 | 26 | if (! $typeDiffer) { 27 | throw CannotDiff::noDifferFound($first, $second); 28 | } 29 | 30 | return $typeDiffer->diff($first, $second); 31 | } 32 | 33 | protected function determineTypeDiffer(mixed $first, mixed $second): ?TypeDiffer 34 | { 35 | foreach ($this->differs as $differClass) { 36 | $differClass = new $differClass; 37 | 38 | if ($differClass->candiff($first, $second)) { 39 | return $differClass; 40 | } 41 | } 42 | 43 | return null; 44 | } 45 | 46 | protected function ensureSameTypes(mixed $first, mixed $second): void 47 | { 48 | if (is_scalar($first) && is_scalar($second)) { 49 | return; 50 | } 51 | 52 | if (is_array($first) && is_array($second)) { 53 | return; 54 | } 55 | 56 | if (is_object($first) && is_object($second)) { 57 | return; 58 | } 59 | 60 | throw CannotDiff::differentTypes($first, $second); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Enums/DiffResultLineType.php: -------------------------------------------------------------------------------- 1 | key ?? ''; 15 | 16 | $string = "{$this->getSymbol($line->type)}{$key} => {$this->toString($line->value)}"; 17 | 18 | if ($line->oldValue !== null) { 19 | $string .= " (from {$this->toString($line->oldValue)})"; 20 | } 21 | 22 | return $string; 23 | }, $diffResult->getLines()); 24 | 25 | return implode(PHP_EOL, $stringLines); 26 | } 27 | 28 | protected function getSymbol(DiffResultLineType $lineType): string 29 | { 30 | return match ($lineType) { 31 | DiffResultLineType::Added => '+', 32 | DiffResultLineType::Removed => '-', 33 | DiffResultLineType::Changed => 'U', 34 | }; 35 | } 36 | 37 | protected function toString(mixed $anything): string 38 | { 39 | return match (true) { 40 | is_string($anything) => $anything, 41 | is_array($anything) => json_encode($anything), 42 | is_object($anything) => json_encode($anything), 43 | default => (string) $anything, 44 | }; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/TypeDiffers/ArrayDiffer.php: -------------------------------------------------------------------------------- 1 | isIndexed($first) || $this->isIndexed($second) 22 | ? $this->diffIndexedArray($first, $second) 23 | : $this->diffAssociativeArray($first, $second); 24 | } 25 | 26 | public function diffIndexedArray(array $first, array $second): DiffResult 27 | { 28 | $diffResult = new DiffResult($first, $second); 29 | 30 | foreach (array_diff($second, $first) as $value) { 31 | $diffResult->add(DiffResultLineType::Added, null, $value); 32 | } 33 | 34 | foreach (array_diff($first, $second) as $value) { 35 | $diffResult->add(DiffResultLineType::Removed, null, $value); 36 | } 37 | 38 | return $diffResult; 39 | } 40 | 41 | public function diffAssociativeArray(array $first, array $second): DiffResult 42 | { 43 | $diffResult = new DiffResult($first, $second); 44 | 45 | foreach (array_diff_key($second, $first) as $key => $value) { 46 | $diffResult->add(DiffResultLineType::Added, $key, $value); 47 | } 48 | 49 | foreach (array_diff_key($first, $second) as $key => $value) { 50 | $diffResult->add(DiffResultLineType::Removed, $key, $value); 51 | } 52 | 53 | foreach ($first as $key => $value) { 54 | if (array_key_exists($key, $second)) { 55 | if ($second[$key] !== $value) { 56 | $diffResult->add(DiffResultLineType::Changed, $key, $second[$key], $value); 57 | } 58 | } 59 | } 60 | 61 | return $diffResult; 62 | 63 | } 64 | 65 | protected function isIndexed(array $array): bool 66 | { 67 | return array_values($array) === $array; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/TypeDiffers/ObjectDiffer.php: -------------------------------------------------------------------------------- 1 |