PHP学习笔记10:类和对象 II

PHP学习笔记10:类和对象 II

image-20211129162010327

图源:php.net

自动加载

在现实项目中,我们往往会将类定义单独放在一个php文件中,比如有一个类MyClass,对应的php文件名可能是my_class.cls.php,使用的时候要先使用requireinclude加载对应的文件。

除了手动加载文件以外,php还提供一种自动加载类文件的机制:

<?php
require_once "../../util/class.php";
spl_autoload_register(function ($clsName){
    $fileName = "./".convert_clsname_to_fname($clsName).".cls.php";
    require_once $fileName;
});
$mc = new MyClass;
var_dump($mc);

像上面展示的那样,可以使用spl_autoload_register函数注册一个自定义的类加载器,加载器的具体职责就是根据类名加载对应的类源码文件。

这里的convert_clsname_to_fname工具函数是我编写的一个可以将驼峰样式类名转换为下划线连接的样式的函数。代码行数较多,就不在这里展示了,感兴趣的可以前往本笔记的Github仓库查看。

php解释器会在遇到new MyClass后调用注册的类加载器加载对应的类文件,这样就可以正确使用类了。

构造函数

构造函数的主要用途是在创建类实例时对其进行初始化。

默认情况下子类的构造函数不会主动调用父类的构造函数:

<?php
class Base
{
    public function __construct()
    {
        echo "Base::__construct is called." . PHP_EOL;
    }
}

class Child extends Base
{
    public function __construct()
    {
        echo "Child::__construct is called." . PHP_EOL;
    }
}

$child =  new Child();
// Child::__construct is called.

为了能正确调用父类的构造函数,一般要这样:

...
class Child extends Base
{
    public function __construct()
    {
        parent::__construct();
        echo "Child::__construct is called." . PHP_EOL;
    }
}

$child =  new Child();
// Base::__construct is called.
// Child::__construct is called.

如果子类没有定义构造方法,会继承父类的构造方法:

<?php
class Base
{
    public function __construct()
    {
        echo "Base::__construct is called." . PHP_EOL;
    }
}

class Child extends Base
{
}

$child =  new Child();
// Base::__construct is called.

与其它函数不同,构造函数不会受到“函数签名兼容性规则”的限制,这很好理解,因为构造函数并不涉及多态的问题:

<?php
class Base
{
    public function __construct($param)
    {
        echo "Base::__construct is called." . PHP_EOL;
        echo "\$param:{$param}" . PHP_EOL;
    }
}

class Child extends Base
{
    public function __construct()
    {
        parent::__construct('test');
        echo "Child::__construct is called." . PHP_EOL;
    }
}

$child =  new Child();
// Base::__construct is called.
// $param:test
// Child::__construct is called.

上面的例子中,子类的构造函数不需要参数,父类的构造函数需要1个参数,子类并不能兼容父类,但这并不影响程序的执行。

php8.0.0开始,构造函数也可以使用指名传参:

class Pointer
{
    private int $x;
    private int $y;
    public function __construct($x, $y)
    {
        $this->x = $x;
        $this->y = $y;
    }
}
$p = new Pointer(y: 2, x: 1);

属性提升

像上面实例中那样,使用传入的参数初始化同名属性的构造函数非常常见,所以php8.0.0增加了一个新语法,可以直接将构造函数中的参数转化为同名属性:

<?php
class Pointer
{
    public function __construct(private $x, private $y)
    {
    }
    public function __toString()
    {
        return "Pointer(x:{$this->x},y:{$this->y})";
    }
}
$p = new Pointer(y: 2, x: 1);
echo $p . PHP_EOL;
// Pointer(x:1,y:2)

对构造函数的参数使用访问修饰符后,该参数就会转换为类的同名属性,当然参数本身依然有效。转换完成后,构造函数内部的代码依然会被正常执行。

多构造

php不支持函数重载,所以我们只能定义一个__construct方法作为构造函数,如果我们需要像C++中那样的多构造,可以通过定义多个静态方法作为“工厂方法”来创建实例:

