我正在将一个PHP项目转换为Hack的过程中,我遇到了一点障碍。我想做的是重写一个IoC容器从PHP到Hack,我有一个小麻烦,让一切通过Hack类型检查器工具。
基本上我有一个容器让你注册字符串到闭包映射。其思想是闭包包含实例化类的逻辑。容器还存储它创建的实例,并且还允许您强制创建新实例。下面是我的容器代码:
<?hh // strict
class Container {
private Map<string, mixed> $instances = Map {};
private Map<string, (function (Container): mixed)> $registered = Map {};
public function register(string $alias, (function (Container): mixed) $closure): void
{
$this->registered[$alias] = $closure;
}
public function get(string $alias): ?mixed
{
if (!$this->registered->contains($alias)) {
return null;
}
$instance = $this->instances->get($alias);
if ($instance !== null) {
return $instance;
}
$closure = $this->registered->get($alias);
if ($closure !== null) {
$this->instances->set($alias, $closure($this));
}
return $this->instances->get($alias);
}
public function getNew(string $alias): ?mixed
{
if (!$this->registered->contains($alias)) {
return null;
}
$closure = $this->registered->get($alias);
return ($closure !== null) ? $closure($this) : null;
}
}
这个类本身似乎通过类型检查器,但是当使用Container::get()
或Container::getNew()
时,由于返回类型是mixed
,当我试图在这些返回的对象上执行方法时,它会抛出这个错误:
你试图访问成员x,但这不是一个对象,它是一个混合值
现在我知道这是有意义的,因为混合显然允许非对象,所以我必须确保将这样的代码包装在is_object()
中,但是这样做似乎并不能抑制类型检查器中的错误。是否有更好的方法来确认某些东西是Hack中的对象,类型检查器将理解?
而且,这个IoC容器类严重依赖混合类型,这对我来说有点难看。在运行时必须确保它的返回是对象也是不理想的。还有更好的办法吗?我确实尝试过将混合更改为接口(如iconatable或其他东西)的想法,并有任何我想要存储在容器中实现的类,但随后类型检查器抱怨iconatable接口不包含我试图调用从容器返回的对象的方法(因此在代码的同一点出现错误,但出于不同的原因)。也许我用这种方法就快成功了?
谢谢你的帮助。
是否有更好的方法来确认某些东西是Hack中的对象,类型检查器将理解?
您可能希望通过instanceof
检查特定的实例。例如:(警告,直接在浏览器中输入的代码)
<?hh // strict
class C {
public function f(): void {}
}
function f(): mixed {
return new C();
}
function g(): void {
$c = f();
// $c->f() won't work out here, $c is mixed
if ($c instanceof C) {
$c->f(); // This works now
}
}
还要注意,你不需要?mixed
,那是多余的——只使用mixed
。(因为mixed可以是任何东西,它也可以是null。当你写?mixed
时,会产生一个错误,这是我在某些时候要做的事情。:))
而且,这个IoC容器类严重依赖于混合类型,这对我来说有点难看。
你的直觉很好。在Facebook,我们发现,在大多数情况下,当我们想要输入mixed
时,要么1)我们应该编写更具体的类型,要么2)代码结构非常糟糕,我们应该重新考虑如何构建它以更好地类型化。
也许你应该用一个更具体的类型。如您所示,使用接口将会有所帮助。如果您需要做的大多数事情实际上都是该接口的一部分,并且您只是偶尔需要做其他事情,那么您可以将它与上面描述的instanceof
功能结合起来。您可能还想看看使用泛型,这可能有意义,也可能没有意义,这取决于您如何准确地使用Container
。如果Container
实例化了很多次,每次都是针对不同的特定子类或接口,那么泛型可能正是您想要的。如果它是单例的,那么也许不是。(另外,虽然这里没有显示,但如果Container
以任何方式与它所持有的类交互,则可能需要对泛型进行约束。)
但我怀疑你遇到这个问题是由于结构问题。当我遇到这种情况时,我想到了以下几点:为什么首先需要这个抽象层来实例化类?它提供了什么功能?您能使该功能同质(即可类型)并与异构类实例化问题分离吗?暂时忽略形式类型(即,如果您在香草PHP中编写此代码),使用Container
的类如何知道它得到了正确的东西?必须有一些关于输入和输出的知识,无论多么非正式或以其他方式与调用代码捆绑在一起,否则这将无法首先工作!想想这些信息是在哪里以及如何编纂的,然后看看你是否能找到一种明确的方法。
希望这对你有帮助!如果我能澄清什么,请在评论中告诉我。当然,请随时提问,许多FB Hack工程师和知识渊博的社区成员密切关注"hacklang"标签:)(顺便说一句,我们真的很高兴大家都在尝试Hack !希望你喜欢。
我知道我已经迟了6个月了,但是我想提供我解决这个问题的方法。
我通过创建一个脚本来解决这个问题,该脚本将扫描项目中的工厂方法并将它们编译成单个类。
我通过扫描用户定义的属性provides
实现了这一点,该属性接受两个参数:工厂的别名和工厂产生的对象类型的全名。编译脚本还确保每个工厂只接受一个参数,即编译后的工厂类。
实际上,我不是在运行时填充容器,而是在编译时填充。容器类定义是容器,而不是容器类的实例作为容器。
这仍然是一个正在进行的工作,但你可以看到我的解决方案在github