IMG-LOGO
공지사항 :

PHP에서 클래스 정의

lmkfox - 2025-08-25 07:31:53 21 Views 0 Comment

1) 가장 기본: 클래스, 인스턴스, 메소드

<?php
declare(strict_types=1);

class Greeter
{
    // 프로퍼티(상태)
    private string $prefix = 'Hello';

    // 메소드(행위)
    public function greet(string $name): string
    {
        return "{$this->prefix}, {$name}";
    }
}

$g = new Greeter();          // 인스턴스 생성
echo $g->greet('World');     // "Hello, World"

요점

  • class로 타입(설계도)을 정의하고, new로 인스턴스(실제 객체)를 만듭니다.

  • 메소드 안에서 자기 자신에 접근할 때는 $this를 사용합니다.

  • declare(strict_types=1);를 파일 상단에 두면 타입이 엄격해져 버그를 줄일 수 있습니다.

2) 접근 제어자(가시성)와 타입 선언

class User
{
    public function __construct(
        private int $id,
        private string $name,        // 생성자 프로퍼티 승격(8.0+)
    ) {}

    public function id(): int { return $this->id; }     // 게터
    public function rename(string $to): void { $this->name = $to; }
    public function name(): string { return $this->name; }
}

  • public: 어디서나 접근 가능

  • protected: 자신과 자식 클래스에서만 접근

  • private: 해당 클래스 내부에서만 접근

  • 매개변수/반환 타입, 프로퍼티 타입을 꼭 선언하세요. 유지보수와 도구 지원이 좋아집니다.

3) 생성자, 프로퍼티, 불변(immutable) 설계

final class Money
{
    public function __construct(
        public readonly int $amount,   // 8.1+: readonly 프로퍼티
        public readonly string $currency
    ) {}

    public function add(Money $other): Money
    {
        if ($this->currency !== $other->currency) {
            throw new InvalidArgumentException('Currency mismatch');
        }
        return new Money($this->amount + $other->amount, $this->currency);
    }
}

  • __construct는 인스턴스 초기화.

  • readonly는 생성 이후 변경 불가(불변 값 객체에 유리).

  • 도메인 규칙(통화 일치 등)은 메소드에서 강제하세요.

4) 정적 멤버와 클래스 상수

class MathUtil
{
    public const PI = 3.1415926535;

    public static function circleArea(float $r): float
    {
        return self::PI * $r * $r;
    }
}

echo MathUtil::circleArea(2.0);

  • 정적 메소드/프로퍼티는 인스턴스 없이 ClassName::method()로 호출.

  • 상태가 필요 없고 순수 계산일 때만 제한적으로 사용하세요(과도한 static 남용 지양).

5) 메소드 체이닝(Fluent 인터페이스)

class Query
{
    private array $where = [];
    private ?int $limit = null;

    public function where(string $cond): self
    {
        $this->where[] = $cond;
        return $this;                 // 체이닝 포인트
    }

    public function limit(int $n): self
    {
        $this->limit = $n;
        return $this;
    }

    public function toSql(): string
    {
        $sql = 'SELECT * FROM t';
        if ($this->where) $sql .= ' WHERE ' . implode(' AND ', $this->where);
        if ($this->limit !== null) $sql .= " LIMIT {$this->limit}";
        return $sql;
    }
}

$sql = (new Query())->where('age >= 20')->where('active = 1')->limit(10)->toSql();

6) 상속, 오버라이드, 

parent

abstract class Notifier
{
    abstract public function send(string $msg): void; // 추상 메소드
}

class EmailNotifier extends Notifier
{
    public function __construct(private string $to) {}

    public function send(string $msg): void
    {
        // 이메일 발송 로직
    }
}

class SmsNotifier extends Notifier
{
    public function __construct(private string $phone) {}

    public function send(string $msg): void
    {
        // SMS 발송 로직
    }
}

  • extends로 상속, abstract로 공통 인터페이스 강제.

  • 오버라이드 시 시그니처(매개변수/반환타입)를 호환되게 유지하세요.

  • 부모 메소드를 함께 쓰고 싶으면 parent::method().

7) 인터페이스와 다형성, 의존성 주입

interface Clock { public function now(): DateTimeImmutable; }

final class SystemClock implements Clock {
    public function now(): DateTimeImmutable { return new DateTimeImmutable(); }
}

final class GreetingService
{
    public function __construct(private Clock $clock) {}

    public function greet(string $name): string
    {
        $hour = (int)$this->clock->now()->format('G');
        $prefix = $hour < 12 ? 'Good morning' : 'Hello';
        return "{$prefix}, {$name}";
    }
}

$svc = new GreetingService(new SystemClock()); // 테스트에선 FakeClock 주입 가능

  • 인터페이스로 의존을 추상화하면 테스트/교체가 쉬워집니다.

  • 프레임워크 없이도 생성자 주입 패턴을 쓰는 습관이 좋습니다.

