가시성

가시성 (Visibility)

프로퍼티, 메서드, 그리고 PHP 7.1.0부터는 상수까지 — 그 가시성은 선언 앞에 public, protected, private 키워드를 붙여서 정해요. public으로 선언한 클래스 멤버는 어디서든 접근할 수 있고요. protected로 선언한 멤버는 해당 클래스 자신과, 이를 상속한 자식 클래스, 그리고 부모 클래스 안에서만 접근할 수 있어요. private로 선언한 멤버는 그 멤버를 정의한 클래스 안에서만 접근할 수 있죠.

프로퍼티 가시성 (Property Visibility)

클래스 프로퍼티를 public, private, protected 중 하나로 정의할 수 있어요. 가시성 키워드를 명시하지 않고 선언한 프로퍼티는 기본적으로 public으로 정의돼요.

예제 #1 프로퍼티 선언 (Property declaration)

<?php
/**
 * Define MyClass
 */
class MyClass
{
    public $public = 'Public';
    protected $protected = 'Protected';
    private $private = 'Private';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj = new MyClass();
echo $obj->public; // Works
echo $obj->protected; // Fatal Error
echo $obj->private; // Fatal Error
$obj->printHello(); // Shows Public, Protected and Private


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // We can redeclare the public and protected properties, but not private
    public $public = 'Public2';
    protected $protected = 'Protected2';

    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}

$obj2 = new MyClass2();
echo $obj2->public; // Works
echo $obj2->protected; // Fatal Error
echo $obj2->private; // Undefined
$obj2->printHello(); // Shows Public2, Protected2, Undefined

?>

비대칭 프로퍼티 가시성 (Asymmetric Property Visibility)

PHP 8.4부터는 객체 프로퍼티의 가시성을 비대칭으로 설정할 수도 있어요. 읽기(get)와 쓰기(set)에 서로 다른 유효 범위를 지정하는 방식이죠. 구체적으로는 set 가시성을 따로 지정할 수 있는데, 단 이때 기본 가시성보다 더 관대해지면 안 돼요.

예제 #2 비대칭 프로퍼티 가시성 (Asymmetric Property visibility)

<?php
class Book
{
    public function __construct(
        public private(set) string $title,
        public protected(set) string $author,
        protected private(set) int $pubYear,
    ) {}
}

class SpecialBook extends Book
{
    public function update(string $author, int $year): void
    {
        $this->author = $author; // OK
        $this->pubYear = $year; // Fatal Error
    }
}

$b = new Book('How to PHP', 'Peter H. Peterson', 2024);

echo $b->title; // Works
echo $b->author; // Works
echo $b->pubYear; // Fatal Error

$b->title = 'How not to PHP'; // Fatal Error
$b->author = 'Pedro H. Peterson'; // Fatal Error
$b->pubYear = 2023; // Fatal Error
?>

PHP 8.5부터는 클래스의 정적 프로퍼티에도 set 가시성을 적용할 수 있어요.

예제 #3 비대칭 정적 프로퍼티 가시성 (Asymmetric Static Property Visibility)

<?php
class Manager
{
    public private(set) static int $calls = 0;

    public function doAThing(): string
    {
        self::$calls++;
        // Do other stuff.
        return "some string";
    }
}

$m = new Manager();

$m->doAThing(); // Works
echo Manager::$calls; // Works
Manager::$calls = 5; // Fatal error
?>

위 예제를 실행하면 다음과 같이 출력돼요.

1
Fatal error: Uncaught Error: Cannot modify private(set) property Manager::$calls from global scope in /some/file.php

비대칭 가시성과 관련해 몇 가지 주의할 점이 있어요.

  • set 가시성을 따로 가질 수 있는 것은 타입이 지정된 프로퍼티뿐이에요.

  • set 가시성은 get과 같거나 더 제한적이어야 해요. 즉 public protected(set)protected protected(set)은 허용되지만, protected public(set)은 문법 오류가 나요.

  • 프로퍼티가 public이라면 기본 가시성은 생략할 수 있어요. 즉 public private(set)private(set)은 같은 결과를 내요.

  • private(set) 가시성을 가진 프로퍼티는 자동으로 final이 되어서, 자식 클래스에서 다시 선언할 수 없어요.

  • 프로퍼티의 참조(reference)를 얻는 동작은 get이 아니라 set 가시성을 따라요. 참조는 프로퍼티 값을 바꾸는 데 쓰일 수 있기 때문이에요.

  • 비슷하게, 배열 프로퍼티에 쓰기를 시도하면 내부적으로 getset이 둘 다 일어나요. 그래서 항상 더 제한적인 쪽인 set 가시성을 따르게 돼요.

참고: set-가시성 선언에는 공백을 넣을 수 없어요. private(set)가 맞고, private( set )처럼 쓰면 파싱 오류가 나요.

