我在这看到两件事.
第一个使你的问题有点复杂.您希望使用命名空间,但您当前的配置是通过文件系统.到目前为止,类定义文件的文件名不包含命名空间.所以你不能像实际那样继续.
第二个是你没有PHP自动加载所涵盖的内容,你只需加载一组已定义的类并将其注册到注册表中.
我不确定你是否需要PHP自动加载.当然,将两者结合在一起看起来很有希望.解决第一点可能会帮助你解决后面的问题,所以我建议先从它开始.
让我们使隐藏的依赖项更加明显.在您目前的设计中,您有三件事:
>对象在注册表中注册的名称.
>包含类定义的文件名.
>类本身的名称.
2.和3.的值在一个中,您从文件名解析类本身的名称.如上所述,命名空间现在使这变得复杂.解决方案很简单,您可以从包含此信息的文件中读取,而不是从目录列表中读取.轻量级配置文件格式是json:
{
"Service": {
"file": "test.class.php",
"class": "Library\\Of\\Something\\ConcreteService"
}
}
现在包含三个所需的依赖项,用于通过名称将类注册到注册表中,因为文件名也是已知的.
然后,您可以在注册表中注册类:
class Registry
{
public function registerClass($name, $class) {
$this->$name = new $class($this);
}
}
并为json格式添加一个加载器类:
interface Register
{
public function register(Registry $registry);
}
class JsonClassmapLoader implements Register
{
private $file;
public function __construct($file) {
$this->file = $file;
}
public function register(Registry $registry) {
$definitions = $this->loadDefinitionsFromFile();
foreach ($definitions as $name => $definition) {
$class = $definition->class;
$path = dirname($this->file) . '/' . $definition->file;
$this->define($class, $path);
$registry->registerClass($name, $class);
}
}
protected function define($class, $path) {
if (!class_exists($class)) {
require($path);
}
}
protected function loadDefinitionsFromFile() {
$json = file_get_contents($this->file);
return json_decode($json);
}
}
这里没有太大的魔力,json文件中的文件名相对于它的目录.如果尚未定义类(此处触发PHP自动加载),则需要该类的文件.完成后,该类按其名称注册:
$registry = new Registry();
$json = new JsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
这个例子也非常简单,而且很有效.所以第一个问题就解决了.
第二个问题是自动加载.这个当前的变体和你以前的系统确实隐藏了其他东西.有两个中心事情要做.一个是实际加载类定义,另一个是实例化对象.
在您的原始示例中,技术上不需要自动加载,因为对象在注册表中注册的那一刻,它也是实例化的.您这样做也是为它分配注册表.我不知道你是否只是因为这样做,或者这只是发生在你身上.你在问题中写下你需要的那个.
因此,如果您想将自动加载到您的注册表中(或延迟加载),这会有所不同.由于你的设计已经搞砸了,让我们继续添加更多魔力.您希望将注册表组件的实例化推迟到第一次使用它的时刻.
与在注册表中一样,组件的名称比它的实际类型更重要,这已经非常动态并且只是一个字符串.要延迟组件创建,在注册时但在访问时不会创建类.这可以通过使用需要新类型注册表的__get函数来实现:
class LazyRegistry extends Registry
{
private $defines = [];
public function registerClass($name, $class)
{
$this->defines[$name] = $class;
}
public function __get($name) {
$class = $this->defines[$name];
return $this->$name = new $class($this);
}
}
用法示例再次完全相同,但是,注册表的类型已更改:
$registry = new LazyRegistry();
$json = new JsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
所以现在已经推迟了具体服务对象的创建,直到首次访问.但是,这仍然不是自动加载.类定义的加载已经在json加载器中完成.它不会因为已经创造出动态和魔力,而不是那样.我们需要为每个类启动一个自动加载器,它应该在第一次访问对象时启动.例如.我们实际上希望能够在应用程序中使用腐烂的代码,这些代码可能永远不会被注意到,因为我们不关心它是否被使用.但是我们不想把它加载到内存中.
对于自动加载,您应该了解spl_autoload_register,它允许您具有多个自动加载器功能.有很多原因可以解释为什么它通常很有用(例如,想象你使用第三方软件包),但是这个动态魔术盒称为你的注册表,它只是这项工作的完美工具.一个直接的解决方案(并没有进行任何过早的优化)是为我们在注册表定义中的每个类注册一个自动加载器函数.这需要一种新型的加载器,自动加载器功能只需两行代码:
class LazyJsonClassmapLoader extends JsonClassmapLoader
{
protected function define($class, $path) {
$autoloader = function ($classname) use ($class, $path) {
if ($classname === $class) {
require($path);
}
};
spl_autoload_register($autoloader);
}
}
用法示例再次没有太大变化,只是加载器的类型:
$registry = new LazyRegistry();
$json = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
现在你可以懒得下地狱.这意味着,要再次实际更改代码.因为您希望远程实现将这些文件实际放入该特定目录的必要性.等等,这就是你要求的,所以我们把它留在这里.
否则,请考虑使用将在首次访问时返回实例的callables配置注册表.这通常会使事情变得更加灵活.自动加载 – 如图所示 – 与您实际可以离开基于目录的方法无关,您不再关心代码打包在具体的位置(http://www.getcomposer.org/).
整个代码示例完整(没有registry.json和test.class.php):
class Registry
{
public function registerClass($name, $class) {
$this->$name = new $class($this);
}
}
class LazyRegistry extends Registry
{
private $defines = [];
public function registerClass($name, $class) {
$this->defines[$name] = $class;
}
public function __get($name) {
$class = $this->defines[$name];
return $this->$name = new $class($this);
}
}
interface Register
{
public function register(Registry $registry);
}
class JsonClassmapLoader implements Register
{
private $file;
public function __construct($file) {
$this->file = $file;
}
public function register(Registry $registry) {
$definitions = $this->loadDefinitionsFromFile();
foreach ($definitions as $name => $definition) {
$class = $definition->class;
$path = dirname($this->file) . '/' . $definition->file;
$this->define($class, $path);
$registry->registerClass($name, $class);
}
}
protected function define($class, $path) {
if (!class_exists($class)) {
require($path);
}
}
protected function loadDefinitionsFromFile() {
$json = file_get_contents($this->file);
return json_decode($json);
}
}
class LazyJsonClassmapLoader extends JsonClassmapLoader
{
protected function define($class, $path) {
$autoloader = function ($classname) use ($class, $path) {
if ($classname === $class) {
require($path);
}
};
spl_autoload_register($autoloader);
}
}
$registry = new LazyRegistry();
$json = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
我希望这是有帮助的,但是这主要是在沙盒中播放,你迟早会粉碎它.您实际想要了解的是控制反转,依赖注入,然后是依赖注入容器.
你有的注册表是某种气味.这一切都充满了魔力和动力.您可能认为这对于开发来说很酷或者在系统中有“插件”(很容易扩展),但是您应该将对象的数量保持在较低水平.
Magic可能很难调试,所以你可能想要检查json文件的格式,如果它在你的情况下首先是有意义的,以防止第一手配置问题.
还要考虑传递给每个构造函数的注册表对象不是一个参数,而是表示动态数量的参数.这将开始迟早产生副作用.如果您使用的注册表太多,那么越早越好.这种副作用会使你的维护成本很高,因为通过设计这已经存在缺陷,因此你只能通过努力工作,对回归等进行大量集成测试来控制它.
然而,制作你自己的经历,这只是一些前景而不是你以后告诉我我没有注意到它.