From b4ae1ed4c521b9acb07ceed0a62d959b9c4f795c Mon Sep 17 00:00:00 2001 From: Jay Atkins Date: Tue, 24 Dec 2024 12:39:31 +0000 Subject: [PATCH] Feature: Added generic default classes Feature: All traits support \JsonSerializable. IsCollectionType and derivatives support \Iterator. Feature: Added Percentage and Email classes Feature: Several new methods on IsCollectionType (merge, withMerged, pop, popMultiple, split, shuffle, withReversedOrder) Improvement: Psalm annotations for IDE type hints and autocompletion. Use @extends to make use of it (see README examples) Improvement: More specific error message when adding/subtracting values that bring the total above maximum/below minimum allowed value Improvement: InvalidValue renders different types better instead of wrapping everything in double quotes Improvement: Option to get $reasons back from ConversionError after instantiation Dev: Library tested against PHP 8.4 (no changes were necessary) Dev: Upgraded PhpUnit from 9 to 11.5 and Infection from don't-remember to 0.29.0 (latest) Dev: Upgrades improved MSI from 97% to 99%, MCC from 98% to 99% and CC-MCI from 98% to 100%. ---------------------------------- Squashed commit of the following: commit c2119162e1f42a342566de0b42336378e4744ee6 Author: Jay Atkins Date: Tue Dec 24 12:39:03 2024 +0000 Misc: Updated README and CHANGELOG commit a486b470192e3995aa31d60341704edcc528f1fe Author: Jay Atkins Date: Tue Dec 24 00:23:24 2024 +0000 Improvement: Added jsonSerialize to all types, and implemented \JsonSerializable in generic classes. Updated README.md. Added required tests and ran test suite. Re-ran Infection and looked through infection.log. Only false positives left. Updated badges. commit 903a5afba125167bf36d576f2e57ac53da24fd99 Author: Jay Atkins Date: Sun Dec 22 22:10:22 2024 +0000 Tests: Upgraded from Phpunit 10.5 to 11.5 (this was a forced update, as the testdox option was removed in 10.5 but brought back in 11.5)* Tests: Run Infection and fixed any true issues (ignoring false positives of course) Tests: Changed from using @covers, @uses, @dataProvider, @depends to using relevant attributes Running the tests with testdox means that only errors are printed, not every single assertion with a tick or an X. For over 1,500 assertions, it meant that finding errors was incredibly frustrating and time-consuming. In PhpUnit 11.5 it is called testdoxSummary, as opposed to testdox (as it was called in PHPUnit 8), and isn't available at all in PhpUnit 9.5. commit 215d8a07a82d34dd02713debf52db540d4b6e09d Author: Jay Atkins Date: Sun Dec 22 17:38:16 2024 +0000 Feature: Added more methods to IsCollectionType. - `merge` - `withMerged` - `pop` - `popMultiple` - `split` - `shuffle` - `withReversedOrder` Tests: Added missing Unit tests and fixed pre-existing ones. Back to 100% test coverage. Tests: Ran all tests against PHP 8.4. No changes to code required - all tests successful. Misc: Updated CHANGELOG.md TODO: - Update README.md - Run Infection and fix accordingly. commit 07c4da49d0fb348c598f2a881582a8382135c9e7 Author: Jay Atkins Date: Sun Jun 23 13:12:23 2024 +0100 WIP: Added Any* classes and made changes to ConversionError TODO: Re-run all tests --- .env.dist | 5 +- .nvmrc | 1 + CHANGELOG.md | 252 ++++- README.md | 118 +- README_dev.md | 106 +- composer.json | 2 +- composer.lock | 1002 ++++++----------- docker-compose.yml | 21 +- docker/{ => 8.1}/Dockerfile | 0 docker/8.4/Dockerfile | 36 + docs/README.md | 118 +- docs/img/ccm.png | Bin 196 -> 194 bytes docs/img/mcc.png | Bin 196 -> 196 bytes docs/img/msi.png | Bin 193 -> 196 bytes infection.phar | Bin 1021389 -> 1064716 bytes package-lock.json | 26 +- package.json | 5 + phpunit.xml | 26 +- src/Exception/ConversionError.php | 62 +- src/Exception/InvalidValue.php | 52 +- src/Generic/AnyCollection.php | 13 + src/Generic/AnyFloat.php | 12 + src/Generic/AnyInteger.php | 12 + src/Generic/AnyString.php | 12 + src/Generic/Email.php | 12 + src/Generic/Percentage.php | 32 + src/IsArrayEnumType.php | 2 + src/IsClassArrayEnumType.php | 3 + src/IsClassCollectionType.php | 8 +- src/IsCollectionType.php | 281 ++++- src/IsFloatType.php | 65 ++ src/IsIntArrayEnumType.php | 4 +- src/IsIntType.php | 65 ++ src/IsStringArrayEnumType.php | 4 +- src/IsStringType.php | 8 + tests/unit/ArrayEnumType/IntArrayEnumTest.php | 104 +- .../ArrayEnumType/ObjectArrayEnumTest.php | 65 +- .../ArrayEnumType/StringArrayEnumTest.php | 103 +- .../ArrayEnumType/StringVOArrayEnumTest.php | 31 +- .../TransformIntArrayEnumTest.php | 28 +- .../DynamicClassArrayEnumTest.php | 5 +- .../StringClassArrayEnumTest.php | 2 + .../Classes/BasicStringCollectionType.php | 3 + .../Classes/DefaultClassCollectionType.php | 3 + tests/unit/Classes/MaxOnlyFloatType.php | 24 + tests/unit/Classes/MaxOnlyIntType.php | 24 + .../Classes/OddFloatWithThreeDecimalsType.php | 4 +- .../unit/Classes/StringClassArrayEnumType.php | 4 + .../Classes/StringClassCollectionType.php | 4 + tests/unit/Classes/StringCollectionType.php | 5 +- tests/unit/Classes/StringVOArrayEnumType.php | 3 + .../Classes/TransformIntArrayEnumType.php | 3 +- .../unit/CollectionType/AnyCollectionTest.php | 296 +++++ .../BasicStringCollectionTest.php | 80 +- .../CustomConversionClassCollectionTest.php | 5 +- .../DefaultClassCollectionTest.php | 5 +- .../DynamicClassCollectionTest.php | 39 +- .../IgnoreDuplicatesCollectionTest.php | 5 +- .../CollectionType/IntVOCollectionTest.php | 57 +- .../StringClassCollectionTest.php | 148 ++- .../CollectionType/StringCollectionTest.php | 56 +- tests/unit/Exception/ValueRendererTest.php | 9 +- tests/unit/FloatType/AnyFloatTest.php | 18 + tests/unit/FloatType/MaxOnlyFloatTest.php | 38 + tests/unit/FloatType/MinMaxFloatTest.php | 45 +- tests/unit/FloatType/NegativeFloatTest.php | 22 +- .../OddFloatWithThreeDecimalsTest.php | 59 +- tests/unit/FloatType/OperationsTest.php | 59 +- tests/unit/FloatType/PercentageTest.php | 84 ++ tests/unit/FloatType/SimpleFloatTest.php | 111 +- .../unit/Helper/CanConvertToInstanceTest.php | 27 +- tests/unit/Helper/ValueExtractorTest.php | 24 +- tests/unit/IntEnumType/ComparisonTest.php | 21 +- tests/unit/IntEnumType/IntEnumTest.php | 78 +- .../IntStringMapType/IntStringMapTest.php | 157 +-- tests/unit/IntType/AnyIntTest.php | 18 + tests/unit/IntType/MaxOnlyIntTest.php | 38 + tests/unit/IntType/MinMaxIntTest.php | 88 +- tests/unit/IntType/NegativeIntTest.php | 22 +- tests/unit/IntType/OddIntTest.php | 48 +- tests/unit/IntType/OperationsTest.php | 57 +- tests/unit/IntType/SimpleIntTest.php | 79 +- tests/unit/StringEnumType/StringEnumTest.php | 51 +- tests/unit/StringType/AnyStringTest.php | 18 + tests/unit/StringType/CapitalStringTest.php | 26 +- tests/unit/StringType/EmailTest.php | 33 +- tests/unit/StringType/LowerCaseStringTest.php | 26 +- tests/unit/StringType/SimpleStringTest.php | 56 +- tests/unit/StringType/UpperCaseStringTest.php | 26 +- 89 files changed, 2813 insertions(+), 1996 deletions(-) create mode 100644 .nvmrc rename docker/{ => 8.1}/Dockerfile (100%) create mode 100644 docker/8.4/Dockerfile mode change 100644 => 100755 infection.phar create mode 100644 package.json create mode 100644 src/Generic/AnyCollection.php create mode 100644 src/Generic/AnyFloat.php create mode 100644 src/Generic/AnyInteger.php create mode 100644 src/Generic/AnyString.php create mode 100644 src/Generic/Email.php create mode 100644 src/Generic/Percentage.php create mode 100644 tests/unit/Classes/MaxOnlyFloatType.php create mode 100644 tests/unit/Classes/MaxOnlyIntType.php create mode 100644 tests/unit/CollectionType/AnyCollectionTest.php create mode 100644 tests/unit/FloatType/AnyFloatTest.php create mode 100644 tests/unit/FloatType/MaxOnlyFloatTest.php create mode 100644 tests/unit/FloatType/PercentageTest.php create mode 100644 tests/unit/IntType/AnyIntTest.php create mode 100644 tests/unit/IntType/MaxOnlyIntTest.php create mode 100644 tests/unit/StringType/AnyStringTest.php diff --git a/.env.dist b/.env.dist index 93b6eaa..aac19e2 100644 --- a/.env.dist +++ b/.env.dist @@ -1,2 +1,5 @@ LIBRARY_NAME=value-types -USER_ID=1000 \ No newline at end of file +USER_ID=1000 + +# Used by composer +COMPOSER_ROOT_VERSION=2.7 \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..c12134b --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.15.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cf675d..7facdf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,101 +1,224 @@ # v2 -Works with PHP 8.1. +Requires a minimum of PHP 8.1. +Works with PHP 8.4. + + +## v2.7 + +### Breaking changes + +1) `next()` and `previous()` on `IsCollectionType` no longer return a value. + - This is because of `\Iterator` requiring `next` to return `void`, and it makes sense for `next` and `previous` to function in the same way. +2) `ConversionError` now extends from `\ValueError` (previously `\RuntimeException`) + + +### Features + +1) Added default classes to be able to use some of the traits instantly without having to create an empty new class each time. The classes added are: + - `\FireMidge\ValueObject\Generic\AnyCollection` + - `\FireMidge\ValueObject\Generic\AnyFloat` + - `\FireMidge\ValueObject\Generic\AnyInteger` + - `\FireMidge\ValueObject\Generic\AnyString` + - All of the above classes implement `\JsonSerializable`, and `AnyCollection` also implements the `\Iterator` interface. + +2) Added default `Percentage` and `Email` classes + - `\FireMidge\ValueObject\Generic\Percentage` + - `\FireMidge\ValueObject\Generic\Email` + +3) New methods on `IsCollectionType`: + - `merge` + - `withMerged` + - `pop` + - `popMultiple` + - `split` + - `shuffle` + - `withReversedOrder` + +4) `jsonSerialize` has been implemented for all types, to aid easy serialisation. + - In order for automatic serialisation to happen when `json_encode` is called, the class using these traits has to implement the `JsonSerializable` interface. + - The new generic classes all implement it already. + + +### Improvements + +1) Added Psalm annotations in the collection type. + - This allows for better IDE-internal type hints for methods like `toArray()`, `first()`, `last()` etc. + - Usage is showcased in `README.md` +2) More specific error messages for adding or subtracting a value from a float or integer type beyond allowed min/max values. +3) Option to return `$reasons` from a `ConversionError` +4) `InvalidValue` renders different types better, e.g. non-strings are no longer wrapped in double quotes +5) Library is tested against **PHP 8.4**. Worked without changes to the code. +6) Upgraded from PhpUnit 9 to 11.5 and upgraded Infection to 29.0. + - Changed all docblock annotations to attributes + - Infection's output is vastly improved, removing the vast majority of false positives. MSI has changed from 97% to 99%, Mutation Code Coverage from 98% to 99% and Covered Code MSI from 98% to 100%. + ## v2.6 -Feature: Added new type `IsClassArrayEnumType`, which is a convenience type between `ArrayEnumType` and `ClassCollectionType`. -It's used for collections of instances which in turn are enum types, i.e. there is a finite list of valid values. It also offers an easy way to create a new collection holding all valid variations of the underlying enum type (`withAll()`). +### Features -Feature: Added methods to navigate through the values of `IsCollectionType` with `current`, `previous`, `next`, `first` and `last`. This is, by extension, also available to instances using the `IsClassCollectionType`, `IsArrayEnumType`, `IsClassArrayEnumType` and all other related traits. Unlike the equivalent built-in PHP methods, these new methods return `null` for non-existing elements instead of `false`. +1) Added new type `IsClassArrayEnumType`, which is a convenience type between `ArrayEnumType` and `ClassCollectionType`. + - It's used for collections of instances which in turn are enum types, i.e. there is a finite list of valid values. It also offers an easy way to create a new collection holding all valid variations of the underlying enum type (`withAll()`). -Dev: Improved `@covers` annotations and re-imported certain traits to improve accuracy of code coverage reports. Added tests for all new functionality as well as addressed all true positives in the Infection log. MSI improved from 94% to 97% and CCM from 95% to 98%. +2) Added pointer methods to `IsCollectionType`: + - `current` + - `previous` + - `next` + - `first` + - `last` + - This is, by extension, also available to instances using the `IsClassCollectionType`, `IsArrayEnumType`, `IsClassArrayEnumType` and all other related traits. + - Unlike the equivalent built-in PHP methods, these new methods return `null` for non-existing elements instead of `false`. + + +### Improvements +1) Improved `@covers` annotations and re-imported certain traits to improve accuracy of code coverage reports. Added tests for all new functionality as well as addressed all true positives in the Infection log. + - MSI improved from 94% to 97% and CCM from 95% to 98%. ## v2.5 -**B/C-Breaking Change**: `IsClassCollectionType::className()` is now a `static` abstract method. This means all existing classes using this trait will need to change it from `protected` to `protected static`. No further changes should be necessary. -As a static, it can be used for a wider range of useful convenience methods, like the new `fromRawArray`. +### Breaking changes + +1) `IsClassCollectionType::className()` is now a `static` abstract method. + - This means all existing classes using this trait will need to change it from `protected` to `protected static`. No further changes should be necessary. + - As a static, it can be used for a wider range of useful convenience methods, like the new `fromRawArray`. + -Feature: `IsClassCollectionType` now has a `fromRawArray` method, which allows creating a new array not from instances of the required class, but from raw values which are then automatically converted into the required class. A custom conversion callback can be provided. In order to globally (for that particular class) override the conversion of raw values into target class instances, you can override the protected static method `convertFromRaw(mixed $value) : object`. +### Features -Feature: `IsCollectionType` now has `find` and `findIndex` methods, which allows returning a specific element (or index, respectively) based on a custom callback. This means it's no longer needed to convert the class back to an array for the sake of finding a specific element. +1) `IsClassCollectionType` now has a `fromRawArray` method + - This allows creating a new array not from instances of the required class, but from raw values which are then automatically converted into the required class. + - A custom conversion callback can be provided. + - In order to globally (for that particular class) override the conversion of raw values into target class instances, you can override the protected static method `convertFromRaw(mixed $value) : object`. -Feature: `isEqualTo` and `isNotEqualTo` have been added to `IsStringEnumType`, `IsStringType`, `IsIntType`, `IsIntEnumType` and `IsFloatType`, to be consistent with other traits. `ConversionError` has been introduced, which is thrown when attempting to perform a loose comparison with a value that cannot be converted to the target type. +2) `IsCollectionType` now has `find` and `findIndex` methods. + - This allows returning a specific element (or index, respectively) based on a custom callback. + - This means it's no longer needed to convert the class back to an array for the sake of finding a specific element. -Feature: Methods for mathematical operations (`add`, `subtract`) and comparisons (`isGreaterThan`, `isGreaterThanOrEqualTo`, `isLessThan`, `isLessThanOrEqualTo` have been added to `IsFloatType` and `IsIntType`. +3) `isEqualTo` and `isNotEqualTo` have been added to the following: + - `IsStringEnumType` + - `IsStringType` + - `IsIntType` + - `IsIntEnumType` + - `IsFloatType` + - This was done to be consistent with other traits. + - `ConversionError` has been introduced, which is thrown when attempting to perform a loose comparison with a value that cannot be converted to the target type. -Feature: `IsFloatType` now has `fromString`, `fromStringOrNull`, `fromNumber`, `fromNumberOrNull`. +4) The following methods for mathematical operations and comparisons have been added to `IsFloatType` and `IsIntType`: + - `add` + - `subtract` + - `isGreaterThan` + - `isGreaterThanOrEqualTo` + - `isLessThan` + - `isLessThanOrEqualTo` -Feature: `IsIntEnumType` now has a `fromString`, `fromStringOrNull` +5) `IsFloatType` now has: + - `fromString` + - `fromStringOrNull` + - `fromNumber` + - `fromNumberOrNull` -Feature: `isIntType` now has `fromStringOrNull` (besides the pre-existing `fromString`) in order to make it consistent with other traits. +6) `IsIntEnumType` now has: + - `fromString` + - `fromStringOrNull` -Feature: `IsIntEnumType` now implements the magic `__toString` method, which aids with comparisons and rendering. +7) `isIntType` now has `fromStringOrNull` (besides the pre-existing `fromString`) in order to make it consistent with other traits. -Dev: Tests to cover all of the above have been added. Some additional tests to improve pre-existing ones have been added. +8) `IsIntEnumType` now implements the magic `__toString` method, which aids with comparisons and rendering. -Dev: Every single mutant in the `infection.log` has been checked - they are all false positives. Several came back as `escaped` when in fact, the same manual mutation causes tests to fail. The @covers annotation is correct. It may be a bug in how multi-layer trait inheritance is perceived as covered by Infection. MSI has fallen from 95% to 94% and Covered Code MSI has fallen from 97% to 95% - however, there is nothing that can be done to improve it. + +### Improvements + +1) Tests to cover all of the above have been added. Some additional tests to improve pre-existing ones have been added. + +2) Every single mutant in the `infection.log` has been checked - they are all false positives. + - Several came back as `escaped` when in fact, the same manual mutation causes tests to fail. + - The @covers annotation is correct. It may be a bug in how multi-layer trait inheritance is perceived as covered by Infection. + - MSI has fallen from 95% to 94% and Covered Code MSI has fallen from 97% to 95% - however, there is nothing that can be done to improve it. ## v2.4 -Change: `isEqualTo` and `isNotEqualTo` on `IsIntStringMapType` default to using a strict check, which means the item to compare it to must be of the same class. If you do not want a strict check to happen, you can continue to pass `false` for the `$strictCheck` parameter. +### Breaking changes + +1) `isEqualTo` and `isNotEqualTo` on `IsIntStringMapType` default to using a **strict check**, which means the item to compare it to must be of the same class. If you do not want a strict check to happen, you can continue to pass `false` for the `$strictCheck` parameter. -Change: `isEqualTo` and `isNotEqualTo` on `IsCollectionType` (which affects all array types) have a new `$strictCheck` parameter, defaulting to `true`. When it is true, the item to compare to must be of the same class. If you do not want a strict check to happen, you can pass `false` for the `$strictCheck` parameter. +2) `isEqualTo` and `isNotEqualTo` on `IsCollectionType` (which affects all array types) have a new `$strictCheck` parameter, defaulting to `true`. When it is true, the item to compare to must be of the same class. If you do not want a strict check to happen, you can pass `false` for the `$strictCheck` parameter. ## v2.3 -Feature: Added `isEqualTo` and `isNotEqualTo` to `IsIntStringMapType`. -Overall Mutation Code Coverage has risen from 97% to 98%. No value has decreased. +### Features + +1) Added `isEqualTo` and `isNotEqualTo` to `IsIntStringMapType`. + - Overall Mutation Code Coverage has risen from 97% to 98%. No value has decreased. + +2) Added `withValues`, `withoutValues` and `tryWithoutValues` to `IsCollectionType`, allowing to add/remove multiple values with a single method call. + - MSI has risen from 94% to 95% and Covered Code MSI from 96% to 97%. + + +## v2.2 -Feature: Added `withValues`, `withoutValues` and `tryWithoutValues` to `IsCollectionType`, allowing to add/remove multiple values with a single method call. MSI has risen from 94% to 95% and Covered Code MSI from 96% to 97%. +### Features +1) It is now possible to also transform values in `IsStringEnumType` before validating. -### v2.2 +2) There is a new `fromString` method on `IsIntType`. -Feature: It is now possible to also transform values in `IsStringEnumType` before validating. -Feature: There is a new `fromString` method on `IsIntType`. +### Improvements -Dev: Instead of using method-based @covers annotations, we now have class-based @covers annotations, which is working SO much better. Now the Infection log and coverage reports are far more accurate. +1) Instead of using method-based @covers annotations, we now have class-based @covers annotations, which is working SO much better. Now the Infection log and coverage reports are far more accurate. -Dev: Added more tests, to bring unit test coverage up to 100%. +2) Added more tests, to bring unit test coverage up to 100%. -Dev: Throwing LogicException when trying to validate the length of a string but passing a higher $minNumber than $maxNumber. +3) Throwing `LogicException` when trying to validate the length of a string but passing a higher `$minNumber` than `$maxNumber`. -### v2.1 +## v2.1 -Feature: Added the following methods to all the array types (`IsArrayEnumType`, `IsCollectionType`, `IsIntArrayEnumType`, `IsStringArrayEnumType`, `IsClassCollectionType`): -- `count`: Returns the number of elements inside the array value object. -- `isEmpty`: Whether the value object contains any elements. -- `isNotEmpty`: The opposite of `isEmpty`. -- `isEqualTo`: Whether the value object's elements are equal to the argument. The argument can be another class (with a `toArray` method), an array, or an object with public properties. -- `isNotEqualTo`: The opposite of `isEqualTo`. -- `empty`: A factory method to create a new instance with no elements. +### Features -Feature: Added a new value type: `IsClassCollectionType`. -This adds on to `IsCollectionType` and makes it easier to create a collection holding an array of objects. It validates whether the items are of a specific class. By default, it does not allow duplicate values, however this can be overridden. +1) Added the following methods to all the array types (`IsArrayEnumType`, `IsCollectionType`, `IsIntArrayEnumType`, `IsStringArrayEnumType`, `IsClassCollectionType`): + - `count`: Returns the number of elements inside the array value object. + - `isEmpty`: Whether the value object contains any elements. + - `isNotEmpty`: The opposite of `isEmpty`. + - `isEqualTo`: Whether the value object's elements are equal to the argument. The argument can be another class (with a `toArray` method), an array, or an object with public properties. + - `isNotEqualTo`: The opposite of `isEqualTo`. + - `empty`: A factory method to create a new instance with no elements. -Feature: Added a small helper trait `CanBeConvertedToStringArray`, which can convert an array type into an array of scalar string values. Useful when using `IsClassCollectionType` with a class implementing the `__toString` method. +2) Added a new value type: `IsClassCollectionType`. + - This adds on to `IsCollectionType` and makes it easier to create a collection holding an array of objects. It validates whether the items are of a specific class. + - By default, it does not allow duplicate values, however this can be overridden. -Feature: Adding duplicate values can now be ignored, by overriding the `protected static function ignoreDuplicateValues() : bool` method and returning `true`. To be backwards-compatible, it returns `false` by default. -When this method returns true, then any duplicate values will be ignored and simply not added to the collection; without throwing an exception. +3) Added a small helper trait `CanBeConvertedToStringArray` + - It can convert an array type into an array of scalar string values. + - Useful when using `IsClassCollectionType` with a class implementing the `__toString` method. -Dev: Separate exception for duplicate values: `DuplicateValue`. -It is backwards compatible as it extends from `InvalidValue`, but it does now allow developers to catch this exception separately. +4) Adding duplicate values can now be ignored + - This is achieved by overriding the `protected static function ignoreDuplicateValues() : bool` method and returning `true`. + - To be backwards-compatible, it returns `false` by default. + - When this method returns true, any duplicate values will be ignored and simply not added to the collection; without throwing an exception. + +### Improvements + +1) Separate exception for duplicate values: `DuplicateValue`. + - It is backwards compatible as it extends from `InvalidValue`, but it does now allow developers to catch this exception separately. ### v2.0.1 -Fix: `IsIntEnumType` was the only type left not using a static `all` method. This has been fixed now. +#### Fixes + +1) `IsIntEnumType` was the only type left not using a static `all` method. This has been fixed now. + +## v2.0 -### v2.0 +### Improvements -Dev: Upgrade to PHP 8.1. Note that anything below PHP 8.1 is no longer supported. +1) Upgrade to PHP 8.1. Note that anything below PHP 8.1 is no longer supported. # v1 @@ -104,24 +227,31 @@ Works with PHP 7.3 and above. Tested with PHP 8.1. -### v1.1 +## v1.1 + +### Features + +1) Added the following types: + - `IsArrayEnumType` + - `IsFloatType` + - `IsIntArrayEnumType` + - `IsIntStringMapType` + - `IsIntType` + - `IsStringArrayEnumType` + - `IsStringType` + - `IsEmailType` + - `IsCollectionType` + +2) Added `fromNull` methods to `IsStringEnumType` and `IsIntEnumType`. + -Feature: Added the following types: -- `IsArrayEnumType` -- `IsFloatType` -- `IsIntArrayEnumType` -- `IsIntStringMapType` -- `IsIntType` -- `IsStringArrayEnumType` -- `IsStringType` -- `IsEmailType` -- `IsCollectionType` +### Improvements -Feature: Added `fromNull` methods to `IsStringEnumType` and `IsIntEnumType`. +1) Added unit and mutation tests which can be run with Docker. -Dev: Added unit and mutation tests which can be run with Docker. +## v1.0 -### v1.0 +### Features -Feature: Basic `IsIntEnumType` and `IsStringEnumType` \ No newline at end of file +1) Basic `IsIntEnumType` and `IsStringEnumType` \ No newline at end of file diff --git a/README.md b/README.md index 96e43c2..df25481 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,44 @@ # value-objects -This library provides convenience methods for creating value objects. + +**Tested with PHP 8.4. Requires PHP ^8.1.** + +This library provides convenience methods for creating value objects in the form of traits, as well as some generic classes implementing said traits to use as-is where no configuration is needed. You may use the below table to decide which type is best for you. -*"Single Value" means the object will hold a single value, whereas "Array of Values" means the object can hold more than one value.* +*"Single Value" means the object will hold a single value, whereas "Array of Values" means the object can hold more than one value.* + +**You can click on the relevant type to jump straight to their documentation.** | | Single Value | Array of Values | |:----------------------------|:------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | List of Valid Values | [`IsStringEnumType`](#isstringenumtype)
[`IsIntEnumType`](#isintenumtype)
[`IsIntStringMapType`](#isintstringmaptype) | [`IsStringArrayEnumType`](#isstringarrayenumtype)
[`IsIntArrayEnumType`](#isintarrayenumtype)
[`IsClassArrayEnumType`](#isclassarrayenumtype)
[`IsArrayEnumType`](#isarrayenumtype) | | Any Value/Custom Validation | [`IsEmailType`](#isemailtype)
[`IsStringType`](#isstringtype)
[`IsFloatType`](#isfloattype)
[`IsIntType`](#isinttype) | [`IsClassCollectionType`](#isclasscollectiontype)
[`IsCollectionType`](#iscollectiontype) | +### Generic classes + +They only exist for convenience, already implementing a type trait with no or minimal configuration. +All classes are extendable if needed, or you can implement the relevant trait directly. + +| Name | Implemented trait | Notes | +|:----------------|:--------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `AnyCollection` | `IsCollectionTrait` | Used when you just want to access an array using normalised OOP methods rather than PHP-native global functions, while not caring about the type of elements. | +| `AnyFloat` | `IsFloatType` | Used when you do not want to customise min/max value or other validation rules and the value is a float. | +| `AnyInteger` | `IsIntType` | Used when you do not want to customise min/max value or other validation rules and the value is an integer. | +| `AnyString` | `IsStringType` | Used when there are no rules about the format of the string and there is no list of valid values either. | +| `Email` | `IsEmailType` | Used when dealing with an email address without custom validation rules/custom formatting. | +| `Percentage` | `IsFloatType` | Used when expecting a value between 0 and 100, which can be represented as a string (with a % symbol), an integer, or a float (with a customisable number of decimal places. Passing a value less than 0 or greater than 100 results in an exception (rather than being clipped silently. The class can be extended to configure further. | + + ## Quality Control The following table is updated with each code update and is generated with the help of PhpUnit (unit testing tool) and Infection (mutation testing tool): -|                                            | Percentage | Description | -|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Code Coverage | ![100%](docs/img/cc.png) | How many methods have been fully covered by tests. | -| Mutation Score Indicator | ![97%](docs/img/msi.png) | Indicates how many generated mutants were detected. *Note that some mutants are false positives.* | -| Mutation Code Coverage | ![98%](docs/img/mcc.png) | Should be in the same ballpark as the normal code coverage. Formula: `(TotalMutantsCount - NotCoveredByTestsCount) / TotalMutantsCount` | -| Covered Code MSI | ![98%](docs/img/ccm.png) | This is the MSI (Mutation Score Indicator) for code that is actually covered by tests. It shows how effective the tests really are. Formula: `TotalDefeatedMutants / (TotalMutantsCount - NotCoveredByTestsCount)`. *Note that for some reason, Infection may report some mutants not being covered by tests when they actually are.* | +|                                            | Percentage | Description | +|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Code Coverage | ![100%](docs/img/cc.png) | How many methods have been fully covered by tests. | +| Mutation Score Indicator | ![99%](docs/img/msi.png) | Indicates how many generated mutants were detected. *Note that some mutants are false positives.* | +| Mutation Code Coverage | ![99%](docs/img/mcc.png) | Should be in the same ballpark as the normal code coverage. Formula: `(TotalMutantsCount - NotCoveredByTestsCount) / TotalMutantsCount` | +| Covered Code MSI | ![100%](docs/img/ccm.png) | This is the MSI (Mutation Score Indicator) for code that is actually covered by tests. It shows how effective the tests really are. Formula: `TotalDefeatedMutants / (TotalMutantsCount - NotCoveredByTestsCount)`.| ## IsStringEnumType @@ -27,6 +47,8 @@ Use this type when there is a set of fixed valid values, and your object represe *If there is a set of fixed valid values but your object represents an array of values, use [`IsStringArrayEnumType`](#isstringarrayenumtype).* +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyString`.** + Example: ```php class Season @@ -55,6 +77,7 @@ Usage: $spring = Season::fromString(Season::SPRING); ``` + ## IsIntEnumType Use this type when there is a set of fixed valid values, and your object represents a single value. @@ -96,6 +119,8 @@ $success = Status::fromInt(Status::SUCCESS); Use this type when the value represents a single e-mail address. This trait uses [`IsStringType`](#isstringtype) under the hood but performs standard e-mail validation. +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\Email`.** + Example: ```php @@ -110,6 +135,7 @@ Usage: $email = Email::fromString('hello@there.co.uk'); ``` + ## IsStringType Use this type when the value represents a single string value, but there is no fixed set of valid values. @@ -162,6 +188,8 @@ $productName = ProductName::fromString(' orange juice'); Use this type when the value represents a single integer value, but there is no fixed list of valid values, or it is not feasible to write up each valid value. +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyInteger`.** + ### Validation @@ -187,6 +215,7 @@ class Percentage } } ``` +**Note that there is a convenient `Percentage` class already available: `FireMidge\ValueObject\Generic\Percentage`.** Another example, for a value without any limitations: ```php @@ -244,6 +273,8 @@ $percentage = Percentage::fromInt(78); Use this type when the value represents a single float value. +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyFloat`.** + ### Validation @@ -695,11 +726,12 @@ When both `areValuesUnique` and `ignoreDuplicateValues` return `true`, `ignoreDu You can provide custom validation by overriding `protected function validateEach(mixed $value) : void`, which is executed for each value separately, both when instantiating it and when calling `withValue`. Note that this validation will also run before `withoutValue`, `tryWithoutValue` and `contains`, so you are notified when passing something entirely invalid rather than it being silently swallowed. Example: + ```php +use FireMidge\ValueObject\IsCollectionType; + /** - * @method static withValue(Status $addedValue) - * @method static tryWithoutValue(Status $value) - * @method static contains(Status $value) + * @extends IsCollectionType */ class StatusList { @@ -744,13 +776,53 @@ $duplicateStatusesIgnored = StatusList::fromArray([ Status::SUCCESS, Status::REDIRECTION, Status::SUCCESS, -]) +]); // $newStatuses will only contain one instance of Status::REDIRECTION. // This is because of `ignoreDuplicateValues` returning true. $newStatuses = $statuses->withValue(Status::REDIRECTION); ``` +You also have a variety of other array methods available, e.g.: + +```php +use FireMidge\ValueObject\Generic\AnyCollection;$statuses = StatusList::fromArray([ + Status::SUCCESS, + Status::REDIRECTION, +]); +$errorStatuses = StatusList::fromArray([ + Status::SERVER_ERROR, + Status::CLIENT_ERROR, +]); + +// $newStatuses contains statuses from both $statuses and $errorStatuses, +// without modifying the merged classes. +$newStatuses = $statuses->withMerged($errorStatuses); + +// This does modify $statuses, and cause it to append the values from +// $errorStatuses to its own. +$statuses->merge($errorStatuses); + +// split() causes the values of one collection to be split into +// 2 collections. This modifies the original instance. +// In this case, $alsoErrorStatuses contains Status::SERVER_ERROR +// and Status::CLIENT_ERROR, while $statuses only keeps +// the first 2 elements, i.e. STATUS::SUCCESS and STATUS::REDIRECTION. +$alsoErrorStatuses = $statuses->split(2); + +// pop() removes the last element and returns it. +// This modifies the original instance ($statuses). +$redirection = $statuses->pop(); + +// You can also pop multiple values at once, but note +// that the returned order will be reversed, as each element +// is popped individually and together, they are returned as a new +// collection instance. +$values = AnyCollection::fromArray('a', 'b', 'c', 'd', 'e'); +$last2 = $values->popMultiple(2); +echo json_encode($last2); // ["e","d"] +``` + ## IsClassCollectionType @@ -775,10 +847,10 @@ You can provide custom validation by overriding `protected function validateEach Example: ```php +use FireMidge\ValueObject\IsCollectionType; + /** - * @method static withValue(Email $addedValue) - * @method static tryWithoutValue(Email $value) - * @method static contains(Email $value) + * @extends IsCollectionType */ class EmailCollection { @@ -899,6 +971,8 @@ $emailsMatch = $emails->isEqualTo([ Use this type when the value represents an array of values and there is **no** finite list of valid values. If there is a list of valid values, use [`IsArrayEnumType`](#isarrayenumtype) (or any of the more specific variations, e.g. [`IsStringArrayEnumType`](#isstringarrayenumtype) if applicable). +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyCollection`.** + ### Combination with other types You can combine this type with any other type, e.g. to get an array of float types, an array of e-mail addresses, etc. @@ -923,7 +997,7 @@ You can provide custom validation by overriding `protected function validateEach ### Value transformation -If you want to transform the input value but not fail validation, override `protected function transformEach($value)`. +If you want to transform the input value but not fail validation, override `protected function transformEach(mixed $value)`. By also using the trait `CanTransformStrings`, you'll get 3 convenience methods that you can call inside `transform` if you want: - `trimAndLowerCase(string $value)` @@ -933,10 +1007,10 @@ By also using the trait `CanTransformStrings`, you'll get 3 convenience methods Example: ```php +use FireMidge\ValueObject\IsCollectionType; + /** - * @method static withValue(string $addedValue) - * @method static tryWithoutValue(string $value) - * @method static contains(string $value) + * @extends IsCollectionType */ class ProductNameCollection { @@ -950,11 +1024,7 @@ class ProductNameCollection } } - /** - * @param mixed $value - * @return mixed - */ - protected function transformEach($value) + protected function transformEach(mixed $value) : mixed { if (! is_string($value)) { return $value; diff --git a/README_dev.md b/README_dev.md index e8e1564..1e2b44c 100755 --- a/README_dev.md +++ b/README_dev.md @@ -1,5 +1,26 @@ # Development +## Table of Contents + +- [Tests](#tests) + - [Set-up](#set-up) + - [Run tests](#run-tests) + - [Multiple PHP versions](#multiple-php-versions) + - [Commands to run tests](#commands-to-run-tests) + - [Run a specific test](#run-a-specific-test) + - [Class/Method](#method-or-class) + - [Named data set](#named-data-set) + - [Unnamed data set](#unnamed-data-set) + - [Adding new tests](#adding-new-tests) +- [Dependencies](#dependencies) +- [Commits](#commits) + - [Validate composer.json](#validate-composerjson) + - [Update CHANGELOG.md](#update-changelogmd) + - [Regenerate badges with new code coverage scores](#regenerate-badges-with-new-code-coverage-scores) + - [Regenerate the README.md file](#regenerate-the-readmemd-file) + - [Tag your commit](#tag-your-commit) + + ## Tests @@ -8,55 +29,114 @@ In order to be able to run tests, go through the following steps to set up the D #### Download and build the Docker image -`docker-compose build --build-arg UID=`id -u` lib` +`docker-compose build --build-arg UID=`id -u` lib84 lib81` + +or: (make sure that `USER_ID` in the `.env` file is correct) + +`docker compose up --build` #### Install dependencies -`docker-compose run lib composer install` +`docker-compose run lib84 composer install` ### Run tests +#### Multiple PHP versions + +Since version `2.7`, we have more than one Dockerfile, to run the tests against more than one PHP version. + +There is only one `vendor` folder as we're not changing the minimum required PHP version dependency, so you only need to install them on the `lib81` service, and they'll automatically be available to `lib84` too. + +In order to run the PHP 8.1 container, you'd use: +`docker compose run --rm lib81 vendor/bin/phpunit` + +In order to run the PHP 8.4 container, you'd use: +`docker compose run --rm lib84 vendor/bin/phpunit` + +*Wherever you see `run --rm lib84` in this file, update the last 2 numbers to run the relevant PHP version (if available), e.g. `run --rm lib81`. (The `--rm` flag makes sure that the container instance is removed once the command has finished executing, avoiding orphans).* + + +#### Commands to run tests + To run mutation tests: -`docker-compose run lib php infection.phar` +`docker compose run --rm lib84 php infection.phar` To run the unit tests: -`docker-compose run lib vendor/bin/phpunit` +`docker compose run --rm lib84 vendor/bin/phpunit` To create a code coverage report in HTML style: -`docker-compose run lib vendor/bin/phpunit --coverage-html ./coverage-report` +`docker compose run --rm lib84 vendor/bin/phpunit --coverage-html ./coverage-report` To create a quick code coverage overview in the CLI: -`docker-compose run lib vendor/bin/phpunit --coverage-text` +`docker compose run --rm lib84 vendor/bin/phpunit --coverage-text` #### Run a specific test +##### Method or Class + To run a specific class, or method, run: -`docker-compose run lib vendor/bin/phpunit --filter testWithValueDoesNotChangePreExisting` +`docker compose run --rm lib84 vendor/bin/phpunit --filter testWithValueDoesNotChangePreExisting` where `testWithValueDoesNotChangePreExisting` is the name of the method. You can also use the name of a class instead. + +##### Named data set + To run a specific data set (when using data providers), you can use the name of the data set after the `@`, e.g.: -` docker-compose run lib vendor/bin/phpunit --filter testWithValueDoesNotChangePreExisting@invalidNumber` +` docker compose run --rm lib84 vendor/bin/phpunit --filter testWithValueDoesNotChangePreExisting@invalidNumber` where `testWithValueDoesNotChangePreExisting` is the name of the method, and `invalidNumber` is the name of the data set. Note that this only works with non-numeric data set names. +##### Unnamed data set + +If you have unnamed data sets, you can still run an individual using the following syntax: + +`--filter methodName#dataSetNumber` + +E.g.: +`docker compose run --rm lib84 vendor/bin/phpunit testFromStringWithValidValue#3` + +Data set numbers start from 0. +You can also use ranges: + +`docker compose run --rm lib84 vendor/bin/phpunit testFromStringWithValidValue#3-5` + +The above will run any test methods that are named "testFromStringWithValidValue", but only with the 4th, 5th and 6th data set. + +For more information, see the PhpUnit documentation. + + #### Adding new tests Remember that each test method name needs to start with "test", otherwise it will be ignored by PhpUnit. +#### Removing orphaned containers + +If you see a lot of container names when running tests and the error message `If you removed or renamed this service in your compose file, you can run this command with the --remove-orphans flag to clean it up.`, you'll probably trust the message and run the same command again but with the `--remove-orphans` flag. +This then results in you seeing `Unknown option "--remove-orphans"`, and thinking "...Wtf?" + +Despite the message telling you otherwise, the `--remove-orphans` flag is only available on the `docker compose up` and `docker compose down` command, so just run the following to clear them: + +```bash +docker compose up --remove-orphans +``` + +In order to avoid orphans in the first place, run all commands with `--rm`, which removes the container automatically once it's finished executing. + + ## Dependencies ### Adding new dev dependencies Use the --dev option when requiring new dev dependencies via composer: -`docker-compose run lib composer require --dev phpunit/phpunit ^9` +`docker compose run --rm lib84 composer require --dev phpunit/phpunit ^9` ## Commits @@ -76,7 +156,7 @@ Any slightly more "complex" tasks have been detailed below. ### Validate composer.json -Run `docker-compose run lib composer validate` to make sure `composer.json` is still valid. +Run `docker compose run --rm lib84 composer validate` to make sure `composer.json` is still valid. ### Update CHANGELOG.md @@ -97,15 +177,15 @@ If there are versions missing in CHANGELOG, add them. These commands should help ### Regenerate badges with new code coverage scores Run this command (but don't forget substituting values with the current values): -`docker-compose run lib php docs/generateBadges.php --cc=0 --msi=0 --mcc=0 --ccm=0` +`docker compose run --rm lib84 php docs/generateBadges.php --cc=0 --msi=0 --mcc=0 --ccm=0` *Round all percentages to 0 decimals. Round down until .49, round up from .5.* #### cc -`cc` is the Code Coverage percentage of covered methods. You get this value by running `docker-compose run lib vendor/bin/phpunit --coverage-text` and taking the "Methods" percentage from the summary section. +`cc` is the Code Coverage percentage of covered methods. You get this value by running `docker compose run --rm lib84vendor/bin/phpunit --coverage-text` and taking the "Methods" percentage from the summary section. #### msi, mcc, ccm -All of these values are taken from the Infection summary. Run `docker-compose run lib php infection.phar`, which gives you a "Metrics" section, from which you take the following percentages: +All of these values are taken from the Infection summary. Run `docker compose run --rm lib84php infection.phar`, which gives you a "Metrics" section, from which you take the following percentages: msi: Mutation Score Indicator diff --git a/composer.json b/composer.json index 93fddfe..7180170 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,6 @@ "minimum-stability": "stable", "require-dev": { "ext-gd": "*", - "phpunit/phpunit": "^9" + "phpunit/phpunit": "^11.5" } } diff --git a/composer.lock b/composer.lock index 4e6a104..3cb0a62 100644 --- a/composer.lock +++ b/composer.lock @@ -4,91 +4,21 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "9670a1401bede4856c869fe606ec17d7", + "content-hash": "7801b23d7e836f9e8064557f363e0d5d", "packages": [], "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", - "shasum": "" - }, - "require": { - "php": "^7.1 || ^8.0" - }, - "require-dev": { - "doctrine/coding-standard": "^9", - "ext-pdo": "*", - "ext-phar": "*", - "phpbench/phpbench": "^0.16 || ^1", - "phpstan/phpstan": "^1.4", - "phpstan/phpstan-phpunit": "^1", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "https://ocramius.github.io/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://www.doctrine-project.org/projects/instantiator.html", - "keywords": [ - "constructor", - "instantiate" - ], - "support": { - "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/1.4.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Finstantiator", - "type": "tidelift" - } - ], - "time": "2022-03-03T08:28:38+00:00" - }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -96,11 +26,12 @@ }, "conflict": { "doctrine/collections": "<1.6.8", - "doctrine/common": "<2.13.3 || >=3,<3.2.2" + "doctrine/common": "<2.13.3 || >=3 <3.2.2" }, "require-dev": { "doctrine/collections": "^1.6.8", "doctrine/common": "^2.13.3 || ^3.2.2", + "phpspec/prophecy": "^1.10", "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13" }, "type": "library", @@ -126,7 +57,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -134,29 +65,31 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nikic/php-parser", - "version": "v4.14.0", + "version": "v5.3.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1" + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/34bea19b6e03d8153165d8f30bba4c3be86184c1", - "reference": "34bea19b6e03d8153165d8f30bba4c3be86184c1", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/8eea230464783aa9671db8eea6f8c6ac5285794b", + "reference": "8eea230464783aa9671db8eea6f8c6ac5285794b", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^9.0" }, "bin": [ "bin/php-parse" @@ -164,7 +97,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -188,26 +121,27 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.14.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.3.1" }, - "time": "2022-05-31T20:59:12+00:00" + "time": "2024-10-08T18:51:32+00:00" }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -248,9 +182,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -303,273 +243,46 @@ }, "time": "2022-02-21T01:04:05+00:00" }, - { - "name": "phpdocumentor/reflection-common", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-2.x": "2.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", - "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" - }, - "time": "2020-06-27T09:03:43+00:00" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.3.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170", - "reference": "622548b623e81ca6d78b721c5e029f4ce664f170", - "shasum": "" - }, - "require": { - "ext-filter": "*", - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.3", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.2", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "account@ijaap.nl" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "support": { - "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" - }, - "time": "2021-10-19T17:43:47+00:00" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "77a32518733312af16a44300404e945338981de3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3", - "reference": "77a32518733312af16a44300404e945338981de3", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0", - "phpdocumentor/reflection-common": "^2.0" - }, - "require-dev": { - "ext-tokenizer": "*", - "psalm/phar": "^4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-1.x": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", - "support": { - "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" - }, - "time": "2022-03-15T21:29:03+00:00" - }, - { - "name": "phpspec/prophecy", - "version": "v1.15.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.2", - "php": "^7.2 || ~8.0, <8.2", - "phpdocumentor/reflection-docblock": "^5.2", - "sebastian/comparator": "^3.0 || ^4.0", - "sebastian/recursion-context": "^3.0 || ^4.0" - }, - "require-dev": { - "phpspec/phpspec": "^6.0 || ^7.0", - "phpunit/phpunit": "^8.0 || ^9.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Prophecy\\": "src/Prophecy" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "support": { - "issues": "https://github.com/phpspec/prophecy/issues", - "source": "https://github.com/phpspec/prophecy/tree/v1.15.0" - }, - "time": "2021-12-08T12:19:24+00:00" - }, { "name": "phpunit/php-code-coverage", - "version": "9.2.15", + "version": "11.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f" + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f", - "reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.13.0", - "php": ">=7.3", - "phpunit/php-file-iterator": "^3.0.3", - "phpunit/php-text-template": "^2.0.2", - "sebastian/code-unit-reverse-lookup": "^2.0.2", - "sebastian/complexity": "^2.0", - "sebastian/environment": "^5.1.2", - "sebastian/lines-of-code": "^1.0.3", - "sebastian/version": "^3.0.1", - "theseer/tokenizer": "^1.2.0" + "nikic/php-parser": "^5.3.1", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", + "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.5.0" }, "suggest": { - "ext-pcov": "*", - "ext-xdebug": "*" + "ext-pcov": "PHP extension that provides line coverage", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "9.2-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -597,7 +310,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15" + "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -605,32 +319,32 @@ "type": "github" } ], - "time": "2022-03-07T09:28:20+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "3.0.6", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf" + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", - "reference": "cf1c2e7c203ac650e352f4cc675a7021e7d1b3cf", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -657,7 +371,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/3.0.6" + "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" }, "funding": [ { @@ -665,28 +380,28 @@ "type": "github" } ], - "time": "2021-12-02T12:48:52+00:00" + "time": "2024-08-27T05:02:59+00:00" }, { "name": "phpunit/php-invoker", - "version": "3.1.1", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/5a10147d0aaf65b58940a0b72f71c9ac0423cc67", - "reference": "5a10147d0aaf65b58940a0b72f71c9ac0423cc67", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -694,7 +409,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -720,7 +435,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/3.1.1" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -728,32 +444,32 @@ "type": "github" } ], - "time": "2020-09-28T05:58:55+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", - "reference": "5da5f67fc95621df9ff4c4e5a84d6a8a2acf7c28", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -779,7 +495,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -787,32 +504,32 @@ "type": "github" } ], - "time": "2020-10-26T05:33:50+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "5.0.3", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", - "reference": "5a63ce20ed1b5bf577850e2c4e87f4aa902afbd2", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -838,7 +555,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/5.0.3" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -846,58 +564,52 @@ "type": "github" } ], - "time": "2020-10-26T13:16:10+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "9.5.21", + "version": "11.5.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1" + "reference": "153d0531b9f7e883c5053160cad6dd5ac28140b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1", - "reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/153d0531b9f7e883c5053160cad6dd5ac28140b3", + "reference": "153d0531b9f7e883c5053160cad6dd5ac28140b3", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=7.3", - "phpspec/prophecy": "^1.12.1", - "phpunit/php-code-coverage": "^9.2.13", - "phpunit/php-file-iterator": "^3.0.5", - "phpunit/php-invoker": "^3.1.1", - "phpunit/php-text-template": "^2.0.3", - "phpunit/php-timer": "^5.0.2", - "sebastian/cli-parser": "^1.0.1", - "sebastian/code-unit": "^1.0.6", - "sebastian/comparator": "^4.0.5", - "sebastian/diff": "^4.0.3", - "sebastian/environment": "^5.1.3", - "sebastian/exporter": "^4.0.3", - "sebastian/global-state": "^5.0.1", - "sebastian/object-enumerator": "^4.0.3", - "sebastian/resource-operations": "^3.0.3", - "sebastian/type": "^3.0", - "sebastian/version": "^3.0.2" - }, - "require-dev": { - "phpspec/prophecy-phpunit": "^2.0.1" + "myclabs/deep-copy": "^1.12.1", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.8", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.2", + "sebastian/comparator": "^6.2.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.3.0", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.0", + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files" }, "bin": [ "phpunit" @@ -905,7 +617,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -936,7 +648,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.2" }, "funding": [ { @@ -946,34 +659,38 @@ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit", + "type": "tidelift" } ], - "time": "2022-06-19T12:14:25+00:00" + "time": "2024-12-21T05:51:08+00:00" }, { "name": "sebastian/cli-parser", - "version": "1.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2", - "reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -996,7 +713,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -1004,32 +722,32 @@ "type": "github" } ], - "time": "2020-09-28T06:08:49+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "1.0.8", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120" + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/1fc9f64c0927627ef78ba436c9b17d967e68e120", - "reference": "1fc9f64c0927627ef78ba436c9b17d967e68e120", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", + "reference": "ee88b0cdbe74cf8dd3b54940ff17643c0d6543ca", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1052,7 +770,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/1.0.8" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.2" }, "funding": [ { @@ -1060,32 +779,32 @@ "type": "github" } ], - "time": "2020-10-26T13:08:54+00:00" + "time": "2024-12-12T09:59:06+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "2.0.3", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", - "reference": "ac91f01ccec49fb77bdc6fd1e548bc70f7faa3e5", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1107,7 +826,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/2.0.3" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -1115,34 +835,36 @@ "type": "github" } ], - "time": "2020-09-28T05:30:19+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "4.0.6", + "version": "6.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382" + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55f4261989e546dc112258c7a75935a81a7ce382", - "reference": "55f4261989e546dc112258c7a75935a81a7ce382", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/diff": "^4.0", - "sebastian/exporter": "^4.0" + "ext-dom": "*", + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.4" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.2-dev" } }, "autoload": { @@ -1181,7 +903,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.6" + "security": "https://github.com/sebastianbergmann/comparator/security/policy", + "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" }, "funding": [ { @@ -1189,33 +912,33 @@ "type": "github" } ], - "time": "2020-10-26T15:49:45+00:00" + "time": "2024-10-31T05:30:08+00:00" }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1238,7 +961,8 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "security": "https://github.com/sebastianbergmann/complexity/security/policy", + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -1246,33 +970,33 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "4.0.4", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/3461e3fccc7cfdfc2720be910d3bd73c69be590d", - "reference": "3461e3fccc7cfdfc2720be910d3bd73c69be590d", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^11.0", "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1304,7 +1028,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/diff/security/policy", + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -1312,27 +1037,27 @@ "type": "github" } ], - "time": "2020-10-26T13:10:38+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "5.1.4", + "version": "7.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7" + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7", - "reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-posix": "*" @@ -1340,7 +1065,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "5.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -1359,7 +1084,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", + "homepage": "https://github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -1367,7 +1092,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", - "source": "https://github.com/sebastianbergmann/environment/tree/5.1.4" + "security": "https://github.com/sebastianbergmann/environment/security/policy", + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" }, "funding": [ { @@ -1375,34 +1101,34 @@ "type": "github" } ], - "time": "2022-04-03T09:37:03+00:00" + "time": "2024-07-03T04:54:44+00:00" }, { "name": "sebastian/exporter", - "version": "4.0.4", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9" + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/65e8b7db476c5dd267e65eea9cab77584d3cfff9", - "reference": "65e8b7db476c5dd267e65eea9cab77584d3cfff9", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/recursion-context": "^4.0" + "ext-mbstring": "*", + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -1444,7 +1170,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", - "source": "https://github.com/sebastianbergmann/exporter/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/exporter/security/policy", + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" }, "funding": [ { @@ -1452,38 +1179,35 @@ "type": "github" } ], - "time": "2021-11-11T14:18:36+00:00" + "time": "2024-12-05T09:17:50+00:00" }, { "name": "sebastian/global-state", - "version": "5.0.5", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/0ca8db5a5fc9c8646244e629625ac486fa286bf2", - "reference": "0ca8db5a5fc9c8646244e629625ac486fa286bf2", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^9.3" - }, - "suggest": { - "ext-uopz": "*" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "5.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -1502,13 +1226,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/5.0.5" + "security": "https://github.com/sebastianbergmann/global-state/security/policy", + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -1516,33 +1241,33 @@ "type": "github" } ], - "time": "2022-02-14T08:28:10+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", - "php": ">=7.3" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -1565,7 +1290,8 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -1573,34 +1299,34 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "4.0.4", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/5c9eeac41b290a3712d88851518825ad78f45c71", - "reference": "5c9eeac41b290a3712d88851518825ad78f45c71", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=7.3", - "sebastian/object-reflector": "^2.0", - "sebastian/recursion-context": "^4.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1622,7 +1348,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -1630,32 +1357,32 @@ "type": "github" } ], - "time": "2020-10-26T13:12:34+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "2.0.4", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", - "reference": "b4f479ebdbf63ac605d183ece17d8d7fe49c15c7", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -1677,7 +1404,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/2.0.4" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -1685,32 +1413,32 @@ "type": "github" } ], - "time": "2020-10-26T13:14:26+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "4.0.4", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172" + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172", - "reference": "cd9d8cf3c5804de4341c283ed787f099f5506172", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -1737,10 +1465,11 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" }, "funding": [ { @@ -1748,32 +1477,32 @@ "type": "github" } ], - "time": "2020-10-26T13:17:30+00:00" + "time": "2024-07-03T05:10:34+00:00" }, { - "name": "sebastian/resource-operations", - "version": "3.0.3", + "name": "sebastian/type", + "version": "5.1.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8" + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", - "reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", "shasum": "" }, "require": { - "php": ">=7.3" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^9.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -1788,14 +1517,16 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", "support": { - "issues": "https://github.com/sebastianbergmann/resource-operations/issues", - "source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3" + "issues": "https://github.com/sebastianbergmann/type/issues", + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" }, "funding": [ { @@ -1803,32 +1534,29 @@ "type": "github" } ], - "time": "2020-09-28T06:45:17+00:00" + "time": "2024-09-17T13:12:04+00:00" }, { - "name": "sebastian/type", - "version": "3.0.0", + "name": "sebastian/version", + "version": "5.0.2", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/type.git", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad" + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", - "reference": "b233b84bc4465aff7b57cf1c4bc75c86d00d6dad", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.5" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -1847,11 +1575,12 @@ "role": "lead" } ], - "description": "Collection of value objects that represent the types of the PHP type system", - "homepage": "https://github.com/sebastianbergmann/type", + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", "support": { - "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/3.0.0" + "issues": "https://github.com/sebastianbergmann/version/issues", + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -1859,73 +1588,72 @@ "type": "github" } ], - "time": "2022-03-15T09:54:48+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { - "name": "sebastian/version", - "version": "3.0.2", + "name": "staabm/side-effects-detector", + "version": "1.0.5", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c6c1022351a901512170118436c764e473f6de8c" + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c6c1022351a901512170118436c764e473f6de8c", - "reference": "c6c1022351a901512170118436c764e473f6de8c", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", "shasum": "" }, "require": { - "php": ">=7.3" + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.0-dev" - } + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" }, + "type": "library", "autoload": { "classmap": [ - "src/" + "lib/" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "MIT" ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", "support": { - "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/3.0.2" + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" }, "funding": [ { - "url": "https://github.com/sebastianbergmann", + "url": "https://github.com/staabm", "type": "github" } ], - "time": "2020-09-28T06:39:44+00:00" + "time": "2024-10-20T05:08:20+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e", - "reference": "34a41e998c2183e22995f158c581e7b5e755ab9e", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -1954,7 +1682,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -1962,70 +1690,12 @@ "type": "github" } ], - "time": "2021-07-28T10:34:58+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.11.0", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", - "shasum": "" - }, - "require": { - "ext-ctype": "*", - "php": "^7.2 || ^8.0" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" - }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { @@ -2035,5 +1705,5 @@ "platform-dev": { "ext-gd": "*" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/docker-compose.yml b/docker-compose.yml index 812603e..190d849 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,22 @@ -version: '3.7' - services: - lib: - image: fire-midge-php-cli-test-8.1 + lib81: + build: + context: docker/8.1 + args: + UID: ${USER_ID} + container_name: ${LIBRARY_NAME}-lib81 + working_dir: /src + volumes: + - ./:/src + env_file: + - .env + + lib84: build: - context: docker + context: docker/8.4 args: UID: ${USER_ID} - container_name: ${LIBRARY_NAME}-lib + container_name: ${LIBRARY_NAME}-lib84 working_dir: /src volumes: - ./:/src diff --git a/docker/Dockerfile b/docker/8.1/Dockerfile similarity index 100% rename from docker/Dockerfile rename to docker/8.1/Dockerfile diff --git a/docker/8.4/Dockerfile b/docker/8.4/Dockerfile new file mode 100644 index 0000000..851c89b --- /dev/null +++ b/docker/8.4/Dockerfile @@ -0,0 +1,36 @@ +FROM php:8.4-cli-alpine + +RUN \ + # Needed to install pecl libraries + apk add g++ make autoconf && \ + # + # To get code coverage reports in PHPUnit (which is mandatory for running Infection), we need either PCOV or xDebug. + # + # Installing xDebug: + # apk add --no-cache $PHPIZE_DEPS && \ + # pecl install xdebug && docker-php-ext-enable xdebug \ + # + # Installing PCOV (which is faster than xDebug): + pecl install pcov && docker-php-ext-enable pcov + +# The following are required by the newer version of Infection (PHP 8), as it makes use of +# the Normalizer class. Normalizer requires the intl PHP extension, which in turn has +# icu-dev as a dependency. +RUN \ + apk add icu-dev && \ + docker-php-ext-configure intl && docker-php-ext-install intl + +# The following is required to install the PHP GD library, which is used to generate images. +RUN apk add --no-cache libpng libpng-dev && docker-php-ext-install gd && apk del libpng-dev + +RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer + +# Ensure any files created by the running application are owned by the user who first built the image +ARG UID +RUN if [ -z "$UID" ]; then echo "Build argument 'UID' was not set" 1>&2 && exit 1; fi +RUN echo "app:x:$UID:$UID::/home/app:" >> /etc/passwd \ + && echo "app:!:$(($(date +%s) / 60 / 60 / 24)):0:99999:7:::" >> /etc/shadow \ + && echo "app:x:$UID:" >> /etc/group \ + && mkdir /home/app \ + && chown app: /home/app +USER app diff --git a/docs/README.md b/docs/README.md index 1466bab..915e554 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,24 +1,44 @@ # value-objects -This library provides convenience methods for creating value objects. + +**Tested with PHP 8.4. Requires PHP ^8.1.** + +This library provides convenience methods for creating value objects in the form of traits, as well as some generic classes implementing said traits to use as-is where no configuration is needed. You may use the below table to decide which type is best for you. -*"Single Value" means the object will hold a single value, whereas "Array of Values" means the object can hold more than one value.* +*"Single Value" means the object will hold a single value, whereas "Array of Values" means the object can hold more than one value.* + +**You can click on the relevant type to jump straight to their documentation.** | | Single Value | Array of Values | |:----------------------------|:------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | List of Valid Values | [`IsStringEnumType`](#isstringenumtype)
[`IsIntEnumType`](#isintenumtype)
[`IsIntStringMapType`](#isintstringmaptype) | [`IsStringArrayEnumType`](#isstringarrayenumtype)
[`IsIntArrayEnumType`](#isintarrayenumtype)
[`IsClassArrayEnumType`](#isclassarrayenumtype)
[`IsArrayEnumType`](#isarrayenumtype) | | Any Value/Custom Validation | [`IsEmailType`](#isemailtype)
[`IsStringType`](#isstringtype)
[`IsFloatType`](#isfloattype)
[`IsIntType`](#isinttype) | [`IsClassCollectionType`](#isclasscollectiontype)
[`IsCollectionType`](#iscollectiontype) | +### Generic classes + +They only exist for convenience, already implementing a type trait with no or minimal configuration. +All classes are extendable if needed, or you can implement the relevant trait directly. + +| Name | Implemented trait | Notes | +|:----------------|:--------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `AnyCollection` | `IsCollectionTrait` | Used when you just want to access an array using normalised OOP methods rather than PHP-native global functions, while not caring about the type of elements. | +| `AnyFloat` | `IsFloatType` | Used when you do not want to customise min/max value or other validation rules and the value is a float. | +| `AnyInteger` | `IsIntType` | Used when you do not want to customise min/max value or other validation rules and the value is an integer. | +| `AnyString` | `IsStringType` | Used when there are no rules about the format of the string and there is no list of valid values either. | +| `Email` | `IsEmailType` | Used when dealing with an email address without custom validation rules/custom formatting. | +| `Percentage` | `IsFloatType` | Used when expecting a value between 0 and 100, which can be represented as a string (with a % symbol), an integer, or a float (with a customisable number of decimal places. Passing a value less than 0 or greater than 100 results in an exception (rather than being clipped silently. The class can be extended to configure further. | + + ## Quality Control The following table is updated with each code update and is generated with the help of PhpUnit (unit testing tool) and Infection (mutation testing tool): -|                                            | Percentage | Description | -|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Code Coverage | ![100%](docs/img/cc.png) | How many methods have been fully covered by tests. | -| Mutation Score Indicator | ![97%](docs/img/msi.png) | Indicates how many generated mutants were detected. *Note that some mutants are false positives.* | -| Mutation Code Coverage | ![98%](docs/img/mcc.png) | Should be in the same ballpark as the normal code coverage. Formula: `(TotalMutantsCount - NotCoveredByTestsCount) / TotalMutantsCount` | -| Covered Code MSI | ![98%](docs/img/ccm.png) | This is the MSI (Mutation Score Indicator) for code that is actually covered by tests. It shows how effective the tests really are. Formula: `TotalDefeatedMutants / (TotalMutantsCount - NotCoveredByTestsCount)`. *Note that for some reason, Infection may report some mutants not being covered by tests when they actually are.* | +|                                            | Percentage | Description | +|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Code Coverage | ![100%](docs/img/cc.png) | How many methods have been fully covered by tests. | +| Mutation Score Indicator | ![99%](docs/img/msi.png) | Indicates how many generated mutants were detected. *Note that some mutants are false positives.* | +| Mutation Code Coverage | ![99%](docs/img/mcc.png) | Should be in the same ballpark as the normal code coverage. Formula: `(TotalMutantsCount - NotCoveredByTestsCount) / TotalMutantsCount` | +| Covered Code MSI | ![100%](docs/img/ccm.png) | This is the MSI (Mutation Score Indicator) for code that is actually covered by tests. It shows how effective the tests really are. Formula: `TotalDefeatedMutants / (TotalMutantsCount - NotCoveredByTestsCount)`.| ## IsStringEnumType @@ -27,6 +47,8 @@ Use this type when there is a set of fixed valid values, and your object represe *If there is a set of fixed valid values but your object represents an array of values, use [`IsStringArrayEnumType`](#isstringarrayenumtype).* +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyString`.** + Example: ```php class Season @@ -55,6 +77,7 @@ Usage: $spring = Season::fromString(Season::SPRING); ``` + ## IsIntEnumType Use this type when there is a set of fixed valid values, and your object represents a single value. @@ -96,6 +119,8 @@ $success = Status::fromInt(Status::SUCCESS); Use this type when the value represents a single e-mail address. This trait uses [`IsStringType`](#isstringtype) under the hood but performs standard e-mail validation. +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\Email`.** + Example: ```php @@ -110,6 +135,7 @@ Usage: $email = Email::fromString('hello@there.co.uk'); ``` + ## IsStringType Use this type when the value represents a single string value, but there is no fixed set of valid values. @@ -162,6 +188,8 @@ $productName = ProductName::fromString(' orange juice'); Use this type when the value represents a single integer value, but there is no fixed list of valid values, or it is not feasible to write up each valid value. +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyInteger`.** + ### Validation @@ -187,6 +215,7 @@ class Percentage } } ``` +**Note that there is a convenient `Percentage` class already available: `FireMidge\ValueObject\Generic\Percentage`.** Another example, for a value without any limitations: ```php @@ -244,6 +273,8 @@ $percentage = Percentage::fromInt(78); Use this type when the value represents a single float value. +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyFloat`.** + ### Validation @@ -607,11 +638,12 @@ You can combine this type with any other type, e.g. to get an array of float typ You can provide custom validation by overriding `protected function validateEach(mixed $value) : void`, which is executed for each value separately, both when instantiating it and when calling `withValue`. Note that this validation will also run before `withoutValue`, `tryWithoutValue` and `contains`, so you are notified when passing something entirely invalid rather than it being silently swallowed. Example: + ```php +use FireMidge\ValueObject\IsCollectionType; + /** - * @method static withValue(Status $addedValue) - * @method static tryWithoutValue(Status $value) - * @method static contains(Status $value) + * @extends IsCollectionType */ class StatusList { @@ -656,13 +688,53 @@ $duplicateStatusesIgnored = StatusList::fromArray([ Status::SUCCESS, Status::REDIRECTION, Status::SUCCESS, -]) +]); // $newStatuses will only contain one instance of Status::REDIRECTION. // This is because of `ignoreDuplicateValues` returning true. $newStatuses = $statuses->withValue(Status::REDIRECTION); ``` +You also have a variety of other array methods available, e.g.: + +```php +use FireMidge\ValueObject\Generic\AnyCollection;$statuses = StatusList::fromArray([ + Status::SUCCESS, + Status::REDIRECTION, +]); +$errorStatuses = StatusList::fromArray([ + Status::SERVER_ERROR, + Status::CLIENT_ERROR, +]); + +// $newStatuses contains statuses from both $statuses and $errorStatuses, +// without modifying the merged classes. +$newStatuses = $statuses->withMerged($errorStatuses); + +// This does modify $statuses, and cause it to append the values from +// $errorStatuses to its own. +$statuses->merge($errorStatuses); + +// split() causes the values of one collection to be split into +// 2 collections. This modifies the original instance. +// In this case, $alsoErrorStatuses contains Status::SERVER_ERROR +// and Status::CLIENT_ERROR, while $statuses only keeps +// the first 2 elements, i.e. STATUS::SUCCESS and STATUS::REDIRECTION. +$alsoErrorStatuses = $statuses->split(2); + +// pop() removes the last element and returns it. +// This modifies the original instance ($statuses). +$redirection = $statuses->pop(); + +// You can also pop multiple values at once, but note +// that the returned order will be reversed, as each element +// is popped individually and together, they are returned as a new +// collection instance. +$values = AnyCollection::fromArray('a', 'b', 'c', 'd', 'e'); +$last2 = $values->popMultiple(2); +echo json_encode($last2); // ["e","d"] +``` + ## IsClassCollectionType @@ -687,10 +759,10 @@ You can provide custom validation by overriding `protected function validateEach Example: ```php +use FireMidge\ValueObject\IsCollectionType; + /** - * @method static withValue(Email $addedValue) - * @method static tryWithoutValue(Email $value) - * @method static contains(Email $value) + * @extends IsCollectionType */ class EmailCollection { @@ -811,6 +883,8 @@ $emailsMatch = $emails->isEqualTo([ Use this type when the value represents an array of values and there is **no** finite list of valid values. If there is a list of valid values, use [`IsArrayEnumType`](#isarrayenumtype) (or any of the more specific variations, e.g. [`IsStringArrayEnumType`](#isstringarrayenumtype) if applicable). +**If you do not need to do any configuration, there is a generic class available, implementing this type: `FireMidge\ValueObject\Generic\AnyCollection`.** + ### Combination with other types You can combine this type with any other type, e.g. to get an array of float types, an array of e-mail addresses, etc. @@ -835,7 +909,7 @@ You can provide custom validation by overriding `protected function validateEach ### Value transformation -If you want to transform the input value but not fail validation, override `protected function transformEach($value)`. +If you want to transform the input value but not fail validation, override `protected function transformEach(mixed $value)`. By also using the trait `CanTransformStrings`, you'll get 3 convenience methods that you can call inside `transform` if you want: - `trimAndLowerCase(string $value)` @@ -845,10 +919,10 @@ By also using the trait `CanTransformStrings`, you'll get 3 convenience methods Example: ```php +use FireMidge\ValueObject\IsCollectionType; + /** - * @method static withValue(string $addedValue) - * @method static tryWithoutValue(string $value) - * @method static contains(string $value) + * @extends IsCollectionType */ class ProductNameCollection { @@ -862,11 +936,7 @@ class ProductNameCollection } } - /** - * @param mixed $value - * @return mixed - */ - protected function transformEach($value) + protected function transformEach(mixed $value) : mixed { if (! is_string($value)) { return $value; diff --git a/docs/img/ccm.png b/docs/img/ccm.png index 41d186ebec308c31fc0b5f936a01ef0422b8f1c2..3d91e715865316388445c6a23f70d5993a496292 100644 GIT binary patch delta 114 zcmV-&0FD2|0m1>0S8G;DL_t&-mA#Nr3cxT3Lqkp?-v5wGly$=T(VwkI(AQRj$lVEi zj;+B?LfW_#Mqq-$#zj==F$VTuhc1MaZg9mpMvmTVqz4Y)x4{Ra9j*@vH$=807*qoM6N<$g3-k?-~a#s delta 116 zcmV-)0E_>^0mK22S8Z5HL_t&-m9>yj4!|G`LJ21!-2aekXVuO5v5!rD$P1yZ@a_qJ zV}Ulp(&EGjiJ>$DD??$h!6~zK%o3j66&@oieaEiu?7HASvb8-HTyQ@pSNi&^UgHC6 W=Q5X?Lmn;w0000NB|j diff --git a/docs/img/mcc.png b/docs/img/mcc.png index 41d186ebec308c31fc0b5f936a01ef0422b8f1c2..0082d1f225b8b10c8b2cf480e7469a19b35d1af9 100644 GIT binary patch delta 99 zcmV-p0G$8C0mK22Xk2Ny{~<@M)5UGcKAio*7eZ0!=>-190ZOo>f)m3N^rRR#(qw@b zELO%0sfcbKEA0YTtSZp?g@Wmfi}X@;=~Avp)><4Lt(JN zDYJFV5}w@^9wRG#$FA<|y5K&twLKPGa6czk`ueM0;{$8wGMAb|9xea?002ovPDHLk FV1m&TE|LHM diff --git a/docs/img/msi.png b/docs/img/msi.png index 6613c9fe22f877ba234227b786a1202298d6285c..0082d1f225b8b10c8b2cf480e7469a19b35d1af9 100644 GIT binary patch delta 116 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