프로그래밍 언어/PHP

PHP: 클래스 (상속, 문맥, 익명 클래스)

객체지향

PHP 언어도 객체지향(Object-Oriented Programming)을 지원합니다. 이전의 레거시 프로젝트에서는 함수로 표현하지만, 라라벨 등의 모던 프레임워크에서는 객체지향을 기본으로 합니다. 많은 객체지향 언어들이 클래스를 사용하여 현실세계에 존재하는 개체(Entity)를 묘사하며, 행동상태, 그리고 현실세계에서 존재하는 상속이나 인터페이스 등을 통해서 다채롭게 표현할 수 있습니다.

 

이는 모두 우리가 살아가는 세계를 프로그램에서 표현하기 위함이며 조금 더 인간세계와 매핑하기 좋게 만들어줍니다. 함수형 프로그래밍은 프로그램을 조금 더 단순하게 만들어줄 수 있지만, 함수가 각자 독립적이므로 직접적으로 관계를 표현하지 않습니다. 그러나 객체지향 프로그래밍은 관계와 계를 표현할 수 있으므로 함수형보다는 알아야 할 내용도 많고 코드 자체도 조금 더 복잡하지만, 친숙하게 개발을 진행해 나갈 수 있습니다.

클래스

클래스(Class)는 객체지향의 시작입니다. 이는 현실세계에 존재하는 개체를 본 따서 틀로 만든것입니다. 하나의 클래스를 만들어주면 각기 다른 상태를 가진 인스턴스(Instance), 혹은 객체(Object)를 만들 수 있습니다. 두 단어는 보통 혼용해서 사용합니다. 개인적으로 단어의 사용에 민감한 편이 아니지만, 일반적인 것을 지칭할 때는 객체라 하고, 특정 클래스의 객체를 인스턴스라고 표현하는 편입니다. 어떻게 사용하든 개인의 자유입니다.

Class A

우리는 첫 번째 클래스A 를 만들었습니다. 이제 이것을 가지고 인스턴스를 만들어낼 수 있습니다. 특정 클래스의 인스턴스는 new 연산자와 클래스의 이름을 주면 됩니다.

class A
{
}

$a = new A();

new

이렇게 만들어진 객체는 메모리의 힙영역에 저장되고, 변수는 객체의 주소값을 갖습니다. 주소값을 통해 힙에 있는 객체를 참조하여 얻어내거나 변경, 수정하는 등의 작업을 할 수 있습니다. 참고로 함수 또는 메서드의 파라매터로 객체를 전달하는 경우, 주소값을 복사하므로 함수의 내부에서 객체를 변경하면 외부의 값도 변합니다.

프로퍼티와 메서드

프로퍼티, 어트리뷰트 또는 필드라 불리는 이것은 객체의 상태를 갖습니다. 블로거라는 객체가 있다면, 블로거가 가진 블로그의 정보, 이메일, 비밀번호, 닉네임 등 여러 정보를 갖습니다. 이는 블로거마다 다르기 때문에 같은 클래스로부터 도출된 각기 다른 상태를 가진 객체가 필요한 것입니다.

 

아래의 클래스는 message 라는 프로퍼티foo() 라는 메서드를 갖습니다. 프로퍼티는 위에서 언급했듯 상태이며, 메서드는 행동을 말합니다. 블로거가 글을 쓰는 행위가 대표적이라고 볼 수 있습니다. 프로퍼티나 메서드에 접근할 때는 접근 연산자(->)를 사용하고, this현재 인스턴스를 가르킵니다.

class A
{
    public $message = 'Hello, world';

    public function foo()
    {
        // var_dump($this);
        return $this->message;
    }
}

상속

다른 클래스로부터 프로퍼티와 메서드를 물려받아 같은 이름을 가졌지만 다르게 표현할 수 있습니다. 일반으로 상속이 갖는 의미는 특정 계에 속할 때 사용하는데, 예를 들어 블로거라는 부모 클래스를 상속받은 자식 클래스로는 네이버 블로거, 티스토리 블로거, 워드프레스 블로거 등이 있을 수 있습니다. 자식 클래스는 기능을 확장할 수 있으며, 구현을 재정의하는 것도 가능합니다.

