PHP code example of diego-ninja / granite

1. Go to this page and download the library: Download diego-ninja/granite 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/ */

    

diego-ninja / granite example snippets




use Ninja\Granite\GraniteVO;
use Ninja\Granite\Validation\Attributes\Required;
use Ninja\Granite\Validation\Attributes\Email;
use Ninja\Granite\Validation\Attributes\Min;
use Ninja\Granite\Serialization\Attributes\SerializedName;
use Ninja\Granite\Serialization\Attributes\Hidden;

// Create a Value Object with validation
final readonly class User extends GraniteVO
{
    public function __construct(
        public ?int $id,
        
        #[Required]
        #[Min(2)]
        public string $name,
        
        #[Required]
        #[Email]
        public string $email,
        
        #[Hidden] // Won't appear in JSON
        public ?string $password = null,
        
        #[SerializedName('created_at')]
        public DateTime $createdAt = new DateTime()
    ) {}
}

// Create and validate
$user = User::from([
    'name' => 'John Doe',
    'email' => '[email protected]',
    'password' => 'secret123'
]);

// Immutable updates
$updatedUser = $user->with(['name' => 'Jane Doe']);

// Serialization
$json = $user->json();
// {"id":null,"name":"John Doe","email":"[email protected]","created_at":"2024-01-15T10:30:00+00:00"}

$array = $user->array();
// password is hidden, created_at uses custom name

// Request validation
final readonly class CreateUserRequest extends GraniteVO
{
    public function __construct(
        #[Required]
        #[StringType]
        #[Min(2)]
        public string $name,
        
        #[Required]
        #[Email]
        public string $email,
        
        #[Required]
        #[Min(8)]
        #[Regex('/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/', 'Password must contain uppercase, lowercase, and number')]
        public string $password
    ) {}
}

// API Response
final readonly class UserResponse extends GraniteDTO
{
    public function __construct(
        public int $id,
        public string $name,
        public string $email,
        
        #[SerializedName('member_since')]
        public DateTime $createdAt
    ) {}
    
    public static function fromEntity(User $user): self
    {
        return new self(
            id: $user->id,
            name: $user->name,
            email: $user->email,
            createdAt: $user->createdAt
        );
    }
}

// Value Objects
final readonly class Money extends GraniteVO
{
    public function __construct(
        #[Required]
        #[NumberType]
        #[Min(0)]
        public float $amount,
        
        #[Required]
        #[StringType]
        #[Min(3)]
        #[Max(3)]
        public string $currency
    ) {}
    
    public function add(Money $other): Money
    {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException('Cannot add different currencies');
        }
        
        return new Money($this->amount + $other->amount, $this->currency);
    }
}

// Aggregates
final readonly class Order extends GraniteVO
{
    public function __construct(
        public ?int $id,
        
        #[Required]
        public int $customerId,
        
        #[Required]
        #[ArrayType]
        #[Each(new Rules\Callback(fn($item) => OrderItem::from($item)))]
        public array $items,
        
        #[Required]
        public OrderStatus $status = OrderStatus::PENDING
    ) {}
    
    public function getTotal(): Money
    {
        $total = new Money(0.0, 'USD');
        foreach ($this->items as $item) {
            $total = $total->add($item->getTotal());
        }
        return $total;
    }
}

use Ninja\Granite\Mapping\ObjectMapper;
use Ninja\Granite\Mapping\ObjectMapperConfig;
use Ninja\Granite\Mapping\Attributes\MapFrom;

// Source entity
final readonly class UserEntity extends GraniteDTO
{
    public function __construct(
        public int $userId,
        public string $fullName,
        public string $emailAddress,
        public DateTime $createdAt
    ) {}
}

// Destination DTO with mapping attributes
final readonly class UserSummary extends GraniteDTO
{
    public function __construct(
        #[MapFrom('userId')]
        public int $id,
        
        #[MapFrom('fullName')]
        public string $name,
        
        #[MapFrom('emailAddress')]
        public string $email
    ) {}
}

// Create ObjectMapper with clean configuration
$mapper = new ObjectMapper(
    ObjectMapperConfig::forProduction()
        ->withConventions(true, 0.8)
        ->withSharedCache()
);

// Automatic mapping
$summary = $mapper->map($userEntity, UserSummary::class);

// Automatically maps between different naming conventions
class SourceClass {
    public string $firstName;     // camelCase
    public string $email_address; // snake_case
    public string $UserID;        // PascalCase
}

class DestinationClass {
    public string $first_name;    // snake_case
    public string $emailAddress;  // camelCase  
    public string $user_id;       // snake_case
}

$mapper = new ObjectMapper(
    ObjectMapperConfig::create()
        ->withConventions(true, 0.8)
);

