phpexpertsinc/SimpleDTO

View on GitHub
README.md

Summary

Maintainability
Test Coverage
# SimpleDTO

[![TravisCI](https://travis-ci.com/phpexpertsinc/SimpleDTO.svg?branch=master)](https://travis-ci.com/phpexpertsinc/SimpleDTO)
[![Maintainability](https://api.codeclimate.com/v1/badges/503cba0c53eb262c947a/maintainability)](https://codeclimate.com/github/phpexpertsinc/SimpleDTO/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/503cba0c53eb262c947a/test_coverage)](https://codeclimate.com/github/phpexpertsinc/SimpleDTO/test_coverage)

SimpleDTO is a PHP Experts, Inc., Project meant to facilitate easy Data Transfer Objects.

Basically, any protected property on the DTO can be set as an array element passed in to
the __constructor and/or as a default value on the property itself.

The DTOs are immutable: Once created, they cannot be changed. Create a new object instead.

## Installation

Via Composer

```bash
composer require phpexperts/simple-dto
```

## Usage

As of version 2, you *must* define class-level @property docblocks for each one of your properties.

You also must define the data type.

```php
use Carbon\Carbon;
use PHPExperts\SimpleDTO\SimpleDTO;

/**
 * @property-read string $name
 * @property-read Carbon $date
 */
class BirthdayDTO extends SimpleDTO
{
    /** @var string */
    protected $name;
    
    /** @var Carbon */
    protected $date;
}

$birthdayDTO = new BirthdayDTO([
    'name' => 'Donald J. Trump',
    'date' => '1946-06-14',
]);

// Access as a property:
echo $birthday->name; // Donald J. Trump

// Properties with the data type of "Carbon" or "Carbon\Carbon" 
// are automagically converted to Carbon objects.
echo $birthday->date->format('F jS, Y'); // June 14th, 1946

// Easily output as an array:
$birthday->toArray();

// Copy from one to another:
$newDTO = new BirthdayDTO($birthdayDTO->toArray());

// Copy from one to another, with new properties:
$newDTO = new BirthdayDTO($birthdayDTO->toArray() + [
    'date' => '2020-11-03',
]);

// Easily output as JSON:
echo json_encode($birthdayDTO);
/* Output: 
{
    "name": "Donald J. Trump",
    "date": "1946-06-14T00:00:00.000000Z"
}
*/
```

### Fuzzy Data Types

But what if you aren't ready / able to dive into strict PHP data types yet?

Well, just instantiate the parent class like this:

```php
    use PHPExperts\DataTypeValidator\DataTypeValidator;
    use PHPExperts\DataTypeValidator\IsAFuzzyDataType;
    
    /**
     * @property int   $daysAlive
     * @property float $age
     * @property bool  $isHappy
     */
    class MyFuzzyDTO extends SimpleDTO
    {
        public function __construct(array $input)
        {
            parent::__construct($input, new DataTypeValidator(new IsAFuzzyDataType());
        }
    }

    $person = new MyFuzzyDTO([
        'daysAlive' => '5000',
        'age'       => '13.689',
        'isHappy'   => 1,
    ]);

    echo json_encode($person, JSON_PRETTY_PRINT);
    /*
    {
        "daysAlive": "5000",
        "age": "13.689",
        "isHappy": 1
    }
    */
```

### WriteOnce DTOs

Sometimes, you may need to initialize one or more values of a DTO after it has been created. This is particularly
common for stateful DTOs via multiple round-trips in certain APIs (particularly Zuora's).

To overcome the stateless nature of traditional Data Type Objects, you can use the `WriteOnce` trait.

This will enable you to initialize a DTO with *null* and *uninitialized* properties, and set them *once* and only.

Also, you must set every property before you can serialize or `json_encode()` the object, send it to `toArray()`, etc.

```php
/**
* @property string $name
*/
class CityDTO extends SimpleDTO
{
    use WriteOnce;

    protected int $population;
}

$cityDTO = new CityDTO(['name' => 'Dubai']);
dd($cityDTO);
```

### Ignore certain protected properties.

If you are using PHP 8.0 and above, you can have SimpleDTO ignore any particular `protected` property (PHP will treat it
like any regular protected property) using the `#[IgnoreAsDTO]` Attribute:

```php
$testDTO = new class(['name' => 'Sofia', 'birthYear' => 2010]) extends SimpleDTO {
    #[IgnoreAsDTO]
    protected int $age;

    protected string $name;
    protected int $birthYear;

    public function calcAge(): int
    {
        $this->age = date('Y') - $this->birthYear;

        return $this->age;
    }
};
```

### NestedDTOs

You can nest DTOs inside of each other. 

```php
    $myDTO = new MyTestDTO([
        'name' => 'PHP Experts, Inc.',
        'age'  => 7.01,
        'year' => 2019,
    ]);

    /**
     * @property MyTestDTO $myDTO
     */
    $dto = new class(['myDTO' => $myDTO]) extends NestedDTO
    {
    };
    
    /*
    PHPExperts\SimpleDTO\NestedDTO@anonymous {
      -dataTypeRules: array:1 [
        "myDTO" => "?MyTestDTO"
      ]
      -data: array:1 [
        "myDTO" => PHPExperts\SimpleDTO\Tests\MyTestDTO {#355
          -dataTypeRules: array:3 [
            "name" => "?string"
            "age" => "?float"
            "year" => "?int"
          ]
          -data: array:3 [
            "name" => "PHP Experts, Inc."
            "age" => 7.01
            "year" => 2019
          ]
        }
      ]
    }
    */
```

# Use cases
PHPExperts\SimpleDTO\SimpleDTO  
 ✔ Properties are set via the constructor  
 ✔ Properties are accessed as public properties  
 ✔ Constructor assigns default values of typed properties  
 ✔ Public, private and static protected properties will be ignored  
 ✔ Each DTO is immutable  
 ✔ Setting any property returns an exception  
 ✔ Concrete properties can be used to set default values  
 ✔ Properties with the type carbon become carbon dates  
 ✔ Can easily output to array  
 ✔ Can easily be JSON encoded  
 ✔ Can easily be JSON decoded  
 ✔ Nullable properties are allowed  
 ✔ Every property is nullable with permissive mode  
 ✔ Can be serialized  
 ✔ Can be unserialized  
 ✔ Extra validation can be added  
 ✔ Can get the internal data  
 ✔ Can identify if it is permissive or not  
 ✔ Can ignore protected properties with the #[IgnoreDTO] Attribute.

PHPExperts\SimpleDTO\NestedDTO  
 ✔ Will construct nested DTOs  
 ✔ Can construct arrays of nested DTOs  
 ✔ Can retrieve the stored DTOs.  
 ✔ Will convert array data into the appropriate Nested DTOs  
 ✔ Will convert stdClasses into the appropriate Nested DTOs  
 ✔ Nested DTOs use Loose typing  
 ✔ Nested DTOs can be built using Typed Properties  
 ✔ Nested DTOs with Typed Properties use Strict typing  
 ✔ All registered Nested DTOs are required  
 ✔ Optional, unregistered, Nested DTOs are handled gracefully  
 ✔ Can be serialized  
 ✔ Can be unserialized  
 ✔ Can validate the DTO manually  
 ✔ Can get the internal data

PHPExperts\SimpleDTO\WriteOnceTrait  
 ✔ Can accept null values  
 ✔ Can be serialized  
 ✔ Will validate on serialize  
 ✔ Will validate on to array  
 ✔ Can write each null value once  
 ✔ Write-Once values must validate  

SimpleDTO Sad Paths  
 ✔ Cannot initialize with a nonexisting property  
 ✔ Accessing a nonexisting property throws an error  
 ✔ A DTO must have class property docblocks -or- typehint for each concrete property  
 ✔ Carbon date strings must be parsable dates  
 ✔ Properties must match their data types  
 ✔ Will not unserialize DTOs with invalid data  
 ✔ Cannot overwrite a non-existing property  

## Testing

```bash
phpunit --testdox
```

# Contributors

[Theodore R. Smith](https://www.phpexperts.pro/]) <theodore@phpexperts.pro>  
GPG Fingerprint: 4BF8 2613 1C34 87AC D28F  2AD8 EB24 A91D D612 5690  
CEO: PHP Experts, Inc.

## License

MIT license. Please see the [license file](LICENSE) for more information.