PHP code example of zero-to-prod / data-model

1. Go to this page and download the library: Download zero-to-prod/data-model library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

zero-to-prod / data-model example snippets


class User
{
    use \Zerotoprod\DataModel\DataModel;

    public string $name;
    public int $age;
}

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

#[\Zerotoprod\DataModel\Describe([
    'from'     => 'key',                          // Remap: read this context key instead of property name. Callable OK
    'pre'      => [self::class, 'hook'],           // Pre-hook: void callable, runs before cast
    'cast'     => [self::class, 'method'],         // Cast: callable, returns resolved value
    'post'     => [self::class, 'hook'],           // Post-hook: void callable, runs after cast
    'default'  => 'value',                         // Default: used when context key absent. Callable OK
    'assign'   => 'value',                         // Assign: always set; context ignored. Callable OK
    '

> #[Describe([
>     Describe::default => static function (): array {
>         return myFunction();
>     }
> ])]
> public array $property;
> 

class User
{
    use \Zerotoprod\DataModel\DataModel;

    public string $name;
    public int $age;
}

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

class Address
{
    use \Zerotoprod\DataModel\DataModel;

    public string $street;
    public string $city;
}

class User
{
    use \Zerotoprod\DataModel\DataModel;

    public string $username;
    public Address $address;
}

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

echo $User->address->city; // 'Hometown'

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['cast' => [self::class, 'firstName'], 'function' => 'strtoupper'])]
    // Or with first-class callable (PHP 8.5+):
    // #[Describe(['cast' => self::firstName(...), 'function' => 'strtoupper'])]
    public string $first_name;

    #[Describe(['cast' => 'uppercase'])]
    public string $last_name;

    #[Describe(['cast' => [self::class, 'fullName']])]
    // Or: #[Describe(['cast' => self::fullName(...)])]
    public string $full_name;

    private static function firstName(mixed $value, array $context, ?\ReflectionAttribute $ReflectionAttribute, \ReflectionProperty $ReflectionProperty): string
    {
        return $ReflectionAttribute->getArguments()[0]['function']($value);
    }

    public static function fullName(mixed $value, array $context, ?\ReflectionAttribute $Attribute, \ReflectionProperty $Property): string
    {
        return "{$context['first_name']} {$context['last_name']}";
    }
}

function uppercase(mixed $value, array $context){
    return strtoupper($value);
}

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

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

#[Describe(['cast' => static function ($value): string {
    return strtoupper($value);
}])]
public string $last_name;

use Zerotoprod\DataModel\Describe;

class BaseClass
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['pre' => [self::class, 'pre'], 'message' => 'Value too large.'])]
    public int $int;

    public static function pre(mixed $value, array $context, ?\ReflectionAttribute $Attribute, \ReflectionProperty $Property): void
    {
        if ($value > 10) {
            throw new \RuntimeException($Attribute->getArguments()[0]['message']);
        }
    }
}

#[Describe(['pre' => static function (mixed $value): void {
    if ($value > 10) {
        throw new \RuntimeException('Value too large.');
    }
}])]
public int $int;

use Zerotoprod\DataModel\Describe;

class BaseClass
{
    use \Zerotoprod\DataModel\DataModel;

    public const int = 'int';

    #[Describe(['post' => [self::class, 'post'], 'message' => 'Value too large.'])]
    public int $int;

    public static function post(mixed $value, array $context, ?\ReflectionAttribute $Attribute, \ReflectionProperty $Property): void
    {
        if ($value > 10) {
            throw new \RuntimeException($value.$Attribute->getArguments()[0]['message']);
        }
    }
}

#[Describe(['post' => static function (mixed $value): void {
    if ($value > 10) {
        throw new \RuntimeException($value.' Value too large.');
    }
}])]
public int $int;

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    public string $first_name;
    public string $last_name;
    public string $fullName;

    #[Describe('last_name')]
    public function lastName(mixed $value, array $context, ?\ReflectionAttribute $Attribute, \ReflectionProperty $Property): string
    {
        return strtoupper($value);
    }

    #[Describe('fullName')]
    public function fullName(mixed $value, array $context, ?\ReflectionAttribute $Attribute, \ReflectionProperty $Property): string
    {
        return "{$context['first_name']} {$context['last_name']}";
    }
}

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

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

use Zerotoprod\DataModel\Describe;

function uppercase(mixed $value, array $context){
    return strtoupper($value);
}

#[Describe([
    'cast' => [
        'string' => 'uppercase',
        \DateTimeImmutable::class => [self::class, 'toDateTimeImmutable'],
    ]
])]
class User
{
    use \Zerotoprod\DataModel\DataModel;

    public string $first_name;
    public DateTimeImmutable $registered;

    public static function toDateTimeImmutable(mixed $value, array $context): DateTimeImmutable
    {
        return new DateTimeImmutable($value);
    }
}

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

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

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['Throws PropertyRequiredException: Property `$username` is 

#[Describe(['rue;
}])]
public string $username;

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['default' => 'N/A'])]
    public string $username;

    #[Describe(['default' => [self::class, 'newCollection']])]
    public Collection $username;

    public static function newCollection(): Collection
    {
        return new Collection();
    }
}

$User = User::from();

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

#[Describe(['default' => static function (): array {
    return myFunction();
}])]
public array $property;

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['assign' => ['role' => 'admin']])]
    public array $config;
}

$User = User::from();
// $User->config === ['role' => 'admin']

