PHP 클래스: 기초
PHP 클래스: 기초 (The Basics)
원문: PHP 공식 매뉴얼 — Language Reference > Classes and Objects > The Basics 이 문서는 PHP 공식 매뉴얼
language.oop5.basic페이지를 한국어로 옮긴 내용이에요.
PHP로 객체지향 코드를 짜기 시작하면 가장 먼저 만나는 게 클래스예요. 클래스는 데이터와 그 데이터를 다루는 동작을 한 덩어리로 묶는 틀인데, 이 페이지에서는 클래스를 정의하고 객체를 만드는 가장 기본적인 문법을 차근차근 살펴볼게요.
class
기본적인 클래스 정의는 class 키워드로 시작해요. 그 뒤에 클래스 이름이 오고, 중괄호 한 쌍이 그 클래스에 속한 속성(property)과 메서드(method)의 정의를 감싸줘요.
클래스 이름은 PHP의 예약어만 아니라면 유효한 라벨이면 뭐든 쓸 수 있어요. 다만 PHP 8.4.0부터 밑줄 하나짜리 _를 클래스 이름으로 쓰는 건 deprecated 처리됐고요. 유효한 클래스 이름은 문자나 밑줄로 시작해서, 그 뒤에 문자·숫자·밑줄이 몇 개든 이어질 수 있어요. 정규식으로 나타내면 이렇게 돼요.
^[a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*$
클래스는 자기만의 상수, 변수(속성이라고 불러요), 함수(메서드라고 불러요)를 가질 수 있어요.
예제 #1 — 간단한 클래스 정의
<?php
class SimpleClass
{
// property declaration
public $var = 'a default value';
// method declaration
public function displayVar() {
echo $this->var;
}
}
?>
객체 컨텍스트 안에서 메서드를 호출하면 의사 변수(pseudo-variable) $this를 쓸 수 있어요. $this는 바로 그 호출을 하고 있는 객체 자신을 가리켜요.
⚠️ 경고
비정적(non-static) 메서드를 정적으로 호출하면
Error가 던져져요. PHP 8.0.0 이전에는 이 상황에서 deprecation 알림이 나오고$this는 undefined가 됐었어요.
예제 #2 — $this 의사 변수의 여러 쓰임
<?php
class A
{
function foo()
{
if (isset($this)) {
echo '$this is defined (';
echo get_class($this);
echo ")\n";
} else {
echo "\$this is not defined.\n";
}
}
}
class B
{
function bar()
{
A::foo();
}
}
$a = new A();
$a->foo();
A::foo();
$b = new B();
$b->bar();
B::bar();
?>
위 예제의 PHP 7 기준 출력:
$this is defined (A)
Deprecated: Non-static method A::foo() should not be called statically in %s on line 27
$this is not defined.
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
$this is not defined.
Deprecated: Non-static method B::bar() should not be called statically in %s on line 32
Deprecated: Non-static method A::foo() should not be called statically in %s on line 20
$this is not defined.
위 예제의 PHP 8 기준 출력:
$this is defined (A)
Fatal error: Uncaught Error: Non-static method A::foo() cannot be called statically in %s :27
Stack trace:
#0 {main}
thrown in %s on line 27
읽기 전용 클래스 (Readonly classes)
PHP 8.2.0부터 클래스에 readonly 수정자를 붙일 수 있어요. 클래스를 readonly로 표시하면 선언된 모든 속성에 readonly 수정자가 붙고, 동적(dynamic) 속성은 만들 수 없게 돼요. 게다가 AllowDynamicProperties 속성을 써서 동적 속성을 지원하는 것도 불가능하고, 그렇게 시도하면 컴파일 타임 에러가 나요.
<?php
#[\AllowDynamicProperties]
readonly class Foo {
}
// Fatal error: Cannot apply #[AllowDynamicProperties] to readonly class Foo
?>
readonly 수정자는 타입 없는 속성이나 정적(static) 속성에는 붙일 수 없으니까, readonly 클래스도 그런 속성을 선언할 수 없어요.
<?php
readonly class Foo
{
public $bar;
}
// Fatal error: Readonly property Foo::$bar must have type
?>
<?php
readonly class Foo
{
public static int $bar;
}
// Fatal error: Readonly class Foo cannot declare static properties
?>
readonly 클래스는 자식 클래스도 역시 readonly 클래스일 때만 상속할 수 있어요.
new
클래스의 인스턴스를 만들려면 new 키워드를 써야 해요. 에러가 나면 예외를 던지는 생성자(constructor)가 정의되어 있지 않는 한, 객체는 항상 생성돼요. 클래스는 인스턴스화하기 전에 정의해 두는 게 좋고요(어떤 경우엔 필수예요).
클래스 이름이 담긴 문자열 변수를 new와 함께 쓰면 그 클래스의 새 인스턴스가 생성돼요. 클래스가 네임스페이스 안에 있다면 이때 완전한 이름(fully qualified name)을 써야 해요.
📌 참고
클래스 생성자에 넘길 인자가 없다면, 클래스 이름 뒤의 괄호는 생략해도 돼요.
예제 #3 — 인스턴스 만들기
<?php
class SimpleClass {
}
$instance = new SimpleClass();
var_dump($instance);
// This can also be done with a variable:
$className = 'SimpleClass';
$instance = new $className(); // new SimpleClass()
var_dump($instance);
?>
PHP 8.0.0부터는 new를 임의의 표현식(arbitrary expression)과 함께 쓸 수 있어요. 그 표현식이 문자열을 만들어 내면 더 복잡한 인스턴스화가 가능해지는데, 표현식은 괄호로 감싸줘야 해요.
예제 #4 — 임의의 표현식으로 인스턴스 만들기
여기서는 클래스 이름을 만들어 내는 여러 유효한 표현식 예시를 보여줘요. 함수 호출, 문자열 결합, ::class 상수를 쓰는 경우를 확인할 수 있어요.
<?php
class ClassA extends \stdClass {}
class ClassB extends \stdClass {}
class ClassC extends ClassB {}
class ClassD extends ClassA {}
function getSomeClass(): string
{
return 'ClassA';
}
var_dump(new (getSomeClass()));
var_dump(new ('Class' . 'B'));
var_dump(new ('Class' . 'C'));
var_dump(new (ClassD::class));
?>
위 예제의 PHP 8 기준 출력:
object(ClassA)#1 (0) {
}
object(ClassB)#1 (0) {
}
object(ClassC)#1 (0) {
}
object(ClassD)#1 (0) {
}
클래스 컨텍스트 안에서는 new self와 new parent로도 새 객체를 만들 수 있어요.
이미 만든 인스턴스를 새 변수에 할당하면, 그 새 변수는 할당된 그 객체와 같은 인스턴스에 접근해요. 이 동작은 인스턴스를 함수에 넘길 때도 똑같아요. 이미 만들어진 객체의 복사본은 복제(clone)해서 만들 수 있고요.
예제 #5 — 객체 할당
<?php
class SimpleClass {
public string $var;
}
$instance = new SimpleClass();
$assigned = $instance;
$reference =& $instance;
$instance->var = '$assigned will have this value';
$instance = null; // $instance and $reference become null
var_dump($instance);
var_dump($reference);
var_dump($assigned);
?>
위 예제의 출력은 이렇게 돼요.
NULL
NULL
object(SimpleClass)#1 (1) {
["var"]=>
string(30) "$assigned will have this value"
}
객체의 인스턴스를 만드는 방법은 몇 가지 더 있어요.
예제 #6 — 새 객체 만들기
<?php
class Test
{
public static function getNew()
{
return new static();
}
}
class Child extends Test {}
$obj1 = new Test(); // By the class name
$obj2 = new $obj1(); // Through the variable containing an object
var_dump($obj1 !== $obj2);
$obj3 = Test::getNew(); // By the class method
var_dump($obj3 instanceof Test);
$obj4 = Child::getNew(); // Through a child class method
var_dump($obj4 instanceof Child);
?>
위 예제의 출력은 이렇게 돼요.
bool(true)
bool(true)
bool(true)
새로 만든 객체의 멤버에 접근하는 것도 하나의 표현식으로 가능해요.
예제 #7 — 새로 만든 객체의 멤버 접근
<?php
echo (new DateTime())->format('Y'), PHP_EOL;
// surrounding parentheses are optional as of PHP 8.4.0
echo new DateTime()->format('Y'), PHP_EOL;
?>
위 예제의 출력은 이와 비슷한 형태예요.
2025
2025
📌 참고
PHP 7.1 이전에는 생성자 함수가 정의되어 있지 않으면 인자가 평가되지 않았어요.
속성과 메서드 (Properties and methods)
클래스의 속성과 메서드는 서로 다른 "네임스페이스"에 살아요. 그래서 같은 이름의 속성과 메서드를 둘 다 가질 수 있어요. 속성을 가리키는 표기와 메서드를 호출하는 표기가 같은데, 속성이 접근될지 메서드가 호출될지는 오로지 맥락에 달려 있어요. 즉 그 사용이 변수 접근인지 함수 호출인지에 따라 달라진다는 뜻이에요.
예제 #8 — 속성 접근과 메서드 호출
<?php
class Foo
{
public $bar = 'property';
public function bar() {
return 'method';
}
}
$obj = new Foo();
echo $obj->bar, PHP_EOL, $obj->bar(), PHP_EOL;
위 예제의 출력은 이렇게 돼요.
property
method
이 말은 즉, 속성에 할당해 둔 익명 함수를 직접 호출하는 것은 불가능하다는 뜻이에요. 대신 그 속성을 일단 변수에 할당해야 해요. 아니면 괄호로 감싸서 그런 속성을 직접 호출할 수도 있고요.
예제 #9 — 속성에 저장된 익명 함수 호출
<?php
class Foo
{
public $bar;
public function __construct() {
$this->bar = function() {
return 42;
};
}
}
$obj = new Foo();
echo ($obj->bar)(), PHP_EOL;
위 예제의 출력은 이렇게 돼요.
42
extends
클래스는 클래스 선언에서 extends 키워드를 쓰면 다른 클래스의 상수·메서드·속성을 상속받을 수 있어요. 여러 클래스를 한 번에 상속하는 건 불가능하고, 클래스는 오직 하나의 기반 클래스에서만 상속받을 수 있어요.
상속받은 상수·메서드·속성은 부모 클래스에 정의된 것과 같은 이름으로 다시 선언하면 오버라이드할 수 있어요. 다만 부모 클래스가 메서드나 상수를 final로 정의했다면 오버라이드할 수 없고요. 오버라이드된 메서드나 정적 속성은 parent::로 참조해서 접근할 수 있어요.
📌 참고
PHP 8.1.0부터 상수도 final로 선언할 수 있어요.
예제 #10 — 간단한 클래스 상속
<?php
class SimpleClass
{
function displayVar()
{
echo "Parent class\n";
}
}
class ExtendClass extends SimpleClass
{
// Redefine the parent method
function displayVar()
{
echo "Extending class\n";
parent::displayVar();
}
}
$extended = new ExtendClass();
$extended->displayVar();
?>
위 예제의 출력은 이렇게 돼요.
Extending class
Parent class
시그니처 호환 규칙 (Signature compatibility rules)
메서드를 오버라이드할 때 그 시그니처는 부모 메서드와 호환되어야 해요. 그렇지 않으면 치명적(fatal) 에러가 나거나, PHP 8.0.0 이전에는 E_WARNING 수준의 에러가 발생했어요. 시그니처가 호환된다는 건 변량(variance) 규칙을 지키고, 필수 매개변수를 선택적으로 바꾸고, 선택적인 새 매개변수만 추가하며, 접근성을 좁히지 않고 완화만 하는 경우를 말해요. 이걸 리스코프 치환 원칙(Liskov Substitution Principle, 줄여서 LSP)이라고 불러요. 생성자와 private 메서드는 이 시그니처 호환 규칙에서 제외되어서, 시그니처가 어긋나도 치명적 에러가 나지 않아요.
예제 #11 — 호환되는 자식 메서드
<?php
class Base
{
public function foo(int $a) {
echo "Valid\n";
}
}
class Extend1 extends Base
{
function foo(int $a = 5)
{
parent::foo($a);
}
}
class Extend2 extends Base
{
function foo(int $a, $b = 5)
{
parent::foo($a);
}
}
$extended1 = new Extend1();
$extended1->foo();
$extended2 = new Extend2();
$extended2->foo(1);
위 예제의 출력은 이렇게 돼요.
Valid
Valid
아래 예시들은 자식 메서드가 매개변수를 제거하거나, 선택적 매개변수를 필수로 바꾸면 부모 메서드와 호환되지 않는다는 걸 보여줘요.
예제 #12 — 자식 메서드가 매개변수를 제거할 때의 치명적 에러
<?php
class Base
{
public function foo(int $a = 5) {
echo "Valid\n";
}
}
class Extend extends Base
{
function foo()
{
parent::foo(1);
}
}
위 예제의 PHP 8 기준 출력은 이와 비슷해요.
Fatal error: Declaration of Extend::foo() must be compatible with Base::foo(int $a = 5) in /in/evtlq on line 13
예제 #13 — 자식 메서드가 선택적 매개변수를 필수로 바꿀 때의 치명적 에러
<?php
class Base
{
public function foo(int $a = 5) {
echo "Valid\n";
}
}
class Extend extends Base
{
function foo(int $a)
{
parent::foo($a);
}
}
위 예제의 PHP 8 기준 출력은 이와 비슷해요.
Fatal error: Declaration of Extend::foo(int $a) must be compatible with Base::foo(int $a = 5) in /in/qJXVC on line 13
⚠️ 경고
자식 클래스에서 메서드 매개변수의 이름을 바꾸는 건 시그니처 불일치는 아니에요. 다만 그렇게 하면 명명된 인자(named arguments)를 쓸 때 런타임
Error가 나기 때문에 권장하지 않아요.
예제 #14 — 명명된 인자를 쓸 때 자식 클래스에서 매개변수 이름을 바꾼 경우의 에러
<?php
class A {
public function test($foo, $bar) {}
}
class B extends A {
public function test($a, $b) {}
}
$obj = new B;
// Pass parameters according to A::test() contract
$obj->test(foo: "foo", bar: "bar"); // ERROR!
위 예제의 출력은 이와 비슷해요.
Fatal error: Uncaught Error: Unknown named parameter $foo in /in/XaaeN:14
Stack trace:
#0 {main}
thrown in /in/XaaeN on line 14
::class
class 키워드는 클래스 이름을 해석할 때도 쓰여요. 클래스 ClassName의 완전한 이름을 얻으려면 ClassName::class를 쓰면 되는데, 특히 네임스페이스를 쓰는 클래스에서 유용해요.
예제 #15 — 클래스 이름 해석
<?php
namespace NS {
class ClassName {
}
echo ClassName::class;
}
?>
위 예제의 출력은 이렇게 돼요.
NS\ClassName
📌 참고
::class를 쓰는 클래스 이름 해석은 컴파일 타임 변환이에요. 즉 클래스 이름 문자열이 만들어지는 시점에는 아직 오토로딩이 일어나지 않았어요. 그래서 클래스가 존재하지 않아도 클래스 이름은 확장돼요. 그 경우에도 에러는 발생하지 않아요.
예제 #16 — 존재하지 않는 클래스 이름 해석
<?php
print Does\Not\Exist::class;
?>
위 예제의 출력은 이렇게 돼요.
Does\Not\Exist
PHP 8.0.0부터는 ::class를 객체에도 쓸 수 있어요. 이 해석은 컴파일 타임이 아니라 런타임에 일어나고, 효과는 객체에 get_class()를 호출한 것과 같아요.
예제 #17 — 객체 이름 해석
<?php
namespace NS {
class ClassName {
}
$c = new ClassName();
print $c::class;
}
?>
위 예제의 출력은 이렇게 돼요.
NS\ClassName
널세이프 메서드와 속성 (Nullsafe methods and properties)
PHP 8.0.0부터 대신 ?-> 라는 "nullsafe" 연산자로 속성과 메서드에 접근할 수도 있어요. nullsafe 연산자는 위에서 본 속성·메서드 접근과 똑같이 동작하되, 역참조하는 객체가 null이면 예외를 던지는 대신 null을 돌려줘요. 역참조가 체인의 일부라면 체인의 나머지 부분은 건너뛰어요.
이 효과는 각 접근을 is_null() 검사로 감싼 것과 비슷하지만 훨씬 간결해요.
예제 #18 — 널세이프 연산자
<?php
// As of PHP 8.0.0, this line:
$result = $repository?->getUser(5)?->name;
// Is equivalent to the following code block:
if (is_null($repository)) {
$result = null;
} else {
$user = $repository->getUser(5);
if (is_null($user)) {
$result = null;
} else {
$result = $user->name;
}
}
?>
📌 참고
nullsafe 연산자는
null이 속성이나 메서드 반환값의 유효하고 예상 가능한 값으로 여겨질 때 가장 잘 어울려요. 에러를 나타내는 용도로는 예외를 던지는 쪽이 더 좋아요.