hack类型常量:简介
想象一下,你有一个非泛型的类,并有一些不同的extends类。
<?hh
namespace Hack\UserDocumentation\TypeConstants\Intro\Examples\NonParameterized;
abstract class User {
public function __construct(private int $id) {}
public function getID(): int {
return $this->id;
}
}
trait UserTrait {
require extends User;
}
interface IUser {
require extends User;
}
class AppUser extends User implements IUser {
use UserTrait;
}
function run(): void {
$au = new AppUser(-1);
var_dump($au->getID());
}
run();
Output
int(-1)
现在想象一下,你意识到,有时用户的ID可能是一个string以及一个int。但是你知道具体的类User将确切地知道将返回什么类型。
泛型引入了类型参数的概念,它基本上允许您将类型占位符关联到一个类或方法,然后在类实例化或方法被调用后,该类就可以完全关联。
<?hh
namespace Hack\UserDocumentation\TypeConstants\Intro\Examples\Generics;
abstract class User<T as arraykey> {
public function __construct(private T $id) {}
public function getID(): T {
return $this->id;
}
}
trait UserTrait<T as arraykey> {
require extends User<T>;
}
interface IUser<T as arraykey> {
require extends User<T>;
}
// We know that AppUser will only have int ids
class AppUser extends User<int> implements IUser<int> {
use UserTrait<int>;
}
class WebUser extends User<string> implements IUser<string> {
use UserTrait<string>;
}
class OtherUser extends User<arraykey> implements IUser<arraykey> {
use UserTrait<arraykey>;
}
function run(): void {
$au = new AppUser(-1);
var_dump($au->getID());
$wu = new WebUser('-1');
var_dump($wu->getID());
$ou1 = new OtherUser(-1);
var_dump($ou1->getID());
$ou2 = new OtherUser('-1');
var_dump($ou2->getID());
}
run();
Output
int(-1)
string(2) "-1"
int(-1)
string(2) "-1"
请注意,我们必须传播一个类型参数的附加到类本身和所有扩展它。现在想想,如果我们有成百上千个使用特征和接口的地方,我们也必须更新它们。
在类型参数化方面,Hack引入了泛型称为类型常量的替代特性。类型常量允许将类型声明为类成员常量,而不是直接在类本身声明的类型。
<?hh
namespace Hack\UserDocumentation\TypeConstants\Intro\Examples\TypeConstants;
abstract class User {
abstract const type T as arraykey;
public function __construct(private this::T $id) {}
public function getID(): this::T {
return $this->id;
}
}
trait UserTrait {
require extends User;
}
interface IUser {
require extends User;
}
// We know that AppUser will only have int ids
class AppUser extends User implements IUser {
const type T = int;
use UserTrait;
}
class WebUser extends User implements IUser {
const type T = string;
use UserTrait;
}
class OtherUser extends User implements IUser {
const type T = arraykey;
use UserTrait;
}
function run(): void {
$au = new AppUser(-1);
var_dump($au->getID());
$wu = new WebUser('-1');
var_dump($wu->getID());
$ou1 = new OtherUser(-1);
var_dump($ou1->getID());
$ou2 = new OtherUser('-1');
var_dump($ou2->getID());
}
run();
Output
int(-1)
string(2) "-1"
int(-1)
string(2) "-1"
注意语法abstract const type <name> [ as <constraint> ];。所有类型的常量是,const并使用关键字type。您可以为常量指定一个名称,以及必须遵守的任何可能的约束条件。有关语法的信息,请参阅下文。
还要注意,只有类本身和直接的孩子需要更新新的类型信息。
类型常量有点类似于抽象方法,其中基类定义了方法签名而没有实体,而子类提供了实际的实现。
语法
类型常量的语法取决于您是处于抽象类还是具体类。
抽象类
在抽象类中,语法是最简单的:
abstract const type <name> [as <constraint>]; // constraint optional
例如:
abstract class A {
abstract const type Foo;
abstract const type Bar as arraykey;
}
那么在这个具体的子类中:
class C extends A {
const type Foo = string;
// Has to be int or string since was constrained to arraykey
const type Bar = int;
}
具体类
你可以在具体类中声明一个类型常量,但是它需要不同的语法:
const type <name> [as <constraint>] = <type>; // constraint optional
例如:
class NoChild {
const type Foo = ?string;
}
class Parent {
const type Foo as arraykey = arraykey; // need constraint for child override
}
class Child extends Parent {
const type Foo = string; // a string is an arraykey, so ok
}
使用类型常量
假设类型常量是类的一个常数,那么可以使用它来引用它this。作为一个类型注释,你注释一个类型常量,如:
this::<name>
例如,
this::T
你可以this::用类似的方式来考虑this返回类型。
这个例子显示了类型常量的真正好处。该属性被定义Base,但可以有不同的类型,取决于它在哪里使用的上下文。
<?hh
namespace Hack\UserDocumentation\TypeConstants\Introduction\Examples\Annotate;
abstract class Base {
abstract const type T;
protected this::T $value;
}
class Stringy extends Base {
const type T = string;
public function __construct() {
// inherits $value in Base which is now setting T as a string
$this->value = "Hi";
}
public function getString(): string {
return $this->value; // property of type string
}
}
class Inty extends Base {
const type T = int;
public function __construct() {
// inherits $value in Base which is now setting T as an int
$this->value = 4;
}
public function getInt(): int {
return $this->value; // property of type int
}
}
function run(): void {
$s = new Stringy();
$i = new Inty();
var_dump($s->getString());
var_dump($i->getInt());
}
run();
Output
string(2) "Hi"
int(4)
其他规则
还有一些关于类型常量的其他规则:
- 像类型常量一样,类型常量具有public可见性。
- 在声明类型常量的直接类层次结构之外,可以通过classname::typeConstantName(例如Foo::T)引用它们。
- 像泛型一样,类型常量只能用在类型注释中。它们不能用于其他语言结构,比如new,instanceof()。