$result = $mapper->map($source, DestinationClass::class);
// Properties automatically mapped based on naming conventions!

use Ninja\Granite\Mapping\ObjectMapperConfig;
use Ninja\Granite\Mapping\MappingProfile;

// Fluent configuration with builder pattern
$mapper = new ObjectMapper(
    ObjectMapperConfig::forProduction()
        ->withSharedCache()
        ->withConventions(true, 0.8)
        ->withProfile(new UserMappingProfile())
        ->withProfile(new ProductMappingProfile())
        ->withWarmup()
);

// Predefined configurations
$devMapper = new ObjectMapper(ObjectMapperConfig::forDevelopment());
$prodMapper = new ObjectMapper(ObjectMapperConfig::forProduction());
$testMapper = new ObjectMapper(ObjectMapperConfig::forTesting());

use Ninja\Granite\Mapping\MappingProfile;

class UserMappingProfile extends MappingProfile
{
    protected function configure(): void
    {
        // Complex transformations
        $this->createMap(UserEntity::class, UserResponse::class)
            ->forMember('fullName', fn($m) => 
                $m->using(function($value, $sourceData) {
                    return $sourceData['firstName'] . ' ' . $sourceData['lastName'];
                })
            )
            ->forMember('age', fn($m) => 
                $m->mapFrom('birthDate')
                  ->using(fn($birthDate) => (new DateTime())->diff($birthDate)->y)
            )
            ->forMember('isActive', fn($m) => 
                $m->mapFrom('status')
                  ->using(fn($status) => $status === 'active')
            )
            ->seal();

        // Bidirectional mapping
        $this->createMapBidirectional(UserEntity::class, UserDto::class)
            ->forMembers('userId', 'id')
            ->forMembers('fullName', 'name')
            ->forMembers('emailAddress', 'email')
            ->seal();
    }
}

final readonly class CreditCard extends GraniteVO
{
    public function __construct(
        #[Required]
        #[Regex('/^\d{4}\s?\d{4}\s?\d{4}\s?\d{4}$/', 'Invalid card number format')]
        public string $number,
        
        #[Required]
        #[Regex('/^(0[1-9]|1[0-2])\/([0-9]{2})$/', 'Invalid expiry format (MM/YY)')]
        public string $expiry,
        
        #[Required]
        #[Regex('/^\d{3,4}$/', 'Invalid CVV')]
        public string $cvv,
        
        #[When(
            condition: fn($value, $data) => $data['type'] === 'business',
            rule: new Required()
        )]
        public ?string $companyName = null
    ) {}
    
    protected static function rules(): array
    {
        return [
            'number' => [
                new Callback(function($number) {
                    // Luhn algorithm validation
                    return $this->isValidLuhn(str_replace(' ', '', $number));
                }, 'Invalid credit card number')
            ]
        ];
    }
}

// Configure once at application startup
ObjectMapper::configure(function(ObjectMapperConfig $config) {
    $config->withSharedCache()
           ->withConventions(true, 0.8)
           ->withProfiles([
               new UserMappingProfile(),
               new ProductMappingProfile(),
               new OrderMappingProfile()
           ])
           ->withWarmup();
});

// Use anywhere in your application
$mapper = ObjectMapper::getInstance();
$userDto = $mapper->map($userEntity, UserDto::class);

// Use shared cache for web applications
$mapper = new ObjectMapper(
    ObjectMapperConfig::forProduction()
        ->withSharedCache()
        ->withWarmup()  // Preload configurations
);

// Preload mappings for better performance
use Ninja\Granite\Mapping\MappingPreloader;

MappingPreloader::preload($mapper, [
    [UserEntity::class, UserResponse::class],
    [ProductEntity::class, ProductResponse::class]
]);

class UserTest extends PHPUnit\Framework\TestCase
{
    public function testUserCreation(): void
    {
        $user = User::from([
            'name' => 'John Doe',
            'email' => '[email protected]'
        ]);
        
        $this->assertEquals('John Doe', $user->name);
        $this->assertEquals('[email protected]', $user->email);
    }
    
    public function testObjectMapping(): void
    {
        $mapper = new ObjectMapper(ObjectMapperConfig::forTesting());
        
        $entity = new UserEntity(1, 'John Doe', '[email protected]', new DateTime());
        $dto = $mapper->map($entity, UserDto::class);
        
        $this->assertInstanceOf(UserDto::class, $dto);
        $this->assertEquals(1, $dto->id);
    }
    
    public function testImmutability(): void
    {
        $user = User::from(['name' => 'John', 'email' => '[email protected]']);
        $updated = $user->with(['name' => 'Jane']);
        
        // Original unchanged
        $this->assertEquals('John', $user->name);
        // New instance created
        $this->assertEquals('Jane', $updated->name);
    }
}