Yii组件架构的内幕,第3部分

tech2023-11-30  32

Welcome to the last article in this three-part whirlwind tour of the Yii framework’s component class. The goal of the series is to show you how Yii implements a component-based architecture and how its CComponent class handles the implementation details by making clever use of PHP’s magic methods.

欢迎来到这个由三部分组成的旋风式教程,介绍Yii框架的组件类的最后一篇文章。 本系列的目的是向您展示Yii如何实现基于组件的体系结构,以及其CComponent类如何通过巧妙地使用PHP的魔术方法来处理实现细节。

In the first article you learned how Yii implements properties and a simple, but effective, configuration system. In the second, you learned how to implement event-based programming in your projects and how Yii uses magic methods to declare and bind events. In this article I want to focus on how you can use behaviors to dynamically modify the functionality of objects at runtime.

在第一篇文章中,您了解了Yii如何实现属性和简单但有效的配置系统。 在第二篇中,您学习了如何在项目中实现基于事件的编程,以及Yii如何使用魔术方法声明和绑定事件。 在本文中,我想重点介绍如何使用行为在运行时动态修改对象的功能。

A behavior, as it is called in Yii, is a manner of combining objects at runtime to extend an object’s functionality. Behaviors are an excellent way to decouple code and keep ever expanding systems maintainable.

在Yii中称为行为 ,是一种在运行时组合对象以扩展对象功能的方式。 行为是使代码脱钩并保持不断扩展的系统可维护性的极好方法。

In the PHP object model, you can reuse functionality by extending a base class and the new class inherits the functions and properties from the parent. Inheritance is a great programming tool, however in complex systems it can very quickly become limiting. What happens when you have two or more classes with functionality relevant to a new class? We can not reuse our code effectively because PHP doesn’t support multiple inheritance. The Yii behavior system is a way of achieving multiple inheritance by implementing mixins.

在PHP对象模型中,您可以通过扩展基类来重用功能,而新类将从父类继承函数和属性。 继承是一个很棒的编程工具,但是在复杂的系统中,继承会很快变得局限。 当您有两个或多个具有与新类相关的功能的类时,会发生什么? 我们无法有效地重用我们的代码,因为PHP不支持多重继承。 Yii行为系统是一种通过实现mixin实现多重继承的方法。

用户的构建位 (Building Bits of a User)

Let’s take a simple example. A new application you are building requires the user to connect to a third-party billing and credit card management service. You could dump this functionality into a generic user class, but before long your user class would grow very big and cluttered. Instead, it would be better to isolate the functionality and then only add it to a user object if its used. But how?

让我们举一个简单的例子。 您正在构建的新应用程序要求用户连接到第三方账单和信用卡管理服务。 您可以将此功能转储到通用用户类中,但是不久之后您的用户类将变得非常庞大且混乱。 相反,最好隔离功能,然后仅将其添加到用户对象(如果已使用)。 但是如何?

Aha! I know! Let’s add the functionality to a behavior! I bet you didn’t see that one coming.

啊哈! 我知道! 让我们将功能添加到行为中! 我敢打赌,您没有看到那个来临。

First we create a UserAccountBehavior class. Behaviors need to extend from the CBehavior base class, and CBehavior extends from CComponent meaning behaviors themselves can also have behaviors, events, and properties — but let’s not get too carried away.

首先,我们创建一个UserAccountBehavior类。 行为需要从CBehavior基类进行扩展,而CBehavior从CComponent扩展,这意味着行为本身也可以具有行为,事件和属性-但不要太过分。

