Just a quick note that it's possible to declare visibility for multiple properties at the same time, by separating them by commas.
eg:
<?php
class a
{
    protected $a, $b;
    public $c, $d;
    private $e, $f;
}
?>
   プロパティ、メソッドまたは (PHP 7.1.0 以降) 定数のアクセス権 (visibility) は、
   キーワード: public, protected
   または private を指定することにより定義できます。
   public 宣言されたクラスのメンバーには、どこからでもアクセス可能です。
   protected 宣言されたメンバーには、
   そのクラス自身、そのクラスを継承したクラス、および親クラスからのみアクセスできます。
   private 宣言されたメンバーには、そのメンバーを定義したクラスからのみアクセスできます。
  
クラスのプロパティは、public, private, または protected として定義することができます。 アクセス権を明示的に指定しない場合、 そのプロパティは public として定義されます。
例1 プロパティの宣言
<?php
/**
 * 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; // 動作します
echo $obj->protected; // Fatal エラー
echo $obj->private; // Fatal エラー
$obj->printHello(); // Public、Protected そして Private を表示します
/**
 * MyClass2 の定義
 */
class MyClass2 extends MyClass
{
    // public および protected プロパティは再定義できますが、
    // private はできません。
    public $public = 'Public2';
    protected $protected = 'Protected2';
    function printHello()
    {
        echo $this->public;
        echo $this->protected;
        echo $this->private;
    }
}
$obj2 = new MyClass2();
echo $obj2->public; // 動作します
echo $obj2->protected; // Fatal エラー
echo $obj2->private; // 未定義です
$obj2->printHello(); // Public2, Protected2, Undefined を表示します
?>
     PHP 8.4 から、プロパティは非対称に
     アクセス権を設定できるようになりました。
     読み取り(get)と書き込み(set)に
     異なるスコープを設定できます。具体的には、set のアクセス権が
     メインのアクセス権より広くならない限り、
     別々に指定できます。
    
例2 非対称可視性プロパティ
<?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; // 致命的なエラー
    }
}
$b = new Book('How to PHP', 'Peter H. Peterson', 2024);
echo $b->title; // OK
echo $b->author; // OK
echo $b->pubYear; // 致命的なエラー
$b->title = 'How not to PHP'; // 致命的なエラー
$b->author = 'Pedro H. Peterson'; // 致命的なエラー
$b->pubYear = 2023; // 致命的なエラー
?>非対称可視性プロパティにはいくつかの注意点があります:
set のアクセス権を個別に指定できます。
      
     set のアクセス権は、get と同じか、
       より厳しくなければなりません。つまり、
       public protected(set) および protected protected(set)
       は許可されますが、protected public(set) は構文エラーになります。
      
     public の場合、メインのアクセス権は
       省略できます。つまり、public private(set) と private(set) は
       同じ結果になります。
      
     private(set) のアクセス権を持つプロパティは
       自動的に final となり、子クラスで再宣言できません。
      
     get ではなく set のアクセス権に従います。
       これは、リファレンスを使用してプロパティの値を変更できるためです。
      
     get と
       set の両方の操作が行われます。そのため、常により厳しい set
       のアクセス権に従います。
      
     注意: アクセス権を set する宣言に、スペースは許されません。
private(set)は正しい宣言です。private( set )は正しくないので、 パースエラーになります。
     クラスが別のクラスを継承するとき、子クラスは
     final でない任意のプロパティを再定義できます。その際、
     新しいアクセス権が親クラスと同じか広い場合に限り、
     メインのアクセス権または set
     のアクセス権を広げることができます。ただし、
     private プロパティがオーバーライドされると、
     実際には親のプロパティを変更するのではなく、
     異なる内部名を持つ新しいプロパティを作成することに注意してください。
    
例3 非対称可視性プロパティの継承
<?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。読み取りのアクセス権が広く、書き込みは同じだから。
    public string $author; // OK。読み取りのアクセス権は同じで、書き込みが広いから。
    public protected(set) int $pubYear; // 致命的なエラー。private(set) プロパティは final です。
}
?>クラスメソッドは、public, private, または protected として定義します。アクセス権を明示せずに宣言したメソッドは、 public となります。
例4 メソッドの宣言
<?php
/**
 * MyClass の定義
 */
class MyClass
{
    // public コンストラクタの宣言
    public function __construct() { }
    // public メソッドの宣言
    public function MyPublic() { }
    // protected メソッドの宣言
    protected function MyProtected() { }
    // private メソッドの宣言
    private function MyPrivate() { }
    // これは public となります
    function Foo()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate();
    }
}
$myclass = new MyClass;
$myclass->MyPublic(); // 動作します
$myclass->MyProtected(); // Fatal エラー
$myclass->MyPrivate(); // Fatal エラー
$myclass->Foo(); // Public、Protected および Private が動作します
/**
 * MyClass2 の定義
 */
class MyClass2 extends MyClass
{
    // これは public となります
    function Foo2()
    {
        $this->MyPublic();
        $this->MyProtected();
        $this->MyPrivate(); // Fatal エラー
    }
}
$myclass2 = new MyClass2;
$myclass2->MyPublic(); // 動作します
$myclass2->Foo2(); // Public および Protected は動作しますが、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
?>PHP 7.1.0 以降では、クラスの定数は public, private, protected として定義できるようになりました。 明示的に公開範囲のキーワードを明示的に宣言しない定数は、public として定義されます。
例5 PHP 7.1.0 以降での定数の宣言
<?php
/**
 * MyClass を定義する
 */
