图源:
自动加载
MyClass
,对应的php文件名可能是my_class.cls.php
,使用的时候要先使用require
或include
加载对应的文件。
除了手动加载文件以外,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
类。如果通过这种方式创建类,可以将构造函数定义为private
或protected
,这样做可以避免无意中对构造函数的使用。
析构函数
同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
其它对象
需要注意的是,访问修饰符的限制并不仅仅局限与当前类的实例,同样可以在类方法中访问当前类的其它实例的private
和protected
属性和方法:
<?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
就不使用protected
和public
,能使用protected
就不使用public
。因为声明为private
的属性和方法可以很容易地修改为protected
或public
,声明为protected
的属性和方法也能很容易地修改为public
,反之则不行,可能需要大量重构已有代码。
往期内容
文章评论