Skip to content

Commit

Permalink
feat/add-support-for-ignoring-properties (#29)
Browse files Browse the repository at this point in the history
* Add support for ignoring properties in `Describe.php`.

* Add support for ignoring properties in `DataModel.php`.

* Add tests.

* Update `README.md`.

* Update `README.md`.

---------

Co-authored-by: david_smith <david_smith@sweetwater.com>
  • Loading branch information
zero-to-prod and david_smith authored Oct 25, 2024
1 parent ab16d82 commit 759b38b
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 22 deletions.
81 changes: 60 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Transform data into hydrated objects by [describing](#property-level-cast) how t

## Features

- [Simple Interface](#hydrating-from-data): A single entry point to create class instances from associative arrays or objects.
- [Non-Invasive](#hydrating-from-data): Simply add the DataModel trait to a class. No need to extend, implement, or construct.
- [Recursive Instantiation](#recursive-hydration): Recursively instantiate classes based on their type.
- [Type Casting](#property-level-cast): Supports primitives, custom classes, enums, and more.
- [Life-Cycle Hooks](#life-cycle-hooks): Run methods before and after a value is resolved with [pre](#pre-hook) and [post](#post-hook).
Expand All @@ -27,6 +27,7 @@ Transform data into hydrated objects by [describing](#property-level-cast) how t
- [Default Values](#default-values): Set a default property value.
- [Nullable Missing Values](#nullable-missing-values): Resolve a missing value as null.
- [Remapping](#re-mapping): Re-map a key to a property of a different name.
- [Ignoring Properties](#ignoring-properties): Ignore a resolving a property.

## Installation

Expand Down Expand Up @@ -67,12 +68,12 @@ class User
Use the `from` method to instantiate your class, passing an associative array or object.

```php
$user = User::from([
$User = User::from([
'name' => 'John Doe',
'age' => '30',
]);
echo $user->name; // 'John Doe'
echo $user->age; // 30
echo $User->name; // 'John Doe'
echo $User->age; // 30
```

### Recursive Hydration
Expand All @@ -81,7 +82,7 @@ The `DataModel` trait recursively instantiates classes based on their type decla
If a property’s type hint is a class, its value is passed to that class’s `from()` method.

In this example, the `address` element is automatically converted into an `Address` object,
allowing direct access to its properties: `$user->address->city`.
allowing direct access to its properties: `$User->address->city`.

```php
class Address
Expand All @@ -100,15 +101,15 @@ class User
public Address $address;
}

$user = User::from([
$User = User::from([
'username' => 'John Doe',
'address' => [
'street' => '123 Main St',
'city' => 'Hometown',
],
]);

echo $user->address->city; // Outputs: Hometown
echo $User->address->city; // Outputs: Hometown
```

## Transformations
Expand All @@ -125,6 +126,7 @@ The `Describe` attribute can accept these arguments.

```php
#[\Zerotoprod\DataModel\Describe([
'ignore' => true
// Re-map a key to a property of a different name
'from' => 'key',
// Runs before 'cast'
Expand Down Expand Up @@ -186,14 +188,14 @@ function uppercase(mixed $value, array $context){
return strtoupper($value);
}

$user = User::from([
$User = User::from([
'first_name' => 'Jane',
'last_name' => 'Doe',
]);

$user->first_name; // 'JANE'
$user->last_name; // 'DOE'
$user->full_name; // 'Jane Doe'
$User->first_name; // 'JANE'
$User->last_name; // 'DOE'
$User->full_name; // 'Jane Doe'
```

#### Life-Cycle Hooks
Expand Down Expand Up @@ -276,14 +278,14 @@ class User
}
}

$user = User::from([
$User = User::from([
'first_name' => 'Jane',
'last_name' => 'Doe',
]);

$user->first_name; // 'Jane'
$user->last_name; // 'DOE'
$user->fullName; // 'Jane Doe'
$User->first_name; // 'Jane'
$User->last_name; // 'DOE'
$User->fullName; // 'Jane Doe'
```

### Union Types
Expand Down Expand Up @@ -321,13 +323,13 @@ class User
}
}

$user = User::from([
$User = User::from([
'first_name' => 'Jane',
'registered' => '2015-10-04 17:24:43.000000',
]);

$user->first_name; // 'JANE'
$user->registered->format('l'); // 'Sunday'
$User->first_name; // 'JANE'
$User->registered->format('l'); // 'Sunday'
```

## Required Properties
Expand All @@ -347,7 +349,7 @@ class User
public string $email;
}

$user = User::from(['email' => 'john@example.com']);
User::from(['email' => 'john@example.com']);
// Throws PropertyRequiredException exception: Property: username is required
```

Expand All @@ -366,9 +368,9 @@ class User
public string $username;
}

$user = User::from();
$User = User::from();

echo $user->username // 'N/A'
echo $User->username // 'N/A'
```

### Limitations
Expand Down Expand Up @@ -432,6 +434,43 @@ $User = User::from([
echo $User->first_name; // John
```

## Ignoring Properties

You can ignore a property like this:

```php
use Zerotoprod\DataModel\Describe;

class User
{
use \Zerotoprod\DataModel\DataModel;

public string $name;

#[Describe(['ignore' => true])]
public int $age;
}
```

```php
use Zerotoprod\DataModel\Describe;

class User
{
use \Zerotoprod\DataModel\DataModel;

#[Describe(['from' => 'firstName'])]
public string $first_name;
}

$User = User::from([
'name' => 'John Doe',
'age' => '30',
]);

isset($User->age); // false
```

## Examples

### Array of DataModels
Expand Down
5 changes: 5 additions & 0 deletions src/DataModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ public static function from(iterable|object|null $context = []): self
$Attribute = ($ReflectionProperty->getAttributes(Describe::class)[0] ?? null);
/** @var Describe $Describe */
$Describe = $Attribute?->newInstance();

if(isset($Describe->ignore) && $Describe->ignore){
continue;
}

$context_key = $Describe->from ?? $ReflectionProperty->getName();

/** Property-level Pre Hook */
Expand Down
3 changes: 2 additions & 1 deletion src/Describe.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ class Describe
public mixed $pre;
public mixed $post;
public bool $missing_as_null;
public bool $ignore;

/**
* Pass an associative array to the constructor to describe the behavior of a property when it is resolved.
Expand Down Expand Up @@ -198,7 +199,7 @@ class Describe
* }
* ```
*
* @param string|array{'from': string,'pre': string|string[], 'cast': string|string[], 'post': string|string[], 'required': bool, 'default': mixed, 'missing_as_null': bool}|null| $attributes
* @param string|array{'from': string,'pre': string|string[], 'cast': string|string[], 'post': string|string[], 'required': bool, 'default': mixed, 'missing_as_null': bool, 'ignore': bool}|null $attributes
*
* @link https://github.com/zero-to-prod/data-model
*
Expand Down
20 changes: 20 additions & 0 deletions tests/Unit/Examples/Ignore/IgnoreTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

namespace Tests\Unit\Examples\Ignore;

use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

class IgnoreTest extends TestCase
{
#[Test] public function from(): void
{
$user = User::from([
'name' => 'John Doe',
'age' => '30',
]);

$this->assertEquals('John Doe', $user->name);
$this->assertFalse(isset($user->age));
}
}
16 changes: 16 additions & 0 deletions tests/Unit/Examples/Ignore/User.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Tests\Unit\Examples\Ignore;

use Tests\Unit\Examples\ExtendsTrait\DataModel;
use Zerotoprod\DataModel\Describe;

class User
{
use DataModel;

public string $name;

#[Describe(['ignore' => true])]
public int $age;
}

0 comments on commit 759b38b

Please sign in to comment.