<?php
class Pointer
{
    private function __construct(private $x, private $y)
    {
    }
    public function __toString()
    {
        return "Pointer(x:{$this->x},y:{$this->y})";
    }
    public static function create_from_normal(int $x, int $y): self
    {
        return new self($x, $y);
    }
    public static function create_from_json(string $jsonStr): self
    {
        $arr = json_decode($jsonStr, true);
        return new self($arr['x'], $arr['y']);
    }
    public static function create_from_array(array $arr): self
    {
        return new self($arr['x'], $arr['y']);
    }
}
$p1 = Pointer::create_from_array(['x' => 1, 'y' => 9]);
$p2 = Pointer::create_from_json('{"x":2,"y":6}');
$p3 = Pointer::create_from_normal(5, 10);
echo $p1 . PHP_EOL;
echo $p2 . PHP_EOL;
echo $p3 . PHP_EOL;
// Pointer(x:1,y:9)
// Pointer(x:2,y:6)
// Pointer(x:5,y:10)

示例展示了如何通过三种方式来创建Pointer类。如果通过这种方式创建类,可以将构造函数定义为privateprotected,这样做可以避免无意中对构造函数的使用。

析构函数

同C++一样,php的类也具有析构函数,会在对象销毁时调用:

<?php
class MyClass
{
    public function __destruct()
    {
        echo __CLASS__ . "'s __destruct is called." . PHP_EOL;
    }
}
$mc =  new MyClass;
// MyClass's __destruct is called.

访问控制

可以使用访问修饰符public/protected/private对类的属性、方法、常量进行访问控制。

属性

public的属性可以在任何地方访问,protected属性仅能在类和子类中访问,private属性只能在所属的类中访问:

<?php
class MyClass
{
    public $publicAttr = 'public attr';
    protected $protectedAttr = 'protected attr';
    private $privateAttr = 'private attr';
    public function __construct()
    {
        echo $this->publicAttr . PHP_EOL;
        echo $this->protectedAttr . PHP_EOL;
        echo $this->privateAttr . PHP_EOL;
    }
}
class Child extends MyClass
{
    public function __construct()
    {
        echo $this->publicAttr . PHP_EOL;
        echo $this->protectedAttr . PHP_EOL;
    }
}
$mc = new MyClass;
echo $mc->publicAttr;
// public attr
// protected attr
// private attr
// public attr

静态属性的访问规则与普通属性相同。

方法

访问限定符对方法的控制规则与属性类似:

<?php
class MyClass{
    public function __construct()
    {
        $this->public_method();
        $this->protected_method();
        $this->private_method();
    }
    public function public_method(){
        echo "public method is called.".PHP_EOL;
    }
    protected function protected_method(){
        echo "protected method is called.".PHP_EOL;
    }
    private function private_method(){
        echo "private method is called.".PHP_EOL;
    }
}
class Child extends MyClass{
    public function __construct()
    {
        $this->public_method();
        $this->protected_method();
    }
}
$mc = new MyClass;
$mc->public_method();
// public method is called.
// protected method is called.
// private method is called.
// public method is called.

常量

php7.1.0开始,类常量也可以使用访问修饰符:

<?php
class MyClass
{
    public const PUBLIC_CONST = 'public const';
    protected const PROTECTED_CONST = 'protected const';
    private const PRIVATE_CONST = 'private const';
    public function __construct()
    {
        echo self::PUBLIC_CONST . PHP_EOL;
        echo self::PROTECTED_CONST . PHP_EOL;
        echo self::PRIVATE_CONST . PHP_EOL;
    }
}
class Child extends MyClass
{
    public function __construct()
    {
        echo parent::PUBLIC_CONST . PHP_EOL;
        echo parent::PROTECTED_CONST . PHP_EOL;
    }
}
echo MyClass::PUBLIC_CONST . PHP_EOL;
$mc = new MyClass;
// public const
// public const
// protected const
// private const

其它对象

需要注意的是,访问修饰符的限制并不仅仅局限与当前类的实例,同样可以在类方法中访问当前类的其它实例的privateprotected属性和方法:

<?php
class Pointer
{
    public function __construct(private int $x, private  int $y)
    {
    }
    public function add(Pointer $other): Pointer
    {
        $x = $this->x + $other->x;
        $y = $this->y + $other->y;
        return new self($x, $y);
    }
    public function __toString()
    {
        return "({$this->x},{$this->y})";
    }
}
$p1 = new Pointer(1, 3);
$p2 = new Pointer(2, 6);
$p3 = $p1->add($p2);
echo "{$p1}+{$p2}={$p3}" . PHP_EOL;
// (1,3)+(2,6)=(3,9)

对使用访问控制符的建议是:应当遵循最小访问原则。即能使用private就不使用protectedpublic,能使用protected就不使用public。因为声明为private的属性和方法可以很容易地修改为protectedpublic,声明为protected的属性和方法也能很容易地修改为public,反之则不行,可能需要大量重构已有代码。

往期内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值