CSS规则的specificity

05 Dec 2016 Category: CSS

  • 当Speficity值相等时,后来选择符居上。
  • 当Speficity值不相等时,Speficity值高的选择符生效。
  • 越具体的选择符越有更高的优先级数
  • 最后的CSS规则将覆盖任何之前或冲突的CSS规则。
  • 嵌入式样式的Speficity值高于其它。
  • ID选择符比属性选择符Speficity值要高。
  • 可用IDs去提高选择符的Speficity值
  • 另外,!important规则高于一切,慎用;继承的样式属式不参与优先级数值计算,低于其它规则

关于specificity的具体计算在各种情况下的数字加成有如下一般规则:

  • 每个ID选择符(#someid),加100
  • 每个class选择符(.someclass)、每个属性选择符(形如[attr=””]等)、每个伪类(形如:hover等)加10
  • 每个元素或伪元素(:firstchild)等,加1
  • 其他选择符包括全局选择符*,加0相当于没加,不过这也是一种specificity,后面会解释。
  • 按这些规则将数字串逐位相加,就得到最终计算得的specificity,然后在比较取舍时按照从左到右的顺序逐位比较

h1 {color: red;} 
//只有一个普通元素加成,结果是 1 
body h1 {color: green;} 
//两个普通元素加成,结果是 2 */ ——后者胜出 
h2.grape {color: purple;} 
//一个普通元素、一个class选择符加成,结果是 11*/ 
h2 {color: silver;} 
//一个普通元素,结果是 01 */ ——前者胜出 
html > body table tr[id=”totals”] td ul > li {color: maroon;} 
//7个普通元素、一个属性选择符、两个其他选择符,结果是17 */ 
li#answer {color: navy;} 
//一个ID选择符,一个普通选择符,结果是101 */ ——后者胜出

看一下实际的效果:

<head>
<meta charset="utf-8">
<title>demo</title>
<style>

.demoa {color:#777 !important;}

/* 10+1+10 = 21*/
.colortest a[id=testa] {color:#ccc;}

/*10+1+10 = 21*/
.colortest p .demoa {color:#666;}

/*10*/
.colortest {color:red;}

/*10+1 = 11*/
.colortest a {color:green;}

p a {color:#222;}

/* 10 + 10 = 20*/
.colortest .demoa {color:yellow;}	



</style>
</head>
<body>

<div class="colortest">
<p style="color:#999;">
<a id="testa" class="demoa" style="color:#111;"> a color test</a>
</p>
</div>
</body>

avatar

浏览器中显示的顺序就是样式优先级别的顺序。important除外。从结果可以看出:

  • important具有最高优先级别

  • 行内的specificity高于其他的样式specificity

  • 具有相同specificity,后面的样式覆盖前面的样式

  • 父元素的行内样式不影响specificity

查看更多

Yii2 源码学习 对象依赖注入(一)

30 Nov 2016 Category: PHP

在YII2中,实现对象依赖注入的功能主要通过\yii\base\di 下的相关文件实现。 对象依赖注入的机制有两种,

  • 控制反转(DI) Container
  • 服务定位器ServiceLocator

先看一下Yii中创建对象的函数,分析Container的使用



	public static function createObject($type, array $params = [])
    {
        if (is_string($type)) {
            return static::$container->get($type, $params);
        } elseif (is_array($type) && isset($type['class'])) {
            $class = $type['class'];
            unset($type['class']);
            return static::$container->get($class, $params, $type);
        } elseif (is_callable($type, true)) {
            return static::$container->invoke($type, $params);
        } elseif (is_array($type)) {
            throw new InvalidConfigException('Object configuration must be an array containing a "class" element.');
        } else {
            throw new InvalidConfigException('Unsupported configuration type: ' . gettype($type));
        }
    }


创建对象分三种情况

  • 根据类名创建createObject('app\models\Mymodel',['property'=>1]);
  • 根据配置数组创建createObject($config)
  • 根据回调函数创建
createObject(function(){
	return $object;
	},$params);

先看前面两种对象创建过程:

    public function get($class, $params = [], $config = [])
    {
        if (isset($this->_singletons[$class])) {
            // singleton
            return $this->_singletons[$class];
        } elseif (!isset($this->_definitions[$class])) {
            return $this->build($class, $params, $config);
        }

        $definition = $this->_definitions[$class];

        if (is_callable($definition, true)) {
            $params = $this->resolveDependencies($this->mergeParams($class, $params));
            $object = call_user_func($definition, $this, $params, $config);
        } elseif (is_array($definition)) {
            $concrete = $definition['class'];
            unset($definition['class']);

            $config = array_merge($definition, $config);
            $params = $this->mergeParams($class, $params);

            if ($concrete === $class) {
                $object = $this->build($class, $params, $config);
            } else {
                $object = $this->get($concrete, $params, $config);
            }

        } elseif (is_object($definition)) {
            return $this->_singletons[$class] = $definition;
        } else {
            throw new InvalidConfigException('Unexpected object definition type: ' . gettype($definition));
        }

        if (array_key_exists($class, $this->_singletons)) {
            // singleton
            $this->_singletons[$class] = $object;
        }

        return $object;
    }

  • 判断_singletons是否存在要获取的对象,如果有,则是获取单例对象,直接返回否则继续判断;
  • 如果对象不在_definitions里,则是第一次获取,创建一个新对象,并在_definitions缓存
  • 获取缓存里的数据
  • 如果缓存中的数据是一个回调函数,则通过回调函数获取最终的类实例,否则继续
  • 如果缓存中的数据是一个对象配置数组,则根据配置数组创建对象实例
  • 如果是一个对象实例,则把对象实例放在_singletons中,作为单例返回
创建对象的时候,对对象所依赖对象进行创建注入。

    protected function build($class, $params, $config)
    {
        /* @var $reflection ReflectionClass */
        list ($reflection, $dependencies) = $this->getDependencies($class);

        foreach ($params as $index => $param) {
            $dependencies[$index] = $param;
        }

        $dependencies = $this->resolveDependencies($dependencies, $reflection);
        if (!$reflection->isInstantiable()) {
            throw new NotInstantiableException($reflection->name);
        }
        if (empty($config)) {
            return $reflection->newInstanceArgs($dependencies);
        }

        if (!empty($dependencies) && $reflection->implementsInterface('yii\base\Configurable')) {
            // set $config as the last parameter (existing one will be overwritten)
            $dependencies[count($dependencies) - 1] = $config;
            return $reflection->newInstanceArgs($dependencies);
        } else {
            $object = $reflection->newInstanceArgs($dependencies);
            foreach ($config as $name => $value) {
                $object->$name = $value;
            }
            return $object;
        }
    }

    protected function getDependencies($class)
    {
        if (isset($this->_reflections[$class])) {
            return [$this->_reflections[$class], $this->_dependencies[$class]];
        }

        $dependencies = [];
        $reflection = new ReflectionClass($class);

        $constructor = $reflection->getConstructor();
        if ($constructor !== null) {
            foreach ($constructor->getParameters() as $param) {
                if ($param->isDefaultValueAvailable()) {
                    $dependencies[] = $param->getDefaultValue();
                } else {
                    $c = $param->getClass();
                    $dependencies[] = Instance::of($c === null ? null : $c->getName());
                }
            }
        }

        $this->_reflections[$class] = $reflection;
        $this->_dependencies[$class] = $dependencies;

        return [$reflection, $dependencies];
    }

    protected function resolveDependencies($dependencies, $reflection = null)
    {
        foreach ($dependencies as $index => $dependency) {
            if ($dependency instanceof Instance) {
                if ($dependency->id !== null) {
                    $dependencies[$index] = $this->get($dependency->id);
                } elseif ($reflection !== null) {
                    $name = $reflection->getConstructor()->getParameters()[$index]->getName();
                    $class = $reflection->getName();
                    throw new InvalidConfigException("Missing required parameter \"$name\" when instantiating \"$class\".");
                }
            }
        }
        return $dependencies;
    }
  • 通过类名,获取反射类,通过反射类获取类对象构造函数所需要的参数信息,如果参数是默认的参数,则将默认 参数值放入数组$dependencies,如果对象属于类,则把类名放入$dependencies。

  • 根据输入的参数$params,调用$this->resolveDependencies,将$dependencies依赖参数转换成实际类对象。获取要获取对象所依赖的对象数组$dependencies

  • 通过$reflection->newInstanceArgs($dependencies)实例化要获取的对象,如果有配置config,则还需要 通过config设置对象的相应属性。

通过回调函数对象的创建

在createObject函数中,只有当is_callable($type, true)条件成立的时候才会执行invoke方法。

函数里才会再次判断```is_callable($callback)```。只有当条件成立的时候才会执行注入操作,否则执行
简单的调用函数。


public function invoke(callable $callback, $params = [])
{
    if (is_callable($callback)) {
        return call_user_func_array($callback, $this->resolveCallableDependencies($callback, $params));
    } else {
        return call_user_func_array($callback, $params);
    }
}

public function resolveCallableDependencies(callable $callback, $params = [])
{
    if (is_array($callback)) {
        $reflection = new \ReflectionMethod($callback[0], $callback[1]);
    } else {
        $reflection = new \ReflectionFunction($callback);
    }

    $args = [];

    $associative = ArrayHelper::isAssociative($params);

    foreach ($reflection->getParameters() as $param) {
        $name = $param->getName();
        if (($class = $param->getClass()) !== null) {
            $className = $class->getName();
            if ($associative && isset($params[$name]) && $params[$name] instanceof $className) {
                $args[] = $params[$name];
                unset($params[$name]);
            } elseif (!$associative && isset($params[0]) && $params[0] instanceof $className) {
                $args[] = array_shift($params);
            } elseif (isset(Yii::$app) && Yii::$app->has($name) && ($obj = Yii::$app->get($name)) instanceof $className) {
                $args[] = $obj;
            } else {
                // If the argument is optional we catch not instantiable exceptions
                try {
                    $args[] = $this->get($className);
                } catch (NotInstantiableException $e) {
                    if ($param->isDefaultValueAvailable()) {
                        $args[] = $param->getDefaultValue();
                    } else {
                        throw $e;
                    }
                }

            }
        } elseif ($associative && isset($params[$name])) {
            $args[] = $params[$name];
            unset($params[$name]);
        } elseif (!$associative && count($params)) {
            $args[] = array_shift($params);
        } elseif ($param->isDefaultValueAvailable()) {
            $args[] = $param->getDefaultValue();
        } elseif (!$param->isOptional()) {
            $funcName = $reflection->getName();
            throw new InvalidConfigException("Missing required parameter \"$name\" when calling \"$funcName\".");
        }
    }

    foreach ($params as $value) {
        $args[] = $value;
    }
    return $args;
}

```

根据回调函数,通过反射方法获取方法的参数列表,根据函数传入的参数,以及反射方法获取的参数列表生成 函数所需要的参数,执行方法回调。

查看更多

Yii2 源码学习 Components

20 Nov 2016 Category: PHP

Yii 组件组成了Yii2绝大部分的功能。Controller属于组件。 Action属于组件,Model属于组件。Request属于组件,Response属于组件…只有几个 Object的子类,以及Exception类不是组件。组件实现了三个主要的功能:

  • Property 属性获取设置(继承于Object类)
  • Event 事件
  • Behavior 行为

因为继承于Object,实现了configure接口,组件可以通过配置方式创建,同时设置好 创建对象的属性。比如

component=>[
	'user'=>[
		'class'=>'yii\web\User'
		'enableAutoLogin' => true,
	],
	'response'=>[
		'class'=>'yii\web\Response',
		'on beforeSend' => function ($event) {
                $response = $event->sender;
                if ($response->data !== null) {
                    $response->data = [
                        'success' => $response->isSuccessful,
                        'data' => $response->data,
                    ];
                    $response->statusCode = 200;
                }
            }
	],
	...

]

在上面可以看出来有一个比较特别的属性,’on beforeSend’,但是Response中并没有on beforeSend 属性 因此会执行魔术方法__set。

    public function __set($name, $value)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            // set property
            $this->$setter($value);

            return;
        } elseif (strncmp($name, 'on ', 3) === 0) {
            // on event: attach event handler
            $this->on(trim(substr($name, 3)), $value);

            return;
        } elseif (strncmp($name, 'as ', 3) === 0) {
            // as behavior: attach behavior
            $name = trim(substr($name, 3));
            $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));

            return;
        } else {
            // behavior property
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canSetProperty($name)) {
                    $behavior->$name = $value;

                    return;
                }
            }
        }
        if (method_exists($this, 'get' . $name)) {
            throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
        } else {
            throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
        }
    }

在__set方法中,对输入属性进行判断,如果以on 开头的,则作为事件绑定。如果以as 开头的属性,则作为行为绑定。 如果属性不存在,同时没有对应的setter方法,则检查所绑定的行为中有没有对应的属性,进行设置。

在组件中,可以像调用自己的属性一样去调用行为的属性,以及方法。这主要通过魔术方法__get,__call实现。


    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            // read property, e.g. getName()
            return $this->$getter();
        } else {
            // behavior property
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canGetProperty($name)) {
                    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);
        }
    }

    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()");
    }

从函数中可以看出,只有当组件中属性或函数不存,才会去获取行为的属性和方法。

Component中的事件实现跟Event中的实现类似。


	public function on($name, $handler, $data = null, $append = true)
    {
        $this->ensureBehaviors();
        if ($append || empty($this->_events[$name])) {
            $this->_events[$name][] = [$handler, $data];
        } else {
            array_unshift($this->_events[$name], [$handler, $data]);
        }
    }

    public function off($name, $handler = null)
    {
        $this->ensureBehaviors();
        if (empty($this->_events[$name])) {
            return false;
        }
        if ($handler === null) {
            unset($this->_events[$name]);
            return true;
        } else {
            $removed = false;
            foreach ($this->_events[$name] as $i => $event) {
                if ($event[0] === $handler) {
                    unset($this->_events[$name][$i]);
                    $removed = true;
                }
            }
            if ($removed) {
                $this->_events[$name] = array_values($this->_events[$name]);
            }
            return $removed;
        }
    }

    
    public function trigger($name, $event = null)
    {
        $this->ensureBehaviors();
        if (!empty($this->_events[$name])) {
            if ($event === null) {
                $event = new Event;
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            foreach ($this->_events[$name] as $handler) {
                $event->data = $handler[1];
                call_user_func($handler[0], $event);
                // stop further handling if the event is handled
                if ($event->handled) {
                    return;
                }
            }
        }
        // invoke class-level attached handlers
        Event::trigger($this, $name, $event);
    }

区别有:

  • 事件触发的时候,如果event是null,则event赋值的是当前对象实例。Event中的是当前类全名。

  • 事件触发的时候,Component会触发当前类的类级别的事件。

  • 在事件绑定解绑,以及触发事件的时候都会检查注册的行为是否绑定了,如果没有绑定,则执行绑定操作。 确保在后续的操作中可以获取行为的属性,方法以及触发属性中的事件处理函数。

Behavior的绑定解绑实现:

Component将Behavior放在_behaviors中。绑定的时候,如果behavior 不是Behavior的子类,则根据behavior创建 一个对象。


    public function attachBehavior($name, $behavior)
    {
        $this->ensureBehaviors();
        return $this->attachBehaviorInternal($name, $behavior);
    }

    public function attachBehaviors($behaviors)
    {
        $this->ensureBehaviors();
        foreach ($behaviors as $name => $behavior) {
            $this->attachBehaviorInternal($name, $behavior);
        }
    }

    public function detachBehavior($name)
    {
        $this->ensureBehaviors();
        if (isset($this->_behaviors[$name])) {
            $behavior = $this->_behaviors[$name];
            unset($this->_behaviors[$name]);
            $behavior->detach();
            return $behavior;
        } else {
            return null;
        }
    }

    public function detachBehaviors()
    {
        $this->ensureBehaviors();
        foreach ($this->_behaviors as $name => $behavior) {
            $this->detachBehavior($name);
        }
    }

    private function attachBehaviorInternal($name, $behavior)
    {
        if (!($behavior instanceof Behavior)) {
            $behavior = Yii::createObject($behavior);
        }
        if (is_int($name)) {
            $behavior->attach($this);
            $this->_behaviors[] = $behavior;
        } else {
            if (isset($this->_behaviors[$name])) {
                $this->_behaviors[$name]->detach();
            }
            $behavior->attach($this);
            $this->_behaviors[$name] = $behavior;
        }
        return $behavior;
    }

查看更多

Yii2 源码学习 Behavior

10 Nov 2016 Category: PHP

Yii2的行为,用来在不修改组件主体代码的情况下,增强组件的功能。 行为可以将自己的方法以及属性注入到组件中。在组件中可以像使用自己的 方法和属性一样使用,通过$this直接调用。行为通过组件能响应被触发的事 件, 从而自定义或调整组件正常执行的代码。

Behavior类:

class Behavior extends Object
{

    public $owner;

    public function events()
    {
        return [];
    }

    public function attach($owner)
    {
        $this->owner = $owner;
        foreach ($this->events() as $event => $handler) {
            $owner->on($event, is_string($handler) ? [$this, $handler] : $handler);
        }
    }

    public function detach()
    {
        if ($this->owner) {
            foreach ($this->events() as $event => $handler) {
                $this->owner->off($event, is_string($handler) ? [$this, $handler] : $handler);
            }
            $this->owner = null;
        }
    }
}

行为主要提供两个方法,一个是行为绑定,一个是行为解绑。绑定的过程中, 通过events属性,对定义的组件事件以及事件响应函数进行绑定。实现在行 为中响应组件事件的功能。

比如要实现一个将数据添加排序字段的行为SortrableModelBehavior

class SortrableModelBehavior extends Behavior{
	 public function events()
    {
        return [
            ActiveRecord::EVENT_BEFORE_INSERT => 'findMaxOrderNum',
        ];
    }

    public function findMaxOrderNum($event)
    {
        if(!$this->owner->order_num) {
            $maxOrderNum = (int)(new \yii\db\Query())
                ->select('MAX(`order_num`)')
                ->from($this->owner->tableName())
                ->scalar();
            $this->owner->order_num = ++$maxOrderNum;
        }
    }
}

这样在ActiveRecord组件执行对象插入的时候就会获取当前最大排序值并加一赋值给新数据。 而在ActiveRecord组件中不需要做任何更改。

behavior中注意事项:

  • 在behavior中访问组件:$this->owner
  • behavior中$this指代行为本身
  • 如果behavior中的函数名或属性跟组件冲突的时候,访问的是组件的方法和属性,这个 从Component的属性获取的方式,和函数调用的魔术方法中就可以看出
    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            // read property, e.g. getName()
            return $this->$getter();
        } else {
            // behavior property
            $this->ensureBehaviors();
            foreach ($this->_behaviors as $behavior) {
                if ($behavior->canGetProperty($name)) {
                    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);
        }
    }

    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()");
    }

只用当组件中属性,方法不存在才会去检查所绑定行为中的属性和方法。

查看更多

Yii2 源码学习 Event

30 Oct 2016 Category: PHP

虽然PHP语言本身不能实现异步事件,但是并不代表事件在PHP中不是 一个非常有效的代码注入方式。TinkSNS中就有钩子,允许用户在不修 改主体业务代码的时候执行不同的操作。钩子对于一些活动,比如充 值送积分,充值打折扣。不同的活动,不一样,如果每次都要修改充 值业务的主体代码,不仅麻烦,而且容易产生bug。因此可以在充值成 功之后触发充值成功的事件,在事件处理中去处理相应的返现,优惠打 折操作。

看一下Event类的主体结构


class Event extends Object{

	public $name;
	public $sender;
	public $handled = false;
	private static $_events = [];
	public static function on($class, $name, $handler, $data = null, $append = true) {

	}

	public static function off($class, $name, $handler = null) {

	}

	public static function offAll() {}

	public static function hasHandlers($class, $name) {

	}

	public static function trigger($class, $name, $event = null) {

	}
}

整个类非常简单。事件绑定,事件解绑,解绑所有事件,是否绑定事件,触发事件五个函数,实现 事件处理机制。所有通过静态方法绑定的事件都存储在静态变量_events中。

1.事件绑定

	public static function on($class, $name, $handler, $data = null, $append = true) {
		$class = ltrim($class, '\\');
		if ($append || empty(self::$_events[$name][$class])) {
			self::$_events[$name][$class][] = [$handler, $data];
		} else {
			array_unshift(self::$_events[$name][$class], [$handler, $data]);
		}
	}

事件通过调用Event::on($class,$eventName,$handler,$data,$append)进行绑定。在event内部,相同类名, 相同事件名的事件handler和数据data放入数组中。根据$append的值添加到事件数组最末尾或最前面。

2.事件解绑


	public static function off($class, $name, $handler = null) {
		$class = ltrim($class, '\\');
		if (empty(self::$_events[$name][$class])) {
			return false;
		}
		if ($handler === null) {
			unset(self::$_events[$name][$class]);
			return true;
		} else {
			$removed = false;
			foreach (self::$_events[$name][$class] as $i => $event) {
				if ($event[0] === $handler) {
					unset(self::$_events[$name][$class][$i]);
					$removed = true;
				}
			}
			if ($removed) {
				self::$_events[$name][$class] = array_values(self::$_events[$name][$class]);
			}

			return $removed;
		}
	}

事件通过调用Event::off($class,$eventName,$handler)进行事件处理函数解绑。 通过handler的值解绑类的某个事件处理,删除在$_events中的数据。如果事件不存在,则返回false.如果handler 是空的,则解除这个类这个事件的所有事件处理函数。如果事件handler不为空,则删除指定的handler,并重新生 成事件handler数组(事件执行需要循环)。

3.事件触发


	public static function trigger($class, $name, $event = null) {
		if (empty(self::$_events[$name])) {
			return;
		}
		if ($event === null) {
			$event = new static;
		}
		$event->handled = false;
		$event->name = $name;

		if (is_object($class)) {
			if ($event->sender === null) {
				$event->sender = $class;
			}
			$class = get_class($class);
		} else {
			$class = ltrim($class, '\\');
		}

		$classes = array_merge(
			[$class],
			class_parents($class, true),
			class_implements($class, true)
		);

		foreach ($classes as $class) {
			if (!empty(self::$_events[$name][$class])) {
				foreach (self::$_events[$name][$class] as $handler) {
					$event->data = $handler[1];
					call_user_func($handler[0], $event);
					if ($event->handled) {
						return;
					}
				}
			}
		}
	}

在业务中,调用Event::trigger($class,$eventName,$event),触发事件,执行事件处理函数。 从_events数组中找到绑定改事件名的所有类,获取传入$class的类名,所有父类的类名,实现接口的 类名。判断这些类名是否在绑定改事件的类数组中,如果在则获取handler数组,调用call_user_func 执行。事件handler中,可以设置event->handled的值去停止执行后续事件处理函数。

从上面的触发机制可以看出来,子类触发事件,会触发父类的事件。因此在事件定义中,尽量把事 件安排在子类中。同时,如果需要传入在事件处理中传入参数,需要在事件绑定中传入data,不能通 过$event传入.

Yii中的事件分三个级别。一个是由Event绑定触发的类级别事件。还有就是由Commponent绑定的对象级别, 还有Yii::$app绑定触发的全局级别事件。

  • 类级别事件绑定触发
class Handler{
	public static afterPay($event){

	}
}
class Recharge{
	$chargeMoney;
	$user;

	public function pay(){
		//to-do pay for charge
		if($res)
		{
			Event::on(get_class($this),"AFTER_PAY",[Handler,afterPay],['money'=>$chargeMoney,'user'=>$user]);
			Event::trigger(get_class($this),"AFTER_PAY");
		}
	}
}

  • Commponent对象级别事件
class Recharge extends Component{


	public function pay(){
		//to-do pay for charge
		if($res)
		{
			$this->on("AFTER_PAY",[Handler,afterPay],['money'=>$chargeMoney,'user'=>$user]);
			$this->trigger("AFTER_PAY");
		}
	}	
}

Component 事件触发函数

    public function trigger($name, Event $event = null)
    {
        $this->ensureBehaviors();
        if (!empty($this->_events[$name])) {
            if ($event === null) {
                $event = new Event;
            }
            if ($event->sender === null) {
                $event->sender = $this;
            }
            $event->handled = false;
            $event->name = $name;
            foreach ($this->_events[$name] as $handler) {
                $event->data = $handler[1];
                call_user_func($handler[0], $event);
                // stop further handling if the event is handled
                if ($event->handled) {
                    return;
                }
            }
        }
        // invoke class-level attached handlers
        Event::trigger($this, $name, $event);
    }

可以看出,Component事件触发跟Event的触发类似,重点在触发函数设置sender。类级别的sender是类名 对象级别的sender是对象。

查看更多

Yii2 源码学习--yii\base\Object

24 Oct 2016 Category: PHP

在Yii2中,所有的类的都集成于基类Object。Object对象通过几个php的魔术方法, 实现属性获取,设置,属性是否存在,属性是否可设置的方法。

1.构造函数

    public function __construct($config = [])
    {
        if (!empty($config)) {
            Yii::configure($this, $config);
        }
        $this->init();
    }

Object实现接口Configurable,因此可以通过构造函数通过传入config配置数组对 对象属性进行注入。对象创建之后,调用init方法执行用户设置的初始化。因此, 在Yii2中,如果我们要在某个类初始化执行相应的操作,应该重写init函数,而不 是构造函数。

2.获取属性

$value = $object->propert

对于对象中定义的public属性,直接返回。如果获取的属性没有定义为public,或者 没有定义,将执行魔术方法_get。

    public function __get($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter();
        } elseif (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);
        }
    }

Object的魔术方法_get通过调用类中定义的get方法获取属性。如果对应的get方法不 存在,则根据对应set方法是否存在抛出属性不存在,或属性不可读的异常。

3.属性设置

    public function __set($name, $value)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            $this->$setter($value);
        } elseif (method_exists($this, 'get' . $name)) {
            throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
        } else {
            throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
        }
    }

Object的魔术方法_get通过调用类中定义的set方法获取属性。如果对应的set方法不 存在,则根据对应get方法是否存在抛出属性不存在,或属性不可设置的异常。

4.属性检测

    public function hasProperty($name, $checkVars = true)
    {
        return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
    }

    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);
    }

通过hasProperty判断属性是否存在(包括只读,只写的属性)。如果$checkVars为true,则检查类的property。否 则只检查set方法,get方法是否存在。

4.属性是否设置

当调用isset($object->property),如果property不存在,将会调用。如果该属性未定义或者该属性值为null,将返 回false

    public function __isset($name)
    {
        $getter = 'get' . $name;
        if (method_exists($this, $getter)) {
            return $this->$getter() !== null;
        } else {
            return false;
        }
    }

5.unset属性

当调用unset($object->property),如果property不存在,将会调用。如果该属性未定义值抛出异常,否则调用set方法 将属性设置成null

    public function __unset($name)
    {
        $setter = 'set' . $name;
        if (method_exists($this, $setter)) {
            $this->$setter(null);
        } elseif (method_exists($this, 'get' . $name)) {
            throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
        }
    }

查看更多

PHP 依赖注入

18 Aug 2016 Category: PHP

考虑一个问题,如果一个web应用需要一个日志服务,日志服务可以是文本,数据库或者邮件的形式, 而且日志需要将获取的信息格式化指定的形式。应用可以根据需要,任意切换日志服务是文本还是数 据库还是邮件。如果以传统的方式,日志记录的代码类似下面的形式:



    class Logger{
        public function formatLog($log)
        {
            //TO-DO format logger
            return $log;
        }

        public function log($str,$type)
        {
            $logTxt = $this->format($str);
            swtich($type)
            {
                case 'database':
                    $this->databaseLog($logTxt);break;
                case 'file':
                    $this->fileLog($$logTxt);break;
                case 'email':
                    $this->emailLog($$logTxt);break;
            }
        }

        public function databaseLog($str)
        {
            $db = new DbHelper();
            $db->save('logger',$str);
        }

        public function fileLog($str)
        {
            $file = new FileHelper();
            $file->save('logger',$str);
        }

        public function emailLog($str)
        {
            $email = new EmailHelper();
            $email->send(ADMIN_EMAIL,$str);
        }
        ...
    }


从上面的代码中可以看出来,logger类依赖数据库操作类DbHelper,依赖文件操作类FileHelper, 依赖邮箱操作类FileHelper。但是这些关系都是写在Logger这个类文件中的,耦合性高。如果项目 中希望在不同的页面将日志记录在不同的文件,数据表,或者发送给不同的管理者邮箱呢?这个时 候就需要对类的依赖在不同的地方进行注入。在百度百科上,依赖注入 说的太高深了,就是解除代码之间的耦合关系。

注入有一下几种方式

  • 通过构造函数传递参数
    class Logger{
        public $loggerHnadler;
        public __construct($loggerHandler)
        {
            $this->loggerHandler = $this->loggerHandler;
        }

        public function log($str)
        {
            $this->loggerHnadler->log($str);
        }
    }

    class FileLoggerHandler{
        $public $filePath;
        public __construct($filePath)
        {
            $this->filePath = $this->filePath;
        }

        public function log($str)
        {
            file_put_contents($this->filePath,$str);
        }
    }

    class EmailLoggerHandler{
        $public $adminMail;
        public __construct($adminMail)
        {
            $this->adminMail = $this->adminMail;
        }

        public function log($str)
        {
            sendEmail($this->adminMail,$str);
        }
    }

    //调用

    $fileLoggerHandler = new FileLoggerHandler(WEB_ROOT.'/logger/login.log');

    $logger = new Logger(fileLoggerHandler);
    $logger->log('login error'.getError());

    $emailLoggerHandler = new EmailLoggerHandler(DEV_ABB_EMAIL);
    $logger = new Logger(emailLoggerHandler);
    $logger->log('login error'.getError());


  • 通过set方式进行注入 这种方式跟构造函数类似,只不过是在实例化类之后对其属性进行复制设置。
    $logger = new Logger();
    $logger->setLoggerHandler(fileLoggerHandler);
    $logger->log();
    $logger->setLoggerHandler(emailLoggerHandler);
    $logger->log();

上面的两种注入方式都需要提前准备依赖的类对象。加速文件日志类依赖文件读写类,依赖 目录操作,权限验证等相关类文件,则需要按依赖顺序构造好相应的类对象,并且对各个对 象进行依赖的注入。所以需要重复很多对象创建,set的操作。因此就有了依赖注入容器 container,用于实现类的依赖对象的管理等操作。github上有一个最最简单的Container Twittee,简单而有效。

    class Container{
        public $cont = array();
        function __set($k, $c) { $this->s[$k]=$c; }
        function __get($k) { return $this->s[$k](); }
    }

    class Logger{
        public $container;

        public __construct()
        {
            $this->container = new Container();
        }

        public function addContainerData($key,$class)
        {
            $this->container->set($key,$class);
        }

        public function log($containerKey,$str)
        {
            $this->container->get($containerKey)->log();
        }
    }


    //使用

    $logger = new Logger();
    $logger->addContainnerData('fileLog','FileLoggerHandler');
    $logger->log('error info');

这个最简单的注入容器解决了依赖对象创建繁杂的问题。但是没有解决注入对象依赖问题。所以 一些框架的依赖注入容器中添加了获取对象依赖的操作:


    class Container{
        public $cont = array();
        function __set($k, $c) { $this->s[$k]=$c; }
        function __get($k) { return $this->s[$k](); }
        function getDepends($key)
        {
            $class = $this->s[$k];
            $dependencies = array();
            $ref = new ReflectionClass($class);//获取对象的实例
            $constructor = $ref->getConstructor();//获取对象的构造方法
            if($constructor !== null)
            {//如果构造方法有参数
                foreach($constructor->getParameters() as $param)
                {//获取构造方法的参数
                    if($param->isDefaultValueAvailable())
                    {//如果是默认 直接取默认值
                        $dependencies[] = $param->getDefaultValue();
                    }
                    else
                    {//将构造函数中的参数实例化
                        $temp = $param->getClass();
                        $temp = ($temp === null ? null : $temp->getName());
                        $temp = Instance::getInstance($temp);//这里使用Instance 类标示需要实例化 并且存储类的名字
                        $dependencies[] = $temp;
                    }
                }
            }
            $this->_reflections[$class] = $ref;
            $this->_dependencies[$class] = $dependencies;
        }
    }

    //使用

    $logger = new Logger();
    $logger->addContainnerData('fileLog','FileLoggerHandler');//这里会自动创建FileLoggerHandler依赖类对象
    $logger->log('error info');

查看更多

PHP GC学习

17 May 2016 Category: PHP

在5.2及更早版本的PHP中,没有专门的垃圾回收器GC(Garbage Collection),引擎在判断 一个变量空间是否能够被释放的时候是依据这个变量的zval的refcount的值,如果refcount 为0,那么变量的空间可以被释放,否则就不释放。思考一个问题:

$a = array('one');
$a[] = &$a;
unset($a);

在执行unset之前,PHP进程中变量的指向:

执行上述代码之后,PHP进程中变量的指向:

那么问题来了,那些在内存里的,没有被任何变量指向,但是指向自身引用的变量容器怎么 被回收?如果采用引用计数器的方式进行GC,那么如果出现上述这种自身引用的情况就容易 造成内存泄漏。PHP5.3之后引入一种新的垃圾回收机制,用于处理类似”垃圾”数据。

  • 如果一个zval的refcount增加,那么此zval还在使用,不属于垃圾
  • 如果一个zval的refcount减少到0, 那么zval可以被释放掉,不属于垃圾
  • 如果一个zval的refcount减少之后大于0,那么此zval还不能被释放,此zval可能成为一个垃圾,进一步判断

为了避免每次refcount减少都进行GC判断,算法会先把所有可能是垃圾的zval节点放入一个节点(root)缓冲区(root buffer),当缓冲区被节点塞满的时候,GC才开始开始对缓冲区中的zval节点进行垃圾判断。并且将这些zval节点标记成紫色

当缓冲区满了之后,算法以深度优先对每一个节点所包含的zval进行减1操作,为了确保不会对同一个zval的refcount重复执行减1操作,一旦zval的refcount减1之后会将zval标记成灰色。需要强调的是,这个步骤中,起初节点zval本身不做减1操作,但是如果节点zval中包含的zval又指向了节点zval(环形引用),那么这个时候需要对节点zval进行减1操作。

算法再次以深度优先判断每一个节点包含的zval的值,如果zval的refcount等于0,那么将其标记成白色(代表垃圾),如果zval的refcount大于0,那么将对此zval以及其包含的zval进行refcount加1操作,这个是对非垃圾的还原操作,同时将这些zval的颜色变成黑色(zval的默认颜色属性)。 D:遍历zval节点,将C中标记成白色的节点zval释放掉。

查看更多