클래스가 다른 클래스를 상속하면, 자식 클래스는 final이 아닌 프로퍼티는 다시 정의할 수 있어요. 이때 기본 가시성이나 set 가시성을 넓힐 수 있지만, 넓힌 결과의 가시성은 부모 클래스와 같거나 더 넓어야 해요. 다만 주의할 점이 하나 있어요. private 프로퍼티를 오버라이드하면 실제로는 부모의 프로퍼티를 바꾸는 게 아니라, 내부 이름이 다른 새 프로퍼티를 만드는 것이에요.

예제 #4 비대칭 프로퍼티 상속 (Asymmetric Property inheritance)

<?php
class Book
{
    protected string $title;
    public protected(set) string $author;
    protected private(set) int $pubYear;
}

class SpecialBook extends Book
{
    public protected(set) string $title; // OK, as reading is wider and writing the same.
    public string $author; // OK, as reading is the same and writing is wider.
    public protected(set) int $pubYear; // Fatal Error. private(set) properties are final.
}
?>

메서드 가시성 (Method Visibility)

클래스 메서드를 public, private, protected 중 하나로 정의할 수 있어요. 가시성 키워드를 명시하지 않고 선언한 메서드는 기본적으로 public으로 정의돼요.

예제 #5 메서드 선언 (Method Declaration)

<?php
/**
 * Define MyClass
 */
class MyClass
{
    // Declare a public constructor
    public function __construct() { }

    // Declare a public method
    public function MyPublic() { }

    // Declare a protected method
    protected function MyProtected() { }

    // Declare a private method
    private function MyPrivate() { }

    // This is public
    function Foo()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate();
    }
}

$myclass = new MyClass;
$myclass->MyPublic(); // Works
$myclass->MyProtected(); // Fatal Error
$myclass->MyPrivate(); // Fatal Error
$myclass->Foo(); // Public, Protected and Private work


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // This is public
    function Foo2()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate(); // Fatal Error
    }
}

$myclass2 = new MyClass2;
$myclass2->MyPublic(); // Works
$myclass2->Foo2(); // Public and Protected work, not Private

class Bar 
{
    public function test() {
        $this->testPrivate();
        $this->testPublic();
    }

    public function testPublic() {
        echo "Bar::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Bar::testPrivate\n";
    }
}

class Foo extends Bar 
{
    public function testPublic() {
        echo "Foo::testPublic\n";
    }
    
    private function testPrivate() {
        echo "Foo::testPrivate\n";
    }
}

$myFoo = new Foo();
$myFoo->test(); // Bar::testPrivate 
                // Foo::testPublic
?>

상수 가시성 (Constant Visibility)

PHP 7.1.0부터 클래스 상수를 public, private, protected 중 하나로 정의할 수 있어요. 가시성 키워드를 명시하지 않고 선언한 상수는 기본적으로 public으로 정의돼요.

예제 #6 상수 선언 (PHP 7.1.0부터)

<?php
/**
 * Define MyClass
 */
class MyClass
{
    // Declare a public constant
    public const MY_PUBLIC = 'public';

    // Declare a protected constant
    protected const MY_PROTECTED = 'protected';

    // Declare a private constant
    private const MY_PRIVATE = 'private';

    public function foo()
    {
        echo self::MY_PUBLIC;
        echo self::MY_PROTECTED;
        echo self::MY_PRIVATE;
    }
}

$myclass = new MyClass();
MyClass::MY_PUBLIC; // Works
MyClass::MY_PROTECTED; // Fatal Error
MyClass::MY_PRIVATE; // Fatal Error
$myclass->foo(); // Public, Protected and Private work


/**
 * Define MyClass2
 */
class MyClass2 extends MyClass
{
    // This is public
    function foo2()
    {
        echo self::MY_PUBLIC;
        echo self::MY_PROTECTED;
        echo self::MY_PRIVATE; // Fatal Error
    }
}

$myclass2 = new MyClass2;
echo MyClass2::MY_PUBLIC; // Works
$myclass2->foo2(); // Public and Protected work, not Private
?>

다른 객체에서의 가시성 (Visibility from other objects)

같은 타입의 객체라면 인스턴스가 서로 달라도 서로의 private 멤버와 protected 멤버에 접근할 수 있어요. 그 객체 안에 들어가 보면 구현에 대한 구체적인 내용이 이미 알려져 있기 때문이죠.

예제 #7 같은 객체 타입의 private 멤버 접근 (Accessing private members of the same object type)

<?php
class Test
{
    private $foo;

    public function __construct($foo)
    {
        $this->foo = $foo;
    }

    private function bar()
    {
        echo 'Accessed the private method.';
    }

    public function baz(Test $other)
    {
        // We can change the private property:
        $other->foo = 'hello';
        var_dump($other->foo);

        // We can also call the private method:
        $other->bar();
    }
}

$test = new Test('test');

$test->baz(new Test('other'));
?>

위 예제를 실행하면 다음과 같이 출력돼요.

string(5) "hello"
Accessed the private method.