在PHP中寻找一种加载依赖项/服务/配置的优雅方式


Searching for an elegant way in PHP for loading dependencies/services/configuration?

我正在构建一个MVC PHP框架,我想知道哪些是加载我需要的类的最佳实践,无论是其他类还是普通配置。

直到今天,我一直在使用单例、注册表和依赖注入容器。虽然许多人声称DI是正确的选择,但在我看来,它只是把组件之间的耦合问题转移到了另一个地方。

singleton引入全局状态,registry引入紧耦合,DI引入…嗯,非常复杂。我还是很困惑,找不到一个合适的方法把我的类彼此连接起来。

与此同时,我想出了一个定制的解决方案。实际上,这不是一个解决方案,它只是从我的代码中抽象了服务加载的实现。

我用_load_service和_load_config方法构建了一个抽象类,我的框架的所有组件都扩展了它们,以便加载其他服务或配置。

abstract class Base_Component {
    function _load_service($service) {
        // could be either
        return DI_container::getInstance()->$service;
        // or
        $class = ''services'''.$service;
        return new $class;
        // or other implementation
    }
}

加载它们的实现现在只在一个地方实现,基类,所以至少我在组件中去掉了如下代码行:

$database = new Database(Registry::getInstance()->load('db_config'));

$database = DI_container::getInstance()->database;

现在如果我想要一个数据库实例,我做这个

$database = $this->_load_service('database');

和服务加载器、容器、注册表等的实现都可以在单个类方法中轻松更改,而无需搜索所有代码来更改对之前使用的容器实现的调用。

但是正如我所说的,我甚至不确定我将使用什么方法来加载类和配置。

你的意见是什么?

为什么要重新发明轮子?使用ple作为您的DI容器,并从它的文档中学习如何使用它。

或者,使用Silex微框架作为基础来创建您自己的框架。它扩展了ple的功能,所以你可以使用依赖注入。

回答你的问题,这是你如何使用DI而不耦合你的类:

interface ContainerInterface {
    public function getService($service_name);
    public function registerService($service_name,Closure $service_definition);
}
class Application {
    public function __construct(ContainerInterface $container) {
        $this->container= $container;
    }
    public function run() {
        // very simple to use!
        $this->container->getService('db')->someDatabaseQuery();
    }
}
$c = new My_DI_Container;
// Service definitions could be in a separate file
$c->registerService('db',function() { return new Database('some config'); });
// Then you inject your DI container into the objects that need it
$app = new Application($c);
$app->run(); // or whatever
这样,DI容器就解耦了,将来你可以使用不同的实现。唯一的要求是它实现了ContainerInterface。

注意容器对象是被推的,而不是被拉的。避免使用单例。要获取/设置单实例对象,请使用容器(这是它的职责)。要获得容器实例,只需将其推入构造函数。

回答你的问题;看看PHP的自动加载。通过自动加载注册类,这样你就不必把require/includes放到任何地方,这对RAD(快速应用程序开发)确实有积极的影响。

我的想法:

为尝试这样一个艰巨的任务而喝彩,你的方法似乎是基于良好的实践,如单例和工厂。

我不关心依赖注入。OOP是基于封装的,在我看来,将一个对象注入另一个对象会打破这种封装。当你将一个对象注入另一个对象时,目标对象必须"相信"被注入对象没有任何变化,否则你可能会得到不寻常的行为。

考虑类的名称间距(不是PHP的名称间距,而是像Zend那样给框架加上前缀Zend_),这将有助于注册命名空间,然后当调用类时,自动加载器将确保加载正确的类。Zend_Framework就是这样工作的。具体请查看Zend_Loader_Autoloader。Symfony框架实际上更进一步;在第一次请求期间,它将遍历所有已知位置查找类文件,然后构建一个类数组和文件路径,然后将该数组保存到文件(文件缓存),因此后续请求不会有相同的开销。为你的框架考虑一些东西。

就配置文件而言,Symfony使用YAML文件,我发现它非常灵活。您甚至可以包含PHP代码以增加灵活性。Symfony提供了一个易于使用的独立YAML解析器。您可以通过添加缓存层和缓存解析的YAML文件来提高性能,这样您就不必为每个请求解析文件。

我假设你是在ORM之上构建你的框架。我的建议是不要升级任何特定于ORM版本的功能,否则您的框架将与该版本耦合,并且您将不得不同时升级ORM和框架。

我建议你看看其他框架,看看你是否能从中挑选出最好的;生成一个可靠的、易于使用的框架。