<?php

namespace App\DTOs;

use ReflectionClass;
use ReflectionNamedType;

abstract class CommonDTO
{
  protected function __construct(array $datas = [])
  {
    // 데이터가 없으면 바로 리턴
    if (empty($datas)) {
      return;
    }

    // $this는 이 시점에 자식 클래스(AccountDTO)의 인스턴스입니다.
    $reflection = new ReflectionClass($this);

    foreach ($datas as $key => $value) {
      // 1. 속성이 존재하는지 확인
      if (!$reflection->hasProperty($key)) {
        continue;
      }

      $property = $reflection->getProperty($key);

      // public이 아닌 속성도 접근 가능하게 설정 (필요시)
      // $property->setAccessible(true); 

      // 2. 속성의 타입 정보를 가져옴
      $type = $property->getType();

      // 값을 할당하기 전 정제된 값을 담을 변수
      $assignValue = $value;

      // 3. 빈 문자열("") 처리
      // HTML 폼에서 온 빈 값은 null로 처리해야 ?int 등에 들어갈 수 있음
      if ($value === '') {
        $assignValue = null;
      }
      // 4. 타입에 따른 강제 형변환 (Casting)
      elseif ($type instanceof ReflectionNamedType) {
        $typeName = $type->getName();

        // int 타입이고 값이 숫자형일 때
        if ($typeName === 'int' && is_numeric($value)) {
          $assignValue = (int) $value;
        }
        // float 타입이고 값이 숫자형일 때
        elseif ($typeName === 'float' && is_numeric($value)) {
          $assignValue = (float) $value;
        }
        // 필요하다면 bool 처리 등 추가 가능
      }

      // 5. 값 할당
      $this->{$key} = $assignValue;
    }
  }

  public function __get(string $name)
  {
    // 1. 속성이 클래스에 실제로 존재하는지 확인
    if (property_exists($this, $name)) {
      // 2. 속성이 존재하면 값을 반환 (PHP 7.4 이상의 타입 속성 덕분에 null 병합 연산자 사용 불필요)
      return $this->{$name};
    }
    // 3. 속성이 존재하지 않으면 오류 발생 (일반적인 Entity/DTO 접근 방식)
    throw new \BadMethodCallException(
      "Undefined property or method: " . static::class . "::{$name}"
    );
  }
  final public function toArray(): array
  {
    // $this가 가진 모든 public 프로퍼티와 그 값을 배열로 반환합니다.
    return get_object_vars($this);
  }
}
