   'phpDocumentor\Descriptor\FileDescriptor * hash f67d58854d475ade1a0f9c19661f2f70 * pathPools/CoroutineRedisProxy.php	 * source7<?php
/**
 * CoroutineRedisProxy
 *
 * @author tmtbe
 * @author camera360_server@camera360.com
 * @copyright Chengdu pinguo Technology Co.,Ltd.
 */

namespace PG\MSF\Pools;

use PG\MSF\Coroutine\Redis;
use PG\MSF\Helpers\Context;
use PG\MSF\Marco;

/**
 * Class CoroutineRedisProxy
 * @package PG\MSF\Pools
 */
class CoroutineRedisProxy
{
    /**
     * @var RedisAsynPool Redis连接池
     */
    private $redisAsynPool;

    /**
     * @var string Redis key前缀
     */
    public $keyPrefix = '';

    /**
     * @var bool 是否需要hash key
     */
    public $hashKey = false;

    /**
     * @var bool 是否启用PHP序列化
     */
    public $phpSerialize = false;

    /**
     * @var bool 是否启用Redis序列化
     */
    public $redisSerialize = false;

    /**
     * CoroutineRedisProxy constructor.
     *
     * @param RedisAsynPool $redisAsynPool Redis连接池对象
     */
    public function __construct(RedisAsynPool $redisAsynPool)
    {
        $this->redisAsynPool  = $redisAsynPool;
        $this->hashKey        = $redisAsynPool->hashKey;
        $this->phpSerialize   = $redisAsynPool->phpSerialize;
        $this->keyPrefix      = $redisAsynPool->keyPrefix;
        $this->redisSerialize = $redisAsynPool->redisSerialize;
    }

    /**
     * redis cache 操作封装
     *
     * @param Context $context 请求上下文对象
     * @param string $key Redis Key
     * @param string $value Redis Value
     * @param int $expire 过期时间，单位秒
     * @return mixed|Redis
     */
    public function cache($context, string $key, $value = '', int $expire = 0)
    {
        if ($value === '') {
            $commandData = [$context, $key];
            $command = 'get';
        } else {
            if (!empty($expire)) {
                $command = 'setex';
                $commandData = [$context, $key, $expire, $value];
            } else {
                $command = 'set';
                $commandData = [$context, $key, $value];
            }
        }

        return $this->__call($command, $commandData);
    }

    /**
     * 在redis执行lua脚本，因为redis原生方法eval是php的保留关键字，所以用该方法名代替
     * 会根据$args参数中的缓存key进行redis实例选择
     *
     * @author niulingyun
     *
     * @param Context $context 上下文
     * @param string $script Lua脚本代码
     * @param array $args 参数
     * @param int $numKeys 脚本用到的redis的key的数量
     *
     * @return array
     *
     * redis中执行的Lua脚本，不支持直接返回多维数组，需要将多维数组转成json返回
     * 本方法的返回值会对Lua脚本的返回值进行封装：
     * 1. 如果脚本中需要返回多个key的操作结果，Lua脚本的返回值格式请如下，需要是json：
     * @example {
     *              key1 : '' or array(),
     *              key2 : '' or array(),
     *              ...
     *          }
     * 本方法会将各个实例的返回值按照缓存key进行合并，然后返作为数组返回
     * @example [
     *              key1 => '' or array(),
     *              key2 => '' or array(),
     *              ...
     *          ]
     *
     * 2. 如果只有一个key的操作结果，Lua脚本的返回值就无需按照key封装成数组，比如：
     * @example true 或者 array(value1, value2, ...)
     * 本方法会将这些值直接返回
     *
     * 3. 如果redis中本身存的就是未做序列化的json，该方法会自己将json解析成数组返回：
     * @example array(value1, value2, ...)
     *
     * @throws
     */
    public function evalMock($context, string $script, array $args = array(), int $numKeys = 0)
    {
        $keys         = array_slice($args, 0, $numKeys);
        $evalMockArgs = array_slice($args, $numKeys);

        if (!empty($keys)) {
            foreach ($keys as $i => $key) {
                $keys[$i] = $this->generateUniqueKey($key);
            }
        }

        if (getInstance()->isTaskWorker()) {//task进程
            $commandData = [$context, $script, array_merge($keys, $evalMockArgs), $numKeys];
        } else {
            $commandData = array_merge([$context, $script, $numKeys], $keys, $evalMockArgs);
        }

        return $this->__call('eval', $commandData);
    }

