首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
183 阅读
2
php接口优化 使用curl_multi_init批量请求
144 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
31
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
560
篇与
的结果
2023-08-08
理解PHP 依赖注入|Laravel IoC容器
理解PHP 依赖注入|Laravel IoC容器看Laravel的IoC容器文档只是介绍实例,但是没有说原理,之前用MVC框架都没有在意这个概念,无意中在phalcon的文档中看到这个详细的介绍,感觉豁然开朗,复制粘贴过来,主要是好久没有写东西了,现在确实很懒变得!首先,我们假设,我们要开发一个组件命名为SomeComponent。这个组件中现在将要注入一个数据库连接。在这个例子中,数据库连接在component中被创建,这种方法是不切实际的,这样做的话,我们将不能改变数据库连接参数及数据库类型等一些参数。01 <?php02 03 class SomeComponent04 {05 06 /**07 * The instantiation of the connection is hardcoded inside08 * the component so is difficult to replace it externally09 * or change its behavior10 */11 public function someDbTask()12 {13 $connection = new Connection(array(14 "host" => "localhost",15 "username" => "root",16 "password" => "secret",17 "dbname" => "invo"18 ));19 20 // ...21 }22 23 }24 25 $some = new SomeComponent();26 $some->someDbTask();为了解决上面所说的问题,我们需要在使用前创建一个外部连接,并注入到容器中。就目前而言,这看起来是一个很好的解决方案:01 <?php02 03 class SomeComponent04 {05 06 protected $_connection;07 08 /**09 * Sets the connection externally10 */11 public function setConnection($connection)12 {13 $this->_connection = $connection;14 }15 16 public function someDbTask()17 {18 $connection = $this->_connection;19 20 // ...21 }22 23 }24 25 $some = new SomeComponent();26 27 //Create the connection28 $connection = new Connection(array(29 "host" => "localhost",30 "username" => "root",31 "password" => "secret",32 "dbname" => "invo"33 ));34 35 //Inject the connection in the component36 $some->setConnection($connection);37 38 $some->someDbTask();现在我们来考虑一个问题,我们在应用程序中的不同地方使用此组件,将多次创建数据库连接。使用一种类似全局注册表的方式,从这获得一个数据库连接实例,而不是使用一次就创建一次。01 <?php02 03 class Registry04 {05 06 /**07 * Returns the connection08 */09 public static function getConnection()10 {11 return new Connection(array(12 "host" => "localhost",13 "username" => "root",14 "password" => "secret",15 "dbname" => "invo"16 ));17 }18 19 }20 21 class SomeComponent22 {23 24 protected $_connection;25 26 /**27 * Sets the connection externally28 */29 public function setConnection($connection){30 $this->_connection = $connection;31 }32 33 public function someDbTask()34 {35 $connection = $this->_connection;36 37 // ...38 }39 40 }41 42 $some = new SomeComponent();43 44 //Pass the connection defined in the registry45 $some->setConnection(Registry::getConnection());46 47 $some->someDbTask();现在,让我们来想像一下,我们必须在组件中实现两个方法,首先需要创建一个新的数据库连接,第二个总是获得一个共享连接:01 <?php02 03 class Registry04 {05 06 protected static $_connection;07 08 /**09 * Creates a connection10 */11 protected static function _createConnection()12 {13 return new Connection(array(14 "host" => "localhost",15 "username" => "root",16 "password" => "secret",17 "dbname" => "invo"18 ));19 }20 21 /**22 * Creates a connection only once and returns it23 */24 public static function getSharedConnection()25 {26 if (self::$_connection===null){27 $connection = self::_createConnection();28 self::$_connection = $connection;29 }30 return self::$_connection;31 }32 33 /**34 * Always returns a new connection35 */36 public static function getNewConnection()37 {38 return self::_createConnection();39 }40 41 }42 43 class SomeComponent44 {45 46 protected $_connection;47 48 /**49 * Sets the connection externally50 */51 public function setConnection($connection){52 $this->_connection = $connection;53 }54 55 /**56 * This method always needs the shared connection57 */58 public function someDbTask()59 {60 $connection = $this->_connection;61 62 // ...63 }64 65 /**66 * This method always needs a new connection67 */68 public function someOtherDbTask($connection)69 {70 71 }72 73 }74 75 $some = new SomeComponent();76 77 //This injects the shared connection78 $some->setConnection(Registry::getSharedConnection());79 80 $some->someDbTask();81 82 //Here, we always pass a new connection as parameter83 $some->someOtherDbTask(Registry::getConnection());到此为止,我们已经看到了如何使用依赖注入解决我们的问题。不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。但是从长远来看,这种形式的依赖注入也有一些缺点。例如,如果组件中有较多的依赖关系,我们需要创建多个setter方法传递,或创建构造函数进行传递。另外,每次使用组件时,都需要创建依赖组件,使代码维护不太易,我们编写的代码可能像这样:01 <?php02 03 //Create the dependencies or retrieve them from the registry04 $connection = new Connection();05 $session = new Session();06 $fileSystem = new FileSystem();07 $filter = new Filter();08 $selector = new Selector();09 10 //Pass them as constructor parameters11 $some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);12 13 // ... or using setters14 15 $some->setConnection($connection);16 $some->setSession($session);17 $some->setFileSystem($fileSystem);18 $some->setFilter($filter);19 $some->setSelector($selector);我想,我们不得不在应用程序的许多地方创建这个对象。如果你不需要依赖的组件后,我们又要去代码注入部分移除构造函数中的参数或者是setter方法。为了解决这个问题,我们再次返回去使用一个全局注册表来创建组件。但是,在创建对象之前,它增加了一个新的抽象层:01 <?php02 03 class SomeComponent04 {05 06 // ...07 08 /**09 * Define a factory method to create SomeComponent instances injecting its dependencies10 */11 public static function factory()12 {13 14 $connection = new Connection();15 $session = new Session();16 $fileSystem = new FileSystem();17 $filter = new Filter();18 $selector = new Selector();19 20 return new self($connection, $session, $fileSystem, $filter, $selector);21 }22 23 }这一刻,我们好像回到了问题的开始,我们正在创建组件内部的依赖,我们每次都在修改以及找寻一种解决问题的办法,但这都不是很好的做法。一种实用和优雅的来解决这些问题,是使用容器的依赖注入,像我们在前面看到的,容器作为全局注册表,使用容器的依赖注入做为一种桥梁来解决依赖可以使我们的代码耦合度更低,很好的降低了组件的复杂性:01 <?php02 03 class SomeComponent04 {05 06 protected $_di;07 08 public function __construct($di)09 {10 $this->_di = $di;11 }12 13 public function someDbTask()14 {15 16 // Get the connection service17 // Always returns a new connection18 $connection = $this->_di->get('db');19 20 }21 22 public function someOtherDbTask()23 {24 25 // Get a shared connection service,26 // this will return the same connection everytime27 $connection = $this->_di->getShared('db');28 29 //This method also requires a input filtering service30 $filter = $this->_db->get('filter');31 32 }33 34 }35 36 $di = new PhalconDI();37 38 //Register a "db" service in the container39 $di->set('db', function(){40 return new Connection(array(41 "host" => "localhost",42 "username" => "root",43 "password" => "secret",44 "dbname" => "invo"45 ));46 });47 48 //Register a "filter" service in the container49 $di->set('filter', function(){50 return new Filter();51 });52 53 //Register a "session" service in the container54 $di->set('session', function(){55 return new Session();56 });57 58 //Pass the service container as unique parameter59 $some = new SomeComponent($di);60 61 $some->someTask();现在,该组件只有访问某种service的时候才需要它,如果它不需要,它甚至不初始化,以节约资源。该组件是高度解耦。他们的行为,或者说他们的任何其他方面都不会影响到组件本身。我们的实现办法¶PhalconDI 是一个实现了服务的依赖注入功能的组件,它本身也是一个容器。由于Phalcon高度解耦,PhalconDI 是框架用来集成其他组件的必不可少的部分,开发人员也可以使用这个组件依赖注入和管理应用程序中不同类文件的实例。基本上,这个组件实现了 Inversion of Control 模式。基于此,对象不再以构造函数接收参数或者使用setter的方式来实现注入,而是直接请求服务的依赖注入。这就大大降低了整体程序的复杂性,因为只有一个方法用以获得所需要的一个组件的依赖关系。此外,这种模式增强了代码的可测试性,从而使它不容易出错。在容器中注册服务¶框架本身或开发人员都可以注册服务。当一个组件A要求调用组件B(或它的类的一个实例),可以从容器中请求调用组件B,而不是创建组件B的一个实例。这种工作方式为我们提供了许多优点:我们可以更换一个组件,从他们本身或者第三方轻松创建。在组件发布之前,我们可以充分的控制对象的初始化,并对对象进行各种设置。我们可以使用统一的方式从组件得到一个结构化的全局实例服务可以通过以下几种方式注入到容器:01 <?php02 03 //Create the Dependency Injector Container04 $di = new PhalconDI();05 06 //By its class name07 $di->set("request", 'PhalconHttpRequest');08 09 //Using an anonymous function, the instance will lazy loaded10 $di->set("request", function(){11 return new PhalconHttpRequest();12 });13 14 //Registering directly an instance15 $di->set("request", new PhalconHttpRequest());16 17 //Using an array definition18 $di->set("request", array(19 "className" => 'PhalconHttpRequest'20 ));在上面的例子中,当向框架请求访问一个请求数据时,它将首先确定容器中是否存在这个”reqeust”名称的服务。容器会反回一个请求数据的实例,开发人员最终得到他们想要的组件。在上面示例中的每一种方法都有优缺点,具体使用哪一种,由开发过程中的特定场景来决定的。用一个字符串来设定一个服务非常简单,但缺少灵活性。设置服务时,使用数组则提供了更多的灵活性,而且可以使用较复杂的代码。lambda函数是两者之间一个很好的平衡,但也可能导致更多的维护管理成本。PhalconDI 提供服务的延迟加载。除非开发人员在注入服务的时候直接实例化一个对象,然后存存储到容器中。在容器中,通过数组,字符串等方式存储的服务都将被延迟加载,即只有在请求对象的时候才被初始化。01 <?php02 03 //Register a service "db" with a class name and its parameters04 $di->set("db", array(05 "className" => "PhalconDbAdapterPdoMysql",06 "parameters" => array(07 "parameter" => array(08 "host" => "localhost",09 "username" => "root",10 "password" => "secret",11 "dbname" => "blog"12 )13 )14 ));15 16 //Using an anonymous function17 $di->set("db", function(){18 return new PhalconDbAdapterPdoMysql(array(19 "host" => "localhost",20 "username" => "root",21 "password" => "secret",22 "dbname" => "blog"23 ));24 });以上这两种服务的注册方式产生相同的结果。然后,通过数组定义的,在后面需要的时候,你可以修改服务参数:1 <?php2 3 $di->setParameter("db", 0, array(4 "host" => "localhost",5 "username" => "root",6 "password" => "secret"7 ));从容器中获得服务的最简单方式就是使用”get”方法,它将从容器中返回一个新的实例:1 <?php $request = $di->get("request");或者通过下面这种魔术方法的形式调用:1 <?php2 3 $request = $di->getRequest();4 5 PhalconDI 同时允许服务重用,为了得到一个已经实例化过的服务,可以使用 getShared() 方法的形式来获得服务。具体的 PhalconHttpRequest 请求示例:1 <?php2 3 $request = $di->getShared("request");参数还可以在请求的时候通过将一个数组参数传递给构造函数的方式:1 <?php2 3 $component = $di->get("MyComponent", array("some-parameter", "other"))
2023年08月08日
12 阅读
0 评论
0 点赞
2023-08-08
话说 依赖注入(DI) or 控制反转(IoC)
话说 依赖注入(DI) or 控制反转(IoC)科普:首先依赖注入和控制反转说的是同一个东西,是一种设计模式,这种设计模式用来减少程序间的耦合,鄙人学习了一下,看TP官网还没有相关的文章,就写下这篇拙作介绍一下这种设计模式,希望能为TP社区贡献一些力量。首先先别追究这个设计模式的定义,否则你一定会被说的云里雾里,笔者就是深受其害,百度了N多文章,都是从理论角度来描述,充斥着大量的生涩词汇,要么就是java代码描述的,也生涩。不管怎么样,总算弄清楚一些了,下面就以php的角度来描述一下依赖注入这个概念。先假设我们这里有一个类,类里面需要用到数据库连接,按照最最原始的办法,我们可能是这样写这个类的:class example {private $_db; function __construct(){ include "./Lib/Db.php"; $this->_db = new Db("localhost","root","123456","test"); } function getList(){ $this->_db->query("......");//这里具体sql语句就省略不写了 }}复制代码过程:在构造函数里先将数据库类文件include进来;然后又通过new Db并传入数据库连接信息实例化db类;之后getList方法就可以通过$this->_db来调用数据库类,实现数据库操作。看上去我们实现了想要的功能,但是这是一个噩梦的开始,以后example1,example2,example3....越来越多的类需要用到db组件,如果都这么写的话,万一有一天数据库密码改了或者db类发生变化了,岂不是要回头修改所有类文件?ok,为了解决这个问题,工厂模式出现了,我们创建了一个Factory方法,并通过Factory::getDb()方法来获得db组件的实例:class Factory {public static function getDb(){ include "./Lib/Db.php"; return new Db("localhost","root","123456","test"); }}复制代码sample类变成:class example {private $_db; function __construct(){ $this->_db = Factory::getDb(); } function getList(){ $this->_db->query("......");//这里具体sql语句就省略不写了 }}复制代码这样就完美了吗?再次想想一下以后example1,example2,example3....所有的类,你都需要在构造函数里通过Factory::getDb();获的一个Db实例,实际上你由原来的直接与Db类的耦合变为了和Factory工厂类的耦合,工厂类只是帮你把数据库连接信息给包装起来了,虽然当数据库信息发生变化时只要修改Factory::getDb()方法就可以了,但是突然有一天工厂方法需要改名,或者getDb方法需要改名,你又怎么办?当然这种需求其实还是很操蛋的,但有时候确实存在这种情况,一种解决方式是:我们不从example类内部实例化Db组件,我们依靠从外部的注入,什么意思呢?看下面的例子:class example {private $_db; function getList(){ $this->_db->query("......");//这里具体sql语句就省略不写了 } //从外部注入db连接 function setDb($connection){ $this->_db = $connection; }} //调用$example = new example();$example->setDb(Factory::getDb());//注入db连接$example->getList();复制代码这样一来,example类完全与外部类解除耦合了,你可以看到Db类里面已经没有工厂方法或Db类的身影了。我们通过从外部调用example类的setDb方法,将连接实例直接注入进去。这样example完全不用关心db连接怎么生成的了。这就叫依赖注入,实现不是在代码内部创建依赖关系,而是让其作为一个参数传递,这使得我们的程序更容易维护,降低程序代码的耦合度,实现一种松耦合。这还没完,我们再假设example类里面除了db还要用到其他外部类,我们通过:$example->setDb(Factory::getDb());//注入db连接$example->setFile(Factory::getFile());//注入文件处理类$example->setImage(Factory::getImage());//注入Image处理类 ...复制代码我们没完没了的写这么多set?累不累?ok,为了不用每次写这么多行代码,我们又去弄了一个工厂方法:class Factory {public static function getExample(){ $example = new example(); $example->setDb(Factory::getDb());//注入db连接 $example->setFile(Factory::getFile());//注入文件处理类 $example->setImage(Factory::getImage());//注入Image处理类 return $expample; }}复制代码实例化example时变为:$example=Factory::getExample();$example->getList();复制代码似乎完美了,但是怎么感觉又回到了上面第一次用工厂方法时的场景?这确实不是一个好的解决方案,所以又提出了一个概念:容器,又叫做IoC容器、DI容器。我们本来是通过setXXX方法注入各种类,代码很长,方法很多,虽然可以通过一个工厂方法包装,但是还不是那么爽,好吧,我们不用setXXX方法了,这样也就不用工厂方法二次包装了,那么我们还怎么实现依赖注入呢?这里我们引入一个约定:在example类的构造函数里传入一个名为Di $di的参数,如下:class example {private $_di; function __construct(Di &$di){ $this->_di = $di; } //通过di容器获取db实例 function getList(){ $this->_di->get('db')->query("......");//这里具体sql语句就省略不写了 }}$di = new Di();$di->set("db",function(){ return new Db("localhost","root","root","test"); });$example = new example($di);$example->getList();复制代码Di就是IoC容器,所谓容器就是存放我们可能会用到的各种类的实例,我们通过$di->set()设置一个名为db的实例,因为是通过回调函数的方式传入的,所以set的时候并不会立即实例化db类,而是当$di->get('db')的时候才会实例化,同样,在设计di类的时候还可以融入单例模式。这样我们只要在全局范围内申明一个Di类,将所有需要注入的类放到容器里,然后将容器作为构造函数的参数传入到example,即可在example类里面从容器中获取实例。当然也不一定是构造函数,你也可以用一个 setDi(Di $di)的方法来传入Di容器,总之约定是你制定的,你自己清楚就行。这样一来依赖注入以及关键的容器概念已经介绍完毕,剩下的就是在实际中使用并理解它吧!本文如有错误,请务必指出,大家共同学习,共同进步。楼主讲完了 80% 还有剩下的 20% 精华部分没有说到。就像您说的 在 example 里面放入 Factory工厂对象 是一种依赖,不好。同样的。您在 example 里面放入 DI 对象依赖 也是不好的。那我们应该怎么做呢?答案就是: 所有涉及到实例化对象的地方,都不要用 new class() 去实例化了。我们用一个 叫 DI 的容器对象 去实例化 所有对象。假设我们现在需要 用ID容器 去实例化 example 对象。class example {private $_db; function __construct(db){ $this->_db = db } function getList(){ $this->_db->query("......");//这里具体sql语句就省略不写了 }}复制代码复制代码DI 对象有一个方法 ,用途是 分析类的构造函数的参数。这里我们分析 example 的构造函数, 发现有一个叫 db 的参数。DI 就会去实例化 db对象。 然后 把实例化的 db对象 传入 example 的构造函数。经过这个步骤。example 对象就创建出来了, 而且自动传入了 db对象。过程简单描述如下:1 将 db 对象注册到 DI 容器。2 DI对象分析 example 类。 发现需要一个叫 db 的对象3 DI查找 db 对象, 发现找到了, 实例化 db 对象。4 调用 example 对构造函数,传入 db。 这样 example 就实例化完成了。$example = DI->invoke('example')复制代码复制代码大概就是这样个过程吧。不知道说的对不对。 呵呵。
2023年08月08日
11 阅读
0 评论
0 点赞
2023-08-08
PHP程序员如何理解IoC/DI
PHP程序员如何理解IoC/DI思想思想是解决问题的根本思想必须转换成习惯构建一套完整的思想体系是开发能力成熟的标志——《简单之美》(前言).“成功的软件项目就是那些提交产物达到或超出客户的预期的项目,而且开发过程符合时间和费用上的要求,结果在面对变化和调整时有弹性。”——《面向对象分析与设计》(第3版)P.236术语介绍——引用《Spring 2.0 技术手册》林信良非侵入性 No intrusive框架的目标之一是非侵入性(No intrusive)组件可以直接拿到另一个应用或框架之中使用增加组件的可重用性(Reusability)容器(Container)管理对象的生成、资源取得、销毁等生命周期建立对象与对象之间的依赖关系启动容器后,所有对象直接取用,不用编写任何一行代码来产生对象,或是建立对象之间的依赖关系。IoC控制反转 Inversion of Control依赖关系的转移依赖抽象而非实践DI依赖注入 Dependency Injection不必自己在代码中维护对象的依赖容器自动根据配置,将依赖注入指定对象AOPAspect-oriented programming面向方面编程无需修改任何一行程序代码,将功能加入至原先的应用程序中,也可以在不修改任何程序的情况下移除。分层表现层:提供服务,显示信息。领域层:逻辑,系统中真正的核心。数据源层:与数据库、消息系统、事务管理器及其它软件包通信。——《企业应用架构模式》P.14代码演示IoC假设应用程序有储存需求,若直接在高层的应用程序中调用低层模块API,导致应用程序对低层模块产生依赖。/**高层 */class Business{private $writer; public function __construct() { $this->writer = new FloppyWriter(); } public function save() { $this->writer->saveToFloppy(); }}/**低层,软盘存储 */class FloppyWriter{public function saveToFloppy() { echo __METHOD__; }}$biz = new Business();$biz->save(); // FloppyWriter::saveToFloppy假设程序要移植到另一个平台,而该平台使用USB磁盘作为存储介质,则这个程序无法直接重用,必须加以修改才行。本例由于低层变化导致高层也跟着变化,不好的设计。正如前方提到的控制反转 Inversion of Control依赖关系的转移依赖抽象而非实践程序不应该依赖于具体的实现,而是要依赖抽像的接口。请看代码演示/**接口 */interface IDeviceWriter{public function saveToDevice();}/**高层 */class Business{/** * @var IDeviceWriter */ private $writer; /** * @param IDeviceWriter $writer */ public function setWriter($writer) { $this->writer = $writer; } public function save() { $this->writer->saveToDevice(); }}/**低层,软盘存储 */class FloppyWriter implements IDeviceWriter{public function saveToDevice() { echo __METHOD__; }}/**低层,USB盘存储 */class UsbDiskWriter implements IDeviceWriter{public function saveToDevice() { echo __METHOD__; }}$biz = new Business();$biz->setWriter(new UsbDiskWriter());$biz->save(); // UsbDiskWriter::saveToDevice$biz->setWriter(new FloppyWriter());$biz->save(); // FloppyWriter::saveToDevice控制权从实际的FloppyWriter转移到了抽象的IDeviceWriter接口上,让Business依赖于IDeviceWriter接口,且FloppyWriter、UsbDiskWriter也依赖于IDeviceWriter接口。这就是IoC,面对变化,高层不用修改一行代码,不再依赖低层,而是依赖注入,这就引出了DI。比较实用的注入方式有三种:Setter injection 使用setter方法Constructor injection 使用构造函数Property Injection 直接设置属性事实上不管有多少种方法,都是IoC思想的实现而已,上面的代码演示的是Setter方式的注入。依赖注入容器 Dependency Injection Container管理应用程序中的『全局』对象(包括实例化、处理依赖关系)。可以延时加载对象(仅用到时才创建对象)。促进编写可重用、可测试和松耦合的代码。理解了IoC和DI之后,就引发了另一个问题,引用Phalcon文档描述如下:如果这个组件有很多依赖, 我们需要创建多个参数的setter方法来传递依赖关系,或者建立一个多个参数的构造函数来传递它们,另外在使用组件前还要每次都创建依赖,这让我们的代码像这样不易维护//创建依赖实例或从注册表中查找$connection = new Connection();$session = new Session();$fileSystem = new FileSystem();$filter = new Filter();$selector = new Selector();//把实例作为参数传递给构造函数$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);// ... 或者使用setter$some->setConnection($connection);$some->setSession($session);$some->setFileSystem($fileSystem);$some->setFilter($filter);$some->setSelector($selector);假设我们必须在应用的不同地方使用和创建这些对象。如果当你永远不需要任何依赖实例时,你需要去删掉构造函数的参数,或者去删掉注入的setter。为了解决这样的问题,我们再次回到全局注册表创建组件。不管怎么样,在创建对象之前,它增加了一个新的抽象层:class SomeComponent{// ... /** * Define a factory method to create SomeComponent instances injecting its dependencies */ public static function factory() { $connection = new Connection(); $session = new Session(); $fileSystem = new FileSystem(); $filter = new Filter(); $selector = new Selector(); return new self($connection, $session, $fileSystem, $filter, $selector); } }瞬间,我们又回到刚刚开始的问题了,我们再次创建依赖实例在组件内部!我们可以继续前进,找出一个每次能奏效的方法去解决这个问题。但似乎一次又一次,我们又回到了不实用的例子中。一个实用和优雅的解决方法,是为依赖实例提供一个容器。这个容器担任全局的注册表,就像我们刚才看到的那样。使用依赖实例的容器作为一个桥梁来获取依赖实例,使我们能够降低我们的组件的复杂性:class SomeComponent{protected $_di; public function __construct($di) { $this->_di = $di; } public function someDbTask() { // 获得数据库连接实例 // 总是返回一个新的连接 $connection = $this->_di->get('db'); } public function someOtherDbTask() { // 获得共享连接实例 // 每次请求都返回相同的连接实例 $connection = $this->_di->getShared('db'); // 这个方法也需要一个输入过滤的依赖服务 $filter = $this->_di->get('filter'); } }$di = new Phalcon\DI();//在容器中注册一个db服务$di->set('db', function() {return new Connection(array( "host" => "localhost", "username" => "root", "password" => "secret", "dbname" => "invo" ));});//在容器中注册一个filter服务$di->set('filter', function() {return new Filter();});//在容器中注册一个session服务$di->set('session', function() {return new Session();});//把传递服务的容器作为唯一参数传递给组件$some = new SomeComponent($di);$some->someTask();这个组件现在可以很简单的获取到它所需要的服务,服务采用延迟加载的方式,只有在需要使用的时候才初始化,这也节省了服务器资源。这个组件现在是高度解耦。例如,我们可以替换掉创建连接的方式,它们的行为或它们的任何其他方面,也不会影响该组件。参考文章http://docs.phalconphp.com/zh/latest/reference/di.htmlWhat is Dependency Injection? Fabien PotencierInversion of Control Containers and the Dependency Injection pattern by Martin Fowler补充很多代码背后,都是某种哲学思想的体现。以下引用《面向模式的软件架构》卷1模式系统第六章模式与软件架构软件架构支持技术(开发软件时要遵循的基本原则)抽象封装信息隐藏分离关注点耦合与内聚充分、完整、简单策略与实现分离策略组件负责上下文相关决策,解读信息的语义和含义,将众多不同结果合并或选择参数值实现组件负责执行定义完整的算法,不需要作出与上下文相关的决策。上下文和解释是外部的,通常由传递给组件的参数提供。接口与实现分离接口部分定义了组件提供的功能以及如何使用该组件。组件的客户端可以访问该接口。实现部分包含实现组件提供的功能的实际代码,还可能包含仅供组件内部使用的函数和数据结构。组件的客户端不能访问其实现部分。单个引用点软件系统中的任何元素都应只声明和定义一次,避免不一致性问题。分而治之软件架构的非功能特性可修改性可维护性可扩展性重组可移植性互操作性与其它系统或环境交互效率可靠性容错:发生错误时确保行为正确并自行修复健壮性:对应用程序进行保护,抵御错误的使用方式和无效输入,确保发生意外错误时处于指定状态。可测试性可重用性通过重用开发软件开发软件时考虑重用
2023年08月08日
19 阅读
0 评论
0 点赞
2023-08-08
PHP程序员如何理解依赖注入容器(dependency injection container)
PHP程序员如何理解依赖注入容器(dependency injection container)背景知识传统的思路是应用程序用到一个Foo类,就会创建Foo类并调用Foo类的方法,假如这个方法内需要一个Bar类,就会创建Bar类并调用Bar类的方法,而这个方法内需要一个Bim类,就会创建Bim类,接着做些其它工作。<?php// 代码【1】class Bim{public function doSomething() { echo __METHOD__, '|'; }}class Bar{public function doSomething() { $bim = new Bim(); $bim->doSomething(); echo __METHOD__, '|'; }}class Foo{public function doSomething() { $bar = new Bar(); $bar->doSomething(); echo __METHOD__; }}$foo = new Foo();$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething使用依赖注入的思路是应用程序用到Foo类,Foo类需要Bar类,Bar类需要Bim类,那么先创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar类注入,再调用Foo方法,Foo调用Bar方法,接着做些其它工作。<?php// 代码【2】class Bim{public function doSomething() { echo __METHOD__, '|'; }}class Bar{private $bim; public function __construct(Bim $bim) { $this->bim = $bim; } public function doSomething() { $this->bim->doSomething(); echo __METHOD__, '|'; }}class Foo{private $bar; public function __construct(Bar $bar) { $this->bar = $bar; } public function doSomething() { $this->bar->doSomething(); echo __METHOD__; }}$foo = new Foo(new Bar(new Bim()));$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething这就是控制反转模式。依赖关系的控制反转到调用链的起点。这样你可以完全控制依赖关系,通过调整不同的注入对象,来控制程序的行为。例如Foo类用到了memcache,可以在不修改Foo类代码的情况下,改用redis。使用依赖注入容器后的思路是应用程序需要到Foo类,就从容器内取得Foo类,容器创建Bim类,再创建Bar类并把Bim注入,再创建Foo类,并把Bar注入,应用程序调用Foo方法,Foo调用Bar方法,接着做些其它工作.总之容器负责实例化,注入依赖,处理依赖关系等工作。代码演示 依赖注入容器 (dependency injection container)通过一个最简单的容器类来解释一下,这段代码来自 Twittee<?phpclass Container{private $s = array(); function __set($k, $c) { $this->s[$k] = $c; } function __get($k) { return $this->s[$k]($this); }}这段代码使用了魔术方法,在给不可访问属性赋值时,__set() 会被调用。读取不可访问属性的值时,__get() 会被调用。<?php$c = new Container();$c->bim = function () {return new Bim();};$c->bar = function ($c) {return new Bar($c->bim);};$c->foo = function ($c) {return new Foo($c->bar);};// 从容器中取得Foo$foo = $c->foo;$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething这段代码使用了匿名函数再来一段简单的代码演示一下,容器代码来自simple di container<?phpclass IoC{protected static $registry = []; public static function bind($name, Callable $resolver) { static::$registry[$name] = $resolver; } public static function make($name) { if (isset(static::$registry[$name])) { $resolver = static::$registry[$name]; return $resolver(); } throw new Exception('Alias does not exist in the IoC registry.'); }}IoC::bind('bim', function () {return new Bim();});IoC::bind('bar', function () {return new Bar(IoC::make('bim'));});IoC::bind('foo', function () {return new Foo(IoC::make('bar'));});// 从容器中取得Foo$foo = IoC::make('foo');$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething这段代码使用了后期静态绑定依赖注入容器 (dependency injection container) 高级功能真实的dependency injection container会提供更多的特性,如自动绑定(Autowiring)或 自动解析(Automatic Resolution)注释解析器(Annotations)延迟注入(Lazy injection)下面的代码在Twittee的基础上,实现了Autowiring。<?phpclass Bim{public function doSomething() { echo __METHOD__, '|'; }}class Bar{private $bim; public function __construct(Bim $bim) { $this->bim = $bim; } public function doSomething() { $this->bim->doSomething(); echo __METHOD__, '|'; }}class Foo{private $bar; public function __construct(Bar $bar) { $this->bar = $bar; } public function doSomething() { $this->bar->doSomething(); echo __METHOD__; }}class Container{private $s = array(); public function __set($k, $c) { $this->s[$k] = $c; } public function __get($k) { // return $this->s[$k]($this); return $this->build($this->s[$k]); } /** * 自动绑定(Autowiring)自动解析(Automatic Resolution) * * @param string $className * @return object * @throws Exception */ public function build($className) { // 如果是匿名函数(Anonymous functions),也叫闭包函数(closures) if ($className instanceof Closure) { // 执行闭包函数,并将结果 return $className($this); } /** @var ReflectionClass $reflector */ $reflector = new ReflectionClass($className); // 检查类是否可实例化, 排除抽象类abstract和对象接口interface if (!$reflector->isInstantiable()) { throw new Exception("Can't instantiate this."); } /** @var ReflectionMethod $constructor 获取类的构造函数 */ $constructor = $reflector->getConstructor(); // 若无构造函数,直接实例化并返回 if (is_null($constructor)) { return new $className; } // 取构造函数参数,通过 ReflectionParameter 数组返回参数列表 $parameters = $constructor->getParameters(); // 递归解析构造函数的参数 $dependencies = $this->getDependencies($parameters); // 创建一个类的新实例,给出的参数将传递到类的构造函数。 return $reflector->newInstanceArgs($dependencies); } /** * @param array $parameters * @return array * @throws Exception */ public function getDependencies($parameters) { $dependencies = []; /** @var ReflectionParameter $parameter */ foreach ($parameters as $parameter) { /** @var ReflectionClass $dependency */ $dependency = $parameter->getClass(); if (is_null($dependency)) { // 是变量,有默认值则设置默认值 $dependencies[] = $this->resolveNonClass($parameter); } else { // 是一个类,递归解析 $dependencies[] = $this->build($dependency->name); } } return $dependencies; } /** * @param ReflectionParameter $parameter * @return mixed * @throws Exception */ public function resolveNonClass($parameter) { // 有默认值则返回默认值 if ($parameter->isDefaultValueAvailable()) { return $parameter->getDefaultValue(); } throw new Exception('I have no idea what to do here.'); }}// ----$c = new Container();$c->bar = 'Bar';$c->foo = function ($c) {return new Foo($c->bar);};// 从容器中取得Foo$foo = $c->foo;$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething// ----$di = new Container();$di->foo = 'Foo';/* @var Foo $foo /$foo = $di->foo;var_dump($foo);/*Foo#10 (1) { private $bar => class Bar#14 (1) {private $bim => class Bim#16 (0) { }}}*/$foo->doSomething(); // Bim::doSomething|Bar::doSomething|Foo::doSomething以上代码的原理参考PHP官方文档:反射,PHP 5 具有完整的反射 API,添加了对类、接口、函数、方法和扩展进行反向工程的能力。 此外,反射 API 提供了方法来取出函数、类和方法中的文档注释。若想进一步提供一个数组访问接口,如$di->foo可以写成$di['foo'],则需用到ArrayAccess(数组式访问)接口 。一些复杂的容器会有许多特性,下面列出一些相关的github项目,欢迎补充。参考代码Twitteesimple di containerPimplePHP-DIDing推荐阅读PHP程序员如何理解IoC/DIPHP之道PHP最佳实践
2023年08月08日
13 阅读
0 评论
0 点赞
2023-08-08
用Gearman实现PHP的分布式处理
用Gearman实现PHP的分布式处理本机环境:Debian6 .0+ PHP5.3.3-7+squeeze1先安装任务分发的Job端安装Gearman server and library最新0.20版:wget http://launchpad.net/gearmand/trunk/0.20/+download/gearmand-0.20.tar.gztar zxf gearmand-0.20.tar.gzcd gearmand-0.20./configuresudo makesudo make install中间可能会遇到些问题:在./configure的时候,可能会出现缺少libraries的情况:checking for libevent… noconfigure: error: cannot find the flags to link with Boost program_optionsconfigure: error: libevent is required for gearmand. On Debian this can be found in libevent-dev. On RedHat this can be found in libevent-devel.可能系统缺少libboost、libevent和uuid等等,需要依次安装:sudo apt-get install libboost-program-options-devsudo apt-get install libevent-devsudo apt-get install uuid-dev安装完成后再重新配置安装,安装完成后执行sudo ldconfig因为本试验Client和Worker端都由PHP来实现,所以需要安装php的gearman扩展安装Gearman PHP extension:wget http://pecl.php.net/get/gearman-0.7.0.tgztar zxf gearman-0.7.0.tgzcd gearman-0.7.0phpize./configuresudo makesudo make install中间可能遇到的问题:找不到phpize命令,phpize在php开发包中,所以要先安装php5-devsudo apt-get install php5-dev安装完后,就可以在源码目录中执行phpize生成相关安装配置信息,接着执行后面的./configure等make install后,它告诉你一个目录,生成的gearman.so就在那里。根据需要考到相应PHP的扩展目录里(因为我直接用系统默认安装的php,它自动生成就在扩展中)接下来修改php.ini以使php加载该模块:php –ini看下php.ini在哪里,sudo vim 修改之,在其中加入extension = “gearman.so”然后,开始编写client和worker端client.php<?php$client= new GearmanClient();$client->addServer(“127.0.0.1″, 4730);print $client->do(“title”, “Linvo”);print “\n”;?>worker.php<?php$worker= new GearmanWorker();$worker->addServer(“127.0.0.1″, 4730);$worker->addFunction(“title”, “title_function”);while ($worker->work());function title_function($job){$str = $job->workload();return strlen($str);}?>准备工作已经完毕,试验开始1、启动jobgearmand -d2、启动workerphp -c /etc/php5/apache2/php.ini worker.php3、启动client(新开终端中打开)php -c /etc/php5/apache2/php.ini client.php屏幕显示字符串的长度 “5”这里,有几点需要说明一下:1、这里直接用php cli方式运行,添加-c参数是为了加载php.ini配置文件,以加载gearman扩展2、worker应该做成守护进程(CLI模式),可以开启多个,这样client发起的任务就会分发到各个worker分别来执行(自动负载均衡)这个例子由于太过简单,即使开启多个worker也无法看出效果,不过可以通过终止其中一个,可以看出系统自动切换到其他worker继续正常执行3、同理,client也是可以开启多个的(模型请参考之前的那边日志)4、同时,job也可以开启多个,以避免单点故障
2023年08月08日
8 阅读
0 评论
0 点赞
1
...
87
88
89
...
112