详解PHP的Yii框架中组件行为的属性注入和方法注入,yii框架
行为的属性和方法注入原理
上面我们了解到了行为的用意在于将自身的属性和方法注入给所依附的类。 那么Yii中是如何将一个行为 yii\base\Behavior 的属性和方法, 注入到一个 yii\base\Component 中的呢? 对于属性而言,是通过 __get() 和 __set() 魔术方法来实现的。 对于方法,是通过 __call() 方法。
属性的注入
以读取为例,如果访问 $Component->property1 ,Yii在幕后干了些什么呢? 这个看看 yii\base\Component::__get()
public function __get($name) { $getter = 'get' . $name; if (method_exists($this, $getter)) { return $this->$getter(); } else { // 注意这个 else 分支的内容,正是与 yii\base\Object::__get() 的 // 不同之处 $this->ensureBehaviors(); foreach ($this->_behaviors as $behavior) { if ($behavior->canGetProperty($name)) { // 属性在行为中须为 public。否则不可能通过下面的形式访问呀。 return $behavior->$name; } } } if (method_exists($this, 'set' . $name)) { throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name); } else { throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name); } }
重点来看 yii\base\Compoent::__get() 与 yii\base\Object::__get() 的不同之处。 就是在于对于未定义getter函数之后的处理, yii\base\Object 是直接抛出异常, 告诉你想要访问的属性不存在之类。 但是 yii\base\Component 则是在不存在getter之后,还要看看是不是注入的行为的属性:
首先,调用了 $this->ensureBehaviors() 。这个方法已经在前面讲过了,主要是确保行为已经绑定。
在确保行为已经绑定后,开始遍历 $this->_behaviors 。 Yii将类所有绑定的行为都保存在 yii\base\Compoent::$_behaviors[] 数组中。
最后,通过行为的 canGetProperty() 判断这个属性, 是否是所绑定行为的可读属性,如果是,就返回这个行为的这个属性 $behavior->name 。 完成属性的读取。 至于 canGetProperty() 已经在 :ref::property 部分已经简单讲过了, 后面还会有针对性地一个介绍。
对于setter,代码类似,这里就不占用篇幅了。
方法的注入
与属性的注入通过 __get() __set() 魔术方法类似, Yii通过 __call() 魔术方法实现对行为中方法的注入:
public function __call($name, $params) { $this->ensureBehaviors(); foreach ($this->_behaviors as $object) { if ($object->hasMethod($name)) { return call_user_func_array([$object, $name], $params); } } throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()"); }
从上面的代码中可以看出,Yii还是先是调用了 $this->ensureBehaviors() 确保行为已经绑定。
然后,也是遍历 yii\base\Component::$_behaviros[] 数组。 通过 hasMethod() 方法判断方法是否存在。 如果所绑定的行为中要调用的方法存在,则使用PHP的 call_user_func_array() 调用之。 至于 hasMethod() 方法,我们后面再讲。
注入属性与方法的访问控制
在前面我们针对行为中public和private、protected的成员在所绑定的类中是否可访问举出了具体例子。 这里我们从代码层面解析原因。
在上面的内容,我们知道,一个属性可不可访问,主要看行为的 canGetProperty() 和 canSetProperty() 。 而一个方法可不可调用,主要看行为的 hasMethod() 。 由于 yii\base\Behavior 继承自我们的老朋友 yii\base\Object ,所以上面提到的三个判断方法, 事实上代码都在 Object 中。我们一个一个来看:
public function canGetProperty($name, $checkVars = true) { return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name); } public function canSetProperty($name, $checkVars = true) { return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name); } public function hasMethod($name) { return method_exists($this, $name); }
这三个方法真的谈不上复杂。对此,我们可以得出以下结论:
当向Component绑定的行为读取(写入)一个属性时,如果行为为该属性定义了一个getter (setter),则可以访问。 或者,如果行为确实具有该成员变量即可通过上面的判断,此时,该成员变量可为 public, private, protected。 但最终只有 public 的成员变量才能正确访问。原因在上面讲注入的原理时已经交待了。
当调用Component绑定的行为的一个方法时,如果行为已经定义了该方法,即可通过上面的判断。 此时,这个方法可以为 public, private, protected。 但最终只有 public 的方法才能正确调用。如果你理解了上一款的原因,那么这里也就理解了。
依赖注入容器
依赖注入(Dependency Injection,DI)容器就是一个对象,它知道怎样初始化并配置对象及其依赖的所有对象。Martin 的文章 已经解释了 DI 容器为什么很有用。这里我们主要讲解 Yii 提供的 DI 容器的使用方法。
依赖注入
Yii 通过 yii\di\Container 类提供 DI 容器特性。它支持如下几种类型的依赖注入:
- 构造方法注入;
- Setter 和属性注入;
- PHP 回调注入.
- 构造方法注入
在参数类型提示的帮助下,DI 容器实现了构造方法注入。当容器被用于创建一个新对象时,类型提示会告诉它要依赖什么类或接口。容器会尝试获取它所依赖的类或接口的实例,然后通过构造器将其注入新的对象。例如:
class Foo { public function __construct(Bar $bar) { } } $foo = $container->get('Foo'); // 上面的代码等价于: $bar = new Bar; $foo = new Foo($bar);
Setter 和属性注入
Setter 和属性注入是通过配置提供支持的。当注册一个依赖或创建一个新对象时,你可以提供一个配置,该配置会提供给容器用于通过相应的 Setter 或属性注入依赖。例如:
use yii\base\Object; class Foo extends Object { public $bar; private $_qux; public function getQux() { return $this->_qux; } public function setQux(Qux $qux) { $this->_qux = $qux; } } $container->get('Foo', [], [ 'bar' => $container->get('Bar'), 'qux' => $container->get('Qux'), ]);
PHP 回调注入
这种情况下,容器将使用一个注册过的 PHP 回调创建一个类的新实例。回调负责解决依赖并将其恰当地注入新创建的对象。例如:
$container->set('Foo', function () { return new Foo(new Bar); }); $foo = $container->get('Foo');
注册依赖关系
可以用 yii\di\Container::set() 注册依赖关系。注册会用到一个依赖关系名称和一个依赖关系的定义。依赖关系名称可以是一个类名,一个接口名或一个别名。依赖关系的定义可以是一个类名,一个配置数组,或者一个 PHP 回调。
$container = new \yii\di\Container; // 注册一个同类名一样的依赖关系,这个可以省略。 $container->set('yii\db\Connection'); // 注册一个接口 // 当一个类依赖这个接口时,相应的类会被初始化作为依赖对象。 $container->set('yii\mail\MailInterface', 'yii\swiftmailer\Mailer'); // 注册一个别名。 // 你可以使用 $container->get('foo') 创建一个 Connection 实例 $container->set('foo', 'yii\db\Connection'); // 通过配置注册一个类 // 通过 get() 初始化时,配置将会被使用。 $container->set('yii\db\Connection', [ 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', 'username' => 'root', 'password' => '', 'charset' => 'utf8', ]); // 通过类的配置注册一个别名 // 这种情况下,需要通过一个 “class” 元素指定这个类 $container->set('db', [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', 'username' => 'root', 'password' => '', 'charset' => 'utf8', ]); // 注册一个 PHP 回调 // 每次调用 $container->get('db') 时,回调函数都会被执行。 $container->set('db', function ($container, $params, $config) { return new \yii\db\Connection($config); }); // 注册一个组件实例 // $container->get('pageCache') 每次被调用时都会返回同一个实例。 $container->set('pageCache', new FileCache);
Tip: 如果依赖关系名称和依赖关系的定义相同,则不需要通过 DI 容器注册该依赖关系。
通过 set() 注册的依赖关系,在每次使用时都会产生一个新实例。可以使用 yii\di\Container::setSingleton() 注册一个单例的依赖关系:
$container->setSingleton('yii\db\Connection', [ 'dsn' => 'mysql:host=127.0.0.1;dbname=demo', 'username' => 'root', 'password' => '', 'charset' => 'utf8', ]);
解决依赖关系
注册依赖关系后,就可以使用 DI 容器创建新对象了。容器会自动解决依赖关系,将依赖实例化并注入新创建的对象。依赖关系的解决是递归的,如果一个依赖关系中还有其他依赖关系,则这些依赖关系都会被自动解决。
可以使用 yii\di\Container::get() 创建新的对象。该方法接收一个依赖关系名称,它可以是一个类名,一个接口名或一个别名。依赖关系名或许是通过 set() 或 setSingleton() 注册的。你可以随意地提供一个类的构造器参数列表和一个configuration 用于配置新创建的对象。例如:
// "db" 是前面定义过的一个别名 $db = $container->get('db'); // 等价于: $engine = new \app\components\SearchEngine($apiKey, ['type' => 1]); $engine = $container->get('app\components\SearchEngine', [$apiKey], ['type' => 1]);
代码背后,DI 容器做了比创建对象多的多的工作。容器首先将检查类的构造方法,找出依赖的类或接口名,然后自动递归解决这些依赖关系。
如下代码展示了一个更复杂的示例。UserLister 类依赖一个实现了 UserFinderInterface 接口的对象;UserFinder 类实现了这个接口,并依赖于一个 Connection 对象。所有这些依赖关系都是通过类构造器参数的类型提示定义的。通过属性依赖关系的注册,DI 容器可以自动解决这些依赖关系并能通过一个简单的 get('userLister') 调用创建一个新的 UserLister 实例。
namespace app\models; use yii\base\Object; use yii\db\Connection; use yii\di\Container; interface UserFinderInterface { function findUser(); } class UserFinder extends Object implements UserFinderInterface { public $db; public function __construct(Connection $db, $config = []) { $this->db = $db; parent::__construct($config); } public function findUser() { } } class UserLister extends Object { public $finder; public function __construct(UserFinderInterface $finder, $config = []) { $this->finder = $finder; parent::__construct($config); } } $container = new Container; $container->set('yii\db\Connection', [ 'dsn' => '...', ]); $container->set('app\models\UserFinderInterface', [ 'class' => 'app\models\UserFinder', ]); $container->set('userLister', 'app\models\UserLister'); $lister = $container->get('userLister'); // 等价于: $db = new \yii\db\Connection(['dsn' => '...']); $finder = new UserFinder($db); $lister = new UserLister($finder);
实践中的运用
当在应用程序的入口脚本中引入 Yii.php 文件时,Yii 就创建了一个 DI 容器。这个 DI 容器可以通过 Yii::$container 访问。当调用 Yii::createObject() 时,此方法实际上会调用这个容器的 yii\di\Container::get() 方法创建新对象。如上所述,DI 容器会自动解决依赖关系(如果有)并将其注入新创建的对象中。因为 Yii 在其多数核心代码中都使用了 Yii::createObject() 创建新对象,所以你可以通过 Yii::$container 全局性地自定义这些对象。
例如,你可以全局性自定义 yii\widgets\LinkPager 中分页按钮的默认数量:
\Yii::$container->set('yii\widgets\LinkPager', ['maxButtonCount' => 5]);
这样如果你通过如下代码在一个视图里使用这个挂件,它的 maxButtonCount 属性就会被初始化为 5 而不是类中定义的默认值 10。
echo \yii\widgets\LinkPager::widget();
然而你依然可以覆盖通过 DI 容器设置的值:
echo \yii\widgets\LinkPager::widget(['maxButtonCount' => 20]);
另一个例子是借用 DI 容器中自动构造方法注入带来的好处。假设你的控制器类依赖一些其他对象,例如一个旅馆预订服务。你可以通过一个构造器参数声明依赖关系,然后让 DI 容器帮你自动解决这个依赖关系。
namespace app\controllers; use yii\web\Controller; use app\components\BookingInterface; class HotelController extends Controller { protected $bookingService; public function __construct($id, $module, BookingInterface $bookingService, $config = []) { $this->bookingService = $bookingService; parent::__construct($id, $module, $config); } }
如果你从浏览器中访问这个控制器,你将看到一个报错信息,提醒你 BookingInterface 无法被实例化。这是因为你需要告诉 DI 容器怎样处理这个依赖关系。
\Yii::$container->set('app\components\BookingInterface', 'app\components\BookingService');
现在如果你再次访问这个控制器,一个 app\components\BookingService 的实例就会被创建并被作为第三个参数注入到控制器的构造器中。
什么时候注册依赖关系
由于依赖关系在创建新对象时需要解决,因此它们的注册应该尽早完成。如下是推荐的实践:
如果你是一个应用程序的开发者,你可以在应用程序的入口脚本或者被入口脚本引入的脚本中注册依赖关系。
如果你是一个可再分发扩展的开发者,你可以将依赖关系注册到扩展的引导类中。
总结
依赖注入和服务定位器都是流行的设计模式,它们使你可以用充分解耦且更利于测试的风格构建软件。强烈推荐你阅读 Martin 的文章,对依赖注入和服务定位器有个更深入的理解。
Yii 在依赖住入(DI)容器之上实现了它的服务定位器。当一个服务定位器尝试创建一个新的对象实例时,它会把调用转发到 DI 容器。后者将会像前文所述那样自动解决依赖关系。
您可能感兴趣的文章:
- PHP的Yii框架中移除组件所绑定的行为的方法
- PHP的Yii框架中行为的定义与绑定方法讲解
- 详解在PHP的Yii框架中使用行为Behaviors的方法
- 深入讲解PHP的Yii框架中的属性(Property)
- PHP的Yii框架中使用数据库的配置和SQL操作实例教程
- 深入解析PHP的Yii框架中的event事件机制
- 全面解读PHP的Yii框架中的日志功能
- Yii使用find findAll查找出指定字段的实现方法
- 解析yii数据库的增删查改
- Yii PHP Framework实用入门教程(详细介绍)

防止會話固定攻擊的有效方法包括:1.在用戶登錄後重新生成會話ID;2.使用安全的會話ID生成算法;3.實施會話超時機制;4.使用HTTPS加密會話數據,這些措施能確保應用在面對會話固定攻擊時堅不可摧。

實現無會話身份驗證可以通過使用JSONWebTokens(JWT)來實現,這是一種基於令牌的認證系統,所有的必要信息都存儲在令牌中,無需服務器端會話存儲。 1)使用JWT生成和驗證令牌,2)確保使用HTTPS防止令牌被截獲,3)在客戶端安全存儲令牌,4)在服務器端驗證令牌以防篡改,5)實現令牌撤銷機制,如使用短期訪問令牌和長期刷新令牌。