    /**
     * __call魔术方法
     *
     * @param string $name Redis指令
     * @param array $arguments Redis指令参数
     * @return array|bool|mixed
     */
    public function __call(string $name, array $arguments)
    {
        // key start， 排除eval
        if (isset($arguments[1]) && $name !== 'eval') {
            $key = $arguments[1];
            if (is_array($key)) {
                // mset mget mdelete等为空
                if (empty($key)) {
                    return false;
                }
                $isAssoc = array_keys($key) !== range(0, count($key) - 1); //true关联 false索引
                $newKey = [];
                foreach ($key as $k => $v) {
                    if ($isAssoc) {
                        $newKey[$this->generateUniqueKey($k)] = $v;
                    } else {
                        $newKey[$k] = $this->generateUniqueKey($v);
                    }
                }
            } else {
                $newKey = $this->generateUniqueKey($key);
            }
            $arguments[1] = $newKey;
        }

        //多key操作特殊逻辑
        switch (strtolower($name)) {
            //kkk...
            case 'sdiff':
            case 'sdiffstore':
            case 'sunion':
            case 'sunionstore':
                foreach ($arguments as $k => $argument) {
                    if ($k >= 2) {
                        $arguments[$k] = $this->generateUniqueKey($k);
                    }
                }
                break;
            //kkv or kk
            case 'smove':
            case 'rpoplpush':
            case 'brpoplpush':
                $arguments[2] = $this->generateUniqueKey(2);
                break;
        }
        // key end

        // value serialize start
        switch (strtolower($name)) {
            case 'set':
                $arguments[2] = $this->serializeHandler($arguments[2], true);
                break;
            case 'setex':
                $arguments[3] = $this->serializeHandler($arguments[3], true);
                break;
            case 'mset':
                $keysValues = $arguments[1];
                $newValues = [];
                foreach ($keysValues as $k => $v) {
                    $newValues[$k] = $this->serializeHandler($v, true);
                }
                $arguments[1] = $newValues;
                break;
            case 'sadd':
            case 'srem':
                //member是array
                if (is_array($arguments[2])) {
                    $newValues = [];
                    foreach ($arguments[2] as $v) {
                        $newValues[] = $this->serializeHandler($v);
                    }
                    $arguments[2] = $newValues;
                } else {
                    foreach ($arguments as $k => $argument) {
                        if ($k >= 2) {
                            $arguments[$k] = $this->serializeHandler($argument);
                        }
                    }
                }
                break;
            case 'zadd':
            case 'hset':
                $argument[3] = $this->serializeHandler($arguments[3]);
                break;
            case 'hmset':
                $keysValues = $arguments[2];
                $newValues = [];
                foreach ($keysValues as $k => $v) {
                    $newValues[$k] = $this->serializeHandler($v);
                }
                $arguments[2] = $newValues;
                break;
        }
        // value serialize end

        if (getInstance()->isTaskWorker()) {//如果是task进程自动转换为同步模式
            /**
             * @var Context $context
             */
            $context = array_shift($arguments);
            $context->getLog()->profileStart('redis.' . $name);
            $value = $this->redisAsynPool->getSync()->$name(...$arguments);
            $context->getLog()->profileEnd('redis.' . $name);
            // return value unserialize start
            switch ($name) {
                case 'mget';
                    $keys = $this->arguments[0];
                    $len = strlen($this->keyPrefix);
                    $value = $this->unSerializeHandler($value, $keys, $len);
                    break;
                case 'eval':
                    //如果redis中的数据本身没有进行序列化，同时返回值是json，那么解析成array
                    $decodeVal = @json_decode($value, true);
                    if (is_array($decodeVal)) {
                        $value = $decodeVal;
                    }
                    $value = $this->unSerializeHandler($value);
                    break;
                default:
                    $value = $this->unSerializeHandler($value);
            }
            // return value unserialize end

            return $value;
        } else {
            return $arguments[0]->getObjectPool()->get(Redis::class, [$this->redisAsynPool, $name, array_slice($arguments, 1)]);
        }
    }