extends

아래의 클래스는 위에서 선언한 class A 를 상속받아 선언한 class B 입니다. 특정 클래스를 상속받을 때는 extends 키워드를 사용하고, 여기서 A 를 부모 클래스, B 를 자식 클래스라 표현합니다. 슈퍼 클래스와 서브 클래스라고 이야기해도 됩니다. B 클래스는 부모 클래스가 가지고 있던 message 프로퍼티와 foo() 메서드를 가지고 있습니다.

class B extends A
{
}

$b = new B();
var_dump($b->foo());

문맥

여기서 문맥이란 클래스 메서드 내부에서 객체에 대한 메서드나 프로퍼티를 호출하고 싶을 때, 어떤 클래스를 가르키냐는 것을 말합니다. 3가지 키워드가 존재하며 self, static, parent 로 사용할 수 있습니다.

 

아래의 코드를 보십시오. DC 를 상속받고 현재 foo() 메서드를 가지고 있습니다. 여기서 스코프C 가 되며 self 는 C 입니다. 하지만, static 을 사용하면 늦은 정적 바인딩을 사용하여 D 를 가르킬 수 있습니다. 마지막으로 parent부모 클래스를 나타내며 현재 스코프가 C 이므로 부모는 A 가 됩니다.

class C extends A
{
    public function foo()
    {
        return new self(); // C
        // return new static; // D
        // return new parent; // A
    }
}

class D extends C
{
}

$d = new D();
var_dump($d->foo());
 이는 클래스와 객체를 공부할 때 헷갈리는 부분이 될 수 있습니다. 이해하면 좋긴 하지만, 완벽하게 이해할 필요는 없습니다.

상수

클래스에서 상수를 사용하는 것은 const 키워드를 통해 할 수 있습니다. 이는 컴파일 타임에 정의되므로define() 처럼 함수나 메서드 내부에서 사용될 수 없습니다. 이에 대해서는 PHP: 상수를 참고하면 알 수 있습니다.

 

상수는 객체를 통해 접근하거나, 스코프 해결 연산자(::)를 사용하여 직접 접근할 수 있습니다.

class E
{
    const MESSAGE = 'Hello, world';

    public function getConstants()
    {
        return self::MESSAGE;
    }

    public function getClassName()
    {
        return __CLASS__;
    }
}

// $e = new E();
// var_dump($e->getConstants());

var_dump(E::MESSAGE);
// var_dump($e->getClassName());
매직 상수를 기억하시나요? 클래스 내부에서 __CLASS__, __METHOD__ 등을 통해 메타정보에 접근할 수 있습니다.

instanceof

인스턴스가 특정 클래스의 객체인지를 판별합니다. 여기서 중요한 것은 자식 클래스의 인스턴스는 부모 클래스의 인스턴스라고 판단한다는 점입니다. 아래의 코드는 dC 의 인스턴스라고 이야기하고 있습니다.

$d = new D();
var_dump($d instanceof C); // -> true

익명 클래스

놀랍게도 익명 클래스를 지원합니다. 익명 함수처럼 이름을 주지 않아도 상관없으며 상속이나 객체 생성등 일반적인 행동을 할 수 있습니다. 이를 활용하면 종속적인 클래스에 대해 따로 정의를 하지 않더라도 내부에서 자체적으로 해결할 수도 있을 것입니다. 저는 그다지 좋아하지 않는 방법이지만요.

class A
{
    public function foo()
    {
        return 'Hello, world';
    }
}

class B
{
    public function create()
    {
        return new class extends A {};
    }
}

$b = new B();
echo $b->create()->foo();

더 읽을거리

PHP: Static (정적 변수, 정적 메서드, 늦은 정적 바인딩)

https://www.inflearn.com/course/php7-reboot

https://www.inflearn.com/course/php7-oop

PHP 카테고리 목차 및 문서 정리

2020년, PHP 언어가 가지는 의미

https://www.php.net/manual/en/