오브젝트 인터페이스

오브젝트 인터페이스 (Object Interfaces)

오브젝트 인터페이스(Object interface)는 클래스가 반드시 구현해야 하는 메서드와 프로퍼티를 지정하는 코드를 만들 수 있게 해 줘요. 이때 그 메서드나 프로퍼티를 어떻게 구현할지까지는 정의하지 않아요. 인터페이스는 클래스, 트레이트, enum과 같은 네임스페이스를 공유하기 때문에, 서로 같은 이름을 쓸 수 없어요.

인터페이스는 클래스를 정의하는 것과 같은 방식으로 만드는데, class 키워드 대신 interface 키워드를 쓰고, 어떤 메서드에도 본문(내용)을 정의하지 않아요.

인터페이스에 선언된 모든 메서드는 반드시 public이어야 해요. 그게 인터페이스의 본질이에요.

실제로 인터페이스는 서로를 보완하는 두 가지 목적을 위해 쓰여요.

  • 같은 인터페이스를 구현하기 때문에 서로 바꿔 쓸 수 있는, 서로 다른 클래스의 객체를 만들 수 있게 해 줘요. 대표적인 예로 여러 데이터베이스 접근 서비스, 여러 결제 게이트웨이, 다양한 캐시 전략을 들 수 있어요. 구현이 서로 달라도 그걸 쓰는 코드는 전혀 바꾸지 않고 다른 구현으로 갈아 끼울 수 있죠.
  • 함수나 메서드가 어떤 인터페이스에 맞는 파라미터를 받아서 동작하도록 해 주면서, 그 객체가 그 외에 무엇을 하든, 어떻게 구현되어 있든 신경 쓰지 않게 해 줘요. 이런 인터페이스는 흔히 Iterable, Cacheable, Renderable처럼 행동의 의미를 드러내는 이름을 붙여요.

인터페이스는 매직 메서드(magic method)를 정의해서, 구현하는 클래스가 그 메서드들을 반드시 구현하도록 요구할 수도 있어요.

Note: 생성자(constructor)를 인터페이스에 넣는 것은 지원이 되긴 하지만, 크게 권장하지 않아요. 생성자를 넣으면 인터페이스를 구현하는 객체의 유연성이 크게 줄어들거든요. 게다가 생성자는 상속 규칙에 강제되지 않아서, 일관되지 않고 예상 밖의 동작이 생길 수 있어요.

implements

인터페이스를 구현하려면 implements 연산자를 써요. 인터페이스의 모든 메서드는 클래스 안에서 반드시 구현해야 해요. 그러지 않으면 치명적 오류(fatal error)가 나요. 클래스는 인터페이스 여러 개를 콤마로 구분해서, 원한다면 한꺼번에 구현할 수도 있어요.

Warning: 인터페이스를 구현하는 클래스는 파라미터 이름을 인터페이스와 다르게 쓸 수도 있어요. 그런데 PHP 8.0부터는 명명된 인자(named arguments)를 지원해서, 호출하는 쪽이 인터페이스의 파라미터 이름에 의존할 수 있어요. 그래서 개발자들은 구현하는 인터페이스와 같은 파라미터 이름을 쓰는 것을 강력히 권장해요.

Note: 인터페이스도 클래스처럼 extends 연산자로 확장할 수 있어요.

Note: 인터페이스를 구현하는 클래스는 인터페이스의 모든 메서드를 호환되는 시그니처로 선언해야 해요. 클래스가 같은 이름의 메서드를 선언하는 인터페이스 여러 개를 구현할 수도 있는데, 이 경우에는 모든 인터페이스에 대해 시그니처 호환 규칙을 따라야 해요. 그래서 공변(covariance)과 반변(contravariance)을 적용할 수 있어요.

상수 (Constants)

인터페이스는 상수를 가질 수 있어요. 인터페이스 상수는 클래스 상수와 똑같이 동작해요. PHP 8.1.0 이전에는 그것을 상속하는 클래스나 인터페이스가 상수를 덮어쓸 수 없었어요.

프로퍼티 (Properties)

PHP 8.4.0부터 인터페이스는 프로퍼티도 선언할 수 있어요. 선언한다면 그 프로퍼티가 읽기 전용인지, 쓰기 전용인지, 아니면 둘 다인지 지정해야 해요. 인터페이스 선언은 public 읽기·쓰기 접근에만 적용돼요.

클래스는 인터페이스 프로퍼티를 여러 가지 방법으로 충족할 수 있어요. public 프로퍼티를 정의할 수도 있고, 해당하는 훅(hook)만 구현한 public 가상 프로퍼티(virtual property)를 정의할 수도 있어요. 읽기 프로퍼티는 readonly 프로퍼티로 충족할 수도 있죠. 다만 설정할 수 있는(settable) 인터페이스 프로퍼티는 readonly일 수 없어요.