    /**
     * 生成唯一Redis Key
     *
     * @param string $key a key identifying a value to be cached
     * @return string a key generated from the provided key which ensures the uniqueness across applications
     */
    protected function generateUniqueKey(string $key)
    {
        return $this->hashKey ? md5($this->keyPrefix . $key) : $this->keyPrefix . $key;
    }

    /**
     * 序列化
     *
     * @param mixed $data 待序列化数据
     * @param bool $phpSerialize 是否启动PHP序列化
     * @return string
     */
    protected function serializeHandler($data, $phpSerialize = false)
    {
        try {
            //只有set、mset等才需要
            if ($this->phpSerialize && $phpSerialize) {
                $data = [$data, null];
                switch ($this->phpSerialize) {
                    case Marco::SERIALIZE_PHP:
                        $data = serialize($data);
                        break;
                    case Marco::SERIALIZE_IGBINARY:
                        $data = @igbinary_serialize($data);
                        break;
                }
            }

            switch ($this->redisSerialize) {
                case Marco::SERIALIZE_PHP:
                    $data = serialize($data);
                    break;
                case Marco::SERIALIZE_IGBINARY:
                    $data = @igbinary_serialize($data);
                    break;
            }
        } catch (\Exception $exception) {
            // do noting
        }

        return $data;
    }

    /**
     * 反序列化
     *
     * @param mixed $data 响应数据
     * @param array $keys 操作的多个Key列表
     * @param int $len 操作的数量
     * @return array|bool|mixed
     */
    protected function unSerializeHandler($data, $keys = [], $len = 0)
    {
        // 如果值是null，直接返回false
        if (null === $data) {
            return false;
        }

        if ('OK' === $data) {
            return $data;
        }

        try {
            //mget
            if (!empty($keys) && is_array($data)) {
                $ret = [];
                array_walk($data, function ($val, $k) use ($keys, $len, &$ret) {
                    if (!is_null($val)) {
                        $key = substr($keys[$k], $len);
                        $val = $this->realUnserialize($val);
                        $ret[$key] = $val;
                    }
                });

                $data = $ret;
            } elseif (is_array($data) && empty($keys)) {
                //eval sRandMember...
                $ret = [];
                array_walk($data, function ($val, $k) use (&$ret) {
                    if (is_array($val)) {
                        foreach ($val as $i => $v) {
                            $val[$i] = $this->realUnserialize($v);
                        }
                    } else {
                        $val = $this->realUnserialize($val);
                    }

                    $ret[$k] = $val;
                });

                $data = $ret;
            } else {
                //get
                $data = $this->realUnserialize($data);
            }
        } catch (\Exception $exception) {
            // do noting
        }

        return $data;
    }

    /**
     * 是否可以反序列化
     *
     * @param string $string 待反序列化数据
     * @return bool
     */
    private function canUnserialize(string $string)
    {
        return in_array(substr($string, 0, 2), ['s:', 'i:', 'b:', 'N', 'a:', 'O:', 'd:']);
    }