PHP會話的安全風險主要包括會話劫持、會話固定、會話預測和會話中毒。 1.會話劫持可以通過使用HTTPS和保護cookie來防範。 2.會話固定可以通過在用戶登錄前重新生成會話ID來避免。 3.會話預測需要確保會話ID的隨機性和不可預測性。 4.會話中毒可以通過對會話數據進行驗證和過濾來預防。

銷毀PHP會話需要先啟動會話,然後清除數據並銷毀會話文件。 1.使用session_start()啟動會話。 2.用session_unset()清除會話數據。 3.最後用session_destroy()銷毀會話文件,確保數據安全和資源釋放。

如何改變PHP的默認會話保存路徑?可以通過以下步驟實現:在PHP腳本中使用session_save_path('/var/www/sessions');session_start();設置會話保存路徑。在php.ini文件中設置session.save_path="/var/www/sessions"來全局改變會話保存路徑。使用Memcached或Redis存儲會話數據,如ini_set('session.save_handler','memcached');ini_set(

tomodifyDataNaphPsession,startTheSessionWithSession_start(),然後使用$ _sessionToset,修改,orremovevariables.1)startThesession.2)setthesession.2)使用$ _session.3)setormodifysessessvariables.3)emovervariableswithunset()

在PHP會話中可以存儲數組。 1.啟動會話,使用session_start()。 2.創建數組並存儲在$_SESSION中。 3.通過$_SESSION檢索數組。 4.優化會話數據以提升性能。

PHP會話垃圾回收通過概率機制觸發,清理過期會話數據。 1)配置文件中設置觸發概率和會話生命週期;2)可使用cron任務優化高負載應用;3)需平衡垃圾回收頻率與性能,避免數據丟失。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

SAP NetWeaver Server Adapter for Eclipse
將Eclipse與SAP NetWeaver應用伺服器整合。

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

MantisBT
Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SecLists
SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。