<?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);를 파일 상단에 두면 타입이 엄격해져 버그를 줄일 수 있습니다.
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: 해당 클래스 내부에서만 접근
매개변수/반환 타입, 프로퍼티 타입을 꼭 선언하세요. 유지보수와 도구 지원이 좋아집니다.
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는 생성 이후 변경 불가(불변 값 객체에 유리).
도메인 규칙(통화 일치 등)은 메소드에서 강제하세요.
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 남용 지양).
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();
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().
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 주입 가능
인터페이스로 의존을 추상화하면 테스트/교체가 쉬워집니다.
프레임워크 없이도 생성자 주입 패턴을 쓰는 습관이 좋습니다.
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로 해결 가능.
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은 실제 호출한 하위 클래스를 따릅니다.
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 등이 있습니다.
과도하게 의존하면 디버깅이 어려워지므로 최소 사용을 권장합니다.
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를 쓰거나 클래스로 감싸 관리성을 높입니다.
$a = new User(1, 'A');
$b = $a;
var_dump($a === $b); // true (같은 인스턴스)
$c = clone $a; // 얕은 복제
var_dump($a === $c); // false
===는 같은 객체인지(동일 참조) 비교, ==는 프로퍼티 값 동등 비교.
복제 시 내부 참조가 있으면 깊은 복제를 직접 구현해야 합니다.
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; }
}
잘못된 입력은 예외로 막아 불변식을 지키세요.
퍼블릭 메소드는 클래스의 “계약”입니다. 입력·출력 타입과 예외를 문서화하세요.
클래스명 StudlyCaps, 메소드/프로퍼티 camelCase(PSR-12).
가능한 한 public 프로퍼티 금지, private + 메소드로 캡슐화.
상태가 없는 유틸은 정적 메소드보다 독립 함수가 종종 더 깔끔합니다.
도메인 규칙을 값 객체로 모델링하면 버그가 줄어듭니다.
단위 테스트하기 좋게 의존성은 생성자 주입으로.
<?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