    /**
     * 真正反序列化
     *
     * @param string $data 待反序列化数据
     * @return mixed|string
     */
    private function realUnserialize($data)
    {
        //get
        if (is_string($data) && $this->redisSerialize) {
            switch ($this->redisSerialize) {
                case Marco::SERIALIZE_PHP:
                    if ($this->canUnserialize($data)) {
                        $data = unserialize($data);
                    }
                    break;
                case Marco::SERIALIZE_IGBINARY:
                    $data = @igbinary_unserialize($data);
                    break;
            }
        }

        if (is_string($data) && $this->phpSerialize) {
            switch ($this->phpSerialize) {
                case Marco::SERIALIZE_PHP:
                    if ($this->canUnserialize($data)) {
                        $data = unserialize($data);
                    }
                    break;
                case Marco::SERIALIZE_IGBINARY:
                    $data = @igbinary_unserialize($data);
                    break;
            }

            //兼容yii逻辑
            if (is_array($data) && count($data) === 2 && array_key_exists(1, $data) && $data[1] === null) {
                $data = $data[0];
            }
        }
        return $data;
    }
}
 * namespaceAliases#phpDocumentor\Descriptor\Collection * itemsRedis\PG\MSF\Coroutine\RedisContext\PG\MSF\Helpers\ContextMarco\PG\MSF\Marco * includes	  * constants	  * functions	 
 * classes	!\PG\MSF\Pools\CoroutineRedisProxy(phpDocumentor\Descriptor\ClassDescriptor	 * parent * implements	  * abstract * final	  * properties	redisAsynPool+phpDocumentor\Descriptor\PropertyDescriptor" * types 
 * default 	 * static * visibilityprivate * fqsen0\PG\MSF\Pools\CoroutineRedisProxy::redisAsynPool * name * namespace 
 * package
 * summary * description * fileDescriptor  * line * tags	var	 *phpDocumentor\Descriptor\Tag\VarDescriptor * variableName	 3phpDocumentor\Descriptor\Type\UnknownTypeDescriptor$\PG\MSF\Pools\RedisAsynPool$,(Redis连接池	 * errors	 2	  * inheritedElement 	keyPrefix" '' public",\PG\MSF\Pools\CoroutineRedisProxy::keyPrefix$4% &'() *+	,	 -.	 .phpDocumentor\Descriptor\Type\StringDescriptor $,(Redis key前缀2	 2	 3 hashKey" false 6"*\PG\MSF\Pools\CoroutineRedisProxy::hashKey$:% &'() *#+	,	 -.	 /phpDocumentor\Descriptor\Type\BooleanDescriptor $,(是否需要hash key2	 2	 3 phpSerialize" ; 6"/\PG\MSF\Pools\CoroutineRedisProxy::phpSerialize$?% &'() *(+	,	 -.	 = $,(是否启用PHP序列化2	 2	 3 redisSerialize" ; 6"1\PG\MSF\Pools\CoroutineRedisProxy::redisSerialize$B% &'() *-+	,	 -.	 = $,(是否启用Redis序列化2	 2	 3 
 * methods		__construct)phpDocumentor\Descriptor\MethodDescriptor" 6 * arguments	$redisAsynPool+phpDocumentor\Descriptor\ArgumentDescriptor	 * method"U	 /$0  * byReference * isVariadic"$I% &'(Redis连接池对象) * +	 2	 3 "0\PG\MSF\Pools\CoroutineRedisProxy::__construct()$F% &' CoroutineRedisProxy constructor.() *4+	param	 ,phpDocumentor\Descriptor\Tag\ParamDescriptor.I"Y$Q(N2	 return	 2	 3 cacheG" 6H	$contextJK"k	 /$ LM"$U% &'(请求上下文对象) * +	 2	 3 $keyJK"k	 8  LM"$W% &'(	Redis Key) * +	 2	 3 $valueJK"k	 8 5LM"$Y% &'(Redis Value) * +	 2	 3 $expireJK"k	 /phpDocumentor\Descriptor\Type\IntegerDescriptor 0LM"$[% &'(过期时间，单位秒) * +	 2	 3 "*\PG\MSF\Pools\CoroutineRedisProxy::cache()$T% &'redis cache 操作封装() *F+	Q	 R.U"o$Q(V2	 R.W"w$Q(X2	 R.Y"$Q(Z2	 R.["$Q(^2	 S	 -phpDocumentor\Descriptor\Tag\ReturnDescriptor	 /$mixed/$$S(2	 2	 3 evalMockG" 6H	UJK"	 /$ LM"$U% &'(	上下文) * +	 2	 3 $scriptJK"	 8  LM"$e% &'(Lua脚本代码) * +	 2	 3 $argsJK"	 /$arrayarray()LM"$g% &'(参数) * +	 2	 3 $numKeysJK"	 \ ]LM"$k% &'(#脚本用到的redis的key的数量) * +	 2	 3 "-\PG\MSF\Pools\CoroutineRedisProxy::evalMock()$c% &'在redis执行lua脚本，因为redis原生方法eval是php的保留关键字，所以用该方法名代替
会根据$args参数中的缓存key进行redis实例选择() *}+	author	 -phpDocumentor\Descriptor\Tag\AuthorDescriptor$o(
niulingyun2	 Q	 R.U"$Q(d2	 R.e"$Q(f2	 R.g"$Q(j2	 R.k"$Q(l2	 S	 a	 /$h$S(redis中执行的Lua脚本，不支持直接返回多维数组，需要将多维数组转成json返回
本方法的返回值会对Lua脚本的返回值进行封装：
1. 如果脚本中需要返回多个key的操作结果，Lua脚本的返回值格式请如下，需要是json：2	 example	 .phpDocumentor\Descriptor\Tag\ExampleDescriptor * filePath{ * lineCount  * startingLine
 * example** File not found : { **$s(key1 : '' or array(),
             key2 : '' or array(),
             ...
         }
本方法会将各个实例的返回值按照缓存key进行合并，然后返作为数组返回2	 tu[w xy** File not found : [ **$s(key1 => '' or array(),
             key2 => '' or array(),
             ...
         ]

2. 如果只有一个key的操作结果，Lua脚本的返回值就无需按照key封装成数组，比如：2	 tutruew xy** File not found : true **$s(或者 array(value1, value2, ...)
本方法会将这些值直接返回

3. 如果redis中本身存的就是未做序列化的json，该方法会自己将json解析成数组返回：2	 tuarray(value1,w xy$** File not found : array(value1, **$s(value2, ...)2	 throws	 -phpDocumentor\Descriptor\Tag\ThrowsDescriptor	 $(2	 2	 3 __callG" 6H	$nameJK# 	 8  LM"$% &'(Redis指令) * +	 2	 3 
$argumentsJK# 	 /$h LM"$% &'(Redis指令参数) * +	 2	 3 "+\PG\MSF\Pools\CoroutineRedisProxy::__call()$% &'__call魔术方法() *+	Q	 R.#$Q(2	 R.#$Q(2	 S	 a	 /$h= /$b$S(2	 2	 3 generateUniqueKeyG" 	protectedH	WJK#)	 8  LM"$W% &'(&a key identifying a value to be cached) * +	 2	 3 "6\PG\MSF\Pools\CoroutineRedisProxy::generateUniqueKey()$% &'生成唯一Redis Key() *+	Q	 R.W#-$Q(2	 S	 a	 8 $S(Va key generated from the provided key which ensures the uniqueness across applications2	 2	 3 serializeHandlerG" H	$dataJK#E	 /$b LM"$% &'(待序列化数据) * +	 2	 3 $phpSerializeJK#E	 = ;LM"$% &'(是否启动PHP序列化) * +	 2	 3 "5\PG\MSF\Pools\CoroutineRedisProxy::serializeHandler()$% &'	序列化() *++	Q	 R.#I$Q(2	 R.#Q$Q(2	 S	 a	 8 $S(2	 2	 3 unSerializeHandlerG" H	JK#l	 /$b LM"$% &'(响应数据) * +	 2	 3 $keysJK#l	 /$hiLM"$% &'(操作的多个Key列表) * +	 2	 3 $lenJK#l	 \ ]LM"$% &'(操作的数量) * +	 2	 3 "7\PG\MSF\Pools\CoroutineRedisProxy::unSerializeHandler()$% &'反序列化() *R+	Q	 R.#p$Q(2	 R.#x$Q(2	 R.#$Q(2	 S	 a	 /$h= /$b$S(2	 2	 3 canUnserializeG" !H	$stringJK#	 8  LM"$% &'(待反序列化数据) * +	 2	 3 "3\PG\MSF\Pools\CoroutineRedisProxy::canUnserialize()$% &'是否可以反序列化() *+	Q	 R.#$Q(2	 S	 a	 = $S(2	 2	 3 realUnserializeG" !H	JK#	 8  LM"$% &'() * +	 2	 3 "4\PG\MSF\Pools\CoroutineRedisProxy::realUnserialize()$% &'真正反序列化() *+	Q	 R.#$Q(2	 S	 a	 /$b8 $S(2	 2	 3  * usedTraits	 "$CoroutineRedisProxy%\PG\MSF\Pools&PG\MSF\Pools'Class CoroutineRedisProxy()" *+	package	 &phpDocumentor\Descriptor\TagDescriptor$(2	 
subpackage	 2	 3  * interfaces	 	 * traits	 
 * markers	 "$CoroutineRedisProxy.php% &Default'() * +	o	 p$o(tmtbe2	 p$o(camera360_server@camera360.com2	 	copyright	 $("Chengdu pinguo Technology Co.,Ltd.2	 	 $(2	 	 2	 3 