class MyClass
{
    // public な定数を宣言する
    public const MY_PUBLIC = 'public';
    // protected な定数を宣言する
    protected const MY_PROTECTED = 'protected';
    // private な定数を宣言する
    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; // 動作します
MyClass::MY_PROTECTED; // Fatal エラー
MyClass::MY_PRIVATE; // Fatal エラー
$myclass->foo(); // Public, Protected, Private のいずれでも動作します
/**
 * MyClass2 を定義する
 */
class MyClass2 extends MyClass
{
    // これは public
    function foo2()
    {
        echo self::MY_PUBLIC;
        echo self::MY_PROTECTED;
        echo self::MY_PRIVATE; // Fatal エラー
    }
}
$myclass2 = new MyClass2;
echo MyClass2::MY_PUBLIC; // 動作します
$myclass2->foo2(); // Public, Protected では動作しますが、Private では動作しません
?>同じ型のオブジェクト間では、たとえ同一のインスタンスでなくても お互いの private メンバーや protected メンバーにアクセスすることができます。 これは、そのオブジェクトの内部ではオブジェクトの実装の詳細が既知であるからです。
例6 同じ型のオブジェクトの private メンバーへのアクセス
<?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)
    {
        // private プロパティを変更することができます
        $other->foo = 'hello';
        var_dump($other->foo);
        // private メソッドをコールすることもできます
        $other->bar();
    }
}
$test = new Test('test');
$test->baz(new Test('other'));
?>上の例の出力は以下となります。
string(5) "hello" Accessed the private method.
Just a quick note that it's possible to declare visibility for multiple properties at the same time, by separating them by commas.
eg:
<?php
class a
{
    protected $a, $b;
    public $c, $d;
    private $e, $f;
}
?>I couldn't find this documented anywhere, but you can access protected and private member varaibles in different instance of the same class, just as you would expect
i.e.
<?php
class A
{
    protected $prot;
    private $priv;
    
    public function __construct($a, $b)
    {
        $this->prot = $a;
        $this->priv = $b;
    }
    
    public function print_other(A $other)
    {
        echo $other->prot;
        echo $other->priv;
    }
}
class B extends A
{
}
$a = new A("a_protected", "a_private");
$other_a = new A("other_a_protected", "other_a_private");
$b = new B("b_protected", "ba_private");
$other_a->print_other($a); //echoes a_protected and a_private
$other_a->print_other($b); //echoes b_protected and ba_private
$b->print_other($a); //echoes a_protected and a_private
?>if not overwritten, self::$foo in a subclass actually refers to parent's self::$foo 
<?php
class one
{
    protected static $foo = "bar";
    public function change_foo($value)
    {
        self::$foo = $value;
    }
}
class two extends one
{
    public function tell_me()
    {
        echo self::$foo;
    }
}
$first = new one;
$second = new two;
$second->tell_me(); // bar
$first->change_foo("restaurant");
$second->tell_me(); // restaurant
?>> Members declared protected can be accessed only within 
> the class itself and by inherited classes. Members declared 
> as private may only be accessed by the class that defines 
> the member.
This is not strictly true. Code outside the object can get and set private and protected members:
<?php
class Sealed { private $value = 'foo'; }
$sealed = new Sealed;
var_dump($sealed); // private $value => string(3) "foo"
call_user_func(\Closure::bind(
    function () use ($sealed) { $sealed->value = 'BAZ'; },
    null,
    $sealed
));
var_dump($sealed); // private $value => string(3) "BAZ"
?>
The magic lay in \Closure::bind, which allows an anonymous function to bind to a particular class scope. The documentation on \Closure::bind says:
> If an object is given, the type of the object will be used
> instead. This determines the visibility of protected and
> private methods of the bound object.
So, effectively, we're adding a run-time setter to $sealed, then calling that setter. This can be elaborated to generic functions that can force set and force get object members:
<?php
function force_set($object, $property, $value) {
    call_user_func(\Closure::bind(
        function () use ($object, $property, $value) {
            $object->{$property} = $value;
        },
        null,
        $object
    ));
}
function force_get($object, $property) {
    return call_user_func(\Closure::bind(
        function () use ($object, $property) {
            return $object->{$property};
        },
        null,
        $object
    ));
}
force_set($sealed, 'value', 'quux');
var_dump(force_get($sealed, 'value')); // 'quux'
?>
You should probably not rely on this ability for production quality code, but having this ability for debugging and testing is handy.Dynamic properties are "public".
<?php
class MyClass {
    public function setProperty($value) {
        $this->dynamicProperty = $value;
    }
}
$obj = new MyClass();
$obj->setProperty('Hello World');
echo $obj->dynamicProperty; // Outputs "Hello World"
?>
This usage is the same as well:
<?php
class MyClass {
}
$obj = new MyClass();
$obj->dynamicProperty = 'Hello World';
echo $obj->dynamicProperty; // Outputs "Hello World"
?>I see we can redeclare private properties into child class 
<?php   
 class A{
        private int $private_prop = 4;
        protected int $protected_prop = 8;
    }
    class B extends A{
        private int $private_prop = 7; // we can redeclare private property!!!
        public function printAll() {
            echo $this->private_prop;
            echo $this->protected_prop;
    }
    }
    $b = new B;
    $b->printAll(); // show 78
}
?>