<?php Class UserAccountBehavior extends CBehavior { public function getAccountInfomation() { // connect to a service to get credit card details and payment info return $paymentService->getDetails($this->owner->id); } }

Inside the behavior, $this->owner returns the object that this behavior has been attached to. As you will only be attaching this behavior to user objects, you know $this->owner will be a user object (which in Yii is an instance of the CWebUser class). Therefore you can access the user’s ID by using $this->owner->id.

在行为内部, $this->owner返回此行为已附加到的对象。 因为您只会将此行为附加到用户对象,所以您知道$this->owner将是一个用户对象(在Yii中是CWebUser类的实例)。 因此,您可以使用$this->owner->id来访问用户的ID。

Now you need to attach the behavior to the user object at runtime. In Yii, the CWebUser class represents the logged in user. Yii creates a static instance of CWebUser and stores it in the user property of the application and you can refer to it using Yii::app()->user.

现在,您需要在运行时将行为附加到用户对象。 在Yii中, CWebUser类表示已登录的用户。 Yii创建一个CWebUser静态实例并将其存储在应用程序的user属性中,您可以使用Yii::app()->user引用它。

<?php $user = Yii::app()->user; $user->attachBehavior("account", new UserAccountBehavior()); echo $user->getAccountInformation();

There you have it, first you retrieved the user object using Yii::app()->user, then called the attachBehavior() method and passed in the behavior object to attach. The user object now has access to the additional methods defined by the behavior. In this example getAccountInformation() is called on the user object which in turn calls the getAccountInformation() method of the UserAccountBehavior object.

有了它,首先使用Yii::app()->user检索了用户对象,然后调用了attachBehavior()方法并传递了要附加的行为对象。 用户对象现在可以访问由行为定义的其他方法。 在这个例子中getAccountInformation()被称为用户对象进而调用上getAccountInformation()的方法UserAccountBehavior对象。

You can also access the UserAccountBehavior object itself using $user->account, so $user->account->getAccountInformation() would be the same as calling $user->getAccountInformation().

您还可以使用$user->account访问UserAccountBehavior对象本身,因此$user->account->getAccountInformation()与调用$user->getAccountInformation() 。

乐高积木利用行为和事件 (Lego Blocks Utilizing Behaviors and Events)

A good example of where behaviors can be most useful is when combined with the active record pattern. Using behaviors, you can define a runtime records behavior by building it in a series of blocks.

当行为与活动记录模式结合使用时,行为最有用的一个很好的例子。 使用行为,可以通过在一系列块中进行构建来定义运行时记录行为。

For instance, an active record class representing a blog post may need to have several similar things. You may want it to have a “tagable” behavior so users can add tags and search for records using them. A tagable behavior might provide an easy to use findAllByTags(), getTags() and setTags() methods.

例如,代表博客文章的活动记录类可能需要具有一些类似的内容。 您可能希望它具有“可标记”的行为,以便用户可以添加标记并使用它们搜索记录。 可标记的行为可能会提供易于使用的findAllByTags() , getTags()和setTags()方法。

You may also want a soft delete behavior so when a user deletes a record a deleted flag is added but the record is not removed from the database itself to preserve historical data. The behavior can interact with its parent object (in this case the post record) and modify the standard delete functionality, but it may also expose a new deleteForever() method to permanently remove the record.

您可能还需要一种软删除行为,因此,当用户删除记录时,会添加一个已删除标志,但不会从数据库本身删除该记录以保留历史数据。 该行为可以与其父对象(在这种情况下为后记录)进行交互,并修改标准的删除功能,但它也可以公开新的deleteForever()方法以永久删除记录。

It’s worth noting here that behaviors can not override methods in its parent class as is normally the case with class inheritance, but you can write your code to allow events to handle such situations. For example, before running the class’ default delete method you can first check if the delete has already been handled. If it has, you know that some code somewhere (perhaps in a behavior) has dealt with the delete and has everything under control. If you cast your mind back to part two of this series you saw that I passed a CEvent object when raising events; this object has a handled property and here you would check to see if it is true or false. For example:

此处值得注意的是,行为不能像类继承那样通常在其父类中覆盖方法,但是您可以编写代码以允许事件处理此类情况。 例如,在运行类的默认删除方法之前,您可以首先检查是否已经处理了删除。 如果已删除,则您知道某个地方的某些代码(可能是某种行为)已经处理了删除操作,并且所有内容都处于受控状态。 如果您回想起本系列的第二部分,您会看到在引发事件时我传递了一个CEvent对象。 该对象具有处理的属性,在这里您将检查它是对还是错。 例如:

<?php public function beforeDelete() { $event = new CEvent; // raises and calls events attached to "onBeforeDelete" $this->onBeforeDelete($event); if ($event->handled) { return true; } else { return $this->_delete(); } }

Now for the soft delete behavior you can attach the onBeforeDelete() method, set the deleted property in the database and set the event object’s handled property to true.

现在,对于软删除行为,您可以附加onBeforeDelete()方法,在数据库中设置Deleted属性,并将事件对象的handled属性设置为true。

Yii provides a CActiveRecordBehavior behavior which exposes and automatically binds methods to the following events: onBeforeSave, onAfterSave, onBeforeDelete, onAfterDelete, onBeforeFind, and onAfterFind. When creating behaviors for active record objects, you can extend from this class. Note that you can also manipulate the beforeFind() and afterFind() methods to modify the retrieval query criteria to prevent records marked as deleted from being returned.

Yii提供了一个CActiveRecordBehavior行为,该行为公开并自动将方法绑定到以下事件: onBeforeSave , onAfterSave , onBeforeDelete , onAfterDelete , onBeforeFind和onAfterFind 。 为活动记录对象创建行为时,可以从此类扩展。 请注意,您还可以操纵beforeFind()和afterFind()方法来修改检索查询条件,以防止返回标记为已删除的记录。

Another example might be that you want this record to represent nodes within a tree so you can create a hierarchy of posts; you can add a tree behavior giving access to additional methods such as addChild(), getChildren(), getAncestors(), etc. The list of behaviors could go on and on forever, but the best part is that these can be stored as individual files like little Lego blocks enabling you to build up objects with advanced functionality from existing code very quickly.

另一个示例可能是您希望该记录表示树中的节点,以便您可以创建帖子的层次结构。 您可以添加树行为,以访问其他方法,例如addChild() , getChildren() , getAncestors()等。行为列表可以永远存在,但是最好的部分是这些行为可以单独存储像小块乐高积木一样的文件,使您可以快速从现有代码中构建具有高级功能的对象。

Yii的魔术 (Yii’s Magic)

Now I’ll dive straight into the implementation details of how Yii creates its behavior system. This will give you an understanding of what is happening under the hood so that you can better understand how to use behaviors in your own programming. You’ll also see how you might implement your own behavior system in your own framework or project.

现在,我将直接介绍Yii如何创建其行为系统的实现细节。 这将使您了解实际情况,以便您可以更好地了解如何在自己的编程中使用行为。 您还将看到如何在自己的框架或项目中实现自己的行为系统。

The behavior system will need the following ingredients:

行为系统将需要以下要素:

A method of attaching an object as a behavior

一种将对象附加为行为的方法 A method of storing all attached behaviors on an object

一种将所有附加行为存储在对象上的方法 Some magic methods to work out if the functionality has been delegated to one of the attached behavior objects

一些神奇的方法可以解决功能是否已委派给附加的行为对象之一的问题 A method to remove behaviors

一种消除行为的方法

Let’s start with a method for attaching behaviors:

让我们从一种附加行为的方法开始:

<?php public function attachBehavior($name, $behavior) { $behavior->attach($this); return $this->_m[$name]=$behavior; }

This function to attach a behavior is quite simple; it calls the behavior’s attach() method which is defined by the CBehavior base class in Yii. It receives a pointer to the current object $this to allow the behavior to track which object it is attached to.

这个附加行为的功能非常简单; 它会调用行为的attach()方法,该方法由Yii中的CBehavior基类定义。 它接收到指向当前对象$this的指针,以允许该行为跟踪其附加到的对象。

Inside the CBehavior class which all behaviors must extends is the definition of the attach() method:

在CBehavior类中,所有行为都必须扩展的是attach()方法的定义:

<?php public function attach($owner) { $this->owner = $owner; }

As I mentioned earlier, this allows you to use $this->owner inside your behavior object to refer to it’s owner (the object it is currently attached to).

如前所述,这允许您在行为对象内部使用$this->owner来引用其所有者(当前附加到该对象的对象)。

Next, a method of storing all attached behaviors is needed. This has actually already been demonstrated in the attachBehavior() method with this code $this->_m[$name] = $behavior. To store a list of behaviors, you simply save them to an associative array keyed by the behavior name. Now you can attach behaviors to any object that subclasses CComponent.

接下来,需要一种存储所有附加行为的方法。 实际上,已经在attachBehavior()方法中使用此代码$this->_m[$name] = $behavior attachBehavior() $this->_m[$name] = $behavior进行了演示。 要存储行为列表,只需将它们保存到以行为名称为键的关联数组中。 现在,您可以将行为附加到CComponent子类的任何对象。

Now there needs to be a way to enable interacting with these behaviors and call the correct methods when necessary. The PHP magic __call method suits this perfectly.

现在需要一种方法来与这些行为进行交互,并在必要时调用正确的方法。 PHP魔术__call方法非常适合。

<?php public function __call($name,$parameters) { if ($this->_m!==null) { foreach($this->_m as $object) { if (method_exists($object,$name)) { return call_user_func_array(array($object, $name), $parameters); } } throw new Exception(get_class($this) . " and its behaviors do not have a method named '" . $name . "'"); }

When calling an undefined method of an object, PHP will invoke the magic __call method. The name of the method that was called is passed in as the $name argument and the arguments that were in the method call are passed in as $parameters. Now all the code needs to do is simply loop through all the attached behaviors (stored in $this->_m) and check if they contain a method with the same name as was just called. If the method does exist in a behavior, then it is invoked and the results are returned. If no matching method is found in any of the behaviors, an exception is thrown complaining about just that.

当调用对象的未定义方法时,PHP将调用魔术__call方法。 调用的方法的名称作为$name参数传递,而方法调用中的$parameters作为$parameters传递。 现在,所有代码所需要做的就是简单地遍历所有附加的行为(存储在$this->_m ),并检查它们是否包含与刚才调用的名称相同的方法。 如果该方法确实存在于行为中,则将调用该方法并返回结果。 如果在任何行为中都没有找到匹配的方法,则抛出异常来抱怨。

In a nut shell, that’s it – your very own behavior system. I’ve based these examples heavily on code in the CComponent class of the Yii framework, and simplified them a fair bit for the purpose of this article, but feel free to download the framework and have a peruse yourself.

简而言之,就是您自己的行为系统。 我已将这些示例大量地基于Yii框架的CComponent类中的代码,并出于本文的目的对它们进行了一些简化,但可以随时下载该框架并仔细阅读。

摘要 (Summary)

Together we have gone on a journey to discover ways we can better encapsulate our code and make reusable components. Yii’s component class shows how easy you can achieve very complex and clever patterns in PHP with relative ease, from simple properties and configuration to events and behaviors.

我们一起走上了探索更好地封装代码并制造可重用组件的方式。 Yii的组件类展示了从简单的属性和配置到事件和行为,可以相对容易地在PHP中轻松实现非常复杂和聪明的模式。

I hope you’ve enjoyed this three-part series and you now have a few more tools at your disposal in your programming toolbox. If you have never used events or behaviors in your programming before, it’s definitely worth taking these ideas out for a test drive. As with most programming the concepts do not fully sink in until you try them out yourself and apply them in real world situations.

我希望您喜欢这个由三部分组成的系列,现在您可以在编程工具箱中使用更多工具。 如果您以前从未在编程中使用过事件或行为,那么绝对值得将这些想法用于测试。 与大多数编程一样,这些概念只有在您亲自尝试并将其应用到现实世界中之后,才能完全融入其中。

翻译自: https://www.sitepoint.com/under-the-hood-of-yiis-component-architecture-part-3/

最新回复(0)