Example #1 Interface properties example

<?php
interface I
{
    // An implementing class MUST have a publicly-readable property,
    // but whether or not it's publicly settable is unrestricted.
    public string $readable { get; }

    // An implementing class MUST have a publicly-writeable property,
    // but whether or not it's publicly readable is unrestricted.
    public string $writeable { set; }

    // An implementing class MUST have a property that is both publicly
    // readable and publicly writeable.
    public string $both { get; set; }
}

// This class implements all three properties as traditional, un-hooked
// properties. That's entirely valid.
class C1 implements I
{
    public string $readable;

    public string $writeable;

    public string $both;
}

// This class implements all three properties using just the hooks
// that are requested.  This is also entirely valid.
class C2 implements I
{
    private string $written = '';
    private string $all = '';

    // Uses only a get hook to create a virtual property.
    // This satisfies the "public get" requirement.
    // It is not writeable, but that is not required by the interface.
    public string $readable { get => strtoupper($this->writeable); }

    // The interface only requires the property be settable,
    // but also including get operations is entirely valid.
    // This example creates a virtual property, which is fine.
    public string $writeable {
        get => $this->written;
        set {
            $this->written = $value;
        }
    }

    // This property requires both read and write be possible,
    // so we need to either implement both, or allow it to have
    // the default behavior.
    public string $both {
        get => $this->all;
        set {
            $this->all = strtoupper($value);
        }
    }
}
?>

예제 (Examples)

Example #2 Interface example

<?php

// Declare the interface 'Template'
interface Template
{
    public function setVariable($name, $var);
    public function getHtml($template);
}

// Implement the interface
// This will work
class WorkingTemplate implements Template
{
    private $vars = [];
  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
  
    public function getHtml($template)
    {
        foreach($this->vars as $name => $value) {
            $template = str_replace('{' . $name . '}', $value, $template);
        }
 
        return $template;
    }
}

// This will not work
// Fatal error: Class BadTemplate contains 1 abstract methods
// and must therefore be declared abstract (Template::getHtml)
class BadTemplate implements Template
{
    private $vars = [];
  
    public function setVariable($name, $var)
    {
        $this->vars[$name] = $var;
    }
}
?>

Example #3 Extendable Interfaces

<?php
interface A
{
    public function foo();
}

interface B extends A
{
    public function baz(Baz $baz);
}

// This will work
class C implements B
{
    public function foo()
    {
    }

    public function baz(Baz $baz)
    {
    }
}

// This will not work and result in a fatal error
class D implements B
{
    public function foo()
    {
    }

    public function baz(Foo $foo)
    {
    }
}
?>

Example #4 Variance compatibility with multiple interfaces

<?php
class Foo {}
class Bar extends Foo {}

interface A {
    public function myfunc(Foo $arg): Foo;
}

interface B {
    public function myfunc(Bar $arg): Bar;
}

class MyClass implements A, B
{
    public function myfunc(Foo $arg): Bar
    {
        return new Bar();
    }
}
?>

Example #5 Multiple interface inheritance

<?php
interface A
{
    public function foo();
}

interface B
{
    public function bar();
}

interface C extends A, B
{
    public function baz();
}

class D implements C
{
    public function foo()
    {
    }

    public function bar()
    {
    }

    public function baz()
    {
    }
}
?>

Example #6 Interfaces with constants

<?php
interface A
{
    const B = 'Interface constant';
}

// Prints: Interface constant
echo A::B;


class B implements A
{
    const B = 'Class constant';
}

// Prints: Class constant
// Prior to PHP 8.1.0, this will however not work because it was not
// allowed to override constants.
echo B::B;
?>

Example #7 Interfaces with abstract classes

<?php
interface A
{
    public function foo(string $s): string;

    public function bar(int $i): int;
}

// An abstract class may implement only a portion of an interface.
// Classes that extend the abstract class must implement the rest.
abstract class B implements A
{
    public function foo(string $s): string
    {
        return $s . PHP_EOL;
    }
}

class C extends B
{
    public function bar(int $i): int
    {
        return $i * 2;
    }
}
?>

Example #8 Extending and implementing simultaneously

<?php

class One
{
    /* ... */
}

interface Usable
{
    /* ... */
}

interface Updatable
{
    /* ... */
}

// The keyword order here is important. 'extends' must come first.
class Two extends One implements Usable, Updatable
{
    /* ... */
}
?>

인터페이스는 타입 선언(type declaration)과 함께 쓰면, 어떤 객체가 특정 메서드를 꼭 가졌는지 확실히 보장하는 좋은 방법이 돼요. instanceof 연산자와 타입 선언을 함께 보세요.