php类中的访问控制

发布时间 2024-01-03 11:27:39作者: Fogwind

对属性或方法的访问控制,是通过在前面添加关键字 public(公有),protected(受保护)或 private(私有)来实现的。

  1. 被定义为公有的类成员可以在任何地方被访问。
  2. 被定义为受保护的类成员则可以被其自身以及其子类和父类访问(不可以被实例对象访问)。
  3. 被定义为私有的类成员则只能被其定义所在的类访问(不可以被实例对象访问)。

在没有任何访问控制关键字的情况下,默认声明为 public。

类的内部是指:

class Test {
    // 大括号内就是类的内部
    // protected 和 private 定义的类成员只能在类的内部访问/调用
}

属性的访问控制

例子1:

class FatherClass {
    public $public = 'father public ';
    protected $protected = 'father protected';
    private $private = 'father private';
}

$father = new FatherClass();
echo $father->public; // 输出 father public
echo '<br/>';
echo $father->protected; // 报错 Fatal error: Cannot access protected property FatherClass::$protected
echo '<br/>';
echo $father->private; // 报错 Fatal error: Cannot access private property FatherClass::$private

例子2:

class FatherClass {
    public $public = 'father public ';
    protected $protected = 'father protected';
    private $private = 'father private';
}
class ChildClass extends FatherClass {
 
    public function getProtected() {
        echo $this->protected;
    }

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

$child = new ChildClass();
echo $child->public; // father public
echo '<br/>';
echo $child->getProtected(); // father protected
echo '<br/>';
echo $child->getPrivate(); // 报错 Notice: Undefined property: ChildClass::$private
/**
 * 因为 ChildClass中没有定义private属性,而FatherClass类中的private属性是私有属性不能被子类继承。
 */

方法的访问控制

例子1:

 class FatherClass {
    public $public = 'father public ';
    protected $protected = 'father protected';
    private $private = 'father private';

    public function test() {
        $this->testProtected();
        $this->testPrivate();
    }

    public function testPublic() {
        echo 'father testPublic';
        echo '<br/>';
    }

    protected function testProtected() {
        echo 'father testProtected';
        echo '<br/>';
    }

    private function testPrivate() {
        echo 'father testPrivate';
        echo '<br/>';
    }
}

$father = new FatherClass();
$father->testPublic(); // father testPublic
// protected 和 private 定义的类成员只能在类的内部访问/调用
$father->testProtected(); // 报错 Fatal error: Call to protected method FatherClass::testProtected() from context ''
$father->testPrivate(); // 报错 Fatal error: Call to private method FatherClass::testPrivate() from context '' 

$father->test(); // 输出 father testProtected 和 father testPrivate

例子2:

// 这个例子很有意思
class FatherClass {
    public $public = 'father public ';
    protected $protected = 'father protected';
    private $private = 'father private';

    public function test() {
        $this->testProtected();
        $this->testPrivate();
    }

    public function testPublic() {
        echo 'father testPublic';
        echo '<br/>';
    }

    protected function testProtected() {
        echo 'father testProtected';
        echo '<br/>';
    }

    private function testPrivate() {
        echo 'father testPrivate';
        echo '<br/>';
    }
}

class ChildClass extends FatherClass {

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

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

    public function testPublic() {
        echo 'child testPublic';
        echo '<br/>';
    }

    protected function testProtected() {
        echo 'child testProtected';
        echo '<br/>';
    }

    private function testPrivate() {
        echo 'child testPrivate';
        echo '<br/>';
    }
}

$child = new ChildClass();

$child->test(); // 输出 child testProtected 和 father testPrivate
/**
 * 为什么输出father testPrivate?
 * 只能强行解释:
 * 因为test函数是继承自父类FatherClass,在父类中定义的,test函数中又调了私有方法testProvate,而私有方法只能在类自身内部调用,所以调用的是父类的testPrivate。
 * 官方的原话是:由于 $this-> 会在同一作用范围内尝试调用私有方法 (出处: https://www.php.net/manual/zh/language.oop5.late-static-bindings.php)
 */

类常量

可以把在类中始终保持不变的值定义为 常量 。 类常量的默认可见性是 public 。

自 PHP 7.1.0 起,类的常量可以定义为 public、private 或 protected。如果没有设置这些关键字,则该常量默认为 public。

接口(interface)中也可以定义常量。

可以用一个变量来动态调用类。但该变量的值不能为关键字(如 self , parent 或 static)。

注意,类常量只为每个类分配一次,而不是为每个类的实例分配。换句话说类常量是存在于类上的,不管通过类实例化多少个对象,各个对象在访问类常量的时候访问的是同一个常量。
例子:

class MyClass
{
    const CONSTANT = 'constant value';

    function showConstant() {
        echo  self::CONSTANT . "<br/>";
    }
}

echo MyClass::CONSTANT . "<br/>";

$classname = "MyClass";
echo $classname::CONSTANT . "<br/>";

$class = new MyClass();
$class->showConstant();

echo $class::CONSTANT."<br/>";

类常量是通过::操作符访问的。

类常量可以通过子类重新定义。PHP 8.1.0 起,如果类常量定义为 final,则不能被子类重新定义。

final 关键字

只有类,方法,常量(php 8.0新增)可以被定义为final。属性不能定义为final。

从 PHP 8.0.0 起,除了构造函数之外,私有方法也不能声明为 final 。

定义方法和常量时在其前面加上 final 关键字来防止被子类覆盖。

如果一个类被声明为 final,则不能被继承。

例子1:

// 子类不能覆盖父类加了final关键字的方法
class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }
   
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
   public function moreTesting() {
       echo "ChildClass::moreTesting() called\n";
   }
}
// 产生 Fatal error: Cannot override final method BaseClass::moreTesting()

例子2:

// 定义为final的类不能被继承
final class BaseClass {
   public function test() {
       echo "BaseClass::test() called\n";
   }
   
   // 由于类已经是 final,所以 final 关键字是多余的
   final public function moreTesting() {
       echo "BaseClass::moreTesting() called\n";
   }
}

class ChildClass extends BaseClass {
}
// 产生 Fatal error: Class ChildClass may not inherit from final class (BaseClass)

范围解析操作符(::)

范围解析操作符是一对冒号::,可以用于访问静态成员,类常量,还可以用于覆盖类中的属性和方法。

当在类定义之外引用到这些项目时,要使用类名。

引用类常量:

class MyClass {
    const CONST_VALUE = 'A constant value';
}

$classname = 'MyClass';
echo $classname::CONST_VALUE; // 输出 A constant value

echo MyClass::CONST_VALUE; // 输出 A constant value

selfparentstatic 这三个特殊的关键字是用于在类的内部对其属性或方法进行访问的。例子:

class MyClass {
    const CONST_VALUE = 'A constant value';
}

class OtherClass extends MyClass
{
    public static $my_static = 'static var';

    public static function doubleColon() {
        echo parent::CONST_VALUE . "\n"; // 输出 A constant value
        echo self::$my_static . "\n"; // 输出 static var
    }
}

$classname = 'OtherClass';
$classname::doubleColon();

OtherClass::doubleColon();

当一个子类覆盖其父类中的方法时,PHP 不会调用父类中已被覆盖的方法。是否调用父类的方法取决于子类。例子:

class MyClass
{
    protected function myFunc() {
        echo "MyClass::myFunc()\n";
    }
}

class OtherClass extends MyClass
{
    // 覆盖了父类的定义
    public function myFunc()
    {
        // 但还是可以调用父类中被覆盖的方法
        parent::myFunc();
        echo "OtherClass::myFunc()\n";
    }
}

$class = new OtherClass();
$class->myFunc();