Skip to content

Commit

Permalink
Create basic array/object functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
shadowhand committed Nov 3, 2023
1 parent cb66aca commit 24157b5
Show file tree
Hide file tree
Showing 16 changed files with 3,639 additions and 7 deletions.
8 changes: 8 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/.gitattributes export-ignore
/.github/ export-ignore
/.gitignore export-ignore
/composer.lock export-ignore
/docs/ export-ignore
/phpcs.xml export-ignore
/phpunit.xml export-ignore
/tests/ export-ignore
88 changes: 88 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
name: CI

on:
pull_request: ~
push:
branches: [ main ]

jobs:
check-style:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Cache dependencies
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-vendor-${{ hashFiles('**/composer.lock') }}

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.2'

- name: Install dependencies
run: composer install

- name: Check code style
run: vendor/bin/phpcs

test-code:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php:
- '8.2'
- '8.3'
minimum_versions: [false]
coverage: ['none']
include:
- description: 'Minimum version'
php: '8.2'
minimum_versions: true
- description: 'Code coverage'
php: '8.2'
coverage: 'pcov'
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Cache dependencies
uses: actions/cache@v3
with:
path: vendor
key: ${{ runner.os }}-vendor-${{ hashFiles('**/composer.lock') }}

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
coverage: ${{ matrix.coverage }}

- name: Install dependencies
run: composer install
if: matrix.minimum_versions == false

- name: Install dependencies (minimum versions)
run: composer update --no-interaction --prefer-lowest
if: matrix.minimum_versions == true

- name: Run tests
run: vendor/bin/phpunit --no-coverage
if: matrix.coverage == 'none'

- name: Run tests with code coverage
run: vendor/bin/phpunit
if: matrix.coverage == 'pcov'

- name: Upload code coverage report
uses: codecov/codecov-action@v4-beta
if: matrix.coverage == 'pcov'
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
file: build/coverage.xml
fail_ci_if_error: true
8 changes: 4 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.phpcs-cache
.phpunit.cache
composer.lock
composer.phar
/build/
/vendor/

# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
# composer.lock
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 focusphp
Copyright (c) 2023 Woody Gilk <woody@shadowhand.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
126 changes: 124 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,124 @@
# data
A collection of tools for working with unstructured data, such as JSON
# Focus: Data

[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%208.2-8892BF.svg?style=flat)](https://php.net/)
[![Latest Stable Version](http://img.shields.io/packagist/v/focus/data.svg?style=flat)](https://packagist.org/packages/focus/data)
[![CI Status](https://github.com/focusphp/data/workflows/CI/badge.svg?branch=main&event=push)](https://github.com/focusphp/data/actions)
[![Code Coverage](https://codecov.io/gh/focusphp/data/graph/badge.svg?token=XFMRWA70FN)](https://codecov.io/gh/focusphp/data)
[![License](https://img.shields.io/packagist/l/focus/data.svg?style=flat)](https://packagist.org/packages/focus/data)

A collection of tools for working with unstructured data, such as JSON.

## Installation

The best way to install and use this package is with [composer](https://getcomposer.org/):

```shell
composer require focus/data
```

## Usage

The most basic usage is `KeyedData`, which wraps arrays and objects:

```php
use Focus\Data\KeyedData;

$value = [
'user' => [
'name' => 'Susan Smith',
'email' => 'susan@example.com',
'hobbies' => [
'football',
'swimming',
'reading',
],
'deactivated_at' => null,
],
];

$data = KeyedData::from($value);
```

Once you have an instance of data, you can access the values by using dot paths:

```php
$name = $data->get(path: 'user.name'); // Susan Smith
$email = $data->get(path: 'user.email'); // susan@example.com
```

Values that do not exist will be returned as `null`:

```php
$phone = $data->get(path: 'user.phone'); // null
```

[JMESPath](https://jmespath.org) expressions are also supported using the search() method:

```php
$sports = $data->search(path: "user.hobbies[? contains(@, 'ball')]"); // ['football']
```

It is also possible to check for the existence of a path, even when the value is `null`:

```php
$deactivated = $data->has(path: 'user.deactivated_at'); // true
```

## FAQ

These are some of the most common questions about usage and design of this package.

### Why does has() return true for null values?

This allows detecting when input has a value that should not be overwritten. For instance,
if an application sets a `deactivated_at` timestamp to indicate that the user has left,
it might also need to be able to reactivate the user by setting `deactivated_at: null`:

```php
if ($data->get(path: 'user.deactivated_at')) {
$this->userRepository->deactivate(
id: $data->get(path: 'user.id'),
timestamp: $data->get(path: 'user.deactivated_at'),
);
} elseif ($data->has(path: 'user.deactivated_at')) {
$this->userRepository->activate(
id: $data->get(path: 'user.id'),
);
}
```

If has() did not return true for null values, detecting the existence of a null value would
be impossible, since get() returns null for undefined paths.

### Why is there a Data interface?

Keen observers will note that `KeyedData` implements a `Data` interface and the existence of
the `DataProxy` abstract class. This allows for customization of the implementation, despite
`KeyedData` being a `final readonly` class, by using a [proxy object][proxy] to satisfy the
[Open/Closed Principle][open-closed].

By default, the `DataProxy` object will forward all calls directly to the source `Data` object.
This allows customizing the behavior of any method without having to implement the full `Data`
interface. For example, this would modify the get() method to treat `false` values as `null`:

```php
use Focus\Data\Data;
use Focus\Data\DataProxy;

final class MyData extends DataProxy
{
public function get(string $path): mixed
{
$value = $this->source()->get($path);

if ($value === false) {
return null;
}

return $value;
}
}
```

[proxy]: https://refactoring.guru/design-patterns/proxy
[open-closed]: https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle
53 changes: 53 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "focus/data",
"description": "A collection of tools for working with unstructured data, such as JSON.",
"license": "MIT",
"type": "library",
"authors": [
{
"name": "Woody Gilk",
"email": "woody@shadowhand.com"
}
],
"require": {
"php": "^8.2",
"mtdowling/jmespath.php": "^2.7",
"psr/http-message": "^2.0"
},
"require-dev": {
"doctrine/coding-standard": "^12.0",
"ergebnis/composer-normalize": "^2.39",
"phpunit/phpunit": "^10.4"
},
"minimum-stability": "stable",
"autoload": {
"psr-4": {
"Focus\\Data\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Focus\\Data\\Tests\\": "tests/"
}
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true,
"ergebnis/composer-normalize": true
},
"sort-packages": true
},
"scripts": {
"check": "phpcs",
"test": "phpunit"
},
"tags": [
"json",
"data",
"dot",
"notation",
"path",
"array",
"object"
]
}
Loading

0 comments on commit 24157b5

Please sign in to comment.