README.md
PHP Fast JSON Patch
=====================
[![Test](https://github.com/blancks/fast-jsonpatch-php/workflows/Test/badge.svg)](https://github.com/blancks/fast-jsonpatch-php/blob/main/.github/workflows/test.yml)
[![phpstan](https://github.com/blancks/fast-jsonpatch-php/workflows/phpstan/badge.svg)](https://github.com/blancks/fast-jsonpatch-php/blob/main/phpstan.neon.dist)
[![codecov](https://codecov.io/github/blancks/fast-jsonpatch-php/graph/badge.svg?token=3PUC5RAPPQ)](https://codecov.io/github/blancks/fast-jsonpatch-php)
[![Maintainability](https://api.codeclimate.com/v1/badges/92765b3410e20dc9a431/maintainability)](https://codeclimate.com/github/blancks/fast-jsonpatch-php/maintainability)
[![PHP Version Require](https://poser.pugx.org/blancks/fast-jsonpatch-php/require/php)](https://packagist.org/packages/blancks/fast-jsonpatch-php)
[![Latest Stable Version](https://poser.pugx.org/blancks/fast-jsonpatch-php/v)](https://packagist.org/packages/blancks/fast-jsonpatch-php)
This documentation covers the `FastJsonPatch` PHP class, designed to apply a series of JSON Patch operations as specified in [RFC 6902](https://datatracker.ietf.org/doc/html/rfc6902).
Fast JSON Patch is the fastest fully RFC compliant package available in PHP, you can find compliance test and benchmark results [here](https://github.com/blancks/php-jsonpatch-benchmarks).
## Installation via Composer
``` bash
composer require blancks/fast-jsonpatch-php
```
## Class Overview
The `FastJsonPatch` class provides a way to modify JSON documents using a structured patch object. \
The patch object contains an array of operations (`add`, `remove`, `replace`, `move`, `copy`, and `test`) that describe the changes to be made to the target JSON document.
### Usage Example
Below is an example of how to use the `FastJsonPatch` class to apply a patch to a JSON document:
```php
use blancks\JsonPatch\FastJsonPatch;
use blancks\JsonPatch\exceptions\FastJsonPatchException;
$document = '{
"contacts":[
{"name":"John","number":"-"},
{"name":"Dave","number":"+1 222 333 4444"}
]
}';
$patch = '[
{"op":"add","path":"/contacts/-","value":{"name":"Jane", "number":"+1 353 644 2121"}},
{"op":"replace","path":"/contacts/0/number","value":"+1 212 555 1212"},
{"op":"remove","path":"/contacts/1"}
]';
$FastJsonPatch = FastJsonPatch::fromJson($document);
try {
$FastJsonPatch->apply($patch);
} catch (FastJsonPatchException $e) {
// here if patch cannot be applied for some reason
echo $e->getMessage(), "\n";
}
var_dump($FastJsonPatch->getDocument());
```
**Expected Output:**
```txt
object(stdClass) (1) {
["contacts"]=>
array(2) {
[0]=>
object(stdClass) (2) {
["name"]=>
string(4) "John"
["number"]=>
string(15) "+1 212 555 1212"
}
[1]=>
object(stdClass) (2) {
["name"]=>
string(4) "Jane"
["number"]=>
string(15) "+1 353 644 2121"
}
}
}
```
The expected workflow is that once you got a `FastJsonPatch` instance you can call the `apply` method each time a new patch is received and this is particularly handy in long-running context like a websocket client/server.
Patch application is designed to be atomic. If any operation of a given patch fails the original document is restored, ensuring a consistent state of the document.
## Constructor
#### `__construct(mixed &$document, ?JsonHandlerInterface $JsonHandler = null, ?JsonPointerHandlerInterface $JsonPointerHandler = null)`
- **Description**: Initializes a new instance of the `FastJsonPatch` class.
- **Parameters**:
- `mixed &$document`: The decoded JSON document.
- `?JsonHandlerInterface $JsonHandler`: An instance of the JSON handler which will be responsible for encoding/decoding and CRUD operations.\
The default handler is `blancks\JsonPatch\json\handlers\BasicJsonHandler` and decodes json objects as php \stdClass instances. This is the recommended way.\
If you application requires working with associative arrays only, you can pass a `blancks\JsonPatch\json\handlers\ArrayJsonHandler` instance instead.
- `?JsonPointerHandlerInterface $JsonPointerHandler`: Instance of the object responsible to validate and parse JSON Pointers.\
The default handler is `blancks\JsonPatch\json\pointer\JsonPointer6901`
- **Returns**: Instance of the `FastJsonPatch` class.
- **Example**:
```php
$document = ['one', 'two', 'three'];
$FastJsonPatch = new FastJsonPatch($document);
```
## Public Methods
#### `static function fromJson(string $patch, ?JsonHandlerInterface $JsonHandler = null, ?JsonPointerHandlerInterface $JsonPointerHandler = null) : void`
- **Description**: Returns a new instance of the `FastJsonPatch` class.
- **Parameters**:
- `string $document`: A json encoded document to which the patches will be applied
- `?JsonHandlerInterface $JsonHandler`: An instance of the JSON handler which will be responsible for encoding/decoding and CRUD operations.\
The default handler is `blancks\JsonPatch\json\handlers\BasicJsonHandler` and decodes json objects as php \stdClass instances. This is the recommended way.\
If you application requires working with associative arrays only, you can pass a `blancks\JsonPatch\json\handlers\ArrayJsonHandler` instance instead.
- `?JsonPointerHandlerInterface $JsonPointerHandler`: Instance of the object responsible to validate and parse JSON Pointers.\
The default handler is `blancks\JsonPatch\json\pointer\JsonPointer6901`
- **Example**:
```php
$FastJsonPatch = FastJsonPatch::fromJson('{"foo":"bar","baz":["qux","quux"]}');
```
#### `function apply(string $patch) : void`
- **Description**: Applies a series of patch operations to the specified JSON document. Ensures atomicity by applying all operations successfully or restoring the original document if any operation fails.
- **Parameters**:
- `string $patch`: A json-encoded array of patch operations.
- **Exceptions**:
- Throws `FastJsonPatchValidationException` if a patch operation is invalid or improperly formatted.
- Throws `FastJsonPatchException` if any other error occurs while applying the patch
- **Example**:
```php
$patch = '[{"op":"test", "path":"/foo", "value":"bar"}]';
$FastJsonPatch->apply($patch);
```
#### `function isValidPatch(string $patch): bool`
- **Description**: Tells if the $patch is syntactically valid
- **Parameters**:
- `string $patch`: A json-encoded array of patch operations.
- **Returns**: True is the patch is valid, false otherwise
- **Example**:
```php
$patch = '[{"op":"add","path":"/foo"}]'; // invalid because there's no "value" key
if ($FastJsonPatch->isValidPatch($patch)) {
$FastJsonPatch->apply($patch);
} else {
echo "Invalid patch!";
}
```
#### `function read(string $path): mixed`
- **Description**: Uses a JSON Pointer (RFC-6901) to fetch data from the referenced document
- **Parameters**:
- `string $patch`: A json pointer
- **Returns**: The value located by the provided pointer
- **Example**:
```php
$FastJsonPatch = FastJsonPatch::fromJson('{"foo":"bar","baz":["qux","quux"]}');
echo $FastJsonPatch->read('/baz/1'); // "quux"
```
#### `function &getDocument(): mixed`
- **Description**: Returns the document reference that the instance is holding
- **Returns**: The referenced document
- **Example**:
```php
$FastJsonPatch = FastJsonPatch::fromJson('["qux","quux"]');
var_dump($FastJsonPatch->getDocument()); // array(2) {[0]=> string(3) "qux" [1]=> string(4) "quux"}
```
#### `function registerOperation(PatchOperationInterface $PatchOperation): void`
- **Description**: Allows to register new patch operation handlers or to override existing ones.
- **Parameters**:
- `PatchOperationInterface $PatchOperation`: The handler class for handling the operation.
- **Example**:
```php
$FastJsonPatch->registerOperation(new Add);
```
## Supported Operations
#### `add`
- **Description**: Adds a value to the specified path in the document.
- **Parameters**:
- `path`: JSON Pointer to the location where the value should be added.
- `value`: The value to add.
- **Example**:
```json
{"op":"add","path":"/new/key","value":"example"}
```
#### `remove`
- **Description**: Removes the value at the specified path.
- **Parameters**:
- `path`: JSON Pointer to the location of the value to remove.
- **Example**:
```json
{"op":"remove","path":"/old/key"}
```
#### `replace`
- **Description**: Replaces the value at the specified path with a new value.
- **Parameters**:
- `path`: JSON Pointer to the location of the value to replace.
- `value`: The new value.
- **Example**:
```json
{"op":"replace","path":"/replace/key","value":"newValue"}
```
#### `move`
- **Description**: Moves the value from one path to another.
- **Parameters**:
- `from`: JSON Pointer to the source location of the value to move.
- `path`: JSON Pointer to the destination location.
- **Example**:
```json
{"op":"move","from":"/old/key","path":"/new/key"}
```
#### `copy`
- **Description**: Copies the value from one path to another.
- **Parameters**:
- `from`: JSON Pointer to the source location of the value to copy.
- `path`: JSON Pointer to the destination location.
- **Example**:
```json
{"op":"copy","from":"/source/key","path":"/target/key"}
```
#### `test`
- **Description**: Tests that the specified value matches the value at the path.
- **Parameters**:
- `path`: JSON Pointer to the location of the value to test.
- `value`: The value to compare.
- **Example**:
```json
{"op":"test","path":"/test/key","value":"expectedValue"}
```
## Running tests
``` bash
composer test
```
Test cases comes from [json-patch/json-patch-tests](https://github.com/json-patch/json-patch-tests) and extended furthermore to ensure a strict compliance with RFC-6902.
## Contributing
Contributions are welcome! 🎉\
Feel free to fork the repository if you'd like to contribute to this project!
Please ensure your contributions align with the project's goals and code style:
* Use `composer run static-analyse` for static analysis
* Use `composer run pretty-php` to reformat your code before commit
* Provide unit tests for the code you wrote, this project targets 100% coverage
* It is ok to write alternative handlers to address slower operations, such as working with huge json files
* Any changes that hurts the package performance in its default configuration must be motivated with a good reason
If you have any questions, need guidance, reporting a bug or suggesting an improvement feel free to open an issue. Every bit helps!
Thank you for helping to improve this project!
## License
This software is licensed under the [MIT License](LICENSE.md).