$User = User::from(['config' => ['role' => 'guest']]);
// $User->config === ['role' => 'admin']  (context value ignored)

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['assign' => [self::class, 'account']])]
    public string $account;

    public static function account($value, array $context): string
    {
        return 'service-account';
    }
}

$User = User::from(['account' => 'other']);
// $User->account === 'service-account'  (context value ignored)

#[Describe(['assign' => static function (): string {
    return 'service-account';
}])]
public string $account;

use Zerotoprod\DataModel\Describe;

#[Describe(['nullable' => true])]
class User
{
    use \Zerotoprod\DataModel\DataModel;

    public ?string $name;

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

$User = User::from();

echo $User->name; // null
echo $User->age;  // null

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

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

$User = User::from([
    'firstName' => 'John',
]);

echo $User->first_name; // 'John'

#[Describe(['from' => static function (string $propertyName, array $context): string {
    return isset($context['firstName']) ? 'firstName' : 'legacyFirstName';
}])]
public string $first_name;

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    public string $name;

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

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

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

#[Describe(['ignore' => static function (): bool {
    return true;
}])]
public int $age;

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['cast' => [self::class, 'firstName'], 'function' => 'strtoupper'])]
    public string $first_name;

    private static function firstName(
        mixed $value,
        array $context,
        ?\ReflectionAttribute $Attribute,
        \ReflectionProperty $Property
    ): string
    {
        // Access via reflection (still works)
        $fn = $Attribute->getArguments()[0]['function'];

        // Or access via extra (no reflection needed)
        $Describe = $Attribute->newInstance();
        $fn = $Describe->extra['function'];

        return $fn($value);
    }
}

class User
{
    use \Zerotoprod\DataModel\DataModel;

    public string $name;

    public function __construct(array $data = [])
    {
        self::from($data, $this);
    }
}

$User = new User([
    'name' => 'Jane Doe',
]);

echo $User->name; // 'Jane Doe';

use Zerotoprod\DataModel\Describe;

class BaseClass
{
    use DataModel;

    #[Describe(['via' => 'via'])]
    public ChildClass $ChildClass;

    #[Describe(['via' => [ChildClass::class, 'via']])]
    public ChildClass $ChildClass2;
}

class ChildClass
{
    public function __construct(public int $int)
    {
    }

    public static function via(array $context): self
    {
        return new self($context[self::int]);
    }
}

$BaseClass = BaseClass::from([
    'ChildClass' => ['int' => 1],
    'ChildClass2' => ['int' => 1],
]);

$BaseClass->ChildClass->int;  // 1
$BaseClass->ChildClass2->int; // 1

namespace App\DataModels;

trait DataModel
{
    use \Zerotoprod\DataModel\DataModel;

    public function toArray(): array
    {
        return collect($this)->toArray();
    }
}

use Attribute;
use Zerotoprod\DataModel\Describe;

#[Attribute]
class MyDescribe extends Describe {}

readonly class Config
{
    use \Zerotoprod\DataModel\DataModel;

    #[MyDescribe(['default' => 'fallback'])]
    public string $name;

    #[MyDescribe(['nullable' => true])]
    public ?string $label;
}

$Config = Config::from();

echo $Config->name;  // 'fallback'
echo $Config->label; // null

class User
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe(['default' => 'guest'])]
    public string $role;

    #[Describe(['nullable' => true])]
    public ?string $name;
}

$User = User::from('any_string');

echo $User->role; // 'guest'
echo $User->name; // null

$UserDataModel = UserDataModel::from($user->toArray());

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;
    use \Zerotoprod\DataModelHelper\DataModelHelper;

    /** @var Alias[] $Aliases */
    #[Describe([
        'cast' => [self::class, 'mapOf'],   // Use the mapOf helper method
        // 'cast' => self::mapOf(...),       // Or use first-class callable (PHP 8.5+)
        'type' => Alias::class,             // Target type for each item
    ])]
    public array $Aliases;
}

class Alias
{
    use \Zerotoprod\DataModel\DataModel;

    public string $name;
}

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

echo $User->Aliases[0]->name; // 'John Doe'
echo $User->Aliases[1]->name; // 'John Smith'

use Zerotoprod\DataModel\Describe;

class User
{
    use \Zerotoprod\DataModel\DataModel;
    use \Zerotoprod\DataModelHelper\DataModelHelper;

    /** @var Collection<int, Alias> $Aliases */
    #[Describe([
        'cast' => [self::class, 'mapOf'],   // Or: self::mapOf(...) on PHP 8.5+
        'type' => Alias::class,
    ])]
    public \Illuminate\Support\Collection $Aliases;
}

class Alias
{
    use \Zerotoprod\DataModel\DataModel;

    public string $name;
}

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

echo $User->Aliases->first()->name; // 'John Doe'

use Illuminate\Support\Facades\Validator;
use Zerotoprod\DataModel\Describe;

readonly class FullName
{
    use \Zerotoprod\DataModel\DataModel;

    #[Describe([
        'pre' => [self::class, 'validate'],
        'rule' => 'min:2'
    ])]
    public string $first_name;

    public static function validate(mixed $value, array $context, ?\ReflectionAttribute $Attribute): void
    {
        $validator = Validator::make(['value' => $value], ['value' => $Attribute?->getArguments()[0]['rule']]);
        if ($validator->fails()) {
            throw new \RuntimeException($validator->errors()->toJson());
        }
    }
}