PHP가 동적 프로퍼티를 제거했습니다. 이유와 해결 방법

발행: (2025년 12월 18일 오전 02:44 GMT+9)
4 min read
원문: Dev.to

Source: Dev.to

위 링크에 있는 전체 글을 제공해 주시면, 해당 내용을 한국어로 번역해 드리겠습니다. (코드 블록, URL 및 마크다운 형식은 그대로 유지됩니다.)

동적 속성이란?

동적 속성은 클래스에 선언되지 않은 상태에서 객체에 추가되는 속성입니다.

class User {}

$user = new User();
$user->age = 30; // ⚠️ Dynamic property

The Problem

Silent Typos → Hidden Bugs

$user->custmer_email = 'john@example.com'; // Typo!
echo $user->customer_email; // null 😱

No Type Safety

$user->age = 30;        // int
$user->age = 'thirty'; // string – no error
$user->age = null;     // also "fine"

IDE & Tooling Limitations

  • 자동 완성 없음
  • 리팩터링 지원 없음
  • 정적 분석 도구가 문제를 감지하지 못함

Architecture Decay

// Anemic domain model
$user->cached_data = $something;
$user->temp_flag   = true;
$user->random_stuff = $whatever;

해결책: 속성을 명시적으로 선언하기

최신 PHP 방식

class User {
    public string $name;
    public int    $age;
    public bool   $isActive;
}

생성자 속성 승격 (PHP 8+)

class CreateOrderRequest {
    public function __construct(
        public readonly string $customerId,
        public readonly array  $items,
        public readonly ?string $promoCode = null,
    ) {}
}

Laravel‑특화 솔루션

❌ 정의되지 않은 속성을 직접 추가하지 마세요

$user = User::find(1);
$user->is_verified = true; // Undefined property!

✅ Fillable 속성과 Cast 정의하기

class User extends Model {
    protected $fillable = [
        'name',
        'email',
        'is_verified',
    ];

    protected $casts = [
        'is_verified'       => 'boolean',
        'email_verified_at'=> 'datetime',
    ];
}

✅ 접근자(Accessor) 사용하기

use Illuminate\Database\Eloquent\Casts\Attribute;

class User extends Model {
    protected function fullName(): Attribute {
        return Attribute::make(
            get: fn() => "{$this->first_name} {$this->last_name}",
        );
    }
}

탈출구 (절제해서 사용!)

#[\AllowDynamicProperties]
class LegacyCode {
    // Opt into old behavior
}

이것은 다음 경우에만 사용하세요:

  • 레거시 벤더 코드
  • 단계적 마이그레이션 (제거 계획 포함)

새로운 코드에는 절대 사용하지 마세요.

Migration Strategy

  1. Run Static Analysis

    ./vendor/bin/phpstan analyze --level 8
    ./vendor/bin/psalm --no-cache
  2. Fix Systematically

    • Critical – Domain models (fix now)
    • High – Controllers / Services (next sprint)
    • Medium – DTOs (incremental)
    • Low – Test utilities (add attribute if needed)
  3. Prevent Regression

    Add static analysis to CI:

    # .github/workflows/ci.yml
    - name: Static Analysis
      run: ./vendor/bin/phpstan analyze

Version Status

PHP 버전상태
8.1✅ 허용
8.2⚠️ 사용 중단 (경고)
9.0❌ 치명적 오류 (제거됨)

왜 이 변경이 중요한가

현대 PHP는 다음을 지향하고 있습니다:

  • 암시보다 명시
  • 기본적인 타입 안전성
  • 향상된 도구 지원
  • 리팩터링에 친화적인 코드

동적 프로퍼티는 이러한 목표와 충돌합니다.

실제 예시: 전과 후

이전 ❌

class ApiResponse {}

$response = new ApiResponse();
$response->data   = $data;
$response->status = 200;

이후 ✅

class ApiResponse {
    public function __construct(
        public readonly mixed $data,
        public readonly int   $status,
        public readonly int   $timestamp = time(),
    ) {}
}

$response = new ApiResponse($data, 200);

이점: 타입 안전, 자체 문서화, 불변성, 그리고 리팩터링에 용이함.

주요 내용

  • 모든 속성을 명시적으로 선언합니다.
  • 안전성을 위해 타입이 지정된 속성을 사용합니다.
  • PHP 8+ 기능(생성자 프로모션, readonly)을 활용합니다.
  • 정적 분석을 정기적으로 실행합니다.
  • 레거시 코드를 위한 마이그레이션 경로를 계획합니다.

전체 가이드—역사적 배경, 상세 마이그레이션 전략, 고급 Laravel 패턴 등—는 Medium 원문을 읽어보세요:

The Death of Dynamic Properties in PHP – A Deep Dive into Modern Object Design

Back to Blog

관련 글

더 보기 »

리팩토링 없이 레거시 Laravel 코드 테스트

실제 PHP 프로젝트를 위한 실용적인 전략 레거시 코드베이스는 삶의 일부입니다. 대부분 우리는 그린필드 프로젝트에 참여하지 않습니다. 우리는 애플리케이션을 물려받습니다.