8) 트레이트로 코드 재사용

trait Timestamps
{
    private ?DateTimeImmutable $createdAt = null;

    public function touch(): void
    {
        $this->createdAt = new DateTimeImmutable();
    }

    public function createdAt(): ?DateTimeImmutable { return $this->createdAt; }
}

class Post { use Timestamps; }

  • trait는 다중 상속 대신 메소드/프로퍼티 묶음을 주입하는 도구.

  • 트레이트끼리 충돌 시 insteadof, as로 해결 가능.

9) 

self

 vs 

static

(늦은 정적 바인딩)

class Base
{
    public static function make(): static   // 8.0+: static 반환 타입
    {
        return new static();                // 호출한 클래스 타입으로 생성
    }

    public function who(): string { return self::class; }
}

class Sub extends Base
{
    public function who(): string { return static::class; }
}

echo Base::make()->who(); // "Base"
echo Sub::make()->who();  // "Sub"

  • self는 정의된 클래스 고정, static은 실제 호출한 하위 클래스를 따릅니다.

10) 매직 메소드(필요할 때만)

class Box
{
    public function __construct(private array $items = []) {}

    public function __toString(): string { return json_encode($this->items); }
    public function __get(string $name)   { /* 동적 접근 처리 */ }
    public function __set(string $name, $value) { /* 동적 설정 처리 */ }
}

  • __construct, __destruct, __get/__set, __call/__callStatic, __toString, __clone 등이 있습니다.

  • 과도하게 의존하면 디버깅이 어려워지므로 최소 사용을 권장합니다.

11) 네임스페이스와 오토로딩(PSR-4)

src/
  Domain/
    User.php      // namespace App\Domain;
<?php
namespace App\Domain;

final class User { /* ... */ }

composer.json

{
  "autoload": { "psr-4": { "App\\": "src/" } }
}

  • composer dump-autoload 후 use App\Domain\User;로 클래스 자동 로딩.

  • 함수만 담긴 파일은 autoload.files를 쓰거나 클래스로 감싸 관리성을 높입니다.

12) 객체 비교, 복제, 불변 컬렉션

$a = new User(1, 'A');
$b = $a;
var_dump($a === $b);   // true (같은 인스턴스)

$c = clone $a;         // 얕은 복제
var_dump($a === $c);   // false

  • ===는 같은 객체인지(동일 참조) 비교, ==는 프로퍼티 값 동등 비교.

  • 복제 시 내부 참조가 있으면 깊은 복제를 직접 구현해야 합니다.

13) 예외 처리와 계약 보증

class BankAccount
{
    public function __construct(private int $balance = 0) {}

    public function deposit(int $amount): void
    {
        if ($amount <= 0) throw new InvalidArgumentException('amount > 0');
        $this->balance += $amount;
    }

    public function withdraw(int $amount): void
    {
        if ($amount <= 0) throw new InvalidArgumentException('amount > 0');
        if ($amount > $this->balance) throw new RuntimeException('insufficient funds');
        $this->balance -= $amount;
    }

    public function balance(): int { return $this->balance; }
}

  • 잘못된 입력은 예외로 막아 불변식을 지키세요.

  • 퍼블릭 메소드는 클래스의 “계약”입니다. 입력·출력 타입과 예외를 문서화하세요.

14) 코딩 규칙과 실무 팁

  • 클래스명 StudlyCaps, 메소드/프로퍼티 camelCase(PSR-12).

  • 가능한 한 public 프로퍼티 금지, private + 메소드로 캡슐화.

  • 상태가 없는 유틸은 정적 메소드보다 독립 함수가 종종 더 깔끔합니다.

  • 도메인 규칙을 값 객체로 모델링하면 버그가 줄어듭니다.

  • 단위 테스트하기 좋게 의존성은 생성자 주입으로.

15) 한 번에 복습하는 예제

<?php
declare(strict_types=1);

namespace App\Billing;

interface DiscountRule { public function apply(int $price): int; }

final class PercentOff implements DiscountRule
{
    public function __construct(private float $rate) {} // 0.1 = 10%
    public function apply(int $price): int { return (int) round($price * (1 - $this->rate)); }
}

final class Cart
{
    /** @var list<int> */
    private array $items = [];

    public function addItem(int $price): self
    {
        if ($price < 0) throw new \InvalidArgumentException('price >= 0');
        $this->items[] = $price;
        return $this;
    }

    public function total(): int { return array_sum($this->items); }

    public function checkout(DiscountRule $rule): int
    {
        return max(0, $rule->apply($this->total()));
    }
}

// 사용
use App\Billing\{Cart, PercentOff};
$cart = (new Cart())->addItem(12000)->addItem(8000);
echo $cart->checkout(new PercentOff(0.1)); // 18000

댓글