Yii Framework 核心之 Component part3

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.

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.

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.

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.

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.

<?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.

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.

<?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.

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

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.

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:

<?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.

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.

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.

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.

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.

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

<?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).

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.

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
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.

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.

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.

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.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值